Files
hsa/Html/apps/static/js/modelinput.js
2025-09-18 11:47:24 +00:00

116 lines
4.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ======= 工具:创建编辑按钮(含铅笔 SVG =======
function createEditButton(onClick) {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'edit-btn';
btn.setAttribute('aria-label', '编辑标题');
btn.innerHTML = `
<svg viewBox="0 0 24 24" aria-hidden="true">
<i class="fas fa-pencil-alt"></i>
</svg>
`;
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 = `
<h3 id="dialog-title">${title}</h3>
<div class="field">
<label for="title-input" class="sr-only">标题</label>
<input id="title-input" type="text" placeholder="请输入标题" value="${escapeHtmlAttr(initialValue)}" />
<div class="error" id="title-error"></div>
</div>
<div class="actions">
<button type="button" class="btn" id="cancel-btn">取消</button>
<button type="button" class="btn primary" id="ok-btn">确定</button>
</div>
`;
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,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}