@sutrata/editor 0.1.1 → 0.2.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.
- package/README.md +87 -0
- package/dist/ai/VoiceConfirmDialog.d.ts +1 -0
- package/dist/ai/VoiceConfirmDialog.js +6 -6
- package/dist/ai/VoiceToolbar.d.ts +1 -0
- package/dist/ai/VoiceToolbar.js +22 -16
- package/dist/ai/clean-response.d.ts +7 -0
- package/dist/ai/clean-response.js +58 -0
- package/dist/ai/format-transcript.d.ts +3 -0
- package/dist/ai/format-transcript.js +7 -0
- package/dist/ai/prompts.d.ts +8 -1
- package/dist/ai/prompts.js +34 -7
- package/dist/autocomplete/suggestions.js +1 -1
- package/dist/context/DocumentContext.d.ts +28 -4
- package/dist/context/DocumentContext.js +67 -37
- package/dist/editor/EditorView.js +32 -4
- package/dist/editor/SelectionMenu.js +5 -3
- package/dist/editor/external-decorations-plugin.d.ts +23 -0
- package/dist/editor/external-decorations-plugin.js +151 -0
- package/dist/editor/omitted-scene-plugin.d.ts +9 -0
- package/dist/editor/omitted-scene-plugin.js +44 -0
- package/dist/editor/plugins.js +4 -0
- package/dist/editor/private-metadata-plugin.d.ts +10 -0
- package/dist/editor/private-metadata-plugin.js +60 -0
- package/dist/editor/prosemirror-to-sutra.js +179 -74
- package/dist/editor/scene-heading-view.js +2 -0
- package/dist/editor/schema.js +69 -12
- package/dist/editor/sutra-to-prosemirror.d.ts +2 -0
- package/dist/editor/sutra-to-prosemirror.js +32 -34
- package/dist/extensions/ai-provider.d.ts +44 -0
- package/dist/extensions/ai-provider.js +27 -0
- package/dist/extensions/collab.d.ts +21 -0
- package/dist/extensions/collab.js +6 -0
- package/dist/extensions/command-registry.d.ts +49 -0
- package/dist/extensions/command-registry.js +28 -0
- package/dist/extensions/decorations.d.ts +48 -0
- package/dist/extensions/decorations.js +6 -0
- package/dist/extensions/export-registry.d.ts +59 -0
- package/dist/extensions/export-registry.js +10 -0
- package/dist/extensions/panel-registry.d.ts +30 -0
- package/dist/extensions/panel-registry.js +16 -0
- package/dist/extensions/registries.d.ts +15 -0
- package/dist/extensions/registries.js +12 -0
- package/dist/extensions/registry-store.d.ts +26 -0
- package/dist/extensions/registry-store.js +35 -0
- package/dist/extensions/session.d.ts +29 -0
- package/dist/extensions/session.js +20 -0
- package/dist/extensions/speech-provider.d.ts +35 -0
- package/dist/extensions/speech-provider.js +17 -0
- package/dist/file/builtin-exporters.d.ts +9 -0
- package/dist/file/builtin-exporters.js +72 -0
- package/dist/file/docx-exporter.js +7 -4
- package/dist/file/docx-importer.d.ts +9 -0
- package/dist/file/docx-importer.js +47 -19
- package/dist/file/scene-number.d.ts +5 -0
- package/dist/file/scene-number.js +8 -0
- package/dist/file/workflow-reports.js +13 -7
- package/dist/findreplace/FindReplace.js +5 -2
- package/dist/import/AIImportDialog.d.ts +11 -0
- package/dist/import/AIImportDialog.js +112 -0
- package/dist/import/ai-import.d.ts +51 -0
- package/dist/import/ai-import.js +200 -0
- package/dist/import/apply-import.d.ts +7 -0
- package/dist/import/apply-import.js +35 -0
- package/dist/import/extract.d.ts +17 -0
- package/dist/import/extract.js +18 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.js +7 -0
- package/dist/locales/bn.json +8 -0
- package/dist/locales/en.json +8 -0
- package/dist/locales/gu.json +8 -0
- package/dist/locales/hi.json +8 -0
- package/dist/locales/kn.json +8 -0
- package/dist/locales/ml.json +8 -0
- package/dist/locales/mr.json +8 -0
- package/dist/locales/or.json +8 -0
- package/dist/locales/pa.json +8 -0
- package/dist/locales/si.json +8 -0
- package/dist/locales/ta.json +8 -0
- package/dist/locales/te.json +8 -0
- package/dist/navigator/SceneNavigator.js +125 -131
- package/dist/navigator/StatisticsDialog.js +5 -1
- package/dist/navigator/scene-edits.d.ts +18 -0
- package/dist/navigator/scene-edits.js +143 -0
- package/dist/navigator/scene-list.js +4 -4
- package/dist/navigator/scene-numbering.d.ts +37 -0
- package/dist/navigator/scene-numbering.js +88 -0
- package/dist/settings/SettingsDialog.js +8 -120
- package/dist/shell/AppBar.js +21 -2
- package/dist/shell/AppShell.js +18 -4
- package/dist/shell/ElementToolbar.d.ts +5 -0
- package/dist/shell/ElementToolbar.js +35 -25
- package/dist/shell/LanguageStatus.js +6 -0
- package/dist/shell/StatusBar.js +11 -6
- package/dist/shell/builtin-commands.d.ts +10 -0
- package/dist/shell/builtin-commands.js +43 -0
- package/dist/shell/icons.d.ts +6 -0
- package/dist/shell/icons.js +6 -0
- package/dist/shell/use-commands.d.ts +7 -0
- package/dist/shell/use-commands.js +24 -0
- package/dist/shell/use-panel-context.d.ts +3 -0
- package/dist/shell/use-panel-context.js +39 -0
- package/dist/source/SourceView.js +18 -1
- package/dist/styles/screenplay.css +113 -1
- package/dist/titlepage/ExportDialog.d.ts +5 -0
- package/dist/titlepage/ExportDialog.js +59 -96
- package/package.json +32 -5
- package/dist/ai/OnboardingDialog.d.ts +0 -6
- package/dist/ai/OnboardingDialog.js +0 -115
- package/dist/ai/ai-client.d.ts +0 -71
- package/dist/ai/ai-client.js +0 -494
- package/dist/ai/generated-providers.json +0 -3696
- package/dist/ai/voice-service.d.ts +0 -15
- package/dist/ai/voice-service.js +0 -81
- package/dist/editor/character-view.d.ts +0 -35
- package/dist/editor/character-view.js +0 -112
- package/dist/editor/transliterate-input.d.ts +0 -10
- package/dist/editor/transliterate-input.js +0 -88
- package/dist/extensions/types.d.ts +0 -73
- package/dist/extensions/types.js +0 -12
- package/scripts/copy-assets.mjs +0 -16
- package/scripts/generate-providers.mjs +0 -127
- package/src/ai/OnboardingDialog.tsx +0 -390
- package/src/ai/VoiceConfirmDialog.tsx +0 -137
- package/src/ai/VoiceToolbar.tsx +0 -321
- package/src/ai/ai-client.ts +0 -596
- package/src/ai/commands.ts +0 -164
- package/src/ai/generated-providers.json +0 -3696
- package/src/ai/prompts.ts +0 -62
- package/src/ai/voice-service.ts +0 -96
- package/src/autocomplete/AutocompletePlugin.ts +0 -21
- package/src/autocomplete/suggestions.ts +0 -147
- package/src/context/DocumentContext.tsx +0 -648
- package/src/desktop-api.d.ts +0 -40
- package/src/editor/EditorView.tsx +0 -82
- package/src/editor/SelectionMenu.tsx +0 -152
- package/src/editor/attr-pencil-plugin.ts +0 -172
- package/src/editor/character-decoration-plugin.ts +0 -104
- package/src/editor/editor-bus.ts +0 -51
- package/src/editor/find-highlight-plugin.ts +0 -68
- package/src/editor/frontmatter-field-view.ts +0 -52
- package/src/editor/frontmatter-field.ts +0 -115
- package/src/editor/grapheme-cursor.ts +0 -99
- package/src/editor/ime-plugin.ts +0 -85
- package/src/editor/insert-helper.ts +0 -43
- package/src/editor/keymap.ts +0 -124
- package/src/editor/language-class-plugin.ts +0 -91
- package/src/editor/plugins.ts +0 -26
- package/src/editor/prosemirror-to-sutra.ts +0 -192
- package/src/editor/scene-heading-view.ts +0 -147
- package/src/editor/schema.ts +0 -156
- package/src/editor/sutra-to-prosemirror.ts +0 -156
- package/src/extensions/storage-adapter.ts +0 -65
- package/src/extensions/types.ts +0 -60
- package/src/file/docx-exporter.ts +0 -585
- package/src/file/docx-importer.ts +0 -313
- package/src/file/print-paginator.ts +0 -301
- package/src/file/templates.ts +0 -73
- package/src/file/workflow-reports.ts +0 -820
- package/src/findreplace/FindReplace.tsx +0 -242
- package/src/findreplace/find-engine.ts +0 -97
- package/src/findreplace/scroll-to-match.ts +0 -89
- package/src/fonts/tofu-detect.ts +0 -53
- package/src/i18n/LanguageContext.tsx +0 -207
- package/src/i18n/language-detect.ts +0 -73
- package/src/i18n/useTranslation.tsx +0 -74
- package/src/index.ts +0 -27
- package/src/locales/bn.json +0 -143
- package/src/locales/en.json +0 -143
- package/src/locales/gu.json +0 -143
- package/src/locales/hi.json +0 -143
- package/src/locales/kn.json +0 -143
- package/src/locales/ml.json +0 -143
- package/src/locales/mr.json +0 -143
- package/src/locales/or.json +0 -143
- package/src/locales/pa.json +0 -143
- package/src/locales/si.json +0 -143
- package/src/locales/ta.json +0 -143
- package/src/locales/te.json +0 -143
- package/src/navigator/SceneNavigator.tsx +0 -844
- package/src/navigator/StatisticsDialog.tsx +0 -249
- package/src/navigator/scene-list.ts +0 -84
- package/src/settings/SettingsDialog.tsx +0 -303
- package/src/shell/AppBar.tsx +0 -354
- package/src/shell/AppShell.tsx +0 -71
- package/src/shell/ConfirmDialog.tsx +0 -71
- package/src/shell/ElementToolbar.tsx +0 -175
- package/src/shell/LanguageStatus.tsx +0 -101
- package/src/shell/StatusBar.tsx +0 -135
- package/src/shell/SutrataLogo.tsx +0 -89
- package/src/shell/Toast.tsx +0 -46
- package/src/shell/ToolbarButton.tsx +0 -30
- package/src/shell/VersionHistory.tsx +0 -212
- package/src/shell/Watermark.tsx +0 -9
- package/src/shell/fonts/fonts.css +0 -1133
- package/src/shell/icons.tsx +0 -335
- package/src/shell/toolbar-actions.ts +0 -59
- package/src/source/SourceView.tsx +0 -111
- package/src/source/find-highlight-source.ts +0 -57
- package/src/source/sutra-lang.ts +0 -121
- package/src/spellcheck/dictionaries.ts +0 -21
- package/src/spellcheck/hunspell.ts +0 -110
- package/src/spellcheck/spellcheck-plugin.ts +0 -83
- package/src/styles/StyleDialog.tsx +0 -148
- package/src/styles/builtin-styles.ts +0 -139
- package/src/styles/css-adapter.ts +0 -76
- package/src/styles/docx-adapter.ts +0 -109
- package/src/styles/embed-fonts.ts +0 -81
- package/src/styles/pagination.ts +0 -27
- package/src/styles/print-adapter.ts +0 -288
- package/src/styles/registry.ts +0 -20
- package/src/styles/screenplay.css +0 -3006
- package/src/styles/script-ranges.ts +0 -17
- package/src/styles/types.ts +0 -104
- package/src/styles/validate.ts +0 -114
- package/src/templates/templates.ts +0 -194
- package/src/titlepage/ExportDialog.tsx +0 -494
- package/src/titlepage/TitlePageDialog.tsx +0 -312
- package/src/titlepage/frontmatter-form.ts +0 -280
- package/src/titlepage/languages.ts +0 -37
- package/src/types.ts +0 -7
- package/tests/AppBar.test.tsx +0 -35
- package/tests/AppShell.test.tsx +0 -22
- package/tests/ElementToolbar.test.tsx +0 -36
- package/tests/LanguageStatus.test.tsx +0 -129
- package/tests/SelectionMenu.test.tsx +0 -71
- package/tests/ToolbarButton.test.tsx +0 -24
- package/tests/ai-onboarding.test.ts +0 -102
- package/tests/ai-status.test.tsx +0 -72
- package/tests/ai-structured.test.ts +0 -94
- package/tests/autocomplete/AutocompletePlugin.test.ts +0 -20
- package/tests/autocomplete/suggestions.test.ts +0 -106
- package/tests/document-context.test.tsx +0 -565
- package/tests/docx-importer.test.ts +0 -160
- package/tests/editor/character-decoration-plugin.test.ts +0 -101
- package/tests/editor/character-extension.test.ts +0 -44
- package/tests/editor/grapheme-cursor.test.ts +0 -118
- package/tests/editor/ime-plugin.test.ts +0 -72
- package/tests/editor/inline-note-bridge.test.ts +0 -32
- package/tests/editor/language-class-plugin.test.ts +0 -265
- package/tests/editor/title-page.test.ts +0 -124
- package/tests/embed-fonts.test.ts +0 -17
- package/tests/exporters.test.ts +0 -365
- package/tests/findreplace/FindReplace.test.tsx +0 -391
- package/tests/findreplace/find-engine.test.ts +0 -86
- package/tests/findreplace/scroll-to-match.test.ts +0 -50
- package/tests/fonts/tofu-detect.test.ts +0 -122
- package/tests/i18n/LanguageContext.test.tsx +0 -146
- package/tests/i18n/language-detect.test.ts +0 -54
- package/tests/i18n/locale-parity.test.ts +0 -25
- package/tests/i18n/useTranslation.test.tsx +0 -45
- package/tests/keymap.test.ts +0 -18
- package/tests/navigator/SceneNavigator.test.tsx +0 -177
- package/tests/navigator/StatisticsDialog.test.tsx +0 -63
- package/tests/navigator/scene-list.test.ts +0 -71
- package/tests/print-paginator.test.ts +0 -82
- package/tests/shell/VersionHistory.test.tsx +0 -129
- package/tests/spellcheck/hunspell.test.ts +0 -82
- package/tests/spellcheck/spellcheck-plugin.test.ts +0 -56
- package/tests/styles.test.ts +0 -315
- package/tests/templates/templates.test.ts +0 -83
- package/tests/titlepage/TitlePageDialog.test.tsx +0 -90
- package/tests/titlepage/apply-title-page.test.ts +0 -61
- package/tests/titlepage/frontmatter-form.test.ts +0 -115
- package/tests/toolbar-actions.test.ts +0 -93
- package/tests/voice-commands.test.ts +0 -58
- package/tsconfig.json +0 -17
- package/vitest.config.ts +0 -11
- package/vitest.setup.ts +0 -65
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { makeSetBlock, makeSetOrToggleNote, makeToggleMark, undo, redo } from './toolbar-actions';
|
|
3
|
+
import { runCommand, activeBlockType } from '../editor/editor-bus';
|
|
4
|
+
import { UndoIcon, RedoIcon, SceneIcon, ActionIcon, CharacterIcon, DialogueIcon, ParentheticalIcon, TransitionIcon, CenteredIcon, LyricsIcon, NoteIcon, PageBreakIcon, SectionIcon, BoldIcon, ItalicIcon, UnderlineIcon, } from './icons';
|
|
5
|
+
const ELEMENTS = [
|
|
6
|
+
{ type: 'scene_heading', sigil: '##', shortcut: 'Alt-Shift-H', Icon: SceneIcon, labelKey: 'toolbar.scene' },
|
|
7
|
+
{ type: 'action', sigil: '(plain)', shortcut: 'Alt-Shift-A', Icon: ActionIcon, labelKey: 'toolbar.action' },
|
|
8
|
+
{ type: 'character', sigil: '@', shortcut: 'Alt-Shift-C', Icon: CharacterIcon, labelKey: 'toolbar.character' },
|
|
9
|
+
{ type: 'dialogue', sigil: '(after @)', shortcut: 'Alt-Shift-D', Icon: DialogueIcon, labelKey: 'toolbar.dialogue' },
|
|
10
|
+
{ type: 'parenthetical', sigil: '( )', shortcut: 'Alt-Shift-P', Icon: ParentheticalIcon, labelKey: 'toolbar.parenthetical' },
|
|
11
|
+
{ type: 'transition', sigil: '>>', shortcut: 'Alt-Shift-X', Icon: TransitionIcon, labelKey: 'toolbar.transition' },
|
|
12
|
+
{ type: 'centered', sigil: '>> <<', Icon: CenteredIcon, labelKey: 'toolbar.centered' },
|
|
13
|
+
{ type: 'lyrics', sigil: '~', shortcut: 'Alt-Shift-L', Icon: LyricsIcon, labelKey: 'toolbar.lyrics' },
|
|
14
|
+
{ type: 'note', sigil: '[[ ]]', shortcut: 'Alt-Shift-N', Icon: NoteIcon, labelKey: 'toolbar.note' },
|
|
15
|
+
{ type: 'page_break', sigil: '===', Icon: PageBreakIcon, labelKey: 'toolbar.pageBreak' },
|
|
16
|
+
{ type: 'section', sigil: '#', Icon: SectionIcon, labelKey: 'toolbar.section' },
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* The editor's own commands, registered like an embedder's: undo/redo,
|
|
20
|
+
* element types (with their Alt+Shift shortcuts) and inline marks, all shown
|
|
21
|
+
* in the element toolbar. Mark and history keys are bound by the ProseMirror
|
|
22
|
+
* keymap, so they only carry a `shortcutHint`.
|
|
23
|
+
*/
|
|
24
|
+
export function builtinCommands(t) {
|
|
25
|
+
return [
|
|
26
|
+
{ id: 'history.undo', label: t('toolbar.undo'), group: 'history', menu: 'toolbar', shortcutHint: 'Mod-Z', icon: s => _jsx(UndoIcon, { size: s }), run: () => runCommand(undo) },
|
|
27
|
+
{ id: 'history.redo', label: t('toolbar.redo'), group: 'history', menu: 'toolbar', shortcutHint: 'Mod-Shift-Z', icon: s => _jsx(RedoIcon, { size: s }), run: () => runCommand(redo) },
|
|
28
|
+
...ELEMENTS.map((el) => ({
|
|
29
|
+
id: `element.${el.type}`,
|
|
30
|
+
label: t(el.labelKey),
|
|
31
|
+
group: 'elements',
|
|
32
|
+
menu: 'toolbar',
|
|
33
|
+
sigil: el.sigil,
|
|
34
|
+
shortcut: el.shortcut,
|
|
35
|
+
icon: s => _jsx(el.Icon, { size: s }),
|
|
36
|
+
isActive: () => activeBlockType() === el.type,
|
|
37
|
+
run: () => runCommand(el.type === 'note' ? makeSetOrToggleNote() : makeSetBlock(el.type)),
|
|
38
|
+
})),
|
|
39
|
+
{ id: 'mark.bold', label: t('toolbar.bold'), group: 'marks', menu: 'toolbar', shortcutHint: 'Mod-B', icon: s => _jsx(BoldIcon, { size: s }), run: () => runCommand(makeToggleMark('bold')) },
|
|
40
|
+
{ id: 'mark.italic', label: t('toolbar.italic'), group: 'marks', menu: 'toolbar', shortcutHint: 'Mod-I', icon: s => _jsx(ItalicIcon, { size: s }), run: () => runCommand(makeToggleMark('italic')) },
|
|
41
|
+
{ id: 'mark.underline', label: t('toolbar.underline'), group: 'marks', menu: 'toolbar', shortcutHint: 'Mod-U', icon: s => _jsx(UnderlineIcon, { size: s }), run: () => runCommand(makeToggleMark('underline')) },
|
|
42
|
+
];
|
|
43
|
+
}
|
package/dist/shell/icons.d.ts
CHANGED
|
@@ -35,6 +35,12 @@ export declare const OverflowIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
|
35
35
|
export declare const EyeIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
36
36
|
export declare const EyeOffIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
37
37
|
export declare const LockIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
38
|
+
/** Numbered list: "Renumber scenes". */
|
|
39
|
+
export declare const RenumberIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
40
|
+
/** Circle with a slash: "Omit scene". */
|
|
41
|
+
export declare const OmitIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
42
|
+
/** Curved arrow back: "Restore scene". */
|
|
43
|
+
export declare const RestoreIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
38
44
|
export declare const StatsIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
39
45
|
export declare const HistoryIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
40
46
|
export declare const MicIcon: ({ size }: IconProps) => React.JSX.Element;
|
package/dist/shell/icons.js
CHANGED
|
@@ -42,6 +42,12 @@ export const OverflowIcon = ({ size = 14 }) => (_jsxs("svg", { width: size, heig
|
|
|
42
42
|
export const EyeIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("path", { d: "M1 8s3-6 7-6 7 6 7 6-3 6-7 6-7-6-7-6z" }), _jsx("circle", { cx: "8", cy: "8", r: "2.2" })] }));
|
|
43
43
|
export const EyeOffIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("path", { d: "M1 8s3-6 7-6 7 6 7 6-3 6-7 6-7-6-7-6z" }), _jsx("circle", { cx: "8", cy: "8", r: "2.2" }), _jsx("line", { x1: "2", y1: "2", x2: "14", y2: "14" })] }));
|
|
44
44
|
export const LockIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("rect", { x: "3", y: "7", width: "10", height: "7", rx: "1.5" }), _jsx("path", { d: "M4.5 7V4.5a3.5 3.5 0 017 0V7" })] }));
|
|
45
|
+
/** Numbered list: "Renumber scenes". */
|
|
46
|
+
export const RenumberIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("path", { d: "M2.5 3.5h1v3M2.5 6.5h2" }), _jsx("path", { d: "M2.5 10h2l-2 2.5h2" }), _jsx("path", { d: "M7 4.5h6.5M7 11.5h6.5" })] }));
|
|
47
|
+
/** Circle with a slash: "Omit scene". */
|
|
48
|
+
export const OmitIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("circle", { cx: "8", cy: "8", r: "5.5" }), _jsx("path", { d: "M4.2 11.8l7.6-7.6" })] }));
|
|
49
|
+
/** Curved arrow back: "Restore scene". */
|
|
50
|
+
export const RestoreIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("path", { d: "M3.5 6.5h6a3.5 3.5 0 010 7H6" }), _jsx("path", { d: "M6 3.5l-2.5 3 2.5 3" })] }));
|
|
45
51
|
export const StatsIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("line", { x1: "12", y1: "14", x2: "12", y2: "4" }), _jsx("line", { x1: "8", y1: "14", x2: "8", y2: "7" }), _jsx("line", { x1: "4", y1: "14", x2: "4", y2: "10" })] }));
|
|
46
52
|
export const HistoryIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("circle", { cx: "8", cy: "8.5", r: "6" }), _jsx("polyline", { points: "8,5.5 8,8.5 10.5,10" })] }));
|
|
47
53
|
export const MicIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("rect", { x: "5.5", y: "2", width: "5", height: "8", rx: "2.5" }), _jsx("path", { d: "M3 7.5a5 5 0 0010 0" }), _jsx("line", { x1: "8", y1: "12.5", x2: "8", y2: "14.5" }), _jsx("line", { x1: "5.5", y1: "14.5", x2: "10.5", y2: "14.5" })] }));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CommandContext, CommandContribution } from '../extensions/command-registry';
|
|
2
|
+
/** Built-in + registered commands available in this session (read-only sessions keep only `readOnly` ones). */
|
|
3
|
+
export declare function useCommands(): CommandContribution[];
|
|
4
|
+
/** A CommandContext for the current moment. */
|
|
5
|
+
export declare function useCommandContext(): () => CommandContext;
|
|
6
|
+
/** `Alt-Shift-H` → `Alt+Shift+H` for tooltips. */
|
|
7
|
+
export declare function displayShortcut(shortcut: string | undefined): string | undefined;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useDocument } from '../context/DocumentContext';
|
|
3
|
+
import { useTranslation } from '../i18n/useTranslation';
|
|
4
|
+
import { useCanEdit } from '../extensions/session';
|
|
5
|
+
import { useCommandRegistry } from '../extensions/command-registry';
|
|
6
|
+
import { useRegistryList, withBuiltins } from '../extensions/registry-store';
|
|
7
|
+
import { getEditorView, getSourceView } from '../editor/editor-bus';
|
|
8
|
+
import { builtinCommands } from './builtin-commands';
|
|
9
|
+
/** Built-in + registered commands available in this session (read-only sessions keep only `readOnly` ones). */
|
|
10
|
+
export function useCommands() {
|
|
11
|
+
const { t } = useTranslation();
|
|
12
|
+
const canEdit = useCanEdit();
|
|
13
|
+
const registered = useRegistryList(useCommandRegistry());
|
|
14
|
+
return useMemo(() => withBuiltins(builtinCommands(t), registered).filter(c => canEdit || c.readOnly), [t, registered, canEdit]);
|
|
15
|
+
}
|
|
16
|
+
/** A CommandContext for the current moment. */
|
|
17
|
+
export function useCommandContext() {
|
|
18
|
+
const { mode, text, setText } = useDocument();
|
|
19
|
+
return () => ({ view: getEditorView(), sourceView: getSourceView(), mode, text, setText });
|
|
20
|
+
}
|
|
21
|
+
/** `Alt-Shift-H` → `Alt+Shift+H` for tooltips. */
|
|
22
|
+
export function displayShortcut(shortcut) {
|
|
23
|
+
return shortcut?.split(/[-+](?=.)/).join('+');
|
|
24
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { useDocument } from '../context/DocumentContext';
|
|
3
|
+
import { getEditorView, getSourceView, subscribe } from '../editor/editor-bus';
|
|
4
|
+
import { buildSceneList } from '../navigator/scene-list';
|
|
5
|
+
/** Id of the scene containing the caret (formatted or source editor), if it has one. */
|
|
6
|
+
function currentSceneId(text) {
|
|
7
|
+
const view = getEditorView();
|
|
8
|
+
if (view) {
|
|
9
|
+
const caret = view.state.selection.from;
|
|
10
|
+
let id = null;
|
|
11
|
+
view.state.doc.forEach((node, offset) => {
|
|
12
|
+
if (offset < caret && node.type.name === 'scene_heading')
|
|
13
|
+
id = node.attrs['id'] ?? null;
|
|
14
|
+
});
|
|
15
|
+
return id;
|
|
16
|
+
}
|
|
17
|
+
const source = getSourceView();
|
|
18
|
+
if (source) {
|
|
19
|
+
const caret = source.state.selection.main.head;
|
|
20
|
+
let id = null;
|
|
21
|
+
for (const scene of buildSceneList(text))
|
|
22
|
+
if (scene.textOffset <= caret)
|
|
23
|
+
id = scene.id;
|
|
24
|
+
return id;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
/** The context panels render with; re-computed on document and selection changes. */
|
|
29
|
+
export function usePanelContext() {
|
|
30
|
+
const { ast, text } = useDocument();
|
|
31
|
+
const [activeSceneId, setActiveSceneId] = useState(null);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const update = () => setActiveSceneId(currentSceneId(text));
|
|
34
|
+
update();
|
|
35
|
+
return subscribe(update);
|
|
36
|
+
}, [text]);
|
|
37
|
+
const sceneIds = useMemo(() => ast.children.flatMap(n => (n.type === 'scene-heading' && n.id ? [n.id] : [])), [ast]);
|
|
38
|
+
return useMemo(() => ({ ast, sceneIds, activeSceneId }), [ast, sceneIds, activeSceneId]);
|
|
39
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef } from 'react';
|
|
3
|
-
import { EditorState } from '@codemirror/state';
|
|
3
|
+
import { Annotation, Compartment, EditorState } from '@codemirror/state';
|
|
4
4
|
import { EditorView, keymap } from '@codemirror/view';
|
|
5
5
|
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
|
|
6
6
|
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
@@ -9,6 +9,9 @@ import { sutraLanguage } from './sutra-lang';
|
|
|
9
9
|
import { useDocument } from '../context/DocumentContext';
|
|
10
10
|
import { setSourceView } from '../editor/editor-bus';
|
|
11
11
|
import { sourceFindHighlightExtension } from './find-highlight-source';
|
|
12
|
+
import { useCanEdit } from '../extensions/session';
|
|
13
|
+
/** Marks the transaction that mirrors an external text change into the view. */
|
|
14
|
+
const externalSync = Annotation.define();
|
|
12
15
|
// Light theme matching the app's warm off-white palette.
|
|
13
16
|
// No internal scroll — the editor grows to content height and .cs-main scrolls.
|
|
14
17
|
const sutraTheme = EditorView.theme({
|
|
@@ -54,6 +57,12 @@ const sutraHighlight = HighlightStyle.define([
|
|
|
54
57
|
]);
|
|
55
58
|
export function SourceView() {
|
|
56
59
|
const { text, setText } = useDocument();
|
|
60
|
+
// Read-only sessions: the view is not editable and doc changes other than
|
|
61
|
+
// mirroring external text are filtered out (see the transactionFilter).
|
|
62
|
+
const canEdit = useCanEdit();
|
|
63
|
+
const canEditRef = useRef(canEdit);
|
|
64
|
+
canEditRef.current = canEdit;
|
|
65
|
+
const editableCompartment = useRef(new Compartment());
|
|
57
66
|
const containerRef = useRef(null);
|
|
58
67
|
const viewRef = useRef(null);
|
|
59
68
|
// Track externally-driven text so we don't feed our own updates back
|
|
@@ -72,6 +81,8 @@ export function SourceView() {
|
|
|
72
81
|
syntaxHighlighting(sutraHighlight),
|
|
73
82
|
sourceFindHighlightExtension,
|
|
74
83
|
EditorView.lineWrapping,
|
|
84
|
+
editableCompartment.current.of([EditorView.editable.of(canEdit), EditorState.readOnly.of(!canEdit)]),
|
|
85
|
+
EditorState.transactionFilter.of(tr => tr.docChanged && !canEditRef.current && !tr.annotation(externalSync) ? [] : tr),
|
|
75
86
|
EditorView.updateListener.of(update => {
|
|
76
87
|
if (update.docChanged) {
|
|
77
88
|
const newText = update.state.doc.toString();
|
|
@@ -100,7 +111,13 @@ export function SourceView() {
|
|
|
100
111
|
lastTextRef.current = text;
|
|
101
112
|
view.dispatch({
|
|
102
113
|
changes: { from: 0, to: view.state.doc.length, insert: text },
|
|
114
|
+
annotations: externalSync.of(true),
|
|
103
115
|
});
|
|
104
116
|
}, [text]);
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
viewRef.current?.dispatch({
|
|
119
|
+
effects: editableCompartment.current.reconfigure([EditorView.editable.of(canEdit), EditorState.readOnly.of(!canEdit)]),
|
|
120
|
+
});
|
|
121
|
+
}, [canEdit]);
|
|
105
122
|
return _jsx("div", { ref: containerRef, className: "cs-source-view" });
|
|
106
123
|
}
|
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
--cs-ui-accent-badge: #d97706;
|
|
32
32
|
--cs-ui-accent-border: #fcd34d;
|
|
33
33
|
|
|
34
|
+
/* Warnings (e.g. a scene without an {#id}) — distinct from the amber accent */
|
|
35
|
+
--cs-ui-warning: #c2410c;
|
|
36
|
+
--cs-ui-warning-subtle: #fff7ed;
|
|
37
|
+
|
|
34
38
|
/* Screenplay note tokens */
|
|
35
39
|
--cs-note-bg: #fffbeb;
|
|
36
40
|
--cs-note-border-color: #d97706;
|
|
@@ -91,6 +95,45 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
|
|
|
91
95
|
flex-shrink: 0;
|
|
92
96
|
}
|
|
93
97
|
|
|
98
|
+
/* Sidebar with embedder tabs (only when PanelRegistry has 'sidebar' panels) */
|
|
99
|
+
.cs-sidebar {
|
|
100
|
+
width: min(33%,400px);
|
|
101
|
+
min-width: 180px;
|
|
102
|
+
max-width: 480px;
|
|
103
|
+
display: flex;
|
|
104
|
+
flex-direction: column;
|
|
105
|
+
overflow: hidden;
|
|
106
|
+
flex-shrink: 0;
|
|
107
|
+
border-right: 1px solid #ddd;
|
|
108
|
+
background: #f8f8f6;
|
|
109
|
+
}
|
|
110
|
+
.cs-sidebar > .cs-navigator { width: auto; min-width: 0; max-width: none; flex: 1; border-right: none; }
|
|
111
|
+
.cs-sidebar-tabs { display: flex; gap: 2px; padding: 4px 6px 0; border-bottom: 1px solid var(--cs-ui-border-light, #e2e0d6); }
|
|
112
|
+
.cs-sidebar-tab {
|
|
113
|
+
background: none; border: none; border-bottom: 2px solid transparent; padding: 6px 8px;
|
|
114
|
+
font-size: 12px; font-weight: 600; color: var(--cs-ui-text-light-secondary, #59574f); cursor: pointer;
|
|
115
|
+
}
|
|
116
|
+
.cs-sidebar-tab-active { color: var(--cs-ui-text-light-primary, #1c1b18); border-bottom-color: var(--cs-ui-accent, #c4760a); }
|
|
117
|
+
.cs-sidebar-panel { flex: 1; overflow: auto; padding: 8px 12px; font-size: 12px; }
|
|
118
|
+
|
|
119
|
+
/* Inspector (only when PanelRegistry has 'inspector' panels) */
|
|
120
|
+
.cs-inspector {
|
|
121
|
+
width: min(28%, 320px);
|
|
122
|
+
min-width: 200px;
|
|
123
|
+
flex-shrink: 0;
|
|
124
|
+
overflow-y: auto;
|
|
125
|
+
border-left: 1px solid var(--cs-ui-border-light, #e2e0d6);
|
|
126
|
+
background: var(--cs-ui-bg-panel, #fcfbfa);
|
|
127
|
+
font-size: 12px;
|
|
128
|
+
}
|
|
129
|
+
.cs-inspector-section { padding: 10px 12px; border-bottom: 1px solid var(--cs-ui-border-light-subtle, #eeece4); }
|
|
130
|
+
.cs-inspector-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--cs-ui-text-light-secondary, #59574f); margin-bottom: 6px; }
|
|
131
|
+
@media (max-width: 640px) {
|
|
132
|
+
.cs-inspector { display: none; }
|
|
133
|
+
}
|
|
134
|
+
.cs-ab-text-label, .cs-tb-text-label { font-size: 12px; font-weight: 600; }
|
|
135
|
+
.cs-ab-panel { display: inline-flex; align-items: center; }
|
|
136
|
+
|
|
94
137
|
.cs-nav-count {
|
|
95
138
|
background: #e0e0dd;
|
|
96
139
|
border-radius: 10px;
|
|
@@ -160,6 +203,36 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
|
|
|
160
203
|
border-bottom-color: #1976d2;
|
|
161
204
|
}
|
|
162
205
|
|
|
206
|
+
.cs-nav-scene-missing-id {
|
|
207
|
+
box-shadow: inset 3px 0 0 var(--cs-ui-warning, #c2410c);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.cs-nav-missing-id-icon {
|
|
211
|
+
font-size: 12px;
|
|
212
|
+
line-height: 1.4;
|
|
213
|
+
color: var(--cs-ui-warning, #c2410c);
|
|
214
|
+
flex-shrink: 0;
|
|
215
|
+
align-self: flex-start;
|
|
216
|
+
padding-left: 8px;
|
|
217
|
+
cursor: help;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/* Scene number needs renumbering (format spec §7.4) */
|
|
221
|
+
.cs-nav-scene-number-issue { box-shadow: inset 3px 0 0 var(--cs-ui-warning, #c2410c); }
|
|
222
|
+
.cs-nav-num-issue {
|
|
223
|
+
color: var(--cs-ui-warning, #c2410c) !important;
|
|
224
|
+
background: var(--cs-ui-warning-subtle, #fff7ed);
|
|
225
|
+
border-radius: 3px;
|
|
226
|
+
padding: 0 4px;
|
|
227
|
+
cursor: help;
|
|
228
|
+
}
|
|
229
|
+
.cs-nav-scene-side { display: flex; align-items: flex-start; gap: 2px; flex-shrink: 0; }
|
|
230
|
+
.cs-nav-omit-btn { opacity: 0; transition: opacity 0.1s; }
|
|
231
|
+
.cs-nav-item:hover .cs-nav-omit-btn,
|
|
232
|
+
.cs-nav-item:focus-within .cs-nav-omit-btn,
|
|
233
|
+
.cs-nav-omit-btn:focus-visible { opacity: 1; }
|
|
234
|
+
.cs-nav-scene-omitted .cs-nav-heading { text-decoration: line-through; opacity: 0.7; }
|
|
235
|
+
|
|
163
236
|
.cs-nav-scene-num {
|
|
164
237
|
font-size: 12px;
|
|
165
238
|
font-weight: 600;
|
|
@@ -434,6 +507,11 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
|
|
|
434
507
|
margin-left: max(10%, 8px);
|
|
435
508
|
}
|
|
436
509
|
|
|
510
|
+
/* Tool-private `x-` keys (format spec §7.1) stay in the text but are not shown */
|
|
511
|
+
.cs-scene-metadata[data-private] {
|
|
512
|
+
display: none;
|
|
513
|
+
}
|
|
514
|
+
|
|
437
515
|
/* Synopsis renders as italic prose, visually distinct from key-value metadata */
|
|
438
516
|
.cs-scene-metadata[data-key="synopsis"] {
|
|
439
517
|
font-size: 10pt;
|
|
@@ -2675,9 +2753,12 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
|
|
|
2675
2753
|
padding: 10px 12px;
|
|
2676
2754
|
font-size: 13px;
|
|
2677
2755
|
color: var(--cs-ui-text-light-primary, #1c1b18);
|
|
2678
|
-
min-height: 140px;
|
|
2679
2756
|
line-height: 1.5;
|
|
2680
2757
|
white-space: pre-wrap;
|
|
2758
|
+
/* Long selections scroll inside the pane; .cs-dialog clips overflow. */
|
|
2759
|
+
box-sizing: border-box;
|
|
2760
|
+
height: min(50vh, 420px);
|
|
2761
|
+
overflow-y: auto;
|
|
2681
2762
|
}
|
|
2682
2763
|
.cs-voice-box-formatted {
|
|
2683
2764
|
width: 100%;
|
|
@@ -2689,6 +2770,8 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
|
|
|
2689
2770
|
font-size: 13px;
|
|
2690
2771
|
font-family: 'Courier Prime', Courier, monospace;
|
|
2691
2772
|
color: var(--cs-ui-text-light-primary, #1c1b18);
|
|
2773
|
+
/* Same height as the raw pane so the two read side by side. */
|
|
2774
|
+
height: min(50vh, 420px);
|
|
2692
2775
|
min-height: 140px;
|
|
2693
2776
|
line-height: 1.4;
|
|
2694
2777
|
resize: vertical;
|
|
@@ -3004,3 +3087,32 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
|
|
|
3004
3087
|
.cs-tp-row > :first-child { grid-column: 1 / -1; }
|
|
3005
3088
|
.cs-tp-add { margin-left: 0; }
|
|
3006
3089
|
}
|
|
3090
|
+
|
|
3091
|
+
/* AI-assisted import dialog */
|
|
3092
|
+
.cs-ai-import-body { padding: 14px 16px; max-height: 80vh; overflow-y: auto; font-size: 13px; }
|
|
3093
|
+
.cs-ai-import-hint { margin: 0 0 10px; color: var(--cs-ui-text-light-secondary, #59574f); display: flex; align-items: center; flex-wrap: wrap; gap: 4px; }
|
|
3094
|
+
.cs-ai-import-error { background: #fef2f2; border: 1px solid #fecaca; color: #991b1b; padding: 8px 10px; border-radius: 4px; margin-bottom: 10px; }
|
|
3095
|
+
.cs-ai-import-paste { width: 100%; box-sizing: border-box; font-family: 'Courier New', monospace; font-size: 12px; padding: 8px; border: 1px solid var(--cs-ui-border-light, #e2e0d6); border-radius: 4px; }
|
|
3096
|
+
.cs-ai-import-policy { background: var(--cs-ui-bg-panel-subtle, #f4f2ea); border-radius: 4px; padding: 10px; font-size: 12px; margin-bottom: 10px; white-space: pre-wrap; }
|
|
3097
|
+
.cs-ai-import-agree { display: flex; gap: 8px; align-items: center; font-size: 12px; }
|
|
3098
|
+
.cs-ai-import-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }
|
|
3099
|
+
.cs-ai-import-warnings { margin-bottom: 10px; }
|
|
3100
|
+
.cs-ai-import-review { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
3101
|
+
.cs-ai-import-col-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--cs-ui-text-light-secondary, #59574f); margin-bottom: 6px; }
|
|
3102
|
+
.cs-ai-import-source, .cs-ai-import-block pre { white-space: pre-wrap; word-break: break-word; font-family: 'Courier New', monospace; font-size: 12px; margin: 0; }
|
|
3103
|
+
.cs-ai-import-source { max-height: 60vh; overflow: auto; background: var(--cs-ui-bg-panel-subtle, #f4f2ea); padding: 8px; border-radius: 4px; }
|
|
3104
|
+
.cs-ai-import-blocks { list-style: none; margin: 0; padding: 0; max-height: 60vh; overflow: auto; }
|
|
3105
|
+
.cs-ai-import-block { display: grid; grid-template-columns: 150px 1fr; gap: 8px; align-items: start; padding: 6px 0; border-bottom: 1px solid var(--cs-ui-border-light-subtle, #eeece4); }
|
|
3106
|
+
@media (max-width: 640px) {
|
|
3107
|
+
.cs-ai-import-review { grid-template-columns: 1fr; }
|
|
3108
|
+
.cs-ai-import-block { grid-template-columns: 1fr; }
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
/* Omitted scene (format spec §7.4) */
|
|
3112
|
+
.cs-scene-omitted .cs-scene-heading-text { text-decoration: line-through; opacity: 0.6; }
|
|
3113
|
+
.cs-omitted-badge {
|
|
3114
|
+
display: inline-block; margin-left: 12px; padding: 0 6px; border-radius: 3px;
|
|
3115
|
+
font-size: 0.75em; font-weight: 700; letter-spacing: 0.08em; text-decoration: none;
|
|
3116
|
+
color: var(--cs-ui-warning, #c2410c); background: var(--cs-ui-warning-subtle, #fff7ed);
|
|
3117
|
+
user-select: none; vertical-align: middle;
|
|
3118
|
+
}
|
|
@@ -1 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders every export format from one list: the editor's built-ins
|
|
3
|
+
* (file/builtin-exporters.ts) plus the embedder's ExportRegistry entries.
|
|
4
|
+
* Screenplay formats are cards; reports are grouped by `family` with chips.
|
|
5
|
+
*/
|
|
1
6
|
export declare function ExportDialog(): import("react").JSX.Element;
|
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
3
3
|
import { useDocument } from '../context/DocumentContext';
|
|
4
|
-
import {
|
|
4
|
+
import { parse, romanizeSutra } from '@sutrata/parser';
|
|
5
5
|
import { useTranslation } from '../i18n/useTranslation';
|
|
6
|
-
import { exportToDocx } from '../file/docx-exporter';
|
|
7
|
-
import { extractWorkflowData, exportOneLinerCsv, exportShotListCsv, exportCastReportCsv, exportOneLinerDocx, exportShotListDocx, exportCastReportDocx, openPrintPreview, openScreenplayPrintPreview, } from '../file/workflow-reports';
|
|
8
6
|
import { allStyles } from '../styles/registry';
|
|
7
|
+
import { useExportRegistry } from '../extensions/export-registry';
|
|
8
|
+
import { useRegistryList, withBuiltins } from '../extensions/registry-store';
|
|
9
|
+
import { builtinExporters } from '../file/builtin-exporters';
|
|
10
|
+
/**
|
|
11
|
+
* Renders every export format from one list: the editor's built-ins
|
|
12
|
+
* (file/builtin-exporters.ts) plus the embedder's ExportRegistry entries.
|
|
13
|
+
* Screenplay formats are cards; reports are grouped by `family` with chips.
|
|
14
|
+
*/
|
|
9
15
|
export function ExportDialog() {
|
|
10
16
|
const { ast, text, filePath, setExportVisible, resolvedStyle, customStyles, showToast } = useDocument();
|
|
11
17
|
const { t } = useTranslation();
|
|
18
|
+
const registered = useRegistryList(useExportRegistry());
|
|
19
|
+
const exporters = useMemo(() => withBuiltins(builtinExporters(t), registered), [t, registered]);
|
|
20
|
+
const screenplayFormats = exporters.filter(e => e.group === 'screenplay');
|
|
21
|
+
const reportFormats = exporters.filter(e => e.group === 'report');
|
|
22
|
+
const reportFamilies = useMemo(() => {
|
|
23
|
+
const families = new Map();
|
|
24
|
+
for (const e of reportFormats)
|
|
25
|
+
families.set(e.family ?? e.label, [...(families.get(e.family ?? e.label) ?? []), e]);
|
|
26
|
+
return [...families];
|
|
27
|
+
}, [reportFormats]);
|
|
12
28
|
const [tab, setTab] = useState('screenplay');
|
|
13
|
-
const [
|
|
29
|
+
const [formatId, setFormatId] = useState(screenplayFormats[0]?.id ?? '');
|
|
14
30
|
const [warnings, setWarnings] = useState([]);
|
|
15
31
|
const [isExporting, setIsExporting] = useState(false);
|
|
16
32
|
const [styleOverride, setStyleOverride] = useState('');
|
|
@@ -20,7 +36,11 @@ export function ExportDialog() {
|
|
|
20
36
|
const [skipNotes, setSkipNotes] = useState(false);
|
|
21
37
|
const hasIndicText = /[\u0900-\u0DFF]/.test(text);
|
|
22
38
|
const styleChoices = allStyles(customStyles);
|
|
23
|
-
const
|
|
39
|
+
const selected = exporters.find(e => e.id === formatId) ?? screenplayFormats[0];
|
|
40
|
+
const showStylePicker = tab === 'screenplay' && !!selected?.supports?.style;
|
|
41
|
+
const showSkipNotes = tab === 'screenplay' && !!selected?.supports?.skipNotes;
|
|
42
|
+
const showRomanize = tab === 'screenplay' && hasIndicText && !!selected?.supports?.romanize;
|
|
43
|
+
const showReportRomanize = hasIndicText && reportFormats.some(e => e.supports?.romanize);
|
|
24
44
|
useEffect(() => {
|
|
25
45
|
const onKey = (e) => {
|
|
26
46
|
if (e.key === 'Escape')
|
|
@@ -30,87 +50,37 @@ export function ExportDialog() {
|
|
|
30
50
|
return () => document.removeEventListener('keydown', onKey);
|
|
31
51
|
}, [setExportVisible]);
|
|
32
52
|
const baseName = (filePath ?? 'screenplay').replace(/\.[^.]+$/, '');
|
|
33
|
-
/** The
|
|
53
|
+
/** The context an exporter runs with: the document, or a romanized copy
|
|
34
54
|
* (Indic → ISO 15919, export only; the .sutra text is never changed). */
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
function exportContext(exporter) {
|
|
56
|
+
const romanizeScopeFor = !exporter.supports?.romanize || !hasIndicText ? null
|
|
57
|
+
: exporter.group === 'report' ? (romanizeReports ? 'all' : null)
|
|
58
|
+
: romanizeScope === 'off' ? null : romanizeScope;
|
|
59
|
+
const options = {
|
|
60
|
+
styleId: exporter.group === 'screenplay' ? styleOverride || undefined : undefined,
|
|
61
|
+
skipNotes: exporter.group === 'screenplay' && skipNotes,
|
|
62
|
+
romanized: romanizeScopeFor ? { scope: romanizeScopeFor, variant: romanizeVariant } : null,
|
|
63
|
+
};
|
|
64
|
+
if (!romanizeScopeFor)
|
|
65
|
+
return { text, ast, style: resolvedStyle, customStyles, options, baseName };
|
|
66
|
+
const romanized = romanizeSutra(text, {
|
|
67
|
+
scope: romanizeScopeFor, variant: romanizeVariant, ...(exporter.group === 'report' ? { notice: false } : {}),
|
|
68
|
+
});
|
|
69
|
+
return { text: romanized, ast: parse(romanized), style: resolvedStyle, customStyles, options, baseName };
|
|
47
70
|
}
|
|
48
71
|
async function doExport() {
|
|
72
|
+
if (!selected)
|
|
73
|
+
return;
|
|
49
74
|
setIsExporting(true);
|
|
50
75
|
try {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
else if (format === 'docx') {
|
|
60
|
-
setWarnings([]);
|
|
61
|
-
const { doc, suffix } = screenplayAst();
|
|
62
|
-
const blob = await exportToDocx(doc, styleOverride || undefined, customStyles, skipNotes);
|
|
63
|
-
downloadBlob(`${baseName}${suffix}.docx`, blob);
|
|
64
|
-
showToast('Exported Word document (.docx)', 'success');
|
|
65
|
-
}
|
|
66
|
-
else if (format === 'pdf') {
|
|
67
|
-
setWarnings([]);
|
|
68
|
-
const { doc, suffix } = screenplayAst();
|
|
69
|
-
const title = `${baseName.toUpperCase()} - SCREENPLAY${suffix ? ' (ROMANIZED)' : ''}`;
|
|
70
|
-
await openScreenplayPrintPreview(doc, title, styleOverride || undefined, customStyles, skipNotes);
|
|
71
|
-
showToast('Opened Screenplay Print Preview', 'info');
|
|
72
|
-
}
|
|
73
|
-
else if (format === 'oneliner-print') {
|
|
74
|
-
setWarnings([]);
|
|
75
|
-
await openPrintPreview(data.scenes, `${baseName.toUpperCase()} - ONE-LINER SCHEDULE${reportTitleSuffix}`);
|
|
76
|
-
showToast('Opened One-Liner Schedule', 'info');
|
|
77
|
-
}
|
|
78
|
-
else if (format === 'oneliner-docx') {
|
|
79
|
-
setWarnings([]);
|
|
80
|
-
const blob = await exportOneLinerDocx(data.scenes);
|
|
81
|
-
downloadBlob(`${baseName}_one_liner${reportSuffix}.docx`, blob);
|
|
82
|
-
showToast('Exported One-Liner DOCX', 'success');
|
|
83
|
-
}
|
|
84
|
-
else if (format === 'oneliner-csv') {
|
|
85
|
-
setWarnings([]);
|
|
86
|
-
const csv = exportOneLinerCsv(data.scenes);
|
|
87
|
-
download(`${baseName}_one_liner${reportSuffix}.csv`, csv, 'text/csv');
|
|
88
|
-
showToast('Exported One-Liner CSV', 'success');
|
|
89
|
-
}
|
|
90
|
-
else if (format === 'shotlist-docx') {
|
|
91
|
-
setWarnings([]);
|
|
92
|
-
const blob = await exportShotListDocx(data.scenes);
|
|
93
|
-
downloadBlob(`${baseName}_shot_list${reportSuffix}.docx`, blob);
|
|
94
|
-
showToast('Exported Shot List DOCX', 'success');
|
|
95
|
-
}
|
|
96
|
-
else if (format === 'shotlist-csv') {
|
|
97
|
-
setWarnings([]);
|
|
98
|
-
const csv = exportShotListCsv(data.scenes);
|
|
99
|
-
download(`${baseName}_shot_list${reportSuffix}.csv`, csv, 'text/csv');
|
|
100
|
-
showToast('Exported Shot List CSV', 'success');
|
|
101
|
-
}
|
|
102
|
-
else if (format === 'cast-docx') {
|
|
103
|
-
setWarnings([]);
|
|
104
|
-
const blob = await exportCastReportDocx(data.characters);
|
|
105
|
-
downloadBlob(`${baseName}_cast_report${reportSuffix}.docx`, blob);
|
|
106
|
-
showToast('Exported Cast Report DOCX', 'success');
|
|
107
|
-
}
|
|
108
|
-
else if (format === 'cast-csv') {
|
|
109
|
-
setWarnings([]);
|
|
110
|
-
const csv = exportCastReportCsv(data.characters);
|
|
111
|
-
download(`${baseName}_cast_report${reportSuffix}.csv`, csv, 'text/csv');
|
|
112
|
-
showToast('Exported Cast Report CSV', 'success');
|
|
113
|
-
}
|
|
76
|
+
const ctx = exportContext(selected);
|
|
77
|
+
// Called before any await: print previews must open inside the click.
|
|
78
|
+
const output = await selected.run(ctx);
|
|
79
|
+
const blob = output instanceof Blob ? output : output?.blob;
|
|
80
|
+
setWarnings(output && !(output instanceof Blob) ? output.warnings ?? [] : []);
|
|
81
|
+
if (blob)
|
|
82
|
+
downloadBlob(selected.fileName(ctx), blob);
|
|
83
|
+
showToast(selected.successMessage ?? `Exported ${selected.label}`, blob ? 'success' : 'info');
|
|
114
84
|
}
|
|
115
85
|
catch (err) {
|
|
116
86
|
console.error('Export failed', err);
|
|
@@ -121,11 +91,6 @@ export function ExportDialog() {
|
|
|
121
91
|
setIsExporting(false);
|
|
122
92
|
}
|
|
123
93
|
}
|
|
124
|
-
function download(filename, content, mimeType) {
|
|
125
|
-
const data = mimeType === 'text/csv' ? ['\uFEFF', content] : [content];
|
|
126
|
-
const blob = new Blob(data, { type: `${mimeType};charset=utf-8` });
|
|
127
|
-
downloadBlob(filename, blob);
|
|
128
|
-
}
|
|
129
94
|
function downloadBlob(filename, blob) {
|
|
130
95
|
const url = URL.createObjectURL(blob);
|
|
131
96
|
const a = document.createElement('a');
|
|
@@ -139,14 +104,12 @@ export function ExportDialog() {
|
|
|
139
104
|
setExportVisible(false);
|
|
140
105
|
}, children: _jsxs("div", { className: "cs-export-dialog", role: "dialog", "aria-modal": "true", "aria-labelledby": "cs-export-title", style: { maxHeight: '90vh', maxWidth: '540px', width: '92%', display: 'flex', flexDirection: 'column' }, children: [_jsxs("div", { className: "cs-panel-header", children: [_jsx("span", { id: "cs-export-title", className: "cs-panel-title", children: t('export.title') }), _jsx("button", { className: "cs-panel-close", onClick: () => setExportVisible(false), "aria-label": "Close", children: "\u00D7" })] }), _jsxs("div", { className: "cs-panel-body", style: { overflowY: 'auto', flex: 1, padding: '16px' }, children: [_jsxs("div", { className: "cs-export-segmented", role: "tablist", "aria-label": "Export category", children: [_jsx("button", { type: "button", role: "tab", "aria-selected": tab === 'screenplay', className: `cs-export-seg-btn${tab === 'screenplay' ? ' cs-export-seg-btn-active' : ''}`, onClick: () => {
|
|
141
106
|
setTab('screenplay');
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
}, children: "Screenplay Formats" }), _jsx("button", { type: "button", role: "tab", "aria-selected": tab === 'reports', className: `cs-export-seg-btn${tab === 'reports' ? ' cs-export-seg-btn-active' : ''}`, onClick: () => {
|
|
107
|
+
if (selected?.group !== 'screenplay')
|
|
108
|
+
setFormatId(screenplayFormats[0]?.id ?? '');
|
|
109
|
+
}, children: "Screenplay Formats" }), reportFormats.length > 0 && _jsx("button", { type: "button", role: "tab", "aria-selected": tab === 'reports', className: `cs-export-seg-btn${tab === 'reports' ? ' cs-export-seg-btn-active' : ''}`, onClick: () => {
|
|
145
110
|
setTab('reports');
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
}, children: "Production Reports" })] }), tab === 'screenplay' ? (_jsxs("div", { children: [
|
|
149
|
-
|
|
150
|
-
setFormat('docx'); }, children: [_jsx("input", { type: "radio", name: "format", value: "docx", checked: format === 'docx', onChange: () => setFormat('docx'), style: { marginTop: '2px' } }), _jsxs("div", { children: [_jsx("strong", { style: { fontSize: '13px', display: 'block', color: 'var(--cs-ui-text-light-primary, #1c1b18)' }, children: "Microsoft Word (.docx)" }), _jsx("span", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }, children: t('export.docxDesc') })] })] }), _jsxs("div", { className: `cs-export-format-card${format === 'fountain' ? ' cs-export-format-card-selected' : ''}`, onClick: () => setFormat('fountain'), role: "radio", "aria-checked": format === 'fountain', tabIndex: 0, onKeyDown: e => { if (e.key === 'Enter' || e.key === ' ')
|
|
151
|
-
setFormat('fountain'); }, children: [_jsx("input", { type: "radio", name: "format", value: "fountain", checked: format === 'fountain', onChange: () => setFormat('fountain'), style: { marginTop: '2px' } }), _jsxs("div", { children: [_jsx("strong", { style: { fontSize: '13px', display: 'block', color: 'var(--cs-ui-text-light-primary, #1c1b18)' }, children: "Fountain (.fountain)" }), _jsx("span", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }, children: t('export.fountainDesc') })] })] })] }), showStylePicker && (_jsxs("div", { className: "cs-settings-row", style: { marginTop: '14px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px' }, children: [_jsx("label", { className: "cs-settings-label", htmlFor: "cs-export-style-select", style: { fontSize: '12px' }, children: t('export.style') }), _jsxs("select", { id: "cs-export-style-select", className: "cs-settings-select", value: styleOverride, onChange: e => setStyleOverride(e.target.value), children: [_jsxs("option", { value: "", children: [t('export.styleDefault'), " (", resolvedStyle.name, ")"] }), styleChoices.map(s => (_jsx("option", { value: s.id, children: s.name }, s.id)))] })] })), showStylePicker && (_jsxs("div", { className: "cs-settings-row", style: { marginTop: '8px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px', display: 'flex', alignItems: 'center', gap: '8px' }, children: [_jsx("input", { id: "cs-export-skip-notes", type: "checkbox", checked: skipNotes, onChange: e => setSkipNotes(e.target.checked) }), _jsx("label", { htmlFor: "cs-export-skip-notes", className: "cs-settings-label", style: { fontSize: '12px', margin: 0 }, children: "Skip notes ([[ ]])" })] })), showStylePicker && hasIndicText && (_jsxs("div", { className: "cs-settings-row", style: { marginTop: '8px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px', flexWrap: 'wrap', gap: '8px' }, children: [_jsx("label", { className: "cs-settings-label", htmlFor: "cs-export-romanize-select", style: { fontSize: '12px' }, children: "Romanized copy (ISO 15919)" }), _jsxs("select", { id: "cs-export-romanize-select", className: "cs-settings-select", value: romanizeScope, onChange: e => setRomanizeScope(e.target.value), children: [_jsx("option", { value: "off", children: "Off (native script)" }), _jsx("option", { value: "all", children: "All text" }), _jsx("option", { value: "dialogue", children: "Dialogue only" })] }), romanizeScope !== 'off' && (_jsxs("select", { "aria-label": "Romanization variant", className: "cs-settings-select", value: romanizeVariant, onChange: e => setRomanizeVariant(e.target.value), children: [_jsx("option", { value: "strict", children: "Strict ISO 15919" }), _jsx("option", { value: "colloquial", children: "Casual (no diacritics)" })] })), romanizeScope !== 'off' && (_jsx("span", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)', flexBasis: '100%' }, children: "Your script is not changed. Page numbers in the romanized copy differ from the original." }))] }))] })) : (_jsxs("div", { children: [_jsxs("div", { className: "cs-export-report-box", children: [_jsxs("div", { className: "cs-export-report-header", children: [_jsx("span", { className: "cs-export-report-title", children: "One-Liner Schedule" }), _jsxs("div", { className: "cs-export-chips", children: [_jsx("button", { type: "button", className: `cs-export-chip${format === 'oneliner-print' ? ' cs-export-chip-active' : ''}`, onClick: () => setFormat('oneliner-print'), children: "Print View" }), _jsx("button", { type: "button", className: `cs-export-chip${format === 'oneliner-docx' ? ' cs-export-chip-active' : ''}`, onClick: () => setFormat('oneliner-docx'), children: "Word (.docx)" }), _jsx("button", { type: "button", className: `cs-export-chip${format === 'oneliner-csv' ? ' cs-export-chip-active' : ''}`, onClick: () => setFormat('oneliner-csv'), children: "Spreadsheet (.csv)" })] })] }), _jsx("div", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }, children: "A scene-by-scene production stripboard breakdown with locations, cast, and timings." })] }), _jsxs("div", { className: "cs-export-report-box", children: [_jsxs("div", { className: "cs-export-report-header", children: [_jsx("span", { className: "cs-export-report-title", children: "Shot List" }), _jsxs("div", { className: "cs-export-chips", children: [_jsx("button", { type: "button", className: `cs-export-chip${format === 'shotlist-docx' ? ' cs-export-chip-active' : ''}`, onClick: () => setFormat('shotlist-docx'), children: "Word (.docx)" }), _jsx("button", { type: "button", className: `cs-export-chip${format === 'shotlist-csv' ? ' cs-export-chip-active' : ''}`, onClick: () => setFormat('shotlist-csv'), children: "Spreadsheet (.csv)" })] })] }), _jsx("div", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }, children: "Camera shot specifications extracted from scene headings and production notes." })] }), _jsxs("div", { className: "cs-export-report-box", children: [_jsxs("div", { className: "cs-export-report-header", children: [_jsx("span", { className: "cs-export-report-title", children: "Cast Breakdown" }), _jsxs("div", { className: "cs-export-chips", children: [_jsx("button", { type: "button", className: `cs-export-chip${format === 'cast-docx' ? ' cs-export-chip-active' : ''}`, onClick: () => setFormat('cast-docx'), children: "Word (.docx)" }), _jsx("button", { type: "button", className: `cs-export-chip${format === 'cast-csv' ? ' cs-export-chip-active' : ''}`, onClick: () => setFormat('cast-csv'), children: "Spreadsheet (.csv)" })] })] }), _jsx("div", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }, children: "Character occurrences, dialogue counts, and assigned actors list." })] }), hasIndicText && (_jsxs("div", { className: "cs-settings-row", style: { marginTop: '8px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px', flexWrap: 'wrap', gap: '8px' }, children: [_jsx("label", { className: "cs-settings-label", htmlFor: "cs-export-romanize-reports", style: { fontSize: '12px' }, children: "Romanized copy (ISO 15919)" }), _jsxs("select", { id: "cs-export-romanize-reports", className: "cs-settings-select", value: romanizeReports ? 'on' : 'off', onChange: e => setRomanizeReports(e.target.value === 'on'), children: [_jsx("option", { value: "off", children: "Off (native script)" }), _jsx("option", { value: "on", children: "Romanize all text" })] }), romanizeReports && (_jsxs("select", { "aria-label": "Romanization variant", className: "cs-settings-select", value: romanizeVariant, onChange: e => setRomanizeVariant(e.target.value), children: [_jsx("option", { value: "strict", children: "Strict ISO 15919" }), _jsx("option", { value: "colloquial", children: "Casual (no diacritics)" })] }))] }))] })), warnings.length > 0 && (_jsxs("div", { className: "cs-export-warnings", style: { background: '#fffbeb', border: '1px solid #fcd34d', padding: '10px', borderRadius: '4px', marginTop: '14px' }, children: [_jsx("div", { className: "cs-export-warn-title", style: { fontWeight: 600, color: '#b45309', fontSize: '12px', marginBottom: '4px' }, children: t('export.warnings') }), warnings.map((w, i) => (_jsxs("div", { className: "cs-export-warn-item", style: { fontSize: '11px', color: '#59574f' }, children: ["\u2022 ", w] }, i)))] }))] }), _jsxs("div", { className: "cs-export-actions", style: { borderTop: '1px solid var(--cs-ui-border-light-subtle, #eeece4)', padding: '12px 16px', display: 'flex', justifyContent: 'flex-end', gap: '8px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)' }, children: [_jsx("button", { type: "button", className: "cs-confirm-btn-cancel", onClick: () => setExportVisible(false), children: t('export.cancel') }), _jsx("button", { type: "button", className: "cs-confirm-btn-primary", onClick: doExport, disabled: isExporting, children: isExporting ? 'Exporting...' : t('export.download') })] })] }) }));
|
|
111
|
+
if (selected?.group !== 'report')
|
|
112
|
+
setFormatId(reportFormats[0]?.id ?? '');
|
|
113
|
+
}, children: "Production Reports" })] }), tab === 'screenplay' ? (_jsxs("div", { children: [_jsx("div", { className: "cs-export-card-grid", children: screenplayFormats.map(f => (_jsxs("div", { className: `cs-export-format-card${selected?.id === f.id ? ' cs-export-format-card-selected' : ''}`, onClick: () => setFormatId(f.id), role: "radio", "aria-checked": selected?.id === f.id, tabIndex: 0, onKeyDown: e => { if (e.key === 'Enter' || e.key === ' ')
|
|
114
|
+
setFormatId(f.id); }, children: [_jsx("input", { type: "radio", name: "format", value: f.id, checked: selected?.id === f.id, onChange: () => setFormatId(f.id), style: { marginTop: '2px' } }), _jsxs("div", { children: [_jsx("strong", { style: { fontSize: '13px', display: 'block', color: 'var(--cs-ui-text-light-primary, #1c1b18)' }, children: f.label }), f.description && (_jsx("span", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }, children: f.description }))] })] }, f.id))) }), showStylePicker && (_jsxs("div", { className: "cs-settings-row", style: { marginTop: '14px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px' }, children: [_jsx("label", { className: "cs-settings-label", htmlFor: "cs-export-style-select", style: { fontSize: '12px' }, children: t('export.style') }), _jsxs("select", { id: "cs-export-style-select", className: "cs-settings-select", value: styleOverride, onChange: e => setStyleOverride(e.target.value), children: [_jsxs("option", { value: "", children: [t('export.styleDefault'), " (", resolvedStyle.name, ")"] }), styleChoices.map(s => (_jsx("option", { value: s.id, children: s.name }, s.id)))] })] })), showSkipNotes && (_jsxs("div", { className: "cs-settings-row", style: { marginTop: '8px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px', display: 'flex', alignItems: 'center', gap: '8px' }, children: [_jsx("input", { id: "cs-export-skip-notes", type: "checkbox", checked: skipNotes, onChange: e => setSkipNotes(e.target.checked) }), _jsx("label", { htmlFor: "cs-export-skip-notes", className: "cs-settings-label", style: { fontSize: '12px', margin: 0 }, children: "Skip notes ([[ ]])" })] })), showRomanize && (_jsxs("div", { className: "cs-settings-row", style: { marginTop: '8px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px', flexWrap: 'wrap', gap: '8px' }, children: [_jsx("label", { className: "cs-settings-label", htmlFor: "cs-export-romanize-select", style: { fontSize: '12px' }, children: "Romanized copy (ISO 15919)" }), _jsxs("select", { id: "cs-export-romanize-select", className: "cs-settings-select", value: romanizeScope, onChange: e => setRomanizeScope(e.target.value), children: [_jsx("option", { value: "off", children: "Off (native script)" }), _jsx("option", { value: "all", children: "All text" }), _jsx("option", { value: "dialogue", children: "Dialogue only" })] }), romanizeScope !== 'off' && (_jsxs("select", { "aria-label": "Romanization variant", className: "cs-settings-select", value: romanizeVariant, onChange: e => setRomanizeVariant(e.target.value), children: [_jsx("option", { value: "strict", children: "Strict ISO 15919" }), _jsx("option", { value: "colloquial", children: "Casual (no diacritics)" })] })), romanizeScope !== 'off' && (_jsx("span", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)', flexBasis: '100%' }, children: "Your script is not changed. Page numbers in the romanized copy differ from the original." }))] }))] })) : (_jsxs("div", { children: [reportFamilies.map(([family, formats]) => (_jsxs("div", { className: "cs-export-report-box", children: [_jsxs("div", { className: "cs-export-report-header", children: [_jsx("span", { className: "cs-export-report-title", children: family }), _jsx("div", { className: "cs-export-chips", children: formats.map(f => (_jsx("button", { type: "button", className: `cs-export-chip${selected?.id === f.id ? ' cs-export-chip-active' : ''}`, onClick: () => setFormatId(f.id), children: f.formatLabel ?? f.label }, f.id))) })] }), formats[0].description && (_jsx("div", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }, children: formats[0].description }))] }, family))), showReportRomanize && (_jsxs("div", { className: "cs-settings-row", style: { marginTop: '8px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px', flexWrap: 'wrap', gap: '8px' }, children: [_jsx("label", { className: "cs-settings-label", htmlFor: "cs-export-romanize-reports", style: { fontSize: '12px' }, children: "Romanized copy (ISO 15919)" }), _jsxs("select", { id: "cs-export-romanize-reports", className: "cs-settings-select", value: romanizeReports ? 'on' : 'off', onChange: e => setRomanizeReports(e.target.value === 'on'), children: [_jsx("option", { value: "off", children: "Off (native script)" }), _jsx("option", { value: "on", children: "Romanize all text" })] }), romanizeReports && (_jsxs("select", { "aria-label": "Romanization variant", className: "cs-settings-select", value: romanizeVariant, onChange: e => setRomanizeVariant(e.target.value), children: [_jsx("option", { value: "strict", children: "Strict ISO 15919" }), _jsx("option", { value: "colloquial", children: "Casual (no diacritics)" })] }))] }))] })), warnings.length > 0 && (_jsxs("div", { className: "cs-export-warnings", style: { background: '#fffbeb', border: '1px solid #fcd34d', padding: '10px', borderRadius: '4px', marginTop: '14px' }, children: [_jsx("div", { className: "cs-export-warn-title", style: { fontWeight: 600, color: '#b45309', fontSize: '12px', marginBottom: '4px' }, children: t('export.warnings') }), warnings.map((w, i) => (_jsxs("div", { className: "cs-export-warn-item", style: { fontSize: '11px', color: '#59574f' }, children: ["\u2022 ", w] }, i)))] }))] }), _jsxs("div", { className: "cs-export-actions", style: { borderTop: '1px solid var(--cs-ui-border-light-subtle, #eeece4)', padding: '12px 16px', display: 'flex', justifyContent: 'flex-end', gap: '8px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)' }, children: [_jsx("button", { type: "button", className: "cs-confirm-btn-cancel", onClick: () => setExportVisible(false), children: t('export.cancel') }), _jsx("button", { type: "button", className: "cs-confirm-btn-primary", onClick: doExport, disabled: isExporting || !selected, children: isExporting ? 'Exporting...' : t('export.download') })] })] }) }));
|
|
152
115
|
}
|