/>

Dozzon 文章草稿编辑器

← 阅读
就绪
HTML 源代码 0 行
1
实时预览
${inner}

常见问题

编辑器页面用途是什么?

用于撰写与排版 Dozzon 内容资产,统一结构与 SEO/GEO 元信息规范。

内容发布有什么规范?

需完整骨架、合法结构化数据、闭合标签与无宣传性相当化表述,符合搜索引擎与平台规范。

源头工厂直供 · 支持 OEM/ODM 定制 · 7 天样机试用
`); doc.close(); } // ---- Stats ---- function updateStats() { const code = $("edTextarea").value; // Strip HTML tags const text = code .replace(//gi, "") .replace(//gi, "") .replace(/<[^>]+>/g, "") .replace(/&\w+;/g, " ") .replace(/\s+/g, " ").trim(); const chars = text.length; const cnChars = (text.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []).length; const enWords = (text.match(/[a-zA-Z]+/g) || []).length; const totalWords = cnChars + enWords; const readingTime = Math.max(1, Math.ceil(totalWords / 400)); const headings = (code.match(/]*>/gi) || []).length; const images = (code.match(/image]*>/gi) || []).length; $("ftChars").textContent = chars; $("ftWords").textContent = cnChars; $("ftReadingTime").textContent = readingTime; $("ftHeadings").textContent = headings; $("ftImages").textContent = images; $("edPreviewInfo").textContent = cnChars + " 字 · " + readingTime + " 分钟"; } // ---- Insert Snippets ---- function insertTag(tag) { const ta = $("edTextarea"); const start = ta.selectionStart, end = ta.selectionEnd; const sel = ta.value.substring(start, end); const wrapped = "<" + tag + ">" + (sel || "") + ""; ta.setRangeText(wrapped, start, end, "select"); ta.focus(); onInput(); } function insertHeading(level) { const ta = $("edTextarea"); const start = ta.selectionStart; let lineStart = ta.value.lastIndexOf("\n", start - 1) + 1; let lineEnd = ta.value.indexOf("\n", start); if (lineEnd === -1) lineEnd = ta.value.length; const line = ta.value.substring(lineStart, lineEnd); // Remove existing heading tags const cleaned = line.replace(/^]*>/, "").replace(/<\/h\d>$/, ""); const wrapped = "" + cleaned + ""; ta.setRangeText(wrapped, lineStart, lineEnd, "end"); ta.focus(); onInput(); } function insertSnippet(type) { const ta = $("edTextarea"); const pos = ta.selectionStart; const snippets = { link: '链接文字', image: '描述', table: '\n\n\n
列A列B
数据1数据2
', blockquote: '
\n

引用内容

\n
', }; ta.setRangeText(snippets[type] || "", pos, pos, "end"); ta.focus(); onInput(); } // ---- Save ---- async function saveArticle() { if (!currentFilename) { showToast("请先选择一篇文章"); return; } const content = $("edTextarea").value; try { const resp = await fetch(API_ARTICLES + "/" + encodeURIComponent(currentFilename), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ content }), }); if (!resp.ok) { const err = await resp.json().catch(() => ({})); throw new Error(err.error || "HTTP " + resp.status); } lastSavedContent = content; isDirty = false; updateSaveStatus("已保存"); clearDraftForFile(currentFilename); showToast("✅ 已保存到服务器 — " + currentFilename); } catch (e) { showToast("❌ 保存失败: " + e.message); } } // ---- Draft ---- async function saveDraft() { if (!currentFilename) { showToast("请先选择一篇文章"); return; } const content = $("edTextarea").value; const draftId = "draft_" + currentFilename.replace(/[.\s]/g, "_"); try { const resp = await fetch(API_DRAFTS + "/" + draftId, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title: currentFilename, content: content, sourceFile: currentFilename, }), }); if (!resp.ok) throw new Error("HTTP " + resp.status); localStorage.setItem("dozzon_editor_draft_file", currentFilename); localStorage.setItem("dozzon_editor_draft_content", content); localStorage.setItem("dozzon_editor_draft_time", Date.now()); showToast("✅ 草稿已保存"); } catch (e) { // Fallback: local-only localStorage.setItem("dozzon_editor_draft_file", currentFilename); localStorage.setItem("dozzon_editor_draft_content", content); localStorage.setItem("dozzon_editor_draft_time", Date.now()); showToast("⚠ 仅保存到本地 (服务器不可用)"); } } function clearDraftForFile(filename) { const draftFile = localStorage.getItem("dozzon_editor_draft_file"); if (draftFile === filename) { localStorage.removeItem("dozzon_editor_draft_file"); localStorage.removeItem("dozzon_editor_draft_content"); localStorage.removeItem("dozzon_editor_draft_time"); } } // ---- Auto-Save ---- function startAutoSave() { setInterval(() => { if (!isDirty || !currentFilename) return; const content = $("edTextarea").value; localStorage.setItem("dozzon_editor_draft_file", currentFilename); localStorage.setItem("dozzon_editor_draft_content", content); localStorage.setItem("dozzon_editor_draft_time", Date.now()); updateSaveStatus("已自动保存"); console.log("[Editor] Auto-saved", new Date().toLocaleTimeString()); }, 30000); } // ---- Draft Recovery ---- function checkDraftRecovery() { const draftContent = localStorage.getItem("dozzon_editor_draft_content"); const draftFile = localStorage.getItem("dozzon_editor_draft_file"); const draftTime = localStorage.getItem("dozzon_editor_draft_time"); if (!draftContent || !draftFile || !draftTime) return; const elapsed = Math.round((Date.now() - parseInt(draftTime)) / 60000); const fileLabel = formatScenario(draftFile.replace(/^dozzon-/, "").replace("-v2", "").replace(/\.html$/, "")); $("draftModalDesc").textContent = "文件: " + fileLabel + " · " + elapsed + " 分钟前"; $("draftModalOverlay").style.display = "flex"; } function recoverDraft() { const filename = localStorage.getItem("dozzon_editor_draft_file"); const content = localStorage.getItem("dozzon_editor_draft_content"); if (filename) { currentFilename = filename; $("edTextarea").value = content || ""; $("edFileName").textContent = filename; $("edFileSelect").value = filename; lastSavedContent = content || ""; updateLineNumbers(); updatePreview(); updateStats(); updateSaveStatus("草稿已恢复"); } $("draftModalOverlay").style.display = "none"; } function discardDraft() { localStorage.removeItem("dozzon_editor_draft_file"); localStorage.removeItem("dozzon_editor_draft_content"); localStorage.removeItem("dozzon_editor_draft_time"); $("draftModalOverlay").style.display = "none"; } // ---- Preview Toggle ---- function togglePreview() { $("edPreviewPane").classList.toggle("ed-hidden"); $("edCodePane").classList.toggle("ed-full"); const hidden = $("edPreviewPane").classList.contains("ed-hidden"); $("btnTogglePreview").textContent = hidden ? "📱 显示预览" : "📱 隐藏预览"; document.querySelectorAll(".ed-resizer").forEach(r => r.style.display = hidden ? "none" : ""); } // ---- Resizer (Drag to resize panes) ---- function setupResizer() { const resizer = $("edResizer"); const codePane = $("edCodePane"); let startX, startW; resizer.addEventListener("mousedown", e => { startX = e.clientX; startW = codePane.getBoundingClientRect().width; document.addEventListener("mousemove", onDrag); document.addEventListener("mouseup", onDragEnd); document.body.style.cursor = "col-resize"; document.body.style.userSelect = "none"; }); function onDrag(e) { const dx = e.clientX - startX; const w = Math.max(300, Math.min(window.innerWidth - 360, startW + dx)); codePane.style.flex = "none"; codePane.style.width = w + "px"; } function onDragEnd() { document.removeEventListener("mousemove", onDrag); document.removeEventListener("mouseup", onDragEnd); document.body.style.cursor = ""; document.body.style.userSelect = ""; } } // ---- Scroll Sync (line numbers + preview) ---- function syncScroll() { $("edLineNumbers").scrollTop = $("edTextarea").scrollTop; } // ---- Keyboard Shortcuts ---- function onKeyDown(e) { if (e.ctrlKey && e.key === "s") { e.preventDefault(); saveArticle(); } if (e.ctrlKey && e.key === "b") { e.preventDefault(); insertTag("strong"); } if (e.ctrlKey && e.key === "i") { e.preventDefault(); insertTag("em"); } } // ---- Helpers ---- function updateSaveStatus(text) { $("edSaveStatus").textContent = text; $("edSaveStatus").className = "ed-save-status"; if (text.includes("未保存")) $("edSaveStatus").classList.add("ed-status-dirty"); if (text.includes("已保存") || text.includes("自动保存")) $("edSaveStatus").classList.add("ed-status-ok"); } function formatScenario(s) { const map = { "commercial":"商业广场","office":"办公场景","hotel":"酒店场景", "gym":"健身房","hospital":"医院","university":"大学校园", "export":"出海","procurement":"采购指南","retiree":"退休人群", "side-hustle":"副业创业","teardown":"硬件拆解","self-service":"自助场景", "park":"公园场景","industry-report":"行业报告" }; return map[s] || s; } function showToast(msg) { const t = $("edToast"); t.textContent = msg; t.classList.add("show"); clearTimeout(t._tid); t._tid = setTimeout(() => t.classList.remove("show"), 3000); } // Warn before leaving with unsaved changes window.addEventListener("beforeunload", e => { if (isDirty) { e.preventDefault(); e.returnValue = "您有未保存的修改!"; return e.returnValue; } });