agentic-documents 0.4.0 → 0.6.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +161 -11
  3. package/dist/{chunk-TFCRPEEM.js → chunk-34C47PDC.js} +1 -1
  4. package/dist/chunk-5IQX2W52.js +195 -0
  5. package/dist/chunk-6UYJV2EJ.js +163 -0
  6. package/dist/{chunk-R5XUTBC3.js → chunk-CN6UAW7B.js} +32 -9
  7. package/dist/chunk-DWVJIKVT.js +8 -0
  8. package/dist/{chunk-YBBQNCMC.js → chunk-GQ5E6T7Z.js} +18 -191
  9. package/dist/{chunk-FZA5LQV3.js → chunk-JBAQEIOH.js} +89 -12
  10. package/dist/chunk-KC5KFB5R.js +354 -0
  11. package/dist/{chunk-IU3YIQ4Y.js → chunk-V6TBTOHW.js} +38 -13
  12. package/dist/cli.js +16 -12
  13. package/dist/client.d.ts +23 -6
  14. package/dist/client.js +9 -2
  15. package/dist/{doc-C_8kCsl7.d.ts → doc-BXG9qG5X.d.ts} +8 -2
  16. package/dist/editor.css +37 -0
  17. package/dist/embed.d.ts +86 -0
  18. package/dist/embed.js +431 -0
  19. package/dist/formats.d.ts +52 -0
  20. package/dist/formats.js +24 -0
  21. package/dist/index.d.ts +12 -10
  22. package/dist/index.js +15 -9
  23. package/dist/live-worker.d.ts +2 -2
  24. package/dist/live-worker.js +7 -4
  25. package/dist/live.d.ts +6 -2
  26. package/dist/live.js +5 -3
  27. package/dist/{mcp-P2C75IIG.js → mcp-ZTUTC4CS.js} +5 -2
  28. package/dist/render-463xHb-x.d.ts +16 -0
  29. package/dist/{review-BQW2Ci2h.d.ts → review-Ds-i5Vnc.d.ts} +2 -16
  30. package/dist/{service-Do2Jwb5U.d.ts → service-Ck6oX-Kq.d.ts} +38 -5
  31. package/dist/ui/_headers +4 -0
  32. package/dist/ui/app.css +1 -0
  33. package/dist/ui/app.js +270 -0
  34. package/dist/ui/assets/inter-latin-ext-wght-normal-HA22NDSG.woff2 +0 -0
  35. package/dist/ui/assets/inter-latin-wght-normal-NRMW37G5.woff2 +0 -0
  36. package/dist/ui/assets/jetbrains-mono-latin-ext-wght-normal-TSLVDSQR.woff2 +0 -0
  37. package/dist/ui/assets/jetbrains-mono-latin-wght-normal-VBS632QH.woff2 +0 -0
  38. package/dist/ui/index.html +13 -0
  39. package/dist/worker.d.ts +2 -2
  40. package/dist/worker.js +7 -4
  41. package/llms.txt +1 -0
  42. package/package.json +43 -10
  43. package/dist/chunk-GYQFTF2E.js +0 -389
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Documents</title>
7
+ <link rel="stylesheet" href="/app.css" />
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/app.js"></script>
12
+ </body>
13
+ </html>
package/dist/worker.d.ts CHANGED
@@ -2,8 +2,8 @@ import { Hono } from 'hono';
2
2
  import { Region } from 'agentic-identity';
3
3
  import { D1Like, R2Like } from 'agentic-identity/cloudflare';
4
4
  import { Realtime, Role } from 'agentic-realtime';
5
- import { a as Principal } from './doc-C_8kCsl7.js';
6
- import { c as DocsService } from './service-Do2Jwb5U.js';
5
+ import { P as Principal } from './doc-BXG9qG5X.js';
6
+ import { c as DocsService } from './service-Ck6oX-Kq.js';
7
7
  import 'yjs';
8
8
 
