@bendyline/docblocks-react 1.1.0 → 1.1.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.
- package/dist/AppMenu/AppMenu.d.ts +11 -1
- package/dist/AppMenu/AppMenu.d.ts.map +1 -1
- package/dist/AppMenu/AppMenu.js +2 -2
- package/dist/AppMenu/AppMenu.js.map +1 -1
- package/dist/DocBlocksShell/DocBlocksShell.d.ts +21 -1
- package/dist/DocBlocksShell/DocBlocksShell.d.ts.map +1 -1
- package/dist/DocBlocksShell/DocBlocksShell.js +562 -67
- package/dist/DocBlocksShell/DocBlocksShell.js.map +1 -1
- package/dist/Export/ExportDialog.d.ts.map +1 -1
- package/dist/Export/ExportDialog.js +104 -5
- package/dist/Export/ExportDialog.js.map +1 -1
- package/dist/Export/ExportToolbarControls.d.ts.map +1 -1
- package/dist/Export/ExportToolbarControls.js +44 -3
- package/dist/Export/ExportToolbarControls.js.map +1 -1
- package/dist/Export/export-options.d.ts +35 -0
- package/dist/Export/export-options.d.ts.map +1 -1
- package/dist/Export/export-options.js +6 -1
- package/dist/Export/export-options.js.map +1 -1
- package/dist/Export/run-export.d.ts.map +1 -1
- package/dist/Export/run-export.js +163 -91
- package/dist/Export/run-export.js.map +1 -1
- package/dist/FileExplorer/FileExplorer.d.ts.map +1 -1
- package/dist/FileExplorer/FileExplorer.js +17 -2
- package/dist/FileExplorer/FileExplorer.js.map +1 -1
- package/dist/WorkspacePicker/WorkspaceSettingsButton.d.ts +2 -1
- package/dist/WorkspacePicker/WorkspaceSettingsButton.d.ts.map +1 -1
- package/dist/WorkspacePicker/WorkspaceSettingsButton.js +2 -2
- package/dist/WorkspacePicker/WorkspaceSettingsButton.js.map +1 -1
- package/dist/WorkspacePicker/WorkspaceSettingsDialog.d.ts +20 -0
- package/dist/WorkspacePicker/WorkspaceSettingsDialog.d.ts.map +1 -0
- package/dist/WorkspacePicker/WorkspaceSettingsDialog.js +36 -0
- package/dist/WorkspacePicker/WorkspaceSettingsDialog.js.map +1 -0
- package/dist/hooks/useAutoSave.d.ts +8 -2
- package/dist/hooks/useAutoSave.d.ts.map +1 -1
- package/dist/hooks/useAutoSave.js +65 -30
- package/dist/hooks/useAutoSave.js.map +1 -1
- package/dist/preferences/versioning.d.ts +27 -0
- package/dist/preferences/versioning.d.ts.map +1 -0
- package/dist/preferences/versioning.js +62 -0
- package/dist/preferences/versioning.js.map +1 -0
- package/package.json +10 -8
- package/src/AppMenu/AppMenu.tsx +64 -1
- package/src/DocBlocksShell/DocBlocksShell.tsx +756 -80
- package/src/Export/ExportDialog.tsx +208 -19
- package/src/Export/ExportToolbarControls.tsx +45 -3
- package/src/Export/export-options.ts +43 -1
- package/src/Export/run-export.ts +190 -91
- package/src/FileExplorer/FileExplorer.tsx +31 -9
- package/src/WorkspacePicker/WorkspaceSettingsButton.tsx +9 -0
- package/src/WorkspacePicker/WorkspaceSettingsDialog.tsx +121 -0
- package/src/hooks/useAutoSave.ts +87 -29
- package/src/preferences/versioning.ts +69 -0
- package/src/styles/docblocks.css +920 -53
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
6
6
|
import { getThemeSummaries } from '@bendyline/squisq/schemas';
|
|
7
7
|
import { getTransformStyleSummaries } from '@bendyline/squisq/transform';
|
|
8
|
-
import type { ExportFormat, ExportOptions } from './export-options.js';
|
|
8
|
+
import type { ExportFormat, ExportOptions, HtmlBundle, HtmlStyle } from './export-options.js';
|
|
9
9
|
import { FORMAT_LABELS, saveExportOptions } from './export-options.js';
|
|
10
10
|
|
|
11
11
|
export interface ExportDialogProps {
|
|
@@ -21,25 +21,150 @@ export interface ExportDialogProps {
|
|
|
21
21
|
|
|
22
22
|
const FORMATS: ExportFormat[] = ['pdf', 'docx', 'pptx', 'html', 'md'];
|
|
23
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
|
+
|
|
24
99
|
export function ExportDialog({ initial, exporting, onExport, onClose }: ExportDialogProps) {
|
|
25
100
|
const [format, setFormat] = useState<ExportFormat>(initial.format);
|
|
26
101
|
const [themeId, setThemeId] = useState(initial.themeId);
|
|
27
102
|
const [transformStyle, setTransformStyle] = useState(initial.transformStyle);
|
|
28
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);
|
|
29
108
|
const dialogRef = useRef<HTMLDivElement>(null);
|
|
30
109
|
|
|
31
110
|
const themes = getThemeSummaries();
|
|
32
111
|
const transforms = getTransformStyleSummaries();
|
|
33
112
|
|
|
34
|
-
|
|
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';
|
|
35
119
|
const showTransform = format === 'pptx';
|
|
36
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);
|
|
37
141
|
|
|
38
142
|
const handleExport = useCallback(() => {
|
|
39
|
-
const opts: ExportOptions = {
|
|
143
|
+
const opts: ExportOptions = {
|
|
144
|
+
format,
|
|
145
|
+
themeId,
|
|
146
|
+
transformStyle,
|
|
147
|
+
pageSize,
|
|
148
|
+
htmlStyle,
|
|
149
|
+
htmlBundle,
|
|
150
|
+
includeLinkedDocs: showIncludeLinked && includeLinkedDocs,
|
|
151
|
+
entryAsIndex: showEntryAsIndex && entryAsIndex,
|
|
152
|
+
};
|
|
40
153
|
saveExportOptions(opts);
|
|
41
154
|
onExport(opts);
|
|
42
|
-
}, [
|
|
155
|
+
}, [
|
|
156
|
+
format,
|
|
157
|
+
themeId,
|
|
158
|
+
transformStyle,
|
|
159
|
+
pageSize,
|
|
160
|
+
htmlStyle,
|
|
161
|
+
htmlBundle,
|
|
162
|
+
includeLinkedDocs,
|
|
163
|
+
entryAsIndex,
|
|
164
|
+
showIncludeLinked,
|
|
165
|
+
showEntryAsIndex,
|
|
166
|
+
onExport,
|
|
167
|
+
]);
|
|
43
168
|
|
|
44
169
|
// Close on Escape
|
|
45
170
|
useEffect(() => {
|
|
@@ -69,25 +194,89 @@ export function ExportDialog({ initial, exporting, onExport, onClose }: ExportDi
|
|
|
69
194
|
</div>
|
|
70
195
|
|
|
71
196
|
<div className="db-dialog-body">
|
|
72
|
-
{/* Format */}
|
|
197
|
+
{/* Format — chip radio row for fast switching */}
|
|
73
198
|
<div className="db-export-field">
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
<select
|
|
78
|
-
id="db-export-format"
|
|
79
|
-
className="db-export-select"
|
|
199
|
+
<span className="db-export-label">Format</span>
|
|
200
|
+
<ChipRadioGroup
|
|
201
|
+
name="Format"
|
|
80
202
|
value={format}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
</select>
|
|
203
|
+
options={FORMATS.map((f) => ({
|
|
204
|
+
key: f,
|
|
205
|
+
label: FORMAT_CHIP_LABELS[f],
|
|
206
|
+
title: FORMAT_LABELS[f],
|
|
207
|
+
}))}
|
|
208
|
+
onChange={setFormat}
|
|
209
|
+
/>
|
|
89
210
|
</div>
|
|
90
211
|
|
|
212
|
+
{/* HTML style + bundle (HTML only) — also chip rows */}
|
|
213
|
+
{showHtmlOptions && (
|
|
214
|
+
<>
|
|
215
|
+
<div className="db-export-field">
|
|
216
|
+
<span className="db-export-label">Style</span>
|
|
217
|
+
<ChipRadioGroup
|
|
218
|
+
name="Style"
|
|
219
|
+
value={htmlStyle}
|
|
220
|
+
options={HTML_STYLE_CHIPS}
|
|
221
|
+
onChange={setHtmlStyle}
|
|
222
|
+
/>
|
|
223
|
+
<span className="db-export-hint">
|
|
224
|
+
{HTML_STYLE_CHIPS.find((c) => c.key === htmlStyle)?.hint}
|
|
225
|
+
</span>
|
|
226
|
+
</div>
|
|
227
|
+
{showHtmlBundle && (
|
|
228
|
+
<div className="db-export-field">
|
|
229
|
+
<span className="db-export-label">Bundle</span>
|
|
230
|
+
<ChipRadioGroup
|
|
231
|
+
name="Bundle"
|
|
232
|
+
value={htmlBundle}
|
|
233
|
+
options={HTML_BUNDLE_CHIPS}
|
|
234
|
+
onChange={setHtmlBundle}
|
|
235
|
+
/>
|
|
236
|
+
<span className="db-export-hint">
|
|
237
|
+
{HTML_BUNDLE_CHIPS.find((c) => c.key === htmlBundle)?.hint}
|
|
238
|
+
</span>
|
|
239
|
+
</div>
|
|
240
|
+
)}
|
|
241
|
+
{showIncludeLinked && (
|
|
242
|
+
<div className="db-export-field">
|
|
243
|
+
<label className="db-export-checkbox">
|
|
244
|
+
<input
|
|
245
|
+
type="checkbox"
|
|
246
|
+
checked={includeLinkedDocs}
|
|
247
|
+
onChange={(e) => setIncludeLinkedDocs(e.target.checked)}
|
|
248
|
+
/>
|
|
249
|
+
<span>Include linked-to documents</span>
|
|
250
|
+
</label>
|
|
251
|
+
<span className="db-export-hint">
|
|
252
|
+
{includeLinkedDocs
|
|
253
|
+
? '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.'
|
|
254
|
+
: 'Only this document is exported.'}
|
|
255
|
+
</span>
|
|
256
|
+
</div>
|
|
257
|
+
)}
|
|
258
|
+
{showEntryAsIndex && (
|
|
259
|
+
<div className="db-export-field">
|
|
260
|
+
<label className="db-export-checkbox">
|
|
261
|
+
<input
|
|
262
|
+
type="checkbox"
|
|
263
|
+
checked={entryAsIndex}
|
|
264
|
+
onChange={(e) => setEntryAsIndex(e.target.checked)}
|
|
265
|
+
/>
|
|
266
|
+
<span>Name entry page index.html</span>
|
|
267
|
+
</label>
|
|
268
|
+
<span className="db-export-hint">
|
|
269
|
+
{entryAsIndex
|
|
270
|
+
? includeLinkedDocs
|
|
271
|
+
? '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.'
|
|
272
|
+
: 'Downloads as index.html instead of the document name. Drops straight into a static-site host.'
|
|
273
|
+
: 'Exports under the document name.'}
|
|
274
|
+
</span>
|
|
275
|
+
</div>
|
|
276
|
+
)}
|
|
277
|
+
</>
|
|
278
|
+
)}
|
|
279
|
+
|
|
91
280
|
{/* Theme */}
|
|
92
281
|
{showTheme && (
|
|
93
282
|
<div className="db-export-field">
|
|
@@ -35,9 +35,24 @@ export interface ExportToolbarControlsProps {
|
|
|
35
35
|
|
|
36
36
|
/** Build the quick-export label from saved options. */
|
|
37
37
|
function quickLabel(opts: ExportOptions): string {
|
|
38
|
-
const
|
|
38
|
+
const baseExt = FORMAT_EXTENSIONS[opts.format].toUpperCase().replace('.', '');
|
|
39
|
+
// Recursive HTML always emits a ZIP (multi-doc tree), regardless of
|
|
40
|
+
// the saved `htmlBundle` value. Applies to both plain and rendered
|
|
41
|
+
// styles since both now ship recursive bundle helpers.
|
|
42
|
+
const isRecursiveHtml = opts.format === 'html' && opts.includeLinkedDocs;
|
|
43
|
+
const ext =
|
|
44
|
+
opts.format === 'html' && (opts.htmlBundle === 'zip' || isRecursiveHtml) ? 'ZIP' : baseExt;
|
|
39
45
|
const parts: string[] = [];
|
|
40
46
|
|
|
47
|
+
if (opts.format === 'html' && opts.htmlStyle === 'rendered') {
|
|
48
|
+
parts.push('rendered');
|
|
49
|
+
}
|
|
50
|
+
if (isRecursiveHtml) {
|
|
51
|
+
parts.push('linked docs');
|
|
52
|
+
}
|
|
53
|
+
// Plain HTML also applies themes now (squisq's `markdownDocToPlainHtml`
|
|
54
|
+
// emits theme-driven CSS), so include the theme name in the quick-
|
|
55
|
+
// export label for every HTML style — not just rendered.
|
|
41
56
|
if (opts.themeId !== 'standard') {
|
|
42
57
|
const theme = getThemeSummaries().find((t) => t.id === opts.themeId);
|
|
43
58
|
if (theme) parts.push(theme.name);
|
|
@@ -57,7 +72,7 @@ export function ExportToolbarControls({
|
|
|
57
72
|
selectedFile,
|
|
58
73
|
mediaContainer,
|
|
59
74
|
}: ExportToolbarControlsProps) {
|
|
60
|
-
const { markdownSource } = useEditorContext();
|
|
75
|
+
const { markdownSource, markdownDoc } = useEditorContext();
|
|
61
76
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
62
77
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
63
78
|
const [videoModalOpen, setVideoModalOpen] = useState(false);
|
|
@@ -66,6 +81,33 @@ export function ExportToolbarControls({
|
|
|
66
81
|
|
|
67
82
|
const lastOptions = loadLastExportOptions();
|
|
68
83
|
|
|
84
|
+
/** Doc's currently-set squisq theme, pulled from the markdown
|
|
85
|
+
* frontmatter. The Document Settings dialog and Theme Customizer
|
|
86
|
+
* write the theme under `squisq-theme` (canonical) or `theme`
|
|
87
|
+
* (legacy); some older docs persist it as `themeId`. We honor all
|
|
88
|
+
* three so an author who set their theme anywhere upstream sees it
|
|
89
|
+
* pre-selected in the export dialog. */
|
|
90
|
+
const docThemeId = useMemo(() => {
|
|
91
|
+
const fm = markdownDoc?.frontmatter as Record<string, unknown> | undefined;
|
|
92
|
+
if (!fm) return null;
|
|
93
|
+
const candidates = [fm['squisq-theme'], fm['theme'], fm['themeId']];
|
|
94
|
+
for (const v of candidates) {
|
|
95
|
+
if (typeof v === 'string' && v.trim()) return v.trim();
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}, [markdownDoc]);
|
|
99
|
+
|
|
100
|
+
/** Options used to populate the export dialog. Layered: built-in
|
|
101
|
+
* defaults → user's last-chosen export options (if any) → doc's
|
|
102
|
+
* current frontmatter theme (wins). The frontmatter override
|
|
103
|
+
* guarantees that "set theme in the editor, then export" pre-selects
|
|
104
|
+
* the right theme instead of resurrecting whatever the user picked
|
|
105
|
+
* for some unrelated previous doc. */
|
|
106
|
+
const dialogInitial = useMemo(() => {
|
|
107
|
+
const base = lastOptions ?? DEFAULT_OPTIONS;
|
|
108
|
+
return docThemeId ? { ...base, themeId: docThemeId } : base;
|
|
109
|
+
}, [lastOptions, docThemeId]);
|
|
110
|
+
|
|
69
111
|
/** Build a Doc from the current markdown for video export. */
|
|
70
112
|
const doc = useMemo(() => {
|
|
71
113
|
if (!videoModalOpen) return null;
|
|
@@ -168,7 +210,7 @@ export function ExportToolbarControls({
|
|
|
168
210
|
|
|
169
211
|
{dialogOpen && (
|
|
170
212
|
<ExportDialog
|
|
171
|
-
initial={
|
|
213
|
+
initial={dialogInitial}
|
|
172
214
|
exporting={exporting}
|
|
173
215
|
onExport={handleExport}
|
|
174
216
|
onClose={handleCloseDialog}
|
|
@@ -4,6 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
export type ExportFormat = 'docx' | 'pdf' | 'pptx' | 'md' | 'html';
|
|
6
6
|
|
|
7
|
+
/** Visual style for HTML export. */
|
|
8
|
+
export type HtmlStyle =
|
|
9
|
+
/** Render via SquisqPlayer (themed, embeds player bundle). */
|
|
10
|
+
| 'rendered'
|
|
11
|
+
/** Plain semantic HTML — small, no JS, no playback. */
|
|
12
|
+
| 'plain';
|
|
13
|
+
|
|
14
|
+
/** Packaging for HTML export. */
|
|
15
|
+
export type HtmlBundle =
|
|
16
|
+
/** Single self-contained .html file (images base64-embedded). */
|
|
17
|
+
| 'single'
|
|
18
|
+
/** ZIP containing index.html + asset files. */
|
|
19
|
+
| 'zip';
|
|
20
|
+
|
|
7
21
|
export interface ExportOptions {
|
|
8
22
|
format: ExportFormat;
|
|
9
23
|
themeId: string;
|
|
@@ -11,6 +25,29 @@ export interface ExportOptions {
|
|
|
11
25
|
transformStyle: string;
|
|
12
26
|
/** Only applies to PDF */
|
|
13
27
|
pageSize: 'letter' | 'a4';
|
|
28
|
+
/** HTML rendering style. Only applies to format=html. */
|
|
29
|
+
htmlStyle: HtmlStyle;
|
|
30
|
+
/** HTML bundle layout. Only applies to format=html. */
|
|
31
|
+
htmlBundle: HtmlBundle;
|
|
32
|
+
/**
|
|
33
|
+
* Recursive HTML bundle: walk relative `.md` links from the entry
|
|
34
|
+
* document, render every reachable sibling/child page, and rewrite
|
|
35
|
+
* cross-doc links from `.md` → `.html`. The output is a ZIP
|
|
36
|
+
* regardless of `htmlBundle` because multi-doc output needs a
|
|
37
|
+
* directory tree. Supported for both plain (`markdownDocsToPlainHtmlBundle`)
|
|
38
|
+
* and rendered (`markdownDocsToHtmlBundle`) HTML styles. Only
|
|
39
|
+
* meaningful when `format === 'html'`.
|
|
40
|
+
*/
|
|
41
|
+
includeLinkedDocs: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Rename the entry page from `<basename>.html` to `index.html` so the
|
|
44
|
+
* export drops straight into a static-site host that expects an
|
|
45
|
+
* `index.html` landing page. For recursive bundles, cross-doc links
|
|
46
|
+
* targeting the entry also rewrite to `index.html` so siblings don't
|
|
47
|
+
* 404. For a single-doc HTML download, this just renames the file.
|
|
48
|
+
* Only meaningful when `format === 'html'`.
|
|
49
|
+
*/
|
|
50
|
+
entryAsIndex: boolean;
|
|
14
51
|
}
|
|
15
52
|
|
|
16
53
|
export const FORMAT_LABELS: Record<ExportFormat, string> = {
|
|
@@ -36,13 +73,18 @@ export const DEFAULT_OPTIONS: ExportOptions = {
|
|
|
36
73
|
themeId: 'standard',
|
|
37
74
|
transformStyle: 'documentary',
|
|
38
75
|
pageSize: 'letter',
|
|
76
|
+
htmlStyle: 'plain',
|
|
77
|
+
htmlBundle: 'single',
|
|
78
|
+
includeLinkedDocs: false,
|
|
79
|
+
entryAsIndex: false,
|
|
39
80
|
};
|
|
40
81
|
|
|
41
82
|
export function loadLastExportOptions(): ExportOptions | null {
|
|
42
83
|
try {
|
|
43
84
|
const raw = localStorage.getItem(STORAGE_KEY);
|
|
44
85
|
if (!raw) return null;
|
|
45
|
-
|
|
86
|
+
const parsed = JSON.parse(raw) as Partial<ExportOptions>;
|
|
87
|
+
return { ...DEFAULT_OPTIONS, ...parsed };
|
|
46
88
|
} catch {
|
|
47
89
|
return null;
|
|
48
90
|
}
|