您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
为 StackEdit 添加“一键格式化内容”和“格式化表格”按钮,自动格式化输入区内容(换行、LaTeX、表格等)
当前为
// ==UserScript== // @name StackEdit一键格式化内容 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 为 StackEdit 添加“一键格式化内容”和“格式化表格”按钮,自动格式化输入区内容(换行、LaTeX、表格等) // @author damu // @match https://stackedit.io/app* // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; /** ---------- 读取编辑器内容(优先 CodeMirror) ---------- */ function getEditorContent() { try { const cmHost = document.querySelector('.CodeMirror'); if (cmHost && cmHost.CodeMirror) return cmHost.CodeMirror.getValue(); } catch (e) { } const pre = document.querySelector('pre.editor__inner, pre.editor__inner.markdown-highlighting'); if (pre) return pre.innerText || pre.textContent || ''; const ta = document.querySelector('textarea, .editormd-markdown-textarea'); if (ta) return ta.value || ''; return ''; } /** ---------- 写回编辑器内容(多重策略) ---------- */ function setEditorContent(content) { try { const cmHost = document.querySelector('.CodeMirror'); if (cmHost && cmHost.CodeMirror) { cmHost.CodeMirror.setValue(content); cmHost.CodeMirror.refresh && cmHost.CodeMirror.refresh(); return true; } } catch (e) { } const pre = document.querySelector('pre.editor__inner, pre.editor__inner.markdown-highlighting'); if (pre) { try { pre.focus(); const sel = window.getSelection(); sel.removeAllRanges(); const range = document.createRange(); range.selectNodeContents(pre); sel.addRange(range); const execOk = document.execCommand('insertText', false, content); if (!execOk || pre.innerText !== content) { sel.removeAllRanges(); const r2 = document.createRange(); r2.selectNodeContents(pre); r2.deleteContents(); r2.insertNode(document.createTextNode(content)); } ['input', 'keyup', 'change'].forEach(ev => pre.dispatchEvent(new Event(ev, { bubbles: true }))); setTimeout(() => { try { pre.blur(); } catch (e) { } }, 60); return true; } catch (e) { try { pre.textContent = content; pre.dispatchEvent(new Event('input', { bubbles: true })); return true; } catch (ee) { } } } const ta = document.querySelector('textarea, .editormd-markdown-textarea'); if (ta) { ta.value = content;['input', 'change'].forEach(ev => ta.dispatchEvent(new Event(ev, { bubbles: true }))); return true; } return false; } /** ---------- 表格格式化函数 ---------- */ const formatTableLine = line => { const cells = line.split('|'); let result = '|'; for (let i = 1; i < cells.length - 1; i++) { const content = cells[i].trim() === '' ? ' ' : ` ${cells[i].trim()} `; result += content + '|'; } return result; }; const formatSeparatorLine = line => { const cells = line.split('|').map(c => c.trim()); let result = '|'; for (let i = 1; i < cells.length - 1; i++) result += ' --- |'; return result; }; const formatTable = content => { const lines = content.split('\n'); let tableStart = -1, tableEnd = -1, tables = []; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line.startsWith('|') && line.endsWith('|')) { if (tableStart === -1) tableStart = i; tableEnd = i; } else if (tableStart !== -1) { if (i === tableStart + 1 && line.includes('|') && line.replace(/[^|-]/g, '').length > 0) tableEnd = i; else { if (tableEnd - tableStart >= 1) tables.push({ start: tableStart, end: tableEnd }); tableStart = -1; tableEnd = -1; } } } if (tableStart !== -1 && tableEnd - tableStart >= 1) tables.push({ start: tableStart, end: tableEnd }); if (tables.length === 0) return content; const newLines = [...lines]; tables.forEach(({ start, end }) => { newLines[start] = formatTableLine(newLines[start]); if (start + 1 <= end) newLines[start + 1] = formatSeparatorLine(newLines[start + 1]); for (let i = start + 2; i <= end; i++) newLines[i] = formatTableLine(newLines[i]); }); return newLines.join('\n'); }; /** ---------- 主格式化流程 ---------- */ function formatAllContent() { let content = getEditorContent(); if (!content) { showToast('未获取到编辑器内容,无法格式化!', 2); return; } const needFormat = content.includes('\\n') || /\\\\\[/.test(content) || /\\\\\(/.test(content) || /\\\\\]/.test(content) || /\\\\\)/.test(content); if (!needFormat) { showToast('内容无需格式化!', 3); return; } content = content.replace(/\\n/g, '\n').replace(/\\\\\[/g, '$$ ').replace(/\\\\\(/g, '$$').replace(/\\\\\]/g, ' $$').replace(/\\\\\)/g, '$$').replace(/\\\\/g, '\\'); content = formatTable(content).trim(); setEditorContent(content) ? showToast('一键格式化完成!') : showToast('写回编辑器失败!', 1); } /** ---------- 创建按钮并安装快捷键 ---------- */ function createButtonIfMissing() { const nav = document.querySelector('.navigation-bar__inner.navigation-bar__inner--edit-pagedownButtons') || document.querySelector('.navigation-bar__inner'); if (!nav || document.getElementById('stackedit-format-one-click')) return; const btn = document.createElement('button'); btn.id = 'stackedit-format-one-click'; btn.title = '一键格式化内容 – alt+Shift+F'; btn.innerText = '一键格式化内容'; btn.style.cssText = 'margin-left:4px;padding:2px 6px;font-size:13px;cursor:pointer;border:1px solid #ccc;border-radius:3px;background:#f0f0f0;color:#333;white-space:nowrap;'; btn.addEventListener('click', formatAllContent); nav.appendChild(btn); } /** ---------- 新增单独格式化表格按钮 ---------- */ function createFormatTableButton() { const nav = document.querySelector('.navigation-bar__inner.navigation-bar__inner--edit-pagedownButtons') || document.querySelector('.navigation-bar__inner'); if (!nav || document.getElementById('stackedit-format-table')) return; const btn = document.createElement('button'); btn.id = 'stackedit-format-table'; btn.title = '仅格式化表格'; btn.innerText = '格式化表格'; btn.style.cssText = 'margin-left:4px;padding:2px 6px;font-size:13px;cursor:pointer;border:1px solid #ccc;border-radius:3px;background:#f9f9f9;color:#333;white-space:nowrap;'; btn.addEventListener('click', () => { let content = getEditorContent(); if (!content) return showToast('未获取到编辑器内容', 2); if (!/\|.*\|/.test(content)) return showToast('内容无需格式化!', 3); const formatted = formatTable(content); if (formatted === content) return showToast('没有发现表格需要格式化', 2); setEditorContent(formatted); showToast('表格已格式化', 1); }); nav.appendChild(btn); } // 初始化按钮 const checker = setInterval(() => { const navExists = !!document.querySelector('.navigation-bar__inner, .navigation-bar__inner.navigation-bar__inner--edit-pagedownButtons'); const editorExists = !!document.querySelector('pre.editor__inner, .CodeMirror, textarea'); if (navExists && editorExists) { createButtonIfMissing(); createFormatTableButton(); clearInterval(checker); } }, 150); const mo = new MutationObserver(() => { createButtonIfMissing(); createFormatTableButton(); }); mo.observe(document.body, { childList: true, subtree: true }); // 快捷键 alt+Shift+F document.addEventListener('keydown', e => { if (e.altKey && e.shiftKey && (e.key === 'F' || e.key === 'f')) { e.preventDefault(); formatAllContent(); } }); /** ---------- 显示轻量通知 ---------- */ function showToast(msg, type = 0, duration = 1500) { const div = document.createElement('div'); div.textContent = msg; let bgColor = '#4caf50'; switch (type) { case 1: bgColor = '#f44336'; break; case 2: bgColor = '#9e9e9e'; break; case 3: bgColor = '#ffffff'; break; } div.style.cssText = ` position: fixed; top: 50px; right: 50px; background: ${bgColor}; color: ${type === 3 ? '#333' : 'white'}; padding: 6px 12px; border-radius: 4px; box-shadow: 0 2px 6px rgba(0,0,0,0.2); z-index: 9999; font-size: 14px; pointer-events: none; opacity: 0; transition: opacity 0.2s;`; document.body.appendChild(div); requestAnimationFrame(() => div.style.opacity = 1); setTimeout(() => { div.style.opacity = 0; setTimeout(() => div.remove(), 200); }, duration); } })();