9
9
  interface WorkerEnv {
package/dist/worker.js CHANGED
@@ -3,10 +3,13 @@ import {
3
3
  regionOf,
4
4
  serviceFor,
5
5
  worker_default
6
- } from "./chunk-IU3YIQ4Y.js";
7
- import "./chunk-FZA5LQV3.js";
8
- import "./chunk-GYQFTF2E.js";
9
- import "./chunk-YBBQNCMC.js";
6
+ } from "./chunk-V6TBTOHW.js";
7
+ import "./chunk-6UYJV2EJ.js";
8
+ import "./chunk-JBAQEIOH.js";
9
+ import "./chunk-5IQX2W52.js";
10
+ import "./chunk-KC5KFB5R.js";
11
+ import "./chunk-GQ5E6T7Z.js";
12
+ import "./chunk-DWVJIKVT.js";
10
13
  export {
11
14
  createApp,
12
15
  worker_default as default,
package/llms.txt CHANGED
@@ -7,6 +7,7 @@ Keywords: documents, editor, rich-text, tiptap, yjs, collaboration, agents.
7
7
  - SDK: import { AgenticDoc } from "agentic-documents".
8
8
  - Read as Markdown, read an outline, edit by section id or exact text.
9
9
  - Remote client: import { createClient } from "agentic-documents/client"; createClient({ baseUrl, token, fetch? }). It has one method per HTTP route. An error is a DocsApiError with a code and a status. Every call takes { signal }. The client never retries.
10
+ - Web UI: the Worker serves it from dist/ui (a document list, a rich-text editor on the live room, rename, delete, share, presence, a mark on agent text). It ships in the npm package. Run npm run build in a clone, or point the assets directory of your wrangler config at node_modules/agentic-documents/dist/ui.
10
11
  - CLI: npx agentic-documents <command> <id>. MCP: npx agentic-documents mcp --as <kind>:<name>.
11
12
  - Every edit carries an author kind and name.
12
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-documents",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Write documents with people and agents in real time.",
5
5
  "keywords": [
6
6
  "documents",
@@ -58,24 +58,43 @@
58
58
  "types": "./dist/client.d.ts",
59
59
  "import": "./dist/client.js"
60
60
  },
61
+ "./formats": {
62
+ "types": "./dist/formats.d.ts",
63
+ "import": "./dist/formats.js"
64
+ },
65
+ "./ui": {
66
+ "types": "./dist/embed.d.ts",
67
+ "import": "./dist/embed.js"
68
+ },
69
+ "./ui.css": "./dist/editor.css",
61
70
  "./live-worker": {
62
71
  "types": "./dist/live-worker.d.ts",
63
72
  "import": "./dist/live-worker.js"
64
73
  }
65
74
  },
66
75
  "scripts": {
67
- "build": "tsup",
76
+ "build": "tsup && node ui/build.mjs",
68
77
  "prepack": "npm run build",
69
78
  "typecheck": "tsc --noEmit",
70
79
  "test": "vitest run",
71
80
  "clean": "rm -rf dist",
72
- "ci:local": "npm run build && npm run typecheck && npm test"
81
+ "ci:local": "npm run build && npm run typecheck && npm test && playwright test",
82
+ "e2e": "npm run build && playwright test"
73
83
  },
74
84
  "dependencies": {
75
85
  "@modelcontextprotocol/sdk": "^1.12.0",
86
+ "@tiptap/core": "^3.32.1",
87
+ "@tiptap/extension-collaboration": "^3.32.1",
88
+ "@tiptap/extension-image": "^3.32.1",
89
+ "@tiptap/extension-list": "^3.32.1",
90
+ "@tiptap/extension-table": "^3.32.1",
91
+ "@tiptap/pm": "^3.32.1",
92
+ "@tiptap/starter-kit": "^3.32.1",
93
+ "@tiptap/y-tiptap": "^3.0.11",
94
+ "agentic-design": "^0.1.0",
95
+ "agentic-formats": "^0.2.0",
76
96
  "agentic-identity": "^0.4.0",
77
97
  "agentic-realtime": "^0.1.0",
78
- "fflate": "^0.8.3",
79
98
  "hono": "^4.13.14",
80
99
  "lib0": "^0.2.119",
81
100
  "mdast-util-from-markdown": "^2.1.0",
@@ -91,19 +110,33 @@
91
110
  "zod": "^3.25.0"
92
111
  },
