npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

gbdocs

v0.1.2

Published

挂逼文档:独立 .docx 编辑器(内核移植自 guabi office/docx 编解码 + 自研 contenteditable 编辑器内核,UI 用原生 HTML 重写),只依赖 gbooxml。

Readme

gbdocs

独立 .docx 编辑器:OOXML docx 编解码内核 + 自研 contenteditable 编辑器内核 + 原生 HTML 界面。

没有 React/Vue,没有 ProseMirror/TipTap,没有 CKEditor。整个包只依赖 gbooxml 做 ZIP 与 XML,其余全部自持:.docx 双向编解码、以 DOM 为文档模型的编辑器内核、分页/页眉页脚/脚注尾注/批注/修订,以及菜单、工具栏、查找、颜色面板这一整套 UI。

三层结构

| 层 | 入口 | 职责 | | --- | --- | --- | | 编解码 | gbdocs/kernel/docx/codec/* | .docx 字节 ⇄ 文档模型 ⇄ HTML | | 编辑器内核 | gbdocs/surface/editor-surface | EditorSurface 承载 EditorCore:命令、状态快照、历史、表格、批注、修订 | | 完整应用 | gbdocs(根入口) | WordApp:把上面两层装配成一个可用的 Word 界面 |

只想做一个在线文档编辑器 → 用 WordApp;已有自己的界面、只要编辑内核 → 用 EditorSurface;只要读写文件、不要编辑器 → 用 codec。

安装

npm install gbdocs

先把它跑起来

npm start            # node serve.mjs,默认 http://localhost:5273/

serve.mjs 只是薄封装,静态服务器本体是 guabiutils/dev-server(三端共用同一份),端口可 node serve.mjs 8080 或用环境变量 GBDOCS_PORT。打开根路径即为完整编辑器,控制台里有 window.gbdocs 指向 WordApp 实例,可直接调 await window.gbdocs.selfTest() 做一次全链路自检。

页面本身只有 index.html,它加载四个样式表(顺序有意义):tokens.css → editor-content.css → word-panel.css → app.css。自己装配页面时请全部引入,否则正文排版和面板都会走形。

index.html 里有一条 importmap,把裸标识符 gbooxml / guabiutils 映射到 /vendor/*,而 serve.mjs 把 /vendor/gbooxml/*、/vendor/guabiutils/* 指向同级目录的源码。这意味着这个自带 demo 只在仓库的 monorepo 布局下能跑;从 npm 单独安装后要用 demo 页面,请自行把 gbooxml 映射到 node_modules/gbooxml/src/index.js。

嵌入到自己的页面

import { WordApp } from 'gbdocs';
import 'gbdocs/styles/tokens.css';
import 'gbdocs/styles/editor-content.css';
import 'gbdocs/styles/word-panel.css';
import 'gbdocs/styles/app.css';

const app = new WordApp(document.getElementById('app'));
await app.start();

构造函数只有一个参数:一个已经挂在文档里的 DOM 元素,编辑器 UI 会 appendChild 进去。没有 options 对象,配置都在实例方法上:

app.setEditMode('readonly');   // 'edit' | 'suggest' | 'readonly'
app.setZoom(120);              // 40–300,会持久化到 localStorage
app.setView('commentsPanel', true);
await app.saveAs();            // 另存为 .docx(优先 File System Access API,回落下载)
app.print();
app.destroy();                 // 停观察器与定时器,但不会把 DOM 从挂载点摘掉

destroy() 只解除副作用(ResizeObserver、window resize、分页与自动保存定时器),不会移除它自己插入的 DOM;要彻底回收请连挂载点子树一起删。

只读写文件(codec)

import { parseDocx } from 'gbdocs/kernel/docx/codec/docx';
import { buildDocxVerified } from 'gbdocs/kernel/docx/codec/roundtrip';
import { docToMarkdown } from 'gbdocs/kernel/docx/codec/markdown';
import { lossText } from 'gbdocs/kernel/docx/codec/losses';

const doc = await parseDocx(bytes);            // Uint8Array | ArrayBuffer → Promise<Document>
console.log(lossText(doc.losses));             // 这次打开丢掉了哪些原文特性
const back = await buildDocxVerified(doc);     // Promise<Uint8Array>
const md = docToMarkdown(doc);                 // 同步返回字符串

文档模型就是 codec 与编辑器之间唯一的接口:

{
  html: '<p>正文</p>',        // 正文(编辑器的工作内容)
  sections: [{
    type: '',                 // 分节类型
    page: { size: 'A4', orientation: 'portrait',
            margins: { top: 25.4, right: 25.4, bottom: 25.4, left: 25.4 },
            columns: 1, colGap: 8, colSeparator: false, headerTop: 0, footerBottom: 0 },
    hfMode: 'all', header: '', footer: '', headerEven: '', footerEven: '', headerFirst: '', footerFirst: '',
    pageNumbers: { enabled: false, align: 'center', format: 'arabic', start: 1 },
    watermark: { data: '', color: '#AEB5C0', size: 120, opacity: 0.3 },
  }],
  comments: [], footnotes: [], endnotes: [],
  props: null,               // 标题/作者等文档属性
  defaultTabStop: 720,
  carry: [],                 // 整包透传的分节(主题、术语表、customXml…)
  losses: [],                // 只有读出来的 doc 上有
}

WordApp.currentDoc() 产出的就是这个形状,applyDoc(model) 接受它(传 null 得到空白文档)。

保存自检

buildDocx() 直接生成字节;buildDocxVerified() 生成后会立刻读回来比对内容摘要,任何不一致都抛 保存自检未通过… 并拒绝这次保存。应用内所有保存路径走的都是 buildDocxVerified。想强行写出未经自检的结果(比如调试),才用 buildDocx。

保住与丢失

往返会保住:段落对齐/缩进/行距、H1–H6、加粗斜体下划删除线、颜色字号字体高亮、等宽代码、超链接、有序无序列表、表格(列宽、单元格底纹、横纵合并)、内联图片、分隔线、引用块、分页符、页眉页脚与页码、纸张尺寸方向页边距分栏、批注、修订(含作者与时间)、脚注尾注、文档属性,以及主题等整包透传内容。

会丢的主要是:文本框/形状、域代码、SmartArt。丢失项在读入时就被清点进 doc.losses,用 lossText() 可以直接展示给用户。

旧版二进制 .doc 会在文件头就被拒绝:旧版二进制 .doc 不支持,请先另存为 .docx。

只用编辑内核

import { EditorSurface } from 'gbdocs/surface/editor-surface';

const surface = new EditorSurface({
  mount: document.getElementById('editor'),
  modelValue: '<p>正文</p>',
  placeholder: '开始输入',
  editable: true,
  backdrop: (host) => { /* 自己画纸张、页眉页脚带 */ },
});

