@bendyline/docblocks-react 1.1.2 → 2.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 (225) hide show
  1. package/README.md +37 -5
  2. package/dist/ExportDialog-7LDIXJZ3.js +10 -0
  3. package/dist/ExportDialog-7LDIXJZ3.js.map +1 -0
  4. package/dist/ExportToolbarControls-GHAOJMH6.js +508 -0
  5. package/dist/ExportToolbarControls-GHAOJMH6.js.map +1 -0
  6. package/dist/GitToolbarControl-GNMRVKWQ.js +136 -0
  7. package/dist/GitToolbarControl-GNMRVKWQ.js.map +1 -0
  8. package/dist/GitUI-VHOBQY43.js +1133 -0
  9. package/dist/GitUI-VHOBQY43.js.map +1 -0
  10. package/dist/LazyEditorShell-EF2BWRXU.js +6 -0
  11. package/dist/LazyEditorShell-EF2BWRXU.js.map +1 -0
  12. package/dist/ShareDialog-63ENBEDZ.js +213 -0
  13. package/dist/ShareDialog-63ENBEDZ.js.map +1 -0
  14. package/dist/chunk-4XXSWN3T.js +13 -0
  15. package/dist/chunk-4XXSWN3T.js.map +1 -0
  16. package/dist/chunk-57ZJJOC5.js +89 -0
  17. package/dist/chunk-57ZJJOC5.js.map +1 -0
  18. package/dist/chunk-5A4KEWKT.js +97 -0
  19. package/dist/chunk-5A4KEWKT.js.map +1 -0
  20. package/dist/chunk-DD4VFJQI.js +51 -0
  21. package/dist/chunk-DD4VFJQI.js.map +1 -0
  22. package/dist/chunk-FEWQUQML.js +256 -0
  23. package/dist/chunk-FEWQUQML.js.map +1 -0
  24. package/dist/chunk-FMGTHSTX.js +110 -0
  25. package/dist/chunk-FMGTHSTX.js.map +1 -0
  26. package/dist/chunk-UNYZQXWR.js +324 -0
  27. package/dist/chunk-UNYZQXWR.js.map +1 -0
  28. package/dist/chunk-VHDP4IPL.js +70 -0
  29. package/dist/chunk-VHDP4IPL.js.map +1 -0
  30. package/dist/chunk-ZI56BNB7.js +270 -0
  31. package/dist/chunk-ZI56BNB7.js.map +1 -0
  32. package/dist/editor.d.ts +11 -0
  33. package/dist/editor.js +9 -0
  34. package/dist/editor.js.map +1 -0
  35. package/dist/export/index.d.ts +93 -0
  36. package/dist/export/index.js +27 -0
  37. package/dist/export/index.js.map +1 -0
  38. package/dist/index.d.ts +346 -18
  39. package/dist/index.js +6169 -18
  40. package/dist/index.js.map +1 -1
  41. package/dist/run-export-ICTZITXC.js +10 -0
  42. package/dist/run-export-ICTZITXC.js.map +1 -0
  43. package/dist/settings/index.d.ts +36 -0
  44. package/dist/settings/index.js +14 -0
  45. package/dist/settings/index.js.map +1 -0
  46. package/package.json +33 -13
  47. package/src/styles/docblocks.css +2738 -132
  48. package/dist/AppMenu/AppMenu.d.ts +0 -26
  49. package/dist/AppMenu/AppMenu.d.ts.map +0 -1
  50. package/dist/AppMenu/AppMenu.js +0 -28
  51. package/dist/AppMenu/AppMenu.js.map +0 -1
  52. package/dist/AppMenu/index.d.ts +0 -3
  53. package/dist/AppMenu/index.d.ts.map +0 -1
  54. package/dist/AppMenu/index.js +0 -2
  55. package/dist/AppMenu/index.js.map +0 -1
  56. package/dist/DocBlocksShell/DocBlocksShell.d.ts +0 -36
  57. package/dist/DocBlocksShell/DocBlocksShell.d.ts.map +0 -1
  58. package/dist/DocBlocksShell/DocBlocksShell.js +0 -1351
  59. package/dist/DocBlocksShell/DocBlocksShell.js.map +0 -1
  60. package/dist/DocBlocksShell/index.d.ts +0 -3
  61. package/dist/DocBlocksShell/index.d.ts.map +0 -1
  62. package/dist/DocBlocksShell/index.js +0 -2
  63. package/dist/DocBlocksShell/index.js.map +0 -1
  64. package/dist/Export/ExportDialog.d.ts +0 -16
  65. package/dist/Export/ExportDialog.d.ts.map +0 -1
  66. package/dist/Export/ExportDialog.js +0 -158
  67. package/dist/Export/ExportDialog.js.map +0 -1
  68. package/dist/Export/ExportToolbarControls.d.ts +0 -17
  69. package/dist/Export/ExportToolbarControls.d.ts.map +0 -1
  70. package/dist/Export/ExportToolbarControls.js +0 -200
  71. package/dist/Export/ExportToolbarControls.js.map +0 -1
  72. package/dist/Export/export-options.d.ts +0 -53
  73. package/dist/Export/export-options.d.ts.map +0 -1
  74. package/dist/Export/export-options.js +0 -49
  75. package/dist/Export/export-options.js.map +0 -1
  76. package/dist/Export/index.d.ts +0 -6
  77. package/dist/Export/index.d.ts.map +0 -1
  78. package/dist/Export/index.js +0 -3
  79. package/dist/Export/index.js.map +0 -1
  80. package/dist/Export/run-export.d.ts +0 -8
  81. package/dist/Export/run-export.d.ts.map +0 -1
  82. package/dist/Export/run-export.js +0 -296
  83. package/dist/Export/run-export.js.map +0 -1
  84. package/dist/Export/transform-summaries.d.ts +0 -7
  85. package/dist/Export/transform-summaries.d.ts.map +0 -1
  86. package/dist/Export/transform-summaries.js +0 -6
  87. package/dist/Export/transform-summaries.js.map +0 -1
  88. package/dist/FileExplorer/FileExplorer.d.ts +0 -21
  89. package/dist/FileExplorer/FileExplorer.d.ts.map +0 -1
  90. package/dist/FileExplorer/FileExplorer.js +0 -140
  91. package/dist/FileExplorer/FileExplorer.js.map +0 -1
  92. package/dist/FileExplorer/FileTreeNode.d.ts +0 -18
  93. package/dist/FileExplorer/FileTreeNode.d.ts.map +0 -1
  94. package/dist/FileExplorer/FileTreeNode.js +0 -112
  95. package/dist/FileExplorer/FileTreeNode.js.map +0 -1
  96. package/dist/FileExplorer/index.d.ts +0 -6
  97. package/dist/FileExplorer/index.d.ts.map +0 -1
  98. package/dist/FileExplorer/index.js +0 -4
  99. package/dist/FileExplorer/index.js.map +0 -1
  100. package/dist/FileExplorer/useFileTree.d.ts +0 -35
  101. package/dist/FileExplorer/useFileTree.d.ts.map +0 -1
  102. package/dist/FileExplorer/useFileTree.js +0 -124
  103. package/dist/FileExplorer/useFileTree.js.map +0 -1
  104. package/dist/WorkspacePicker/WorkspacePicker.d.ts +0 -17
  105. package/dist/WorkspacePicker/WorkspacePicker.d.ts.map +0 -1
  106. package/dist/WorkspacePicker/WorkspacePicker.js +0 -63
  107. package/dist/WorkspacePicker/WorkspacePicker.js.map +0 -1
  108. package/dist/WorkspacePicker/WorkspaceSettingsButton.d.ts +0 -11
  109. package/dist/WorkspacePicker/WorkspaceSettingsButton.d.ts.map +0 -1
  110. package/dist/WorkspacePicker/WorkspaceSettingsButton.js +0 -27
  111. package/dist/WorkspacePicker/WorkspaceSettingsButton.js.map +0 -1
  112. package/dist/WorkspacePicker/WorkspaceSettingsDialog.d.ts +0 -20
  113. package/dist/WorkspacePicker/WorkspaceSettingsDialog.d.ts.map +0 -1
  114. package/dist/WorkspacePicker/WorkspaceSettingsDialog.js +0 -36
  115. package/dist/WorkspacePicker/WorkspaceSettingsDialog.js.map +0 -1
  116. package/dist/WorkspacePicker/index.d.ts +0 -3
  117. package/dist/WorkspacePicker/index.d.ts.map +0 -1
  118. package/dist/WorkspacePicker/index.js +0 -2
  119. package/dist/WorkspacePicker/index.js.map +0 -1
  120. package/dist/hooks/index.d.ts +0 -2
  121. package/dist/hooks/index.d.ts.map +0 -1
  122. package/dist/hooks/index.js +0 -2
  123. package/dist/hooks/index.js.map +0 -1
  124. package/dist/hooks/useAutoSave.d.ts +0 -15
  125. package/dist/hooks/useAutoSave.d.ts.map +0 -1
  126. package/dist/hooks/useAutoSave.js +0 -83
  127. package/dist/hooks/useAutoSave.js.map +0 -1
  128. package/dist/icons.d.ts +0 -7
  129. package/dist/icons.d.ts.map +0 -1
  130. package/dist/icons.js +0 -27
  131. package/dist/icons.js.map +0 -1
  132. package/dist/index.d.ts.map +0 -1
  133. package/dist/monaco-slim.d.ts +0 -19
  134. package/dist/monaco-slim.d.ts.map +0 -1
  135. package/dist/monaco-slim.js +0 -19
  136. package/dist/monaco-slim.js.map +0 -1
  137. package/dist/preferences/versioning.d.ts +0 -27
  138. package/dist/preferences/versioning.d.ts.map +0 -1
  139. package/dist/preferences/versioning.js +0 -62
  140. package/dist/preferences/versioning.js.map +0 -1
  141. package/src/AppMenu/AppMenu.tsx +0 -264
  142. package/src/AppMenu/index.ts +0 -2
  143. package/src/DocBlocksShell/DocBlocksShell.tsx +0 -1778
  144. package/src/DocBlocksShell/index.ts +0 -2
  145. package/src/Export/ExportDialog.tsx +0 -383
  146. package/src/Export/ExportToolbarControls.tsx +0 -313
  147. package/src/Export/export-options.ts +0 -99
  148. package/src/Export/index.ts +0 -5
  149. package/src/Export/run-export.ts +0 -349
  150. package/src/Export/transform-summaries.ts +0 -14
  151. package/src/FileExplorer/FileExplorer.tsx +0 -282
  152. package/src/FileExplorer/FileTreeNode.tsx +0 -214
  153. package/src/FileExplorer/index.ts +0 -5
  154. package/src/FileExplorer/useFileTree.ts +0 -178
  155. package/src/WorkspacePicker/WorkspacePicker.tsx +0 -137
  156. package/src/WorkspacePicker/WorkspaceSettingsButton.tsx +0 -88
  157. package/src/WorkspacePicker/WorkspaceSettingsDialog.tsx +0 -121
  158. package/src/WorkspacePicker/index.ts +0 -2
  159. package/src/fonts/fonts.css +0 -362
  160. package/src/fonts/licenses/LICENSE-cormorant-garamond.txt +0 -93
  161. package/src/fonts/licenses/LICENSE-crimson-text.txt +0 -93
  162. package/src/fonts/licenses/LICENSE-dm-sans.txt +0 -93
  163. package/src/fonts/licenses/LICENSE-dm-serif-display.txt +0 -93
  164. package/src/fonts/licenses/LICENSE-hanken-grotesk.txt +0 -94
  165. package/src/fonts/licenses/LICENSE-ibm-plex-sans.txt +0 -93
  166. package/src/fonts/licenses/LICENSE-inter.txt +0 -93
  167. package/src/fonts/licenses/LICENSE-jetbrains-mono.txt +0 -93
  168. package/src/fonts/licenses/LICENSE-lora.txt +0 -93
  169. package/src/fonts/licenses/LICENSE-merriweather.txt +0 -93
  170. package/src/fonts/licenses/LICENSE-oswald.txt +0 -93
  171. package/src/fonts/licenses/LICENSE-playfair-display.txt +0 -93
  172. package/src/fonts/licenses/LICENSE-pt-serif.txt +0 -93
  173. package/src/fonts/licenses/LICENSE-roboto.txt +0 -93
  174. package/src/fonts/licenses/LICENSE-source-serif-4.txt +0 -93
  175. package/src/fonts/woff2/cormorant-garamond-400.woff2 +0 -0
  176. package/src/fonts/woff2/cormorant-garamond-700.woff2 +0 -0
  177. package/src/fonts/woff2/crimson-text-400.woff2 +0 -0
  178. package/src/fonts/woff2/crimson-text-400i.woff2 +0 -0
  179. package/src/fonts/woff2/crimson-text-700.woff2 +0 -0
  180. package/src/fonts/woff2/dm-sans-400.woff2 +0 -0
  181. package/src/fonts/woff2/dm-sans-400i.woff2 +0 -0
  182. package/src/fonts/woff2/dm-sans-500.woff2 +0 -0
  183. package/src/fonts/woff2/dm-sans-700.woff2 +0 -0
  184. package/src/fonts/woff2/dm-serif-display-400.woff2 +0 -0
  185. package/src/fonts/woff2/hanken-grotesk-400.woff2 +0 -0
  186. package/src/fonts/woff2/hanken-grotesk-400i.woff2 +0 -0
  187. package/src/fonts/woff2/hanken-grotesk-500.woff2 +0 -0
  188. package/src/fonts/woff2/hanken-grotesk-600.woff2 +0 -0
  189. package/src/fonts/woff2/hanken-grotesk-700.woff2 +0 -0
  190. package/src/fonts/woff2/ibm-plex-sans-400.woff2 +0 -0
  191. package/src/fonts/woff2/ibm-plex-sans-400i.woff2 +0 -0
  192. package/src/fonts/woff2/ibm-plex-sans-500.woff2 +0 -0
  193. package/src/fonts/woff2/ibm-plex-sans-600.woff2 +0 -0
  194. package/src/fonts/woff2/ibm-plex-sans-700.woff2 +0 -0
  195. package/src/fonts/woff2/inter-400.woff2 +0 -0
  196. package/src/fonts/woff2/inter-400i.woff2 +0 -0
  197. package/src/fonts/woff2/inter-500.woff2 +0 -0
  198. package/src/fonts/woff2/inter-600.woff2 +0 -0
  199. package/src/fonts/woff2/inter-700.woff2 +0 -0
  200. package/src/fonts/woff2/jetbrains-mono-400.woff2 +0 -0
  201. package/src/fonts/woff2/jetbrains-mono-400i.woff2 +0 -0
  202. package/src/fonts/woff2/jetbrains-mono-700.woff2 +0 -0
  203. package/src/fonts/woff2/lora-400.woff2 +0 -0
  204. package/src/fonts/woff2/lora-400i.woff2 +0 -0
  205. package/src/fonts/woff2/lora-700.woff2 +0 -0
  206. package/src/fonts/woff2/merriweather-400.woff2 +0 -0
  207. package/src/fonts/woff2/merriweather-700.woff2 +0 -0
  208. package/src/fonts/woff2/oswald-400.woff2 +0 -0
  209. package/src/fonts/woff2/oswald-700.woff2 +0 -0
  210. package/src/fonts/woff2/playfair-display-400.woff2 +0 -0
  211. package/src/fonts/woff2/playfair-display-700.woff2 +0 -0
  212. package/src/fonts/woff2/pt-serif-400.woff2 +0 -0
  213. package/src/fonts/woff2/pt-serif-700.woff2 +0 -0
  214. package/src/fonts/woff2/roboto-400.woff2 +0 -0
  215. package/src/fonts/woff2/roboto-400i.woff2 +0 -0
  216. package/src/fonts/woff2/roboto-700.woff2 +0 -0
  217. package/src/fonts/woff2/source-serif-4-400.woff2 +0 -0
  218. package/src/fonts/woff2/source-serif-4-400i.woff2 +0 -0
  219. package/src/fonts/woff2/source-serif-4-700.woff2 +0 -0
  220. package/src/hooks/index.ts +0 -1
  221. package/src/hooks/useAutoSave.ts +0 -116
  222. package/src/icons.tsx +0 -70
  223. package/src/index.ts +0 -31
  224. package/src/monaco-slim.ts +0 -20
  225. package/src/preferences/versioning.ts +0 -69
