// ==UserScript== // @name 提示词模板插入 // @namespace https://github.com/local/deepseek-template-insert // @version 1.3.1 // @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; // 面板固定高度 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; var dragId = null; // 缓存常用 DOM 元素 var panelEl = null; var btnEl = null; var searchEl = null; var listEl = null; var previewEl = null; // ====== 工具函数 ====== 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]; } // 检测暗色主题 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.dragging{opacity:.5}" + ".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"); // 悬浮按钮 btnEl = document.createElement("button"); btnEl.className = "dst-btn"; btnEl.type = "button"; btnEl.textContent = "📋"; btnEl.title = "提示词模板"; btnEl.addEventListener("mousedown", function (e) { e.preventDefault(); }); btnEl.addEventListener("click", togglePanel); root.appendChild(btnEl); // 面板 panelEl = document.createElement("div"); panelEl.className = "dst-panel"; panelEl.style.display = "none"; panelEl.innerHTML = '' + '
' + '
' + '
悬停查看预览
' + "
" + ''; root.appendChild(panelEl); // 缓存元素 searchEl = panelEl.querySelector(".dst-search"); listEl = panelEl.querySelector(".dst-list"); previewEl = panelEl.querySelector(".dst-preview"); // 面板内部 mousedown 防止抢焦点(搜索框除外) panelEl.addEventListener("mousedown", function (e) { if (!e.target.closest(".dst-search")) e.preventDefault(); }); // 新建模板按钮 panelEl.querySelector(".dst-create").addEventListener("click", function () { openEditor(null); }); // 搜索输入过滤 searchEl.addEventListener("input", function () { lastFilter = searchEl.value; renderList(); }); // 面板键盘导航 panelEl.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); } }); // 列表事件委托 listEl.addEventListener("click", onListClick); listEl.addEventListener("dragstart", onDragStart); listEl.addEventListener("dragover", onDragOver); listEl.addEventListener("drop", onDrop); listEl.addEventListener("dragend", onDragEnd); document.body.appendChild(root); return root; } // ====== 列表事件处理 ====== function onListClick(e) { var item = e.target.closest(".dst-item"); if (!item) return; var id = item.getAttribute("data-id"); var tpl = templates.find(function (t) { return t.id === id; }); if (!tpl) return; // 处理操作按钮 var actionBtn = e.target.closest(".dst-op-btn"); if (actionBtn) { var action = actionBtn.getAttribute("data-action"); e.stopPropagation(); // 阻止冒泡,避免触发插入 if (action === "edit") { openEditor(id); } else if (action === "delete") { openDeleteConfirm(id); } else if (action === "up") { moveTemplate(id, -1); } else if (action === "down") { moveTemplate(id, 1); } return; } // 普通点击插入 if (dragId) return; // 拖拽后的点击不触发插入 insertTemplate(tpl.content); } // 上移/下移模板 function moveTemplate(id, direction) { var index = templates.findIndex(function (t) { return t.id === id; }); if (index === -1) return; var newIndex = index + direction; if (newIndex < 0 || newIndex >= templates.length) return; var temp = templates[index]; templates[index] = templates[newIndex]; templates[newIndex] = temp; saveTemplates(); renderList(); } // 拖拽开始 function onDragStart(e) { var item = e.target.closest(".dst-item"); if (!item) return; if (e.target.closest(".dst-op-btn")) { e.preventDefault(); return; } dragId = item.getAttribute("data-id"); item.classList.add("dragging"); e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", dragId); // 必须设置,否则拖拽无法启动 } function onDragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = "move"; } function onDrop(e) { e.preventDefault(); var targetItem = e.target.closest(".dst-item"); if (!targetItem) return; var targetId = targetItem.getAttribute("data-id"); var sourceId = e.dataTransfer.getData("text/plain") || dragId; if (!sourceId || sourceId === targetId) return; var sourceIndex = templates.findIndex(function (t) { return t.id === sourceId; }); var targetIndex = templates.findIndex(function (t) { return t.id === targetId; }); if (sourceIndex === -1 || targetIndex === -1) return; var temp = templates[sourceIndex]; templates[sourceIndex] = templates[targetIndex]; templates[targetIndex] = temp; saveTemplates(); renderList(); } function onDragEnd(e) { var item = e.target.closest(".dst-item"); if (item) item.classList.remove("dragging"); dragId = null; } // ====== 列表渲染 ====== function renderList() { if (!listEl) return; 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"; item.setAttribute("data-id", t.id); item.draggable = true; 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 upBtn = document.createElement("button"); upBtn.type = "button"; upBtn.className = "dst-op-btn"; upBtn.textContent = "↑"; upBtn.title = "上移"; upBtn.setAttribute("data-action", "up"); // 下移按钮 var downBtn = document.createElement("button"); downBtn.type = "button"; downBtn.className = "dst-op-btn"; downBtn.textContent = "↓"; downBtn.title = "下移"; downBtn.setAttribute("data-action", "down"); // 编辑按钮 var editBtn = document.createElement("button"); editBtn.type = "button"; editBtn.className = "dst-op-btn"; editBtn.textContent = "✎"; editBtn.title = "编辑"; editBtn.setAttribute("data-action", "edit"); // 删除按钮 var delBtn = document.createElement("button"); delBtn.type = "button"; delBtn.className = "dst-op-btn"; delBtn.textContent = "✕"; delBtn.title = "删除"; delBtn.setAttribute("data-action", "delete"); ops.appendChild(upBtn); ops.appendChild(downBtn); ops.appendChild(editBtn); ops.appendChild(delBtn); item.appendChild(name); item.appendChild(ops); listEl.appendChild(item); // ===== 鼠标悬停更新预览和高亮 ===== item.addEventListener("mouseenter", function () { activeIndex = lastMatched.indexOf(t); // 找到该模板在过滤后列表中的索引 applyActive(); // 更新高亮并显示预览 }); }); 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 (!listEl) return; var items = listEl.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 (!listEl) return; 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 (!previewEl) return; if (content) { previewEl.textContent = content; previewEl.classList.remove("dst-placeholder"); } else { previewEl.textContent = "悬停查看预览"; previewEl.classList.add("dst-placeholder"); } } // ====== 面板开关 ====== function openPanel() { if (!root || !lastInput) return; panelOpen = true; btnEl.classList.add("active"); restoreFocusEl = document.activeElement; renderList(); listEl.scrollTop = savedListScroll; positionPanel(); panelEl.style.display = "flex"; searchEl.value = lastFilter; // 聚焦搜索框 setTimeout(function () { searchEl.focus(); }, 0); } function closePanel() { if (!root || !panelOpen) return; panelOpen = false; savedListScroll = listEl.scrollTop; panelEl.style.display = "none"; btnEl.classList.remove("active"); if (!inserting && restoreFocusEl && document.contains(restoreFocusEl)) { restoreFocusEl.focus(); } inserting = false; } function togglePanel() { if (panelOpen) closePanel(); else openPanel(); } // ====== 定位 ====== function positionButton() { if (!lastInput || !btnEl) return; var rect = lastInput.getBoundingClientRect(); if (rect.width === 0 && rect.height === 0) return; btnEl.style.left = rect.right - BTN_SIZE - 12 + "px"; btnEl.style.top = rect.top + 12 + "px"; } function positionPanel() { if (!btnEl || !panelEl) return; var brect = btnEl.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); panelEl.style.left = left + "px"; panelEl.style.bottom = bottom + "px"; } // ====== 弹窗与模板编辑 ====== 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 = ""; if (searchEl) searchEl.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; 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); ensureRoot(); root.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(); }, 500); // 防抖 500ms }); mo.observe(body, { childList: true, subtree: true }); // 主题监听 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(); } }); // 全局快捷键 document.addEventListener("keydown", function (e) { // Ctrl+Shift+P 切换面板 if (e.ctrlKey && e.shiftKey && e.key === "P") { e.preventDefault(); togglePanel(); return; } // Alt+Shift+数字 插入对应模板 if (e.altKey && e.shiftKey && !e.ctrlKey && !e.metaKey) { var num = null; if (e.code && e.code.startsWith("Digit")) { num = parseInt(e.code.slice(5), 10); } else if (e.key >= "1" && e.key <= "9") { num = parseInt(e.key, 10); } if (num >= 1 && num <= 9) { var idx = num - 1; if (templates[idx]) { e.preventDefault(); insertTemplate(templates[idx].content); } } } }); // 点击面板外关闭 document.addEventListener("click", function (e) { if (panelOpen && root && !root.contains(e.target)) closePanel(); }); } // 启动 refresh(); watch(); })();