| (function () { | |
| "use strict"; | |
| const MARKDOWN_IT_CDN = | |
| "https://cdn.jsdelivr.net/npm/[email protected]/dist/markdown-it.min.js"; | |
| const HIGHLIGHT_JS_CDN = | |
| "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"; | |
| const HIGHLIGHT_LANG_GO_CDN = | |
| "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/go.min.js"; | |
| const HIGHLIGHT_LANG_RUST_CDN = | |
| "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/rust.min.js"; | |
| const HIGHLIGHT_LANG_TS_CDN = | |
| "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/typescript.min.js"; | |
| const EDITOR_BUTTON_ID = "custom-markdown-edit-btn"; | |
| const MODAL_ID = "markdown-editor-modal"; | |
| const CHAT_INPUT_SELECTOR = "#chat-input"; | |
| const VOICE_BUTTON_SELECTOR = "#voice-input-button"; | |
| let md; | |
| let hljs; | |
| let editorModal = null; | |
| let editorInput = null; | |
| let markdownPreview = null; | |
| let mainChatInput = null; | |
| let mouseDownTarget = null; | |
| function loadScript(src) { | |
| return new Promise((resolve, reject) => { | |
| if (document.querySelector(`script[src="${src}"]`)) { | |
| resolve(); | |
| return; | |
| } | |
| const script = document.createElement("script"); | |
| script.src = src; | |
| script.onload = resolve; | |
| script.onerror = reject; | |
| document.head.appendChild(script); | |
| }); | |
| } | |
| async function ensureDependencies() { | |
| try { | |
| if (typeof window.markdownit === "undefined") { | |
| await loadScript(MARKDOWN_IT_CDN); | |
| } | |
| if (typeof window.hljs === "undefined") { | |
| await loadScript(HIGHLIGHT_JS_CDN); | |
| await Promise.all([ | |
| loadScript(HIGHLIGHT_LANG_GO_CDN), | |
| loadScript(HIGHLIGHT_LANG_RUST_CDN), | |
| loadScript(HIGHLIGHT_LANG_TS_CDN), | |
| ]).catch((e) => | |
| console.warn("Could not load optional highlight.js languages:", e) | |
| ); | |
| } | |
| md = window.markdownit({ | |
| html: true, | |
| linkify: true, | |
| typographer: true, | |
| highlight: function (str, lang) { | |
| if (window.hljs) { | |
| hljs = window.hljs; | |
| if (lang && hljs.getLanguage(lang)) { | |
| try { | |
| return ( | |
| '<pre class="hljs"><code>' + | |
| hljs.highlight(str, { language: lang, ignoreIllegals: true }) | |
| .value + | |
| "</code></pre>" | |
| ); | |
| } catch (__) {} | |
| } | |
| try { | |
| return ( | |
| '<pre class="hljs"><code>' + | |
| hljs.highlightAuto(str).value + | |
| "</code></pre>" | |
| ); | |
| } catch (__) {} | |
| } | |
| return ( | |
| '<pre class="hljs"><code>' + | |
| md.utils.escapeHtml(str) + | |
| "</code></pre>" | |
| ); | |
| }, | |
| }); | |
| hljs = window.hljs; | |
| console.log("Markdown Editor Dependencies loaded."); | |
| } catch (error) { | |
| console.error("Failed to load Markdown editor dependencies:", error); | |
| } | |
| } | |
| function createModal() { | |
| if (document.getElementById(MODAL_ID)) { | |
| return document.getElementById(MODAL_ID); | |
| } | |
| const modalHTML = `<div class="editor-container"><div class="editor-header"><div class="editor-title">Markdown编辑器</div><button class="close-btn"title="关闭 (Esc)">×</button></div><div class="editor-body"><textarea class="editor-input"placeholder="在这里输入 Markdown 内容..."></textarea><div class="preview-pane"><div class="markdown-preview"></div></div></div><div class="editor-footer"><button class="apply-btn"title="应用更改 (Ctrl+Enter)">应用</button></div></div>`; | |
| const modalDiv = document.createElement("div"); | |
| modalDiv.id = MODAL_ID; | |
| modalDiv.innerHTML = modalHTML; | |
| document.body.appendChild(modalDiv); | |
| editorModal = modalDiv; | |
| editorInput = modalDiv.querySelector(".editor-input"); | |
| markdownPreview = modalDiv.querySelector(".markdown-preview"); | |
| const closeBtn = modalDiv.querySelector(".close-btn"); | |
| const applyBtn = modalDiv.querySelector(".apply-btn"); | |
| closeBtn.addEventListener("click", closeModal); | |
| applyBtn.addEventListener("click", applyChanges); | |
| editorInput.addEventListener("input", updatePreview); | |
| modalDiv.addEventListener("mousedown", (e) => { | |
| if (e.target === modalDiv) { | |
| mouseDownTarget = e.target; | |
| } else { | |
| mouseDownTarget = null; | |
| } | |
| }); | |
| modalDiv.addEventListener("mouseup", (e) => { | |
| if (e.target === modalDiv && mouseDownTarget === modalDiv) { | |
| closeModal(); | |
| } | |
| mouseDownTarget = null; | |
| }); | |
| return modalDiv; | |
| } | |
| function openModal() { | |
| if (!md || !hljs) { | |
| console.error("Markdown editor dependencies not ready."); | |
| alert("编辑器依赖未能加载,请检查网络连接或控制台错误。"); | |
| return; | |
| } | |
| if (!mainChatInput) { | |
| mainChatInput = document.querySelector(CHAT_INPUT_SELECTOR); | |
| if (!mainChatInput) { | |
| console.error("Chat input element not found when opening modal."); | |
| return; | |
| } | |
| } | |
| editorModal = createModal(); | |
| const currentText = (mainChatInput.innerText || "").trim(); | |
| editorInput.value = currentText; | |
| updatePreview(); | |
| editorModal.classList.add("active"); | |
| editorInput.focus(); | |
| } | |
| function closeModal() { | |
| if (editorModal) { | |
| editorModal.classList.remove("active"); | |
| } | |
| } | |
| function updatePreview() { | |
| if (!md || !editorInput || !markdownPreview) return; | |
| const content = editorInput.value; | |
| markdownPreview.innerHTML = md.render(content); | |
| } | |
| function applyChanges() { | |
| if (!mainChatInput || !editorInput) return; | |
| const newText = editorInput.value; | |
| mainChatInput.innerText = newText; | |
| const placeholderP = mainChatInput.querySelector("p.is-empty"); | |
| if (newText.trim() === "") { | |
| if (!placeholderP) { | |
| } | |
| } else { | |
| if (placeholderP) { | |
| } | |
| mainChatInput.classList.remove("is-editor-empty"); | |
| const parentPlaceholder = mainChatInput.querySelector("p.is-empty"); | |
| if (parentPlaceholder) parentPlaceholder.classList.remove("is-empty"); | |
| } | |
| mainChatInput.dispatchEvent( | |
| new Event("input", { bubbles: true, cancelable: true }) | |
| ); | |
| closeModal(); | |
| mainChatInput.focus(); | |
| } | |
| function injectEditorButton() { | |
| if (document.getElementById(EDITOR_BUTTON_ID)) { | |
| return; | |
| } | |
| mainChatInput = document.querySelector(CHAT_INPUT_SELECTOR); | |
| const voiceButton = document.querySelector(VOICE_BUTTON_SELECTOR); | |
| if (!mainChatInput || !voiceButton) { | |
| return; | |
| } | |
| const voiceButtonContainer = voiceButton.closest(".flex"); | |
| if (!voiceButtonContainer || !voiceButtonContainer.parentElement) { | |
| console.warn("Could not find suitable container for Markdown button."); | |
| return; | |
| } | |
| const editButton = document.createElement("button"); | |
| editButton.id = EDITOR_BUTTON_ID; | |
| editButton.className = "markdown-edit-btn"; | |
| editButton.type = "button"; | |
| editButton.title = "Markdown 编辑 (打开/关闭)"; | |
| editButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg"width="20"height="20"viewBox="0 0 24 24"fill="none"stroke="currentColor"stroke-width="2"stroke-linecap="round"stroke-linejoin="round"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path></svg>`; | |
| editButton.addEventListener("click", (e) => { | |
| e.preventDefault(); | |
| if (editorModal && editorModal.classList.contains("active")) { | |
| closeModal(); | |
| } else { | |
| openModal(); | |
| } | |
| }); | |
| voiceButtonContainer.parentElement.insertBefore( | |
| editButton, | |
| voiceButtonContainer | |
| ); | |
| console.log("Markdown Editor button injected."); | |
| } | |
| function handleKeyDown(e) { | |
| if (editorModal && editorModal.classList.contains("active")) { | |
| if (e.key === "Escape") { | |
| e.preventDefault(); | |
| closeModal(); | |
| } | |
| if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { | |
| e.preventDefault(); | |
| applyChanges(); | |
| } | |
| } | |
| } | |
| ensureDependencies() | |
| .then(() => { | |
| injectEditorButton(); | |
| const observer = new MutationObserver((mutationsList, observer) => { | |
| if ( | |
| document.querySelector(CHAT_INPUT_SELECTOR) && | |
| !document.getElementById(EDITOR_BUTTON_ID) | |
| ) { | |
| injectEditorButton(); | |
| } | |
| }); | |
| observer.observe(document.body, { childList: true, subtree: true }); | |
| document.addEventListener("keydown", handleKeyDown); | |
| console.log("Markdown Editor script initialized and observing DOM."); | |
| }) | |
| .catch((error) => { | |
| console.error("Initialization failed:", error); | |
| }); | |
| })(); |