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 |
+
})();
|