// ==UserScript==
// @name 提示词模板插入
// @namespace https://github.com/local/deepseek-template-insert
// @version 1.0.3
// @description 在 DeepSeek 会话输入框内注入提示词模板面板:搜索、预览、点击即可在光标处插入模板(修复键盘冲突)
// @author local
// @match https://chat.deepseek.com/*
// @match https://www.doubao.com/*
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-idle
// @noframes
// @license Apache-2.0
// ==/UserScript==
(function () {
"use strict";
// ====== 输入框匹配与按钮尺寸等常量配置 ======
var INPUT_SELECTOR = "[contenteditable], textarea";
var STORAGE_KEY = "ds_prompt_templates_v1";
var BTN_SIZE = 28;
var PANEL_WIDTH = 460; // 桌面端面板宽度(移动端自动缩放)
var PANEL_HEIGHT = 480; // 固定面板高度,内容变化不改变UI尺寸
var PRESET_TEMPLATES = [
{
name: "简洁回答",
content: "请直接回答用户问题,不要有过多解释,用户问题如下:",
},
];
// ====== 全局状态 ======
var templates = loadTemplates();
var lastInput = null;
var root = null;
var panelOpen = false;
var lastFilter = "";
var lastMatched = [];
var activeIndex = -1;
var savedListScroll = 0;
var sizeObs = null;
var restoreFocusEl = null; // 打开面板前聚焦的元素,关闭时恢复焦点
var inserting = false; // 标记是否正在插入模板,避免关闭面板时错误恢复焦点
// ====== 工具函数 ======
function genId() {
return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
}
function loadTemplates() {
var raw = null;
try {
raw = JSON.parse(GM_getValue(STORAGE_KEY, "null"));
} catch (e) {
raw = null;
}
if (!Array.isArray(raw)) {
raw = PRESET_TEMPLATES.map(function (t) {
return { id: genId(), name: t.name, content: t.content };
});
GM_setValue(STORAGE_KEY, JSON.stringify(raw));
return raw;
}
var before = raw.length;
raw = raw.filter(function (t) {
return t && typeof t === "object" && typeof t.name === "string" && typeof t.content === "string";
});
raw.forEach(function (t) {
if (typeof t.id !== "string" || !t.id) t.id = genId();
});
if (raw.length !== before) GM_setValue(STORAGE_KEY, JSON.stringify(raw));
return raw;
}
function saveTemplates() {
GM_setValue(STORAGE_KEY, JSON.stringify(templates));
}
// 查找当前可见的输入框:优先当前焦点元素,否则取最靠下的可见输入框
function findInput() {
var active = document.activeElement;
if (active && active.matches && active.matches(INPUT_SELECTOR)) {
var ar = active.getBoundingClientRect();
if (ar.width > 0 && ar.height > 0) return active;
}
var els = Array.prototype.slice.call(document.querySelectorAll(INPUT_SELECTOR));
var visible = els.filter(function (el) {
var rect = el.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
});
if (!visible.length) return null;
visible.sort(function (a, b) {
return b.getBoundingClientRect().bottom - a.getBoundingClientRect().bottom;
});
return visible[0];
}
// 检测暗色主题:依次检查 html、body 的 class / data 属性,最后回退到系统偏好
function detectDark() {
function check(el) {
if (!el) return null;
if (el.classList.contains("dark")) return true;
if (el.classList.contains("light")) return false;
var mode = el.getAttribute("data-mode") || el.getAttribute("data-theme");
if (mode) return mode === "dark";
return null;
}
var res = check(document.documentElement) || check(document.body);
if (res !== null) return res;
return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
}
// 注入样式(含移动端媒体查询和固定高度)
function injectStyle() {
var id = "dst-style";
if (document.getElementById(id)) return;
var style = document.createElement("style");
style.id = id;
style.textContent =
'.dst-root{position:fixed;inset:0;pointer-events:none;z-index:2147483000;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif}' +
".dst-root *{box-sizing:border-box}" +
'.dst-root[data-theme="dark"]{--bg:#1f1f1f;--surface:#2a2a2a;--hover:rgba(255,255,255,.06);--border:rgba(255,255,255,.10);--text:#ececec;--text-dim:#9a9a9a;--accent:#4d6bfe;--danger:#f87171}' +
'.dst-root[data-theme="light"]{--bg:#ffffff;--surface:#f6f6f6;--hover:rgba(0,0,0,.05);--border:rgba(0,0,0,.10);--text:#1f1f1f;--text-dim:#6b6b6b;--accent:#4d6bfe;--danger:#dc2626}' +
".dst-btn{position:fixed;width:28px;height:28px;border-radius:8px;background:transparent;border:1px solid var(--border);color:var(--text);font-size:16px;line-height:1;display:flex;align-items:center;justify-content:center;cursor:pointer;pointer-events:auto;opacity:1;transition:background .15s ease;padding:0}" +
".dst-btn:hover,.dst-btn.active{background:var(--hover)}" +
".dst-panel{position:fixed;width:min(460px, calc(100vw - 16px));height:" + PANEL_HEIGHT + "px;max-height:70vh;min-height:0;display:flex;flex-direction:column;background:var(--bg);border:1px solid var(--border);border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,.35);overflow:hidden;pointer-events:auto;animation:dstFadeIn .12s ease}" +
"@keyframes dstFadeIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}" +
".dst-search{margin:10px 10px 6px;padding:7px 10px;border-radius:6px;border:1px solid var(--border);background:var(--surface);color:var(--text);font-size:13px;outline:none}" +
".dst-search::placeholder{color:var(--text-dim)}" +
".dst-search:focus{border-color:var(--accent)}" +
".dst-body{display:flex;min-height:0;overflow:hidden;flex:1}" +
".dst-list{width:180px;overflow-y:auto;border-right:1px solid var(--border);flex-shrink:0;padding:4px 0}" +
".dst-item{padding:8px 10px;cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:4px;color:var(--text);font-size:13px}" +
".dst-item:hover{background:var(--hover)}" +
".dst-item.active{background:var(--hover);box-shadow:inset 2px 0 0 var(--accent)}" +
".dst-item-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}" +
".dst-item-ops{display:none;gap:2px;flex-shrink:0}" +
".dst-item:hover .dst-item-ops{display:inline-flex}" +
".dst-op-btn{background:none;border:none;cursor:pointer;font-size:12px;padding:2px 4px;color:var(--text-dim);line-height:1}" +
".dst-op-btn:hover{color:var(--text)}" +
".dst-empty{padding:10px;color:var(--text-dim);font-size:13px}" +
".dst-preview{flex:1;overflow-y:auto;padding:10px;white-space:pre-wrap;word-break:break-word;color:var(--text);font-size:13px;line-height:1.6}" +
".dst-preview.dst-placeholder{color:var(--text-dim)}" +
".dst-create{margin:6px 10px 10px;padding:8px;border-radius:6px;border:none;background:var(--accent);color:#fff;font-size:13px;cursor:pointer}" +
".dst-create:hover{filter:brightness(1.1)}" +
".dst-modal{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;pointer-events:auto;z-index:2147483001}" +
".dst-modal-card{width:420px;max-width:90vw;background:var(--bg);border:1px solid var(--border);border-radius:12px;box-shadow:0 8px 24px rgba(0,0,0,.35);padding:16px}" +
".dst-modal-title{font-size:15px;font-weight:600;color:var(--text);margin-bottom:12px}" +
".dst-field{margin-bottom:12px}" +
".dst-name{width:100%;padding:8px 10px;border-radius:6px;border:1px solid var(--border);background:var(--surface);color:var(--text);font-size:13px;outline:none}" +
".dst-name:focus{border-color:var(--accent)}" +
".dst-content{width:100%;height:180px;padding:8px 10px;border-radius:6px;border:1px solid var(--border);background:var(--surface);color:var(--text);font-size:13px;outline:none;resize:vertical;font-family:inherit}" +
".dst-content:focus{border-color:var(--accent)}" +
".dst-content::placeholder{color:var(--text-dim)}" +
".dst-error{color:var(--danger);font-size:12px;margin-top:4px;min-height:14px}" +
".dst-modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}" +
".dst-cancel{padding:6px 14px;border-radius:6px;border:1px solid var(--border);background:var(--surface);color:var(--text);font-size:13px;cursor:pointer}" +
".dst-save{padding:6px 14px;border-radius:6px;border:none;background:var(--accent);color:#fff;font-size:13px;cursor:pointer}" +
".dst-save:hover{filter:brightness(1.1)}" +
".dst-danger{padding:6px 14px;border-radius:6px;border:none;background:var(--danger);color:#fff;font-size:13px;cursor:pointer}" +
".dst-danger:hover{filter:brightness(1.1)}" +
".dst-confirm-text{color:var(--text);font-size:13px;margin-bottom:16px}" +
".dst-toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:var(--surface);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:8px 16px;font-size:13px;pointer-events:none;opacity:0;transition:opacity .2s ease;z-index:2147483002}" +
".dst-toast.show{opacity:1}" +
"@media (max-width:600px){.dst-panel{width:calc(100vw - 16px);height:60vh}.dst-list{width:100%;border-right:none}.dst-preview{display:none}}";
document.head.appendChild(style);
}
// 显示轻提示(用于复制成功等)
function showToast(msg, duration) {
if (!root) return;
var toast = root.querySelector(".dst-toast");
if (!toast) {
toast = document.createElement("div");
toast.className = "dst-toast";
root.appendChild(toast);
}
toast.textContent = msg;
toast.classList.add("show");
clearTimeout(toast.__timer);
toast.__timer = setTimeout(function () {
toast.classList.remove("show");
}, duration || 2000);
}
// 构建 UI 根容器
function ensureRoot() {
if (root && document.body.contains(root)) return root;
injectStyle();
root = document.createElement("div");
root.className = "dst-root";
root.setAttribute("data-theme", detectDark() ? "dark" : "light");
// 📋 按钮(放在输入框右上角)
var btn = document.createElement("button");
btn.className = "dst-btn";
btn.type = "button";
btn.textContent = "📋";
btn.title = "提示词模板";
btn.addEventListener("mousedown", function (e) {
e.preventDefault();
});
btn.addEventListener("click", function () {
togglePanel();
});
root.appendChild(btn);
// 面板
var panel = document.createElement("div");
panel.className = "dst-panel";
panel.style.display = "none";
panel.innerHTML =
'' +
'
" +
'';
root.appendChild(panel);
panel.addEventListener("mousedown", function (e) {
if (!e.target.closest(".dst-search")) e.preventDefault();
});
// 新建模板按钮
panel.querySelector(".dst-create").addEventListener("click", function () {
openEditor(null);
});
// 搜索框输入过滤
var search = panel.querySelector(".dst-search");
search.addEventListener("input", function () {
lastFilter = search.value;
renderList();
});
// 面板内部键盘导航(不再使用全局键盘监听,避免干扰编辑弹窗)
panel.addEventListener("keydown", function (e) {
if (e.key === "Escape") {
e.preventDefault();
closePanel();
return;
}
if (e.key === "ArrowDown") {
e.preventDefault();
moveActive(1);
return;
}
if (e.key === "ArrowUp") {
e.preventDefault();
moveActive(-1);
return;
}
if (e.key === "Enter" && activeIndex >= 0 && lastMatched[activeIndex]) {
e.preventDefault();
insertTemplate(lastMatched[activeIndex].content);
}
});
document.body.appendChild(root);
return root;
}
// 定位按钮:输入框右上角
function positionButton() {
if (!lastInput || !root) return;
var rect = lastInput.getBoundingClientRect();
if (rect.width === 0 && rect.height === 0) return;
var btn = root.querySelector(".dst-btn");
btn.style.left = rect.right - BTN_SIZE - 12 + "px";
btn.style.top = rect.top + 12 + "px";
}
// 定位面板:按钮上方,右对齐
function positionPanel() {
if (!root) return;
var btn = root.querySelector(".dst-btn");
var panel = root.querySelector(".dst-panel");
var brect = btn.getBoundingClientRect();
var panelWidth = Math.min(PANEL_WIDTH, window.innerWidth - 16);
var left = Math.min(brect.right - panelWidth, window.innerWidth - panelWidth - 8);
if (left < 8) left = 8;
var bottom = Math.min(window.innerHeight - brect.top + 8, window.innerHeight - 8);
panel.style.left = left + "px";
panel.style.bottom = bottom + "px";
}
// 渲染模板列表
function renderList() {
if (!root) return;
var listEl = root.querySelector(".dst-list");
listEl.innerHTML = "";
var q = lastFilter.trim().toLowerCase();
lastMatched = templates.filter(function (t) {
if (!q) return true;
return t.name.toLowerCase().indexOf(q) !== -1 || t.content.toLowerCase().indexOf(q) !== -1;
});
lastMatched.forEach(function (t) {
var item = document.createElement("div");
item.className = "dst-item";
var name = document.createElement("span");
name.className = "dst-item-name";
name.textContent = t.name;
var ops = document.createElement("span");
ops.className = "dst-item-ops";
var editBtn = document.createElement("button");
editBtn.type = "button";
editBtn.className = "dst-op-btn";
editBtn.textContent = "✎";
editBtn.title = "编辑";
var delBtn = document.createElement("button");
delBtn.type = "button";
delBtn.className = "dst-op-btn";
delBtn.textContent = "✕";
delBtn.title = "删除";
ops.appendChild(editBtn);
ops.appendChild(delBtn);
item.appendChild(name);
item.appendChild(ops);
item.addEventListener("mouseenter", function () {
activeIndex = lastMatched.indexOf(t);
applyActive();
});
item.addEventListener("click", function () {
insertTemplate(t.content);
});
editBtn.addEventListener("click", function (e) {
e.stopPropagation();
openEditor(t.id);
});
delBtn.addEventListener("click", function (e) {
e.stopPropagation();
openDeleteConfirm(t.id);
});
listEl.appendChild(item);
});
if (lastMatched.length === 0) {
var empty = document.createElement("div");
empty.className = "dst-empty";
empty.textContent = "无匹配模板";
listEl.appendChild(empty);
}
activeIndex = lastMatched.length ? 0 : -1;
applyActive();
}
function applyActive() {
if (!root) return;
var items = root.querySelectorAll(".dst-item");
for (var i = 0; i < items.length; i++) {
items[i].classList.toggle("active", i === activeIndex);
}
if (activeIndex >= 0 && lastMatched[activeIndex]) {
showPreview(lastMatched[activeIndex].content);
}
}
function moveActive(delta) {
var n = lastMatched.length;
if (!n) return;
activeIndex = (activeIndex + delta + n) % n;
applyActive();
scrollActiveIntoView();
}
function scrollActiveIntoView() {
if (!root) return;
var listEl = root.querySelector(".dst-list");
var items = listEl.querySelectorAll(".dst-item");
if (activeIndex < 0 || activeIndex >= items.length) return;
var lr = listEl.getBoundingClientRect();
var ir = items[activeIndex].getBoundingClientRect();
if (ir.top < lr.top) listEl.scrollTop -= lr.top - ir.top;
else if (ir.bottom > lr.bottom) listEl.scrollTop += ir.bottom - lr.bottom;
}
function showPreview(content) {
if (!root) return;
var preview = root.querySelector(".dst-preview");
if (content) {
preview.textContent = content;
preview.classList.remove("dst-placeholder");
} else {
preview.textContent = "悬停查看预览";
preview.classList.add("dst-placeholder");
}
}
function openPanel() {
if (!root || !lastInput) return;
var panel = root.querySelector(".dst-panel");
panelOpen = true;
root.querySelector(".dst-btn").classList.add("active");
restoreFocusEl = document.activeElement;
renderList();
root.querySelector(".dst-list").scrollTop = savedListScroll;
positionPanel();
panel.style.display = "flex";
root.querySelector(".dst-search").value = lastFilter;
// 自动聚焦搜索框,键盘导航立即可用
setTimeout(function () {
root.querySelector(".dst-search").focus();
}, 0);
}
function closePanel() {
if (!root || !panelOpen) return;
panelOpen = false;
savedListScroll = root.querySelector(".dst-list").scrollTop;
root.querySelector(".dst-panel").style.display = "none";
root.querySelector(".dst-btn").classList.remove("active");
// 恢复焦点(若插入操作已聚焦输入框则跳过)
if (!inserting && restoreFocusEl && document.contains(restoreFocusEl)) {
restoreFocusEl.focus();
}
inserting = false;
}
function togglePanel() {
if (panelOpen) closePanel();
else openPanel();
}
// ====== 弹窗与模板编辑 ======
function showModal(html) {
var modal = document.createElement("div");
modal.className = "dst-modal";
modal.innerHTML = html;
root.appendChild(modal);
modal.addEventListener("click", function (e) {
if (e.target === modal) modal.remove();
});
modal.addEventListener("keydown", function (e) {
if (e.key === "Escape") modal.remove();
});
return modal;
}
function setError(input, msg) {
var field = input.closest(".dst-field");
var err = field.querySelector(".dst-error");
err.textContent = msg;
}
function clearError(input) {
var field = input.closest(".dst-field");
var err = field.querySelector(".dst-error");
if (err) err.textContent = "";
}
function openEditor(id) {
var t = id ? templates.find(function (x) { return x.id === id; }) : null;
var modal = showModal(
'' +
'
' + (t ? "编辑模板" : "新建模板") + "
" +
'
' +
'
' +
'
' +
"
"
);
modal.__templateId = id || null;
var nameInput = modal.querySelector(".dst-name");
var contentInput = modal.querySelector(".dst-content");
if (t) {
nameInput.value = t.name;
contentInput.value = t.content;
}
nameInput.addEventListener("input", function () { clearError(nameInput); });
contentInput.addEventListener("input", function () { clearError(contentInput); });
contentInput.addEventListener("keydown", function (e) {
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
doSave(modal);
}
});
modal.querySelector(".dst-cancel").addEventListener("click", function () { modal.remove(); });
modal.querySelector(".dst-save").addEventListener("click", function () { doSave(modal); });
nameInput.focus();
}
function doSave(modal) {
var nameInput = modal.querySelector(".dst-name");
var contentInput = modal.querySelector(".dst-content");
var name = nameInput.value.trim();
var content = contentInput.value;
var ok = true;
if (!name) { setError(nameInput, "名称不能为空"); ok = false; }
if (!content) { setError(contentInput, "内容不能为空"); ok = false; }
if (!ok) return;
var id = modal.__templateId;
var newId = null;
if (id) {
var t = templates.find(function (x) { return x.id === id; });
if (t) { t.name = name; t.content = content; }
} else {
var tpl = { id: genId(), name: name, content: content };
templates.push(tpl);
newId = tpl.id;
lastFilter = "";
var search = root.querySelector(".dst-search");
if (search) search.value = "";
}
saveTemplates();
modal.remove();
renderList();
if (newId) {
activeIndex = templates.findIndex(function (x) { return x.id === newId; });
applyActive();
scrollActiveIntoView();
}
}
function openDeleteConfirm(id) {
var t = templates.find(function (x) { return x.id === id; });
if (!t) return;
var modal = showModal(
'' +
'
删除模板
' +
'
' +
'
' +
"
"
);
modal.querySelector(".dst-confirm-text").textContent = "确定删除模板「" + t.name + "」?此操作不可恢复。";
modal.querySelector(".dst-cancel").addEventListener("click", function () { modal.remove(); });
modal.querySelector(".dst-danger").addEventListener("click", function () {
templates = templates.filter(function (x) { return x.id !== id; });
saveTemplates();
modal.remove();
renderList();
});
}
// ====== 文本插入 ======
function placeCaretAtEnd(input) {
var sel = window.getSelection();
var range = document.createRange();
var walker = document.createTreeWalker(input, NodeFilter.SHOW_TEXT);
var lastText = null;
while (walker.nextNode()) lastText = walker.currentNode;
if (lastText) {
range.setStart(lastText, lastText.nodeValue.length);
range.setEnd(lastText, lastText.nodeValue.length);
} else {
var children = input.children;
var lastChild = children[children.length - 1];
if (lastChild) {
range.selectNodeContents(lastChild);
range.collapse(false);
} else {
range.selectNodeContents(input);
range.collapse(false);
}
}
sel.removeAllRanges();
sel.addRange(range);
}
function editorText(el) {
if (el.tagName === "TEXTAREA" || el.tagName === "INPUT") return el.value;
return el.innerText || el.textContent || "";
}
function insertIntoTextarea(el, content) {
var start = el.selectionStart != null ? el.selectionStart : el.value.length;
var before = el.value;
var newValue = before.slice(0, start) + content + before.slice(start);
if (newValue === before) return false;
var setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value");
if (setter && setter.set) setter.set.call(el, newValue);
else el.value = newValue;
el.selectionStart = el.selectionEnd = start + content.length;
el.dispatchEvent(new Event("input", { bubbles: true }));
return true;
}
function copyToClipboard(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(function () {
showToast("模板已复制到剪贴板,请手动粘贴");
}).catch(function () {
var ta = document.createElement("textarea");
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
showToast("模板已复制到剪贴板,请手动粘贴");
});
} else {
var ta = document.createElement("textarea");
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
showToast("模板已复制到剪贴板,请手动粘贴");
}
}
function insertTemplate(content) {
var input = findInput();
if (!input) return;
input.focus();
inserting = true; // 标记插入中,closePanel 不清除焦点
if (input.tagName === "TEXTAREA" || input.tagName === "INPUT") {
insertIntoTextarea(input, content);
input.focus();
closePanel();
return;
}
var sel = window.getSelection();
var needPlace = !sel.rangeCount || !input.contains(sel.getRangeAt(0).commonAncestorContainer);
var doInsert = function () {
if (needPlace) placeCaretAtEnd(input);
input.focus();
var before = editorText(input);
var ok = false;
try { ok = document.execCommand("insertText", false, content); } catch (e) { ok = false; }
if (!ok || editorText(input) === before) {
var ok2 = false;
try {
var dt = new DataTransfer();
dt.setData("text/plain", content);
var pe = new ClipboardEvent("paste", { clipboardData: dt, bubbles: true, cancelable: true });
input.dispatchEvent(pe);
ok2 = editorText(input) !== before;
} catch (e2) { ok2 = false; }
if (!ok2) {
// 不再手动插入文本节点,改为复制提示,保护编辑器结构
copyToClipboard(content);
}
}
input.focus();
var s3 = window.getSelection();
if (!s3.rangeCount || !input.contains(s3.getRangeAt(0).commonAncestorContainer)) {
placeCaretAtEnd(input);
}
closePanel();
};
if (needPlace) setTimeout(doInsert, 30);
else doInsert();
}
// ====== 刷新与监听 ======
function refresh() {
var input = findInput();
if (!input) {
if (root) root.style.display = "none";
return;
}
if (input !== lastInput) {
lastInput = input;
attachSizeObserver(input);
var r = ensureRoot();
r.style.display = "";
positionButton();
} else if (root) {
root.style.display = "";
positionButton();
}
}
function updateTheme() {
if (!root) return;
root.setAttribute("data-theme", detectDark() ? "dark" : "light");
}
function attachSizeObserver(input) {
if (sizeObs) {
sizeObs.disconnect();
sizeObs = null;
}
if (window.ResizeObserver) {
sizeObs = new ResizeObserver(function () { positionButton(); });
sizeObs.observe(input);
}
}
function watch() {
var body = document.body || document.documentElement;
var timer = null;
var mo = new MutationObserver(function () {
if (timer) return;
timer = setTimeout(function () {
timer = null;
refresh();
}, 300); // 用 300ms 防抖降低频率
});
mo.observe(body, { childList: true, subtree: true });
// 监听 html 和 body 主题变化
var themeMo = new MutationObserver(updateTheme);
themeMo.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class", "data-mode", "data-theme", "style"],
});
if (document.body) {
themeMo.observe(document.body, {
attributes: true,
attributeFilter: ["class", "data-mode", "data-theme", "style"],
});
}
// 系统主题切换
if (window.matchMedia) {
var mq = window.matchMedia("(prefers-color-scheme: dark)");
var themeHandler = function () { updateTheme(); };
if (mq.addEventListener) mq.addEventListener("change", themeHandler);
else if (mq.addListener) mq.addListener(themeHandler);
}
setInterval(function () {
if (!lastInput) return;
if (!document.contains(lastInput)) {
refresh();
return;
}
if (!sizeObs) attachSizeObserver(lastInput);
positionButton();
}, 3000);
window.addEventListener("scroll", function () {
if (root) {
positionButton();
if (panelOpen) positionPanel();
}
}, { passive: true });
window.addEventListener("resize", function () {
if (root) {
positionButton();
if (panelOpen) positionPanel();
}
});
// 全局快捷键:Ctrl+Shift+P 打开/关闭面板(不再处理面板导航,避免干扰其他输入)
document.addEventListener("keydown", function (e) {
if (e.ctrlKey && e.shiftKey && e.key === "P") {
e.preventDefault();
togglePanel();
}
});
// 点击面板外部关闭
document.addEventListener("click", function (e) {
if (panelOpen && root && !root.contains(e.target)) closePanel();
});
}
// 启动
refresh();
watch();
})();