document-outline.js 0.0.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +121 -0
- package/dist/index.cjs +509 -0
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +500 -0
- package/package.json +90 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joseph Mearman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# document-outline.js
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ExaDev/document-outline.js) [](https://www.npmjs.com/package/document-outline.js) [](https://github.com/ExaDev/document-outline.js/releases/latest) [](https://github.com/ExaDev/document-outline.js/actions)
|
|
4
|
+
|
|
5
|
+
> Utilities for consumers holding a tree-form `DocumentPackage` (document-schema.js 4.0.0) — the table-of-contents projection, effective-property resolution, and the flatten-to-leaves / leaf-text / stable-hash helpers — without importing the producer that made it. The outline package for the [documents.js family](https://github.com/ExaDev). Worker-isomorphic: the same code runs under Node and inside a Cloudflare Workers isolate.
|
|
6
|
+
|
|
7
|
+
Created for [document-schema.js#14](https://github.com/ExaDev/document-schema.js/issues/14): none of the content shapes groups content by heading or list level — a heading paragraph sits in a flat `blocks` array like any other — so every consumer needing a nested tree (chunking a document for retrieval, generating a table of contents, structural diffing) had to rebuild the same nesting transform for itself. This package is that transform, once. It depends only on `document-schema.js` (plus `zod`): it never touches a codec, because it only ever operates on an already-produced package, regardless of which producer made it.
|
|
8
|
+
|
|
9
|
+
[document-schema.js#20](https://github.com/ExaDev/document-schema.js/issues/20) then made the tree the canonical form: since 4.0.0, `DocumentPackage` **is** the tree — a discriminated union of `{ node, children }` group wrappers (`SectionGroupNode`, `SlideGroupNode`, `SheetGroupNode`, `DrawPageGroupNode`, `ShapeGroupNode`, `HeadingGroupNode`, `ListGroupNode`, all imported from `document-schema.js` itself). With the tree vocabulary owned by the schema, this package's phase-1 `decompose`/`flatten` pair — the flat-to-tree transform and its bijection — moved wholesale into [documents.js](https://github.com/ExaDev/documents.js)'s package boundary ([document-outline.js#2](https://github.com/ExaDev/document-outline.js/issues/2), phase 2): one implementation, one authority, no second copy of the grouping semantics here. What remains — and what this major release re-charters the package around — is the artefact-utility surface: everything a consumer holding a serialised tree-form package JSON needs to project, resolve, and hash it, with `document-schema.js` as the only dependency. The removal is the release note: `decompose`, `flatten`, `documentEnvelope`, and the local `PackageNode` types are gone from this package's surface outright, not `@deprecated` — the tree types live in `document-schema.js`, the lossless tree↔flat pair lives in `documents.js`.
|
|
10
|
+
|
|
11
|
+
## Getting started
|
|
12
|
+
|
|
13
|
+
Requires Node.js `>=20` and pnpm `11.6.0`.
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
pnpm install
|
|
17
|
+
pnpm build # tsdown -> dist/ (ESM + CJS + .d.ts)
|
|
18
|
+
pnpm typecheck # tsc -p tsconfig.json && tsc -p tsconfig.node.json (dual tsconfig)
|
|
19
|
+
pnpm lint # eslint . --fix --cache --max-warnings 0
|
|
20
|
+
pnpm test # vitest run
|
|
21
|
+
pnpm test:watch # vitest
|
|
22
|
+
pnpm test:workers # vitest run --config vitest.workers.config.ts, inside a real Cloudflare Workers (workerd) isolate
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
To run a single test file, pass its path to vitest directly, e.g. `pnpm exec vitest run src/outline/build.test.ts`.
|
|
26
|
+
|
|
27
|
+
## What it provides
|
|
28
|
+
|
|
29
|
+
| Module | Exports |
|
|
30
|
+
|---|---|
|
|
31
|
+
| `outline/build` | `buildOutline` (per-kind TOC projection over a `DocumentPackage`) |
|
|
32
|
+
| `outline/effective` | `effectivePackage` (effective-property resolution) |
|
|
33
|
+
| `outline/node` | `OutlineNode`, `OutlineChild`, `OutlineLeaf`, `OutlineNodeSchema`, `isOutlineNode`, `isOutlineChild`, `isOutlineLeaf` |
|
|
34
|
+
| `outline/helpers` | `flattenOutline`, `outlineLeafText`, `leafContentHash` |
|
|
35
|
+
|
|
36
|
+
Every module in the table is re-exported from the package root, so its exports import from `'document-outline.js'` directly. `outline/hash` (the `stableContentHash`/`canonicalise`/`sha256` primitives behind `leafContentHash`'s published recipe) is deliberately not on the root entry — it stays reachable via the `document-outline.js/outline/hash` subpath, keeping the root surface small.
|
|
37
|
+
|
|
38
|
+
`buildOutline(pkg)` dispatches on `pkg.kind` and projects `pkg.children` into the root scope's children — `OutlineChild[]`, an ordered mix of this package's own `OutlineNode` groups and the schema's leaf payloads. The root is deliberately not itself a node (no synthetic "document" group), so a wordprocessing package's pre-heading content — or a package with no grouping signal at all — appears as leaves directly in the returned array.
|
|
39
|
+
|
|
40
|
+
This is the **TOC projection**, not a decomposition, and the difference is the charter: it deliberately re-groups across container boundaries — a wordprocessing package's sections flow into one tree, a slide's paragraphs are taken across its shapes in shape order — which is exactly the lossiness a table of contents wants, and exactly why the lossless container-boundary-respecting pair lives in `documents.js`'s package boundary instead.
|
|
41
|
+
|
|
42
|
+
An `OutlineNode` carries `text` (the group's own label), `level` (its source level signal, verbatim), and `children` (nested groups and leaf payloads in document order). `level` is the source signal, not tree depth: heading groups carry their anchor's `headingLevel` (1-based), list-item groups carry `list.level` (0-based), and the synthetic slide/sheet/page/formula groups are level 1. Render indentation from the nesting, never from `level` — a slide group (level 1) legitimately contains list items at levels 0, 1, 2… on the other scale.
|
|
43
|
+
|
|
44
|
+
### Per-kind hierarchy
|
|
45
|
+
|
|
46
|
+
| Kind | Groups | Nesting | Leaves |
|
|
47
|
+
|---|---|---|---|
|
|
48
|
+
| wordprocessing | one per heading group, by `headingLevel` | stack semantics; list groups nest inside by `list.level` | non-list blocks, at the current depth |
|
|
49
|
+
| presentation | one per slide group, `Slide N` | slide paragraphs nest by `list.level`, across its shapes | non-paragraph blocks, at the current depth |
|
|
50
|
+
| spreadsheet | one per sheet group, the sheet's name | — | the sheet's images, then its embedded objects |
|
|
51
|
+
| drawing | one per page group, `Page N` | — | the page's shape contents flattened, then its vectors |
|
|
52
|
+
| formula | a single node | — | the `ContentFormula` itself |
|
|
53
|
+
|
|
54
|
+
Both nesting scales follow the same stack semantics, modelled on how Word's navigation pane and PowerPoint's outline view present structure: each new group nests under the deepest open group with a strictly shallower level and pops equal-or-deeper groups closed, so an H4 following an H2 becomes its direct child (no synthetic intermediates) and an H1 after an H3 pops to the root; list groups behave identically on `list.level`'s 0-based scale (a jump from level 0 to level 2 nests directly under the 0). Within a group the two compose: heading groups open scopes, list groups nest inside them, and non-paragraph blocks (tables, images, page breaks, embedded objects) attach as leaves at the current depth without changing it. A paragraph at a leaf position carries neither grouping signal in a well-formed tree, sits flat at its scope, and closes the list nesting — which is what keeps the flattened leaf order identical to document order. `headingLevel` is the only heading signal read — a Heading `styleId` without `headingLevel` does not group — and in presentations it is not read at all: slides have no heading hierarchy of their own, so `list.level` is the only depth signal they carry.
|
|
55
|
+
|
|
56
|
+
Slide and page labels (`Slide 1`, `Page 1`, …) are 1-based, matching the Markdown renderer's own per-slide/per-page heading convention; spreadsheet groups are labelled with the sheet's own name (cells are addressable data, not outline content, and never appear); drawing vectors stay in the tree as textless leaves so structural diffing still sees them.
|
|
57
|
+
|
|
58
|
+
## Effective properties
|
|
59
|
+
|
|
60
|
+
A tree group may carry a `style` ref into the package's `styles` table ([document-schema.js#21](https://github.com/ExaDev/document-schema.js/issues/21)). `effectivePackage(pkg)` resolves those refs away using document-schema.js's own overlay helpers (`resolveStyleChain`, `applyParagraphStyleProperties`, `applyRunStyleProperties` — the mechanics are the schema's to own, the same single-authority rule that moved the tree vocabulary there) and returns the package with every ref consumed and the styles table dropped:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { effectivePackage } from 'document-outline.js';
|
|
64
|
+
|
|
65
|
+
const resolved = effectivePackage(pkg); // same tree, properties inlined, no styles table
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The semantics: a group's ref, plus every ancestor group's ref, overlays onto each paragraph in that group's subtree — group anchors (heading and list groups carry full `ContentParagraph` anchors) and bare paragraph leaves alike — with the chain ordered outermost-first so the nearest group's entry wins over further-out ones, and the paragraph's own direct properties win over everything (the schema's apply helpers fill gaps, never overwrite). The run half of a resolved entry applies to every run of each paragraph it resolved for. The walk's boundary is the block flow: a table leaf's cell paragraphs and an embedded document's own content are leaf-local payload this walk does not rewrite — an embedded document is its own whole document context.
|
|
69
|
+
|
|
70
|
+
Two guarantees worth depending on. First, `effectivePackage(factored)` deep-equals `effectivePackage(unfactored)`: a serialisation that factored properties into style refs and one that inlined them everywhere resolve to the same effective tree, so consumers comparing or hashing content never see the producer's compression choices. To get that property for hashes, resolve first — `leafContentHash` over the leaves of `buildOutline(effectivePackage(pkg))` names the document, not the factoring. Second, resolution runs loudly: a ref the styles table does not carry is malformed, and `resolveStyleChain` throws rather than silently skipping. A styles-free package is returned as the same object — nothing anywhere needs rewriting.
|
|
71
|
+
|
|
72
|
+
## Helpers
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { buildOutline, effectivePackage, flattenOutline, leafContentHash, outlineLeafText } from 'document-outline.js';
|
|
76
|
+
|
|
77
|
+
const outline = buildOutline(pkg); // OutlineChild[] — the TOC projection
|
|
78
|
+
const resolved = effectivePackage(pkg); // style refs consumed, table dropped
|
|
79
|
+
flattenOutline(outline); // every leaf payload, in document order
|
|
80
|
+
outlineLeafText(aLeaf); // the leaf's own text (paragraph runs, table cells,
|
|
81
|
+
// image altText, formula LaTeX; '' for textless leaves)
|
|
82
|
+
leafContentHash(aLeaf); // stable content hash — see the recipe below
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Heading and list paragraphs are represented by their group nodes and are not duplicated as leaves, so a tree of groups flattens to the non-paragraph content plus every unlevelled paragraph; a group's own text is always its `text` field. `leafContentHash` hashes the leaf as given and deliberately does not fold style resolution in — a leaf alone does not know its ancestor group refs, so effective-property resolution can only happen with the whole package in hand. The resolve-then-hash route is `effectivePackage(pkg)` first, then hash the resolved leaves; hash the raw leaf only when you truly mean the literal object.
|
|
86
|
+
|
|
87
|
+
### The hash recipe
|
|
88
|
+
|
|
89
|
+
`leafContentHash` (via `stableContentHash`) is a published contract — changing any step changes every hash ever issued:
|
|
90
|
+
|
|
91
|
+
1. Strip `$schema` keys recursively from the value (arrays mapped, plain objects rebuilt without the key). Serialised dumps carry a release-pinned `$schema` CDN URI stamped by `document-schema.js`'s serialisation helper; the label is transport metadata about which schema version produced the JSON, not content, and no content field is named `$schema` — so a dump and its parsed-then-rehashed original agree.
|
|
92
|
+
2. Canonicalise the result: rebuild every plain object with its own keys sorted ascending by UTF-16 code unit (arrays keep their order, primitives pass through) — so independently constructed, structurally identical content is byte-identical from here on regardless of field-construction order.
|
|
93
|
+
3. `JSON.stringify` the canonicalised value (no spacing; `undefined`-valued optional fields drop out, so "absent" and "explicitly undefined" hash the same).
|
|
94
|
+
4. UTF-8 encode with `TextEncoder`.
|
|
95
|
+
5. SHA-256, hand-rolled over `Uint8Array` (Worker-isomorphic; no `node:crypto`, no async `SubtleCrypto`) and pinned against the FIPS 180-4 example vectors in `hash.test.ts`.
|
|
96
|
+
6. Hex-encode the digest, lowercase.
|
|
97
|
+
|
|
98
|
+
The result is deterministic across processes and platforms, equal exactly when the leaf's content is equal, and different for different content up to SHA-256 collision resistance.
|
|
99
|
+
|
|
100
|
+
## Where decompose and flatten went
|
|
101
|
+
|
|
102
|
+
The phase-1 `decompose`/`flatten` pair and its property-tested bijection — the lossless tree↔flat transform this package once carried as the vehicle for the `DocumentPackage` promotion — now live in [documents.js](https://github.com/ExaDev/documents.js)'s package boundary. Schema 4.0.0 made `DocumentPackage` itself tree-form, so the grouping semantics have one home next to the codecs that produce and consume packages, and the tree types (`PackageNode`, `PackageGroup`, `SectionGroupNode`, …) import from `document-schema.js`. If you hold a flat `ContentDocument` and need the tree, or need the exact container-boundary-preserving inverse of the TOC projection above, that is documents.js's surface now.
|
|
103
|
+
|
|
104
|
+
## Conventions
|
|
105
|
+
|
|
106
|
+
- Worker-isomorphic (see the [family-wide convention](https://github.com/ExaDev/documents.js/blob/main/README.md#conventions)): runtime `src/` must not import `node:*`, a bare Node builtin, or use the `Buffer` global — enforced by a `no-restricted-imports`/`no-restricted-globals` ESLint rule and exercised in CI by running a test suite inside an actual `workerd` isolate (`pnpm test:workers`).
|
|
107
|
+
- Only `src/index.ts` may be named `index.*` — a custom ESLint rule (`local/no-non-barrel-index`) rejects any other module using an `index` basename, since that would be a hidden entry point the `exports` map in `package.json` doesn't advertise.
|
|
108
|
+
- `OutlineNodeSchema` follows document-schema.js's `z.custom` hand-written-guard pattern (`ContentBlock` is the precedent): `z.lazy()` collapses recursive schemas' static type to `unknown` in the pinned zod 4, so the recursion lives in a plain function guard instead.
|
|
109
|
+
- Releases are fully automated: a push to `main` runs `semantic-release` in CI, which determines the version from Conventional Commit messages and publishes to npm via OIDC trusted publishing (no local `NPM_TOKEN` needed). There is no manual publish step.
|
|
110
|
+
|
|
111
|
+
## Install
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
pnpm add document-outline.js
|
|
115
|
+
# or
|
|
116
|
+
npm install document-outline.js
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let document_schema_js = require("document-schema.js");
|
|
3
|
+
let zod = require("zod");
|
|
4
|
+
//#region src/outline/build.ts
|
|
5
|
+
function buildOutline(pkg) {
|
|
6
|
+
switch (pkg.kind) {
|
|
7
|
+
case "wordprocessing": return wordprocessingOutline(pkg.children);
|
|
8
|
+
case "presentation": return presentationOutline(pkg.children);
|
|
9
|
+
case "spreadsheet": return spreadsheetOutline(pkg.children);
|
|
10
|
+
case "drawing": return drawingOutline(pkg.children);
|
|
11
|
+
case "formula": return formulaOutline(pkg.children[0]);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function wordprocessingOutline(sections) {
|
|
15
|
+
const root = [];
|
|
16
|
+
const headingStack = [];
|
|
17
|
+
const listStack = [];
|
|
18
|
+
const headingScope = () => headingStack.at(-1)?.children ?? root;
|
|
19
|
+
const walk = (children) => {
|
|
20
|
+
for (const child of children) if ((0, document_schema_js.isHeadingGroupNode)(child)) {
|
|
21
|
+
listStack.length = 0;
|
|
22
|
+
const level = child.node.headingLevel;
|
|
23
|
+
for (let top = headingStack.at(-1); top !== void 0 && top.level >= level; top = headingStack.at(-1)) headingStack.pop();
|
|
24
|
+
const node = {
|
|
25
|
+
text: paragraphText(child.node),
|
|
26
|
+
level,
|
|
27
|
+
children: []
|
|
28
|
+
};
|
|
29
|
+
const parent = headingStack.at(-1);
|
|
30
|
+
(parent !== void 0 ? parent.children : root).push(node);
|
|
31
|
+
headingStack.push(node);
|
|
32
|
+
walk(child.children);
|
|
33
|
+
} else if ((0, document_schema_js.isListGroupNode)(child)) {
|
|
34
|
+
openListGroup(listStack, headingScope(), paragraphText(child.node), child.node.list.level);
|
|
35
|
+
walk(child.children);
|
|
36
|
+
} else if (child.kind === "paragraph") {
|
|
37
|
+
listStack.length = 0;
|
|
38
|
+
headingScope().push(child);
|
|
39
|
+
} else {
|
|
40
|
+
const parent = listStack.at(-1);
|
|
41
|
+
(parent !== void 0 ? parent.children : headingScope()).push(child);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
for (const section of sections) walk(section.children);
|
|
45
|
+
return root;
|
|
46
|
+
}
|
|
47
|
+
function presentationOutline(slides) {
|
|
48
|
+
return slides.map((slide, index) => {
|
|
49
|
+
const group = {
|
|
50
|
+
text: `Slide ${String(index + 1)}`,
|
|
51
|
+
level: 1,
|
|
52
|
+
children: []
|
|
53
|
+
};
|
|
54
|
+
const listStack = [];
|
|
55
|
+
const walk = (children) => {
|
|
56
|
+
for (const child of children) if ((0, document_schema_js.isListGroupNode)(child)) {
|
|
57
|
+
openListGroup(listStack, group.children, paragraphText(child.node), child.node.list.level);
|
|
58
|
+
walk(child.children);
|
|
59
|
+
} else if (child.kind === "paragraph") {
|
|
60
|
+
listStack.length = 0;
|
|
61
|
+
group.children.push(child);
|
|
62
|
+
} else {
|
|
63
|
+
const parent = listStack.at(-1);
|
|
64
|
+
(parent !== void 0 ? parent.children : group.children).push(child);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
for (const shape of slide.children) walk(shape.children);
|
|
68
|
+
return group;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function spreadsheetOutline(sheets) {
|
|
72
|
+
return sheets.map((sheet) => ({
|
|
73
|
+
text: sheet.node.name,
|
|
74
|
+
level: 1,
|
|
75
|
+
children: [...sheet.children]
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
function drawingOutline(pages) {
|
|
79
|
+
return pages.map((page, index) => ({
|
|
80
|
+
text: `Page ${String(index + 1)}`,
|
|
81
|
+
level: 1,
|
|
82
|
+
children: page.children.flatMap((child) => (0, document_schema_js.isShapeGroupNode)(child) ? flattenShapeChildren(child) : [child])
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
function flattenShapeChildren(group) {
|
|
86
|
+
return flattenListFlow(group.children);
|
|
87
|
+
}
|
|
88
|
+
function flattenListFlow(children) {
|
|
89
|
+
const leaves = [];
|
|
90
|
+
for (const child of children) if ((0, document_schema_js.isListGroupNode)(child)) leaves.push(child.node, ...flattenListFlow(child.children));
|
|
91
|
+
else leaves.push(child);
|
|
92
|
+
return leaves;
|
|
93
|
+
}
|
|
94
|
+
function formulaOutline(formula) {
|
|
95
|
+
return [{
|
|
96
|
+
text: formula.presentation?.latex ?? "",
|
|
97
|
+
level: 1,
|
|
98
|
+
children: [formula]
|
|
99
|
+
}];
|
|
100
|
+
}
|
|
101
|
+
function openListGroup(listStack, scopeChildren, text, level) {
|
|
102
|
+
for (let top = listStack.at(-1); top !== void 0 && top.level >= level; top = listStack.at(-1)) listStack.pop();
|
|
103
|
+
const node = {
|
|
104
|
+
text,
|
|
105
|
+
level,
|
|
106
|
+
children: []
|
|
107
|
+
};
|
|
108
|
+
const parent = listStack.at(-1);
|
|
109
|
+
(parent !== void 0 ? parent.children : scopeChildren).push(node);
|
|
110
|
+
listStack.push(node);
|
|
111
|
+
}
|
|
112
|
+
function paragraphText(paragraph) {
|
|
113
|
+
return paragraph.runs.map((run) => run.text).join("");
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/outline/effective.ts
|
|
117
|
+
function effectivePackage(pkg) {
|
|
118
|
+
const styles = pkg.styles;
|
|
119
|
+
if (styles === void 0) return pkg;
|
|
120
|
+
switch (pkg.kind) {
|
|
121
|
+
case "wordprocessing": return withoutStyles({
|
|
122
|
+
...pkg,
|
|
123
|
+
children: pkg.children.map((group) => resolveSectionGroup(styles, [], group))
|
|
124
|
+
});
|
|
125
|
+
case "presentation": return withoutStyles({
|
|
126
|
+
...pkg,
|
|
127
|
+
children: pkg.children.map((group) => resolveSlideGroup(styles, [], group))
|
|
128
|
+
});
|
|
129
|
+
case "spreadsheet": return withoutStyles({
|
|
130
|
+
...pkg,
|
|
131
|
+
children: pkg.children.map(resolveSheetGroup)
|
|
132
|
+
});
|
|
133
|
+
case "drawing": return withoutStyles({
|
|
134
|
+
...pkg,
|
|
135
|
+
children: pkg.children.map((group) => resolveDrawPageGroup(styles, [], group))
|
|
136
|
+
});
|
|
137
|
+
case "formula": return withoutStyles({ ...pkg });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function withoutStyles(pkg) {
|
|
141
|
+
const copy = { ...pkg };
|
|
142
|
+
delete copy.styles;
|
|
143
|
+
return copy;
|
|
144
|
+
}
|
|
145
|
+
function chainWithRef(chain, group) {
|
|
146
|
+
return group.style === void 0 ? chain : [...chain, group.style];
|
|
147
|
+
}
|
|
148
|
+
function resolveSectionGroup(styles, chain, group) {
|
|
149
|
+
const children = resolveSectionChildren(styles, chainWithRef(chain, group), group.children);
|
|
150
|
+
if (group.style === void 0 && children === group.children) return group;
|
|
151
|
+
return {
|
|
152
|
+
node: group.node,
|
|
153
|
+
children
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function resolveSlideGroup(styles, chain, group) {
|
|
157
|
+
const own = chainWithRef(chain, group);
|
|
158
|
+
let changed = group.style !== void 0;
|
|
159
|
+
const children = [];
|
|
160
|
+
for (const shape of group.children) {
|
|
161
|
+
const resolved = resolveShapeGroup(styles, own, shape);
|
|
162
|
+
changed ||= resolved !== shape;
|
|
163
|
+
children.push(resolved);
|
|
164
|
+
}
|
|
165
|
+
if (!changed) return group;
|
|
166
|
+
return {
|
|
167
|
+
node: group.node,
|
|
168
|
+
children
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function resolveSheetGroup(group) {
|
|
172
|
+
return group.style === void 0 ? group : {
|
|
173
|
+
node: group.node,
|
|
174
|
+
children: group.children
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function resolveDrawPageGroup(styles, chain, group) {
|
|
178
|
+
const own = chainWithRef(chain, group);
|
|
179
|
+
let changed = group.style !== void 0;
|
|
180
|
+
const children = [];
|
|
181
|
+
for (const child of group.children) {
|
|
182
|
+
const resolved = isShapeGroup(child) ? resolveShapeGroup(styles, own, child) : child;
|
|
183
|
+
changed ||= resolved !== child;
|
|
184
|
+
children.push(resolved);
|
|
185
|
+
}
|
|
186
|
+
if (!changed) return group;
|
|
187
|
+
return {
|
|
188
|
+
node: group.node,
|
|
189
|
+
children
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function resolveShapeGroup(styles, chain, group) {
|
|
193
|
+
const children = resolveListChildren(styles, chainWithRef(chain, group), group.children);
|
|
194
|
+
if (group.style === void 0 && children === group.children) return group;
|
|
195
|
+
return {
|
|
196
|
+
node: group.node,
|
|
197
|
+
children
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function resolveHeadingGroup(styles, chain, group) {
|
|
201
|
+
const own = chainWithRef(chain, group);
|
|
202
|
+
const entry = own.length > 0 ? (0, document_schema_js.resolveStyleChain)(styles, own) : void 0;
|
|
203
|
+
let anchor = group.node;
|
|
204
|
+
if (entry !== void 0) {
|
|
205
|
+
const applied = applyEntry(entry, group.node);
|
|
206
|
+
assertResolvedHeadingAnchor(applied);
|
|
207
|
+
anchor = applied;
|
|
208
|
+
}
|
|
209
|
+
const children = resolveSectionChildren(styles, own, group.children);
|
|
210
|
+
if (group.style === void 0 && anchor === group.node && children === group.children) return group;
|
|
211
|
+
return {
|
|
212
|
+
node: anchor,
|
|
213
|
+
children
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function resolveListGroup(styles, chain, group) {
|
|
217
|
+
const own = chainWithRef(chain, group);
|
|
218
|
+
const entry = own.length > 0 ? (0, document_schema_js.resolveStyleChain)(styles, own) : void 0;
|
|
219
|
+
let anchor = group.node;
|
|
220
|
+
if (entry !== void 0) {
|
|
221
|
+
const applied = applyEntry(entry, group.node);
|
|
222
|
+
assertResolvedListAnchor(applied);
|
|
223
|
+
anchor = applied;
|
|
224
|
+
}
|
|
225
|
+
const children = resolveListChildren(styles, own, group.children);
|
|
226
|
+
if (group.style === void 0 && anchor === group.node && children === group.children) return group;
|
|
227
|
+
return {
|
|
228
|
+
node: anchor,
|
|
229
|
+
children
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function assertResolvedHeadingAnchor(paragraph) {
|
|
233
|
+
if (paragraph.headingLevel === void 0) throw new Error("effectivePackage: resolution dropped a heading anchor's headingLevel");
|
|
234
|
+
}
|
|
235
|
+
function assertResolvedListAnchor(paragraph) {
|
|
236
|
+
if (paragraph.list === void 0) throw new Error("effectivePackage: resolution dropped a list anchor's list membership");
|
|
237
|
+
}
|
|
238
|
+
function resolveSectionChildren(styles, chain, children) {
|
|
239
|
+
let changed = false;
|
|
240
|
+
const out = [];
|
|
241
|
+
for (const child of children) {
|
|
242
|
+
let resolved;
|
|
243
|
+
if ((0, document_schema_js.isHeadingGroupNode)(child)) resolved = resolveHeadingGroup(styles, chain, child);
|
|
244
|
+
else if ((0, document_schema_js.isListGroupNode)(child)) resolved = resolveListGroup(styles, chain, child);
|
|
245
|
+
else if (child.kind === "paragraph") resolved = resolveParagraphLeaf(styles, chain, child);
|
|
246
|
+
else resolved = child;
|
|
247
|
+
changed ||= resolved !== child;
|
|
248
|
+
out.push(resolved);
|
|
249
|
+
}
|
|
250
|
+
return changed ? out : children;
|
|
251
|
+
}
|
|
252
|
+
function resolveListChildren(styles, chain, children) {
|
|
253
|
+
let changed = false;
|
|
254
|
+
const out = [];
|
|
255
|
+
for (const child of children) {
|
|
256
|
+
let resolved;
|
|
257
|
+
if ((0, document_schema_js.isListGroupNode)(child)) resolved = resolveListGroup(styles, chain, child);
|
|
258
|
+
else if (child.kind === "paragraph") resolved = resolveParagraphLeaf(styles, chain, child);
|
|
259
|
+
else resolved = child;
|
|
260
|
+
changed ||= resolved !== child;
|
|
261
|
+
out.push(resolved);
|
|
262
|
+
}
|
|
263
|
+
return changed ? out : children;
|
|
264
|
+
}
|
|
265
|
+
function resolveParagraphLeaf(styles, chain, leaf) {
|
|
266
|
+
if (chain.length === 0) return leaf;
|
|
267
|
+
return applyEntry((0, document_schema_js.resolveStyleChain)(styles, chain), leaf);
|
|
268
|
+
}
|
|
269
|
+
function applyEntry(entry, paragraph) {
|
|
270
|
+
const withParagraph = (0, document_schema_js.applyParagraphStyleProperties)(entry.paragraph, paragraph);
|
|
271
|
+
const runProperties = entry.run;
|
|
272
|
+
if (runProperties === void 0) return withParagraph;
|
|
273
|
+
return {
|
|
274
|
+
...withParagraph,
|
|
275
|
+
runs: withParagraph.runs.map((run) => (0, document_schema_js.applyRunStyleProperties)(runProperties, run))
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function isShapeGroup(child) {
|
|
279
|
+
return "node" in child;
|
|
280
|
+
}
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/outline/node.ts
|
|
283
|
+
function isOutlineLeaf(value) {
|
|
284
|
+
return (0, document_schema_js.isPackageLeaf)(value);
|
|
285
|
+
}
|
|
286
|
+
function isOutlineChild(value) {
|
|
287
|
+
return isOutlineNode(value) || isOutlineLeaf(value);
|
|
288
|
+
}
|
|
289
|
+
function isOutlineNode(value) {
|
|
290
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
291
|
+
if (!("text" in value) || typeof value.text !== "string") return false;
|
|
292
|
+
if (!("level" in value) || typeof value.level !== "number" || !Number.isFinite(value.level)) return false;
|
|
293
|
+
if (!("children" in value) || !Array.isArray(value.children)) return false;
|
|
294
|
+
return value.children.every(isOutlineChild);
|
|
295
|
+
}
|
|
296
|
+
const OutlineNodeSchema = zod.z.custom(isOutlineNode);
|
|
297
|
+
//#endregion
|
|
298
|
+
//#region src/outline/hash.ts
|
|
299
|
+
function stableContentHash(value) {
|
|
300
|
+
return sha256Hex(new TextEncoder().encode(JSON.stringify(canonicalise(stripSchemaKeys(value)))));
|
|
301
|
+
}
|
|
302
|
+
function isRecord(value) {
|
|
303
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
304
|
+
}
|
|
305
|
+
function stripSchemaKeys(value) {
|
|
306
|
+
if (Array.isArray(value)) return value.map(stripSchemaKeys);
|
|
307
|
+
if (isRecord(value)) {
|
|
308
|
+
const stripped = {};
|
|
309
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
310
|
+
if (key === "$schema") continue;
|
|
311
|
+
stripped[key] = stripSchemaKeys(entry);
|
|
312
|
+
}
|
|
313
|
+
return stripped;
|
|
314
|
+
}
|
|
315
|
+
return value;
|
|
316
|
+
}
|
|
317
|
+
function canonicalise(value) {
|
|
318
|
+
if (Array.isArray(value)) return value.map(canonicalise);
|
|
319
|
+
if (isRecord(value)) {
|
|
320
|
+
const sorted = {};
|
|
321
|
+
for (const key of Object.keys(value).sort()) sorted[key] = canonicalise(value[key]);
|
|
322
|
+
return sorted;
|
|
323
|
+
}
|
|
324
|
+
return value;
|
|
325
|
+
}
|
|
326
|
+
function sha256Hex(bytes) {
|
|
327
|
+
const digest = sha256(bytes);
|
|
328
|
+
let hex = "";
|
|
329
|
+
for (const byte of digest) hex += byte.toString(16).padStart(2, "0");
|
|
330
|
+
return hex;
|
|
331
|
+
}
|
|
332
|
+
const K = [
|
|
333
|
+
1116352408,
|
|
334
|
+
1899447441,
|
|
335
|
+
3049323471,
|
|
336
|
+
3921009573,
|
|
337
|
+
961987163,
|
|
338
|
+
1508970993,
|
|
339
|
+
2453635748,
|
|
340
|
+
2870763221,
|
|
341
|
+
3624381080,
|
|
342
|
+
310598401,
|
|
343
|
+
607225278,
|
|
344
|
+
1426881987,
|
|
345
|
+
1925078388,
|
|
346
|
+
2162078206,
|
|
347
|
+
2614888103,
|
|
348
|
+
3248222580,
|
|
349
|
+
3835390401,
|
|
350
|
+
4022224774,
|
|
351
|
+
264347078,
|
|
352
|
+
604807628,
|
|
353
|
+
770255983,
|
|
354
|
+
1249150122,
|
|
355
|
+
1555081692,
|
|
356
|
+
1996064986,
|
|
357
|
+
2554220882,
|
|
358
|
+
2821834349,
|
|
359
|
+
2952996808,
|
|
360
|
+
3210313671,
|
|
361
|
+
3336571891,
|
|
362
|
+
3584528711,
|
|
363
|
+
113926993,
|
|
364
|
+
338241895,
|
|
365
|
+
666307205,
|
|
366
|
+
773529912,
|
|
367
|
+
1294757372,
|
|
368
|
+
1396182291,
|
|
369
|
+
1695183700,
|
|
370
|
+
1986661051,
|
|
371
|
+
2177026350,
|
|
372
|
+
2456956037,
|
|
373
|
+
2730485921,
|
|
374
|
+
2820302411,
|
|
375
|
+
3259730800,
|
|
376
|
+
3345764771,
|
|
377
|
+
3516065817,
|
|
378
|
+
3600352804,
|
|
379
|
+
4094571909,
|
|
380
|
+
275423344,
|
|
381
|
+
430227734,
|
|
382
|
+
506948616,
|
|
383
|
+
659060556,
|
|
384
|
+
883997877,
|
|
385
|
+
958139571,
|
|
386
|
+
1322822218,
|
|
387
|
+
1537002063,
|
|
388
|
+
1747873779,
|
|
389
|
+
1955562222,
|
|
390
|
+
2024104815,
|
|
391
|
+
2227730452,
|
|
392
|
+
2361852424,
|
|
393
|
+
2428436474,
|
|
394
|
+
2756734187,
|
|
395
|
+
3204031479,
|
|
396
|
+
3329325298
|
|
397
|
+
];
|
|
398
|
+
function rotr(x, n) {
|
|
399
|
+
return (x >>> n | x << 32 - n) >>> 0;
|
|
400
|
+
}
|
|
401
|
+
function sha256(bytes) {
|
|
402
|
+
const bitLength = bytes.length * 8;
|
|
403
|
+
const paddedLength = (bytes.length + 8 >> 6) + 1 << 6;
|
|
404
|
+
const padded = new Uint8Array(paddedLength);
|
|
405
|
+
padded.set(bytes);
|
|
406
|
+
padded[bytes.length] = 128;
|
|
407
|
+
const view = new DataView(padded.buffer);
|
|
408
|
+
view.setUint32(paddedLength - 8, Math.floor(bitLength / 4294967296));
|
|
409
|
+
view.setUint32(paddedLength - 4, bitLength >>> 0);
|
|
410
|
+
let h0 = 1779033703;
|
|
411
|
+
let h1 = 3144134277;
|
|
412
|
+
let h2 = 1013904242;
|
|
413
|
+
let h3 = 2773480762;
|
|
414
|
+
let h4 = 1359893119;
|
|
415
|
+
let h5 = 2600822924;
|
|
416
|
+
let h6 = 528734635;
|
|
417
|
+
let h7 = 1541459225;
|
|
418
|
+
const w = /* @__PURE__ */ new Uint32Array(64);
|
|
419
|
+
for (let offset = 0; offset < paddedLength; offset += 64) {
|
|
420
|
+
for (let i = 0; i < 16; i++) w[i] = view.getUint32(offset + i * 4);
|
|
421
|
+
for (let i = 16; i < 64; i++) {
|
|
422
|
+
const w15 = w[i - 15];
|
|
423
|
+
const w2 = w[i - 2];
|
|
424
|
+
const s0 = rotr(w15, 7) ^ rotr(w15, 18) ^ w15 >>> 3;
|
|
425
|
+
const s1 = rotr(w2, 17) ^ rotr(w2, 19) ^ w2 >>> 10;
|
|
426
|
+
w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
|
|
427
|
+
}
|
|
428
|
+
let a = h0;
|
|
429
|
+
let b = h1;
|
|
430
|
+
let c = h2;
|
|
431
|
+
let d = h3;
|
|
432
|
+
let e = h4;
|
|
433
|
+
let f = h5;
|
|
434
|
+
let g = h6;
|
|
435
|
+
let h = h7;
|
|
436
|
+
for (let i = 0; i < 64; i++) {
|
|
437
|
+
const s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
438
|
+
const ch = e & f ^ ~e & g;
|
|
439
|
+
const temp1 = h + s1 + ch + K[i] + w[i] >>> 0;
|
|
440
|
+
const temp2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
|
|
441
|
+
h = g;
|
|
442
|
+
g = f;
|
|
443
|
+
f = e;
|
|
444
|
+
e = d + temp1 >>> 0;
|
|
445
|
+
d = c;
|
|
446
|
+
c = b;
|
|
447
|
+
b = a;
|
|
448
|
+
a = temp1 + temp2 >>> 0;
|
|
449
|
+
}
|
|
450
|
+
h0 = h0 + a >>> 0;
|
|
451
|
+
h1 = h1 + b >>> 0;
|
|
452
|
+
h2 = h2 + c >>> 0;
|
|
453
|
+
h3 = h3 + d >>> 0;
|
|
454
|
+
h4 = h4 + e >>> 0;
|
|
455
|
+
h5 = h5 + f >>> 0;
|
|
456
|
+
h6 = h6 + g >>> 0;
|
|
457
|
+
h7 = h7 + h >>> 0;
|
|
458
|
+
}
|
|
459
|
+
const digest = /* @__PURE__ */ new Uint8Array(32);
|
|
460
|
+
const out = new DataView(digest.buffer);
|
|
461
|
+
out.setUint32(0, h0);
|
|
462
|
+
out.setUint32(4, h1);
|
|
463
|
+
out.setUint32(8, h2);
|
|
464
|
+
out.setUint32(12, h3);
|
|
465
|
+
out.setUint32(16, h4);
|
|
466
|
+
out.setUint32(20, h5);
|
|
467
|
+
out.setUint32(24, h6);
|
|
468
|
+
out.setUint32(28, h7);
|
|
469
|
+
return digest;
|
|
470
|
+
}
|
|
471
|
+
//#endregion
|
|
472
|
+
//#region src/outline/helpers.ts
|
|
473
|
+
function flattenOutline(children) {
|
|
474
|
+
const leaves = [];
|
|
475
|
+
const walk = (subtree) => {
|
|
476
|
+
for (const child of subtree) if (isOutlineNode(child)) walk(child.children);
|
|
477
|
+
else leaves.push(child);
|
|
478
|
+
};
|
|
479
|
+
walk(children);
|
|
480
|
+
return leaves;
|
|
481
|
+
}
|
|
482
|
+
function outlineLeafText(leaf) {
|
|
483
|
+
if ("runs" in leaf) return leaf.runs.map((run) => run.text).join("");
|
|
484
|
+
if ("rows" in leaf) return leaf.rows.map((row) => row.cells.map((cell) => blockTexts(cell.blocks).join(" ")).join(" ")).join("\n");
|
|
485
|
+
if ("base64" in leaf) return leaf.altText ?? "";
|
|
486
|
+
if ("mathml" in leaf) return leaf.presentation?.latex ?? "";
|
|
487
|
+
return "";
|
|
488
|
+
}
|
|
489
|
+
function blockTexts(blocks) {
|
|
490
|
+
const parts = [];
|
|
491
|
+
for (const block of blocks) {
|
|
492
|
+
if (block.kind === "paragraph") parts.push(block.runs.map((run) => run.text).join(""));
|
|
493
|
+
if (block.kind === "table") parts.push(...block.rows.flatMap((row) => row.cells.map((cell) => blockTexts(cell.blocks).join(" "))));
|
|
494
|
+
}
|
|
495
|
+
return parts;
|
|
496
|
+
}
|
|
497
|
+
function leafContentHash(leaf) {
|
|
498
|
+
return stableContentHash(leaf);
|
|
499
|
+
}
|
|
500
|
+
//#endregion
|
|
501
|
+
exports.OutlineNodeSchema = OutlineNodeSchema;
|
|
502
|
+
exports.buildOutline = buildOutline;
|
|
503
|
+
exports.effectivePackage = effectivePackage;
|
|
504
|
+
exports.flattenOutline = flattenOutline;
|
|
505
|
+
exports.isOutlineChild = isOutlineChild;
|
|
506
|
+
exports.isOutlineLeaf = isOutlineLeaf;
|
|
507
|
+
exports.isOutlineNode = isOutlineNode;
|
|
508
|
+
exports.leafContentHash = leafContentHash;
|
|
509
|
+
exports.outlineLeafText = outlineLeafText;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DocumentPackage, PackageLeaf } from "document-schema.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/outline/node.d.ts
|
|
4
|
+
type OutlineLeaf = PackageLeaf;
|
|
5
|
+
type OutlineChild = OutlineNode | OutlineLeaf;
|
|
6
|
+
interface OutlineNode {
|
|
7
|
+
readonly text: string;
|
|
8
|
+
readonly level: number;
|
|
9
|
+
children: OutlineChild[];
|
|
10
|
+
}
|
|
11
|
+
declare function isOutlineLeaf(value: unknown): value is OutlineLeaf;
|
|
12
|
+
declare function isOutlineChild(value: unknown): value is OutlineChild;
|
|
13
|
+
declare function isOutlineNode(value: unknown): value is OutlineNode;
|
|
14
|
+
declare const OutlineNodeSchema: z.ZodCustom<OutlineNode, OutlineNode>;
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/outline/build.d.ts
|
|
17
|
+
declare function buildOutline(pkg: DocumentPackage): OutlineChild[];
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/outline/effective.d.ts
|
|
20
|
+
declare function effectivePackage(pkg: DocumentPackage): DocumentPackage;
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/outline/helpers.d.ts
|
|
23
|
+
declare function flattenOutline(children: readonly OutlineChild[]): OutlineLeaf[];
|
|
24
|
+
declare function outlineLeafText(leaf: OutlineLeaf): string;
|
|
25
|
+
declare function leafContentHash(leaf: OutlineLeaf): string;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { OutlineChild, OutlineLeaf, OutlineNode, OutlineNodeSchema, buildOutline, effectivePackage, flattenOutline, isOutlineChild, isOutlineLeaf, isOutlineNode, leafContentHash, outlineLeafText };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { DocumentPackage, PackageLeaf } from "document-schema.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/outline/node.d.ts
|
|
4
|
+
type OutlineLeaf = PackageLeaf;
|
|
5
|
+
type OutlineChild = OutlineNode | OutlineLeaf;
|
|
6
|
+
interface OutlineNode {
|
|
7
|
+
readonly text: string;
|
|
8
|
+
readonly level: number;
|
|
9
|
+
children: OutlineChild[];
|
|
10
|
+
}
|
|
11
|
+
declare function isOutlineLeaf(value: unknown): value is OutlineLeaf;
|
|
12
|
+
declare function isOutlineChild(value: unknown): value is OutlineChild;
|
|
13
|
+
declare function isOutlineNode(value: unknown): value is OutlineNode;
|
|
14
|
+
declare const OutlineNodeSchema: z.ZodCustom<OutlineNode, OutlineNode>;
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/outline/build.d.ts
|
|
17
|
+
declare function buildOutline(pkg: DocumentPackage): OutlineChild[];
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/outline/effective.d.ts
|
|
20
|
+
declare function effectivePackage(pkg: DocumentPackage): DocumentPackage;
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/outline/helpers.d.ts
|
|
23
|
+
declare function flattenOutline(children: readonly OutlineChild[]): OutlineLeaf[];
|
|
24
|
+
declare function outlineLeafText(leaf: OutlineLeaf): string;
|
|
25
|
+
declare function leafContentHash(leaf: OutlineLeaf): string;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { OutlineChild, OutlineLeaf, OutlineNode, OutlineNodeSchema, buildOutline, effectivePackage, flattenOutline, isOutlineChild, isOutlineLeaf, isOutlineNode, leafContentHash, outlineLeafText };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import { applyParagraphStyleProperties, applyRunStyleProperties, isHeadingGroupNode, isListGroupNode, isPackageLeaf, isShapeGroupNode, resolveStyleChain } from "document-schema.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/outline/build.ts
|
|
4
|
+
function buildOutline(pkg) {
|
|
5
|
+
switch (pkg.kind) {
|
|
6
|
+
case "wordprocessing": return wordprocessingOutline(pkg.children);
|
|
7
|
+
case "presentation": return presentationOutline(pkg.children);
|
|
8
|
+
case "spreadsheet": return spreadsheetOutline(pkg.children);
|
|
9
|
+
case "drawing": return drawingOutline(pkg.children);
|
|
10
|
+
case "formula": return formulaOutline(pkg.children[0]);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function wordprocessingOutline(sections) {
|
|
14
|
+
const root = [];
|
|
15
|
+
const headingStack = [];
|
|
16
|
+
const listStack = [];
|
|
17
|
+
const headingScope = () => headingStack.at(-1)?.children ?? root;
|
|
18
|
+
const walk = (children) => {
|
|
19
|
+
for (const child of children) if (isHeadingGroupNode(child)) {
|
|
20
|
+
listStack.length = 0;
|
|
21
|
+
const level = child.node.headingLevel;
|
|
22
|
+
for (let top = headingStack.at(-1); top !== void 0 && top.level >= level; top = headingStack.at(-1)) headingStack.pop();
|
|
23
|
+
const node = {
|
|
24
|
+
text: paragraphText(child.node),
|
|
25
|
+
level,
|
|
26
|
+
children: []
|
|
27
|
+
};
|
|
28
|
+
const parent = headingStack.at(-1);
|
|
29
|
+
(parent !== void 0 ? parent.children : root).push(node);
|
|
30
|
+
headingStack.push(node);
|
|
31
|
+
walk(child.children);
|
|
32
|
+
} else if (isListGroupNode(child)) {
|
|
33
|
+
openListGroup(listStack, headingScope(), paragraphText(child.node), child.node.list.level);
|
|
34
|
+
walk(child.children);
|
|
35
|
+
} else if (child.kind === "paragraph") {
|
|
36
|
+
listStack.length = 0;
|
|
37
|
+
headingScope().push(child);
|
|
38
|
+
} else {
|
|
39
|
+
const parent = listStack.at(-1);
|
|
40
|
+
(parent !== void 0 ? parent.children : headingScope()).push(child);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
for (const section of sections) walk(section.children);
|
|
44
|
+
return root;
|
|
45
|
+
}
|
|
46
|
+
function presentationOutline(slides) {
|
|
47
|
+
return slides.map((slide, index) => {
|
|
48
|
+
const group = {
|
|
49
|
+
text: `Slide ${String(index + 1)}`,
|
|
50
|
+
level: 1,
|
|
51
|
+
children: []
|
|
52
|
+
};
|
|
53
|
+
const listStack = [];
|
|
54
|
+
const walk = (children) => {
|
|
55
|
+
for (const child of children) if (isListGroupNode(child)) {
|
|
56
|
+
openListGroup(listStack, group.children, paragraphText(child.node), child.node.list.level);
|
|
57
|
+
walk(child.children);
|
|
58
|
+
} else if (child.kind === "paragraph") {
|
|
59
|
+
listStack.length = 0;
|
|
60
|
+
group.children.push(child);
|
|
61
|
+
} else {
|
|
62
|
+
const parent = listStack.at(-1);
|
|
63
|
+
(parent !== void 0 ? parent.children : group.children).push(child);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
for (const shape of slide.children) walk(shape.children);
|
|
67
|
+
return group;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function spreadsheetOutline(sheets) {
|
|
71
|
+
return sheets.map((sheet) => ({
|
|
72
|
+
text: sheet.node.name,
|
|
73
|
+
level: 1,
|
|
74
|
+
children: [...sheet.children]
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
function drawingOutline(pages) {
|
|
78
|
+
return pages.map((page, index) => ({
|
|
79
|
+
text: `Page ${String(index + 1)}`,
|
|
80
|
+
level: 1,
|
|
81
|
+
children: page.children.flatMap((child) => isShapeGroupNode(child) ? flattenShapeChildren(child) : [child])
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
function flattenShapeChildren(group) {
|
|
85
|
+
return flattenListFlow(group.children);
|
|
86
|
+
}
|
|
87
|
+
function flattenListFlow(children) {
|
|
88
|
+
const leaves = [];
|
|
89
|
+
for (const child of children) if (isListGroupNode(child)) leaves.push(child.node, ...flattenListFlow(child.children));
|
|
90
|
+
else leaves.push(child);
|
|
91
|
+
return leaves;
|
|
92
|
+
}
|
|
93
|
+
function formulaOutline(formula) {
|
|
94
|
+
return [{
|
|
95
|
+
text: formula.presentation?.latex ?? "",
|
|
96
|
+
level: 1,
|
|
97
|
+
children: [formula]
|
|
98
|
+
}];
|
|
99
|
+
}
|
|
100
|
+
function openListGroup(listStack, scopeChildren, text, level) {
|
|
101
|
+
for (let top = listStack.at(-1); top !== void 0 && top.level >= level; top = listStack.at(-1)) listStack.pop();
|
|
102
|
+
const node = {
|
|
103
|
+
text,
|
|
104
|
+
level,
|
|
105
|
+
children: []
|
|
106
|
+
};
|
|
107
|
+
const parent = listStack.at(-1);
|
|
108
|
+
(parent !== void 0 ? parent.children : scopeChildren).push(node);
|
|
109
|
+
listStack.push(node);
|
|
110
|
+
}
|
|
111
|
+
function paragraphText(paragraph) {
|
|
112
|
+
return paragraph.runs.map((run) => run.text).join("");
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/outline/effective.ts
|
|
116
|
+
function effectivePackage(pkg) {
|
|
117
|
+
const styles = pkg.styles;
|
|
118
|
+
if (styles === void 0) return pkg;
|
|
119
|
+
switch (pkg.kind) {
|
|
120
|
+
case "wordprocessing": return withoutStyles({
|
|
121
|
+
...pkg,
|
|
122
|
+
children: pkg.children.map((group) => resolveSectionGroup(styles, [], group))
|
|
123
|
+
});
|
|
124
|
+
case "presentation": return withoutStyles({
|
|
125
|
+
...pkg,
|
|
126
|
+
children: pkg.children.map((group) => resolveSlideGroup(styles, [], group))
|
|
127
|
+
});
|
|
128
|
+
case "spreadsheet": return withoutStyles({
|
|
129
|
+
...pkg,
|
|
130
|
+
children: pkg.children.map(resolveSheetGroup)
|
|
131
|
+
});
|
|
132
|
+
case "drawing": return withoutStyles({
|
|
133
|
+
...pkg,
|
|
134
|
+
children: pkg.children.map((group) => resolveDrawPageGroup(styles, [], group))
|
|
135
|
+
});
|
|
136
|
+
case "formula": return withoutStyles({ ...pkg });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function withoutStyles(pkg) {
|
|
140
|
+
const copy = { ...pkg };
|
|
141
|
+
delete copy.styles;
|
|
142
|
+
return copy;
|
|
143
|
+
}
|
|
144
|
+
function chainWithRef(chain, group) {
|
|
145
|
+
return group.style === void 0 ? chain : [...chain, group.style];
|
|
146
|
+
}
|
|
147
|
+
function resolveSectionGroup(styles, chain, group) {
|
|
148
|
+
const children = resolveSectionChildren(styles, chainWithRef(chain, group), group.children);
|
|
149
|
+
if (group.style === void 0 && children === group.children) return group;
|
|
150
|
+
return {
|
|
151
|
+
node: group.node,
|
|
152
|
+
children
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function resolveSlideGroup(styles, chain, group) {
|
|
156
|
+
const own = chainWithRef(chain, group);
|
|
157
|
+
let changed = group.style !== void 0;
|
|
158
|
+
const children = [];
|
|
159
|
+
for (const shape of group.children) {
|
|
160
|
+
const resolved = resolveShapeGroup(styles, own, shape);
|
|
161
|
+
changed ||= resolved !== shape;
|
|
162
|
+
children.push(resolved);
|
|
163
|
+
}
|
|
164
|
+
if (!changed) return group;
|
|
165
|
+
return {
|
|
166
|
+
node: group.node,
|
|
167
|
+
children
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function resolveSheetGroup(group) {
|
|
171
|
+
return group.style === void 0 ? group : {
|
|
172
|
+
node: group.node,
|
|
173
|
+
children: group.children
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function resolveDrawPageGroup(styles, chain, group) {
|
|
177
|
+
const own = chainWithRef(chain, group);
|
|
178
|
+
let changed = group.style !== void 0;
|
|
179
|
+
const children = [];
|
|
180
|
+
for (const child of group.children) {
|
|
181
|
+
const resolved = isShapeGroup(child) ? resolveShapeGroup(styles, own, child) : child;
|
|
182
|
+
changed ||= resolved !== child;
|
|
183
|
+
children.push(resolved);
|
|
184
|
+
}
|
|
185
|
+
if (!changed) return group;
|
|
186
|
+
return {
|
|
187
|
+
node: group.node,
|
|
188
|
+
children
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function resolveShapeGroup(styles, chain, group) {
|
|
192
|
+
const children = resolveListChildren(styles, chainWithRef(chain, group), group.children);
|
|
193
|
+
if (group.style === void 0 && children === group.children) return group;
|
|
194
|
+
return {
|
|
195
|
+
node: group.node,
|
|
196
|
+
children
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function resolveHeadingGroup(styles, chain, group) {
|
|
200
|
+
const own = chainWithRef(chain, group);
|
|
201
|
+
const entry = own.length > 0 ? resolveStyleChain(styles, own) : void 0;
|
|
202
|
+
let anchor = group.node;
|
|
203
|
+
if (entry !== void 0) {
|
|
204
|
+
const applied = applyEntry(entry, group.node);
|
|
205
|
+
assertResolvedHeadingAnchor(applied);
|
|
206
|
+
anchor = applied;
|
|
207
|
+
}
|
|
208
|
+
const children = resolveSectionChildren(styles, own, group.children);
|
|
209
|
+
if (group.style === void 0 && anchor === group.node && children === group.children) return group;
|
|
210
|
+
return {
|
|
211
|
+
node: anchor,
|
|
212
|
+
children
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function resolveListGroup(styles, chain, group) {
|
|
216
|
+
const own = chainWithRef(chain, group);
|
|
217
|
+
const entry = own.length > 0 ? resolveStyleChain(styles, own) : void 0;
|
|
218
|
+
let anchor = group.node;
|
|
219
|
+
if (entry !== void 0) {
|
|
220
|
+
const applied = applyEntry(entry, group.node);
|
|
221
|
+
assertResolvedListAnchor(applied);
|
|
222
|
+
anchor = applied;
|
|
223
|
+
}
|
|
224
|
+
const children = resolveListChildren(styles, own, group.children);
|
|
225
|
+
if (group.style === void 0 && anchor === group.node && children === group.children) return group;
|
|
226
|
+
return {
|
|
227
|
+
node: anchor,
|
|
228
|
+
children
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function assertResolvedHeadingAnchor(paragraph) {
|
|
232
|
+
if (paragraph.headingLevel === void 0) throw new Error("effectivePackage: resolution dropped a heading anchor's headingLevel");
|
|
233
|
+
}
|
|
234
|
+
function assertResolvedListAnchor(paragraph) {
|
|
235
|
+
if (paragraph.list === void 0) throw new Error("effectivePackage: resolution dropped a list anchor's list membership");
|
|
236
|
+
}
|
|
237
|
+
function resolveSectionChildren(styles, chain, children) {
|
|
238
|
+
let changed = false;
|
|
239
|
+
const out = [];
|
|
240
|
+
for (const child of children) {
|
|
241
|
+
let resolved;
|
|
242
|
+
if (isHeadingGroupNode(child)) resolved = resolveHeadingGroup(styles, chain, child);
|
|
243
|
+
else if (isListGroupNode(child)) resolved = resolveListGroup(styles, chain, child);
|
|
244
|
+
else if (child.kind === "paragraph") resolved = resolveParagraphLeaf(styles, chain, child);
|
|
245
|
+
else resolved = child;
|
|
246
|
+
changed ||= resolved !== child;
|
|
247
|
+
out.push(resolved);
|
|
248
|
+
}
|
|
249
|
+
return changed ? out : children;
|
|
250
|
+
}
|
|
251
|
+
function resolveListChildren(styles, chain, children) {
|
|
252
|
+
let changed = false;
|
|
253
|
+
const out = [];
|
|
254
|
+
for (const child of children) {
|
|
255
|
+
let resolved;
|
|
256
|
+
if (isListGroupNode(child)) resolved = resolveListGroup(styles, chain, child);
|
|
257
|
+
else if (child.kind === "paragraph") resolved = resolveParagraphLeaf(styles, chain, child);
|
|
258
|
+
else resolved = child;
|
|
259
|
+
changed ||= resolved !== child;
|
|
260
|
+
out.push(resolved);
|
|
261
|
+
}
|
|
262
|
+
return changed ? out : children;
|
|
263
|
+
}
|
|
264
|
+
function resolveParagraphLeaf(styles, chain, leaf) {
|
|
265
|
+
if (chain.length === 0) return leaf;
|
|
266
|
+
return applyEntry(resolveStyleChain(styles, chain), leaf);
|
|
267
|
+
}
|
|
268
|
+
function applyEntry(entry, paragraph) {
|
|
269
|
+
const withParagraph = applyParagraphStyleProperties(entry.paragraph, paragraph);
|
|
270
|
+
const runProperties = entry.run;
|
|
271
|
+
if (runProperties === void 0) return withParagraph;
|
|
272
|
+
return {
|
|
273
|
+
...withParagraph,
|
|
274
|
+
runs: withParagraph.runs.map((run) => applyRunStyleProperties(runProperties, run))
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function isShapeGroup(child) {
|
|
278
|
+
return "node" in child;
|
|
279
|
+
}
|
|
280
|
+
//#endregion
|
|
281
|
+
//#region src/outline/node.ts
|
|
282
|
+
function isOutlineLeaf(value) {
|
|
283
|
+
return isPackageLeaf(value);
|
|
284
|
+
}
|
|
285
|
+
function isOutlineChild(value) {
|
|
286
|
+
return isOutlineNode(value) || isOutlineLeaf(value);
|
|
287
|
+
}
|
|
288
|
+
function isOutlineNode(value) {
|
|
289
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
290
|
+
if (!("text" in value) || typeof value.text !== "string") return false;
|
|
291
|
+
if (!("level" in value) || typeof value.level !== "number" || !Number.isFinite(value.level)) return false;
|
|
292
|
+
if (!("children" in value) || !Array.isArray(value.children)) return false;
|
|
293
|
+
return value.children.every(isOutlineChild);
|
|
294
|
+
}
|
|
295
|
+
const OutlineNodeSchema = z.custom(isOutlineNode);
|
|
296
|
+
//#endregion
|
|
297
|
+
//#region src/outline/hash.ts
|
|
298
|
+
function stableContentHash(value) {
|
|
299
|
+
return sha256Hex(new TextEncoder().encode(JSON.stringify(canonicalise(stripSchemaKeys(value)))));
|
|
300
|
+
}
|
|
301
|
+
function isRecord(value) {
|
|
302
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
303
|
+
}
|
|
304
|
+
function stripSchemaKeys(value) {
|
|
305
|
+
if (Array.isArray(value)) return value.map(stripSchemaKeys);
|
|
306
|
+
if (isRecord(value)) {
|
|
307
|
+
const stripped = {};
|
|
308
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
309
|
+
if (key === "$schema") continue;
|
|
310
|
+
stripped[key] = stripSchemaKeys(entry);
|
|
311
|
+
}
|
|
312
|
+
return stripped;
|
|
313
|
+
}
|
|
314
|
+
return value;
|
|
315
|
+
}
|
|
316
|
+
function canonicalise(value) {
|
|
317
|
+
if (Array.isArray(value)) return value.map(canonicalise);
|
|
318
|
+
if (isRecord(value)) {
|
|
319
|
+
const sorted = {};
|
|
320
|
+
for (const key of Object.keys(value).sort()) sorted[key] = canonicalise(value[key]);
|
|
321
|
+
return sorted;
|
|
322
|
+
}
|
|
323
|
+
return value;
|
|
324
|
+
}
|
|
325
|
+
function sha256Hex(bytes) {
|
|
326
|
+
const digest = sha256(bytes);
|
|
327
|
+
let hex = "";
|
|
328
|
+
for (const byte of digest) hex += byte.toString(16).padStart(2, "0");
|
|
329
|
+
return hex;
|
|
330
|
+
}
|
|
331
|
+
const K = [
|
|
332
|
+
1116352408,
|
|
333
|
+
1899447441,
|
|
334
|
+
3049323471,
|
|
335
|
+
3921009573,
|
|
336
|
+
961987163,
|
|
337
|
+
1508970993,
|
|
338
|
+
2453635748,
|
|
339
|
+
2870763221,
|
|
340
|
+
3624381080,
|
|
341
|
+
310598401,
|
|
342
|
+
607225278,
|
|
343
|
+
1426881987,
|
|
344
|
+
1925078388,
|
|
345
|
+
2162078206,
|
|
346
|
+
2614888103,
|
|
347
|
+
3248222580,
|
|
348
|
+
3835390401,
|
|
349
|
+
4022224774,
|
|
350
|
+
264347078,
|
|
351
|
+
604807628,
|
|
352
|
+
770255983,
|
|
353
|
+
1249150122,
|
|
354
|
+
1555081692,
|
|
355
|
+
1996064986,
|
|
356
|
+
2554220882,
|
|
357
|
+
2821834349,
|
|
358
|
+
2952996808,
|
|
359
|
+
3210313671,
|
|
360
|
+
3336571891,
|
|
361
|
+
3584528711,
|
|
362
|
+
113926993,
|
|
363
|
+
338241895,
|
|
364
|
+
666307205,
|
|
365
|
+
773529912,
|
|
366
|
+
1294757372,
|
|
367
|
+
1396182291,
|
|
368
|
+
1695183700,
|
|
369
|
+
1986661051,
|
|
370
|
+
2177026350,
|
|
371
|
+
2456956037,
|
|
372
|
+
2730485921,
|
|
373
|
+
2820302411,
|
|
374
|
+
3259730800,
|
|
375
|
+
3345764771,
|
|
376
|
+
3516065817,
|
|
377
|
+
3600352804,
|
|
378
|
+
4094571909,
|
|
379
|
+
275423344,
|
|
380
|
+
430227734,
|
|
381
|
+
506948616,
|
|
382
|
+
659060556,
|
|
383
|
+
883997877,
|
|
384
|
+
958139571,
|
|
385
|
+
1322822218,
|
|
386
|
+
1537002063,
|
|
387
|
+
1747873779,
|
|
388
|
+
1955562222,
|
|
389
|
+
2024104815,
|
|
390
|
+
2227730452,
|
|
391
|
+
2361852424,
|
|
392
|
+
2428436474,
|
|
393
|
+
2756734187,
|
|
394
|
+
3204031479,
|
|
395
|
+
3329325298
|
|
396
|
+
];
|
|
397
|
+
function rotr(x, n) {
|
|
398
|
+
return (x >>> n | x << 32 - n) >>> 0;
|
|
399
|
+
}
|
|
400
|
+
function sha256(bytes) {
|
|
401
|
+
const bitLength = bytes.length * 8;
|
|
402
|
+
const paddedLength = (bytes.length + 8 >> 6) + 1 << 6;
|
|
403
|
+
const padded = new Uint8Array(paddedLength);
|
|
404
|
+
padded.set(bytes);
|
|
405
|
+
padded[bytes.length] = 128;
|
|
406
|
+
const view = new DataView(padded.buffer);
|
|
407
|
+
view.setUint32(paddedLength - 8, Math.floor(bitLength / 4294967296));
|
|
408
|
+
view.setUint32(paddedLength - 4, bitLength >>> 0);
|
|
409
|
+
let h0 = 1779033703;
|
|
410
|
+
let h1 = 3144134277;
|
|
411
|
+
let h2 = 1013904242;
|
|
412
|
+
let h3 = 2773480762;
|
|
413
|
+
let h4 = 1359893119;
|
|
414
|
+
let h5 = 2600822924;
|
|
415
|
+
let h6 = 528734635;
|
|
416
|
+
let h7 = 1541459225;
|
|
417
|
+
const w = /* @__PURE__ */ new Uint32Array(64);
|
|
418
|
+
for (let offset = 0; offset < paddedLength; offset += 64) {
|
|
419
|
+
for (let i = 0; i < 16; i++) w[i] = view.getUint32(offset + i * 4);
|
|
420
|
+
for (let i = 16; i < 64; i++) {
|
|
421
|
+
const w15 = w[i - 15];
|
|
422
|
+
const w2 = w[i - 2];
|
|
423
|
+
const s0 = rotr(w15, 7) ^ rotr(w15, 18) ^ w15 >>> 3;
|
|
424
|
+
const s1 = rotr(w2, 17) ^ rotr(w2, 19) ^ w2 >>> 10;
|
|
425
|
+
w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
|
|
426
|
+
}
|
|
427
|
+
let a = h0;
|
|
428
|
+
let b = h1;
|
|
429
|
+
let c = h2;
|
|
430
|
+
let d = h3;
|
|
431
|
+
let e = h4;
|
|
432
|
+
let f = h5;
|
|
433
|
+
let g = h6;
|
|
434
|
+
let h = h7;
|
|
435
|
+
for (let i = 0; i < 64; i++) {
|
|
436
|
+
const s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
437
|
+
const ch = e & f ^ ~e & g;
|
|
438
|
+
const temp1 = h + s1 + ch + K[i] + w[i] >>> 0;
|
|
439
|
+
const temp2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
|
|
440
|
+
h = g;
|
|
441
|
+
g = f;
|
|
442
|
+
f = e;
|
|
443
|
+
e = d + temp1 >>> 0;
|
|
444
|
+
d = c;
|
|
445
|
+
c = b;
|
|
446
|
+
b = a;
|
|
447
|
+
a = temp1 + temp2 >>> 0;
|
|
448
|
+
}
|
|
449
|
+
h0 = h0 + a >>> 0;
|
|
450
|
+
h1 = h1 + b >>> 0;
|
|
451
|
+
h2 = h2 + c >>> 0;
|
|
452
|
+
h3 = h3 + d >>> 0;
|
|
453
|
+
h4 = h4 + e >>> 0;
|
|
454
|
+
h5 = h5 + f >>> 0;
|
|
455
|
+
h6 = h6 + g >>> 0;
|
|
456
|
+
h7 = h7 + h >>> 0;
|
|
457
|
+
}
|
|
458
|
+
const digest = /* @__PURE__ */ new Uint8Array(32);
|
|
459
|
+
const out = new DataView(digest.buffer);
|
|
460
|
+
out.setUint32(0, h0);
|
|
461
|
+
out.setUint32(4, h1);
|
|
462
|
+
out.setUint32(8, h2);
|
|
463
|
+
out.setUint32(12, h3);
|
|
464
|
+
out.setUint32(16, h4);
|
|
465
|
+
out.setUint32(20, h5);
|
|
466
|
+
out.setUint32(24, h6);
|
|
467
|
+
out.setUint32(28, h7);
|
|
468
|
+
return digest;
|
|
469
|
+
}
|
|
470
|
+
//#endregion
|
|
471
|
+
//#region src/outline/helpers.ts
|
|
472
|
+
function flattenOutline(children) {
|
|
473
|
+
const leaves = [];
|
|
474
|
+
const walk = (subtree) => {
|
|
475
|
+
for (const child of subtree) if (isOutlineNode(child)) walk(child.children);
|
|
476
|
+
else leaves.push(child);
|
|
477
|
+
};
|
|
478
|
+
walk(children);
|
|
479
|
+
return leaves;
|
|
480
|
+
}
|
|
481
|
+
function outlineLeafText(leaf) {
|
|
482
|
+
if ("runs" in leaf) return leaf.runs.map((run) => run.text).join("");
|
|
483
|
+
if ("rows" in leaf) return leaf.rows.map((row) => row.cells.map((cell) => blockTexts(cell.blocks).join(" ")).join(" ")).join("\n");
|
|
484
|
+
if ("base64" in leaf) return leaf.altText ?? "";
|
|
485
|
+
if ("mathml" in leaf) return leaf.presentation?.latex ?? "";
|
|
486
|
+
return "";
|
|
487
|
+
}
|
|
488
|
+
function blockTexts(blocks) {
|
|
489
|
+
const parts = [];
|
|
490
|
+
for (const block of blocks) {
|
|
491
|
+
if (block.kind === "paragraph") parts.push(block.runs.map((run) => run.text).join(""));
|
|
492
|
+
if (block.kind === "table") parts.push(...block.rows.flatMap((row) => row.cells.map((cell) => blockTexts(cell.blocks).join(" "))));
|
|
493
|
+
}
|
|
494
|
+
return parts;
|
|
495
|
+
}
|
|
496
|
+
function leafContentHash(leaf) {
|
|
497
|
+
return stableContentHash(leaf);
|
|
498
|
+
}
|
|
499
|
+
//#endregion
|
|
500
|
+
export { OutlineNodeSchema, buildOutline, effectivePackage, flattenOutline, isOutlineChild, isOutlineLeaf, isOutlineNode, leafContentHash, outlineLeafText };
|
package/package.json
CHANGED
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "document-outline.js",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Utilities for consumers holding a tree-form DocumentPackage - the TOC outline projection, effective-property resolution, and flatten/leaf-text/stable-hash helpers, the outline package for the documents.js family.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/ExaDev/document-outline.js.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/ExaDev/document-outline.js#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/ExaDev/document-outline.js/issues"
|
|
13
|
+
},
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": {
|
|
18
|
+
"import": "./dist/index.d.ts",
|
|
19
|
+
"require": "./dist/index.d.cts"
|
|
20
|
+
},
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"require": "./dist/index.cjs"
|
|
23
|
+
},
|
|
24
|
+
"./*": {
|
|
25
|
+
"types": {
|
|
26
|
+
"import": "./dist/*.d.ts",
|
|
27
|
+
"require": "./dist/*.d.cts"
|
|
28
|
+
},
|
|
29
|
+
"import": "./dist/*.js",
|
|
30
|
+
"require": "./dist/*.cjs"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"main": "./dist/index.cjs",
|
|
34
|
+
"module": "./dist/index.js",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"files": [
|
|
37
|
+
"dist"
|
|
38
|
+
],
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public",
|
|
41
|
+
"provenance": true,
|
|
42
|
+
"registry": "https://registry.npmjs.org/"
|
|
43
|
+
},
|
|
44
|
+
"sideEffects": false,
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=20"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "turbo run _build",
|
|
50
|
+
"_build": "tsdown",
|
|
51
|
+
"prepublishOnly": "pnpm run lint && pnpm run typecheck && tsdown && publint && attw --pack",
|
|
52
|
+
"lint": "turbo run _lint",
|
|
53
|
+
"_lint": "eslint . --fix --cache --max-warnings 0",
|
|
54
|
+
"lint:check": "eslint . --max-warnings 0",
|
|
55
|
+
"typecheck": "turbo run _typecheck",
|
|
56
|
+
"_typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.node.json",
|
|
57
|
+
"test": "turbo run _test",
|
|
58
|
+
"_test": "vitest run",
|
|
59
|
+
"test:watch": "vitest",
|
|
60
|
+
"test:workers": "turbo run _test:workers",
|
|
61
|
+
"_test:workers": "vitest run --config vitest.workers.config.ts",
|
|
62
|
+
"prepare": "husky"
|
|
63
|
+
},
|
|
64
|
+
"packageManager": "pnpm@11.6.0",
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"document-schema.js": "^4.0.0",
|
|
67
|
+
"zod": "^4.4.3"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
71
|
+
"@cloudflare/vitest-pool-workers": "^0.20.1",
|
|
72
|
+
"@commitlint/cli": "^21.2.1",
|
|
73
|
+
"@commitlint/config-conventional": "^21.2.0",
|
|
74
|
+
"@eslint/js": "^10.0.1",
|
|
75
|
+
"@semantic-release/changelog": "^7.0.0",
|
|
76
|
+
"@semantic-release/git": "^11.0.1",
|
|
77
|
+
"@types/node": "^26.1.2",
|
|
78
|
+
"eslint": "^10.8.0",
|
|
79
|
+
"globals": "^17.8.0",
|
|
80
|
+
"husky": "^9.1.7",
|
|
81
|
+
"lint-staged": "^17.2.0",
|
|
82
|
+
"publint": "^0.3.21",
|
|
83
|
+
"semantic-release": "^25.0.8",
|
|
84
|
+
"tsdown": "^0.22.13",
|
|
85
|
+
"turbo": "^2.10.8",
|
|
86
|
+
"typescript": "^6.0.3",
|
|
87
|
+
"typescript-eslint": "^8.65.0",
|
|
88
|
+
"vitest": "^4.1.10"
|
|
89
|
+
},
|
|
90
|
+
"lint-staged": {
|
|
91
|
+
"*.ts": "eslint --fix"
|
|
92
|
+
}
|
|
5
93
|
}
|