0/64
HTML 源代码
就绪 📋 粘贴: 富文本 字符: 0
${content}`; document.getElementById('previewFrame').srcdoc = html; } let previewTimer, rafId; function schedulePreview() { if (rafId) return; rafId = requestAnimationFrame(() => { updatePreview(); updateCounts(); rafId = null; }); } editorEl.addEventListener('input', schedulePreview); editorEl.addEventListener('input', scheduleHistoryRecord); new MutationObserver(() => { schedulePreview(); addSectionControls(); }).observe(editorEl, { childList:true, subtree:true, characterData:true, attributes:true }); function togglePreview() { const panel = document.getElementById('previewPanel'); panel.style.display = panel.style.display === 'none' ? '' : 'none'; } function updateCounts() { const chars = (editorEl.innerText || '').replace(/\s/g, '').length; document.getElementById('charCount').textContent = '字符: ' + chars; document.getElementById('wordCount').textContent = chars + ' 字'; } document.getElementById('articleTitle').addEventListener('input', function() { document.getElementById('titleCount').textContent = this.value.length + '/64'; updatePreview(); scheduleSave(); }); // ====== Source Code ====== function toggleCode() { const overlay = document.getElementById('codeOverlay'); if (overlay.classList.contains('show')) { overlay.classList.remove('show'); } else { document.getElementById('codeTextarea').value = editorEl.innerHTML; overlay.classList.add('show'); } } function applyCode() { editorEl.innerHTML = document.getElementById('codeTextarea').value; document.getElementById('codeOverlay').classList.remove('show'); updatePreview(); updateCounts(); showToast('源码已应用'); scheduleSave(); } // ====== Keyboard Shortcuts ====== document.addEventListener('keydown', e => { const key = e.key.toLowerCase(); const editorHasFocus = document.activeElement === editorEl || editorEl.contains(document.activeElement); if (editorHasFocus && (e.ctrlKey || e.metaKey) && !e.altKey && key === 'z') { e.preventDefault(); runHistoryCommand(e.shiftKey ? 'redo' : 'undo'); return; } if (editorHasFocus && (e.ctrlKey || e.metaKey) && !e.altKey && key === 'y') { e.preventDefault(); runHistoryCommand('redo'); return; } if (e.ctrlKey && e.shiftKey && e.key === 'E') { e.preventDefault(); toggleCode(); } if (e.ctrlKey && e.key === 'k') { e.preventDefault(); openLinkModal(); } if (e.ctrlKey && e.key === 's') { e.preventDefault(); window.WXEDIT_SERVER?.authenticated ? saveToCloud(true) : saveToLocal(true); } // Alt+Arrow to move sections if (e.altKey && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) { const sel = window.getSelection(); if (sel.rangeCount && editorEl.contains(sel.anchorNode)) { const sec = sel.anchorNode.parentElement?.closest('section'); if (sec) { e.preventDefault(); moveSection(sec, e.key === 'ArrowUp' ? -1 : 1); } } } }); function toggleShortcuts() { const m = document.getElementById('shortcutsModal'); m.classList.contains('show') ? closeModal('shortcutsModal') : openModal('shortcutsModal'); } // ====== Auto-save ====== const SAVE_KEY = 'wxedit_autosave'; let saveTimer, dirty = false; function scheduleSave(recordHistory = true) { if (recordHistory) recordEditorHistory(); dirty = true; document.getElementById('saveDot').classList.add('unsaved'); document.getElementById('saveStatus').textContent = '未保存'; clearTimeout(saveTimer); saveTimer = setTimeout(() => saveToLocal(), 2000); } function saveToLocal(manual) { const data = { title: document.getElementById('articleTitle').value, content: editorEl.innerHTML, time: Date.now() }; try { localStorage.setItem(SAVE_KEY, JSON.stringify(data)); } catch(e) {} dirty = false; document.getElementById('saveDot').classList.remove('unsaved'); document.getElementById('saveStatus').textContent = '已保存'; if (manual) showToast('已保存到本地', 'success'); } async function saveToCloud(manual) { const server = window.WXEDIT_SERVER || {}; if (!server.authenticated) { saveToLocal(false); if (manual && confirm('登录后可将作品保存到云端。现在去登录吗?')) window.location.href = server.loginUrl || '/login'; return; } const button = document.getElementById('cloudSaveButton'); const label = document.getElementById('cloudSaveLabel'); button.disabled = true; label.textContent = '保存中...'; try { const work = server.work; const response = await fetch(work?.saveUrl || server.createUrl, { method: work ? 'PUT' : 'POST', headers: {'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': server.csrfToken}, body: JSON.stringify({title: document.getElementById('articleTitle').value, content: editorEl.innerHTML}) }); const result = await response.json(); if (!response.ok) throw new Error(result.message || '云端保存失败'); if (!work) { server.work = {id: result.workId, saveUrl: result.saveUrl}; history.replaceState({}, '', result.editorUrl); } saveToLocal(false); label.textContent = '已保存到云端'; showToast(result.message, 'success'); } catch (error) { label.textContent = '保存云端'; showToast(error.message || '云端保存失败,请稍后重试'); } finally { button.disabled = false; } } function loadFromLocal() { try { const raw = localStorage.getItem(SAVE_KEY); if (!raw) return false; const data = JSON.parse(raw); if (data.title) document.getElementById('articleTitle').value = data.title; if (data.content) editorEl.innerHTML = data.content; document.getElementById('titleCount').textContent = (data.title || '').length + '/64'; return true; } catch(e) { return false; } } // ====== Export ====== function exportHTML() { const clone = document.createElement('div'); clone.innerHTML = editorEl.innerHTML; clone.querySelectorAll('.sec-ctrls,[data-spacer]').forEach(el => el.remove()); const content = clone.innerHTML; const fullHTML = `
\n${content}\n
`; navigator.clipboard.writeText(fullHTML).then(() => showToast('全文已复制,可粘贴到公众号后台', 'success')) .catch(() => { const ta = document.createElement('textarea'); ta.value = fullHTML; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); showToast('全文已复制到剪贴板', 'success'); }); saveToLocal(); } // ====== Init ====== function init() { // Ensure template panel is visible on load document.getElementById('tplPanel').classList.remove('collapsed'); setButtonIcon(document.getElementById('toggleLeft'), 'ri-arrow-left-s-line'); document.getElementById('previewPanel').classList.remove('collapsed'); setButtonIcon(document.getElementById('toggleRight'), 'ri-arrow-right-s-line'); buildSwatches(); buildEmojiPicker(); renderCategories(); renderTemplates(); // Restore preferences try { const savedTplColor = localStorage.getItem('wxedit_tpl_color'); if (savedTplColor) { try { const c2 = JSON.parse(savedTplColor); setTemplateColors(c2.p, c2.a); document.querySelectorAll('#tplColors span').forEach(el => { el.classList.toggle('active', el.dataset.p === c2.p && el.dataset.a === c2.a); }); } catch(e){} } if (localStorage.getItem('wxedit_dark') === '1') { document.body.classList.add('dark'); setButtonIcon(document.getElementById('nightToggle'), 'ri-sun-line'); } const savedPaste = localStorage.getItem('wxedit_paste'); if (savedPaste === '0') { pasteRich = false; document.getElementById('pasteMode').classList.add('active'); document.getElementById('pasteLabel').textContent = '纯文本'; } } catch(e) {} const serverWork = window.WXEDIT_SERVER?.work; const restored = serverWork ? true : loadFromLocal(); if (serverWork) { document.getElementById('articleTitle').value = serverWork.title || ''; editorEl.innerHTML = serverWork.content || ''; document.getElementById('titleCount').textContent = (serverWork.title || '').length + '/64'; } if (!restored) { editorEl.innerHTML = `

👋 点击此处开始编辑,或从左侧选择样式模板插入。

支持直接粘贴文字、图片,以及 Ctrl+B 加粗等快捷键。

Ctrl+Shift+E 编辑 HTML 源码,Ctrl+K 插入链接。

`; } updatePreview(); updateCounts(); addSectionControls(); recordEditorHistory(); const cloudLabel = document.getElementById('cloudSaveLabel'); cloudLabel.textContent = window.WXEDIT_SERVER?.authenticated ? (serverWork ? '保存云端' : '保存到云端') : '登录后云端保存'; } init(); window.addEventListener('beforeunload', () => { if (dirty) saveToLocal(); });