@project-ava-supernova/core 0.2.71 → 0.2.72

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 (166) hide show
  1. package/dist/agent/agent.d.ts.map +1 -1
  2. package/dist/agent/agent.js +138 -38
  3. package/dist/agent/agent.js.map +1 -1
  4. package/dist/agent/mode-tags.d.ts +72 -0
  5. package/dist/agent/mode-tags.d.ts.map +1 -0
  6. package/dist/agent/mode-tags.js +84 -0
  7. package/dist/agent/mode-tags.js.map +1 -0
  8. package/dist/agent/system-prompt.d.ts +16 -2
  9. package/dist/agent/system-prompt.d.ts.map +1 -1
  10. package/dist/agent/system-prompt.js +133 -24
  11. package/dist/agent/system-prompt.js.map +1 -1
  12. package/dist/brainstorm/brainstorm-store.d.ts +42 -0
  13. package/dist/brainstorm/brainstorm-store.d.ts.map +1 -0
  14. package/dist/brainstorm/brainstorm-store.js +164 -0
  15. package/dist/brainstorm/brainstorm-store.js.map +1 -0
  16. package/dist/brainstorm/types.d.ts +64 -0
  17. package/dist/brainstorm/types.d.ts.map +1 -0
  18. package/dist/brainstorm/types.js +20 -0
  19. package/dist/brainstorm/types.js.map +1 -0
  20. package/dist/config/project.d.ts.map +1 -1
  21. package/dist/config/project.js +3 -1
  22. package/dist/config/project.js.map +1 -1
  23. package/dist/config/schema.d.ts +13 -0
  24. package/dist/config/schema.d.ts.map +1 -1
  25. package/dist/config/schema.js.map +1 -1
  26. package/dist/dataset/capture.d.ts.map +1 -1
  27. package/dist/dataset/capture.js +6 -14
  28. package/dist/dataset/capture.js.map +1 -1
  29. package/dist/history/conversation-title.d.ts +5 -4
  30. package/dist/history/conversation-title.d.ts.map +1 -1
  31. package/dist/history/conversation-title.js +6 -19
  32. package/dist/history/conversation-title.js.map +1 -1
  33. package/dist/i18n/locales/ar.d.ts.map +1 -1
  34. package/dist/i18n/locales/ar.js +41 -13
  35. package/dist/i18n/locales/ar.js.map +1 -1
  36. package/dist/i18n/locales/de.d.ts.map +1 -1
  37. package/dist/i18n/locales/de.js +41 -13
  38. package/dist/i18n/locales/de.js.map +1 -1
  39. package/dist/i18n/locales/en.d.ts +38 -10
  40. package/dist/i18n/locales/en.d.ts.map +1 -1
  41. package/dist/i18n/locales/en.js +41 -13
  42. package/dist/i18n/locales/en.js.map +1 -1
  43. package/dist/i18n/locales/es.d.ts.map +1 -1
  44. package/dist/i18n/locales/es.js +41 -13
  45. package/dist/i18n/locales/es.js.map +1 -1
  46. package/dist/i18n/locales/fr.d.ts.map +1 -1
  47. package/dist/i18n/locales/fr.js +41 -13
  48. package/dist/i18n/locales/fr.js.map +1 -1
  49. package/dist/i18n/locales/hi.d.ts.map +1 -1
  50. package/dist/i18n/locales/hi.js +41 -13
  51. package/dist/i18n/locales/hi.js.map +1 -1
  52. package/dist/i18n/locales/id.d.ts.map +1 -1
  53. package/dist/i18n/locales/id.js +41 -13
  54. package/dist/i18n/locales/id.js.map +1 -1
  55. package/dist/i18n/locales/it.d.ts.map +1 -1
  56. package/dist/i18n/locales/it.js +41 -13
  57. package/dist/i18n/locales/it.js.map +1 -1
  58. package/dist/i18n/locales/ja.d.ts.map +1 -1
  59. package/dist/i18n/locales/ja.js +41 -13
  60. package/dist/i18n/locales/ja.js.map +1 -1
  61. package/dist/i18n/locales/ko.d.ts.map +1 -1
  62. package/dist/i18n/locales/ko.js +41 -13
  63. package/dist/i18n/locales/ko.js.map +1 -1
  64. package/dist/i18n/locales/nl.d.ts.map +1 -1
  65. package/dist/i18n/locales/nl.js +41 -13
  66. package/dist/i18n/locales/nl.js.map +1 -1
  67. package/dist/i18n/locales/pl.d.ts.map +1 -1
  68. package/dist/i18n/locales/pl.js +41 -13
  69. package/dist/i18n/locales/pl.js.map +1 -1
  70. package/dist/i18n/locales/pt.d.ts.map +1 -1
  71. package/dist/i18n/locales/pt.js +41 -13
  72. package/dist/i18n/locales/pt.js.map +1 -1
  73. package/dist/i18n/locales/ru.d.ts.map +1 -1
  74. package/dist/i18n/locales/ru.js +41 -13
  75. package/dist/i18n/locales/ru.js.map +1 -1
  76. package/dist/i18n/locales/th.d.ts.map +1 -1
  77. package/dist/i18n/locales/th.js +41 -13
  78. package/dist/i18n/locales/th.js.map +1 -1
  79. package/dist/i18n/locales/tr.d.ts.map +1 -1
  80. package/dist/i18n/locales/tr.js +41 -13
  81. package/dist/i18n/locales/tr.js.map +1 -1
  82. package/dist/i18n/locales/uk.d.ts.map +1 -1
  83. package/dist/i18n/locales/uk.js +41 -13
  84. package/dist/i18n/locales/uk.js.map +1 -1
  85. package/dist/i18n/locales/vi.d.ts.map +1 -1
  86. package/dist/i18n/locales/vi.js +41 -13
  87. package/dist/i18n/locales/vi.js.map +1 -1
  88. package/dist/i18n/locales/zh-CN.d.ts.map +1 -1
  89. package/dist/i18n/locales/zh-CN.js +41 -13
  90. package/dist/i18n/locales/zh-CN.js.map +1 -1
  91. package/dist/i18n/locales/zh-TW.d.ts.map +1 -1
  92. package/dist/i18n/locales/zh-TW.js +41 -13
  93. package/dist/i18n/locales/zh-TW.js.map +1 -1
  94. package/dist/index.d.ts +5 -1
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +7 -1
  97. package/dist/index.js.map +1 -1
  98. package/dist/journal/journal-manager.d.ts +22 -1
  99. package/dist/journal/journal-manager.d.ts.map +1 -1
  100. package/dist/journal/journal-manager.js +47 -2
  101. package/dist/journal/journal-manager.js.map +1 -1
  102. package/dist/personas/conductor.d.ts +11 -0
  103. package/dist/personas/conductor.d.ts.map +1 -1
  104. package/dist/personas/conductor.js +56 -12
  105. package/dist/personas/conductor.js.map +1 -1
  106. package/dist/personas/definitions.d.ts +15 -4
  107. package/dist/personas/definitions.d.ts.map +1 -1
  108. package/dist/personas/definitions.js +184 -19
  109. package/dist/personas/definitions.js.map +1 -1
  110. package/dist/personas/types.d.ts +1 -1
  111. package/dist/personas/types.d.ts.map +1 -1
  112. package/dist/projects/project-data.d.ts +73 -0
  113. package/dist/projects/project-data.d.ts.map +1 -0
  114. package/dist/projects/project-data.js +126 -0
  115. package/dist/projects/project-data.js.map +1 -0
  116. package/dist/projects/projects-home.d.ts +33 -0
  117. package/dist/projects/projects-home.d.ts.map +1 -0
  118. package/dist/projects/projects-home.js +43 -0
  119. package/dist/projects/projects-home.js.map +1 -0
  120. package/dist/projects/storage-categories.d.ts +45 -0
  121. package/dist/projects/storage-categories.d.ts.map +1 -0
  122. package/dist/projects/storage-categories.js +86 -0
  123. package/dist/projects/storage-categories.js.map +1 -0
  124. package/dist/tools/authoring/export-document.d.ts +45 -0
  125. package/dist/tools/authoring/export-document.d.ts.map +1 -0
  126. package/dist/tools/authoring/export-document.js +132 -0
  127. package/dist/tools/authoring/export-document.js.map +1 -0
  128. package/dist/tools/authoring/export-formats.d.ts +37 -0
  129. package/dist/tools/authoring/export-formats.d.ts.map +1 -0
  130. package/dist/tools/authoring/export-formats.js +50 -0
  131. package/dist/tools/authoring/export-formats.js.map +1 -0
  132. package/dist/tools/authoring/index.d.ts +6 -1
  133. package/dist/tools/authoring/index.d.ts.map +1 -1
  134. package/dist/tools/authoring/index.js +9 -0
  135. package/dist/tools/authoring/index.js.map +1 -1
  136. package/dist/tools/authoring/render-odf.d.ts +22 -0
  137. package/dist/tools/authoring/render-odf.d.ts.map +1 -0
  138. package/dist/tools/authoring/render-odf.js +604 -0
  139. package/dist/tools/authoring/render-odf.js.map +1 -0
  140. package/dist/tools/authoring/zip.d.ts +33 -0
  141. package/dist/tools/authoring/zip.d.ts.map +1 -0
  142. package/dist/tools/authoring/zip.js +125 -0
  143. package/dist/tools/authoring/zip.js.map +1 -0
  144. package/dist/tools/brainstorm-session.d.ts +29 -0
  145. package/dist/tools/brainstorm-session.d.ts.map +1 -0
  146. package/dist/tools/brainstorm-session.js +184 -0
  147. package/dist/tools/brainstorm-session.js.map +1 -0
  148. package/dist/tools/document-author.d.ts +1 -1
  149. package/dist/tools/document-author.d.ts.map +1 -1
  150. package/dist/tools/document-author.js +22 -5
  151. package/dist/tools/document-author.js.map +1 -1
  152. package/dist/tools/security.d.ts +25 -0
  153. package/dist/tools/security.d.ts.map +1 -1
  154. package/dist/tools/security.js +35 -0
  155. package/dist/tools/security.js.map +1 -1
  156. package/dist/tools/tool-registry.d.ts.map +1 -1
  157. package/dist/tools/tool-registry.js +4 -0
  158. package/dist/tools/tool-registry.js.map +1 -1
  159. package/dist/tools/verification-trust.d.ts +2 -2
  160. package/dist/tools/verification-trust.d.ts.map +1 -1
  161. package/dist/tools/verification-trust.js +11 -13
  162. package/dist/tools/verification-trust.js.map +1 -1
  163. package/dist/tools/write-video-post.d.ts.map +1 -1
  164. package/dist/tools/write-video-post.js +89 -13
  165. package/dist/tools/write-video-post.js.map +1 -1
  166. package/package.json +26 -1