93
112
  "devDependencies": {
94
- "@tiptap/core": "^3.32.1",
95
- "@tiptap/extension-image": "^3.32.1",
96
- "@tiptap/extension-list": "^3.32.1",
97
- "@tiptap/extension-table": "^3.32.1",
98
- "@tiptap/pm": "^3.32.1",
99
- "@tiptap/starter-kit": "^3.32.1",
113
+ "@axe-core/playwright": "^4.14.0",
114
+ "@playwright/test": "^1.64.1",
100
115
  "@types/mdast": "^4.0.4",
101
116
  "@types/node": "^22.0.0",
117
+ "@types/react": "^19.3.0",
118
+ "@types/react-dom": "^19.3.0",
119
+ "esbuild": "^0.28.2",
120
+ "jsdom": "^26.1.0",
121
+ "react": "^19.3.1",
122
+ "react-dom": "^19.3.1",
102
123
  "tsup": "^8.0.0",
103
124
  "typescript": "^5.6.0",
104
125
  "vitest": "^3.0.0",
105
126
  "wrangler": "^4.155.0"
106
127
  },
128
+ "peerDependencies": {
129
+ "react": "^19.0.0",
130
+ "react-dom": "^19.0.0"
131
+ },
132
+ "peerDependenciesMeta": {
133
+ "react": {
134
+ "optional": true
135
+ },
136
+ "react-dom": {
137
+ "optional": true
138
+ }
139
+ },
107
140
  "bin": {
108
141
  "agentic-documents": "./dist/cli.js"
109
142
  }
