@shadow-garden/bapbong-model 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Le Phuoc Minh
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,27 @@
1
+ # @shadow-garden/bapbong-model
2
+
3
+ The ProseMirror **document schema** for bapbong, plus list-numbering helpers.
4
+ This is the canonical in-memory document — `importDocx` produces it, the layout
5
+ engine consumes it, and the hidden editor edits it.
6
+
7
+ - **Scope:** `scope:model`
8
+ - **Depends on:** `prosemirror-model`
9
+
10
+ ## What it provides
11
+
12
+ - **`schema`** — the main document `Schema`: `doc` (with undoable `attrs`
13
+ `numbering` / `sections` / `comments`), `paragraph`, `text`, `image`, `table`,
14
+ `hardBreak`, …; marks `strong`, `em`, `underline`, `strike`, `link`, `color`,
15
+ `highlight`, `vertAlign`, `footnote { num }`, `comment { ids }`.
16
+ - **`commentSchema`** — a tiny schema for comment bodies: `doc` / `paragraph` /
17
+ `text` + an inline **`mention`** atom (`{ id, label }`) with `leafText`/`toDOM`.
18
+ - **`createNumberingCounter(defs)`** — resolves live list markers (decimal,
19
+ bullet, multilevel) from `doc.attrs.numbering`.
20
+ - Types: `BapbongSchema`, `CommentSchema`, `Align`.
21
+
22
+ ## Build / test
23
+
24
+ ```sh
25
+ pnpm nx build @shadow-garden/bapbong-model
26
+ pnpm nx test @shadow-garden/bapbong-model
27
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,441 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // packages/model/src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ commentSchema: () => commentSchema,
24
+ createNumberingCounter: () => createNumberingCounter,
25
+ schema: () => schema
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // packages/model/src/lib/model.ts
30
+ var import_prosemirror_model = require("prosemirror-model");
31
+ var schema = new import_prosemirror_model.Schema({
32
+ nodes: {
33
+ doc: {
34
+ content: "block+",
35
+ attrs: {
36
+ // NumberingDefs (numbering.ts) — list markers are recomputed from
37
+ // these at layout time, so edits renumber live. Importer-set.
38
+ numbering: { default: null },
39
+ // SectionConfig[] (contracts) — per-section column flow, delimited by
40
+ // w:sectPr breaks. null/absent → one implicit single-column section.
41
+ sections: { default: null },
42
+ // CommentNode[] (contracts) — comment threads keyed to `comment` marks.
43
+ // Edited via setDocAttribute so add/reply/resolve/delete undo cleanly.
44
+ comments: { default: null }
45
+ }
46
+ },
47
+ paragraph: {
48
+ group: "block",
49
+ content: "inline*",
50
+ // `list` is null for normal paragraphs, or ListInfo for list items.
51
+ // DOCX stores lists flat (each w:p carries w:numPr), so we keep them
52
+ // flat and let the marker be computed by the numbering counter.
53
+ //
54
+ // `align` mirrors w:jc ('left'|'center'|'right'|'justify'), null = default.
55
+ // `indent` mirrors w:ind, all measured in CSS px (Indent | null):
56
+ // { left, right, firstLine, hanging }. firstLine and hanging are
57
+ // mutually exclusive in OOXML; hanging wins if both appear.
58
+ attrs: {
59
+ list: { default: null },
60
+ align: { default: null },
61
+ indent: { default: null },
62
+ // Heading level 1–6 (maps to a Word "Heading N" style on export, and to
63
+ // an <h1>–<h6> in toDOM so the a11y mirror is semantic), or null for a
64
+ // body paragraph.
65
+ heading: { default: null },
66
+ // w:tabs — [{ pos, val: 'left'|'right'|'center'|'decimal', leader? }]
67
+ // in px from the paragraph's content left edge, or null. Importer-set.
68
+ tabs: { default: null },
69
+ // w:spacing — { before?, after?, line?, lineRule? }, or null.
70
+ spacing: { default: null },
71
+ // w:pageBreakBefore — start this paragraph on a new page.
72
+ pageBreakBefore: { default: false }
73
+ },
74
+ // No getAttrs: nothing in the pipeline parses paragraphs from the DOM
75
+ // yet (the importer builds nodes directly). align/indent still round-trip
76
+ // out through toDOM. Revisit when HTML paste lands.
77
+ parseDOM: [{ tag: "p" }],
78
+ toDOM(node) {
79
+ const attrs = node.attrs;
80
+ const style = paragraphStyle(attrs);
81
+ const tag = attrs.heading ? `h${attrs.heading}` : "p";
82
+ return [tag, style ? { style } : {}, 0];
83
+ }
84
+ },
85
+ text: { group: "inline" },
86
+ // A soft line break inside a paragraph (w:br) — forces a new line without
87
+ // ending the paragraph. Occupies one PM position, like an image atom.
88
+ hard_break: {
89
+ inline: true,
90
+ group: "inline",
91
+ selectable: false,
92
+ parseDOM: [{ tag: "br" }],
93
+ toDOM: () => ["br"]
94
+ },
95
+ // Inline image. `src` is typically a data URL (the importer inlines the
96
+ // embedded media); width/height are CSS pixels, null if unspecified.
97
+ image: {
98
+ inline: true,
99
+ group: "inline",
100
+ draggable: true,
101
+ attrs: {
102
+ src: {},
103
+ alt: { default: "" },
104
+ width: { default: null },
105
+ height: { default: null },
106
+ // wp:anchor (floating image): { wrap: 'square'|'topAndBottom'|'none',
107
+ // hAlign?, hOffset?, hRel?, vOffset?, vRel?, distL?, distR?, distT?,
108
+ // distB? } in px, or null for inline images. Importer-set.
109
+ float: { default: null },
110
+ // Drawn vector shape (wps rect / straight connector) riding this box:
111
+ // { kind: 'rect'|'line', stroke?, strokeWidth?, fill?, flipV? } — src
112
+ // is then '' and the box paints as vector. Null for real bitmaps.
113
+ shape: { default: null },
114
+ // Textbox (wps:txbx) content riding a shape: { paragraphs: <paragraph
115
+ // node JSON>[], inset?: {l,t,r,b} px }. The layout engine flows the
116
+ // paragraphs inside the shape's box (paint-only, not editable v1).
117
+ textbox: { default: null },
118
+ // Clockwise rotation in degrees around the box center (a:xfrm@rot).
119
+ // Paint-only: the layout box stays axis-aligned.
120
+ rotation: { default: 0 }
121
+ },
122
+ toDOM(node) {
123
+ const a = node.attrs;
124
+ const attrs = { src: a["src"], alt: a["alt"] };
125
+ if (a["width"] != null) attrs["width"] = String(a["width"]);
126
+ if (a["height"] != null) attrs["height"] = String(a["height"]);
127
+ return ["img", attrs];
128
+ }
129
+ },
130
+ // PAGE / NUMPAGES fields: atoms whose text is computed per page at paint
131
+ // time. `kind` is 'page' (current page number) or 'pages' (total count).
132
+ page_field: {
133
+ inline: true,
134
+ group: "inline",
135
+ atom: true,
136
+ attrs: { kind: {} },
137
+ parseDOM: [{ tag: "span[data-page-field]" }],
138
+ toDOM: (node) => [
139
+ "span",
140
+ { "data-page-field": String(node.attrs["kind"]) },
141
+ node.attrs["kind"] === "pages" ? "##" : "#"
142
+ ]
143
+ },
144
+ // Tables: kept structural (table → row → cell → block+). Horizontal spans
145
+ // map to colspan; vertical merges aren't collapsed yet (rowspan default 1).
146
+ table: {
147
+ group: "block",
148
+ content: "table_row+",
149
+ isolating: true,
150
+ attrs: {
151
+ // w:tblCellMar overrides (px: {left,right,top,bottom}), or null for
152
+ // Word defaults. Importer-set (same rationale as paragraph attrs).
153
+ cellPadding: { default: null },
154
+ // w:tblBorders visibility { top, bottom, left, right, insideH,
155
+ // insideV }, or null — OOXML tables are borderless unless declared.
156
+ borders: { default: null },
157
+ // w:tblPr/w:jc — 'center' | 'right' table alignment, or null (left).
158
+ align: { default: null }
159
+ },
160
+ parseDOM: [{ tag: "table" }],
161
+ toDOM: (node) => ["table", node.attrs["borders"] ? { "data-borders": "1" } : {}, ["tbody", 0]]
162
+ },
163
+ table_row: {
164
+ content: "table_cell+",
165
+ attrs: {
166
+ header: { default: false },
167
+ // w:trPr/w:tblHeader — repeat on every page
168
+ // w:trPr/w:cantSplit — the row must not break across pages. Absent
169
+ // (Word's default) means the paginator may split the row mid-content.
170
+ cantSplit: { default: false },
171
+ // w:trHeight — { value: px, exact: boolean } or null (auto).
172
+ height: { default: null }
173
+ },
174
+ // No getAttrs (same rationale as paragraph): the importer sets attrs
175
+ // directly; revisit when HTML paste lands.
176
+ parseDOM: [{ tag: "tr" }],
177
+ toDOM: (node) => node.attrs["header"] ? ["tr", { "data-header": "true" }, 0] : ["tr", 0]
178
+ },
179
+ table_cell: {
180
+ content: "block+",
181
+ isolating: true,
182
+ attrs: {
183
+ colspan: { default: 1 },
184
+ rowspan: { default: 1 },
185
+ colwidth: { default: null },
186
+ // px widths of the spanned columns, or null
187
+ background: { default: null },
188
+ // w:shd w:fill — cell fill "#RRGGBB"
189
+ vAlign: { default: null },
190
+ // w:vAlign — 'center' | 'bottom' (top default)
191
+ borders: { default: null }
192
+ // w:tcBorders per-side visibility override
193
+ },
194
+ parseDOM: [{ tag: "td" }, { tag: "th" }],
195
+ toDOM(node) {
196
+ const attrs = {};
197
+ if (node.attrs["colspan"] !== 1) attrs["colspan"] = String(node.attrs["colspan"]);
198
+ if (node.attrs["rowspan"] !== 1) attrs["rowspan"] = String(node.attrs["rowspan"]);
199
+ if (node.attrs["background"]) attrs["style"] = `background-color: ${node.attrs["background"]}`;
200
+ return ["td", attrs, 0];
201
+ }
202
+ }
203
+ },
204
+ marks: {
205
+ // w:b
206
+ strong: {
207
+ parseDOM: [{ tag: "strong" }, { tag: "b" }],
208
+ toDOM: () => ["strong", 0]
209
+ },
210
+ // w:i
211
+ em: {
212
+ parseDOM: [{ tag: "em" }, { tag: "i" }],
213
+ toDOM: () => ["em", 0]
214
+ },
215
+ // w:u
216
+ underline: {
217
+ parseDOM: [{ tag: "u" }],
218
+ toDOM: () => ["u", 0]
219
+ },
220
+ // w:strike
221
+ strike: {
222
+ parseDOM: [{ tag: "s" }, { tag: "strike" }],
223
+ toDOM: () => ["s", 0]
224
+ },
225
+ // w:color — hex "#RRGGBB"
226
+ textColor: {
227
+ attrs: { color: {} },
228
+ parseDOM: [{ style: "color", getAttrs: (value) => ({ color: value }) }],
229
+ toDOM(mark) {
230
+ return ["span", { style: `color: ${mark.attrs["color"]}` }, 0];
231
+ }
232
+ },
233
+ // w:sz — size in points
234
+ fontSize: {
235
+ attrs: { size: {} },
236
+ parseDOM: [
237
+ {
238
+ style: "font-size",
239
+ getAttrs: (value) => {
240
+ const pt = parseFloat(value);
241
+ return Number.isNaN(pt) ? false : { size: pt };
242
+ }
243
+ }
244
+ ],
245
+ toDOM(mark) {
246
+ return ["span", { style: `font-size: ${mark.attrs["size"]}pt` }, 0];
247
+ }
248
+ },
249
+ // w:vertAlign — superscript / subscript
250
+ vertAlign: {
251
+ attrs: { value: {} },
252
+ // 'super' | 'sub'
253
+ parseDOM: [
254
+ { tag: "sup", getAttrs: () => ({ value: "super" }) },
255
+ { tag: "sub", getAttrs: () => ({ value: "sub" }) }
256
+ ],
257
+ toDOM: (mark) => [mark.attrs["value"] === "sub" ? "sub" : "sup", 0]
258
+ },
259
+ // w:highlight / w:shd w:fill — run background color ("#RRGGBB")
260
+ highlight: {
261
+ attrs: { color: {} },
262
+ parseDOM: [
263
+ { style: "background-color", getAttrs: (value) => ({ color: value }) }
264
+ ],
265
+ toDOM(mark) {
266
+ return ["span", { style: `background-color: ${mark.attrs["color"]}` }, 0];
267
+ }
268
+ },
269
+ // w:rFonts — font family
270
+ fontFamily: {
271
+ attrs: { family: {} },
272
+ parseDOM: [{ style: "font-family", getAttrs: (value) => ({ family: value }) }],
273
+ toDOM(mark) {
274
+ return ["span", { style: `font-family: ${mark.attrs["family"]}` }, 0];
275
+ }
276
+ },
277
+ // w:hyperlink — external URL or "#anchor"
278
+ link: {
279
+ attrs: { href: {} },
280
+ inclusive: false,
281
+ toDOM(mark) {
282
+ return ["a", { href: mark.attrs["href"], rel: "noopener", target: "_blank" }, 0];
283
+ }
284
+ },
285
+ // w:footnoteReference — the carrier text is the superscript number; `num`
286
+ // lets the layout engine match the reference to its page-bottom body.
287
+ footnote: {
288
+ attrs: { num: {} },
289
+ inclusive: false,
290
+ parseDOM: [
291
+ {
292
+ tag: "sup[data-footnote]",
293
+ // `el` is an HTMLElement at runtime; this package has no DOM lib, so
294
+ // narrow structurally rather than naming the type.
295
+ getAttrs: (el) => ({
296
+ num: Number(el.getAttribute("data-footnote")) || 0
297
+ })
298
+ }
299
+ ],
300
+ toDOM(mark) {
301
+ return ["sup", { "data-footnote": String(mark.attrs["num"]) }, 0];
302
+ }
303
+ }
304
+ // The `comment` mark (w:commentRangeStart/End) is contributed by the comment
305
+ // plugin (@shadow-garden/bapbong-comments) via the editor's schema
306
+ // composition — it is NOT part of the base document schema, so a build
307
+ // without comments stays free of comment marks. The `comments` doc attr
308
+ // above is inert storage the plugin populates when present.
309
+ }
310
+ });
311
+ var commentSchema = new import_prosemirror_model.Schema({
312
+ nodes: {
313
+ doc: { content: "block+" },
314
+ paragraph: { group: "block", content: "inline*", parseDOM: [{ tag: "p" }], toDOM: () => ["p", 0] },
315
+ text: { group: "inline" },
316
+ // @mention: an inline atom carrying the mentioned user's id + display name.
317
+ // `leafText` lets textContent include "@Name" (search / plain-text preview).
318
+ mention: {
319
+ group: "inline",
320
+ inline: true,
321
+ atom: true,
322
+ selectable: false,
323
+ attrs: { id: {}, label: {} },
324
+ leafText: (node) => `@${node.attrs["label"]}`,
325
+ toDOM: (node) => [
326
+ "span",
327
+ { class: "mention", "data-id": String(node.attrs["id"]) },
328
+ `@${node.attrs["label"]}`
329
+ ],
330
+ parseDOM: [
331
+ {
332
+ tag: "span.mention",
333
+ getAttrs: (el) => {
334
+ const e = el;
335
+ return { id: e.getAttribute("data-id"), label: (e.textContent ?? "").replace(/^@/, "") };
336
+ }
337
+ }
338
+ ]
339
+ }
340
+ },
341
+ marks: {}
342
+ });
343
+ function paragraphStyle(attrs) {
344
+ const parts = [];
345
+ if (attrs.align) parts.push(`text-align: ${attrs.align}`);
346
+ const ind = attrs.indent;
347
+ if (ind) {
348
+ if (ind.left) parts.push(`margin-left: ${ind.left}px`);
349
+ if (ind.right) parts.push(`margin-right: ${ind.right}px`);
350
+ if (ind.hanging) parts.push(`text-indent: ${-ind.hanging}px`);
351
+ else if (ind.firstLine) parts.push(`text-indent: ${ind.firstLine}px`);
352
+ }
353
+ const sp = attrs.spacing;
354
+ if (sp) {
355
+ if (sp.before) parts.push(`margin-top: ${sp.before}px`);
356
+ if (sp.after) parts.push(`margin-bottom: ${sp.after}px`);
357
+ if (sp.line && sp.lineRule === "auto") parts.push(`line-height: ${sp.line}`);
358
+ else if (sp.line) parts.push(`line-height: ${sp.line}px`);
359
+ }
360
+ return parts.join("; ");
361
+ }
362
+
363
+ // packages/model/src/lib/numbering.ts
364
+ function toLetters(n) {
365
+ let s = "";
366
+ let x = n;
367
+ while (x > 0) {
368
+ const rem = (x - 1) % 26;
369
+ s = String.fromCharCode(65 + rem) + s;
370
+ x = Math.floor((x - 1) / 26);
371
+ }
372
+ return s || "A";
373
+ }
374
+ function toRoman(n) {
375
+ if (n <= 0) return String(n);
376
+ const table = [
377
+ [1e3, "M"],
378
+ [900, "CM"],
379
+ [500, "D"],
380
+ [400, "CD"],
381
+ [100, "C"],
382
+ [90, "XC"],
383
+ [50, "L"],
384
+ [40, "XL"],
385
+ [10, "X"],
386
+ [9, "IX"],
387
+ [5, "V"],
388
+ [4, "IV"],
389
+ [1, "I"]
390
+ ];
391
+ let x = n;
392
+ let out = "";
393
+ for (const [v, sym] of table) {
394
+ while (x >= v) {
395
+ out += sym;
396
+ x -= v;
397
+ }
398
+ }
399
+ return out;
400
+ }
401
+ function formatCounter(n, fmt) {
402
+ switch (fmt) {
403
+ case "lowerLetter":
404
+ return toLetters(n).toLowerCase();
405
+ case "upperLetter":
406
+ return toLetters(n);
407
+ case "lowerRoman":
408
+ return toRoman(n).toLowerCase();
409
+ case "upperRoman":
410
+ return toRoman(n);
411
+ default:
412
+ return String(n);
413
+ }
414
+ }
415
+ function createNumberingCounter(defs) {
416
+ const counters = /* @__PURE__ */ new Map();
417
+ function next(numId, level) {
418
+ const def = defs?.[numId];
419
+ if (!def) return "";
420
+ const startOf = (lvl) => def.levels[lvl]?.start ?? 1;
421
+ const arr = counters.get(def.key) ?? [];
422
+ arr[level] = (arr[level] ?? startOf(level) - 1) + 1;
423
+ for (let l = level + 1; l < arr.length; l++) arr[l] = void 0;
424
+ counters.set(def.key, arr);
425
+ const lvlDef = def.levels[level];
426
+ if (!lvlDef || lvlDef.numFmt === "none") return "";
427
+ if (lvlDef.numFmt === "bullet") return lvlDef.lvlText || "\u2022";
428
+ return lvlDef.lvlText.replace(/%(\d)/g, (_match, digit) => {
429
+ const lvl = Number(digit) - 1;
430
+ const value = arr[lvl] ?? startOf(lvl);
431
+ return formatCounter(value, def.levels[lvl]?.numFmt ?? "decimal");
432
+ });
433
+ }
434
+ return { next };
435
+ }
436
+ // Annotate the CommonJS export names for ESM import in node:
437
+ 0 && (module.exports = {
438
+ commentSchema,
439
+ createNumberingCounter,
440
+ schema
441
+ });
@@ -0,0 +1,3 @@
1
+ export * from './lib/model.js';
2
+ export * from './lib/numbering.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,412 @@
1
+ // packages/model/src/lib/model.ts
2
+ import { Schema } from "prosemirror-model";
3
+ var schema = new Schema({
4
+ nodes: {
5
+ doc: {
6
+ content: "block+",
7
+ attrs: {
8
+ // NumberingDefs (numbering.ts) — list markers are recomputed from
9
+ // these at layout time, so edits renumber live. Importer-set.
10
+ numbering: { default: null },
11
+ // SectionConfig[] (contracts) — per-section column flow, delimited by
12
+ // w:sectPr breaks. null/absent → one implicit single-column section.
13
+ sections: { default: null },
14
+ // CommentNode[] (contracts) — comment threads keyed to `comment` marks.
15
+ // Edited via setDocAttribute so add/reply/resolve/delete undo cleanly.
16
+ comments: { default: null }
17
+ }
18
+ },
19
+ paragraph: {
20
+ group: "block",
21
+ content: "inline*",
22
+ // `list` is null for normal paragraphs, or ListInfo for list items.
23
+ // DOCX stores lists flat (each w:p carries w:numPr), so we keep them
24
+ // flat and let the marker be computed by the numbering counter.
25
+ //
26
+ // `align` mirrors w:jc ('left'|'center'|'right'|'justify'), null = default.
27
+ // `indent` mirrors w:ind, all measured in CSS px (Indent | null):
28
+ // { left, right, firstLine, hanging }. firstLine and hanging are
29
+ // mutually exclusive in OOXML; hanging wins if both appear.
30
+ attrs: {
31
+ list: { default: null },
32
+ align: { default: null },
33
+ indent: { default: null },
34
+ // Heading level 1–6 (maps to a Word "Heading N" style on export, and to
35
+ // an <h1>–<h6> in toDOM so the a11y mirror is semantic), or null for a
36
+ // body paragraph.
37
+ heading: { default: null },
38
+ // w:tabs — [{ pos, val: 'left'|'right'|'center'|'decimal', leader? }]
39
+ // in px from the paragraph's content left edge, or null. Importer-set.
40
+ tabs: { default: null },
41
+ // w:spacing — { before?, after?, line?, lineRule? }, or null.
42
+ spacing: { default: null },
43
+ // w:pageBreakBefore — start this paragraph on a new page.
44
+ pageBreakBefore: { default: false }
45
+ },
46
+ // No getAttrs: nothing in the pipeline parses paragraphs from the DOM
47
+ // yet (the importer builds nodes directly). align/indent still round-trip
48
+ // out through toDOM. Revisit when HTML paste lands.
49
+ parseDOM: [{ tag: "p" }],
50
+ toDOM(node) {
51
+ const attrs = node.attrs;
52
+ const style = paragraphStyle(attrs);
53
+ const tag = attrs.heading ? `h${attrs.heading}` : "p";
54
+ return [tag, style ? { style } : {}, 0];
55
+ }
56
+ },
57
+ text: { group: "inline" },
58
+ // A soft line break inside a paragraph (w:br) — forces a new line without
59
+ // ending the paragraph. Occupies one PM position, like an image atom.
60
+ hard_break: {
61
+ inline: true,
62
+ group: "inline",
63
+ selectable: false,
64
+ parseDOM: [{ tag: "br" }],
65
+ toDOM: () => ["br"]
66
+ },
67
+ // Inline image. `src` is typically a data URL (the importer inlines the
68
+ // embedded media); width/height are CSS pixels, null if unspecified.
69
+ image: {
70
+ inline: true,
71
+ group: "inline",
72
+ draggable: true,
73
+ attrs: {
74
+ src: {},
75
+ alt: { default: "" },
76
+ width: { default: null },
77
+ height: { default: null },
78
+ // wp:anchor (floating image): { wrap: 'square'|'topAndBottom'|'none',
79
+ // hAlign?, hOffset?, hRel?, vOffset?, vRel?, distL?, distR?, distT?,
80
+ // distB? } in px, or null for inline images. Importer-set.
81
+ float: { default: null },
82
+ // Drawn vector shape (wps rect / straight connector) riding this box:
83
+ // { kind: 'rect'|'line', stroke?, strokeWidth?, fill?, flipV? } — src
84
+ // is then '' and the box paints as vector. Null for real bitmaps.
85
+ shape: { default: null },
86
+ // Textbox (wps:txbx) content riding a shape: { paragraphs: <paragraph
87
+ // node JSON>[], inset?: {l,t,r,b} px }. The layout engine flows the
88
+ // paragraphs inside the shape's box (paint-only, not editable v1).
89
+ textbox: { default: null },
90
+ // Clockwise rotation in degrees around the box center (a:xfrm@rot).
91
+ // Paint-only: the layout box stays axis-aligned.
92
+ rotation: { default: 0 }
93
+ },
94
+ toDOM(node) {
95
+ const a = node.attrs;
96
+ const attrs = { src: a["src"], alt: a["alt"] };
97
+ if (a["width"] != null) attrs["width"] = String(a["width"]);
98
+ if (a["height"] != null) attrs["height"] = String(a["height"]);
99
+ return ["img", attrs];
100
+ }
101
+ },
102
+ // PAGE / NUMPAGES fields: atoms whose text is computed per page at paint
103
+ // time. `kind` is 'page' (current page number) or 'pages' (total count).
104
+ page_field: {
105
+ inline: true,
106
+ group: "inline",
107
+ atom: true,
108
+ attrs: { kind: {} },
109
+ parseDOM: [{ tag: "span[data-page-field]" }],
110
+ toDOM: (node) => [
111
+ "span",
112
+ { "data-page-field": String(node.attrs["kind"]) },
113
+ node.attrs["kind"] === "pages" ? "##" : "#"
114
+ ]
115
+ },
116
+ // Tables: kept structural (table → row → cell → block+). Horizontal spans
117
+ // map to colspan; vertical merges aren't collapsed yet (rowspan default 1).
118
+ table: {
119
+ group: "block",
120
+ content: "table_row+",
121
+ isolating: true,
122
+ attrs: {
123
+ // w:tblCellMar overrides (px: {left,right,top,bottom}), or null for
124
+ // Word defaults. Importer-set (same rationale as paragraph attrs).
125
+ cellPadding: { default: null },
126
+ // w:tblBorders visibility { top, bottom, left, right, insideH,
127
+ // insideV }, or null — OOXML tables are borderless unless declared.
128
+ borders: { default: null },
129
+ // w:tblPr/w:jc — 'center' | 'right' table alignment, or null (left).
130
+ align: { default: null }
131
+ },
132
+ parseDOM: [{ tag: "table" }],
133
+ toDOM: (node) => ["table", node.attrs["borders"] ? { "data-borders": "1" } : {}, ["tbody", 0]]
134
+ },
135
+ table_row: {
136
+ content: "table_cell+",
137
+ attrs: {
138
+ header: { default: false },
139
+ // w:trPr/w:tblHeader — repeat on every page
140
+ // w:trPr/w:cantSplit — the row must not break across pages. Absent
141
+ // (Word's default) means the paginator may split the row mid-content.
142
+ cantSplit: { default: false },
143
+ // w:trHeight — { value: px, exact: boolean } or null (auto).
144
+ height: { default: null }
145
+ },
146
+ // No getAttrs (same rationale as paragraph): the importer sets attrs
147
+ // directly; revisit when HTML paste lands.
148
+ parseDOM: [{ tag: "tr" }],
149
+ toDOM: (node) => node.attrs["header"] ? ["tr", { "data-header": "true" }, 0] : ["tr", 0]
150
+ },
151
+ table_cell: {
152
+ content: "block+",
153
+ isolating: true,
154
+ attrs: {
155
+ colspan: { default: 1 },
156
+ rowspan: { default: 1 },
157
+ colwidth: { default: null },
158
+ // px widths of the spanned columns, or null
159
+ background: { default: null },
160
+ // w:shd w:fill — cell fill "#RRGGBB"
161
+ vAlign: { default: null },
162
+ // w:vAlign — 'center' | 'bottom' (top default)
163
+ borders: { default: null }
164
+ // w:tcBorders per-side visibility override
165
+ },
166
+ parseDOM: [{ tag: "td" }, { tag: "th" }],
167
+ toDOM(node) {
168
+ const attrs = {};
169
+ if (node.attrs["colspan"] !== 1) attrs["colspan"] = String(node.attrs["colspan"]);
170
+ if (node.attrs["rowspan"] !== 1) attrs["rowspan"] = String(node.attrs["rowspan"]);
171
+ if (node.attrs["background"]) attrs["style"] = `background-color: ${node.attrs["background"]}`;
172
+ return ["td", attrs, 0];
173
+ }
174
+ }
175
+ },
176
+ marks: {
177
+ // w:b
178
+ strong: {
179
+ parseDOM: [{ tag: "strong" }, { tag: "b" }],
180
+ toDOM: () => ["strong", 0]
181
+ },
182
+ // w:i
183
+ em: {
184
+ parseDOM: [{ tag: "em" }, { tag: "i" }],
185
+ toDOM: () => ["em", 0]
186
+ },
187
+ // w:u
188
+ underline: {
189
+ parseDOM: [{ tag: "u" }],
190
+ toDOM: () => ["u", 0]
191
+ },
192
+ // w:strike
193
+ strike: {
194
+ parseDOM: [{ tag: "s" }, { tag: "strike" }],
195
+ toDOM: () => ["s", 0]
196
+ },
197
+ // w:color — hex "#RRGGBB"
198
+ textColor: {
199
+ attrs: { color: {} },
200
+ parseDOM: [{ style: "color", getAttrs: (value) => ({ color: value }) }],
201
+ toDOM(mark) {
202
+ return ["span", { style: `color: ${mark.attrs["color"]}` }, 0];
203
+ }
204
+ },
205
+ // w:sz — size in points
206
+ fontSize: {
207
+ attrs: { size: {} },
208
+ parseDOM: [
209
+ {
210
+ style: "font-size",
211
+ getAttrs: (value) => {
212
+ const pt = parseFloat(value);
213
+ return Number.isNaN(pt) ? false : { size: pt };
214
+ }
215
+ }
216
+ ],
217
+ toDOM(mark) {
218
+ return ["span", { style: `font-size: ${mark.attrs["size"]}pt` }, 0];
219
+ }
220
+ },
221
+ // w:vertAlign — superscript / subscript
222
+ vertAlign: {
223
+ attrs: { value: {} },
224
+ // 'super' | 'sub'
225
+ parseDOM: [
226
+ { tag: "sup", getAttrs: () => ({ value: "super" }) },
227
+ { tag: "sub", getAttrs: () => ({ value: "sub" }) }
228
+ ],
229
+ toDOM: (mark) => [mark.attrs["value"] === "sub" ? "sub" : "sup", 0]
230
+ },
231
+ // w:highlight / w:shd w:fill — run background color ("#RRGGBB")
232
+ highlight: {
233
+ attrs: { color: {} },
234
+ parseDOM: [
235
+ { style: "background-color", getAttrs: (value) => ({ color: value }) }
236
+ ],
237
+ toDOM(mark) {
238
+ return ["span", { style: `background-color: ${mark.attrs["color"]}` }, 0];
239
+ }
240
+ },
241
+ // w:rFonts — font family
242
+ fontFamily: {
243
+ attrs: { family: {} },
244
+ parseDOM: [{ style: "font-family", getAttrs: (value) => ({ family: value }) }],
245
+ toDOM(mark) {
246
+ return ["span", { style: `font-family: ${mark.attrs["family"]}` }, 0];
247
+ }
248
+ },
249
+ // w:hyperlink — external URL or "#anchor"
250
+ link: {
251
+ attrs: { href: {} },
252
+ inclusive: false,
253
+ toDOM(mark) {
254
+ return ["a", { href: mark.attrs["href"], rel: "noopener", target: "_blank" }, 0];
255
+ }
256
+ },
257
+ // w:footnoteReference — the carrier text is the superscript number; `num`
258
+ // lets the layout engine match the reference to its page-bottom body.
259
+ footnote: {
260
+ attrs: { num: {} },
261
+ inclusive: false,
262
+ parseDOM: [
263
+ {
264
+ tag: "sup[data-footnote]",
265
+ // `el` is an HTMLElement at runtime; this package has no DOM lib, so
266
+ // narrow structurally rather than naming the type.
267
+ getAttrs: (el) => ({
268
+ num: Number(el.getAttribute("data-footnote")) || 0
269
+ })
270
+ }
271
+ ],
272
+ toDOM(mark) {
273
+ return ["sup", { "data-footnote": String(mark.attrs["num"]) }, 0];
274
+ }
275
+ }
276
+ // The `comment` mark (w:commentRangeStart/End) is contributed by the comment
277
+ // plugin (@shadow-garden/bapbong-comments) via the editor's schema
278
+ // composition — it is NOT part of the base document schema, so a build
279
+ // without comments stays free of comment marks. The `comments` doc attr
280
+ // above is inert storage the plugin populates when present.
281
+ }
282
+ });
283
+ var commentSchema = new Schema({
284
+ nodes: {
285
+ doc: { content: "block+" },
286
+ paragraph: { group: "block", content: "inline*", parseDOM: [{ tag: "p" }], toDOM: () => ["p", 0] },
287
+ text: { group: "inline" },
288
+ // @mention: an inline atom carrying the mentioned user's id + display name.
289
+ // `leafText` lets textContent include "@Name" (search / plain-text preview).
290
+ mention: {
291
+ group: "inline",
292
+ inline: true,
293
+ atom: true,
294
+ selectable: false,
295
+ attrs: { id: {}, label: {} },
296
+ leafText: (node) => `@${node.attrs["label"]}`,
297
+ toDOM: (node) => [
298
+ "span",
299
+ { class: "mention", "data-id": String(node.attrs["id"]) },
300
+ `@${node.attrs["label"]}`
301
+ ],
302
+ parseDOM: [
303
+ {
304
+ tag: "span.mention",
305
+ getAttrs: (el) => {
306
+ const e = el;
307
+ return { id: e.getAttribute("data-id"), label: (e.textContent ?? "").replace(/^@/, "") };
308
+ }
309
+ }
310
+ ]
311
+ }
312
+ },
313
+ marks: {}
314
+ });
315
+ function paragraphStyle(attrs) {
316
+ const parts = [];
317
+ if (attrs.align) parts.push(`text-align: ${attrs.align}`);
318
+ const ind = attrs.indent;
319
+ if (ind) {
320
+ if (ind.left) parts.push(`margin-left: ${ind.left}px`);
321
+ if (ind.right) parts.push(`margin-right: ${ind.right}px`);
322
+ if (ind.hanging) parts.push(`text-indent: ${-ind.hanging}px`);
323
+ else if (ind.firstLine) parts.push(`text-indent: ${ind.firstLine}px`);
324
+ }
325
+ const sp = attrs.spacing;
326
+ if (sp) {
327
+ if (sp.before) parts.push(`margin-top: ${sp.before}px`);
328
+ if (sp.after) parts.push(`margin-bottom: ${sp.after}px`);
329
+ if (sp.line && sp.lineRule === "auto") parts.push(`line-height: ${sp.line}`);
330
+ else if (sp.line) parts.push(`line-height: ${sp.line}px`);
331
+ }
332
+ return parts.join("; ");
333
+ }
334
+
335
+ // packages/model/src/lib/numbering.ts
336
+ function toLetters(n) {
337
+ let s = "";
338
+ let x = n;
339
+ while (x > 0) {
340
+ const rem = (x - 1) % 26;
341
+ s = String.fromCharCode(65 + rem) + s;
342
+ x = Math.floor((x - 1) / 26);
343
+ }
344
+ return s || "A";
345
+ }
346
+ function toRoman(n) {
347
+ if (n <= 0) return String(n);
348
+ const table = [
349
+ [1e3, "M"],
350
+ [900, "CM"],
351
+ [500, "D"],
352
+ [400, "CD"],
353
+ [100, "C"],
354
+ [90, "XC"],
355
+ [50, "L"],
356
+ [40, "XL"],
357
+ [10, "X"],
358
+ [9, "IX"],
359
+ [5, "V"],
360
+ [4, "IV"],
361
+ [1, "I"]
362
+ ];
363
+ let x = n;
364
+ let out = "";
365
+ for (const [v, sym] of table) {
366
+ while (x >= v) {
367
+ out += sym;
368
+ x -= v;
369
+ }
370
+ }
371
+ return out;
372
+ }
373
+ function formatCounter(n, fmt) {
374
+ switch (fmt) {
375
+ case "lowerLetter":
376
+ return toLetters(n).toLowerCase();
377
+ case "upperLetter":
378
+ return toLetters(n);
379
+ case "lowerRoman":
380
+ return toRoman(n).toLowerCase();
381
+ case "upperRoman":
382
+ return toRoman(n);
383
+ default:
384
+ return String(n);
385
+ }
386
+ }
387
+ function createNumberingCounter(defs) {
388
+ const counters = /* @__PURE__ */ new Map();
389
+ function next(numId, level) {
390
+ const def = defs?.[numId];
391
+ if (!def) return "";
392
+ const startOf = (lvl) => def.levels[lvl]?.start ?? 1;
393
+ const arr = counters.get(def.key) ?? [];
394
+ arr[level] = (arr[level] ?? startOf(level) - 1) + 1;
395
+ for (let l = level + 1; l < arr.length; l++) arr[l] = void 0;
396
+ counters.set(def.key, arr);
397
+ const lvlDef = def.levels[level];
398
+ if (!lvlDef || lvlDef.numFmt === "none") return "";
399
+ if (lvlDef.numFmt === "bullet") return lvlDef.lvlText || "\u2022";
400
+ return lvlDef.lvlText.replace(/%(\d)/g, (_match, digit) => {
401
+ const lvl = Number(digit) - 1;
402
+ const value = arr[lvl] ?? startOf(lvl);
403
+ return formatCounter(value, def.levels[lvl]?.numFmt ?? "decimal");
404
+ });
405
+ }
406
+ return { next };
407
+ }
408
+ export {
409
+ commentSchema,
410
+ createNumberingCounter,
411
+ schema
412
+ };
@@ -0,0 +1,55 @@
1
+ import { Schema } from 'prosemirror-model';
2
+ /**
3
+ * bapbong's ProseMirror document schema.
4
+ *
5
+ * Deliberately minimal for M1 (DOCX import vertical slice): block paragraphs
6
+ * of inline text with the four common character toggles. Lists, tables,
7
+ * headings, images, etc. are added in later milestones — when they land they
8
+ * extend THIS schema so the importer, layout engine, and (canvas) painter all
9
+ * agree on one document model.
10
+ */
11
+ export declare const schema: Schema<"doc" | "paragraph" | "text" | "hard_break" | "image" | "page_field" | "table" | "table_row" | "table_cell", "strong" | "em" | "underline" | "strike" | "textColor" | "fontSize" | "vertAlign" | "highlight" | "fontFamily" | "link" | "footnote">;
12
+ /** Concrete schema type, handy for typing Node/Mark across packages. */
13
+ export type BapbongSchema = typeof schema;
14
+ /**
15
+ * Minimal schema for composing a comment body (the comment sidebar's little
16
+ * editors). Rich enough for paragraphs of text plus an inline `mention` atom
17
+ * (@user). Comment bodies are stored as this schema's JSON on the comment
18
+ * thread, kept separate from the document schema.
19
+ */
20
+ export declare const commentSchema: Schema<"doc" | "paragraph" | "text" | "mention", never>;
21
+ export type CommentSchema = typeof commentSchema;
22
+ /** Paragraph horizontal alignment (mirrors w:jc). */
23
+ export type Align = 'left' | 'center' | 'right' | 'justify';
24
+ /** Paragraph indentation in CSS px (mirrors w:ind). `firstLine` and `hanging`
25
+ * are mutually exclusive; if both are present, `hanging` takes precedence. */
26
+ export interface Indent {
27
+ left?: number;
28
+ right?: number;
29
+ firstLine?: number;
30
+ hanging?: number;
31
+ }
32
+ /** Shape of the paragraph node's attrs (for typed toDOM/serialization). */
33
+ export interface Spacing {
34
+ before?: number;
35
+ after?: number;
36
+ line?: number;
37
+ lineRule?: 'auto' | 'exact' | 'atLeast';
38
+ }
39
+ export interface ParagraphAttrs {
40
+ list: ListInfo | null;
41
+ align: Align | null;
42
+ indent: Indent | null;
43
+ spacing?: Spacing | null;
44
+ heading?: number | null;
45
+ }
46
+ /** Value of a list paragraph's `list` attribute. The marker string ("1.",
47
+ * "2.a", "•") is NOT stored — it's recomputed at layout time from the doc's
48
+ * numbering defs, so edits renumber live. `marker` remains only for legacy
49
+ * callers that pass pre-resolved markers straight to the layout engine. */
50
+ export interface ListInfo {
51
+ numId: string;
52
+ level: number;
53
+ marker?: string;
54
+ }
55
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/lib/model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C;;;;;;;;GAQG;AACH,eAAO,MAAM,MAAM,2PAyRjB,CAAC;AAEH,wEAAwE;AACxE,MAAM,MAAM,aAAa,GAAG,OAAO,MAAM,CAAC;AAE1C;;;;;GAKG;AACH,eAAO,MAAM,aAAa,yDA+BxB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC;AAEjD,qDAAqD;AACrD,MAAM,MAAM,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAE5D;+EAC+E;AAC/E,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,2EAA2E;AAC3E,MAAM,WAAW,OAAO;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;CACzC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAyBD;;;4EAG4E;AAC5E,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Live list numbering. The document stores WHAT a paragraph is (numId +
3
+ * level) and the numbering definitions (on the doc node's `numbering` attr);
4
+ * marker strings ("1.", "2.a.", "•") are recomputed by walking the document —
5
+ * the layout engine does this every pass, so inserting/deleting/reordering
6
+ * list items renumbers everything, like Word.
7
+ */
8
+ /** One w:lvl of an abstract numbering definition (plain data — attr-safe). */
9
+ export interface NumberingLevelDef {
10
+ numFmt: string;
11
+ lvlText: string;
12
+ start: number;
13
+ }
14
+ /** Definitions keyed by numId. `key` groups numIds that share one abstract
15
+ * definition (their counters advance together, mirroring w:abstractNumId). */
16
+ export interface NumberingDefs {
17
+ [numId: string]: {
18
+ key: string;
19
+ levels: Record<number, NumberingLevelDef>;
20
+ };
21
+ }
22
+ /** Stateful counter: call `next` once per list paragraph, in document order. */
23
+ export interface NumberingCounter {
24
+ next(numId: string, level: number): string;
25
+ }
26
+ export declare function createNumberingCounter(defs: NumberingDefs | null | undefined): NumberingCounter;
27
+ //# sourceMappingURL=numbering.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"numbering.d.ts","sourceRoot":"","sources":["../../src/lib/numbering.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;+EAC+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,CAAC,KAAK,EAAE,MAAM,GAAG;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;KAC3C,CAAC;CACH;AAED,gFAAgF;AAChF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5C;AA8CD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,GAAG,gBAAgB,CA2B/F"}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@shadow-garden/bapbong-model",
3
+ "version": "0.1.0",
4
+ "description": "bapbong — ProseMirror document model / schema",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/shadowgarden-app/bapbong.git",
9
+ "directory": "packages/model"
10
+ },
11
+ "type": "module",
12
+ "main": "./dist/index.cjs",
13
+ "module": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ "./package.json": "./package.json",
17
+ ".": {
18
+ "@shadow-garden/source": "./src/index.ts",
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js",
21
+ "require": "./dist/index.cjs",
22
+ "default": "./dist/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "!**/*.tsbuildinfo"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "nx": {
33
+ "tags": [
34
+ "scope:model"
35
+ ],
36
+ "targets": {
37
+ "build": {
38
+ "executor": "@nx/esbuild:esbuild",
39
+ "outputs": [
40
+ "{options.outputPath}"
41
+ ],
42
+ "options": {
43
+ "outputPath": "packages/model/dist",
44
+ "main": "packages/model/src/index.ts",
45
+ "tsConfig": "packages/model/tsconfig.lib.json",
46
+ "format": [
47
+ "esm",
48
+ "cjs"
49
+ ],
50
+ "declarationRootDir": "packages/model/src",
51
+ "generatePackageJson": false
52
+ }
53
+ }
54
+ }
55
+ },
56
+ "dependencies": {
57
+ "prosemirror-model": "^1.25.7"
58
+ }
59
+ }