/** * 文档管理模块 */ // 文档列表 let documents = []; // 当前正在编辑的文档 let currentDocument = null; // Markdown编辑器实例 let editorMd = null; // 创建documentManager对象 const documentManager = { // 初始化文档管理 init: function() { // console.log('初始化文档管理模块...'); // 渲染表头 this.renderDocumentTableHeader(); // 加载文档列表 return this.loadDocuments().catch(err => { // console.error('加载文档列表失败:', err); return Promise.resolve(); // 即使失败也继续初始化过程 }); }, // 渲染文档表格头部 renderDocumentTableHeader: function() { try { const documentTable = document.getElementById('documentTable'); if (!documentTable) { // console.warn('文档管理表格元素 (id=\"documentTable\") 未找到,无法渲染表头。'); return; } // 查找或创建 thead let thead = documentTable.querySelector('thead'); if (!thead) { thead = document.createElement('thead'); documentTable.insertBefore(thead, documentTable.firstChild); // 确保 thead 在 tbody 之前 // console.log('创建了文档表格的 thead 元素。'); } // 设置表头内容 (包含 ID 列) thead.innerHTML = ` # 标题 创建时间 更新时间 状态 操作 `; // console.log('文档表格表头已渲染。'); } catch (error) { // console.error('渲染文档表格表头时出错:', error); } }, // 加载文档列表 loadDocuments: async function() { try { // 显示加载状态 const documentTableBody = document.getElementById('documentTableBody'); if (documentTableBody) { documentTableBody.innerHTML = ' 正在加载文档列表...'; } // 简化会话检查逻辑,只验证会话是否有效 let sessionValid = true; try { const sessionResponse = await fetch('/api/check-session', { headers: { 'Cache-Control': 'no-cache', 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin' }); if (sessionResponse.status === 401) { // console.warn('会话已过期,无法加载文档'); sessionValid = false; } } catch (sessionError) { // console.warn('检查会话状态发生网络错误:', sessionError); // 发生网络错误时继续尝试加载文档 } // 尝试不同的API路径 const possiblePaths = [ '/api/documents', '/api/documentation-list', '/api/documentation' ]; let success = false; let authError = false; for (const path of possiblePaths) { try { // console.log(`尝试从 ${path} 获取文档列表`); const response = await fetch(path, { credentials: 'same-origin', headers: { 'Cache-Control': 'no-cache', 'X-Requested-With': 'XMLHttpRequest' } }); if (response.status === 401) { // console.warn(`API路径 ${path} 返回未授权状态`); authError = true; continue; } if (response.ok) { const data = await response.json(); documents = Array.isArray(data) ? data : []; // 确保每个文档都包含必要的时间字段 documents = documents.map(doc => { if (!doc.createdAt && doc.updatedAt) { // 如果没有创建时间但有更新时间,使用更新时间 doc.createdAt = doc.updatedAt; } else if (!doc.createdAt) { // 如果都没有,使用当前时间 doc.createdAt = new Date().toISOString(); } if (!doc.updatedAt) { // 如果没有更新时间,使用创建时间 doc.updatedAt = doc.createdAt; } return doc; }); // 先渲染表头,再渲染文档列表 this.renderDocumentTableHeader(); this.renderDocumentList(); // console.log(`成功从API路径 ${path} 加载文档列表`, documents); success = true; break; } } catch (e) { // console.warn(`从 ${path} 加载文档失败:`, e); } } // 处理认证错误 - 只有当会话检查和API请求都明确失败时才强制登出 if ((authError || !sessionValid) && !success && localStorage.getItem('isLoggedIn') === 'true') { // console.warn('会话检查和API请求均指示会话已过期'); core.showAlert('会话已过期,请重新登录', 'warning'); setTimeout(() => { localStorage.removeItem('isLoggedIn'); window.location.reload(); }, 1500); return; } // 如果API请求失败但不是认证错误,显示空文档列表 if (!success) { // console.log('API请求失败,显示空文档列表'); documents = []; // 仍然需要渲染表头 this.renderDocumentTableHeader(); this.renderDocumentList(); } } catch (error) { // console.error('加载文档失败:', error); // 在UI上显示错误 const documentTableBody = document.getElementById('documentTableBody'); if (documentTableBody) { documentTableBody.innerHTML = `加载文档失败: ${error.message} `; } // 设置为空文档列表 documents = []; } }, // 初始化编辑器 initEditor: function() { try { const editorContainer = document.getElementById('editor'); if (!editorContainer) { // console.error('找不到编辑器容器元素'); return; } // 检查 toastui 是否已加载 // console.log('检查编辑器依赖项:', typeof toastui); // 确保 toastui 对象存在 if (typeof toastui === 'undefined') { // console.error('Toast UI Editor 未加载'); return; } // 创建编辑器实例 editorMd = new toastui.Editor({ el: editorContainer, height: '600px', initialValue: '', previewStyle: 'vertical', initialEditType: 'markdown', toolbarItems: [ ['heading', 'bold', 'italic', 'strike'], ['hr', 'quote'], ['ul', 'ol', 'task', 'indent', 'outdent'], ['table', 'image', 'link'], ['code', 'codeblock'] ] }); // console.log('编辑器初始化完成', editorMd); } catch (error) { // console.error('初始化编辑器出错:', error); core.showAlert('初始化编辑器失败: ' + error.message, 'error'); } }, // 检查编辑器是否已初始化 isEditorInitialized: function() { return editorMd !== null; }, // 创建新文档 newDocument: function() { // 首先确保编辑器已初始化 if (!editorMd) { this.initEditor(); // 等待编辑器初始化完成后再继续 setTimeout(() => { currentDocument = null; document.getElementById('documentTitle').value = ''; editorMd.setMarkdown(''); this.showEditor(); }, 500); } else { currentDocument = null; document.getElementById('documentTitle').value = ''; editorMd.setMarkdown(''); this.showEditor(); } }, // 显示编辑器 showEditor: function() { document.getElementById('documentTable').style.display = 'none'; document.getElementById('editorContainer').style.display = 'block'; if (editorMd) { // 确保每次显示编辑器时都切换到编辑模式 editorMd.focus(); } }, // 隐藏编辑器 hideEditor: function() { document.getElementById('documentTable').style.display = 'table'; document.getElementById('editorContainer').style.display = 'none'; }, // 取消编辑 cancelEdit: function() { this.hideEditor(); }, // 保存文档 saveDocument: async function() { const title = document.getElementById('documentTitle').value.trim(); const content = editorMd.getMarkdown(); if (!title) { core.showAlert('请输入文档标题', 'error'); return; } // 显示保存中状态 core.showLoading(); try { // 简化会话检查逻辑,只验证会话是否有效 let sessionValid = true; try { const sessionResponse = await fetch('/api/check-session', { headers: { 'Cache-Control': 'no-cache', 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin' }); if (sessionResponse.status === 401) { // console.warn('会话已过期,无法保存文档'); sessionValid = false; } } catch (sessionError) { // console.warn('检查会话状态发生网络错误:', sessionError); // 发生网络错误时继续尝试保存操作 } // 只有在会话明确无效时才退出 if (!sessionValid) { core.showAlert('您的会话已过期,请重新登录', 'warning'); setTimeout(() => { localStorage.removeItem('isLoggedIn'); window.location.reload(); }, 1500); return; } // 确保Markdown内容以标题开始 let processedContent = content; if (!content.startsWith('# ')) { // 如果内容不是以一级标题开始,则在开头添加标题 processedContent = `# ${title}\n\n${content}`; } else { // 如果已经有一级标题,替换为当前标题 processedContent = content.replace(/^# .*$/m, `# ${title}`); } const apiUrl = currentDocument && currentDocument.id ? `/api/documents/${currentDocument.id}` : '/api/documents'; const method = currentDocument && currentDocument.id ? 'PUT' : 'POST'; // console.log(`尝试${method === 'PUT' ? '更新' : '创建'}文档,标题: ${title}`); const response = await fetch(apiUrl, { method: method, headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin', body: JSON.stringify({ title, content: processedContent, published: currentDocument && currentDocument.published ? currentDocument.published : false }) }); // 处理响应 if (response.status === 401) { // 明确的未授权响应 // console.warn('保存文档返回401未授权'); core.showAlert('未登录或会话已过期,请重新登录', 'warning'); setTimeout(() => { localStorage.removeItem('isLoggedIn'); window.location.reload(); }, 1500); return; } if (!response.ok) { const errorText = await response.text(); let errorData; try { errorData = JSON.parse(errorText); } catch (e) { // 如果不是有效的JSON,直接使用文本 throw new Error(errorText || '保存失败,请重试'); } throw new Error(errorData.error || errorData.message || '保存失败,请重试'); } const savedDoc = await response.json(); // console.log('保存的文档:', savedDoc); // 确保savedDoc包含必要的时间字段 if (savedDoc) { // 如果返回的保存文档中没有时间字段,从API获取完整文档信息 if (!savedDoc.createdAt || !savedDoc.updatedAt) { try { const docId = savedDoc.id || (currentDocument ? currentDocument.id : null); if (docId) { const docResponse = await fetch(`/api/documents/${docId}`, { headers: { 'Cache-Control': 'no-cache' }, credentials: 'same-origin' }); if (docResponse.ok) { const fullDoc = await docResponse.json(); Object.assign(savedDoc, { createdAt: fullDoc.createdAt, updatedAt: fullDoc.updatedAt }); // console.log('获取到完整的文档时间信息:', fullDoc); } } } catch (timeError) { // console.warn('获取文档完整时间信息失败:', timeError); } } // 更新文档列表中的文档 const existingIndex = documents.findIndex(d => d.id === savedDoc.id); if (existingIndex >= 0) { documents[existingIndex] = { ...documents[existingIndex], ...savedDoc }; } else { documents.push(savedDoc); } } core.showAlert('文档保存成功', 'success'); this.hideEditor(); await this.loadDocuments(); // 重新加载文档列表 } catch (error) { // console.error('保存文档失败:', error); core.showAlert('保存文档失败: ' + error.message, 'error'); } finally { core.hideLoading(); } }, // 渲染文档列表 renderDocumentList: function() { const tbody = document.getElementById('documentTableBody'); tbody.innerHTML = ''; if (documents.length === 0) { tbody.innerHTML = '没有找到文档'; return; } documents.forEach((doc, index) => { // 确保文档时间有合理默认值 let createdAt = '未知'; let updatedAt = '未知'; try { // 尝试解析创建时间,如果失败则回退到默认值 if (doc.createdAt) { const createdDate = new Date(doc.createdAt); if (!isNaN(createdDate.getTime())) { createdAt = createdDate.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); } } // 尝试解析更新时间,如果失败则回退到默认值 if (doc.updatedAt) { const updatedDate = new Date(doc.updatedAt); if (!isNaN(updatedDate.getTime())) { updatedAt = updatedDate.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); } } // 简化时间显示逻辑 - 直接显示时间戳,不添加特殊标记 if (createdAt === '未知' && updatedAt !== '未知') { // 如果没有创建时间但有更新时间 createdAt = '未记录'; } else if (updatedAt === '未知' && createdAt !== '未知') { // 如果没有更新时间但有创建时间 updatedAt = '未更新'; } } catch (error) { // console.warn(`解析文档时间失败:`, error, doc); } const statusClasses = doc.published ? 'status-badge status-running' : 'status-badge status-stopped'; const statusText = doc.published ? '已发布' : '未发布'; const row = document.createElement('tr'); row.innerHTML = ` ${index + 1} ${doc.title || '无标题文档'} ${createdAt} ${updatedAt} ${statusText} `; tbody.appendChild(row); }); }, // 编辑文档 editDocument: async function(id) { try { // console.log(`准备编辑文档,ID: ${id}`); core.showLoading('加载文档中...'); // 更明确的加载提示 // 会话检查逻辑保持不变 let sessionValid = true; try { const sessionResponse = await fetch('/api/check-session', { headers: { 'Cache-Control': 'no-cache', 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin' }); if (sessionResponse.status === 401) sessionValid = false; } catch (sessionError) { // console.warn('检查会话状态发生网络错误:', sessionError); // 继续尝试,API调用时会再次处理401 } if (!sessionValid) { core.showAlert('您的会话已过期,请重新登录', 'warning'); auth.showLoginModal(); // 使用 auth 模块显示登录 core.hideLoading(); return; } // 不再依赖本地缓存的列表项获取content,始终从API获取完整文档 // console.log('始终从API获取完整文档详情进行编辑,ID:', id); const response = await fetch(`/api/documents/${id}`, { headers: { 'Cache-Control': 'no-cache', // 确保获取最新数据 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin' }); if (response.status === 401) { core.showAlert('您的会话已过期或无权限访问此文档,请重新登录', 'warning'); auth.showLoginModal(); core.hideLoading(); return; } if (!response.ok) { const errorText = await response.text(); // console.error(`获取文档失败 (${response.status}):`, errorText); throw new Error(`获取文档内容失败: ${errorText || response.status}`); } const docToEdit = await response.json(); currentDocument = docToEdit; // 更新当前编辑的文档对象 // 确保编辑器已初始化 if (!editorMd) { this.initEditor(); // 等待编辑器初始化完成后再继续 // 使用短延时确保编辑器DOM完全准备好 await new Promise(resolve => setTimeout(resolve, 100)); } if (!editorMd) { core.showAlert('编辑器初始化失败,无法编辑文档。', 'error'); core.hideLoading(); return; } document.getElementById('documentTitle').value = docToEdit.title || ''; editorMd.setMarkdown(docToEdit.content || ''); this.showEditor(); } catch (error) { // console.error('编辑文档时出错:', error); core.showAlert(`加载文档进行编辑失败: ${error.message}`, 'error'); } finally { core.hideLoading(); } }, // 查看文档 viewDocument: function(id) { const doc = documents.find(doc => doc.id === id); if (!doc) { core.showAlert('未找到指定的文档', 'error'); return; } Swal.fire({ title: doc.title, html: `
${marked.parse(doc.content || '')}
`, width: '70%', showCloseButton: true, showConfirmButton: false, customClass: { container: 'document-preview-container', popup: 'document-preview-popup', content: 'document-preview-content' } }); }, // 删除文档 deleteDocument: async function(id) { Swal.fire({ title: '确定要删除此文档吗?', text: "此操作无法撤销!", icon: 'warning', showCancelButton: true, confirmButtonColor: '#d33', cancelButtonColor: '#3085d6', confirmButtonText: '是,删除它', cancelButtonText: '取消' }).then(async (result) => { if (result.isConfirmed) { try { // console.log(`尝试删除文档: ${id}`); // 检查会话状态 const sessionResponse = await fetch('/api/check-session', { headers: { 'Cache-Control': 'no-cache' } }); if (sessionResponse.status === 401) { // 会话已过期,提示用户并重定向到登录 core.showAlert('您的会话已过期,请重新登录', 'warning'); setTimeout(() => { localStorage.removeItem('isLoggedIn'); window.location.reload(); }, 1500); return; } // 使用正确的API路径删除 const response = await fetch(`/api/documents/${id}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin' // 确保发送cookie }); if (response.status === 401) { // 处理未授权错误 core.showAlert('未登录或会话已过期,请重新登录', 'warning'); setTimeout(() => { localStorage.removeItem('isLoggedIn'); window.location.reload(); }, 1500); return; } if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || '删除文档失败'); } // console.log('文档删除成功响应:', await response.json()); core.showAlert('文档已成功删除', 'success'); await this.loadDocuments(); // 重新加载文档列表 } catch (error) { // console.error('删除文档失败:', error); core.showAlert('删除文档失败: ' + error.message, 'error'); } } }); }, // 切换文档发布状态 togglePublish: async function(id) { try { const doc = documents.find(d => d.id === id); if (!doc) { throw new Error('找不到指定文档'); } // 添加加载指示 core.showLoading(); // 检查会话状态 const sessionResponse = await fetch('/api/check-session', { headers: { 'Cache-Control': 'no-cache' } }); if (sessionResponse.status === 401) { // 会话已过期,提示用户并重定向到登录 core.showAlert('您的会话已过期,请重新登录', 'warning'); setTimeout(() => { localStorage.removeItem('isLoggedIn'); window.location.reload(); }, 1500); return; } // console.log(`尝试切换文档 ${id} 的发布状态,当前状态:`, doc.published); // 构建更新请求数据 const updateData = { id: doc.id, title: doc.title, published: !doc.published // 切换发布状态 }; // 使用正确的API端点进行更新 const response = await fetch(`/api/documentation/toggle-publish/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin', // 确保发送cookie body: JSON.stringify(updateData) }); if (response.status === 401) { // 处理未授权错误 core.showAlert('未登录或会话已过期,请重新登录', 'warning'); setTimeout(() => { localStorage.removeItem('isLoggedIn'); window.location.reload(); }, 1500); return; } if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || '更新文档状态失败'); } // 更新成功 const updatedDoc = await response.json(); // console.log('文档状态更新响应:', updatedDoc); // 更新本地文档列表 const docIndex = documents.findIndex(d => d.id === id); if (docIndex >= 0) { documents[docIndex].published = updatedDoc.published; this.renderDocumentList(); } core.showAlert('文档状态已更新', 'success'); } catch (error) { // console.error('更改发布状态失败:', error); core.showAlert('更改发布状态失败: ' + error.message, 'error'); } finally { core.hideLoading(); } } }; // 全局公开文档管理模块 window.documentManager = documentManager; /** * 显示指定文档的内容 * @param {string} docId 文档ID */ async function showDocument(docId) { try { // console.log('正在获取文档内容,ID:', docId); // 显示加载状态 const documentContent = document.getElementById('documentContent'); if (documentContent) { documentContent.innerHTML = '
正在加载文档内容...
'; } // 获取文档内容 const response = await fetch(`/api/documentation/${docId}`); if (!response.ok) { throw new Error(`获取文档内容失败,状态码: ${response.status}`); } const doc = await response.json(); // console.log('获取到文档:', doc); // 更新文档内容区域 if (documentContent) { if (doc.content) { // 使用marked渲染markdown内容 documentContent.innerHTML = `

${doc.title || '无标题'}

${doc.lastUpdated ? `
最后更新: ${new Date(doc.lastUpdated).toLocaleDateString('zh-CN')}
` : ''}
${window.marked ? marked.parse(doc.content) : doc.content}
`; } else { documentContent.innerHTML = `

${doc.title || '无标题'}

该文档暂无内容

`; } } else { // console.error('找不到文档内容容器,ID: documentContent'); } // 高亮当前选中的文档 highlightSelectedDocument(docId); } catch (error) { // console.error('获取文档内容失败:', error); // 显示错误信息 const documentContent = document.getElementById('documentContent'); if (documentContent) { documentContent.innerHTML = `

加载失败

无法获取文档内容: ${error.message}

`; } } } /** * 高亮选中的文档 * @param {string} docId 文档ID */ function highlightSelectedDocument(docId) { // 移除所有高亮 const docLinks = document.querySelectorAll('.doc-list .doc-item'); docLinks.forEach(link => link.classList.remove('active')); // 添加当前高亮 const selectedLink = document.querySelector(`.doc-list .doc-item[data-id="${docId}"]`); if (selectedLink) { selectedLink.classList.add('active'); // 确保选中项可见 selectedLink.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } }