@sutrata/editor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/dist/ai/OnboardingDialog.d.ts +6 -0
- package/dist/ai/OnboardingDialog.js +115 -0
- package/dist/ai/VoiceConfirmDialog.d.ts +9 -0
- package/dist/ai/VoiceConfirmDialog.js +37 -0
- package/dist/ai/VoiceToolbar.d.ts +2 -0
- package/dist/ai/VoiceToolbar.js +177 -0
- package/dist/ai/ai-client.d.ts +71 -0
- package/dist/ai/ai-client.js +494 -0
- package/dist/ai/commands.d.ts +12 -0
- package/dist/ai/commands.js +140 -0
- package/dist/ai/generated-providers.json +3696 -0
- package/dist/ai/prompts.d.ts +25 -0
- package/dist/ai/prompts.js +59 -0
- package/dist/ai/voice-service.d.ts +15 -0
- package/dist/ai/voice-service.js +81 -0
- package/dist/autocomplete/AutocompletePlugin.d.ts +2 -0
- package/dist/autocomplete/AutocompletePlugin.js +19 -0
- package/dist/autocomplete/suggestions.d.ts +25 -0
- package/dist/autocomplete/suggestions.js +126 -0
- package/dist/context/DocumentContext.d.ts +68 -0
- package/dist/context/DocumentContext.js +605 -0
- package/dist/desktop-api.d.ts +40 -0
- package/dist/editor/EditorView.d.ts +2 -0
- package/dist/editor/EditorView.js +78 -0
- package/dist/editor/SelectionMenu.d.ts +2 -0
- package/dist/editor/SelectionMenu.js +102 -0
- package/dist/editor/attr-pencil-plugin.d.ts +17 -0
- package/dist/editor/attr-pencil-plugin.js +158 -0
- package/dist/editor/character-decoration-plugin.d.ts +26 -0
- package/dist/editor/character-decoration-plugin.js +77 -0
- package/dist/editor/character-view.d.ts +35 -0
- package/dist/editor/character-view.js +112 -0
- package/dist/editor/editor-bus.d.ts +14 -0
- package/dist/editor/editor-bus.js +39 -0
- package/dist/editor/find-highlight-plugin.d.ts +7 -0
- package/dist/editor/find-highlight-plugin.js +57 -0
- package/dist/editor/frontmatter-field-view.d.ts +10 -0
- package/dist/editor/frontmatter-field-view.js +48 -0
- package/dist/editor/frontmatter-field.d.ts +21 -0
- package/dist/editor/frontmatter-field.js +116 -0
- package/dist/editor/grapheme-cursor.d.ts +10 -0
- package/dist/editor/grapheme-cursor.js +104 -0
- package/dist/editor/ime-plugin.d.ts +18 -0
- package/dist/editor/ime-plugin.js +71 -0
- package/dist/editor/insert-helper.d.ts +1 -0
- package/dist/editor/insert-helper.js +42 -0
- package/dist/editor/keymap.d.ts +3 -0
- package/dist/editor/keymap.js +131 -0
- package/dist/editor/language-class-plugin.d.ts +7 -0
- package/dist/editor/language-class-plugin.js +75 -0
- package/dist/editor/plugins.d.ts +1 -0
- package/dist/editor/plugins.js +25 -0
- package/dist/editor/prosemirror-to-sutra.d.ts +2 -0
- package/dist/editor/prosemirror-to-sutra.js +208 -0
- package/dist/editor/scene-heading-view.d.ts +27 -0
- package/dist/editor/scene-heading-view.js +136 -0
- package/dist/editor/schema.d.ts +3 -0
- package/dist/editor/schema.js +135 -0
- package/dist/editor/sutra-to-prosemirror.d.ts +3 -0
- package/dist/editor/sutra-to-prosemirror.js +154 -0
- package/dist/editor/transliterate-input.d.ts +10 -0
- package/dist/editor/transliterate-input.js +88 -0
- package/dist/extensions/storage-adapter.d.ts +59 -0
- package/dist/extensions/storage-adapter.js +9 -0
- package/dist/extensions/types.d.ts +73 -0
- package/dist/extensions/types.js +12 -0
- package/dist/file/docx-exporter.d.ts +8 -0
- package/dist/file/docx-exporter.js +494 -0
- package/dist/file/docx-importer.d.ts +10 -0
- package/dist/file/docx-importer.js +283 -0
- package/dist/file/print-paginator.d.ts +29 -0
- package/dist/file/print-paginator.js +289 -0
- package/dist/file/templates.d.ts +2 -0
- package/dist/file/templates.js +66 -0
- package/dist/file/workflow-reports.d.ts +34 -0
- package/dist/file/workflow-reports.js +719 -0
- package/dist/findreplace/FindReplace.d.ts +2 -0
- package/dist/findreplace/FindReplace.js +164 -0
- package/dist/findreplace/find-engine.d.ts +29 -0
- package/dist/findreplace/find-engine.js +69 -0
- package/dist/findreplace/scroll-to-match.d.ts +29 -0
- package/dist/findreplace/scroll-to-match.js +74 -0
- package/dist/fonts/tofu-detect.d.ts +1 -0
- package/dist/fonts/tofu-detect.js +48 -0
- package/dist/i18n/LanguageContext.d.ts +10 -0
- package/dist/i18n/LanguageContext.js +174 -0
- package/dist/i18n/language-detect.d.ts +1 -0
- package/dist/i18n/language-detect.js +58 -0
- package/dist/i18n/useTranslation.d.ts +11 -0
- package/dist/i18n/useTranslation.js +60 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +5 -0
- package/dist/locales/bn.json +143 -0
- package/dist/locales/en.json +143 -0
- package/dist/locales/gu.json +143 -0
- package/dist/locales/hi.json +143 -0
- package/dist/locales/kn.json +143 -0
- package/dist/locales/ml.json +143 -0
- package/dist/locales/mr.json +143 -0
- package/dist/locales/or.json +143 -0
- package/dist/locales/pa.json +143 -0
- package/dist/locales/si.json +143 -0
- package/dist/locales/ta.json +143 -0
- package/dist/locales/te.json +143 -0
- package/dist/navigator/SceneNavigator.d.ts +2 -0
- package/dist/navigator/SceneNavigator.js +612 -0
- package/dist/navigator/StatisticsDialog.d.ts +6 -0
- package/dist/navigator/StatisticsDialog.js +146 -0
- package/dist/navigator/scene-list.d.ts +16 -0
- package/dist/navigator/scene-list.js +70 -0
- package/dist/settings/SettingsDialog.d.ts +6 -0
- package/dist/settings/SettingsDialog.js +147 -0
- package/dist/shell/AppBar.d.ts +2 -0
- package/dist/shell/AppBar.js +34 -0
- package/dist/shell/AppShell.d.ts +2 -0
- package/dist/shell/AppShell.js +30 -0
- package/dist/shell/ConfirmDialog.d.ts +16 -0
- package/dist/shell/ConfirmDialog.js +23 -0
- package/dist/shell/ElementToolbar.d.ts +2 -0
- package/dist/shell/ElementToolbar.js +30 -0
- package/dist/shell/LanguageStatus.d.ts +2 -0
- package/dist/shell/LanguageStatus.js +65 -0
- package/dist/shell/StatusBar.d.ts +2 -0
- package/dist/shell/StatusBar.js +54 -0
- package/dist/shell/SutrataLogo.d.ts +8 -0
- package/dist/shell/SutrataLogo.js +7 -0
- package/dist/shell/Toast.d.ts +12 -0
- package/dist/shell/Toast.js +21 -0
- package/dist/shell/ToolbarButton.d.ts +11 -0
- package/dist/shell/ToolbarButton.js +4 -0
- package/dist/shell/VersionHistory.d.ts +12 -0
- package/dist/shell/VersionHistory.js +89 -0
- package/dist/shell/Watermark.d.ts +3 -0
- package/dist/shell/Watermark.js +10 -0
- package/dist/shell/fonts/fonts.css +1133 -0
- package/dist/shell/icons.d.ts +45 -0
- package/dist/shell/icons.js +51 -0
- package/dist/shell/toolbar-actions.d.ts +6 -0
- package/dist/shell/toolbar-actions.js +58 -0
- package/dist/source/SourceView.d.ts +1 -0
- package/dist/source/SourceView.js +106 -0
- package/dist/source/find-highlight-source.d.ts +14 -0
- package/dist/source/find-highlight-source.js +47 -0
- package/dist/source/sutra-lang.d.ts +2 -0
- package/dist/source/sutra-lang.js +97 -0
- package/dist/spellcheck/dictionaries.d.ts +9 -0
- package/dist/spellcheck/dictionaries.js +16 -0
- package/dist/spellcheck/hunspell.d.ts +23 -0
- package/dist/spellcheck/hunspell.js +94 -0
- package/dist/spellcheck/spellcheck-plugin.d.ts +18 -0
- package/dist/spellcheck/spellcheck-plugin.js +75 -0
- package/dist/styles/StyleDialog.d.ts +6 -0
- package/dist/styles/StyleDialog.js +63 -0
- package/dist/styles/builtin-styles.d.ts +43 -0
- package/dist/styles/builtin-styles.js +131 -0
- package/dist/styles/css-adapter.d.ts +32 -0
- package/dist/styles/css-adapter.js +71 -0
- package/dist/styles/docx-adapter.d.ts +7 -0
- package/dist/styles/docx-adapter.js +84 -0
- package/dist/styles/embed-fonts.d.ts +20 -0
- package/dist/styles/embed-fonts.js +81 -0
- package/dist/styles/pagination.d.ts +19 -0
- package/dist/styles/pagination.js +22 -0
- package/dist/styles/print-adapter.d.ts +6 -0
- package/dist/styles/print-adapter.js +288 -0
- package/dist/styles/registry.d.ts +6 -0
- package/dist/styles/registry.js +14 -0
- package/dist/styles/screenplay.css +3006 -0
- package/dist/styles/script-ranges.d.ts +6 -0
- package/dist/styles/script-ranges.js +17 -0
- package/dist/styles/types.d.ts +93 -0
- package/dist/styles/types.js +18 -0
- package/dist/styles/validate.d.ts +12 -0
- package/dist/styles/validate.js +105 -0
- package/dist/templates/templates.d.ts +10 -0
- package/dist/templates/templates.js +181 -0
- package/dist/titlepage/ExportDialog.d.ts +1 -0
- package/dist/titlepage/ExportDialog.js +152 -0
- package/dist/titlepage/TitlePageDialog.d.ts +6 -0
- package/dist/titlepage/TitlePageDialog.js +96 -0
- package/dist/titlepage/frontmatter-form.d.ts +56 -0
- package/dist/titlepage/frontmatter-form.js +245 -0
- package/dist/titlepage/languages.d.ts +10 -0
- package/dist/titlepage/languages.js +31 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.js +1 -0
- package/package.json +47 -0
- package/scripts/copy-assets.mjs +16 -0
- package/scripts/generate-providers.mjs +127 -0
- package/src/ai/OnboardingDialog.tsx +390 -0
- package/src/ai/VoiceConfirmDialog.tsx +137 -0
- package/src/ai/VoiceToolbar.tsx +321 -0
- package/src/ai/ai-client.ts +596 -0
- package/src/ai/commands.ts +164 -0
- package/src/ai/generated-providers.json +3696 -0
- package/src/ai/prompts.ts +62 -0
- package/src/ai/voice-service.ts +96 -0
- package/src/autocomplete/AutocompletePlugin.ts +21 -0
- package/src/autocomplete/suggestions.ts +147 -0
- package/src/context/DocumentContext.tsx +648 -0
- package/src/desktop-api.d.ts +40 -0
- package/src/editor/EditorView.tsx +82 -0
- package/src/editor/SelectionMenu.tsx +152 -0
- package/src/editor/attr-pencil-plugin.ts +172 -0
- package/src/editor/character-decoration-plugin.ts +104 -0
- package/src/editor/editor-bus.ts +51 -0
- package/src/editor/find-highlight-plugin.ts +68 -0
- package/src/editor/frontmatter-field-view.ts +52 -0
- package/src/editor/frontmatter-field.ts +115 -0
- package/src/editor/grapheme-cursor.ts +99 -0
- package/src/editor/ime-plugin.ts +85 -0
- package/src/editor/insert-helper.ts +43 -0
- package/src/editor/keymap.ts +124 -0
- package/src/editor/language-class-plugin.ts +91 -0
- package/src/editor/plugins.ts +26 -0
- package/src/editor/prosemirror-to-sutra.ts +192 -0
- package/src/editor/scene-heading-view.ts +147 -0
- package/src/editor/schema.ts +156 -0
- package/src/editor/sutra-to-prosemirror.ts +156 -0
- package/src/extensions/storage-adapter.ts +65 -0
- package/src/extensions/types.ts +60 -0
- package/src/file/docx-exporter.ts +585 -0
- package/src/file/docx-importer.ts +313 -0
- package/src/file/print-paginator.ts +301 -0
- package/src/file/templates.ts +73 -0
- package/src/file/workflow-reports.ts +820 -0
- package/src/findreplace/FindReplace.tsx +242 -0
- package/src/findreplace/find-engine.ts +97 -0
- package/src/findreplace/scroll-to-match.ts +89 -0
- package/src/fonts/tofu-detect.ts +53 -0
- package/src/i18n/LanguageContext.tsx +207 -0
- package/src/i18n/language-detect.ts +73 -0
- package/src/i18n/useTranslation.tsx +74 -0
- package/src/index.ts +27 -0
- package/src/locales/bn.json +143 -0
- package/src/locales/en.json +143 -0
- package/src/locales/gu.json +143 -0
- package/src/locales/hi.json +143 -0
- package/src/locales/kn.json +143 -0
- package/src/locales/ml.json +143 -0
- package/src/locales/mr.json +143 -0
- package/src/locales/or.json +143 -0
- package/src/locales/pa.json +143 -0
- package/src/locales/si.json +143 -0
- package/src/locales/ta.json +143 -0
- package/src/locales/te.json +143 -0
- package/src/navigator/SceneNavigator.tsx +844 -0
- package/src/navigator/StatisticsDialog.tsx +249 -0
- package/src/navigator/scene-list.ts +84 -0
- package/src/settings/SettingsDialog.tsx +303 -0
- package/src/shell/AppBar.tsx +354 -0
- package/src/shell/AppShell.tsx +71 -0
- package/src/shell/ConfirmDialog.tsx +71 -0
- package/src/shell/ElementToolbar.tsx +175 -0
- package/src/shell/LanguageStatus.tsx +101 -0
- package/src/shell/StatusBar.tsx +135 -0
- package/src/shell/SutrataLogo.tsx +89 -0
- package/src/shell/Toast.tsx +46 -0
- package/src/shell/ToolbarButton.tsx +30 -0
- package/src/shell/VersionHistory.tsx +212 -0
- package/src/shell/Watermark.tsx +9 -0
- package/src/shell/fonts/fonts.css +1133 -0
- package/src/shell/icons.tsx +335 -0
- package/src/shell/toolbar-actions.ts +59 -0
- package/src/source/SourceView.tsx +111 -0
- package/src/source/find-highlight-source.ts +57 -0
- package/src/source/sutra-lang.ts +121 -0
- package/src/spellcheck/dictionaries.ts +21 -0
- package/src/spellcheck/hunspell.ts +110 -0
- package/src/spellcheck/spellcheck-plugin.ts +83 -0
- package/src/styles/StyleDialog.tsx +148 -0
- package/src/styles/builtin-styles.ts +139 -0
- package/src/styles/css-adapter.ts +76 -0
- package/src/styles/docx-adapter.ts +109 -0
- package/src/styles/embed-fonts.ts +81 -0
- package/src/styles/pagination.ts +27 -0
- package/src/styles/print-adapter.ts +288 -0
- package/src/styles/registry.ts +20 -0
- package/src/styles/screenplay.css +3006 -0
- package/src/styles/script-ranges.ts +17 -0
- package/src/styles/types.ts +104 -0
- package/src/styles/validate.ts +114 -0
- package/src/templates/templates.ts +194 -0
- package/src/titlepage/ExportDialog.tsx +494 -0
- package/src/titlepage/TitlePageDialog.tsx +312 -0
- package/src/titlepage/frontmatter-form.ts +280 -0
- package/src/titlepage/languages.ts +37 -0
- package/src/types.ts +7 -0
- package/tests/AppBar.test.tsx +35 -0
- package/tests/AppShell.test.tsx +22 -0
- package/tests/ElementToolbar.test.tsx +36 -0
- package/tests/LanguageStatus.test.tsx +129 -0
- package/tests/SelectionMenu.test.tsx +71 -0
- package/tests/ToolbarButton.test.tsx +24 -0
- package/tests/ai-onboarding.test.ts +102 -0
- package/tests/ai-status.test.tsx +72 -0
- package/tests/ai-structured.test.ts +94 -0
- package/tests/autocomplete/AutocompletePlugin.test.ts +20 -0
- package/tests/autocomplete/suggestions.test.ts +106 -0
- package/tests/document-context.test.tsx +565 -0
- package/tests/docx-importer.test.ts +160 -0
- package/tests/editor/character-decoration-plugin.test.ts +101 -0
- package/tests/editor/character-extension.test.ts +44 -0
- package/tests/editor/grapheme-cursor.test.ts +118 -0
- package/tests/editor/ime-plugin.test.ts +72 -0
- package/tests/editor/inline-note-bridge.test.ts +32 -0
- package/tests/editor/language-class-plugin.test.ts +265 -0
- package/tests/editor/title-page.test.ts +124 -0
- package/tests/embed-fonts.test.ts +17 -0
- package/tests/exporters.test.ts +365 -0
- package/tests/findreplace/FindReplace.test.tsx +391 -0
- package/tests/findreplace/find-engine.test.ts +86 -0
- package/tests/findreplace/scroll-to-match.test.ts +50 -0
- package/tests/fonts/tofu-detect.test.ts +122 -0
- package/tests/i18n/LanguageContext.test.tsx +146 -0
- package/tests/i18n/language-detect.test.ts +54 -0
- package/tests/i18n/locale-parity.test.ts +25 -0
- package/tests/i18n/useTranslation.test.tsx +45 -0
- package/tests/keymap.test.ts +18 -0
- package/tests/navigator/SceneNavigator.test.tsx +177 -0
- package/tests/navigator/StatisticsDialog.test.tsx +63 -0
- package/tests/navigator/scene-list.test.ts +71 -0
- package/tests/print-paginator.test.ts +82 -0
- package/tests/shell/VersionHistory.test.tsx +129 -0
- package/tests/spellcheck/hunspell.test.ts +82 -0
- package/tests/spellcheck/spellcheck-plugin.test.ts +56 -0
- package/tests/styles.test.ts +315 -0
- package/tests/templates/templates.test.ts +83 -0
- package/tests/titlepage/TitlePageDialog.test.tsx +90 -0
- package/tests/titlepage/apply-title-page.test.ts +61 -0
- package/tests/titlepage/frontmatter-form.test.ts +115 -0
- package/tests/toolbar-actions.test.ts +93 -0
- package/tests/voice-commands.test.ts +58 -0
- package/tsconfig.json +17 -0
- package/vitest.config.ts +11 -0
- package/vitest.setup.ts +65 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Unicode block ranges for the complex scripts Sutrata recognizes, keyed by the same
|
|
2
|
+
* lang codes used throughout (docx-exporter.ts's SCRIPT_FONT_BY_LANG, types.ts's
|
|
3
|
+
* COMPLEX_SCRIPT_CODES, complexScriptFontOverrides / italicScripts). Shared so
|
|
4
|
+
* docx-exporter.ts (font override) and workflow-reports.ts (forced-italic wrapping in the
|
|
5
|
+
* print/PDF HTML) test the same ranges rather than maintaining two copies that could drift. */
|
|
6
|
+
export declare const SCRIPT_UNICODE_RANGES: Record<string, RegExp>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Unicode block ranges for the complex scripts Sutrata recognizes, keyed by the same
|
|
2
|
+
* lang codes used throughout (docx-exporter.ts's SCRIPT_FONT_BY_LANG, types.ts's
|
|
3
|
+
* COMPLEX_SCRIPT_CODES, complexScriptFontOverrides / italicScripts). Shared so
|
|
4
|
+
* docx-exporter.ts (font override) and workflow-reports.ts (forced-italic wrapping in the
|
|
5
|
+
* print/PDF HTML) test the same ranges rather than maintaining two copies that could drift. */
|
|
6
|
+
export const SCRIPT_UNICODE_RANGES = {
|
|
7
|
+
hi: /[ऀ-ॿ]/,
|
|
8
|
+
ta: /[-]/,
|
|
9
|
+
te: /[ఀ-౿]/,
|
|
10
|
+
kn: /[ಀ-]/,
|
|
11
|
+
ml: /[ഀ-ൿ]/,
|
|
12
|
+
bn: /[ঀ-]/,
|
|
13
|
+
gu: /[-૿]/,
|
|
14
|
+
pa: /[-]/,
|
|
15
|
+
or: /[-]/,
|
|
16
|
+
si: /[-]/,
|
|
17
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/** Screenplay body elements that carry a visual style — one entry per docx-exporter.ts
|
|
2
|
+
* paragraph style / screenplay.css class. Excludes structural/chrome things
|
|
3
|
+
* (scene metadata, page breaks, parenthetical parens) that aren't stylistic choices. */
|
|
4
|
+
export declare const BODY_ELEMENT_KEYS: readonly ["sceneHeading", "action", "character", "parenthetical", "dialogue", "transition", "centered", "lyrics", "section", "note", "comment"];
|
|
5
|
+
export type BodyElementKey = typeof BODY_ELEMENT_KEYS[number];
|
|
6
|
+
/** Cover-page elements. Export-only (DOCX/PDF) — there is no live editor
|
|
7
|
+
* preview of the cover page today, so these never produce editor CSS vars. */
|
|
8
|
+
export declare const COVER_ELEMENT_KEYS: readonly ["title", "author", "logline"];
|
|
9
|
+
export type CoverElementKey = typeof COVER_ELEMENT_KEYS[number];
|
|
10
|
+
export interface ElementStyle {
|
|
11
|
+
fontSizePt: number;
|
|
12
|
+
bold?: boolean;
|
|
13
|
+
italic?: boolean;
|
|
14
|
+
underline?: boolean;
|
|
15
|
+
/** 6 hex digits, no leading '#' (docx-native shape; CSS adapter adds the '#'). */
|
|
16
|
+
color?: string;
|
|
17
|
+
/** Percentage of the printable content width. */
|
|
18
|
+
marginLeftPct?: number;
|
|
19
|
+
marginRightPct?: number;
|
|
20
|
+
align?: 'left' | 'center' | 'right';
|
|
21
|
+
/** Space before/after the paragraph, in points. DOCX-only: the live editor
|
|
22
|
+
* only ever applies `spaceBeforePt` (as margin-top); adjacent elements in
|
|
23
|
+
* the editor create their gap from the *following* element's margin-top,
|
|
24
|
+
* not from a margin-bottom on the preceding one, so `spaceAfterPt` has no
|
|
25
|
+
* editor-CSS effect — see css-adapter.ts. */
|
|
26
|
+
spaceBeforePt?: number;
|
|
27
|
+
spaceAfterPt?: number;
|
|
28
|
+
}
|
|
29
|
+
export type ScreenplayElements = Record<BodyElementKey, ElementStyle> & Record<CoverElementKey, ElementStyle>;
|
|
30
|
+
/** How the exported cover page arranges its blocks (DOCX + PDF; there is no editor
|
|
31
|
+
* preview of the cover). 'centered' stacks everything down the middle of the page;
|
|
32
|
+
* 'corners' puts title / "by <author>" / logline flush top-left and date + contact
|
|
33
|
+
* down in the bottom-right, the arrangement Tamil shooting scripts print. Per-element
|
|
34
|
+
* font/size/color still come from `elements.title|author|logline` either way — this
|
|
35
|
+
* chooses the *placement* those elements are laid out in, nothing else. */
|
|
36
|
+
export type CoverLayout = 'centered' | 'corners';
|
|
37
|
+
export declare const COVER_LAYOUTS: readonly CoverLayout[];
|
|
38
|
+
export declare const DEFAULT_COVER_LAYOUT: CoverLayout;
|
|
39
|
+
export interface ScreenplayStyleDefinition {
|
|
40
|
+
/** Stable slug: persisted in frontmatter's `style:` field and as the IndexedDB key. */
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
/** Latin body font. For the live editor and PDF/print export this is just the
|
|
45
|
+
* first entry in a CSS font stack (screenplay.css / print-adapter.ts put
|
|
46
|
+
* 'Noto Sans'/script-specific Noto Sans fonts after it), so the browser's own
|
|
47
|
+
* per-glyph fallback transparently covers any script this font doesn't have —
|
|
48
|
+
* no per-style CSS plumbing needed. DOCX has no such per-glyph fallback
|
|
49
|
+
* (a `w:rFonts` run property names exactly one font), so a style whose body
|
|
50
|
+
* font *does* cover a non-Latin script it cares about (e.g. Traditional's
|
|
51
|
+
* Vijaya for Tamil) must say so explicitly via `complexScriptFontOverrides`. */
|
|
52
|
+
fontFamily: string;
|
|
53
|
+
/** Per-script font override, keyed by the same lang codes docx-exporter.ts's
|
|
54
|
+
* getComplexFont switches on ('ta', 'hi', 'te', ...). Omit a script and it keeps
|
|
55
|
+
* using the shared default (e.g. 'Noto Sans Tamil') — this is an opt-in exception,
|
|
56
|
+
* not a requirement to theme every script. Consumed by all three renderers:
|
|
57
|
+
* DOCX (docx-exporter.ts, where it's required — a `w:rFonts` run property names
|
|
58
|
+
* exactly one font, so there's no per-glyph fallback to lean on), the live editor
|
|
59
|
+
* (css-adapter.ts turns each entry into a `--cs-lang-<code>-font-family` CSS var
|
|
60
|
+
* that screenplay.css's `.cs-lang-<code>` rules prefer over the generic Noto Sans
|
|
61
|
+
* fallback), and PDF/print (print-adapter.ts folds the values into the body font
|
|
62
|
+
* stack ahead of the generic Indic fallbacks). */
|
|
63
|
+
complexScriptFontOverrides?: Record<string, string>;
|
|
64
|
+
/** Lang codes (same set as above) whose *default* presentation is italic under this
|
|
65
|
+
* style — not "emphasized text happens to be italic", but "upright is not this
|
|
66
|
+
* script's normal look here" (e.g. Traditional's Tamil, styled after Noto Serif
|
|
67
|
+
* Tamil Italic to approximate Vijaya's slant). Forces italic even where the
|
|
68
|
+
* element itself isn't (bold scene headings included) — confirmed as the intended
|
|
69
|
+
* behavior, not layered underneath per-element italic. DOCX applies this
|
|
70
|
+
* unconditionally per run; the editor applies it via a `.cs-force-italic-<code>`
|
|
71
|
+
* class (see EditorView.tsx/css-adapter.ts) rather than a plain CSS var, because a
|
|
72
|
+
* var would unconditionally set `font-style` and stomp elements that are
|
|
73
|
+
* legitimately italic for their own reasons (lyrics, comments) on styles that
|
|
74
|
+
* don't list the script here. PDF/print has no per-span language tagging, so this
|
|
75
|
+
* has no effect there — a known, documented gap, not an oversight. */
|
|
76
|
+
italicScripts?: string[];
|
|
77
|
+
/** Defaults to DEFAULT_COVER_LAYOUT when a style (or an imported one) omits it. */
|
|
78
|
+
coverLayout?: CoverLayout;
|
|
79
|
+
page: {
|
|
80
|
+
marginTopIn: number;
|
|
81
|
+
marginBottomIn: number;
|
|
82
|
+
marginLeftIn: number;
|
|
83
|
+
marginRightIn: number;
|
|
84
|
+
};
|
|
85
|
+
elements: ScreenplayElements;
|
|
86
|
+
}
|
|
87
|
+
export declare const DEFAULT_STYLE_ID = "classic";
|
|
88
|
+
/** Lang codes docx-exporter.ts's getComplexFont / SCRIPT_RANGES recognize — the full
|
|
89
|
+
* key space for `complexScriptFontOverrides` / `italicScripts`. Iterated by
|
|
90
|
+
* css-adapter.ts to clear a previous style's per-script vars/classes when switching
|
|
91
|
+
* to a style that doesn't set them (so nothing lingers). */
|
|
92
|
+
export declare const COMPLEX_SCRIPT_CODES: readonly ["hi", "ta", "te", "kn", "ml", "bn", "gu", "pa", "or", "si"];
|
|
93
|
+
export type ComplexScriptCode = typeof COMPLEX_SCRIPT_CODES[number];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Screenplay body elements that carry a visual style — one entry per docx-exporter.ts
|
|
2
|
+
* paragraph style / screenplay.css class. Excludes structural/chrome things
|
|
3
|
+
* (scene metadata, page breaks, parenthetical parens) that aren't stylistic choices. */
|
|
4
|
+
export const BODY_ELEMENT_KEYS = [
|
|
5
|
+
'sceneHeading', 'action', 'character', 'parenthetical', 'dialogue',
|
|
6
|
+
'transition', 'centered', 'lyrics', 'section', 'note', 'comment',
|
|
7
|
+
];
|
|
8
|
+
/** Cover-page elements. Export-only (DOCX/PDF) — there is no live editor
|
|
9
|
+
* preview of the cover page today, so these never produce editor CSS vars. */
|
|
10
|
+
export const COVER_ELEMENT_KEYS = ['title', 'author', 'logline'];
|
|
11
|
+
export const COVER_LAYOUTS = ['centered', 'corners'];
|
|
12
|
+
export const DEFAULT_COVER_LAYOUT = 'centered';
|
|
13
|
+
export const DEFAULT_STYLE_ID = 'classic';
|
|
14
|
+
/** Lang codes docx-exporter.ts's getComplexFont / SCRIPT_RANGES recognize — the full
|
|
15
|
+
* key space for `complexScriptFontOverrides` / `italicScripts`. Iterated by
|
|
16
|
+
* css-adapter.ts to clear a previous style's per-script vars/classes when switching
|
|
17
|
+
* to a style that doesn't set them (so nothing lingers). */
|
|
18
|
+
export const COMPLEX_SCRIPT_CODES = ['hi', 'ta', 'te', 'kn', 'ml', 'bn', 'gu', 'pa', 'or', 'si'];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ScreenplayStyleDefinition } from './types.js';
|
|
2
|
+
export type ValidationResult = {
|
|
3
|
+
ok: true;
|
|
4
|
+
style: ScreenplayStyleDefinition;
|
|
5
|
+
} | {
|
|
6
|
+
ok: false;
|
|
7
|
+
errors: string[];
|
|
8
|
+
};
|
|
9
|
+
/** Hand-rolled structural validation for a style imported from a .json file — no schema
|
|
10
|
+
* library dependency, matching the codebase's other hand-rolled parsers (frontmatter.ts).
|
|
11
|
+
* Collects every error found rather than failing on the first, for a useful import message. */
|
|
12
|
+
export declare function validateStyleJson(data: unknown): ValidationResult;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { BODY_ELEMENT_KEYS, COVER_ELEMENT_KEYS, COVER_LAYOUTS } from './types.js';
|
|
2
|
+
const ALIGNS = new Set(['left', 'center', 'right']);
|
|
3
|
+
const COLOR_RE = /^[0-9a-fA-F]{6}$/;
|
|
4
|
+
function isFiniteNumber(v) {
|
|
5
|
+
return typeof v === 'number' && Number.isFinite(v);
|
|
6
|
+
}
|
|
7
|
+
function validateElement(path, data, errors) {
|
|
8
|
+
if (typeof data !== 'object' || data === null) {
|
|
9
|
+
errors.push(`${path}: expected an object`);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const el = data;
|
|
13
|
+
if (!isFiniteNumber(el['fontSizePt']) || el['fontSizePt'] <= 0) {
|
|
14
|
+
errors.push(`${path}.fontSizePt: expected a positive number`);
|
|
15
|
+
}
|
|
16
|
+
for (const flag of ['bold', 'italic', 'underline']) {
|
|
17
|
+
if (el[flag] !== undefined && typeof el[flag] !== 'boolean') {
|
|
18
|
+
errors.push(`${path}.${flag}: expected a boolean`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (el['color'] !== undefined && (typeof el['color'] !== 'string' || !COLOR_RE.test(el['color']))) {
|
|
22
|
+
errors.push(`${path}.color: expected 6 hex digits with no '#' (e.g. "0A5C6A")`);
|
|
23
|
+
}
|
|
24
|
+
for (const marginKey of ['marginLeftPct', 'marginRightPct']) {
|
|
25
|
+
const v = el[marginKey];
|
|
26
|
+
if (v !== undefined && (!isFiniteNumber(v) || v < 0 || v > 100)) {
|
|
27
|
+
errors.push(`${path}.${marginKey}: expected a number between 0 and 100`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (el['align'] !== undefined && (typeof el['align'] !== 'string' || !ALIGNS.has(el['align']))) {
|
|
31
|
+
errors.push(`${path}.align: expected "left", "center", or "right"`);
|
|
32
|
+
}
|
|
33
|
+
for (const spaceKey of ['spaceBeforePt', 'spaceAfterPt']) {
|
|
34
|
+
const v = el[spaceKey];
|
|
35
|
+
if (v !== undefined && !isFiniteNumber(v)) {
|
|
36
|
+
errors.push(`${path}.${spaceKey}: expected a number`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Hand-rolled structural validation for a style imported from a .json file — no schema
|
|
41
|
+
* library dependency, matching the codebase's other hand-rolled parsers (frontmatter.ts).
|
|
42
|
+
* Collects every error found rather than failing on the first, for a useful import message. */
|
|
43
|
+
export function validateStyleJson(data) {
|
|
44
|
+
const errors = [];
|
|
45
|
+
if (typeof data !== 'object' || data === null) {
|
|
46
|
+
return { ok: false, errors: ['Expected a JSON object'] };
|
|
47
|
+
}
|
|
48
|
+
const obj = data;
|
|
49
|
+
for (const key of ['id', 'name', 'fontFamily']) {
|
|
50
|
+
if (typeof obj[key] !== 'string' || obj[key] === '') {
|
|
51
|
+
errors.push(`${key}: expected a non-empty string`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (obj['description'] !== undefined && typeof obj['description'] !== 'string') {
|
|
55
|
+
errors.push('description: expected a string');
|
|
56
|
+
}
|
|
57
|
+
if (obj['complexScriptFontOverrides'] !== undefined) {
|
|
58
|
+
const overrides = obj['complexScriptFontOverrides'];
|
|
59
|
+
if (typeof overrides !== 'object' || overrides === null) {
|
|
60
|
+
errors.push('complexScriptFontOverrides: expected an object');
|
|
61
|
+
}
|
|
62
|
+
else if (Object.values(overrides).some(v => typeof v !== 'string' || v === '')) {
|
|
63
|
+
errors.push('complexScriptFontOverrides: expected every value to be a non-empty string');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (obj['italicScripts'] !== undefined) {
|
|
67
|
+
const scripts = obj['italicScripts'];
|
|
68
|
+
if (!Array.isArray(scripts) || scripts.some(v => typeof v !== 'string' || v === '')) {
|
|
69
|
+
errors.push('italicScripts: expected an array of non-empty strings');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (obj['coverLayout'] !== undefined && !COVER_LAYOUTS.includes(obj['coverLayout'])) {
|
|
73
|
+
errors.push(`coverLayout: expected ${COVER_LAYOUTS.map(l => `"${l}"`).join(' or ')}`);
|
|
74
|
+
}
|
|
75
|
+
const page = obj['page'];
|
|
76
|
+
if (typeof page !== 'object' || page === null) {
|
|
77
|
+
errors.push('page: expected an object');
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
const p = page;
|
|
81
|
+
for (const key of ['marginTopIn', 'marginBottomIn', 'marginLeftIn', 'marginRightIn']) {
|
|
82
|
+
if (!isFiniteNumber(p[key]) || p[key] < 0) {
|
|
83
|
+
errors.push(`page.${key}: expected a non-negative number`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const elements = obj['elements'];
|
|
88
|
+
if (typeof elements !== 'object' || elements === null) {
|
|
89
|
+
errors.push('elements: expected an object');
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const el = elements;
|
|
93
|
+
for (const key of [...BODY_ELEMENT_KEYS, ...COVER_ELEMENT_KEYS]) {
|
|
94
|
+
if (!(key in el)) {
|
|
95
|
+
errors.push(`elements.${key}: missing`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
validateElement(`elements.${key}`, el[key], errors);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (errors.length > 0)
|
|
103
|
+
return { ok: false, errors };
|
|
104
|
+
return { ok: true, style: obj };
|
|
105
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Localized screenplay templates for each supported script.
|
|
3
|
+
* Returns a starter Sutra document in the target language.
|
|
4
|
+
*/
|
|
5
|
+
export declare const TEMPLATES: Record<string, string>;
|
|
6
|
+
/**
|
|
7
|
+
* Get the starter template for a given language code.
|
|
8
|
+
* Falls back to English if no template for the requested language.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getTemplate(lang: string): string;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Localized screenplay templates for each supported script.
|
|
3
|
+
* Returns a starter Sutra document in the target language.
|
|
4
|
+
*/
|
|
5
|
+
export const TEMPLATES = {
|
|
6
|
+
en: `---
|
|
7
|
+
title: Untitled Screenplay
|
|
8
|
+
lang: en
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## INT. ROOM - DAY
|
|
12
|
+
|
|
13
|
+
A writer sits at a desk, staring at a blank screen.
|
|
14
|
+
|
|
15
|
+
@WRITER
|
|
16
|
+
(to themselves)
|
|
17
|
+
Where do I begin?
|
|
18
|
+
|
|
19
|
+
`,
|
|
20
|
+
hi: `---
|
|
21
|
+
title: अनाम पटकथा
|
|
22
|
+
lang: hi
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## आंत. कमरा - दिन
|
|
26
|
+
|
|
27
|
+
एक लेखक मेज पर बैठा है, खाली स्क्रीन को देख रहा है।
|
|
28
|
+
|
|
29
|
+
@लेखक
|
|
30
|
+
(खुद से)
|
|
31
|
+
मैं कहाँ से शुरू करूँ?
|
|
32
|
+
|
|
33
|
+
`,
|
|
34
|
+
ta: `---
|
|
35
|
+
title: தலைப்பற்ற திரைக்கதை
|
|
36
|
+
lang: ta
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## உள்ளே. அறை - பகல்
|
|
40
|
+
|
|
41
|
+
ஒரு எழுத்தாளர் மேசையில் அமர்ந்து, வெற்று திரையை பார்க்கிறார்.
|
|
42
|
+
|
|
43
|
+
@எழுத்தாளர்
|
|
44
|
+
(தனக்கே)
|
|
45
|
+
நான் எங்கிருந்து தொடங்குவது?
|
|
46
|
+
|
|
47
|
+
`,
|
|
48
|
+
te: `---
|
|
49
|
+
title: పేరులేని స్క్రీన్ప్లే
|
|
50
|
+
lang: te
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## లోపల. గది - పగలు
|
|
54
|
+
|
|
55
|
+
ఒక రచయిత బల్లదగ్గర కూర్చుని, ఖాళీ స్క్రీన్ని చూస్తున్నారు.
|
|
56
|
+
|
|
57
|
+
@రచయిత
|
|
58
|
+
(తమలో తాము)
|
|
59
|
+
నేను ఎక్కడ నుండి ప్రారంభించాలి?
|
|
60
|
+
|
|
61
|
+
`,
|
|
62
|
+
kn: `---
|
|
63
|
+
title: ಹೆಸರಿಲ್ಲದ ಚಲನಚಿತ್ರ ಕಥೆ
|
|
64
|
+
lang: kn
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## ಒಳಗೆ. ಕೋಣೆ - ಹಗಲು
|
|
68
|
+
|
|
69
|
+
ಒಬ್ಬ ಬರಹಗಾರ ಮೇಜಿನ ಬಳಿ ಕುಳಿತು, ಖಾಲಿ ಪರದೆಯನ್ನು ನೋಡುತ್ತಿದ್ದಾರೆ.
|
|
70
|
+
|
|
71
|
+
@ಬರಹಗಾರ
|
|
72
|
+
(ತಮ್ಮಷ್ಟಕ್ಕೆ)
|
|
73
|
+
ನಾನು ಎಲ್ಲಿಂದ ಪ್ರಾರಂಭಿಸಲಿ?
|
|
74
|
+
|
|
75
|
+
`,
|
|
76
|
+
ml: `---
|
|
77
|
+
title: പേരില്ലാത്ത തിരക്കഥ
|
|
78
|
+
lang: ml
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## അകത്ത്. മുറി - പകൽ
|
|
82
|
+
|
|
83
|
+
ഒരു എഴുത്തുകാരൻ മേശയ്ക്കരുകിൽ ഇരുന്ന്, ശൂന്യമായ സ്ക്രീൻ നോക്കുന്നു.
|
|
84
|
+
|
|
85
|
+
@എഴുത്തുകാരൻ
|
|
86
|
+
(സ്വയം)
|
|
87
|
+
ഞാൻ എവിടെ നിന്ന് തുടങ്ങണം?
|
|
88
|
+
|
|
89
|
+
`,
|
|
90
|
+
bn: `---
|
|
91
|
+
title: নামহীন চিত্রনাট্য
|
|
92
|
+
lang: bn
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## ভেতরে. ঘর - দিন
|
|
96
|
+
|
|
97
|
+
একজন লেখক টেবিলের পাশে বসে, ফাঁকা পর্দার দিকে তাকিয়ে আছেন।
|
|
98
|
+
|
|
99
|
+
@লেখক
|
|
100
|
+
(নিজের মনে)
|
|
101
|
+
আমি কোথা থেকে শুরু করব?
|
|
102
|
+
|
|
103
|
+
`,
|
|
104
|
+
gu: `---
|
|
105
|
+
title: અનામ પટકથા
|
|
106
|
+
lang: gu
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## અંદર. ઓરડો - દિવસ
|
|
110
|
+
|
|
111
|
+
એક લેખક ટેબલ પર બેઠો છે, ખાલી સ્ક્રીન જોઈ રહ્યો છે.
|
|
112
|
+
|
|
113
|
+
@લેખક
|
|
114
|
+
(પોતાની જાતને)
|
|
115
|
+
હું ક્યાંથી શરૂ કરું?
|
|
116
|
+
|
|
117
|
+
`,
|
|
118
|
+
mr: `---
|
|
119
|
+
title: अनाम पटकथा
|
|
120
|
+
lang: mr
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## आत. खोली - दिवस
|
|
124
|
+
|
|
125
|
+
एक लेखक टेबलावर बसला आहे, रिकाम्या स्क्रीनकडे पाहत आहे.
|
|
126
|
+
|
|
127
|
+
@लेखक
|
|
128
|
+
(स्वतःशी)
|
|
129
|
+
मी कुठून सुरुवात करू?
|
|
130
|
+
|
|
131
|
+
`,
|
|
132
|
+
pa: `---
|
|
133
|
+
title: ਬੇਨਾਮ ਸਕ੍ਰੀਨਪਲੇ
|
|
134
|
+
lang: pa
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## ਅੰਦਰ. ਕਮਰਾ - ਦਿਨ
|
|
138
|
+
|
|
139
|
+
ਇੱਕ ਲੇਖਕ ਮੇਜ਼ ਕੋਲ ਬੈਠਾ ਹੈ, ਖਾਲੀ ਸਕ੍ਰੀਨ ਵੱਲ ਦੇਖ ਰਿਹਾ ਹੈ।
|
|
140
|
+
|
|
141
|
+
@ਲੇਖਕ
|
|
142
|
+
(ਆਪਣੇ ਆਪ ਨੂੰ)
|
|
143
|
+
ਮੈਂ ਕਿੱਥੋਂ ਸ਼ੁਰੂ ਕਰਾਂ?
|
|
144
|
+
|
|
145
|
+
`,
|
|
146
|
+
or: `---
|
|
147
|
+
title: ଅନାମ ଚଳଚ୍ଚିତ୍ର ଲିପି
|
|
148
|
+
lang: or
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## ଭିତର. କୋଠରୀ - ଦିନ
|
|
152
|
+
|
|
153
|
+
ଜଣେ ଲେଖକ ଟେବୁଲ ପାଖରେ ବସି, ଖାଲି ସ୍କ୍ରୀନ ଆଡ଼କୁ ଚାହିଁ ରହିଛନ୍ତି।
|
|
154
|
+
|
|
155
|
+
@ଲେଖକ
|
|
156
|
+
(ନିଜ ମନରେ)
|
|
157
|
+
ମୁଁ କୁଆଡ଼ୁ ଆରମ୍ଭ କରିବି?
|
|
158
|
+
|
|
159
|
+
`,
|
|
160
|
+
si: `---
|
|
161
|
+
title: නම් නොකළ තිර රචනය
|
|
162
|
+
lang: si
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## ඇතුළත. කාමරය - දවල්
|
|
166
|
+
|
|
167
|
+
ලේඛකයෙක් මේසයක් ළඟ වාඩි වී, හිස් තිරයක් දෙස බලා සිටියි.
|
|
168
|
+
|
|
169
|
+
@ලේඛකයා
|
|
170
|
+
(තමන්ටම)
|
|
171
|
+
මම කොහෙන්ද පටන් ගන්නේ?
|
|
172
|
+
|
|
173
|
+
`,
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Get the starter template for a given language code.
|
|
177
|
+
* Falls back to English if no template for the requested language.
|
|
178
|
+
*/
|
|
179
|
+
export function getTemplate(lang) {
|
|
180
|
+
return TEMPLATES[lang] ?? TEMPLATES['en'] ?? '';
|
|
181
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ExportDialog(): import("react").JSX.Element;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { useDocument } from '../context/DocumentContext';
|
|
4
|
+
import { exportFountain, parse, romanizeSutra } from '@sutrata/parser';
|
|
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
|
+
import { allStyles } from '../styles/registry';
|
|
9
|
+
export function ExportDialog() {
|
|
10
|
+
const { ast, text, filePath, setExportVisible, resolvedStyle, customStyles, showToast } = useDocument();
|
|
11
|
+
const { t } = useTranslation();
|
|
12
|
+
const [tab, setTab] = useState('screenplay');
|
|
13
|
+
const [format, setFormat] = useState('pdf');
|
|
14
|
+
const [warnings, setWarnings] = useState([]);
|
|
15
|
+
const [isExporting, setIsExporting] = useState(false);
|
|
16
|
+
const [styleOverride, setStyleOverride] = useState('');
|
|
17
|
+
const [romanizeScope, setRomanizeScope] = useState('off');
|
|
18
|
+
const [romanizeVariant, setRomanizeVariant] = useState('strict');
|
|
19
|
+
const [romanizeReports, setRomanizeReports] = useState(false);
|
|
20
|
+
const [skipNotes, setSkipNotes] = useState(false);
|
|
21
|
+
const hasIndicText = /[\u0900-\u0DFF]/.test(text);
|
|
22
|
+
const styleChoices = allStyles(customStyles);
|
|
23
|
+
const showStylePicker = tab === 'screenplay' && (format === 'docx' || format === 'pdf');
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const onKey = (e) => {
|
|
26
|
+
if (e.key === 'Escape')
|
|
27
|
+
setExportVisible(false);
|
|
28
|
+
};
|
|
29
|
+
document.addEventListener('keydown', onKey);
|
|
30
|
+
return () => document.removeEventListener('keydown', onKey);
|
|
31
|
+
}, [setExportVisible]);
|
|
32
|
+
const baseName = (filePath ?? 'screenplay').replace(/\.[^.]+$/, '');
|
|
33
|
+
/** The AST handed to the PDF/DOCX exporters: the document itself, or a romanized copy
|
|
34
|
+
* (Indic → ISO 15919, export only; the .sutra text is never changed). */
|
|
35
|
+
function screenplayAst() {
|
|
36
|
+
if (romanizeScope === 'off' || !hasIndicText)
|
|
37
|
+
return { doc: ast, suffix: '' };
|
|
38
|
+
const romanized = romanizeSutra(text, { scope: romanizeScope, variant: romanizeVariant });
|
|
39
|
+
return { doc: parse(romanized), suffix: '_romanized' };
|
|
40
|
+
}
|
|
41
|
+
/** Workflow-report data, from a fully romanized copy when that option is on. */
|
|
42
|
+
function reportData() {
|
|
43
|
+
if (!romanizeReports || !hasIndicText)
|
|
44
|
+
return { data: extractWorkflowData(ast), suffix: '' };
|
|
45
|
+
const romanized = romanizeSutra(text, { scope: 'all', variant: romanizeVariant, notice: false });
|
|
46
|
+
return { data: extractWorkflowData(parse(romanized)), suffix: '_romanized' };
|
|
47
|
+
}
|
|
48
|
+
async function doExport() {
|
|
49
|
+
setIsExporting(true);
|
|
50
|
+
try {
|
|
51
|
+
const { data, suffix: reportSuffix } = reportData();
|
|
52
|
+
const reportTitleSuffix = reportSuffix ? ' (ROMANIZED)' : '';
|
|
53
|
+
if (format === 'fountain') {
|
|
54
|
+
const result = exportFountain(ast);
|
|
55
|
+
setWarnings(result.warnings);
|
|
56
|
+
download(`${baseName}.fountain`, result.text, 'text/plain');
|
|
57
|
+
showToast('Exported Fountain screenplay', 'success');
|
|
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
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
console.error('Export failed', err);
|
|
117
|
+
setWarnings(['An error occurred during export. Please check the console.']);
|
|
118
|
+
showToast('Export failed. Check console for details.', 'error');
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
setIsExporting(false);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
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
|
+
function downloadBlob(filename, blob) {
|
|
130
|
+
const url = URL.createObjectURL(blob);
|
|
131
|
+
const a = document.createElement('a');
|
|
132
|
+
a.href = url;
|
|
133
|
+
a.download = filename;
|
|
134
|
+
a.click();
|
|
135
|
+
URL.revokeObjectURL(url);
|
|
136
|
+
}
|
|
137
|
+
return (_jsx("div", { className: "cs-export-overlay", role: "presentation", onClick: e => {
|
|
138
|
+
if (e.target === e.currentTarget)
|
|
139
|
+
setExportVisible(false);
|
|
140
|
+
}, 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
|
+
setTab('screenplay');
|
|
142
|
+
if (!['fountain', 'docx', 'pdf'].includes(format))
|
|
143
|
+
setFormat('pdf');
|
|
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: () => {
|
|
145
|
+
setTab('reports');
|
|
146
|
+
if (['fountain', 'docx', 'pdf'].includes(format))
|
|
147
|
+
setFormat('oneliner-print');
|
|
148
|
+
}, children: "Production Reports" })] }), tab === 'screenplay' ? (_jsxs("div", { children: [_jsxs("div", { className: "cs-export-card-grid", children: [_jsxs("div", { className: `cs-export-format-card${format === 'pdf' ? ' cs-export-format-card-selected' : ''}`, onClick: () => setFormat('pdf'), role: "radio", "aria-checked": format === 'pdf', tabIndex: 0, onKeyDown: e => { if (e.key === 'Enter' || e.key === ' ')
|
|
149
|
+
setFormat('pdf'); }, children: [_jsx("input", { type: "radio", name: "format", value: "pdf", checked: format === 'pdf', onChange: () => setFormat('pdf'), style: { marginTop: '2px' } }), _jsxs("div", { children: [_jsx("strong", { style: { fontSize: '13px', display: 'block', color: 'var(--cs-ui-text-light-primary, #1c1b18)' }, children: "PDF / Print Preview" }), _jsx("span", { style: { fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }, children: "Print-ready industry screenplay layout styled according to your active visual template." })] })] }), _jsxs("div", { className: `cs-export-format-card${format === 'docx' ? ' cs-export-format-card-selected' : ''}`, onClick: () => setFormat('docx'), role: "radio", "aria-checked": format === 'docx', tabIndex: 0, onKeyDown: e => { if (e.key === 'Enter' || e.key === ' ')
|
|
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') })] })] }) }));
|
|
152
|
+
}
|