@lotics/docx 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.
Files changed (107) hide show
  1. package/package.json +40 -0
  2. package/src/fixtures/.gitkeep +0 -0
  3. package/src/fixtures/lotics_generated_contract.docx +0 -0
  4. package/src/fonts/bundled.ts +123 -0
  5. package/src/fonts/registry.test.ts +233 -0
  6. package/src/fonts/registry.ts +219 -0
  7. package/src/fonts/types.ts +83 -0
  8. package/src/index.ts +16 -0
  9. package/src/layout/engine.test.ts +430 -0
  10. package/src/layout/engine.ts +566 -0
  11. package/src/layout/page_geometry.ts +43 -0
  12. package/src/layout/types.ts +159 -0
  13. package/src/load.test.ts +144 -0
  14. package/src/load.ts +142 -0
  15. package/src/model/default_numbering.ts +101 -0
  16. package/src/model/default_styles.ts +201 -0
  17. package/src/model/numbering_table.ts +52 -0
  18. package/src/model/properties.ts +328 -0
  19. package/src/model/sections.ts +94 -0
  20. package/src/model/style_resolution.test.ts +219 -0
  21. package/src/model/style_resolution.ts +113 -0
  22. package/src/model/style_table.ts +22 -0
  23. package/src/model/theme.ts +156 -0
  24. package/src/model/types.ts +55 -0
  25. package/src/parse/drawing.ts +157 -0
  26. package/src/parse/font_table.ts +132 -0
  27. package/src/parse/footnotes.ts +60 -0
  28. package/src/parse/header_footer.test.ts +264 -0
  29. package/src/parse/header_footer.ts +66 -0
  30. package/src/parse/numbering.ts +187 -0
  31. package/src/parse/parser.ts +184 -0
  32. package/src/parse/relationships.ts +83 -0
  33. package/src/parse/sections.test.ts +192 -0
  34. package/src/parse/sections.ts +182 -0
  35. package/src/parse/styles.ts +149 -0
  36. package/src/parse/theme.test.ts +86 -0
  37. package/src/parse/theme.ts +112 -0
  38. package/src/pm/bubble_menu.ts +117 -0
  39. package/src/pm/commands.test.ts +185 -0
  40. package/src/pm/commands.ts +697 -0
  41. package/src/pm/commands_insert.test.ts +183 -0
  42. package/src/pm/docx_to_pm.test.ts +330 -0
  43. package/src/pm/docx_to_pm.ts +643 -0
  44. package/src/pm/drag_handle.ts +166 -0
  45. package/src/pm/format_painter.test.ts +91 -0
  46. package/src/pm/format_painter.ts +109 -0
  47. package/src/pm/header_footer_doc.ts +24 -0
  48. package/src/pm/hyperlinks.test.ts +234 -0
  49. package/src/pm/image_registry.test.ts +81 -0
  50. package/src/pm/image_registry.ts +100 -0
  51. package/src/pm/images.test.ts +257 -0
  52. package/src/pm/link_popover.ts +159 -0
  53. package/src/pm/mark_commands.ts +60 -0
  54. package/src/pm/marks.ts +169 -0
  55. package/src/pm/nodes.ts +258 -0
  56. package/src/pm/numbering.test.ts +210 -0
  57. package/src/pm/numbering_plugin.test.ts +71 -0
  58. package/src/pm/numbering_plugin.ts +96 -0
  59. package/src/pm/outline.ts +41 -0
  60. package/src/pm/page_break.test.ts +80 -0
  61. package/src/pm/page_layout.test.ts +87 -0
  62. package/src/pm/pagination_plugin.test.ts +155 -0
  63. package/src/pm/pagination_plugin.ts +590 -0
  64. package/src/pm/phase5.test.ts +271 -0
  65. package/src/pm/phase6.test.ts +215 -0
  66. package/src/pm/placeholder_plugin.ts +24 -0
  67. package/src/pm/plugins.ts +91 -0
  68. package/src/pm/pm_to_docx.ts +0 -0
  69. package/src/pm/roundtrip.test.ts +332 -0
  70. package/src/pm/schema.test.ts +188 -0
  71. package/src/pm/schema.ts +79 -0
  72. package/src/pm/search.ts +46 -0
  73. package/src/pm/table_attrs.ts +48 -0
  74. package/src/pm/table_borders.test.ts +117 -0
  75. package/src/pm/table_borders.ts +130 -0
  76. package/src/pm/table_convert.test.ts +221 -0
  77. package/src/pm/table_convert.ts +541 -0
  78. package/src/pm/table_decorations.ts +132 -0
  79. package/src/pm/table_handles.ts +163 -0
  80. package/src/pm/template_marker.ts +47 -0
  81. package/src/pm/template_plugin.ts +65 -0
  82. package/src/pm/templates.test.ts +162 -0
  83. package/src/render/clipboard.test.ts +115 -0
  84. package/src/render/clipboard.ts +200 -0
  85. package/src/render/editable_view.test.ts +173 -0
  86. package/src/render/footnotes_view.ts +94 -0
  87. package/src/render/header_footer_view.ts +95 -0
  88. package/src/render/link_mark_view.ts +26 -0
  89. package/src/render/media_resolver.ts +61 -0
  90. package/src/render/node_views.ts +296 -0
  91. package/src/render/numbering_counter.ts +149 -0
  92. package/src/render/page_chrome.test.ts +262 -0
  93. package/src/render/page_chrome.ts +343 -0
  94. package/src/render/page_styles.ts +234 -0
  95. package/src/render/paragraph_view.test.ts +162 -0
  96. package/src/render/paragraph_view.ts +141 -0
  97. package/src/render/ruler.ts +110 -0
  98. package/src/render/style_registry.ts +33 -0
  99. package/src/render/table_dom.test.ts +171 -0
  100. package/src/render/table_dom.ts +288 -0
  101. package/src/render/units.ts +18 -0
  102. package/src/render/view.test.ts +165 -0
  103. package/src/render/view.ts +607 -0
  104. package/src/roundtrip.test.ts +179 -0
  105. package/src/serialize/default_parts.ts +128 -0
  106. package/src/serialize/header_footer_pm.ts +82 -0
  107. package/src/serialize/serializer.ts +114 -0
