新增 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>
67 lines
2.2 KiB
JavaScript
67 lines
2.2 KiB
JavaScript
window.$ = (id) => document.getElementById(id);
|
||
|
||
window.escapeHtml = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({
|
||
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
||
}[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>`);
|