// ==UserScript== // @name Word 编辑器(拖入 docx 在线编辑) // @namespace http://tampermonkey.net/ // @version 1.1.0 // @description 点右下角按钮打开 Word 编辑页,把 .docx 拖进去就能在线编辑:富文本工具栏、插入图片可拖动缩放、可添加文件(Word/文本并入内容或作为附件插入)、实时字数统计、导出 docx / HTML、复制、打印。也可直接在任意网页拖入文件。 // @author xizi // @match *://*/* // @match file:///* // @require https://cdn.bootcdn.net/ajax/libs/mammoth/1.8.0/mammoth.browser.min.js // @require https://registry.npmmirror.com/docx/8.5.0/files/build/index.umd.js // @grant GM_addStyle // @grant GM_setClipboard // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant unsafeWindow // @run-at document-start // @noframes // @license MIT // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cmVjdCB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHJ4PSI1IiBmaWxsPSIjMTUyMjM4IiBzdHJva2U9IiM1YjlkZmYiIHN0cm9rZS13aWR0aD0iMS41Ii8+PHBhdGggZD0iTTcgOGgxME03IDEyaDEwTTcgMTZoNiIgc3Ryb2tlPSIjOGZjMGZmIiBzdHJva2Utd2lkdGg9IjEuNiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PHBhdGggZD0iTTE1LjIgMTcuNGwxLjMgMS4zIDIuNi0yLjkiIHN0cm9rZT0iIzViOWRmZiIgc3Ryb2tlLXdpZHRoPSIxLjYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZmlsbD0ibm9uZSIvPjwvc3ZnPg== // ==/UserScript== /* * 功能一览 * 1. 点右下角 📝 按钮 → 进入 Word 编辑页(铺满屏幕)→ 把 .docx 拖进去,或点「选择文件…」。 * 2. 编辑:富文本工具栏(段落样式、字体/字号/颜色/高亮、加粗斜体下划线删除线、项目符号与编号、 * 缩进、四种对齐、插入表格/链接/图片/分隔线、清除格式、撤销重做、缩放),底部实时字数统计。 * 3. 保存:导出标准 .docx(OOXML,Word / WPS 都能打开)、导出 HTML、复制全文、打印,Ctrl+S 快捷保存。 * 4. 支持拖入 .docx / .docm / .txt / .md / .html;老版二进制 .doc 无法解析,会提示先另存为 .docx。 * * 说明:导入靠 mammoth(docx → 语义化 HTML),导出靠 docx.js(HTML → 标准 OOXML)。 * 导入时保留标题/列表/表格/粗斜体/图片/超链接等结构,字体字号这类纯视觉样式以编辑器里的实际效果为准。 */ (function () { 'use strict'; /* ============================================================ * 0. 常量与状态 * ============================================================ */ const VERSION = '1.1.0'; const ROOT_ID = 'wd-editor-root'; const BALL_ID = 'wd-editor-ball'; const BANNER_ID = 'wd-editor-banner'; const TOAST_ID = 'wd-editor-toast'; const EXT_DOC = ['docx', 'docm']; // 主要支持 const EXT_TEXT = ['txt', 'text', 'md', 'markdown', 'html', 'htm']; // 纯文本类 const EXT_BLOCKED = ['doc', 'wps', 'rtf']; // 老格式,明确提示 const CDN = { mammoth: [ 'https://cdn.bootcdn.net/ajax/libs/mammoth/1.8.0/mammoth.browser.min.js', 'https://registry.npmmirror.com/mammoth/1.8.0/files/mammoth.browser.min.js', 'https://cdn.jsdelivr.net/npm/mammoth@1.8.0/mammoth.browser.min.js', 'https://unpkg.com/mammoth@1.8.0/mammoth.browser.min.js' ], docx: [ 'https://registry.npmmirror.com/docx/8.5.0/files/build/index.umd.js', 'https://cdn.jsdelivr.net/npm/docx@8.5.0/build/index.umd.js', 'https://unpkg.com/docx@8.5.0/build/index.umd.js', 'https://cdn.staticfile.net/docx/8.5.0/index.umd.js' ] }; const DRAFT_KEY = 'word_editor_draft_v1'; // 自动保存的草稿 const DRAFT_DELAY = 2000; // 停止输入 2 秒后写草稿 const FONT_LIST = [ { label: '默认字体', value: '' }, { label: '宋体', value: 'SimSun' }, { label: '黑体', value: 'SimHei' }, { label: '楷体', value: 'KaiTi' }, { label: '仿宋', value: 'FangSong' }, { label: '微软雅黑', value: 'Microsoft YaHei' }, { label: 'Times New Roman', value: 'Times New Roman' }, { label: 'Arial', value: 'Arial' }, { label: 'Consolas', value: 'Consolas' } ]; const SIZE_LIST = [10, 12, 14, 16, 18, 20, 24, 28, 32, 40, 48]; const state = { fileName: '', dirty: false, empty: true, // 还没有文档内容 draftTimer: 0, lastEdit: 0 }; const refs = {}; // DOM 引用 const libs = {}; // 解析库缓存 const loading = {}; // CDN 加载中的 Promise let dragDepth = 0; let toastTimer = 0; /* ============================================================ * 1. 小工具 * ============================================================ */ function getLib(name) { if (libs[name]) return libs[name]; const key = name === 'mammoth' ? 'mammoth' : 'docx'; const usable = function (lib) { if (!lib) return false; if (name === 'docx') { // 注意:docx 的 Packer 是个类(typeof 为 function),两种写法都接受 return !!lib.Packer && (typeof lib.Packer.toBlob === 'function' || typeof lib.Packer.toBuffer === 'function'); } return typeof lib.convertToHtml === 'function'; }; const targets = []; try { if (typeof unsafeWindow !== 'undefined' && unsafeWindow) targets.push(unsafeWindow); } catch (e) { /* 忽略 */ } targets.push(window); try { if (typeof globalThis !== 'undefined' && globalThis && targets.indexOf(globalThis) < 0) targets.push(globalThis); } catch (e) { /* 忽略 */ } for (let i = 0; i < targets.length; i++) { try { if (usable(targets[i] && targets[i][key])) { libs[name] = targets[i][key]; return libs[name]; } } catch (e) { /* 忽略 */ } } // 油猴 @require 里 UMD 有时会把导出挂在 module.exports 上 try { if (typeof module !== 'undefined' && module && module.exports) { const m = module.exports; if (usable(m)) { libs[name] = m; return m; } if (usable(m[key])) { libs[name] = m[key]; return libs[name]; } if (usable(m.default && m.default[key])) { libs[name] = m.default[key]; return libs[name]; } } } catch (e) { /* 忽略 */ } /* eslint-disable no-undef */ try { if (usable(typeof mammoth !== 'undefined' ? mammoth : null)) { libs.mammoth = mammoth; return mammoth; } } catch (e) { /* 忽略 */ } try { if (usable(typeof docx !== 'undefined' ? docx : null)) { libs.docx = docx; return docx; } } catch (e) { /* 忽略 */ } /* eslint-enable no-undef */ return null; } /** 动态插一个