@@ -0,0 +1,604 @@
1
+ /**
2
+ * DocModel → OpenDocument (.odt text, .ods spreadsheet).
3
+ *
4
+ * Walks the same rich model the docx and pdf renderers walk, honouring the
5
+ * same brand tokens, so the three formats are one document in three coats
6
+ * rather than three separate documents. Unlike those two this needs no peer
7
+ * dependency: an ODF file is XML in a zip, and both halves are in Node's
8
+ * standard library (see ./zip.ts). The open format is the one that always
9
+ * works, which is the point of offering it.
10
+ */
11
+ import { readFile } from 'node:fs/promises';
12
+ import { isAbsolute, resolve } from 'node:path';
13
+ import { inlineToPlainText } from './doc-model.js';
14
+ import { AVA_TITLE_SIZE_PDF, AVA_H1_SIZE_PDF, AVA_H2_SIZE_PDF, AVA_H3_SIZE_PDF, AVA_H4_SIZE_PDF, AVA_BODY_SIZE_PDF, AVA_SUBTITLE_SIZE_PDF, AVA_LINK_COLOR, CALLOUT_COLORS, resolveBrand, } from '../document-styling.js';
15
+ import { getImageSize } from './image-size.js';
16
+ import { zipSync } from './zip.js';
17
+ const MONO_FONT = 'Consolas';
18
+ /** Usable width between the page margins, in cm — images are capped to it so
19
+ * an oversized screenshot cannot push the text off the page. */
20
+ const CONTENT_WIDTH_CM = 16.5;
21
+ // ── XML helpers ──────────────────────────────────────────────────────────────
22
+ function esc(s) {
23
+ return s
24
+ .replace(/&/g, '&')
25
+ .replace(/</g, '&lt;')
26
+ .replace(/>/g, '&gt;')
27
+ .replace(/"/g, '&quot;')
28
+ .replace(/'/g, '&apos;');
29
+ }
30
+ /**
31
+ * Escape for a text run. ODF collapses whitespace like HTML, so tabs, newlines
32
+ * and runs of spaces need explicit elements or indentation silently vanishes —
33
+ * which matters most inside code blocks.
34
+ */
35
+ function textNode(s) {
36
+ let out = '';
37
+ let i = 0;
38
+ while (i < s.length) {
39
+ const ch = s[i];
40
+ if (ch === '\t') {
41
+ out += '<text:tab/>';
42
+ i++;
43
+ continue;
44
+ }
45
+ if (ch === '\n') {
46
+ out += '<text:line-break/>';
47
+ i++;
48
+ continue;
49
+ }
50
+ if (ch === ' ') {
51
+ let n = 0;
52
+ while (s[i + n] === ' ')
53
+ n++;
54
+ out += n === 1 ? ' ' : ` <text:s text:c="${n - 1}"/>`;
55
+ i += n;
56
+ continue;
57
+ }
58
+ let j = i;
59
+ while (j < s.length && s[j] !== '\t' && s[j] !== '\n' && s[j] !== ' ')
60
+ j++;
61
+ out += esc(s.slice(i, j));
62
+ i = j;
63
+ }
64
+ return out;
65
+ }
66
+ function hex(color) {
67
+ return color.startsWith('#') ? color : `#${color}`;
68
+ }
69
+ function spanStyle(state) {
70
+ if (state.bold && state.italic)
71
+ return 'T_BoldItalic';
72
+ if (state.bold)
73
+ return 'T_Bold';
74
+ if (state.italic)
75
+ return 'T_Italic';
76
+ return null;
77
+ }
78
+ function inlineXml(ctx, inlines, state = { bold: false, italic: false }) {
79
+ let out = '';
80
+ for (const node of inlines) {
81
+ switch (node.type) {
82
+ case 'text': {
83
+ const style = spanStyle(state);
84
+ const body = textNode(node.text);
85
+ out += style ? `<text:span text:style-name="${style}">${body}</text:span>` : body;
86
+ break;
87
+ }
88
+ case 'strong':
89
+ out += inlineXml(ctx, node.inlines, { ...state, bold: true });
90
+ break;
91
+ case 'em':
92
+ out += inlineXml(ctx, node.inlines, { ...state, italic: true });
93
+ break;
94
+ case 'code':
95
+ out += `<text:span text:style-name="T_Code">${textNode(node.text)}</text:span>`;
96
+ break;
97
+ case 'link':
98
+ out += `<text:a xlink:type="simple" xlink:href="${esc(node.href)}">`
99
+ + `<text:span text:style-name="T_Link">${inlineXml(ctx, node.inlines, state)}</text:span></text:a>`;
100
+ break;
101
+ case 'footnoteRef':
102
+ out += footnoteXml(ctx, node.id);
103
+ break;
104
+ case 'break':
105
+ out += '<text:line-break/>';
106
+ break;
107
+ }
108
+ }
109
+ return out;
110
+ }
111
+ /** ODF carries the note body at the reference point rather than in a separate
112
+ * part, so the definition is emitted inline the first time it is cited. */
113
+ function footnoteXml(ctx, id) {
114
+ const n = ctx.footnoteNumbers.get(id);
115
+ if (!n)
116
+ return '';
117
+ const body = ctx.footnotes[id];
118
+ const inner = body ? inlineXml(ctx, body) : '';
119
+ return `<text:note text:id="ftn${n}" text:note-class="footnote">`
120
+ + `<text:note-citation>${n}</text:note-citation>`
121
+ + `<text:note-body><text:p text:style-name="Footnote">${inner}</text:p></text:note-body>`
122
+ + '</text:note>';
123
+ }
124
+ // ── Blocks ───────────────────────────────────────────────────────────────────
125
+ async function blocksXml(ctx, blocks, model) {
126
+ let out = '';
127
+ for (const block of blocks)
128
+ out += await blockXml(ctx, block, model);
129
+ return out;
130
+ }
131
+ async function blockXml(ctx, block, model) {
132
+ switch (block.type) {
133
+ case 'heading':
134
+ return `<text:h text:style-name="Heading_20_${block.level}" text:outline-level="${block.level}">`
135
+ + `${inlineXml(ctx, block.inlines)}</text:h>`;
136
+ case 'paragraph':
137
+ return `<text:p text:style-name="Standard">${inlineXml(ctx, block.inlines)}</text:p>`;
138
+ case 'list':
139
+ return await listXml(ctx, block.ordered, block.items, model);
140
+ case 'table':
141
+ return tableXml(ctx, block.headers, block.rows, block.align);
142
+ case 'blockquote': {
143
+ // Quotation styling lives on the paragraphs, so nested blocks keep their
144
+ // own shape (a list inside a quote stays a list).
145
+ const inner = await blocksXml(ctx, block.blocks, model);
146
+ return inner.replace(/text:style-name="Standard"/g, 'text:style-name="Quotations"');
147
+ }
148
+ case 'callout': {
149
+ const variant = CALLOUT_COLORS[block.variant] ? block.variant : 'note';
150
+ const label = block.title ?? CALLOUT_COLORS[variant].label;
151
+ const inner = await blocksXml(ctx, block.blocks, model);
152
+ const styled = inner.replace(/text:style-name="Standard"/g, `text:style-name="P_Callout_${variant}"`);
153
+ const head = label
154
+ ? `<text:p text:style-name="P_CalloutLabel_${variant}">${textNode(label)}</text:p>`
155
+ : '';
156
+ return head + styled;
157
+ }
158
+ case 'image':
159
+ return await imageXml(ctx, block);
160
+ case 'code':
161
+ // One paragraph per line: ODF has no block-level preformatted container,
162
+ // and a single paragraph with line-breaks loses per-line background.
163
+ return block.text.split('\n')
164
+ .map((line) => `<text:p text:style-name="P_Code">${line ? textNode(line) : ''}</text:p>`)
165
+ .join('');
166
+ case 'pagebreak':
167
+ return '<text:p text:style-name="P_PageBreak"/>';
168
+ case 'hr':
169
+ return '<text:p text:style-name="P_Hr"/>';
170
+ case 'toc':
171
+ return tocXml(model);
172
+ }
173
+ }
174
+ async function listXml(ctx, ordered, items, model) {
175
+ const style = ordered ? 'L_Order' : 'L_Bullet';
176
+ let out = `<text:list text:style-name="${style}">`;
177
+ for (const item of items) {
178
+ out += '<text:list-item>';
179
+ out += `<text:p text:style-name="P_ListItem">${inlineXml(ctx, item.inlines)}</text:p>`;
180
+ if (item.children?.length)
181
+ out += await blocksXml(ctx, item.children, model);
182
+ out += '</text:list-item>';
183
+ }
184
+ return out + '</text:list>';
185
+ }
186
+ function cellParaStyle(align) {
187
+ return align === 'center' ? 'P_CellCenter' : align === 'right' ? 'P_CellRight' : 'P_Cell';
188
+ }
189
+ function tableXml(ctx, headers, rows, align) {
190
+ const cols = Math.max(headers.length, ...rows.map((r) => r.length), 1);
191
+ const name = `Table${++ctx.tableCount}`;
192
+ let out = `<table:table table:name="${name}" table:style-name="Ta_Doc">`;
193
+ out += `<table:table-column table:style-name="Co_Doc" table:number-columns-repeated="${cols}"/>`;
194
+ const cell = (inlines, i, header) => {
195
+ const style = header ? 'Ce_Head' : 'Ce_Body';
196
+ const para = header ? 'P_CellHead' : cellParaStyle(align?.[i]);
197
+ const body = inlines ? inlineXml(ctx, inlines) : '';
198
+ return `<table:table-cell table:style-name="${style}" office:value-type="string">`
199
+ + `<text:p text:style-name="${para}">${body}</text:p></table:table-cell>`;
200
+ };
201
+ if (headers.length) {
202
+ out += '<table:table-header-rows><table:table-row>';
203
+ for (let i = 0; i < cols; i++)
204
+ out += cell(headers[i], i, true);
205
+ out += '</table:table-row></table:table-header-rows>';
206
+ }
207
+ for (const row of rows) {
208
+ out += '<table:table-row>';
209
+ for (let i = 0; i < cols; i++)
210
+ out += cell(row[i], i, false);
211
+ out += '</table:table-row>';
212
+ }
213
+ return out + '</table:table>';
214
+ }
215
+ async function imageXml(ctx, block) {
216
+ let data;
217
+ try {
218
+ const path = isAbsolute(block.src) ? block.src : resolve(ctx.baseDir ?? '.', block.src);
219
+ data = await readFile(path);
220
+ }
221
+ catch {
222
+ // A missing image is not worth failing an export over — say so in place,
223
+ // the way a broken <img> shows its alt text.
224
+ const alt = block.alt || block.src;
225
+ return `<text:p text:style-name="P_ImageMissing">[${textNode(alt)}]</text:p>`;
226
+ }
227
+ const size = getImageSize(data);
228
+ if (!size)
229
+ return `<text:p text:style-name="P_ImageMissing">[${textNode(block.alt || block.src)}]</text:p>`;
230
+ const mime = size.type === 'jpg' ? 'image/jpeg' : `image/${size.type}`;
231
+ const name = `Pictures/image${ctx.pictures.length + 1}.${size.type}`;
232
+ ctx.pictures.push({ name, data, mime });
233
+ // Model widths are pixels; ODF wants physical units. 96dpi is the same
234
+ // assumption the docx renderer makes, so the two agree on scale.
235
+ const pxWidth = block.width || size.width;
236
+ let widthCm = (pxWidth / 96) * 2.54;
237
+ let heightCm = widthCm * (size.height / size.width);
238
+ if (widthCm > CONTENT_WIDTH_CM) {
239
+ heightCm *= CONTENT_WIDTH_CM / widthCm;
240
+ widthCm = CONTENT_WIDTH_CM;
241
+ }
242
+ const title = block.alt ? `<svg:title>${esc(block.alt)}</svg:title>` : '';
243
+ return '<text:p text:style-name="P_Image">'
244
+ + `<draw:frame draw:style-name="Fr_Image" draw:name="Image${ctx.pictures.length}" `
245
+ + `text:anchor-type="as-char" svg:width="${widthCm.toFixed(3)}cm" svg:height="${heightCm.toFixed(3)}cm" draw:z-index="0">`
246
+ + `<draw:image xlink:href="${esc(name)}" xlink:type="simple" xlink:show="embed" xlink:actuate="onLoad"/>`
247
+ + `${title}</draw:frame></text:p>`;
248
+ }
249
+ /**
250
+ * A real ODF index, not a frozen list: the entry templates let LibreOffice
251
+ * refresh page numbers on open, while the stored body means a reader that
252
+ * never refreshes still shows contents.
253
+ */
254
+ function tocXml(model) {
255
+ let templates = '';
256
+ for (let level = 1; level <= 4; level++) {
257
+ templates += `<text:table-of-content-entry-template text:outline-level="${level}" text:style-name="Contents_20_${level}">`
258
+ + '<text:index-entry-chapter/><text:index-entry-text/>'
259
+ + '<text:index-entry-tab-stop style:type="right" style:leader-char="."/>'
260
+ + '<text:index-entry-page-number/></text:table-of-content-entry-template>';
261
+ }
262
+ let body = '<text:index-title text:name="Contents_Head">'
263
+ + '<text:p text:style-name="Contents_20_Heading">Contents</text:p></text:index-title>';
264
+ for (const block of model.blocks) {
265
+ if (block.type !== 'heading')
266
+ continue;
267
+ body += `<text:p text:style-name="Contents_20_${block.level}">${esc(inlineToPlainText(block.inlines))}</text:p>`;
268
+ }
269
+ return '<text:table-of-content text:style-name="Sect_Toc" text:protected="true" text:name="Contents">'
270
+ + '<text:table-of-content-source text:outline-level="4" text:use-outline-level="true">'
271
+ + '<text:index-title-template text:style-name="Contents_20_Heading">Contents</text:index-title-template>'
272
+ + templates
273
+ + '</text:table-of-content-source>'
274
+ + `<text:index-body>${body}</text:index-body>`
275
+ + '</text:table-of-content>';
276
+ }
277
+ // ── Style sheets ─────────────────────────────────────────────────────────────
278
+ const CONTENT_NS = [
279
+ 'xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"',
280
+ 'xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"',
281
+ 'xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"',
282
+ 'xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"',
283
+ 'xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"',
284
+ 'xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"',
285
+ 'xmlns:xlink="http://www.w3.org/1999/xlink"',
286
+ 'xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"',
287
+ 'xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"',
288
+ 'xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"',
289
+ 'xmlns:dc="http://purl.org/dc/elements/1.1/"',
290
+ ].join(' ');
291
+ function fontFaces(brand) {
292
+ return '<office:font-face-decls>'
293
+ + `<style:font-face style:name="${esc(brand.font)}" svg:font-family="${esc(brand.font)}"/>`
294
+ + `<style:font-face style:name="${MONO_FONT}" svg:font-family="${MONO_FONT}" style:font-family-generic="modern" style:font-pitch="fixed"/>`
295
+ + '</office:font-face-decls>';
296
+ }
297
+ /** Named styles — the ones a user sees in the sidebar and can restyle. */
298
+ function stylesXml(brand) {
299
+ const base = `style:font-name="${esc(brand.font)}" fo:font-size="${AVA_BODY_SIZE_PDF}pt" fo:color="${hex(brand.bodyColor)}"`;
300
+ const heading = (name, level, size) => `<style:style style:name="${name}" style:family="paragraph" style:parent-style-name="Standard" style:default-outline-level="${level}">`
301
+ + `<style:paragraph-properties fo:margin-top="${level === 1 ? '0.5' : '0.35'}cm" fo:margin-bottom="0.2cm" fo:keep-with-next="always"/>`
302
+ + `<style:text-properties style:font-name="${esc(brand.font)}" fo:font-size="${size}pt" fo:font-weight="bold" fo:color="${hex(brand.headingColor)}"/>`
303
+ + '</style:style>';
304
+ return '<?xml version="1.0" encoding="UTF-8"?>'
305
+ + `<office:document-styles ${CONTENT_NS} office:version="1.3">`
306
+ + fontFaces(brand)
307
+ + '<office:styles>'
308
+ + `<style:style style:name="Standard" style:family="paragraph" style:class="text">`
309
+ + '<style:paragraph-properties fo:margin-bottom="0.25cm" fo:line-height="130%"/>'
310
+ + `<style:text-properties ${base}/></style:style>`
311
+ + '<style:style style:name="Title" style:family="paragraph" style:parent-style-name="Standard">'
312
+ + '<style:paragraph-properties fo:text-align="center" fo:margin-bottom="0.15cm"/>'
313
+ + `<style:text-properties style:font-name="${esc(brand.font)}" fo:font-size="${AVA_TITLE_SIZE_PDF}pt" fo:font-weight="bold" fo:color="${hex(brand.headingColor)}"/></style:style>`
314
+ + '<style:style style:name="Subtitle" style:family="paragraph" style:parent-style-name="Standard">'
315
+ + '<style:paragraph-properties fo:text-align="center" fo:margin-bottom="0.4cm"/>'
316
+ + `<style:text-properties fo:font-size="${AVA_SUBTITLE_SIZE_PDF}pt" fo:font-style="italic" fo:color="${hex(brand.subtitleColor)}"/></style:style>`
317
+ + heading('Heading_20_1', 1, AVA_H1_SIZE_PDF)
318
+ + heading('Heading_20_2', 2, AVA_H2_SIZE_PDF)
319
+ + heading('Heading_20_3', 3, AVA_H3_SIZE_PDF)
320
+ + heading('Heading_20_4', 4, AVA_H4_SIZE_PDF)
321
+ + '<style:style style:name="Quotations" style:family="paragraph" style:parent-style-name="Standard">'
322
+ + `<style:paragraph-properties fo:margin-left="0.8cm" fo:margin-right="0.4cm" fo:padding-left="0.3cm" fo:border-left="2pt solid ${hex(CALLOUT_COLORS.quote.bar)}" fo:border-right="none" fo:border-top="none" fo:border-bottom="none"/>`
323
+ + `<style:text-properties fo:font-style="italic" fo:color="${hex(brand.subtitleColor)}"/></style:style>`
324
+ + '<style:style style:name="Preformatted_20_Text" style:family="paragraph" style:parent-style-name="Standard">'
325
+ + '<style:paragraph-properties fo:margin-bottom="0cm" fo:line-height="115%"/>'
326
+ + `<style:text-properties style:font-name="${MONO_FONT}" fo:font-size="9pt"/></style:style>`
327
+ + '<style:style style:name="Footnote" style:family="paragraph" style:parent-style-name="Standard">'
328
+ + '<style:paragraph-properties fo:margin-bottom="0cm"/>'
329
+ + `<style:text-properties fo:font-size="9pt" fo:color="${hex(brand.subtitleColor)}"/></style:style>`
330
+ + '<style:style style:name="Contents_20_Heading" style:family="paragraph" style:parent-style-name="Standard">'
331
+ + '<style:paragraph-properties fo:margin-top="0.4cm" fo:margin-bottom="0.25cm"/>'
332
+ + `<style:text-properties fo:font-size="${AVA_H1_SIZE_PDF}pt" fo:font-weight="bold" fo:color="${hex(brand.headingColor)}"/></style:style>`
333
+ + [1, 2, 3, 4].map((l) => `<style:style style:name="Contents_20_${l}" style:family="paragraph" style:parent-style-name="Standard">`
334
+ + `<style:paragraph-properties fo:margin-left="${(l - 1) * 0.5}cm" fo:margin-bottom="0.1cm">`
335
+ + '<style:tab-stops><style:tab-stop style:position="16.5cm" style:type="right" style:leader-style="dotted" style:leader-text="."/></style:tab-stops>'
336
+ + '</style:paragraph-properties></style:style>').join('')
337
+ + '</office:styles>'
338
+ + '<office:automatic-styles>'
339
+ + '<style:page-layout style:name="PL1"><style:page-layout-properties '
340
+ + 'fo:page-width="21.001cm" fo:page-height="29.7cm" style:print-orientation="portrait" '
341
+ + 'fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2.25cm" fo:margin-right="2.25cm"/></style:page-layout>'
342
+ + '</office:automatic-styles>'
343
+ + '<office:master-styles><style:master-page style:name="Standard" style:page-layout-name="PL1"/></office:master-styles>'
344
+ + '</office:document-styles>';
345
+ }
346
+ /** Automatic styles — the machine-generated ones the walk above references. */
347
+ function automaticStyles(brand) {
348
+ const span = (name, props) => `<style:style style:name="${name}" style:family="text"><style:text-properties ${props}/></style:style>`;
349
+ const callouts = Object.entries(CALLOUT_COLORS).map(([variant, c]) => `<style:style style:name="P_Callout_${variant}" style:family="paragraph" style:parent-style-name="Standard">`
350
+ + `<style:paragraph-properties fo:background-color="${hex(c.fill)}" fo:padding="0.15cm" fo:margin-bottom="0cm" `
351
+ + `fo:border-left="3pt solid ${hex(c.bar)}" fo:border-right="none" fo:border-top="none" fo:border-bottom="none"/>`
352
+ + '</style:style>'
353
+ + `<style:style style:name="P_CalloutLabel_${variant}" style:family="paragraph" style:parent-style-name="Standard">`
354
+ + `<style:paragraph-properties fo:background-color="${hex(c.fill)}" fo:padding="0.15cm" fo:padding-bottom="0cm" fo:margin-top="0.25cm" fo:margin-bottom="0cm" `
355
+ + `fo:border-left="3pt solid ${hex(c.bar)}" fo:border-right="none" fo:border-top="none" fo:border-bottom="none" fo:keep-with-next="always"/>`
356
+ + `<style:text-properties fo:font-weight="bold" fo:color="${hex(c.bar)}"/></style:style>`).join('');
357
+ return span('T_Bold', 'fo:font-weight="bold"')
358
+ + span('T_Italic', 'fo:font-style="italic"')
359
+ + span('T_BoldItalic', 'fo:font-weight="bold" fo:font-style="italic"')
360
+ + span('T_Code', `style:font-name="${MONO_FONT}" fo:font-size="9.5pt" fo:background-color="#f1f5f9"`)
361
+ + span('T_Link', `fo:color="${hex(AVA_LINK_COLOR)}" style:text-underline-style="solid" style:text-underline-width="auto"`)
362
+ + '<style:style style:name="P_Code" style:family="paragraph" style:parent-style-name="Preformatted_20_Text">'
363
+ + '<style:paragraph-properties fo:background-color="#f8fafc" fo:padding-left="0.2cm" fo:padding-right="0.2cm"/></style:style>'
364
+ + '<style:style style:name="P_ListItem" style:family="paragraph" style:parent-style-name="Standard">'
365
+ + '<style:paragraph-properties fo:margin-bottom="0.1cm"/></style:style>'
366
+ + '<style:style style:name="P_PageBreak" style:family="paragraph" style:parent-style-name="Standard">'
367
+ + '<style:paragraph-properties fo:break-before="page"/></style:style>'
368
+ + '<style:style style:name="P_Hr" style:family="paragraph" style:parent-style-name="Standard">'
369
+ + '<style:paragraph-properties fo:border-bottom="1pt solid #d4d4d8" fo:border-top="none" fo:border-left="none" fo:border-right="none" fo:margin-top="0.3cm" fo:margin-bottom="0.3cm"/></style:style>'
370
+ + '<style:style style:name="P_Image" style:family="paragraph" style:parent-style-name="Standard">'
371
+ + '<style:paragraph-properties fo:text-align="center"/></style:style>'
372
+ + '<style:style style:name="P_ImageMissing" style:family="paragraph" style:parent-style-name="Standard">'
373
+ + `<style:paragraph-properties fo:text-align="center"/><style:text-properties fo:font-style="italic" fo:color="${hex(brand.subtitleColor)}"/></style:style>`
374
+ + '<style:style style:name="Fr_Image" style:family="graphic">'
375
+ + '<style:graphic-properties style:vertical-pos="top" style:vertical-rel="baseline" fo:border="none"/></style:style>'
376
+ // Tables: relative column widths so any column count fits the page.
377
+ + '<style:style style:name="Ta_Doc" style:family="table">'
378
+ + '<style:table-properties style:rel-width="100%" table:align="margins" fo:margin-top="0.2cm" fo:margin-bottom="0.3cm"/></style:style>'
379
+ + '<style:style style:name="Co_Doc" style:family="table-column">'
380
+ + '<style:table-column-properties style:rel-column-width="1*"/></style:style>'
381
+ + '<style:style style:name="Ce_Head" style:family="table-cell">'
382
+ + `<style:table-cell-properties fo:background-color="${hex(CALLOUT_COLORS.note.fill)}" fo:padding="0.12cm" fo:border="0.5pt solid #d4d4d8"/></style:style>`
383
+ + '<style:style style:name="Ce_Body" style:family="table-cell">'
384
+ + '<style:table-cell-properties fo:padding="0.12cm" fo:border="0.5pt solid #e4e4e7"/></style:style>'
385
+ + '<style:style style:name="P_CellHead" style:family="paragraph" style:parent-style-name="Standard">'
386
+ + `<style:paragraph-properties fo:margin-bottom="0cm"/><style:text-properties fo:font-weight="bold" fo:color="${hex(brand.headingColor)}"/></style:style>`
387
+ + ['P_Cell:start', 'P_CellCenter:center', 'P_CellRight:end'].map((pair) => {
388
+ const [name, align] = pair.split(':');
389
+ return `<style:style style:name="${name}" style:family="paragraph" style:parent-style-name="Standard">`
390
+ + `<style:paragraph-properties fo:text-align="${align}" fo:margin-bottom="0cm"/></style:style>`;
391
+ }).join('')
392
+ + callouts
393
+ // Lists: four levels is what the parser can produce.
394
+ + listStyle('L_Bullet', false)
395
+ + listStyle('L_Order', true)
396
+ + '<style:style style:name="Sect_Toc" style:family="section"/>';
397
+ }
398
+ function listStyle(name, ordered) {
399
+ let out = `<text:list-style style:name="${name}">`;
400
+ const bullets = ['•', '◦', '▪', '·'];
401
+ for (let level = 1; level <= 4; level++) {
402
+ const indent = `<style:list-level-properties text:list-level-position-and-space-mode="label-alignment">`
403
+ + `<style:list-level-label-alignment text:label-followed-by="listtab" fo:text-indent="-0.5cm" fo:margin-left="${level * 0.65}cm"/>`
404
+ + '</style:list-level-properties>';
405
+ out += ordered
406
+ ? `<text:list-level-style-number text:level="${level}" style:num-suffix="." style:num-format="1">${indent}</text:list-level-style-number>`
407
+ : `<text:list-level-style-bullet text:level="${level}" text:bullet-char="${bullets[level - 1]}">${indent}</text:list-level-style-bullet>`;
408
+ }
409
+ return out + '</text:list-style>';
410
+ }
411
+ // ── Package assembly ─────────────────────────────────────────────────────────
412
+ function metaXml(model) {
413
+ const m = model.meta;
414
+ return '<?xml version="1.0" encoding="UTF-8"?>'
415
+ + `<office:document-meta ${CONTENT_NS} office:version="1.3"><office:meta>`
416
+ + '<meta:generator>Ava Supernova</meta:generator>'
417
+ + (m.title ? `<dc:title>${esc(m.title)}</dc:title>` : '')
418
+ + (m.subtitle ? `<dc:subject>${esc(m.subtitle)}</dc:subject>` : '')
419
+ + (m.author ? `<meta:initial-creator>${esc(m.author)}</meta:initial-creator><dc:creator>${esc(m.author)}</dc:creator>` : '')
420
+ + (m.date ? `<dc:date>${esc(m.date)}</dc:date>` : '')
421
+ + '</office:meta></office:document-meta>';
422
+ }
423
+ function manifestXml(mimetype, pictures) {
424
+ const entry = (path, type) => `<manifest:file-entry manifest:full-path="${esc(path)}" manifest:media-type="${type}"/>`;
425
+ return '<?xml version="1.0" encoding="UTF-8"?>'
426
+ + '<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" manifest:version="1.3">'
427
+ + `<manifest:file-entry manifest:full-path="/" manifest:version="1.3" manifest:media-type="${mimetype}"/>`
428
+ + entry('content.xml', 'text/xml')
429
+ + entry('styles.xml', 'text/xml')
430
+ + entry('meta.xml', 'text/xml')
431
+ + pictures.map((p) => entry(p.name, p.mime)).join('')
432
+ + '</manifest:manifest>';
433
+ }
434
+ function pack(mimetype, content, styles, meta, pictures, modified) {
435
+ const entries = [
436
+ // Must be first and uncompressed — a reader sniffs the type from the
437
+ // stream's opening bytes without unpacking anything.
438
+ { name: 'mimetype', data: Buffer.from(mimetype, 'ascii'), store: true },
439
+ { name: 'META-INF/manifest.xml', data: Buffer.from(manifestXml(mimetype, pictures), 'utf8') },
440
+ { name: 'content.xml', data: Buffer.from(content, 'utf8') },
441
+ { name: 'styles.xml', data: Buffer.from(styles, 'utf8') },
442
+ { name: 'meta.xml', data: Buffer.from(meta, 'utf8') },
443
+ ...pictures.map((p) => ({ name: p.name, data: p.data })),
444
+ ];
445
+ return zipSync(entries, modified);
446
+ }
447
+ function newCtx(model, opts) {
448
+ const footnoteNumbers = new Map();
449
+ let n = 1;
450
+ for (const id of Object.keys(model.meta.footnotes ?? {}))
451
+ footnoteNumbers.set(id, n++);
452
+ return {
453
+ brand: resolveBrand(model.meta.brand),
454
+ baseDir: opts.baseDir,
455
+ footnoteNumbers,
456
+ footnotes: model.meta.footnotes ?? {},
457
+ pictures: [],
458
+ tableCount: 0,
459
+ };
460
+ }
461
+ // ── .odt ─────────────────────────────────────────────────────────────────────
462
+ export const ODT_MIMETYPE = 'application/vnd.oasis.opendocument.text';
463
+ export async function renderOdt(model, opts = {}) {
464
+ const ctx = newCtx(model, opts);
465
+ const { brand } = ctx;
466
+ let body = '';
467
+ if (model.meta.title)
468
+ body += `<text:p text:style-name="Title">${textNode(model.meta.title)}</text:p>`;
469
+ if (model.meta.subtitle)
470
+ body += `<text:p text:style-name="Subtitle">${textNode(model.meta.subtitle)}</text:p>`;
471
+ const byline = [model.meta.author, model.meta.date].filter(Boolean).join(' · ');
472
+ if (byline)
473
+ body += `<text:p text:style-name="Subtitle">${textNode(byline)}</text:p>`;
474
+ // A front-matter `toc: true` means the same thing as a `toc` block, but only
475
+ // when the document did not already place one itself.
476
+ if (model.meta.toc && !model.blocks.some((b) => b.type === 'toc'))
477
+ body += tocXml(model);
478
+ body += await blocksXml(ctx, model.blocks, model);
479
+ const content = '<?xml version="1.0" encoding="UTF-8"?>'
480
+ + `<office:document-content ${CONTENT_NS} office:version="1.3">`
481
+ + fontFaces(brand)
482
+ + `<office:automatic-styles>${automaticStyles(brand)}</office:automatic-styles>`
483
+ + `<office:body><office:text>${body}</office:text></office:body>`
484
+ + '</office:document-content>';
485
+ return pack(ODT_MIMETYPE, content, stylesXml(brand), metaXml(model), ctx.pictures, opts.modified);
486
+ }
487
+ // ── .ods ─────────────────────────────────────────────────────────────────────
488
+ export const ODS_MIMETYPE = 'application/vnd.oasis.opendocument.spreadsheet';
489
+ /** Sheet names cannot carry these, and readers cap the length around 31. */
490
+ function sheetName(raw, used) {
491
+ const name = raw.replace(/[[\]*?:/\\]/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 28) || 'Sheet';
492
+ let candidate = name;
493
+ let n = 2;
494
+ while (used.has(candidate.toLowerCase()))
495
+ candidate = `${name} ${n++}`;
496
+ used.add(candidate.toLowerCase());
497
+ return candidate;
498
+ }
499
+ /**
500
+ * Cells that hold a number become numeric cells, not text — that is the whole
501
+ * difference between a spreadsheet you can sum and a grid of strings. Anything
502
+ * with stray characters stays a string, so "12 units" is not silently 12.
503
+ */
504
+ function numericValue(text) {
505
+ const trimmed = text.trim().replace(/,/g, '');
506
+ if (!trimmed || !/^[-+]?(\d+\.?\d*|\.\d+)([eE][-+]?\d+)?$/.test(trimmed))
507
+ return null;
508
+ const value = Number(trimmed);
509
+ return Number.isFinite(value) ? value : null;
510
+ }
511
+ function sheetCell(ctx, inlines, header) {
512
+ const plain = inlines ? inlineToPlainText(inlines) : '';
513
+ const style = header ? 'Ce_Head' : 'Ce_Body';
514
+ const num = header ? null : numericValue(plain);
515
+ if (num !== null) {
516
+ return `<table:table-cell table:style-name="Ce_Num" office:value-type="float" office:value="${num}">`
517
+ + `<text:p>${textNode(plain)}</text:p></table:table-cell>`;
518
+ }
519
+ const body = inlines ? inlineXml(ctx, inlines) : '';
520
+ return `<table:table-cell table:style-name="${style}" office:value-type="string">`
521
+ + `<text:p>${body}</text:p></table:table-cell>`;
522
+ }
523
+ export async function renderOds(model, opts = {}) {
524
+ const ctx = newCtx(model, opts);
525
+ const { brand } = ctx;
526
+ const used = new Set();
527
+ // Every table becomes a sheet, named after the heading above it — which is
528
+ // how the document already labels its tables for a reader.
529
+ let heading = model.meta.title ?? '';
530
+ let sheets = '';
531
+ let count = 0;
532
+ for (const block of model.blocks) {
533
+ if (block.type === 'heading') {
534
+ heading = inlineToPlainText(block.inlines);
535
+ continue;
536
+ }
537
+ if (block.type !== 'table')
538
+ continue;
539
+ count++;
540
+ const cols = Math.max(block.headers.length, ...block.rows.map((r) => r.length), 1);
541
+ let rows = '';
542
+ if (block.headers.length) {
543
+ rows += '<table:table-row>';
544
+ for (let i = 0; i < cols; i++)
545
+ rows += sheetCell(ctx, block.headers[i], true);
546
+ rows += '</table:table-row>';
547
+ }
548
+ for (const row of block.rows) {
549
+ rows += '<table:table-row>';
550
+ for (let i = 0; i < cols; i++)
551
+ rows += sheetCell(ctx, row[i], false);
552
+ rows += '</table:table-row>';
553
+ }
554
+ sheets += `<table:table table:name="${esc(sheetName(heading || `Sheet ${count}`, used))}" table:style-name="Ta_Sheet">`
555
+ + `<table:table-column table:style-name="Co_Sheet" table:number-columns-repeated="${cols}" table:default-cell-style-name="Default"/>`
556
+ + rows + '</table:table>';
557
+ }
558
+ // No tables at all — a spreadsheet of nothing helps nobody, so lay the prose
559
+ // out one block per row rather than handing back an empty grid.
560
+ if (count === 0) {
561
+ let rows = '';
562
+ for (const block of model.blocks) {
563
+ const text = blockPlainText(block);
564
+ if (text === null)
565
+ continue;
566
+ rows += `<table:table-row>${sheetCell(ctx, text ? [{ type: 'text', text }] : undefined, block.type === 'heading')}</table:table-row>`;
567
+ }
568
+ sheets += `<table:table table:name="${esc(sheetName(model.meta.title || 'Document', used))}" table:style-name="Ta_Sheet">`
569
+ + '<table:table-column table:style-name="Co_Sheet" table:default-cell-style-name="Default"/>'
570
+ + rows + '</table:table>';
571
+ }
572
+ const content = '<?xml version="1.0" encoding="UTF-8"?>'
573
+ + `<office:document-content ${CONTENT_NS} office:version="1.3">`
574
+ + fontFaces(brand)
575
+ + '<office:automatic-styles>'
576
+ + automaticStyles(brand)
577
+ + '<style:style style:name="Ta_Sheet" style:family="table"><style:table-properties table:display="true"/></style:style>'
578
+ + '<style:style style:name="Co_Sheet" style:family="table-column"><style:table-column-properties style:column-width="4cm"/></style:style>'
579
+ + '<style:style style:name="Ce_Num" style:family="table-cell"><style:paragraph-properties fo:text-align="end"/></style:style>'
580
+ + '</office:automatic-styles>'
581
+ + `<office:body><office:spreadsheet>${sheets}</office:spreadsheet></office:body>`
582
+ + '</office:document-content>';
583
+ return pack(ODS_MIMETYPE, content, stylesXml(brand), metaXml(model), ctx.pictures, opts.modified);
584
+ }
585
+ /** Flatten a block to one line for the spreadsheet fallback. null = skip. */
586
+ function blockPlainText(block) {
587
+ switch (block.type) {
588
+ case 'heading':
589
+ case 'paragraph':
590
+ return inlineToPlainText(block.inlines);
591
+ case 'code':
592
+ return block.text;
593
+ case 'list':
594
+ return block.items.map((i) => inlineToPlainText(i.inlines)).join('; ');
595
+ case 'blockquote':
596
+ case 'callout':
597
+ return block.blocks.map(blockPlainText).filter((t) => t !== null).join(' ');
598
+ case 'image':
599
+ return block.alt ?? null;
600
+ default:
601
+ return null;
602
+ }
603
+ }
604
+ //# sourceMappingURL=render-odf.js.map