@shadow-garden/bapbong-model 0.12.0 → 0.14.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/dist/index.cjs CHANGED
@@ -71,6 +71,262 @@ function fieldAt(doc, pos) {
71
71
 
72
72
  // packages/model/src/lib/model.ts
73
73
  var import_prosemirror_model = require("prosemirror-model");
74
+
75
+ // packages/contracts/dist/index.js
76
+ var FIELD_SET = {
77
+ family: true,
78
+ sizePt: true,
79
+ bold: true,
80
+ italic: true,
81
+ letterSpacing: true,
82
+ scaleX: true,
83
+ kerning: true
84
+ };
85
+ var FIELDS = Object.keys(FIELD_SET);
86
+ var SUB_DIGIT = "\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089";
87
+ var SUP_DIGIT = "\u2070\xB9\xB2\xB3\u2074\u2075\u2076\u2077\u2078\u2079";
88
+ var digitsVia = (s, alphabet) => /^[0-9]+$/.test(s) ? [...s].map((d) => alphabet[Number(d)]).join("") : null;
89
+ function astToLinear(row) {
90
+ const count = (s) => [...s].length;
91
+ const paren = (s) => count(s) <= 1 || s.startsWith("(") && s.endsWith(")") ? s : `(${s})`;
92
+ const sub = (s) => digitsVia(s, SUB_DIGIT) ?? (count(s) > 1 ? `_(${s})` : `_${s}`);
93
+ const sup = (s) => digitsVia(s, SUP_DIGIT) ?? (count(s) > 1 ? `^(${s})` : `^${s}`);
94
+ const one = (n) => {
95
+ switch (n.t) {
96
+ case "chr":
97
+ return n.ch;
98
+ case "frac":
99
+ return `${paren(astToLinear(n.num))}/${paren(astToLinear(n.den))}`;
100
+ case "rad": {
101
+ const deg = astToLinear(n.deg);
102
+ return `${deg ? sup(deg) : ""}\u221A(${astToLinear(n.body)})`;
103
+ }
104
+ case "scr": {
105
+ const lo = astToLinear(n.sub);
106
+ const hi = astToLinear(n.sup);
107
+ return astToLinear(n.base) + (lo ? sub(lo) : "") + (hi ? sup(hi) : "");
108
+ }
109
+ case "fence":
110
+ return n.l + astToLinear(n.body) + n.r;
111
+ case "big": {
112
+ const lo = astToLinear(n.lo);
113
+ const hi = astToLinear(n.hi);
114
+ return n.op + (lo ? sub(lo) : "") + (hi ? sup(hi) : "") + paren(astToLinear(n.body));
115
+ }
116
+ case "func":
117
+ return `${astToLinear(n.name)}\u2061${paren(astToLinear(n.body))}`;
118
+ case "acc":
119
+ return astToLinear(n.body) + n.chr;
120
+ case "lim": {
121
+ const l = astToLinear(n.lim);
122
+ return astToLinear(n.base) + (l ? n.below ? sub(l) : sup(l) : "");
123
+ }
124
+ case "mat": {
125
+ const lines = [];
126
+ for (let i = 0; i < n.cells.length; i += n.cols)
127
+ lines.push(
128
+ n.cells.slice(i, i + n.cols).map((c) => astToLinear(c)).join("&")
129
+ );
130
+ return `(${lines.join("@")})`;
131
+ }
132
+ }
133
+ };
134
+ return row.map(one).join("");
135
+ }
136
+ function isEqRow(v) {
137
+ if (!Array.isArray(v)) return false;
138
+ return v.every((n) => {
139
+ if (typeof n !== "object" || n === null) return false;
140
+ const t = n.t;
141
+ if (t === "chr") return typeof n.ch === "string";
142
+ if (t === "frac")
143
+ return isEqRow(n.num) && isEqRow(n.den);
144
+ if (t === "rad")
145
+ return isEqRow(n.deg) && isEqRow(n.body);
146
+ if (t === "scr")
147
+ return isEqRow(n.base) && isEqRow(n.sub) && isEqRow(n.sup);
148
+ if (t === "fence")
149
+ return typeof n.l === "string" && typeof n.r === "string" && isEqRow(n.body);
150
+ if (t === "big")
151
+ return typeof n.op === "string" && isEqRow(n.lo) && isEqRow(n.hi) && isEqRow(n.body);
152
+ if (t === "func")
153
+ return isEqRow(n.name) && isEqRow(n.body);
154
+ if (t === "acc")
155
+ return typeof n.chr === "string" && isEqRow(n.body);
156
+ if (t === "lim")
157
+ return typeof n.below === "boolean" && isEqRow(n.base) && isEqRow(n.lim);
158
+ if (t === "mat") {
159
+ const m = n;
160
+ return typeof m.cols === "number" && m.cols > 0 && Array.isArray(m.cells) && m.cells.every((c) => isEqRow(c));
161
+ }
162
+ return false;
163
+ });
164
+ }
165
+ var frac = () => ({ t: "frac", num: [], den: [] });
166
+ var scr = (slots) => ({
167
+ t: "scr",
168
+ base: [],
169
+ sub: [],
170
+ sup: [],
171
+ slots
172
+ });
173
+ var rad = (showDeg) => ({
174
+ t: "rad",
175
+ deg: [],
176
+ body: [],
177
+ showDeg
178
+ });
179
+ var fence = (l, r) => ({
180
+ t: "fence",
181
+ l,
182
+ r,
183
+ body: []
184
+ });
185
+ var upright = (s) => [...s].map((ch) => ({ t: "chr", ch }));
186
+ var func = (name) => ({
187
+ t: "func",
188
+ name: upright(name),
189
+ body: []
190
+ });
191
+ var acc = (chr) => ({ t: "acc", chr, body: [] });
192
+ var mat = (rows, cols) => ({
193
+ t: "mat",
194
+ cols,
195
+ cells: Array.from({ length: rows * cols }, () => [])
196
+ });
197
+ var braced = (l, r, rows, cols) => ({
198
+ t: "fence",
199
+ l,
200
+ r,
201
+ body: [mat(rows, cols)]
202
+ });
203
+ var lim = (name, below = true) => ({
204
+ t: "lim",
205
+ base: upright(name),
206
+ lim: [],
207
+ below
208
+ });
209
+ var big = (op, limits = false) => ({
210
+ t: "big",
211
+ op,
212
+ lo: [],
213
+ hi: [],
214
+ body: [],
215
+ showLo: limits,
216
+ showHi: limits
217
+ });
218
+ var EQ_STRUCTURES = [
219
+ { group: "Fraction", name: "Stacked", node: frac(), focus: ["num"] },
220
+ { group: "Script", name: "Superscript", node: scr("sup"), focus: ["sup"] },
221
+ { group: "Script", name: "Subscript", node: scr("sub"), focus: ["sub"] },
222
+ { group: "Script", name: "Sub and super", node: scr("both"), focus: ["sub"] },
223
+ { group: "Radical", name: "Square root", node: rad(false), focus: ["body"] },
224
+ { group: "Radical", name: "Nth root", node: rad(true), focus: ["deg"] },
225
+ { group: "Integral", name: "Integral", node: big("\u222B"), focus: ["body"] },
226
+ { group: "Integral", name: "Definite", node: big("\u222B", true), focus: ["lo"] },
227
+ { group: "Integral", name: "Double", node: big("\u222C"), focus: ["body"] },
228
+ { group: "Integral", name: "Contour", node: big("\u222E"), focus: ["body"] },
229
+ {
230
+ group: "Large operator",
231
+ name: "Summation",
232
+ node: big("\u2211"),
233
+ focus: ["body"]
234
+ },
235
+ {
236
+ group: "Large operator",
237
+ name: "Sum with limits",
238
+ node: big("\u2211", true),
239
+ focus: ["lo"]
240
+ },
241
+ { group: "Large operator", name: "Product", node: big("\u220F"), focus: ["body"] },
242
+ {
243
+ group: "Large operator",
244
+ name: "Product with limits",
245
+ node: big("\u220F", true),
246
+ focus: ["lo"]
247
+ },
248
+ { group: "Large operator", name: "Union", node: big("\u22C3"), focus: ["body"] },
249
+ {
250
+ group: "Large operator",
251
+ name: "Intersection",
252
+ node: big("\u22C2"),
253
+ focus: ["body"]
254
+ },
255
+ {
256
+ group: "Bracket",
257
+ name: "Parentheses",
258
+ node: fence("(", ")"),
259
+ focus: ["body"]
260
+ },
261
+ {
262
+ group: "Bracket",
263
+ name: "Brackets",
264
+ node: fence("[", "]"),
265
+ focus: ["body"]
266
+ },
267
+ { group: "Bracket", name: "Braces", node: fence("{", "}"), focus: ["body"] },
268
+ {
269
+ group: "Bracket",
270
+ name: "Absolute value",
271
+ node: fence("|", "|"),
272
+ focus: ["body"]
273
+ },
274
+ { group: "Bracket", name: "Norm", node: fence("\u2016", "\u2016"), focus: ["body"] },
275
+ { group: "Bracket", name: "Angle", node: fence("\u27E8", "\u27E9"), focus: ["body"] },
276
+ { group: "Bracket", name: "Floor", node: fence("\u230A", "\u230B"), focus: ["body"] },
277
+ { group: "Bracket", name: "Ceiling", node: fence("\u2308", "\u2309"), focus: ["body"] },
278
+ { group: "Function", name: "sin", node: func("sin"), focus: ["body"] },
279
+ { group: "Function", name: "cos", node: func("cos"), focus: ["body"] },
280
+ { group: "Function", name: "tan", node: func("tan"), focus: ["body"] },
281
+ { group: "Function", name: "cot", node: func("cot"), focus: ["body"] },
282
+ { group: "Function", name: "ln", node: func("ln"), focus: ["body"] },
283
+ { group: "Function", name: "log", node: func("log"), focus: ["body"] },
284
+ { group: "Function", name: "exp", node: func("exp"), focus: ["body"] },
285
+ // The name is empty and editable — for anything the list does not carry.
286
+ { group: "Function", name: "Custom", node: func(""), focus: ["name"] },
287
+ { group: "Accent", name: "Bar", node: acc("\u0305"), focus: ["body"] },
288
+ { group: "Accent", name: "Vector", node: acc("\u20D7"), focus: ["body"] },
289
+ { group: "Accent", name: "Hat", node: acc("\u0302"), focus: ["body"] },
290
+ { group: "Accent", name: "Tilde", node: acc("\u0303"), focus: ["body"] },
291
+ { group: "Accent", name: "Dot", node: acc("\u0307"), focus: ["body"] },
292
+ { group: "Accent", name: "Double dot", node: acc("\u0308"), focus: ["body"] },
293
+ { group: "Limit", name: "Limit", node: lim("lim"), focus: ["lim"] },
294
+ { group: "Limit", name: "Maximum", node: lim("max"), focus: ["lim"] },
295
+ { group: "Limit", name: "Minimum", node: lim("min"), focus: ["lim"] },
296
+ { group: "Limit", name: "Over", node: lim("", false), focus: ["base"] },
297
+ { group: "Matrix", name: "1\xD72", node: mat(1, 2), focus: ["c0"] },
298
+ { group: "Matrix", name: "2\xD71", node: mat(2, 1), focus: ["c0"] },
299
+ { group: "Matrix", name: "2\xD72", node: mat(2, 2), focus: ["c0"] },
300
+ { group: "Matrix", name: "3\xD73", node: mat(3, 3), focus: ["c0"] },
301
+ {
302
+ group: "Matrix",
303
+ name: "Bracketed 2\xD72",
304
+ node: braced("[", "]", 2, 2),
305
+ focus: ["body", 0, "c0"]
306
+ },
307
+ {
308
+ group: "Matrix",
309
+ name: "Determinant 2\xD72",
310
+ node: braced("|", "|", 2, 2),
311
+ focus: ["body", 0, "c0"]
312
+ },
313
+ // Two and three equations under one brace — how a system is set in
314
+ // Vietnamese exam papers, and awkward to build out of the pieces.
315
+ {
316
+ group: "Matrix",
317
+ name: "System of 2",
318
+ node: braced("{", "", 2, 1),
319
+ focus: ["body", 0, "c0"]
320
+ },
321
+ {
322
+ group: "Matrix",
323
+ name: "System of 3",
324
+ node: braced("{", "", 3, 1),
325
+ focus: ["body", 0, "c0"]
326
+ }
327
+ ];
328
+
329
+ // packages/model/src/lib/model.ts
74
330
  function dataJson(el, name) {
75
331
  const raw = el.getAttribute(name);
76
332
  if (!raw) return null;
@@ -292,6 +548,42 @@ var schema = new import_prosemirror_model.Schema({
292
548
  parseDOM: [{ tag: "br.column-break" }],
293
549
  toDOM: () => ["br", { class: "column-break" }]
294
550
  },
551
+ // An equation with 2D structure — the AST (EqNode[], contracts) rides
552
+ // `ast`. An inline ATOM: the layout engine typesets it into a vector box
553
+ // sized by `sizePt`; editing goes through the equation plugin's slot
554
+ // editor, never the hidden text editor. toDOM carries the linear
555
+ // spelling as text, so the a11y mirror and copy read the math.
556
+ equation: {
557
+ inline: true,
558
+ group: "inline",
559
+ atom: true,
560
+ attrs: { ast: {}, sizePt: { default: 12 } },
561
+ parseDOM: [
562
+ {
563
+ tag: "span[data-eq]",
564
+ getAttrs: (el) => {
565
+ const ast = dataJson(el, "data-eq");
566
+ if (!isEqRow(ast)) return false;
567
+ const size = Number(el.getAttribute("data-eq-size"));
568
+ return {
569
+ ast,
570
+ sizePt: Number.isFinite(size) && size > 0 ? size : 12
571
+ };
572
+ }
573
+ }
574
+ ],
575
+ toDOM(node) {
576
+ const ast = node.attrs["ast"];
577
+ return [
578
+ "span",
579
+ {
580
+ "data-eq": JSON.stringify(ast),
581
+ "data-eq-size": String(node.attrs["sizePt"])
582
+ },
583
+ astToLinear(isEqRow(ast) ? ast : [])
584
+ ];
585
+ }
586
+ },
295
587
  // Inline image. `src` is typically a data URL (the importer inlines the
296
588
  // embedded media); width/height are CSS pixels, null if unspecified.
297
589
  image: {
@@ -315,6 +607,27 @@ var schema = new import_prosemirror_model.Schema({
315
607
  // { kind: 'rect'|'line', stroke?, strokeWidth?, fill?, flipV? } — src
316
608
  // is then '' and the box paints as vector. Null for real bitmaps.
317
609
  shape: { default: null },
610
+ // Replayed metafile (VectorImageSpec) — how MathType/OLE equation
611
+ // previews (vector WMF) paint. `src` keeps the original metafile
612
+ // data URL for export; the painter draws the ops instead. Null for
613
+ // bitmaps and preset shapes.
614
+ vector: { default: null },
615
+ // The equation's LINEAR text recovered from the metafile's embedded
616
+ // MTEF (MathType semantics) — what "Convert to editable equation"
617
+ // replaces the picture with. Null when the picture carries none.
618
+ equation: { default: null },
619
+ // The same equation as an AST (EqNode[]) — what conversion turns
620
+ // into a real 2D equation node. Null when undecodable.
621
+ equationAst: { default: null },
622
+ // `o:OLEObject/@ProgID` when this box is an EMBEDDED OBJECT rather
623
+ // than a picture ("Equation.DSMT4" for MathType). The bitmap is only
624
+ // the object's preview, so the editor offers object tools, not the
625
+ // picture toolkit. Null for real pictures.
626
+ oleProgId: { default: null },
627
+ // w:position on the run holding a legacy object — baseline shift in
628
+ // px, positive UP (same convention as InlineRun.raise). MathType
629
+ // lowers every equation so its baseline meets the text line's.
630
+ raise: { default: 0 },
318
631
  // Textbox (wps:txbx) content riding a shape: { paragraphs: <paragraph
319
632
  // node JSON>[], inset?: {l,t,r,b} px }. The layout engine flows the
320
633
  // paragraphs inside the shape's box (paint-only, not editable v1).
@@ -679,6 +992,15 @@ var schema = new import_prosemirror_model.Schema({
679
992
  0
680
993
  ]
681
994
  },
995
+ // m:oMath — the run belongs to an equation. Linear v1: the letterforms
996
+ // live in the TEXT as Unicode math alphanumerics (𝑥, 𝒫, ℝ — see
997
+ // math.ts), so the mark is semantic, not visual: it scopes math
998
+ // autocorrect, keeps the region addressable for the coming in-place
999
+ // editor, and tells the exporter to wrap these runs back into m:oMath.
1000
+ math: {
1001
+ parseDOM: [{ tag: "span[data-math]" }],
1002
+ toDOM: () => ["span", { "data-math": "1" }, 0]
1003
+ },
682
1004
  // w:highlight / w:shd w:fill — run background color ("#RRGGBB")
683
1005
  highlight: {
684
1006
  attrs: { color: {} },
package/dist/index.js CHANGED
@@ -39,6 +39,262 @@ function fieldAt(doc, pos) {
39
39
 
40
40
  // packages/model/src/lib/model.ts
41
41
  import { Schema } from "prosemirror-model";
42
+
43
+ // packages/contracts/dist/index.js
44
+ var FIELD_SET = {
45
+ family: true,
46
+ sizePt: true,
47
+ bold: true,
48
+ italic: true,
49
+ letterSpacing: true,
50
+ scaleX: true,
51
+ kerning: true
52
+ };
53
+ var FIELDS = Object.keys(FIELD_SET);
54
+ var SUB_DIGIT = "\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089";
55
+ var SUP_DIGIT = "\u2070\xB9\xB2\xB3\u2074\u2075\u2076\u2077\u2078\u2079";
56
+ var digitsVia = (s, alphabet) => /^[0-9]+$/.test(s) ? [...s].map((d) => alphabet[Number(d)]).join("") : null;
57
+ function astToLinear(row) {
58
+ const count = (s) => [...s].length;
59
+ const paren = (s) => count(s) <= 1 || s.startsWith("(") && s.endsWith(")") ? s : `(${s})`;
60
+ const sub = (s) => digitsVia(s, SUB_DIGIT) ?? (count(s) > 1 ? `_(${s})` : `_${s}`);
61
+ const sup = (s) => digitsVia(s, SUP_DIGIT) ?? (count(s) > 1 ? `^(${s})` : `^${s}`);
62
+ const one = (n) => {
63
+ switch (n.t) {
64
+ case "chr":
65
+ return n.ch;
66
+ case "frac":
67
+ return `${paren(astToLinear(n.num))}/${paren(astToLinear(n.den))}`;
68
+ case "rad": {
69
+ const deg = astToLinear(n.deg);
70
+ return `${deg ? sup(deg) : ""}\u221A(${astToLinear(n.body)})`;
71
+ }
72
+ case "scr": {
73
+ const lo = astToLinear(n.sub);
74
+ const hi = astToLinear(n.sup);
75
+ return astToLinear(n.base) + (lo ? sub(lo) : "") + (hi ? sup(hi) : "");
76
+ }
77
+ case "fence":
78
+ return n.l + astToLinear(n.body) + n.r;
79
+ case "big": {
80
+ const lo = astToLinear(n.lo);
81
+ const hi = astToLinear(n.hi);
82
+ return n.op + (lo ? sub(lo) : "") + (hi ? sup(hi) : "") + paren(astToLinear(n.body));
83
+ }
84
+ case "func":
85
+ return `${astToLinear(n.name)}\u2061${paren(astToLinear(n.body))}`;
86
+ case "acc":
87
+ return astToLinear(n.body) + n.chr;
88
+ case "lim": {
89
+ const l = astToLinear(n.lim);
90
+ return astToLinear(n.base) + (l ? n.below ? sub(l) : sup(l) : "");
91
+ }
92
+ case "mat": {
93
+ const lines = [];
94
+ for (let i = 0; i < n.cells.length; i += n.cols)
95
+ lines.push(
96
+ n.cells.slice(i, i + n.cols).map((c) => astToLinear(c)).join("&")
97
+ );
98
+ return `(${lines.join("@")})`;
99
+ }
100
+ }
101
+ };
102
+ return row.map(one).join("");
103
+ }
104
+ function isEqRow(v) {
105
+ if (!Array.isArray(v)) return false;
106
+ return v.every((n) => {
107
+ if (typeof n !== "object" || n === null) return false;
108
+ const t = n.t;
109
+ if (t === "chr") return typeof n.ch === "string";
110
+ if (t === "frac")
111
+ return isEqRow(n.num) && isEqRow(n.den);
112
+ if (t === "rad")
113
+ return isEqRow(n.deg) && isEqRow(n.body);
114
+ if (t === "scr")
115
+ return isEqRow(n.base) && isEqRow(n.sub) && isEqRow(n.sup);
116
+ if (t === "fence")
117
+ return typeof n.l === "string" && typeof n.r === "string" && isEqRow(n.body);
118
+ if (t === "big")
119
+ return typeof n.op === "string" && isEqRow(n.lo) && isEqRow(n.hi) && isEqRow(n.body);
120
+ if (t === "func")
121
+ return isEqRow(n.name) && isEqRow(n.body);
122
+ if (t === "acc")
123
+ return typeof n.chr === "string" && isEqRow(n.body);
124
+ if (t === "lim")
125
+ return typeof n.below === "boolean" && isEqRow(n.base) && isEqRow(n.lim);
126
+ if (t === "mat") {
127
+ const m = n;
128
+ return typeof m.cols === "number" && m.cols > 0 && Array.isArray(m.cells) && m.cells.every((c) => isEqRow(c));
129
+ }
130
+ return false;
131
+ });
132
+ }
133
+ var frac = () => ({ t: "frac", num: [], den: [] });
134
+ var scr = (slots) => ({
135
+ t: "scr",
136
+ base: [],
137
+ sub: [],
138
+ sup: [],
139
+ slots
140
+ });
141
+ var rad = (showDeg) => ({
142
+ t: "rad",
143
+ deg: [],
144
+ body: [],
145
+ showDeg
146
+ });
147
+ var fence = (l, r) => ({
148
+ t: "fence",
149
+ l,
150
+ r,
151
+ body: []
152
+ });
153
+ var upright = (s) => [...s].map((ch) => ({ t: "chr", ch }));
154
+ var func = (name) => ({
155
+ t: "func",
156
+ name: upright(name),
157
+ body: []
158
+ });
159
+ var acc = (chr) => ({ t: "acc", chr, body: [] });
160
+ var mat = (rows, cols) => ({
161
+ t: "mat",
162
+ cols,
163
+ cells: Array.from({ length: rows * cols }, () => [])
164
+ });
165
+ var braced = (l, r, rows, cols) => ({
166
+ t: "fence",
167
+ l,
168
+ r,
169
+ body: [mat(rows, cols)]
170
+ });
171
+ var lim = (name, below = true) => ({
172
+ t: "lim",
173
+ base: upright(name),
174
+ lim: [],
175
+ below
176
+ });
177
+ var big = (op, limits = false) => ({
178
+ t: "big",
179
+ op,
180
+ lo: [],
181
+ hi: [],
182
+ body: [],
183
+ showLo: limits,
184
+ showHi: limits
185
+ });
186
+ var EQ_STRUCTURES = [
187
+ { group: "Fraction", name: "Stacked", node: frac(), focus: ["num"] },
188
+ { group: "Script", name: "Superscript", node: scr("sup"), focus: ["sup"] },
189
+ { group: "Script", name: "Subscript", node: scr("sub"), focus: ["sub"] },
190
+ { group: "Script", name: "Sub and super", node: scr("both"), focus: ["sub"] },
191
+ { group: "Radical", name: "Square root", node: rad(false), focus: ["body"] },
192
+ { group: "Radical", name: "Nth root", node: rad(true), focus: ["deg"] },
193
+ { group: "Integral", name: "Integral", node: big("\u222B"), focus: ["body"] },
194
+ { group: "Integral", name: "Definite", node: big("\u222B", true), focus: ["lo"] },
195
+ { group: "Integral", name: "Double", node: big("\u222C"), focus: ["body"] },
196
+ { group: "Integral", name: "Contour", node: big("\u222E"), focus: ["body"] },
197
+ {
198
+ group: "Large operator",
199
+ name: "Summation",
200
+ node: big("\u2211"),
201
+ focus: ["body"]
202
+ },
203
+ {
204
+ group: "Large operator",
205
+ name: "Sum with limits",
206
+ node: big("\u2211", true),
207
+ focus: ["lo"]
208
+ },
209
+ { group: "Large operator", name: "Product", node: big("\u220F"), focus: ["body"] },
210
+ {
211
+ group: "Large operator",
212
+ name: "Product with limits",
213
+ node: big("\u220F", true),
214
+ focus: ["lo"]
215
+ },
216
+ { group: "Large operator", name: "Union", node: big("\u22C3"), focus: ["body"] },
217
+ {
218
+ group: "Large operator",
219
+ name: "Intersection",
220
+ node: big("\u22C2"),
221
+ focus: ["body"]
222
+ },
223
+ {
224
+ group: "Bracket",
225
+ name: "Parentheses",
226
+ node: fence("(", ")"),
227
+ focus: ["body"]
228
+ },
229
+ {
230
+ group: "Bracket",
231
+ name: "Brackets",
232
+ node: fence("[", "]"),
233
+ focus: ["body"]
234
+ },
235
+ { group: "Bracket", name: "Braces", node: fence("{", "}"), focus: ["body"] },
236
+ {
237
+ group: "Bracket",
238
+ name: "Absolute value",
239
+ node: fence("|", "|"),
240
+ focus: ["body"]
241
+ },
242
+ { group: "Bracket", name: "Norm", node: fence("\u2016", "\u2016"), focus: ["body"] },
243
+ { group: "Bracket", name: "Angle", node: fence("\u27E8", "\u27E9"), focus: ["body"] },
244
+ { group: "Bracket", name: "Floor", node: fence("\u230A", "\u230B"), focus: ["body"] },
245
+ { group: "Bracket", name: "Ceiling", node: fence("\u2308", "\u2309"), focus: ["body"] },
246
+ { group: "Function", name: "sin", node: func("sin"), focus: ["body"] },
247
+ { group: "Function", name: "cos", node: func("cos"), focus: ["body"] },
248
+ { group: "Function", name: "tan", node: func("tan"), focus: ["body"] },
249
+ { group: "Function", name: "cot", node: func("cot"), focus: ["body"] },
250
+ { group: "Function", name: "ln", node: func("ln"), focus: ["body"] },
251
+ { group: "Function", name: "log", node: func("log"), focus: ["body"] },
252
+ { group: "Function", name: "exp", node: func("exp"), focus: ["body"] },
253
+ // The name is empty and editable — for anything the list does not carry.
254
+ { group: "Function", name: "Custom", node: func(""), focus: ["name"] },
255
+ { group: "Accent", name: "Bar", node: acc("\u0305"), focus: ["body"] },
256
+ { group: "Accent", name: "Vector", node: acc("\u20D7"), focus: ["body"] },
257
+ { group: "Accent", name: "Hat", node: acc("\u0302"), focus: ["body"] },
258
+ { group: "Accent", name: "Tilde", node: acc("\u0303"), focus: ["body"] },
259
+ { group: "Accent", name: "Dot", node: acc("\u0307"), focus: ["body"] },
260
+ { group: "Accent", name: "Double dot", node: acc("\u0308"), focus: ["body"] },
261
+ { group: "Limit", name: "Limit", node: lim("lim"), focus: ["lim"] },
262
+ { group: "Limit", name: "Maximum", node: lim("max"), focus: ["lim"] },
263
+ { group: "Limit", name: "Minimum", node: lim("min"), focus: ["lim"] },
264
+ { group: "Limit", name: "Over", node: lim("", false), focus: ["base"] },
265
+ { group: "Matrix", name: "1\xD72", node: mat(1, 2), focus: ["c0"] },
266
+ { group: "Matrix", name: "2\xD71", node: mat(2, 1), focus: ["c0"] },
267
+ { group: "Matrix", name: "2\xD72", node: mat(2, 2), focus: ["c0"] },
268
+ { group: "Matrix", name: "3\xD73", node: mat(3, 3), focus: ["c0"] },
269
+ {
270
+ group: "Matrix",
271
+ name: "Bracketed 2\xD72",
272
+ node: braced("[", "]", 2, 2),
273
+ focus: ["body", 0, "c0"]
274
+ },
275
+ {
276
+ group: "Matrix",
277
+ name: "Determinant 2\xD72",
278
+ node: braced("|", "|", 2, 2),
279
+ focus: ["body", 0, "c0"]
280
+ },
281
+ // Two and three equations under one brace — how a system is set in
282
+ // Vietnamese exam papers, and awkward to build out of the pieces.
283
+ {
284
+ group: "Matrix",
285
+ name: "System of 2",
286
+ node: braced("{", "", 2, 1),
287
+ focus: ["body", 0, "c0"]
288
+ },
289
+ {
290
+ group: "Matrix",
291
+ name: "System of 3",
292
+ node: braced("{", "", 3, 1),
293
+ focus: ["body", 0, "c0"]
294
+ }
295
+ ];
296
+
297
+ // packages/model/src/lib/model.ts
42
298
  function dataJson(el, name) {
43
299
  const raw = el.getAttribute(name);
44
300
  if (!raw) return null;
@@ -260,6 +516,42 @@ var schema = new Schema({
260
516
  parseDOM: [{ tag: "br.column-break" }],
261
517
  toDOM: () => ["br", { class: "column-break" }]
262
518
  },
519
+ // An equation with 2D structure — the AST (EqNode[], contracts) rides
520
+ // `ast`. An inline ATOM: the layout engine typesets it into a vector box
521
+ // sized by `sizePt`; editing goes through the equation plugin's slot
522
+ // editor, never the hidden text editor. toDOM carries the linear
523
+ // spelling as text, so the a11y mirror and copy read the math.
524
+ equation: {
525
+ inline: true,
526
+ group: "inline",
527
+ atom: true,
528
+ attrs: { ast: {}, sizePt: { default: 12 } },
529
+ parseDOM: [
530
+ {
531
+ tag: "span[data-eq]",
532
+ getAttrs: (el) => {
533
+ const ast = dataJson(el, "data-eq");
534
+ if (!isEqRow(ast)) return false;
535
+ const size = Number(el.getAttribute("data-eq-size"));
536
+ return {
537
+ ast,
538
+ sizePt: Number.isFinite(size) && size > 0 ? size : 12
539
+ };
540
+ }
541
+ }
542
+ ],
543
+ toDOM(node) {
544
+ const ast = node.attrs["ast"];
545
+ return [
546
+ "span",
547
+ {
548
+ "data-eq": JSON.stringify(ast),
549
+ "data-eq-size": String(node.attrs["sizePt"])
550
+ },
551
+ astToLinear(isEqRow(ast) ? ast : [])
552
+ ];
553
+ }
554
+ },
263
555
  // Inline image. `src` is typically a data URL (the importer inlines the
264
556
  // embedded media); width/height are CSS pixels, null if unspecified.
265
557
  image: {
@@ -283,6 +575,27 @@ var schema = new Schema({
283
575
  // { kind: 'rect'|'line', stroke?, strokeWidth?, fill?, flipV? } — src
284
576
  // is then '' and the box paints as vector. Null for real bitmaps.
285
577
  shape: { default: null },
578
+ // Replayed metafile (VectorImageSpec) — how MathType/OLE equation
579
+ // previews (vector WMF) paint. `src` keeps the original metafile
580
+ // data URL for export; the painter draws the ops instead. Null for
581
+ // bitmaps and preset shapes.
582
+ vector: { default: null },
583
+ // The equation's LINEAR text recovered from the metafile's embedded
584
+ // MTEF (MathType semantics) — what "Convert to editable equation"
585
+ // replaces the picture with. Null when the picture carries none.
586
+ equation: { default: null },
587
+ // The same equation as an AST (EqNode[]) — what conversion turns
588
+ // into a real 2D equation node. Null when undecodable.
589
+ equationAst: { default: null },
590
+ // `o:OLEObject/@ProgID` when this box is an EMBEDDED OBJECT rather
591
+ // than a picture ("Equation.DSMT4" for MathType). The bitmap is only
592
+ // the object's preview, so the editor offers object tools, not the
593
+ // picture toolkit. Null for real pictures.
594
+ oleProgId: { default: null },
595
+ // w:position on the run holding a legacy object — baseline shift in
596
+ // px, positive UP (same convention as InlineRun.raise). MathType
597
+ // lowers every equation so its baseline meets the text line's.
598
+ raise: { default: 0 },
286
599
  // Textbox (wps:txbx) content riding a shape: { paragraphs: <paragraph
287
600
  // node JSON>[], inset?: {l,t,r,b} px }. The layout engine flows the
288
601
  // paragraphs inside the shape's box (paint-only, not editable v1).
@@ -647,6 +960,15 @@ var schema = new Schema({
647
960
  0
648
961
  ]
649
962
  },
963
+ // m:oMath — the run belongs to an equation. Linear v1: the letterforms
964
+ // live in the TEXT as Unicode math alphanumerics (𝑥, 𝒫, ℝ — see
965
+ // math.ts), so the mark is semantic, not visual: it scopes math
966
+ // autocorrect, keeps the region addressable for the coming in-place
967
+ // editor, and tells the exporter to wrap these runs back into m:oMath.
968
+ math: {
969
+ parseDOM: [{ tag: "span[data-math]" }],
970
+ toDOM: () => ["span", { "data-math": "1" }, 0]
971
+ },
650
972
  // w:highlight / w:shd w:fill — run background color ("#RRGGBB")
651
973
  highlight: {
652
974
  attrs: { color: {} },
@@ -8,7 +8,7 @@ import { Schema } from 'prosemirror-model';
8
8
  * extend THIS schema so the importer, layout engine, and (canvas) painter all
9
9
  * agree on one document model.
10
10
  */
11
- export declare const schema: Schema<"paragraph" | "doc" | "text" | "hard_break" | "column_break" | "image" | "page_field" | "table" | "table_row" | "table_cell", "strong" | "em" | "underline" | "strike" | "dstrike" | "smallCaps" | "textColor" | "fontSize" | "vertAlign" | "letterSpacing" | "kern" | "charScale" | "position" | "highlight" | "fontFamily" | "link" | "footnote" | "carryRPr">;
11
+ export declare const schema: Schema<"paragraph" | "text" | "table" | "doc" | "hard_break" | "column_break" | "equation" | "image" | "page_field" | "table_row" | "table_cell", "letterSpacing" | "link" | "strike" | "underline" | "strong" | "em" | "dstrike" | "smallCaps" | "textColor" | "fontSize" | "vertAlign" | "kern" | "charScale" | "position" | "math" | "highlight" | "fontFamily" | "footnote" | "carryRPr">;
12
12
  /** Concrete schema type, handy for typing Node/Mark across packages. */
13
13
  export type BapbongSchema = typeof schema;
14
14
  /**
@@ -17,7 +17,7 @@ export type BapbongSchema = typeof schema;
17
17
  * (@user). Comment bodies are stored as this schema's JSON on the comment
18
18
  * thread, kept separate from the document schema.
19
19
  */
20
- export declare const commentSchema: Schema<"paragraph" | "doc" | "text" | "mention", never>;
20
+ export declare const commentSchema: Schema<"paragraph" | "text" | "doc" | "mention", never>;
21
21
  export type CommentSchema = typeof commentSchema;
22
22
  /** Paragraph horizontal alignment (mirrors w:jc). */
23
23
  export type Align = 'left' | 'center' | 'right' | 'justify';
@@ -1 +1 @@
1
- {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/lib/model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AA0F3C;;;;;;;;GAQG;AACH,eAAO,MAAM,MAAM,yWAwqBjB,CAAC;AAEH,wEAAwE;AACxE,MAAM,MAAM,aAAa,GAAG,OAAO,MAAM,CAAC;AAE1C;;;;;GAKG;AACH,eAAO,MAAM,aAAa,yDA0CxB,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;IACxC;;;6EAGyE;IACzE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;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;IACxB,OAAO,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAAC;CACvC;AA0BD;;;4EAG4E;AAC5E,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/lib/model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AA2F3C;;;;;;;;GAQG;AACH,eAAO,MAAM,MAAM,+XA2uBjB,CAAC;AAEH,wEAAwE;AACxE,MAAM,MAAM,aAAa,GAAG,OAAO,MAAM,CAAC;AAE1C;;;;;GAKG;AACH,eAAO,MAAM,aAAa,yDA0CxB,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;IACxC;;;6EAGyE;IACzE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;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;IACxB,OAAO,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAAC;CACvC;AA0BD;;;4EAG4E;AAC5E,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shadow-garden/bapbong-model",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "bapbong — ProseMirror document model / schema",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -54,6 +54,7 @@
54
54
  }
55
55
  },
56
56
  "dependencies": {
57
- "prosemirror-model": "^1.25.7"
57
+ "prosemirror-model": "^1.25.7",
58
+ "@shadow-garden/bapbong-contracts": "^0.15.0"
58
59
  }
59
60
  }