// ======= 工具:创建编辑按钮(含铅笔 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,'>');
}