@@ -1,2 +0,0 @@
1
- export { DocBlocksShell } from './DocBlocksShell.js';
2
- export type { DocBlocksShellProps } from './DocBlocksShell.js';
@@ -1,383 +0,0 @@
1
- /**
2
- * ExportDialog — modal for choosing export format and options.
3
- */
4
-
5
- import { useState, useCallback, useEffect, useRef } from 'react';
6
- import { getThemeSummaries } from '@bendyline/squisq/schemas';
7
- import type { ExportFormat, ExportOptions, HtmlBundle, HtmlStyle } from './export-options.js';
8
- import { FORMAT_LABELS, saveExportOptions } from './export-options.js';
9
- import { loadTransformStyleSummaries, type ExportSummaryOption } from './transform-summaries.js';
10
-
11
- export interface ExportDialogProps {
12
- /** Initial options (pre-populated from last export). */
13
- initial: ExportOptions;
14
- /** Whether the export is currently running. */
15
- exporting: boolean;
16
- /** Called when the user confirms the export. */
17
- onExport: (options: ExportOptions) => void;
18
- /** Called when the dialog is dismissed. */
19
- onClose: () => void;
20
- }
21
-
22
- const FORMATS: ExportFormat[] = ['pdf', 'docx', 'pptx', 'html', 'md'];
23
-
24
- /** Short labels for the Format chip row. The fuller `FORMAT_LABELS` list
25
- * is still used as a tooltip so users can confirm the file extension. */
26
- const FORMAT_CHIP_LABELS: Record<ExportFormat, string> = {
27
- pdf: 'PDF',
28
- docx: 'Word',
29
- pptx: 'PowerPoint',
30
- html: 'HTML',
31
- md: 'Markdown',
32
- };
33
-
34
- const HTML_STYLE_CHIPS: { key: HtmlStyle; label: string; hint: string }[] = [
35
- {
36
- key: 'plain',
37
- label: 'Plain',
38
- hint: 'A lightweight static HTML document.',
39
- },
40
- {
41
- key: 'rendered',
42
- label: 'Rendered',
43
- hint: 'Renders via SquisqPlayer with themes and playback support.',
44
- },
45
- ];
46
-
47
- const HTML_BUNDLE_CHIPS: { key: HtmlBundle; label: string; hint: string }[] = [
48
- {
49
- key: 'single',
50
- label: 'Single file',
51
- hint: 'One .html file with images embedded as base64 data URIs.',
52
- },
53
- {
54
- key: 'zip',
55
- label: 'ZIP archive',
56
- hint: 'A .zip with index.html plus separate image (and JS) files.',
57
- },
58
- ];
59
-
60
- interface ChipOption<T extends string> {
61
- key: T;
62
- label: string;
63
- title?: string;
64
- }
65
-
66
- function ChipRadioGroup<T extends string>({
67
- name,
68
- value,
69
- options,
70
- onChange,
71
- }: {
72
- name: string;
73
- value: T;
74
- options: ChipOption<T>[];
75
- onChange: (next: T) => void;
76
- }) {
77
- return (
78
- <div className="db-export-chips" role="radiogroup" aria-label={name}>
79
- {options.map((opt) => {
80
- const active = opt.key === value;
81
- return (
82
- <button
83
- key={opt.key}
84
- type="button"
85
- role="radio"
86
- aria-checked={active}
87
- className={`db-export-chip${active ? ' db-export-chip--active' : ''}`}
88
- onClick={() => onChange(opt.key)}
89
- title={opt.title}
90
- >
91
- {opt.label}
92
- </button>
93
- );
94
- })}
95
- </div>
96
- );
97
- }
98
-
99
- export function ExportDialog({ initial, exporting, onExport, onClose }: ExportDialogProps) {
100
- const [format, setFormat] = useState<ExportFormat>(initial.format);
101
- const [themeId, setThemeId] = useState(initial.themeId);
102
- const [transformStyle, setTransformStyle] = useState(initial.transformStyle);
103
- const [pageSize, setPageSize] = useState(initial.pageSize);
104
- const [htmlStyle, setHtmlStyle] = useState<HtmlStyle>(initial.htmlStyle);
105
- const [htmlBundle, setHtmlBundle] = useState<HtmlBundle>(initial.htmlBundle);
106
- const [includeLinkedDocs, setIncludeLinkedDocs] = useState<boolean>(initial.includeLinkedDocs);
107
- const [entryAsIndex, setEntryAsIndex] = useState<boolean>(initial.entryAsIndex);
108
- const [transforms, setTransforms] = useState<ExportSummaryOption[]>([]);
109
- const dialogRef = useRef<HTMLDivElement>(null);
110
-
111
- const themes = getThemeSummaries();
112
-
113
- // Both HTML styles honor themes now: rendered HTML through the
114
- // SquisqPlayer's theme system, plain HTML through squisq's
115
- // `markdownDocToPlainHtml` which emits theme-driven CSS variables
116
- // and Google Fonts links. Show the dropdown for any export format
117
- // whose pipeline actually applies the picked theme.
118
- const showTheme = format === 'docx' || format === 'pdf' || format === 'pptx' || format === 'html';
119
- const showTransform = format === 'pptx';
120
- const showPageSize = format === 'pdf';
121
- const showHtmlOptions = format === 'html';
122
- // Both HTML styles now have a recursive bundle: plain via
123
- // `markdownDocsToPlainHtmlBundle`, rendered via
124
- // `markdownDocsToHtmlBundle` (which rewrites Doc-tree links to
125
- // `.html` before SquisqPlayer serialization). So the option applies
126
- // to any HTML export.
127
- const showIncludeLinked = format === 'html';
128
- // Multi-doc output is inherently a directory tree, so when recursion
129
- // is on the Bundle row would only confuse — ZIP is the only valid
130
- // packaging. Hide the chip row and surface the implication in a hint.
131
- const showHtmlBundle = showHtmlOptions && !includeLinkedDocs;
132
- // `entryAsIndex` is only honored in pipelines where we control the
133
- // output filename ourselves:
134
- // • single-file HTML downloads → renames `<doc>.html` → `index.html`
135
- // • recursive bundles → passed through to squisq's bundle helpers
136
- // In the single-doc ZIP path, squisq's `docToHtmlZip` and our plain-
137
- // HTML zip writer always emit `index.html` inside the archive, so the
138
- // checkbox has no observable effect — hide it to avoid surfacing an
139
- // inert control.
140
- const showEntryAsIndex = showHtmlOptions && (htmlBundle === 'single' || includeLinkedDocs);
141
-
142
- useEffect(() => {
143
- if (!showTransform) return;
144
- let cancelled = false;
145
-
146
- loadTransformStyleSummaries()
147
- .then((nextTransforms) => {
148
- if (!cancelled) setTransforms(nextTransforms);
149
- })
150
- .catch(() => {
151
- if (!cancelled) setTransforms([]);
152
- });
153
-
154
- return () => {
155
- cancelled = true;
156
- };
157
- }, [showTransform]);
158
-
159
- const handleExport = useCallback(() => {
160
- const opts: ExportOptions = {
161
- format,
162
- themeId,
163
- transformStyle,
164
- pageSize,
165
- htmlStyle,
166
- htmlBundle,
167
- includeLinkedDocs: showIncludeLinked && includeLinkedDocs,
168
- entryAsIndex: showEntryAsIndex && entryAsIndex,
169
- };
170
- saveExportOptions(opts);
171
- onExport(opts);
172
- }, [
173
- format,
174
- themeId,
175
- transformStyle,
176
- pageSize,
177
- htmlStyle,
178
- htmlBundle,
179
- includeLinkedDocs,
180
- entryAsIndex,
181
- showIncludeLinked,
182
- showEntryAsIndex,
183
- onExport,
184
- ]);
185
-
186
- // Close on Escape
187
- useEffect(() => {
188
- const onKey = (e: KeyboardEvent) => {
189
- if (e.key === 'Escape') onClose();
190
- };
191
- window.addEventListener('keydown', onKey);
192
- return () => window.removeEventListener('keydown', onKey);
193
- }, [onClose]);
194
-
195
- // Close on backdrop click
196
- const handleBackdropClick = useCallback(
197
- (e: React.MouseEvent) => {
198
- if (e.target === e.currentTarget) onClose();
199
- },
200
- [onClose],
201
- );
202
-
203
- return (
204
- <div className="db-dialog-overlay" onClick={handleBackdropClick}>
205
- <div className="db-dialog db-export-dialog" ref={dialogRef}>
206
- <div className="db-dialog-header">
207
- <h2 className="db-dialog-title">Export Document</h2>
208
- <button className="db-dialog-close" onClick={onClose} aria-label="Close">
209
- &times;
210
- </button>
211
- </div>
212
-
213
- <div className="db-dialog-body">
214
- {/* Format — chip radio row for fast switching */}
215
- <div className="db-export-field">
216
- <span className="db-export-label">Format</span>
217
- <ChipRadioGroup
218
- name="Format"
219
- value={format}
220
- options={FORMATS.map((f) => ({
221
- key: f,
222
- label: FORMAT_CHIP_LABELS[f],
223
- title: FORMAT_LABELS[f],
224
- }))}
225
- onChange={setFormat}
226
- />
227
- </div>
228
-
229
- {/* HTML style + bundle (HTML only) — also chip rows */}
230
- {showHtmlOptions && (
231
- <>
232
- <div className="db-export-field">
233
- <span className="db-export-label">Style</span>
234
- <ChipRadioGroup
235
- name="Style"
236
- value={htmlStyle}
237
- options={HTML_STYLE_CHIPS}
238
- onChange={setHtmlStyle}
239
- />
240
- <span className="db-export-hint">
241
- {HTML_STYLE_CHIPS.find((c) => c.key === htmlStyle)?.hint}
242
- </span>
243
- </div>
244
- {showHtmlBundle && (
245
- <div className="db-export-field">
246
- <span className="db-export-label">Bundle</span>
247
- <ChipRadioGroup
248
- name="Bundle"
249
- value={htmlBundle}
250
- options={HTML_BUNDLE_CHIPS}
251
- onChange={setHtmlBundle}
252
- />
253
- <span className="db-export-hint">
254
- {HTML_BUNDLE_CHIPS.find((c) => c.key === htmlBundle)?.hint}
255
- </span>
256
- </div>
257
- )}
258
- {showIncludeLinked && (
259
- <div className="db-export-field">
260
- <label className="db-export-checkbox">
261
- <input
262
- type="checkbox"
263
- checked={includeLinkedDocs}
264
- onChange={(e) => setIncludeLinkedDocs(e.target.checked)}
265
- />
266
- <span>Include linked-to documents</span>
267
- </label>
268
- <span className="db-export-hint">
269
- {includeLinkedDocs
270
- ? 'Follows relative .md links from this page and bundles every reachable document as its own .html in a ZIP. Cross-doc links rewrite from .md to .html.'
271
- : 'Only this document is exported.'}
272
- </span>
273
- </div>
274
- )}
275
- {showEntryAsIndex && (
276
- <div className="db-export-field">
277
- <label className="db-export-checkbox">
278
- <input
279
- type="checkbox"
280
- checked={entryAsIndex}
281
- onChange={(e) => setEntryAsIndex(e.target.checked)}
282
- />
283
- <span>Name entry page index.html</span>
284
- </label>
285
- <span className="db-export-hint">
286
- {entryAsIndex
287
- ? includeLinkedDocs
288
- ? 'Renames this page to index.html in the ZIP and rewrites any cross-doc links pointing back at it. Drops straight into a static-site host.'
289
- : 'Downloads as index.html instead of the document name. Drops straight into a static-site host.'
290
- : 'Exports under the document name.'}
291
- </span>
292
- </div>
293
- )}
294
- </>
295
- )}
296
-
297
- {/* Theme */}
298
- {showTheme && (
299
- <div className="db-export-field">
300
- <label className="db-export-label" htmlFor="db-export-theme">
301
- Theme
302
- </label>
303
- <select
304
- id="db-export-theme"
305
- className="db-export-select"
306
- value={themeId}
307
- onChange={(e) => setThemeId(e.target.value)}
308
- >
309
- {themes.map((t) => (
310
- <option key={t.id} value={t.id}>
311
- {t.name}
312
- </option>
313
- ))}
314
- </select>
315
- <span className="db-export-hint">
316
- {themes.find((t) => t.id === themeId)?.description}
317
- </span>
318
- </div>
319
- )}
320
-
321
- {/* Transform (PPTX only) */}
322
- {showTransform && (
323
- <div className="db-export-field">
324
- <label className="db-export-label" htmlFor="db-export-transform">
325
- Transform
326
- </label>
327
- <select
328
- id="db-export-transform"
329
- className="db-export-select"
330
- value={transformStyle}
331
- onChange={(e) => setTransformStyle(e.target.value)}
332
- >
333
- {transforms.length === 0 ? (
334
- <option value={transformStyle}>Loading...</option>
335
- ) : (
336
- transforms.map((t) => (
337
- <option key={t.id} value={t.id}>
338
- {t.name}
339
- </option>
340
- ))
341
- )}
342
- </select>
343
- <span className="db-export-hint">
344
- {transforms.find((t) => t.id === transformStyle)?.description}
345
- </span>
346
- </div>
347
- )}
348
-
349
- {/* Page size (PDF only) */}
350
- {showPageSize && (
351
- <div className="db-export-field">
352
- <label className="db-export-label" htmlFor="db-export-pagesize">
353
- Page Size
354
- </label>
355
- <select
356
- id="db-export-pagesize"
357
- className="db-export-select"
358
- value={pageSize}
359
- onChange={(e) => setPageSize(e.target.value as 'letter' | 'a4')}
360
- >
361
- <option value="letter">US Letter</option>
362
- <option value="a4">A4</option>
363
- </select>
364
- </div>
365
- )}
366
- </div>
367
-
368
- <div className="db-export-footer">
369
- <button className="db-export-btn db-export-btn--secondary" onClick={onClose}>
370
- Cancel
371
- </button>
372
- <button
373
- className="db-export-btn db-export-btn--primary"
374
- onClick={handleExport}
375
- disabled={exporting}
376
- >
377
- {exporting ? 'Exporting...' : 'Export'}
378
- </button>
379
- </div>
380
- </div>
381
- </div>
382
- );
383
- }
@@ -1,313 +0,0 @@
1
- /**
2
- * ExportToolbarControls — toolbar-right slot content.
3
- *
4
- * Renders a "..." overflow menu on the right side of the toolbar
5
- * containing export actions (document export + video export).
6
- *
7
- * Must be rendered inside <EditorProvider> so useEditorContext() works.
8
- */
9
-
10
- import { useState, useCallback, useEffect, useRef, useMemo, type ComponentType } from 'react';
11
- import { useEditorContext } from '@bendyline/squisq-editor-react';
12
- import { getThemeSummaries } from '@bendyline/squisq/schemas';
13
- import { parseMarkdown } from '@bendyline/squisq/markdown';
14
- import type { ContentContainer } from '@bendyline/squisq/storage';
15
- import type { VideoExportModalProps } from '@bendyline/squisq-video-react';
16
- import type { ExportOptions } from './export-options.js';
17
- import {
18
- DEFAULT_OPTIONS,
19
- FORMAT_EXTENSIONS,
20
- loadLastExportOptions,
21
- saveExportOptions,
22
- } from './export-options.js';
23
- import { ExportDialog } from './ExportDialog.js';
24
- import { runExport } from './run-export.js';
25
- import { loadTransformStyleSummaries, type ExportSummaryOption } from './transform-summaries.js';
26
-
27
- export interface ExportToolbarControlsProps {
28
- /** Currently selected file path — used to derive the download filename. */
29
- selectedFile: string | null;
30
- /** Media container for resolving images during export. */
31
- mediaContainer?: ContentContainer | null;
32
- }
33
-
34
- type ParsedMarkdown = ReturnType<typeof parseMarkdown>;
35
-
36
- interface VideoExportModules {
37
- Modal: ComponentType<VideoExportModalProps>;
38
- markdownToDoc: (doc: ParsedMarkdown) => VideoExportModalProps['doc'];
39
- playerScript: string;
40
- }
41
-
42
- let videoExportModulesPromise: Promise<VideoExportModules> | null = null;
43
-
44
- function loadVideoExportModules(): Promise<VideoExportModules> {
45
- videoExportModulesPromise ??= Promise.all([
46
- import('@bendyline/squisq/doc'),
47
- import('@bendyline/squisq-video-react'),
48
- import('@bendyline/squisq-react/standalone-source'),
49
- ]).then(([docModule, videoModule, playerModule]) => ({
50
- Modal: videoModule.VideoExportModal,
51
- markdownToDoc: docModule.markdownToDoc,
52
- playerScript: playerModule.PLAYER_BUNDLE,
53
- }));
54
- return videoExportModulesPromise;
55
- }
56
-
57
- /** Build the quick-export label from saved options. */
58
- function quickLabel(opts: ExportOptions, transformSummaries: ExportSummaryOption[]): string {
59
- const baseExt = FORMAT_EXTENSIONS[opts.format].toUpperCase().replace('.', '');
60
- // Recursive HTML always emits a ZIP (multi-doc tree), regardless of
61
- // the saved `htmlBundle` value. Applies to both plain and rendered
62
- // styles since both now ship recursive bundle helpers.
63
- const isRecursiveHtml = opts.format === 'html' && opts.includeLinkedDocs;
64
- const ext =
65
- opts.format === 'html' && (opts.htmlBundle === 'zip' || isRecursiveHtml) ? 'ZIP' : baseExt;
66
- const parts: string[] = [];
67
-
68
- if (opts.format === 'html' && opts.htmlStyle === 'rendered') {
69
- parts.push('rendered');
70
- }
71
- if (isRecursiveHtml) {
72
- parts.push('linked docs');
73
- }
74
- // Plain HTML also applies themes now (squisq's `markdownDocToPlainHtml`
75
- // emits theme-driven CSS), so include the theme name in the quick-
76
- // export label for every HTML style — not just rendered.
77
- if (opts.themeId !== 'standard') {
78
- const theme = getThemeSummaries().find((t) => t.id === opts.themeId);
79
- if (theme) parts.push(theme.name);
80
- }
81
- if (opts.format === 'pptx' && opts.transformStyle) {
82
- const transform = transformSummaries.find((t) => t.id === opts.transformStyle);
83
- if (transform) parts.push(transform.name);
84
- }
85
-
86
- if (parts.length > 0) {
87
- return `Export ${ext} with ${parts.join(' + ')}`;
88
- }
89
- return `Export ${ext}`;
90
- }
91
-
92
- export function ExportToolbarControls({
93
- selectedFile,
94
- mediaContainer,
95
- }: ExportToolbarControlsProps) {
96
- const { markdownSource, markdownDoc } = useEditorContext();
97
- const [menuOpen, setMenuOpen] = useState(false);
98
- const [dialogOpen, setDialogOpen] = useState(false);
99
- const [videoModalOpen, setVideoModalOpen] = useState(false);
100
- const [videoLoading, setVideoLoading] = useState(false);
101
- const [videoLoadError, setVideoLoadError] = useState<string | null>(null);
102
- const [videoModules, setVideoModules] = useState<VideoExportModules | null>(null);
103
- const [videoDoc, setVideoDoc] = useState<VideoExportModalProps['doc'] | null>(null);
104
- const [transformSummaries, setTransformSummaries] = useState<ExportSummaryOption[]>([]);
105
- const [exporting, setExporting] = useState(false);
106
- const menuRef = useRef<HTMLDivElement>(null);
107
-
108
- const lastOptions = loadLastExportOptions();
109
-
110
- /** Doc's currently-set squisq theme, pulled from the markdown
111
- * frontmatter. The Document Settings dialog and Theme Customizer
112
- * write the theme under `squisq-theme` (canonical) or `theme`
113
- * (legacy); some older docs persist it as `themeId`. We honor all
114
- * three so an author who set their theme anywhere upstream sees it
115
- * pre-selected in the export dialog. */
116
- const docThemeId = useMemo(() => {
117
- const fm = markdownDoc?.frontmatter as Record<string, unknown> | undefined;
118
- if (!fm) return null;
119
- const candidates = [fm['squisq-theme'], fm['theme'], fm['themeId']];
120
- for (const v of candidates) {
121
- if (typeof v === 'string' && v.trim()) return v.trim();
122
- }
123
- return null;
124
- }, [markdownDoc]);
125
-
126
- /** Options used to populate the export dialog. Layered: built-in
127
- * defaults → user's last-chosen export options (if any) → doc's
128
- * current frontmatter theme (wins). The frontmatter override
129
- * guarantees that "set theme in the editor, then export" pre-selects
130
- * the right theme instead of resurrecting whatever the user picked
131
- * for some unrelated previous doc. */
132
- const dialogInitial = useMemo(() => {
133
- const base = lastOptions ?? DEFAULT_OPTIONS;
134
- return docThemeId ? { ...base, themeId: docThemeId } : base;
135
- }, [lastOptions, docThemeId]);
136
-
137
- // Close menu on outside click
138
- useEffect(() => {
139
- if (!menuOpen) return;
140
- const onPointerDown = (e: PointerEvent) => {
141
- if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
142
- setMenuOpen(false);
143
- }
144
- };
145
- document.addEventListener('pointerdown', onPointerDown);
146
- return () => document.removeEventListener('pointerdown', onPointerDown);
147
- }, [menuOpen]);
148
-
149
- useEffect(() => {
150
- if (
151
- !menuOpen ||
152
- lastOptions?.format !== 'pptx' ||
153
- !lastOptions.transformStyle ||
154
- transformSummaries.length > 0
155
- ) {
156
- return;
157
- }
158
-
159
- let cancelled = false;
160
- loadTransformStyleSummaries()
161
- .then((nextSummaries) => {
162
- if (!cancelled) setTransformSummaries(nextSummaries);
163
- })
164
- .catch(() => {
165
- if (!cancelled) setTransformSummaries([]);
166
- });
167
-
168
- return () => {
169
- cancelled = true;
170
- };
171
- }, [lastOptions?.format, lastOptions?.transformStyle, menuOpen, transformSummaries.length]);
172
-
173
- const handleToggleMenu = useCallback(() => {
174
- setMenuOpen((prev) => !prev);
175
- }, []);
176
-
177
- const handleOpenDialog = useCallback(() => {
178
- setMenuOpen(false);
179
- setDialogOpen(true);
180
- }, []);
181
-
182
- const handleCloseDialog = useCallback(() => {
183
- setDialogOpen(false);
184
- }, []);
185
-
186
- const handleOpenVideoModal = useCallback(async () => {
187
- setMenuOpen(false);
188
- setVideoModalOpen(true);
189
- setVideoLoading(true);
190
- setVideoLoadError(null);
191
-
192
- try {
193
- const modules = videoModules ?? (await loadVideoExportModules());
194
- setVideoModules(modules);
195
- setVideoDoc(modules.markdownToDoc(parseMarkdown(markdownSource)));
196
- } catch {
197
- setVideoLoadError('Video export could not be loaded.');
198
- } finally {
199
- setVideoLoading(false);
200
- }
201
- }, [markdownSource, videoModules]);
202
-
203
- const handleCloseVideoModal = useCallback(() => {
204
- setVideoModalOpen(false);
205
- setVideoDoc(null);
206
- setVideoLoadError(null);
207
- }, []);
208
-
209
- const handleExport = useCallback(
210
- async (opts: ExportOptions) => {
211
- setExporting(true);
212
- try {
213
- await runExport(markdownSource, selectedFile, opts, mediaContainer);
214
- } finally {
215
- setExporting(false);
216
- setDialogOpen(false);
217
- }
218
- },
219
- [markdownSource, selectedFile, mediaContainer],
220
- );
221
-
222
- const handleQuickExport = useCallback(async () => {
223
- if (!lastOptions) return;
224
- setMenuOpen(false);
225
- setExporting(true);
226
- try {
227
- saveExportOptions(lastOptions);
228
- await runExport(markdownSource, selectedFile, lastOptions, mediaContainer);
229
- } finally {
230
- setExporting(false);
231
- }
232
- }, [lastOptions, markdownSource, selectedFile, mediaContainer]);
233
-
234
- const LoadedVideoExportModal = videoModules?.Modal;
235
-
236
- return (
237
- <>
238
- <div className="db-toolbar-menu" ref={menuRef}>
239
- <button
240
- className="db-toolbar-menu-trigger"
241
- onClick={handleToggleMenu}
242
- aria-label="More actions"
243
- title="More actions"
244
- >
245
- &middot;&middot;&middot;
246
- </button>
247
-
248
- {menuOpen && (
249
- <div className="db-toolbar-menu-dropdown">
250
- {lastOptions && (
251
- <button
252
- className="db-toolbar-menu-item"
253
- onClick={handleQuickExport}
254
- disabled={exporting}
255
- >
256
- {quickLabel(lastOptions, transformSummaries)}
257
- </button>
258
- )}
259
- <button className="db-toolbar-menu-item" onClick={handleOpenDialog}>
260
- Export...
261
- </button>
262
- <div className="db-toolbar-menu-divider" />
263
- <button className="db-toolbar-menu-item" onClick={handleOpenVideoModal}>
264
- Export Video...
265
- </button>
266
- </div>
267
- )}
268
- </div>
269
-
270
- {dialogOpen && (
271
- <ExportDialog
272
- initial={dialogInitial}
273
- exporting={exporting}
274
- onExport={handleExport}
275
- onClose={handleCloseDialog}
276
- />
277
- )}
278
-
279
- {videoModalOpen && (videoLoading || videoLoadError) && (
280
- <div className="db-dialog-overlay">
281
- <div className="db-dialog">
282
- <div className="db-dialog-header">
283
- <h2 className="db-dialog-title">Export Video</h2>
284
- <button
285
- className="db-dialog-close"
286
- onClick={handleCloseVideoModal}
287
- aria-label="Close"
288
- >
289
- &times;
290
- </button>
291
- </div>
292
- <div className="db-dialog-body">
293
- <p className="db-export-hint">{videoLoadError ?? 'Loading...'}</p>
294
- </div>
295
- </div>
296
- </div>
297
- )}
298
-
299
- {videoModalOpen &&
300
- videoModules &&
301
- LoadedVideoExportModal &&
302
- videoDoc &&
303
- !videoLoading &&
304
- !videoLoadError && (
305
- <LoadedVideoExportModal
306
- doc={videoDoc}
307
- playerScript={videoModules.playerScript}
308
- onClose={handleCloseVideoModal}
309
- />
310
- )}
311
- </>
312
- );
313
- }