const editor = surface.editor;          // EditorCore
editor.exec('toggleBold');
editor.state();                         // 一次快照,供工具栏刷新
editor.run(() => editor.exec('insertText', 'hi'), { merge: 'insertText' });
await surface.insertImages(fileList);
surface.destroy();

这里的 文档模型就是那段 contenteditable DOM:进出只有 normalizeBody / serializeBody 两道规范化,对应 editor.getHTML() / editor.setHTML(html)。exec(name, ...args) 的命令名在 COMMANDS 注册表里,未知名返回 false;can(name, ...args) 用来置灰按钮。常用命令:undo、redo、toggleBold/Italic/Underline/Strike/Code、setColor、setFontSize、setFontFamily、setLink、clearFormat、setHeading(level)、toggleBlockquote、toggleBulletList、insertPageBreak、insertTable({ rows, cols, withHeaderRow })、tableAction(action, counts)、setComment(key)、insertNote(spec)、resolveRevisions(accept)、convertBlock(kind, el)。

导入路径

深层入口走通配 ./src/*.js,子路径不带 .js:

import { parseDocx } from 'gbdocs/kernel/docx/codec/docx';   // ✔ → src/kernel/docx/codec/docx.js
import { EditorCore } from 'gbdocs/kernel/editor/engine/editor';
import { useKeyboard } from 'gbdocs/kernel/keyboard';
import { FONT_SIZES } from 'gbdocs/kernel/textStyles';
import { notify } from 'gbdocs/kernel/notify';

写成 gbdocs/kernel/docx/codec/docx.js 会解析成 docx.js.js 而报 ERR_MODULE_NOT_FOUND。

环境说明

读方向(parseDocx)在纯 Node 可跑 —— 它只用 gbooxml 的纯 JS ZIP/XML。

写方向(buildDocx / buildDocxVerified)和整套 UI 需要 DOM:HTML → 块模型的转换用 DOMParser,界面层还用到 ResizeObserver、localStorage、showOpenFilePicker。在 Node 里跑写入请自备 jsdom,本包的测试就是这么做的:

import { JSDOM } from 'jsdom';
const dom = new JSDOM('<!doctype html><html><body></body></html>', { url: 'http://localhost/' });
Object.assign(globalThis, {
  window: dom.window, document: dom.window.document,
  DOMParser: dom.window.DOMParser, XMLSerializer: dom.window.XMLSerializer,
  Node: dom.window.Node, NodeFilter: dom.window.NodeFilter,
  Element: dom.window.Element, HTMLElement: dom.window.HTMLElement,
  DocumentFragment: dom.window.DocumentFragment,
});
const { parseDocx } = await import('gbdocs/kernel/docx/codec/docx');

注意要用动态 import:编解码模块在被加载时就会触碰 DOM 全局,必须在上面的赋值之后再导入。

jsdom 只是本包测试的 devDependency,不在运行时依赖里 —— 浏览器场景无需任何 polyfill。

已知坑

  1. 批注键在写出时会被重编号成数字 id:读到的 data-cmt="c1" 存回去可能变成 data-cmt="0",别把自己的业务 id 直接绑在这个属性上。
  2. new WordApp(mount) 的挂载元素必须已在文档中,构造期间就会测量布局;先 append 再 new。
  3. WordApp 不对外派发任何事件,它只消费编辑器内核的事件总线。要做联动,取 app.editor 后 app.editor.on('state' | 'selection', fn)。
  4. 偏好(缩放、面板开关)持久化在 localStorage['gbdocs.prefs'],SSR 环境请在客户端实例化。
  5. 自带 demo 的 /vendor/gbooxml 依赖仓库同级目录布局,从 npm 单独安装需自己改 importmap。

许可

MIT