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>
This commit is contained in:
lofyer
2026-08-03 12:13:02 +08:00
co-authored by factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
parent b8c8d24107
commit 3ccd044527
307 changed files with 98477 additions and 1148 deletions
+96
View File
@@ -0,0 +1,96 @@
(() => {
const MarkdownIt = window.markdownit;
const purifier = window.DOMPurify;
const MAX_MARKDOWN_LENGTH = 256 * 1024;
function safeExternalUrl(value) {
const raw = String(value || '').trim();
if (!/^https?:\/\//i.test(raw)) return '';
try {
const url = new URL(raw);
if (!/^https?:$/.test(url.protocol) || url.username || url.password) return '';
return url.toString();
} catch (error) {
return '';
}
}
if (typeof MarkdownIt !== 'function' || !purifier || typeof purifier.sanitize !== 'function') {
window.AiMarkdown = Object.freeze({
available: false,
mount(root, source) {
root.classList.add('ai-output-plain');
root.textContent = String(source || '');
},
externalUrl() {
return '';
}
});
return;
}
const markdown = new MarkdownIt({
html: false,
breaks: true,
linkify: true,
typographer: false
});
markdown.renderer.rules.link_open = (tokens, index, options, env, renderer) => {
const token = tokens[index];
const url = safeExternalUrl(token.attrGet('href'));
token.attrSet('href', '#');
if (url) {
token.attrSet('data-external-url', url);
token.attrSet('rel', 'noopener noreferrer');
} else {
token.attrJoin('class', 'ai-md-link-blocked');
token.attrSet('aria-disabled', 'true');
}
return renderer.renderToken(tokens, index, options);
};
markdown.renderer.rules.image = (tokens, index) => {
const alt = markdown.utils.escapeHtml(String(tokens[index].content || '').trim());
const label = alt ? `图片:${alt}` : '外部图片已阻止';
return `<span class="ai-md-image-placeholder" role="note">[${label}]</span>`;
};
const sanitizeOptions = Object.freeze({
ALLOWED_TAGS: [
'p', 'br', 'strong', 'em', 's', 'blockquote', 'pre', 'code',
'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'table', 'thead', 'tbody', 'tr', 'th', 'td', 'hr', 'a', 'span'
],
ALLOWED_ATTR: [
'href', 'title', 'class', 'rel', 'role', 'aria-disabled', 'data-external-url'
],
ALLOW_DATA_ATTR: true,
ALLOW_ARIA_ATTR: true
});
function render(source) {
return purifier.sanitize(markdown.render(String(source || '')), sanitizeOptions);
}
window.AiMarkdown = Object.freeze({
available: true,
render,
mount(root, source) {
const text = String(source || '');
if (text.length > MAX_MARKDOWN_LENGTH) {
root.classList.add('ai-output-plain');
root.textContent = text;
return;
}
root.classList.remove('ai-output-plain');
root.innerHTML = render(text);
},
externalUrl(target) {
const link = target && typeof target.closest === 'function'
? target.closest('a[data-external-url]')
: null;
return link ? safeExternalUrl(link.getAttribute('data-external-url')) : '';
}
});
})();