@@ -1,389 +0,0 @@
1
- import {
2
- DocError,
3
- Suggestions,
4
- Threads
5
- } from "./chunk-YBBQNCMC.js";
6
-
7
- // src/odt.ts
8
- import { strToU8, zipSync } from "fflate";
9
-
10
- // src/package.ts
11
- var IMAGE_TYPES = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp" };
12
- var DATA_IMAGE = /^data:(image\/(?:png|jpeg|gif|webp));base64,([a-z0-9+/=]+)$/i;
13
- var FIXED_DATE = new Date(2e3, 0, 1);
14
- function toBase64(bytes) {
15
- let text2 = "";
16
- for (let i = 0; i < bytes.length; i += 32768) text2 += String.fromCharCode(...bytes.subarray(i, i + 32768));
17
- return btoa(text2);
18
- }
19
- var fromBase64 = (value) => Uint8Array.from(atob(value), (c) => c.charCodeAt(0));
20
- function imageSize(b) {
21
- const v = new DataView(b.buffer, b.byteOffset, b.byteLength);
22
- if (b.length > 24 && v.getUint32(0) === 2303741511) return [v.getUint32(16), v.getUint32(20)];
23
- if (b.length > 10 && v.getUint32(0) === 1195984440) return [v.getUint16(6, true), v.getUint16(8, true)];
24
- if (b.length > 4 && v.getUint16(0) === 65496) {
25
- for (let i = 2; i + 9 < b.length && b[i] === 255; i += 2 + v.getUint16(i + 2)) {
26
- if (b[i + 1] >= 192 && b[i + 1] <= 207 && ![196, 200, 204].includes(b[i + 1])) return [v.getUint16(i + 7), v.getUint16(i + 5)];
27
- }
28
- }
29
- return void 0;
30
- }
31
-
32
- // src/xml.ts
33
- var xmlEscape = (value) => String(value ?? "").replace(/[^\u0009\u000A\u000D -퟿-�\u{10000}-\u{10FFFF}]/gu, "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
34
- var safeHref = (value) => {
35
- const href = String(value ?? "").trim();
36
- return /^(https?:|mailto:|#|[^:]*$)/i.test(href) ? href : void 0;
37
- };
38
-
39
- // src/odt.ts
40
- var ODT_MIME = "application/vnd.oasis.opendocument.text";
41
- var NS = 'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" office:version="1.3"';
42
- var XML = '<?xml version="1.0" encoding="UTF-8"?>\n';
43
- var SPANS = { bold: "B", italic: "I", strike: "S", code: "Source_20_Text" };
44
- var TASK = ["\u2610 ", "\u2611 "];
45
- var LEVELS = (kind) => Array.from(
46
- { length: 10 },
47
- (_, i) => kind === "bullet" ? `<text:list-level-style-bullet text:level="${i + 1}" text:bullet-char="\u2022"><style:list-level-properties text:list-level-position-and-space-mode="label-alignment"><style:list-level-label-alignment text:label-followed-by="listtab" fo:margin-left="${(i + 1) * 0.63}cm" fo:text-indent="-0.63cm"/></style:list-level-properties></text:list-level-style-bullet>` : `<text:list-level-style-number text:level="${i + 1}" style:num-suffix="." style:num-format="1"><style:list-level-properties text:list-level-position-and-space-mode="label-alignment"><style:list-level-label-alignment text:label-followed-by="listtab" fo:margin-left="${(i + 1) * 0.63}cm" fo:text-indent="-0.63cm"/></style:list-level-properties></text:list-level-style-number>`
48
- ).join("");
49
- var SIZES = [24, 18, 14, 12, 11, 10];
50
- var STYLES = `${XML}<office:document-styles ${NS}><office:styles>
51
- <style:style style:name="Standard" style:family="paragraph" style:class="text"/>
52
- ${SIZES.map((s, i) => `<style:style style:name="Heading_20_${i + 1}" style:display-name="Heading ${i + 1}" style:family="paragraph" style:parent-style-name="Standard" style:class="text"><style:text-properties fo:font-size="${s}pt" fo:font-weight="bold"/></style:style>`).join("\n")}
53
- <style:style style:name="Quotations" style:family="paragraph" style:parent-style-name="Standard" style:class="html"><style:paragraph-properties fo:margin-left="1cm" fo:margin-right="1cm"/></style:style>
54
- <style:style style:name="Preformatted_20_Text" style:display-name="Preformatted Text" style:family="paragraph" style:parent-style-name="Standard" style:class="html"><style:text-properties fo:font-family="Liberation Mono, Courier New, monospace" fo:font-size="10pt"/></style:style>
55
- <style:style style:name="Horizontal_20_Line" style:display-name="Horizontal Line" style:family="paragraph" style:parent-style-name="Standard" style:class="html"><style:paragraph-properties fo:border-bottom="0.5pt solid #808080" fo:padding="0cm" fo:margin-bottom="0.3cm"/></style:style>
56
- <style:style style:name="Source_20_Text" style:display-name="Source Text" style:family="text"><style:text-properties fo:font-family="Liberation Mono, Courier New, monospace"/></style:style>
57
- </office:styles></office:document-styles>`;
58
- var AUTO = `<style:style style:name="B" style:family="text"><style:text-properties fo:font-weight="bold"/></style:style>
59
- <style:style style:name="I" style:family="text"><style:text-properties fo:font-style="italic"/></style:style>
60
- <style:style style:name="S" style:family="text"><style:text-properties style:text-line-through-style="solid"/></style:style>
61
- <text:list-style style:name="Bullets">${LEVELS("bullet")}</text:list-style>
62
- <text:list-style style:name="Numbering">${LEVELS("number")}</text:list-style>`;
63
- var spaced = (s) => xmlEscape(s).replace(/\t/g, "<text:tab/>").replace(/\n/g, "<text:line-break/>").replace(/ +/g, (run, at, all) => at === 0 || at + run.length === all.length ? `<text:s text:c="${run.length}"/>` : run.length > 1 ? ` <text:s text:c="${run.length - 1}"/>` : run);
64
- function image(node, out) {
65
- const alt = String(node.attrs?.alt ?? "");
66
- const found = DATA_IMAGE.exec(String(node.attrs?.src ?? "").trim());
67
- let data;
68
- try {
69
- data = found ? fromBase64(found[2]) : void 0;
70
- } catch {
71
- data = void 0;
72
- }
73
- if (!found || !data) return spaced(`[image${alt ? `: ${alt}` : ""}]`);
74
- const ext = Object.keys(IMAGE_TYPES).find((k) => IMAGE_TYPES[k] === found[1].toLowerCase());
75
- const name = `Image${out.images.length + 1}`;
76
- out.images.push({ name: `Pictures/${name}.${ext}`, type: found[1].toLowerCase(), data });
77
- const [w, h] = imageSize(data) ?? [320, 240];
78
- const scale = Math.min(1, 16 / (w * 2.54 / 96));
79
- const cm = (px) => (px * 2.54 * scale / 96).toFixed(2);
80
- return `<draw:frame draw:name="${name}" text:anchor-type="as-char" svg:width="${cm(w)}cm" svg:height="${cm(h)}cm"><draw:image xlink:href="Pictures/${name}.${ext}" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad"/><svg:title>${xmlEscape(alt)}</svg:title></draw:frame>`;
81
- }
82
- function inline(nodes, out) {
83
- return nodes.map((node) => {
84
- if (node.type === "hardBreak") return "<text:line-break/>";
85
- if (node.type === "image") return image(node, out);
86
- return (node.marks ?? []).reduce((xml, mark) => {
87
- if (mark.type === "link") {
88
- const href = safeHref(mark.attrs?.href);
89
- return href ? `<text:a xlink:type="simple" xlink:href="${xmlEscape(href)}">${xml}</text:a>` : xml;
90
- }
91
- return SPANS[mark.type] ? `<text:span text:style-name="${SPANS[mark.type]}">${xml}</text:span>` : xml;
92
- }, spaced(node.text ?? ""));
93
- }).join("");
94
- }
95
- function block(node, out, style = "Standard") {
96
- const kids = node.content ?? [];
97
- const p = (name, inner) => `<text:p text:style-name="${name}">${inner}</text:p>`;
98
- switch (node.type) {
99
- case "paragraph":
100
- return p(style, inline(kids, out));
101
- case "heading": {
102
- const level = Math.min(6, Math.max(1, Math.trunc(Number(node.attrs?.level)) || 1));
103
- return `<text:h text:style-name="Heading_20_${level}" text:outline-level="${level}">${inline(kids, out)}</text:h>`;
104
- }
105
- case "blockquote":
106
- return kids.map((k) => block(k, out, "Quotations")).join("");
107
- case "codeBlock":
108
- return p("Preformatted_20_Text", spaced(kids.map((k) => k.text ?? "").join("")));
109
- case "horizontalRule":
110
- return `<text:p text:style-name="Horizontal_20_Line"/>`;
111
- case "bulletList":
112
- case "orderedList":
113
- case "taskList": {
114
- const ordered = node.type === "orderedList";
115
- const start = Number(node.attrs?.start ?? 1);
116
- const items = kids.map((item, i) => {
117
- const [first, ...rest] = item.content ?? [];
118
- const mark = item.type === "taskItem" && first ? { ...first, content: [{ type: "text", text: TASK[item.attrs?.checked ? 1 : 0] }, ...first.content ?? []] } : first;
119
- const attr = ordered && i === 0 && start !== 1 ? ` text:start-value="${Math.trunc(start) || 1}"` : "";
120
- return `<text:list-item${attr}>${[mark, ...rest].filter(Boolean).map((k) => block(k, out)).join("")}</text:list-item>`;
121
- });
122
- return `<text:list text:style-name="${ordered ? "Numbering" : "Bullets"}">${items.join("")}</text:list>`;
123
- }
124
- case "table": {
125
- const cols = Math.max(0, ...kids.map((r) => r.content?.length ?? 0));
126
- const row = (r) => `<table:table-row>${(r.content ?? []).map((c) => `<table:table-cell office:value-type="string">${(c.content ?? []).map((b) => block(b, out)).join("")}</table:table-cell>`).join("")}</table:table-row>`;
127
- const header = kids[0]?.content?.length && kids[0].content.every((c) => c.type === "tableHeader");
128
- const rows = header ? `<table:table-header-rows>${row(kids[0])}</table:table-header-rows>${kids.slice(1).map(row).join("")}` : kids.map(row).join("");
129
- return `<table:table table:name="Table${++out.tables}">${cols ? `<table:table-column table:number-columns-repeated="${cols}"/>` : ""}${rows}</table:table>`;
130
- }
131
- default:
132
- return "";
133
- }
134
- }
135
- function exportOdt(blocks) {
136
- const out = { images: [], tables: 0 };
137
- const body = blocks.map((b) => block(b, out)).join("\n");
138
- const content2 = `${XML}<office:document-content ${NS}><office:automatic-styles>${AUTO}</office:automatic-styles><office:body><office:text>${body}</office:text></office:body></office:document-content>`;
139
- const entries = [
140
- `<manifest:file-entry manifest:full-path="/" manifest:version="1.3" manifest:media-type="${ODT_MIME}"/>`,
141
- ...["content.xml", "styles.xml", "meta.xml"].map((n) => `<manifest:file-entry manifest:full-path="${n}" manifest:media-type="text/xml"/>`),
142
- ...out.images.map((i) => `<manifest:file-entry manifest:full-path="${i.name}" manifest:media-type="${i.type}"/>`)
143
- ];
144
- const meta = `${XML}<office:document-meta ${NS}><office:meta><meta:generator>agentic-documents</meta:generator></office:meta></office:document-meta>`;
145
- const part = (text2) => [strToU8(text2), { mtime: FIXED_DATE }];
146
- return zipSync({
147
- mimetype: [strToU8(ODT_MIME), { level: 0, mtime: FIXED_DATE }],
148
- // first and stored, so file sniffers can read it
149
- "META-INF/manifest.xml": part(`${XML}<manifest:manifest ${NS}>${entries.join("")}</manifest:manifest>`),
150
- "content.xml": part(content2),
151
- "styles.xml": part(STYLES),
152
- "meta.xml": part(meta),
153
- ...Object.fromEntries(out.images.map((i) => [i.name, [i.data, { mtime: FIXED_DATE }]]))
154
- });
155
- }
156
-
157
- // src/export.ts
158
- var EXPORT_FORMATS = ["md", "html", "odt"];
159
- var esc = (s) => String(s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
160
- var url = (value) => /^(https?:|mailto:|[^:]*$)/i.test(String(value ?? "").trim()) ? esc(String(value).trim()) : "#";
161
- var integer = (value, fallback) => Number.isFinite(Number(value)) ? Math.trunc(Number(value)) : fallback;
162
- var INLINE_IMAGE = /^data:image\/(png|jpeg|gif|webp);base64,[a-z0-9+/=]+$/i;
163
- var textOf = (node) => node.text ?? (node.content ?? []).map(textOf).join("");
164
- var MARK_TAGS = { bold: "strong", italic: "em", strike: "s", code: "code" };
165
- function image2(node, remote) {
166
- const src = String(node.attrs?.src ?? "").trim();
167
- const alt = esc(node.attrs?.alt);
168
- if (INLINE_IMAGE.test(src)) return `<img src="${src}" alt="${alt}">`;
169
- if (remote && url(src) !== "#") return `<img src="${url(src)}" alt="${alt}">`;
170
- return `<span class="image">[image${alt ? `: ${alt}` : ""}]</span>`;
171
- }
172
- function inline2(node, remote) {
173
- if (node.type === "hardBreak") return "<br>";
174
- if (node.type === "image") return image2(node, remote);
175
- return (node.marks ?? []).reduce((html, mark) => {
176
- if (mark.type === "link") return `<a href="${url(mark.attrs?.href)}">${html}</a>`;
177
- const tag = MARK_TAGS[mark.type];
178
- return tag ? `<${tag}>${html}</${tag}>` : html;
179
- }, esc(node.text));
180
- }
181
- function block2(node, remote) {
182
- const kids = (node.content ?? []).map((n) => block2(n, remote)).join("");
183
- const text2 = (node.content ?? []).map((n) => inline2(n, remote)).join("");
184
- switch (node.type) {
185
- case "paragraph":
186
- return `<p>${text2}</p>
187
- `;
188
- case "heading": {
189
- const level = Math.min(6, Math.max(1, integer(node.attrs?.level, 1)));
190
- return `<h${level}>${text2}</h${level}>
191
- `;
192
- }
193
- case "blockquote":
194
- return `<blockquote>
195
- ${kids}</blockquote>
196
- `;
197
- case "codeBlock": {
198
- const lang = node.attrs?.language ? ` class="language-${esc(node.attrs.language)}"` : "";
199
- return `<pre><code${lang}>${esc(textOf(node))}</code></pre>
200
- `;
201
- }
202
- case "horizontalRule":
203
- return "<hr>\n";
204
- case "bulletList":
205
- case "taskList":
206
- return `<ul>
207
- ${kids}</ul>
208
- `;
209
- case "orderedList":
210
- return `<ol start="${integer(node.attrs?.start, 1)}">
211
- ${kids}</ol>
212
- `;
213
- case "listItem":
214
- return `<li>${kids}</li>
215
- `;
216
- case "taskItem":
217
- return `<li><input type="checkbox" disabled${node.attrs?.checked ? " checked" : ""}> ${kids}</li>
218
- `;
219
- case "table":
220
- return `<table>
221
- ${kids}</table>
222
- `;
223
- case "tableRow":
224
- return `<tr>${kids}</tr>
225
- `;
226
- case "tableHeader":
227
- return `<th>${kids}</th>`;
228
- case "tableCell":
229
- return `<td>${kids}</td>`;
230
- default:
231
- return "";
232
- }
233
- }
234
- function toHtml(blocks, remote) {
235
- const title = textOf(blocks.find((b) => b.type === "heading") ?? { type: "doc" }) || "Document";
236
- const css = "body{font-family:system-ui,sans-serif;max-width:46rem;margin:2rem auto;padding:0 1rem;line-height:1.5}table{border-collapse:collapse}td,th{border:1px solid #888;padding:.25rem .5rem}pre{overflow:auto}";
237
- return `<!doctype html>
238
- <html lang="en">
239
- <head>
240
- <meta charset="utf-8">
241
- <title>${esc(title)}</title>
242
- <style>${css}</style>
243
- </head>
244
- <body>
245
- ${blocks.map((b) => block2(b, remote)).join("")}</body>
246
- </html>
247
- `;
248
- }
249
- function exportDoc(doc, format, options = {}) {
250
- if (format === "html") {
251
- return { format, contentType: "text/html; charset=utf-8", encoding: "utf8", data: toHtml(doc.toJSON().content ?? [], options.remoteImages === true) };
252
- }
253
- if (format === "odt") return { format, contentType: ODT_MIME, encoding: "base64", data: toBase64(exportOdt(doc.toJSON().content ?? [])) };
254
- return { format, contentType: "text/markdown; charset=utf-8", encoding: "utf8", data: doc.toMarkdown() };
255
- }
256
-
257
- // src/commands.ts
258
- import { z } from "zod";
259
- var id = z.string().describe("Document id.");
260
- var content = z.string().describe("Markdown for a section target. Plain text for a text target.");
261
- var text = z.string().describe("Exact text to match. It must sit inside one block.");
262
- var occurrence = z.coerce.number().int().min(1).optional().describe("Which match, counting from 1, when the text matches more than once.");
263
- var side = z.enum(["before", "after"]).optional().describe("Put the new text before or after the match.");
264
- var thread = z.string().describe("Thread id.");
265
- var where = {
266
- section: z.string().optional().describe("Section id from the outline. Use this or text."),
267
- text: text.optional(),
268
- occurrence,
269
- at: z.enum(["start", "end", "before", "after"]).optional().describe("Insert at the start or end of a section, or before or after matched text.")
270
- };
271
- function target(input) {
272
- const { section, text: text2, occurrence: occurrence2, at } = input;
273
- if (section !== void 0) {
274
- if (at !== void 0 && at !== "start" && at !== "end") throw new DocError("INVALID_INPUT", `at "${at}" does not fit a section; use start or end`);
275
- return { section, at };
276
- }
277
- if (text2 !== void 0) {
278
- if (at === "start" || at === "end") throw new DocError("INVALID_INPUT", `at "${at}" does not fit text; use before or after`);
279
- return { text: text2, occurrence: occurrence2, at };
280
- }
281
- throw new DocError("INVALID_INPUT", "pass section or text");
282
- }
283
- var textTarget = (input) => ({
284
- text: input.text,
285
- occurrence: input.occurrence,
286
- at: input.at
287
- });
288
- function command(name, description, readOnly, shape, run) {
289
- return {
290
- name,
291
- description,
292
- shape,
293
- readOnly,
294
- async run(service, raw, author) {
295
- const parsed = z.object(shape).strict().safeParse(raw);
296
- if (!parsed.success) {
297
- throw new DocError("INVALID_INPUT", parsed.error.issues.map((i) => `${i.path.join(".") || "input"}: ${i.message}`).join("; "));
298
- }
299
- return run(service, parsed.data, author);
300
- }
301
- };
302
- }
303
- var ok = () => ({ ok: true });
304
- var commands = [
305
- command("new", "Create a document, from Markdown if given.", false, { id, markdown: z.string().optional() }, async (s, i, a) => {
306
- await s.create(i.id, i.markdown ?? "", a);
307
- return { id: i.id };
308
- }),
309
- command(
310
- "read",
311
- "Read a document, or one section, as Markdown.",
312
- true,
313
- { id, section: where.section },
314
- (s, i) => s.read(i.id, (d) => i.section ? d.readSection(i.section) : d.toMarkdown())
315
- ),
316
- command("outline", "List sections with their ids, levels, and titles.", true, { id }, (s, i) => s.read(i.id, (d) => d.outline())),
317
- command("edits", "Read the edit log. It names each author kind and holds no content.", true, { id }, (s, i) => s.read(i.id, (d) => d.edits())),
318
- command("insert", "Insert Markdown into a section, or plain text next to matched text.", false, { id, ...where, content }, async (s, i, a) => {
319
- await s.write(i.id, (d) => d.insert(target(i), i.content, a));
320
- return ok();
321
- }),
322
- command("replace", "Replace a section body (the heading stays), or the matched text.", false, { id, ...where, content }, async (s, i, a) => {
323
- await s.write(i.id, (d) => d.replace(target(i), i.content, a));
324
- return ok();
325
- }),
326
- command("delete", "Delete a section with its subsections, or the matched text.", false, { id, ...where }, async (s, i, a) => {
327
- await s.write(i.id, (d) => d.delete(target(i), a));
328
- return ok();
329
- }),
330
- command("append", "Add Markdown at the end of the document.", false, { id, content: z.string() }, async (s, i, a) => {
331
- await s.write(i.id, (d) => d.append(i.content, a));
332
- return ok();
333
- }),
334
- command("suggest", "Suggest an edit to matched text. An empty replacement suggests a delete.", false, { id, text, occurrence, at: side, replacement: z.string() }, async (s, i, a) => ({
335
- suggestion: await s.write(i.id, (d) => new Suggestions(d).add(textTarget(i), i.replacement, a))
336
- })),
337
- command(
338
- "suggestions",
339
- "List suggested edits.",
340
- true,
341
- { id, status: z.enum(["pending", "accepted", "rejected"]).optional() },
342
- (s, i) => s.read(i.id, (d) => new Suggestions(d).list({ status: i.status }))
343
- ),
344
- command("accept", "Accept a suggestion. The text changes now.", false, { id, suggestion: z.string() }, async (s, i, a) => {
345
- await s.write(i.id, (d) => new Suggestions(d).accept(i.suggestion, a));
346
- return ok();
347
- }),
348
- command("reject", "Reject a suggestion. The text stays.", false, { id, suggestion: z.string() }, async (s, i, a) => {
349
- await s.write(i.id, (d) => new Suggestions(d).reject(i.suggestion, a));
350
- return ok();
351
- }),
352
- command("comment", "Start a comment thread on matched text.", false, { id, text, occurrence, body: z.string() }, async (s, i, a) => ({
353
- thread: await s.write(i.id, (d) => new Threads(d).add(textTarget(i), i.body, a))
354
- })),
355
- command("reply", "Reply in a comment thread.", false, { id, thread, body: z.string() }, async (s, i, a) => {
356
- await s.write(i.id, (d) => new Threads(d).reply(i.thread, i.body, a));
357
- return ok();
358
- }),
359
- command("resolve", "Resolve a comment thread.", false, { id, thread }, async (s, i, a) => {
360
- await s.write(i.id, (d) => new Threads(d).resolve(i.thread, a));
361
- return ok();
362
- }),
363
- command("reopen", "Reopen a comment thread.", false, { id, thread }, async (s, i, a) => {
364
- await s.write(i.id, (d) => new Threads(d).reopen(i.thread, a));
365
- return ok();
366
- }),
367
- command(
368
- "threads",
369
- "List comment threads with their messages.",
370
- true,
371
- { id, status: z.enum(["open", "resolved"]).optional() },
372
- (s, i) => s.read(i.id, (d) => new Threads(d).list({ status: i.status }))
373
- ),
374
- command(
375
- "export",
376
- "Export the document as Markdown (md), self-contained HTML (html), or OpenDocument text (odt, base64). HTML shows no remote image unless remoteImages is true.",
377
- true,
378
- { id, format: z.enum(EXPORT_FORMATS), remoteImages: z.union([z.boolean(), z.enum(["true", "false"])]).optional() },
379
- (s, i) => s.read(i.id, (d) => exportDoc(d, i.format, { remoteImages: i.remoteImages === true || i.remoteImages === "true" }))
380
- )
381
- ];
382
-
383
- export {
384
- ODT_MIME,
385
- exportOdt,
386
- EXPORT_FORMATS,
387
- exportDoc,
388
- commands
389
- };