@portone/docx-editor 0.2.0 → 0.2.1

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 (101) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/CONTRIBUTING.md +4 -0
  3. package/dist/docx/commentOnlyChange.d.ts +3 -1
  4. package/dist/docx/commentOnlyChange.js +38 -21
  5. package/dist/docx/comments/constants.d.ts +3 -3
  6. package/dist/docx/comments/constants.js +4 -3
  7. package/dist/docx/comments/grammar.d.ts +80 -0
  8. package/dist/docx/comments/grammar.js +213 -0
  9. package/dist/docx/comments/people.js +12 -14
  10. package/dist/docx/comments/reading.d.ts +5 -3
  11. package/dist/docx/comments/reading.js +23 -20
  12. package/dist/docx/comments/verifying.d.ts +44 -0
  13. package/dist/docx/comments/verifying.js +206 -0
  14. package/dist/docx/comments/writing.js +73 -46
  15. package/dist/docx/exportDocx.js +2 -1
  16. package/dist/docx/formatting/direct.js +3 -6
  17. package/dist/docx/headersFooters.js +8 -10
  18. package/dist/docx/notes.js +3 -5
  19. package/dist/docx/paraProps.js +51 -46
  20. package/dist/docx/propsXml.d.ts +20 -2
  21. package/dist/docx/propsXml.js +72 -32
  22. package/dist/docx/relationships.js +8 -3
  23. package/dist/docx/runProps.js +34 -34
  24. package/dist/docx/sdt.js +16 -14
  25. package/dist/docx/serializeBlock.js +1 -2
  26. package/dist/docx/serializeParagraph.d.ts +2 -1
  27. package/dist/docx/serializeParagraph.js +25 -14
  28. package/dist/docx/serializeTable.js +38 -24
  29. package/dist/docx/storyProjection.d.ts +36 -0
  30. package/dist/docx/storyProjection.js +23 -0
  31. package/dist/docx/tableFormatting/editing.js +49 -73
  32. package/dist/docx/tableTemplate.js +24 -3
  33. package/dist/docx/theme.d.ts +0 -6
  34. package/dist/docx/theme.js +0 -8
  35. package/dist/editor/clipboard/images.js +17 -3
  36. package/dist/editor/clipboard/inlineFormatting.d.ts +7 -0
  37. package/dist/editor/clipboard/inlineFormatting.js +1 -0
  38. package/dist/editor/commands/breakCommands.js +4 -3
  39. package/dist/editor/commands/canRunCommand.d.ts +2 -2
  40. package/dist/editor/commands/canRunCommand.js +1 -1
  41. package/dist/editor/commands/comments/editing.js +3 -6
  42. package/dist/editor/commands/historyCommands.js +2 -1
  43. package/dist/editor/commands/lockCommands.js +10 -1
  44. package/dist/editor/commands/tabCommands.js +4 -3
  45. package/dist/editor/createEditor.js +3 -9
  46. package/dist/editor/externalClipboard.js +166 -4
  47. package/dist/editor/insertImage.js +4 -3
  48. package/dist/editor/plugins/documentProtection.d.ts +3 -3
  49. package/dist/editor/plugins/lockedContent.d.ts +4 -4
  50. package/dist/editor/plugins/lockedContent.js +1 -1
  51. package/dist/numbering/listTemplate.js +32 -10
  52. package/dist/ooxml/element.d.ts +52 -0
  53. package/dist/ooxml/element.js +49 -0
  54. package/dist/ooxml/fragment.d.ts +53 -0
  55. package/dist/ooxml/fragment.js +76 -0
  56. package/dist/ooxml/image.js +4 -3
  57. package/dist/ooxml/names.d.ts +27 -0
  58. package/dist/ooxml/names.js +29 -0
  59. package/dist/ooxml/precedence.d.ts +31 -0
  60. package/dist/ooxml/precedence.js +51 -0
  61. package/dist/ooxml/xml.d.ts +31 -0
  62. package/dist/ooxml/xml.js +27 -0
  63. package/dist/page/blockKinds.d.ts +42 -0
  64. package/dist/page/blockKinds.js +0 -0
  65. package/dist/page/measureBlocks.d.ts +14 -1
  66. package/dist/page/measureBlocks.js +31 -11
  67. package/dist/page/pageDecorations.d.ts +16 -0
  68. package/dist/page/pageDecorations.js +1 -0
  69. package/dist/page/pageLayout.d.ts +8 -43
  70. package/dist/page/pageLayout.js +28 -51
  71. package/dist/page/tableMeasurements.d.ts +10 -2
  72. package/dist/page/tableMeasurements.js +20 -16
  73. package/dist/page/usePageLayout.d.ts +15 -0
  74. package/dist/page/usePageLayout.js +30 -2
  75. package/dist/schema/attrRoles.d.ts +35 -0
  76. package/dist/schema/attrRoles.js +106 -0
  77. package/dist/schema/docxSchema.d.ts +6 -0
  78. package/dist/schema/docxSchema.js +212 -101
  79. package/dist/schema/editGuard.d.ts +99 -0
  80. package/dist/schema/editGuard.js +43 -0
  81. package/dist/schema/guards.d.ts +68 -0
  82. package/dist/schema/guards.js +82 -0
  83. package/dist/schema/locks.d.ts +8 -35
  84. package/dist/schema/locks.js +29 -64
  85. package/dist/schema/preservedGuards.d.ts +30 -0
  86. package/dist/schema/preservedGuards.js +50 -0
  87. package/dist/schema/protection.d.ts +10 -3
  88. package/dist/schema/protection.js +2 -1
  89. package/dist/schema/sourceEquality.d.ts +27 -0
  90. package/dist/schema/sourceEquality.js +65 -0
  91. package/dist/table/cellFormatting.js +2 -1
  92. package/dist/table/commands.d.ts +2 -2
  93. package/dist/table/commands.js +1 -1
  94. package/dist/table/index.d.ts +2 -2
  95. package/dist/table/merge.d.ts +2 -2
  96. package/dist/table/merge.js +1 -1
  97. package/package.json +4 -1
  98. package/dist/editor/plugins/bookmarkProtection.d.ts +0 -3
  99. package/dist/editor/plugins/bookmarkProtection.js +0 -28
  100. package/dist/editor/plugins/noteProtection.d.ts +0 -3
  101. package/dist/editor/plugins/noteProtection.js +0 -25
