Files
peoplelib/src/ui/util.js
T
lofyerandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> 3ccd044527 feat: 内置阅读器、批注笔记与 AI 助手,发布 1.3.0
新增 PDF/EPUB/MOBI/AZW 内置阅读器,PDF 分段读取支持超大文件,
批注、读书与画布笔记、封面生成与本地导入。AI 助手支持三种协议、
图像上下文与安全 Markdown 渲染,上下文范围改为 选中/当前页/全文,
页面与全文无需选中文本即可发送,全文会提示可能超出模型限制。

便携版输出目录固定为 PeopleLib-windows-x64,不再随版本号变化,
避免升级后 data/ 被遗留在旧目录。

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-08-03 12:13:02 +08:00

67 lines
2.2 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.
window.$ = (id) => document.getElementById(id);
window.escapeHtml = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
}[c]));
// 结果会被插进 style="..."cover 来自第三方接口,属不可信输入。
// 既要防 CSS 串逃逸(引号、反斜杠、括号),也要防 HTML 属性逃逸(交给 escapeHtml)。
window.coverStyle = (cover) => {
if (!cover) return '';
const raw = String(cover);
const url = /^(https?:|data:)/i.test(raw) ? raw : 'file:///' + raw.replace(/\\/g, '/');
if (/[\r\n]/.test(url)) return '';
const css = url.replace(/[\\'"()]/g, (c) => '\\' + c);
return window.escapeHtml(`background-image:url('${css}')`);
};
window.copyText = async (btn, text) => {
await window.api.copy(text);
const orig = btn.textContent;
btn.textContent = '已复制 ✓';
btn.classList.add('copied');
setTimeout(() => { btn.textContent = orig; btn.classList.remove('copied'); }, 1500);
};
window.formatDate = (ts) => {
if (!ts) return '';
const d = new Date(ts);
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
};
window.getEnabledSources = () => {
let ids = null;
try {
const raw = localStorage.getItem('enabledSources');
if (raw) ids = JSON.parse(raw);
} catch (e) { /* ignore */ }
return ids;
};
window.setEnabledSources = (ids) => {
localStorage.setItem('enabledSources', JSON.stringify(ids));
};
// 通用弹窗: 返回 Promise<{ok, values}|null>
window.openModal = (title, bodyHtml, onOk) => {
const modal = $('modal');
$('modalTitle').textContent = title;
$('modalBody').innerHTML = bodyHtml;
modal.classList.remove('hidden');
return new Promise((resolve) => {
const close = (result) => {
modal.classList.add('hidden');
$('modalOk').onclick = null;
$('modalCancel').onclick = null;
resolve(result);
};
$('modalCancel').onclick = () => close(null);
$('modalOk').onclick = async () => {
const r = onOk ? await onOk() : true;
if (r !== false) close(r);
};
});
};
window.confirmModal = (title, text) => window.openModal(title, `<p>${escapeHtml(text)}</p>`);