Create editor.js
Browse files
editor.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
(function () {
|
| 2 |
+
"use strict";
|
| 3 |
+
const MARKDOWN_IT_CDN =
|
| 4 |
+
"https://cdn.jsdelivr.net/npm/[email protected]/dist/markdown-it.min.js";
|
| 5 |
+
const HIGHLIGHT_JS_CDN =
|
| 6 |
+
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js";
|
| 7 |
+
const HIGHLIGHT_LANG_GO_CDN =
|
| 8 |
+
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/go.min.js";
|
| 9 |
+
const HIGHLIGHT_LANG_RUST_CDN =
|
| 10 |
+
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/rust.min.js";
|
| 11 |
+
const HIGHLIGHT_LANG_TS_CDN =
|
| 12 |
+
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/typescript.min.js";
|
| 13 |
+
const EDITOR_BUTTON_ID = "custom-markdown-edit-btn";
|
| 14 |
+
const MODAL_ID = "markdown-editor-modal";
|
| 15 |
+
const CHAT_INPUT_SELECTOR = "#chat-input";
|
| 16 |
+
const VOICE_BUTTON_SELECTOR = "#voice-input-button";
|
| 17 |
+
let md;
|
| 18 |
+
let hljs;
|
| 19 |
+
let editorModal = null;
|
| 20 |
+
let editorInput = null;
|
| 21 |
+
let markdownPreview = null;
|
| 22 |
+
let mainChatInput = null;
|
| 23 |
+
let mouseDownTarget = null;
|
| 24 |
+
function loadScript(src) {
|
| 25 |
+
return new Promise((resolve, reject) => {
|
| 26 |
+
if (document.querySelector(`script[src="${src}"]`)) {
|
| 27 |
+
resolve();
|
| 28 |
+
return;
|
| 29 |
+
}
|
| 30 |
+
const script = document.createElement("script");
|
| 31 |
+
script.src = src;
|
| 32 |
+
script.onload = resolve;
|
| 33 |
+
script.onerror = reject;
|
| 34 |
+
document.head.appendChild(script);
|
| 35 |
+
});
|
| 36 |
+
}
|
| 37 |
+
async function ensureDependencies() {
|
| 38 |
+
try {
|
| 39 |
+
if (typeof window.markdownit === "undefined") {
|
| 40 |
+
await loadScript(MARKDOWN_IT_CDN);
|
| 41 |
+
}
|
| 42 |
+
if (typeof window.hljs === "undefined") {
|
| 43 |
+
await loadScript(HIGHLIGHT_JS_CDN);
|
| 44 |
+
await Promise.all([
|
| 45 |
+
loadScript(HIGHLIGHT_LANG_GO_CDN),
|
| 46 |
+
loadScript(HIGHLIGHT_LANG_RUST_CDN),
|
| 47 |
+
loadScript(HIGHLIGHT_LANG_TS_CDN),
|
| 48 |
+
]).catch((e) =>
|
| 49 |
+
console.warn("Could not load optional highlight.js languages:", e)
|
| 50 |
+
);
|
| 51 |
+
}
|
| 52 |
+
md = window.markdownit({
|
| 53 |
+
html: true,
|
| 54 |
+
linkify: true,
|
| 55 |
+
typographer: true,
|
| 56 |
+
highlight: function (str, lang) {
|
| 57 |
+
if (window.hljs) {
|
| 58 |
+
hljs = window.hljs;
|
| 59 |
+
if (lang && hljs.getLanguage(lang)) {
|
| 60 |
+
try {
|
| 61 |
+
return (
|
| 62 |
+
'<pre class="hljs"><code>' +
|
| 63 |
+
hljs.highlight(str, { language: lang, ignoreIllegals: true })
|
| 64 |
+
.value +
|
| 65 |
+
"</code></pre>"
|
| 66 |
+
);
|
| 67 |
+
} catch (__) {}
|
| 68 |
+
}
|
| 69 |
+
try {
|
| 70 |
+
return (
|
| 71 |
+
'<pre class="hljs"><code>' +
|
| 72 |
+
hljs.highlightAuto(str).value +
|
| 73 |
+
"</code></pre>"
|
| 74 |
+
);
|
| 75 |
+
} catch (__) {}
|
| 76 |
+
}
|
| 77 |
+
return (
|
| 78 |
+
'<pre class="hljs"><code>' +
|
| 79 |
+
md.utils.escapeHtml(str) +
|
| 80 |
+
"</code></pre>"
|
| 81 |
+
);
|
| 82 |
+
},
|
| 83 |
+
});
|
| 84 |
+
hljs = window.hljs;
|
| 85 |
+
console.log("Markdown Editor Dependencies loaded.");
|
| 86 |
+
} catch (error) {
|
| 87 |
+
console.error("Failed to load Markdown editor dependencies:", error);
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
function createModal() {
|
| 91 |
+
if (document.getElementById(MODAL_ID)) {
|
| 92 |
+
return document.getElementById(MODAL_ID);
|
| 93 |
+
}
|
| 94 |
+
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>`;
|
| 95 |
+
const modalDiv = document.createElement("div");
|
| 96 |
+
modalDiv.id = MODAL_ID;
|
| 97 |
+
modalDiv.innerHTML = modalHTML;
|
| 98 |
+
document.body.appendChild(modalDiv);
|
| 99 |
+
editorModal = modalDiv;
|
| 100 |
+
editorInput = modalDiv.querySelector(".editor-input");
|
| 101 |
+
markdownPreview = modalDiv.querySelector(".markdown-preview");
|
| 102 |
+
const closeBtn = modalDiv.querySelector(".close-btn");
|
| 103 |
+
const applyBtn = modalDiv.querySelector(".apply-btn");
|
| 104 |
+
closeBtn.addEventListener("click", closeModal);
|
| 105 |
+
applyBtn.addEventListener("click", applyChanges);
|
| 106 |
+
editorInput.addEventListener("input", updatePreview);
|
| 107 |
+
modalDiv.addEventListener("mousedown", (e) => {
|
| 108 |
+
if (e.target === modalDiv) {
|
| 109 |
+
mouseDownTarget = e.target;
|
| 110 |
+
} else {
|
| 111 |
+
mouseDownTarget = null;
|
| 112 |
+
}
|
| 113 |
+
});
|
| 114 |
+
modalDiv.addEventListener("mouseup", (e) => {
|
| 115 |
+
if (e.target === modalDiv && mouseDownTarget === modalDiv) {
|
| 116 |
+
closeModal();
|
| 117 |
+
}
|
| 118 |
+
mouseDownTarget = null;
|
| 119 |
+
});
|
| 120 |
+
return modalDiv;
|
| 121 |
+
}
|
| 122 |
+
function openModal() {
|
| 123 |
+
if (!md || !hljs) {
|
| 124 |
+
console.error("Markdown editor dependencies not ready.");
|
| 125 |
+
alert("编辑器依赖未能加载,请检查网络连接或控制台错误。");
|
| 126 |
+
return;
|
| 127 |
+
}
|
| 128 |
+
if (!mainChatInput) {
|
| 129 |
+
mainChatInput = document.querySelector(CHAT_INPUT_SELECTOR);
|
| 130 |
+
if (!mainChatInput) {
|
| 131 |
+
console.error("Chat input element not found when opening modal.");
|
| 132 |
+
return;
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
editorModal = createModal();
|
| 136 |
+
const currentText = (mainChatInput.innerText || "").trim();
|
| 137 |
+
editorInput.value = currentText;
|
| 138 |
+
updatePreview();
|
| 139 |
+
editorModal.classList.add("active");
|
| 140 |
+
editorInput.focus();
|
| 141 |
+
}
|
| 142 |
+
function closeModal() {
|
| 143 |
+
if (editorModal) {
|
| 144 |
+
editorModal.classList.remove("active");
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
function updatePreview() {
|
| 148 |
+
if (!md || !editorInput || !markdownPreview) return;
|
| 149 |
+
const content = editorInput.value;
|
| 150 |
+
markdownPreview.innerHTML = md.render(content);
|
| 151 |
+
}
|
| 152 |
+
function applyChanges() {
|
| 153 |
+
if (!mainChatInput || !editorInput) return;
|
| 154 |
+
const newText = editorInput.value;
|
| 155 |
+
mainChatInput.innerText = newText;
|
| 156 |
+
const placeholderP = mainChatInput.querySelector("p.is-empty");
|
| 157 |
+
if (newText.trim() === "") {
|
| 158 |
+
if (!placeholderP) {
|
| 159 |
+
}
|
| 160 |
+
} else {
|
| 161 |
+
if (placeholderP) {
|
| 162 |
+
}
|
| 163 |
+
mainChatInput.classList.remove("is-editor-empty");
|
| 164 |
+
const parentPlaceholder = mainChatInput.querySelector("p.is-empty");
|
| 165 |
+
if (parentPlaceholder) parentPlaceholder.classList.remove("is-empty");
|
| 166 |
+
}
|
| 167 |
+
mainChatInput.dispatchEvent(
|
| 168 |
+
new Event("input", { bubbles: true, cancelable: true })
|
| 169 |
+
);
|
| 170 |
+
closeModal();
|
| 171 |
+
mainChatInput.focus();
|
| 172 |
+
}
|
| 173 |
+
function injectEditorButton() {
|
| 174 |
+
if (document.getElementById(EDITOR_BUTTON_ID)) {
|
| 175 |
+
return;
|
| 176 |
+
}
|
| 177 |
+
mainChatInput = document.querySelector(CHAT_INPUT_SELECTOR);
|
| 178 |
+
const voiceButton = document.querySelector(VOICE_BUTTON_SELECTOR);
|
| 179 |
+
if (!mainChatInput || !voiceButton) {
|
| 180 |
+
return;
|
| 181 |
+
}
|
| 182 |
+
const voiceButtonContainer = voiceButton.closest(".flex");
|
| 183 |
+
if (!voiceButtonContainer || !voiceButtonContainer.parentElement) {
|
| 184 |
+
console.warn("Could not find suitable container for Markdown button.");
|
| 185 |
+
return;
|
| 186 |
+
}
|
| 187 |
+
const editButton = document.createElement("button");
|
| 188 |
+
editButton.id = EDITOR_BUTTON_ID;
|
| 189 |
+
editButton.className = "markdown-edit-btn";
|
| 190 |
+
editButton.type = "button";
|
| 191 |
+
editButton.title = "Markdown 编辑 (打开/关闭)";
|
| 192 |
+
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>`;
|
| 193 |
+
editButton.addEventListener("click", (e) => {
|
| 194 |
+
e.preventDefault();
|
| 195 |
+
if (editorModal && editorModal.classList.contains("active")) {
|
| 196 |
+
closeModal();
|
| 197 |
+
} else {
|
| 198 |
+
openModal();
|
| 199 |
+
}
|
| 200 |
+
});
|
| 201 |
+
voiceButtonContainer.parentElement.insertBefore(
|
| 202 |
+
editButton,
|
| 203 |
+
voiceButtonContainer
|
| 204 |
+
);
|
| 205 |
+
console.log("Markdown Editor button injected.");
|
| 206 |
+
}
|
| 207 |
+
function handleKeyDown(e) {
|
| 208 |
+
if (editorModal && editorModal.classList.contains("active")) {
|
| 209 |
+
if (e.key === "Escape") {
|
| 210 |
+
e.preventDefault();
|
| 211 |
+
closeModal();
|
| 212 |
+
}
|
| 213 |
+
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
|
| 214 |
+
e.preventDefault();
|
| 215 |
+
applyChanges();
|
| 216 |
+
}
|
| 217 |
+
}
|
| 218 |
+
}
|
| 219 |
+
ensureDependencies()
|
| 220 |
+
.then(() => {
|
| 221 |
+
injectEditorButton();
|
| 222 |
+
const observer = new MutationObserver((mutationsList, observer) => {
|
| 223 |
+
if (
|
| 224 |
+
document.querySelector(CHAT_INPUT_SELECTOR) &&
|
| 225 |
+
!document.getElementById(EDITOR_BUTTON_ID)
|
| 226 |
+
) {
|
| 227 |
+
injectEditorButton();
|
| 228 |
+
}
|
| 229 |
+
});
|
| 230 |
+
observer.observe(document.body, { childList: true, subtree: true });
|
| 231 |
+
document.addEventListener("keydown", handleKeyDown);
|
| 232 |
+
console.log("Markdown Editor script initialized and observing DOM.");
|
| 233 |
+
})
|
| 234 |
+
.catch((error) => {
|
| 235 |
+
console.error("Initialization failed:", error);
|
| 236 |
+
});
|
| 237 |
+
})();
|