@@ -10,6 +10,8 @@
10
10
  * document breaks at Letter's height and fits its tables to Letter's width.
11
11
  */
12
12
  import { type PageGeometry } from "../docx/pageGeometry";
13
+ import type { MeasuredBlock, PageCut } from "./blockKinds";
14
+ export type { MeasuredBlock };
13
15
  /** The paper measured in the pixels the sheet is drawn with */
14
16
  export interface PagePixels {
15
17
  /** The whole sheet, padding included */
@@ -45,44 +47,6 @@ export declare const A4_PAGE_PIXELS: PagePixels;
45
47
  * moment before a document is opened.
46
48
  */
47
49
  export declare function pageGeometryStyle(page: PagePixels): string;
48
- /** One body block as drawn on screen */
49
- export interface MeasuredBlock {
50
- /** The position where this block starts in the document */
51
- pos: number;
52
- /** The height that naturally opens up between the previous block and this one */
53
- gap: number;
54
- height: number;
55
- /** Set when the document records that a new page starts at this block */
56
- breakBefore: boolean;
57
- /**
58
- * Every page break inside the block, in document order, each measured from the block's own top
59
- * so that the block can be laid out wherever it lands.
60
- * That top is the one the block would be drawn at with no space in it, so a space opened at one
61
- * break shifts the ones after it.
62
- */
63
- breaks: readonly number[];
64
- /** Row boundaries available when this block is an editable table */
65
- table?: MeasuredTable;
66
- }
67
- /** A row that may start the continued part of a table */
68
- export interface TableBoundary {
69
- /** The row's document position */
70
- pos: number;
71
- /** Its top measured from the table's natural top */
72
- offset: number;
73
- }
74
- /** The measurements needed to continue a table without changing the document */
75
- export interface MeasuredTable {
76
- boundaries: readonly TableBoundary[];
77
- /** The smallest useful first piece: headers followed by one body row group */
78
- firstPageMinimum: number;
79
- repeatHeaderHeight: number;
80
- /** Document positions of the contiguous header rows at the start of the table */
81
- headerRows: readonly number[];
82
- /** Changes when any projected header content or formatting changes */
83
- headerSignature: string;
84
- columns: number;
85
- }
86
50
  /** The space opened up at one page break, so that what follows it starts the next page */
87
51
  export interface BreakSpace {
88
52
  /** The position where the block holding the break starts */
@@ -148,8 +112,8 @@ export interface PageStart {
148
112
  export interface PageLayout {
149
113
  /** Only the blocks to be moved down to the next page */
150
114
  pushes: BlockPush[];
151
- spaces: BreakSpace[];
152
- tableContinuations: TableContinuation[];
115
+ /** Where the layout parted a block, and the space it opened before the continued piece */
116
+ cuts: PageCut[];
153
117
  splits: PageSplit[];
154
118
  /** One per page, the first page included */
155
119
  pages: PageStart[];
@@ -169,8 +133,9 @@ export interface PageLayoutInput {
169
133
  * blocks below it move down by the same amount.
170
134
  * A block taller than one page cannot be pushed, so it is left where it is and only the
171
135
  * places it crosses are reported.
172
- * A page break inside a block is filled out to the end of the page it falls on, which carries
173
- * the rest of that block - the rest of the sentence, the rest of the list item - to the top of
174
- * the next page while the block itself stays whole.
136
+ * A block is parted only where its measurer offered a candidate. A forced one - a page break
137
+ * written into the text - is filled out to the end of the page it falls on, which carries the rest
138
+ * of that block to the top of the next page while the block itself stays whole. An optional one -
139
+ * a row boundary in a table - is taken only where the piece after it would run off the page.
175
140
  */
176
141
  export declare function pageLayout({ blocks, pageBodyHeight, pageStep, }: PageLayoutInput): PageLayout;
@@ -47,8 +47,7 @@ function pageLayout({
47
47
  pageStep
48
48
  }) {
49
49
  const pushes = [];
50
- const spaces = [];
51
- const tableContinuations = [];
50
+ const cuts = [];
52
51
  const splits = [];
53
52
  const firstPage = {
54
53
  page: 1,
@@ -58,14 +57,7 @@ function pageLayout({
58
57
  };
59
58
  const pages = [firstPage];
60
59
  if (!(pageBodyHeight > 0)) {
61
- return {
62
- pushes,
63
- spaces,
64
- tableContinuations,
65
- splits,
66
- pages,
67
- bodyHeight: 0
68
- };
60
+ return { pushes, cuts, splits, pages, bodyHeight: 0 };
69
61
  }
70
62
  let pageStart = 0;
71
63
  let cursor = 0;
@@ -82,11 +74,12 @@ function pageLayout({
82
74
  split(pageStart + pageBodyHeight, true, false);
83
75
  }
84
76
  };
77
+ let breakAfterPrevious = false;
85
78
  for (const block of blocks) {
86
79
  const pageEnd = pageStart + pageBodyHeight;
87
80
  const top = cursor + block.gap;
88
- const startsPage = block.breakBefore && top > pageStart + TOLERANCE_PX;
89
- const first = block.breaks[0] ?? block.table?.firstPageMinimum ?? block.height;
81
+ const startsPage = (block.breakBefore || breakAfterPrevious) && top > pageStart + TOLERANCE_PX;
82
+ const first = block.minFirstPiece;
90
83
  const overflows = top + first > pageEnd + TOLERANCE_PX;
91
84
  const fits = first <= pageBodyHeight + TOLERANCE_PX;
92
85
  const push = startsPage || overflows && fits ? Math.max(0, pageEnd + pageStep - top) : 0;
@@ -98,51 +91,35 @@ function pageLayout({
98
91
  push: round(push)
99
92
  });
100
93
  }
101
- let contentTop = top + push;
102
- for (const [index, offset] of block.breaks.entries()) {
103
- const breakY = contentTop + offset;
104
- crossTo(breakY);
105
- split(pageStart + pageBodyHeight, false, true);
106
- const height = Math.max(0, pageStart - breakY);
107
- spaces.push({ pos: block.pos, index, height: round(height) });
108
- contentTop += height;
109
- }
110
- let tableAdded = 0;
111
- if (block.table) {
112
- let segmentStart = 0;
113
- for (let index = 0; index <= block.table.boundaries.length; index += 1) {
114
- const segmentEnd = block.table.boundaries[index]?.offset ?? block.height;
115
- const segmentTop = contentTop + segmentStart + tableAdded;
116
- let segmentBottom = contentTop + segmentEnd + tableAdded;
117
- const pageEnd2 = pageStart + pageBodyHeight;
118
- const overflows2 = segmentBottom > pageEnd2 + TOLERANCE_PX;
119
- const boundary = index === 0 ? null : block.table.boundaries[index - 1];
120
- const segmentHeight = segmentEnd - segmentStart;
121
- if (overflows2 && boundary && segmentTop > pageStart + TOLERANCE_PX && segmentHeight <= pageBodyHeight + TOLERANCE_PX) {
122
- split(pageEnd2, false, false);
123
- const height = Math.max(0, pageStart - segmentTop);
124
- tableContinuations.push({
125
- pos: boundary.pos,
126
- height: round(height),
127
- headerRows: block.table.headerRows,
128
- headerSignature: block.table.headerSignature,
129
- columns: block.table.columns
130
- });
131
- tableAdded += height + block.table.repeatHeaderHeight;
132
- segmentBottom = contentTop + segmentEnd + tableAdded;
94
+ const contentTop = top + push;
95
+ let added = 0;
96
+ let pieceStart = 0;
97
+ for (let index = 0; index <= block.candidates.length; index += 1) {
98
+ const cut = index === 0 ? null : block.candidates[index - 1];
99
+ const pieceEnd = block.candidates[index]?.offset ?? block.height;
100
+ const pieceTop = contentTop + pieceStart + added;
101
+ let pieceBottom = contentTop + pieceEnd + added;
102
+ if (cut) {
103
+ if (cut.forced) crossTo(pieceTop);
104
+ const pageBottom = pageStart + pageBodyHeight;
105
+ const cutHere = cut.forced || pieceBottom > pageBottom + TOLERANCE_PX && pieceTop > pageStart + TOLERANCE_PX && pieceEnd - pieceStart <= pageBodyHeight + TOLERANCE_PX;
106
+ if (cutHere) {
107
+ split(pageBottom, false, cut.forced);
108
+ const height = Math.max(0, pageStart - pieceTop);
109
+ cuts.push({ at: cut.at, height: round(height) });
110
+ added += height + cut.repeatHeight;
111
+ pieceBottom += height + cut.repeatHeight;
133
112
  }
134
- crossTo(segmentBottom);
135
- segmentStart = segmentEnd;
136
113
  }
114
+ crossTo(pieceBottom);
115
+ pieceStart = pieceEnd;
137
116
  }
138
- const bottom = contentTop + block.height + tableAdded;
139
- crossTo(bottom);
140
- cursor = bottom;
117
+ cursor = contentTop + block.height + added;
118
+ breakAfterPrevious = block.breakAfter;
141
119
  }
142
120
  return {
143
121
  pushes,
144
- spaces,
145
- tableContinuations,
122
+ cuts,
146
123
  splits,
147
124
  pages,
148
125
  bodyHeight: round(pageStart + pageBodyHeight)
@@ -1,10 +1,18 @@
1
1
  /** Measures safe row boundaries without treating pagination decorations as document content. */
2
2
  import type { Node as PMNode } from "prosemirror-model";
3
3
  import type { EditorView } from "prosemirror-view";
4
- import type { MeasuredTable } from "./pageLayout";
4
+ import type { BreakCandidate } from "./blockKinds";
5
5
  export interface TableMeasure {
6
- table: MeasuredTable;
6
+ /** Every row that may start the continued part of the table */
7
+ candidates: readonly BreakCandidate[];
8
+ /** The smallest useful first piece: headers followed by one body row group */
9
+ minFirstPiece: number;
7
10
  /** Height already added by the previous pagination pass */
8
11
  appliedHeight: number;
12
+ /** Document positions of the contiguous header rows at the start of the table */
13
+ headerRows: readonly number[];
14
+ /** Changes when any projected header content or formatting changes */
15
+ headerSignature: string;
16
+ columns: number;
9
17
  }
10
18
  export declare function measureTable(view: EditorView, tableNode: PMNode, tablePos: number, tableDom: HTMLElement, scale?: number): TableMeasure | null;
@@ -81,33 +81,37 @@ function measureTable(view, tableNode, tablePos, tableDom, scale = 1) {
81
81
  if (rows.length === 0) return null;
82
82
  const headers = headerCount(rows);
83
83
  const unsafe = unsafeBoundaries(rows);
84
- const boundaries = [];
84
+ const headerRows = rows.slice(0, headers);
85
+ const repeatHeaderHeight = headerRows.reduce(
86
+ (total, row) => total + row.height,
87
+ 0
88
+ );
89
+ const candidates = [];
85
90
  for (let rowIndex = Math.max(1, headers); rowIndex < rows.length; rowIndex += 1) {
86
91
  const row = rows[rowIndex];
87
92
  if (row && !unsafe.has(rowIndex)) {
88
- boundaries.push({ pos: row.pos, offset: row.top });
93
+ candidates.push({
94
+ at: row.pos,
95
+ offset: row.top,
96
+ forced: false,
97
+ repeatHeight: repeatHeaderHeight
98
+ });
89
99
  }
90
100
  }
91
- const repeatHeaderHeight = rows.slice(0, headers).reduce((total, row) => total + row.height, 0);
92
- const firstBodyBoundary = boundaries.find(
93
- (boundary) => boundary.offset > repeatHeaderHeight + 0.5
101
+ const firstBodyBoundary = candidates.find(
102
+ (candidate) => candidate.offset > repeatHeaderHeight + 0.5
94
103
  );
95
104
  const appliedHeight = appliedRows.reduce(
96
105
  (total, row) => total + row.getBoundingClientRect().height / scale,
97
106
  0
98
107
  );
99
108
  return {
100
- table: {
101
- boundaries,
102
- firstPageMinimum: firstBodyBoundary?.offset ?? tableDom.getBoundingClientRect().height / scale - appliedHeight,
103
- repeatHeaderHeight,
104
- headerRows: rows.slice(0, headers).map((row) => row.pos),
105
- headerSignature: JSON.stringify(
106
- rows.slice(0, headers).map((row) => row.node.toJSON())
107
- ),
108
- columns: columnCount(tableNode)
109
- },
110
- appliedHeight
109
+ candidates,
110
+ minFirstPiece: firstBodyBoundary?.offset ?? tableDom.getBoundingClientRect().height / scale - appliedHeight,
111
+ appliedHeight,
112
+ headerRows: headerRows.map((row) => row.pos),
113
+ headerSignature: JSON.stringify(headerRows.map((row) => row.node.toJSON())),
114
+ columns: columnCount(tableNode)
111
115
  };
112
116
  }
113
117
  export {
@@ -8,9 +8,13 @@
8
8
  * in Japanese or Chinese stays open across a whole clause while the text grows line by line. So no
9
9
  * measurement is taken while a composition is open: the frame is taken again until it is over.
10
10
  */
11
+ import type { Node as PMNode } from "prosemirror-model";
11
12
  import type { EditorView } from "prosemirror-view";
12
13
  import { type RefObject } from "react";
13
14
  import type { PageGeometry } from "../docx/pageGeometry";
15
+ import type { PageCut } from "./blockKinds";
16
+ import { type TableHeaderProjection } from "./measureBlocks";
17
+ import { type BreakSpace, type TableContinuation } from "./pageLayout";
14
18
  /** One place where a page parts from the next. The position is measured on the sheet */
15
19
  export interface PageMark {
16
20
  page: number;
@@ -53,5 +57,16 @@ interface PageLayoutOptions {
53
57
  /** The paper the open document names. A4 where a document names none */
54
58
  geometry?: PageGeometry;
55
59
  }
60
+ /**
61
+ * The layout's cuts as the two kinds of mark the decorations still take.
62
+ *
63
+ * A cut names the position the continued piece starts at, and which of the two it is follows from
64
+ * what stands there: a page `br` the block it sits in counted, or a table row. This stands in
65
+ * until the decorations take the cuts themselves.
66
+ */
67
+ export declare function cutsToLegacyMarks(cuts: readonly PageCut[], tables: ReadonlyMap<number, TableHeaderProjection>, doc: PMNode): {
68
+ spaces: BreakSpace[];
69
+ tableContinuations: TableContinuation[];
70
+ };
56
71
  export declare function usePageLayout({ view, layer, enabled, revision, geometry, }: PageLayoutOptions): PageOverlay | null;
57
72
  export {};
@@ -9,6 +9,7 @@ import {
9
9
  import { editorCssVariables } from "../styles/classNames.js";
10
10
  import { measureSheet } from "./measureBlocks.js";
11
11
  import {
12
+ pageBreaksIn,
12
13
  setPageBreakSpaces,
13
14
  setPagePushes,
14
15
  setTableContinuations
@@ -52,6 +53,27 @@ function useFrameThrottle(run, hold) {
52
53
  function sameOverlay(a, b) {
53
54
  return a !== null && a.left === b.left && a.top === b.top && a.width === b.width && a.sheetHeight === b.sheetHeight && JSON.stringify(a.marks) === JSON.stringify(b.marks) && JSON.stringify(a.badges) === JSON.stringify(b.badges) && JSON.stringify(a.pages) === JSON.stringify(b.pages);
54
55
  }
56
+ function cutsToLegacyMarks(cuts, tables, doc) {
57
+ const spaces = [];
58
+ const tableContinuations = [];
59
+ for (const cut of cuts) {
60
+ const pos = doc.resolve(cut.at).before(1);
61
+ const block = doc.nodeAt(pos);
62
+ if (!block) continue;
63
+ const index = pageBreaksIn(block, pos).findIndex(
64
+ (found) => found.at === cut.at
65
+ );
66
+ if (index >= 0) {
67
+ spaces.push({ pos, index, height: cut.height });
68
+ continue;
69
+ }
70
+ const headers = tables.get(pos);
71
+ if (headers) {
72
+ tableContinuations.push({ pos: cut.at, height: cut.height, ...headers });
73
+ }
74
+ }
75
+ return { spaces, tableContinuations };
76
+ }
55
77
  function usePageLayout({
56
78
  view,
57
79
  layer,
@@ -74,9 +96,14 @@ function usePageLayout({
74
96
  pageBodyHeight: page.bodyHeight,
75
97
  pageStep: page.pageStep
76
98
  });
99
+ const marks = cutsToLegacyMarks(
100
+ layout.cuts,
101
+ measured.tables,
102
+ view.state.doc
103
+ );
77
104
  setPagePushes(view, layout.pushes);
78
- setPageBreakSpaces(view, layout.spaces);
79
- setTableContinuations(view, layout.tableContinuations);
105
+ setPageBreakSpaces(view, marks.spaces);
106
+ setTableContinuations(view, marks.tableContinuations);
80
107
  const sheetHeight = measured.contentTop + layout.bodyHeight + measured.contentBottom;
81
108
  box.style.setProperty(editorCssVariables.sheetHeight, `${sheetHeight}px`);
82
109
  const next = {
@@ -137,5 +164,6 @@ function usePageLayout({
137
164
  return enabled ? overlay : null;
138
165
  }
139
166
  export {
167
+ cutsToLegacyMarks,
140
168
  usePageLayout
141
169
  };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * What each attr of the schema is for, so that a judgement about a node can say which of them it
3
+ * is judging.
4
+ *
5
+ * An attr is one of three things. A `source` attr is what the exporter writes from: the XML the
6
+ * file arrived as, or a value the model owns that replaces part of it. A `display` attr is worked
7
+ * out from the source and the formatting around it, is never written, and is free to change the
8
+ * moment the surroundings do. A `session` attr says which block, control or link of the open
9
+ * document this is, and is never written either.
10
+ *
11
+ * The split exists because a display value changing is not the document changing. Opening a file
12
+ * in the editor re-derives a table's cell borders (`table/gridBorders`), and a comparison that
13
+ * counted that as an edit would rewrite a table nobody touched, losing the markup the writer does
14
+ * not model. `./sourceEquality` leaves display attrs out for that reason, and keeps session attrs
15
+ * in: two blocks that came from different places in the file are different blocks.
16
+ *
17
+ * `attrRoles.test.ts` holds this table against the schema in both directions, so an attr added
18
+ * without a role fails there before it can reach a judgement that does not know what to do with it.
19
+ */
20
+ import { type MarkType, NodeType } from "prosemirror-model";
21
+ export type AttrRole = "source" | "display" | "session";
22
+ type AttrRoles = Readonly<Record<string, Readonly<Record<string, AttrRole>>>>;
23
+ export declare const NODE_ATTR_ROLES: AttrRoles;
24
+ export declare const MARK_ATTR_ROLES: AttrRoles;
25
+ /**
26
+ * What this attr of this node or mark is for.
27
+ *
28
+ * An attr with no role declared is read as `source`: a judgement then compares it, which is the
29
+ * answer that reports a change rather than hiding one. `attrRoles.test.ts` is what keeps the case
30
+ * from arising.
31
+ */
32
+ export declare function attrRole(type: NodeType | MarkType, name: string): AttrRole;
33
+ /** The attrs of this node or mark that are worked out rather than written */
34
+ export declare function displayAttrsOf(type: NodeType | MarkType): readonly string[];
35
+ export {};
@@ -0,0 +1,106 @@
1
+ // src/schema/attrRoles.ts
2
+ import { NodeType } from "prosemirror-model";
3
+ var NODE_ATTR_ROLES = {
4
+ paragraph: {
5
+ srcId: "session",
6
+ pAttrs: "source",
7
+ pPr: "source",
8
+ format: "display",
9
+ styleRun: "display"
10
+ },
11
+ table: {
12
+ srcId: "session",
13
+ tblAttrs: "source",
14
+ tblPr: "source",
15
+ tblW: "source",
16
+ gridCols: "source",
17
+ format: "display",
18
+ styleInside: "display",
19
+ styleCellMargins: "display"
20
+ },
21
+ tableRow: {
22
+ trAttrs: "source",
23
+ tblPrEx: "source",
24
+ trPr: "source",
25
+ format: "display"
26
+ },
27
+ tableCell: {
28
+ colspan: "source",
29
+ rowspan: "source",
30
+ // prosemirror-tables' own attr. Import writes null, the writer never reads it, and the table
31
+ // commands only carry it from one cell to another; a real column resize moves `gridCols`
32
+ colwidth: "display",
33
+ tcAttrs: "source",
34
+ tcPr: "source",
35
+ tcW: "source",
36
+ format: "display",
37
+ // Read from `sdtPrefix` rather than from the file, but a lock is not a display value: leaving
38
+ // it out of the comparison would let a step that unlocks a cell pass as a re-derivation
39
+ sdtPrefix: "source",
40
+ sdtContentsLocked: "source",
41
+ sdtDeletionLocked: "source"
42
+ },
43
+ rawBlock: { xml: "source", name: "source" },
44
+ docxRaw: { srcId: "session", name: "session" },
45
+ bookmarkBlock: { srcId: "session", name: "session" },
46
+ hardBreak: { brAttrs: "source" },
47
+ image: { src: "source", extent: "source", alt: "source", xml: "source" },
48
+ commentStart: { id: "source", xml: "source" },
49
+ commentEnd: { id: "source", xml: "source" },
50
+ commentReference: {
51
+ id: "source",
52
+ referenceXml: "source",
53
+ author: "source",
54
+ authorId: "source",
55
+ initials: "source",
56
+ date: "source",
57
+ text: "source",
58
+ commentXml: "source",
59
+ paraId: "source",
60
+ resolved: "source",
61
+ extensionXml: "source",
62
+ replies: "source",
63
+ // Read to decide whether the comment parts are rewritten at all; neither is written
64
+ imported: "session",
65
+ threadImported: "session"
66
+ },
67
+ noteReference: {
68
+ kind: "source",
69
+ id: "source",
70
+ customMarkFollows: "source",
71
+ referenceXml: "source",
72
+ // Both come from the notes part as the document was opened, and the writer puts back the
73
+ // reference alone: a note renumbered around an edit is the same reference it was
74
+ label: "display",
75
+ text: "display"
76
+ },
77
+ rawInline: { xml: "source" }
78
+ };
79
+ var MARK_ATTR_ROLES = {
80
+ run: { rPr: "source", rAttrs: "source", format: "display" },
81
+ sdt: {
82
+ sdtPrefix: "source",
83
+ // Counted through the document as it was opened, to tell one control from the next
84
+ sdtKey: "session",
85
+ contentsLocked: "source",
86
+ deletionLocked: "source"
87
+ },
88
+ link: { linkPrefix: "source", href: "source", linkKey: "session" },
89
+ tab: { tabAttrs: "source" }
90
+ };
91
+ function rolesOf(type) {
92
+ const roles = type instanceof NodeType ? NODE_ATTR_ROLES[type.name] : MARK_ATTR_ROLES[type.name];
93
+ return roles ?? {};
94
+ }
95
+ function attrRole(type, name) {
96
+ return rolesOf(type)[name] ?? "source";
97
+ }
98
+ function displayAttrsOf(type) {
99
+ return Object.entries(rolesOf(type)).filter(([, role]) => role === "display").map(([name]) => name);
100
+ }
101
+ export {
102
+ MARK_ATTR_ROLES,
103
+ NODE_ATTR_ROLES,
104
+ attrRole,
105
+ displayAttrsOf
106
+ };
@@ -1,6 +1,12 @@
1
1
  /**
2
2
  * ProseMirror schema joining preserved DOCX fragments with validated display values. Original XML
3
3
  * remains on nodes and marks so untouched content can round-trip verbatim.
4
+ *
5
+ * Every block sits in one of two groups beside `block`. A `modelled` block is one the editor
6
+ * takes apart and writes back out itself, so the writer decides what it says; a `preserved` one
7
+ * goes back out as the XML it arrived as. Which of the two a block is decides how it is written
8
+ * (`docx/serializeBlock`) and how a submitted file is compared against the original
9
+ * (`docx/storyProjection`), so a new block node has to name one of them.
4
10
  */
5
11
  import { Schema } from "prosemirror-model";
6
12
  export { imageNodeSpec, runMarkSpec } from "./rendering";