@@ -0,0 +1,185 @@
1
+ // @vitest-environment happy-dom
2
+ import { describe, it, expect, beforeEach } from "vitest";
3
+ import { TextSelection } from "prosemirror-state";
4
+ import { parseStyleTable } from "../parse/styles";
5
+ import { parseNumberingTable } from "../parse/numbering";
6
+ import { docxSchema } from "./schema";
7
+ import {
8
+ applyHeading,
9
+ clearParagraphStyle,
10
+ setParagraphAlignment,
11
+ setParagraphNumbering,
12
+ toggleBulletList,
13
+ toggleNumberedList,
14
+ increaseListIndent,
15
+ decreaseListIndent,
16
+ } from "./commands";
17
+ import { createEditableView } from "../render/view";
18
+ import { buildFontRegistry } from "../fonts/registry";
19
+ import { DEFAULT_SECTION_PROPERTIES } from "../model/sections";
20
+ import type { Section } from "../model/sections";
21
+
22
+ const fontRegistry = buildFontRegistry({
23
+ embeddedFonts: [],
24
+ workspaceFonts: [],
25
+ });
26
+
27
+ const defaultSections: Section[] = [
28
+ {
29
+ properties: { ...DEFAULT_SECTION_PROPERTIES },
30
+ blockStartIndex: 0,
31
+ blockEndIndex: 0,
32
+ },
33
+ ];
34
+
35
+ const STYLES_XML = `<?xml version="1.0"?>
36
+ <w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
37
+ <w:docDefaults><w:rPrDefault><w:rPr><w:sz w:val="22"/></w:rPr></w:rPrDefault></w:docDefaults>
38
+ <w:style w:type="paragraph" w:styleId="Normal" w:default="1"><w:name w:val="Normal"/></w:style>
39
+ <w:style w:type="paragraph" w:styleId="Heading1">
40
+ <w:name w:val="heading 1"/>
41
+ <w:basedOn w:val="Normal"/>
42
+ <w:pPr><w:outlineLvl w:val="0"/></w:pPr>
43
+ <w:rPr><w:b/><w:sz w:val="32"/></w:rPr>
44
+ </w:style>
45
+ </w:styles>`;
46
+
47
+ const NUMBERING_XML = `<?xml version="1.0"?>
48
+ <w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
49
+ <w:abstractNum w:abstractNumId="0">
50
+ <w:lvl w:ilvl="0"><w:start w:val="1"/><w:numFmt w:val="bullet"/><w:lvlText w:val="•"/></w:lvl>
51
+ </w:abstractNum>
52
+ <w:abstractNum w:abstractNumId="1">
53
+ <w:lvl w:ilvl="0"><w:start w:val="1"/><w:numFmt w:val="decimal"/><w:lvlText w:val="%1."/></w:lvl>
54
+ <w:lvl w:ilvl="1"><w:start w:val="1"/><w:numFmt w:val="lowerLetter"/><w:lvlText w:val="%2)"/></w:lvl>
55
+ </w:abstractNum>
56
+ <w:num w:numId="1"><w:abstractNumId w:val="0"/></w:num>
57
+ <w:num w:numId="2"><w:abstractNumId w:val="1"/></w:num>
58
+ </w:numbering>`;
59
+
60
+ const styleTable = parseStyleTable(
61
+ new Map([["word/styles.xml", new TextEncoder().encode(STYLES_XML)]]),
62
+ );
63
+ const numberingTable = parseNumberingTable(
64
+ new Map([["word/numbering.xml", new TextEncoder().encode(NUMBERING_XML)]]),
65
+ );
66
+
67
+ let host: HTMLElement;
68
+ beforeEach(() => {
69
+ document.body.innerHTML = "";
70
+ host = document.createElement("div");
71
+ document.body.appendChild(host);
72
+ });
73
+
74
+ function buildView(text: string) {
75
+ const { doc, paragraph } = docxSchema.nodes;
76
+ const node = doc.create({}, [paragraph.create({}, [docxSchema.text(text)])]);
77
+ return createEditableView(host, node, {
78
+ fontRegistry,
79
+ sections: defaultSections,
80
+ numberingTable,
81
+ });
82
+ }
83
+
84
+ function selectAll(view: ReturnType<typeof buildView>): void {
85
+ const { state } = view.editorView;
86
+ const tr = state.tr.setSelection(
87
+ TextSelection.create(state.doc, 1, state.doc.content.size - 1),
88
+ );
89
+ view.editorView.dispatch(tr);
90
+ }
91
+
92
+ describe("paragraph style commands", () => {
93
+ it("applyHeading(1) sets styleId to Heading1 and resolves CSS", () => {
94
+ const view = buildView("title");
95
+ const cmd = applyHeading(1, { styleTable });
96
+ cmd(view.editorView.state, view.editorView.dispatch);
97
+ const para = view.getDocument().firstChild!;
98
+ expect(para.attrs.properties.styleId).toBe("Heading1");
99
+ expect(para.attrs.resolvedBaseRun.bold).toBe(true);
100
+ view.destroy();
101
+ });
102
+
103
+ it("clearParagraphStyle removes the style", () => {
104
+ const view = buildView("body");
105
+ applyHeading(1, { styleTable })(view.editorView.state, view.editorView.dispatch);
106
+ clearParagraphStyle({ styleTable })(view.editorView.state, view.editorView.dispatch);
107
+ expect(view.getDocument().firstChild!.attrs.properties.styleId).toBeNull();
108
+ view.destroy();
109
+ });
110
+
111
+ it("setParagraphAlignment writes alignment", () => {
112
+ const view = buildView("hello");
113
+ setParagraphAlignment("center", { styleTable })(
114
+ view.editorView.state,
115
+ view.editorView.dispatch,
116
+ );
117
+ expect(view.getDocument().firstChild!.attrs.properties.alignment).toBe(
118
+ "center",
119
+ );
120
+ view.destroy();
121
+ });
122
+ });
123
+
124
+ describe("list commands", () => {
125
+ it("toggleBulletList adds numPr; calling twice removes it", () => {
126
+ const view = buildView("item");
127
+ selectAll(view);
128
+ toggleBulletList({ styleTable })(view.editorView.state, view.editorView.dispatch);
129
+ let para = view.getDocument().firstChild!;
130
+ expect(para.attrs.properties.numbering).toEqual({ numId: 1, ilvl: 0 });
131
+
132
+ selectAll(view);
133
+ toggleBulletList({ styleTable })(view.editorView.state, view.editorView.dispatch);
134
+ para = view.getDocument().firstChild!;
135
+ expect(para.attrs.properties.numbering).toBeNull();
136
+ view.destroy();
137
+ });
138
+
139
+ it("toggleNumberedList sets numId 2", () => {
140
+ const view = buildView("step");
141
+ selectAll(view);
142
+ toggleNumberedList({ styleTable })(view.editorView.state, view.editorView.dispatch);
143
+ const para = view.getDocument().firstChild!;
144
+ expect(para.attrs.properties.numbering).toEqual({ numId: 2, ilvl: 0 });
145
+ view.destroy();
146
+ });
147
+
148
+ it("increase/decrease list indent walks ilvl", () => {
149
+ const view = buildView("nested");
150
+ selectAll(view);
151
+ setParagraphNumbering({ numId: 2, ilvl: 0 }, { styleTable })(
152
+ view.editorView.state,
153
+ view.editorView.dispatch,
154
+ );
155
+ increaseListIndent({ styleTable })(view.editorView.state, view.editorView.dispatch);
156
+ expect(view.getDocument().firstChild!.attrs.properties.numbering.ilvl).toBe(1);
157
+ decreaseListIndent({ styleTable })(view.editorView.state, view.editorView.dispatch);
158
+ expect(view.getDocument().firstChild!.attrs.properties.numbering.ilvl).toBe(0);
159
+ view.destroy();
160
+ });
161
+
162
+ it("decoration plugin renders the live label after toggling a list", () => {
163
+ const view = buildView("first");
164
+ selectAll(view);
165
+ toggleNumberedList({ styleTable })(view.editorView.state, view.editorView.dispatch);
166
+
167
+ const markers = host.querySelectorAll(".docx-numbering-marker");
168
+ expect(markers).toHaveLength(1);
169
+ expect(markers[0].textContent).toBe("1.");
170
+ view.destroy();
171
+ });
172
+ });
173
+
174
+ describe("empty-run cleanup", () => {
175
+ it("paragraphs with no inline content emit a placeholder run on serialize", async () => {
176
+ const { pmToDocx } = await import("./pm_to_docx");
177
+ const { doc, paragraph } = docxSchema.nodes;
178
+ const empty = doc.create({}, [paragraph.create({}, [])]);
179
+ const docx = pmToDocx(empty, { parts: new Map(), documentAttrs: {} });
180
+ const para = docx.body.children[0];
181
+ if (para.kind !== "paragraph") throw new Error();
182
+ expect(para.content).toHaveLength(1);
183
+ expect(para.content[0].kind).toBe("run");
184
+ });
185
+ });