/**
* 文档管理模块
*/
// 文档列表
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' });
}
}