// ======= 工具:创建编辑按钮(含铅笔 SVG) ======= function createEditButton(onClick) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'edit-btn'; btn.setAttribute('aria-label', '编辑标题'); btn.innerHTML = ` `; btn.addEventListener('click', (e) => { e.stopPropagation(); onClick && onClick(); }); return btn; } // ======= 模态框:打开标题编辑器 ======= function openTitleEditor({ title = '修改标题', initialValue = '', onSubmit }) { const backdrop = document.createElement('div'); backdrop.className = 'title_change_modal-backdrop'; const title_change_modal = document.createElement('div'); title_change_modal.className = 'title_change_modal'; title_change_modal.setAttribute('role', 'dialog'); title_change_modal.setAttribute('aria-title_change_modal', 'true'); title_change_modal.innerHTML = `

${title}

`; backdrop.appendChild(title_change_modal); document.body.appendChild(backdrop); const input = title_change_modal.querySelector('#title-input'); const okBtn = title_change_modal.querySelector('#ok-btn'); const cancelBtn = title_change_modal.querySelector('#cancel-btn'); const errBox = title_change_modal.querySelector('#title-error'); const prevFocus = document.activeElement; const close = () => { document.body.removeChild(backdrop); if (prevFocus && prevFocus.focus) prevFocus.focus(); }; const submit = () => { const raw = input.value; const result = onSubmit ? onSubmit(raw) : { ok: true }; if (result && result.ok) { close(); } else if (result && result.message) { errBox.textContent = result.message; input.setAttribute('aria-invalid', 'true'); input.focus(); } }; okBtn.addEventListener('click', submit); cancelBtn.addEventListener('click', close); backdrop.addEventListener('click', (e) => { if (e.target === backdrop) close(); // 点击遮罩关闭 }); title_change_modal.addEventListener('keydown', (e) => { if (e.key === 'Escape') close(); if (e.key === 'Enter') submit(); }); // 初始聚焦 setTimeout(() => input.focus(), 0); } // ======= 验证与规范化 ======= function validateTitle(raw) { if (typeof raw !== 'string') return { ok: false, message: '标题格式不正确' }; // 1) 去掉 Markdown 井号前缀(若用户手动输入了 ### ) let t = raw.replace(/^#+\s*/, ''); // 2) 替换换行为单空格(标题单行) t = t.replace(/\r?\n+/g, ' '); // 3) 去首尾空白 & 折叠多空格 t = t.trim().replace(/\s{2,}/g, ' '); // 4) 基础规则 if (t.length === 0) return { ok: false, message: '标题不能为空' }; if (t.length > 80) return { ok: false, message: '标题不能超过 80 个字符' }; // 5) 可选:禁止只包含标点 if (!/[0-9A-Za-z\u4e00-\u9fa5]/.test(t)) { return { ok: false, message: '标题需包含字母/数字/中文' }; } return { ok: true, value: t }; } // 显示时的规范化(比如初始渲染) function normalizeTitle(s) { if (!s) return ''; return s.replace(/^#+\s*/, '').replace(/\r?\n+/g, ' ').trim().replace(/\s{2,}/g, ' '); } // 简单转义,避免把初始值插进 input 时破坏属性 function escapeHtmlAttr(s) { return String(s).replace(/&/g,'&').replace(/"/g,'"').replace(//g,'>'); }