@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.
Files changed (337) hide show
  1. package/LICENSE +674 -0
  2. package/dist/ai/OnboardingDialog.d.ts +6 -0
  3. package/dist/ai/OnboardingDialog.js +115 -0
  4. package/dist/ai/VoiceConfirmDialog.d.ts +9 -0
  5. package/dist/ai/VoiceConfirmDialog.js +37 -0
  6. package/dist/ai/VoiceToolbar.d.ts +2 -0
  7. package/dist/ai/VoiceToolbar.js +177 -0
  8. package/dist/ai/ai-client.d.ts +71 -0
  9. package/dist/ai/ai-client.js +494 -0
  10. package/dist/ai/commands.d.ts +12 -0
  11. package/dist/ai/commands.js +140 -0
  12. package/dist/ai/generated-providers.json +3696 -0
  13. package/dist/ai/prompts.d.ts +25 -0
  14. package/dist/ai/prompts.js +59 -0
  15. package/dist/ai/voice-service.d.ts +15 -0
  16. package/dist/ai/voice-service.js +81 -0
  17. package/dist/autocomplete/AutocompletePlugin.d.ts +2 -0
  18. package/dist/autocomplete/AutocompletePlugin.js +19 -0
  19. package/dist/autocomplete/suggestions.d.ts +25 -0
  20. package/dist/autocomplete/suggestions.js +126 -0
  21. package/dist/context/DocumentContext.d.ts +68 -0
  22. package/dist/context/DocumentContext.js +605 -0
  23. package/dist/desktop-api.d.ts +40 -0
  24. package/dist/editor/EditorView.d.ts +2 -0
  25. package/dist/editor/EditorView.js +78 -0
  26. package/dist/editor/SelectionMenu.d.ts +2 -0
  27. package/dist/editor/SelectionMenu.js +102 -0
  28. package/dist/editor/attr-pencil-plugin.d.ts +17 -0
  29. package/dist/editor/attr-pencil-plugin.js +158 -0
  30. package/dist/editor/character-decoration-plugin.d.ts +26 -0
  31. package/dist/editor/character-decoration-plugin.js +77 -0
  32. package/dist/editor/character-view.d.ts +35 -0
  33. package/dist/editor/character-view.js +112 -0
  34. package/dist/editor/editor-bus.d.ts +14 -0
  35. package/dist/editor/editor-bus.js +39 -0
  36. package/dist/editor/find-highlight-plugin.d.ts +7 -0
  37. package/dist/editor/find-highlight-plugin.js +57 -0
  38. package/dist/editor/frontmatter-field-view.d.ts +10 -0
  39. package/dist/editor/frontmatter-field-view.js +48 -0
  40. package/dist/editor/frontmatter-field.d.ts +21 -0
  41. package/dist/editor/frontmatter-field.js +116 -0
  42. package/dist/editor/grapheme-cursor.d.ts +10 -0
  43. package/dist/editor/grapheme-cursor.js +104 -0
  44. package/dist/editor/ime-plugin.d.ts +18 -0
  45. package/dist/editor/ime-plugin.js +71 -0
  46. package/dist/editor/insert-helper.d.ts +1 -0
  47. package/dist/editor/insert-helper.js +42 -0
  48. package/dist/editor/keymap.d.ts +3 -0
  49. package/dist/editor/keymap.js +131 -0
  50. package/dist/editor/language-class-plugin.d.ts +7 -0
  51. package/dist/editor/language-class-plugin.js +75 -0
  52. package/dist/editor/plugins.d.ts +1 -0
  53. package/dist/editor/plugins.js +25 -0
  54. package/dist/editor/prosemirror-to-sutra.d.ts +2 -0
  55. package/dist/editor/prosemirror-to-sutra.js +208 -0
  56. package/dist/editor/scene-heading-view.d.ts +27 -0
  57. package/dist/editor/scene-heading-view.js +136 -0
  58. package/dist/editor/schema.d.ts +3 -0
  59. package/dist/editor/schema.js +135 -0
  60. package/dist/editor/sutra-to-prosemirror.d.ts +3 -0
  61. package/dist/editor/sutra-to-prosemirror.js +154 -0
  62. package/dist/editor/transliterate-input.d.ts +10 -0
  63. package/dist/editor/transliterate-input.js +88 -0
  64. package/dist/extensions/storage-adapter.d.ts +59 -0
  65. package/dist/extensions/storage-adapter.js +9 -0
  66. package/dist/extensions/types.d.ts +73 -0
  67. package/dist/extensions/types.js +12 -0
  68. package/dist/file/docx-exporter.d.ts +8 -0
  69. package/dist/file/docx-exporter.js +494 -0
  70. package/dist/file/docx-importer.d.ts +10 -0
  71. package/dist/file/docx-importer.js +283 -0
  72. package/dist/file/print-paginator.d.ts +29 -0
  73. package/dist/file/print-paginator.js +289 -0
  74. package/dist/file/templates.d.ts +2 -0
  75. package/dist/file/templates.js +66 -0
  76. package/dist/file/workflow-reports.d.ts +34 -0
  77. package/dist/file/workflow-reports.js +719 -0
  78. package/dist/findreplace/FindReplace.d.ts +2 -0
  79. package/dist/findreplace/FindReplace.js +164 -0
  80. package/dist/findreplace/find-engine.d.ts +29 -0
  81. package/dist/findreplace/find-engine.js +69 -0
  82. package/dist/findreplace/scroll-to-match.d.ts +29 -0
  83. package/dist/findreplace/scroll-to-match.js +74 -0
  84. package/dist/fonts/tofu-detect.d.ts +1 -0
  85. package/dist/fonts/tofu-detect.js +48 -0
  86. package/dist/i18n/LanguageContext.d.ts +10 -0
  87. package/dist/i18n/LanguageContext.js +174 -0
  88. package/dist/i18n/language-detect.d.ts +1 -0
  89. package/dist/i18n/language-detect.js +58 -0
  90. package/dist/i18n/useTranslation.d.ts +11 -0
  91. package/dist/i18n/useTranslation.js +60 -0
  92. package/dist/index.d.ts +11 -0
  93. package/dist/index.js +5 -0
  94. package/dist/locales/bn.json +143 -0
  95. package/dist/locales/en.json +143 -0
  96. package/dist/locales/gu.json +143 -0
  97. package/dist/locales/hi.json +143 -0
  98. package/dist/locales/kn.json +143 -0
  99. package/dist/locales/ml.json +143 -0
  100. package/dist/locales/mr.json +143 -0
  101. package/dist/locales/or.json +143 -0
  102. package/dist/locales/pa.json +143 -0
  103. package/dist/locales/si.json +143 -0
  104. package/dist/locales/ta.json +143 -0
  105. package/dist/locales/te.json +143 -0
  106. package/dist/navigator/SceneNavigator.d.ts +2 -0
  107. package/dist/navigator/SceneNavigator.js +612 -0
  108. package/dist/navigator/StatisticsDialog.d.ts +6 -0
  109. package/dist/navigator/StatisticsDialog.js +146 -0
  110. package/dist/navigator/scene-list.d.ts +16 -0
  111. package/dist/navigator/scene-list.js +70 -0
  112. package/dist/settings/SettingsDialog.d.ts +6 -0
  113. package/dist/settings/SettingsDialog.js +147 -0
  114. package/dist/shell/AppBar.d.ts +2 -0
  115. package/dist/shell/AppBar.js +34 -0
  116. package/dist/shell/AppShell.d.ts +2 -0
  117. package/dist/shell/AppShell.js +30 -0
  118. package/dist/shell/ConfirmDialog.d.ts +16 -0
  119. package/dist/shell/ConfirmDialog.js +23 -0
  120. package/dist/shell/ElementToolbar.d.ts +2 -0
  121. package/dist/shell/ElementToolbar.js +30 -0
  122. package/dist/shell/LanguageStatus.d.ts +2 -0
  123. package/dist/shell/LanguageStatus.js +65 -0
  124. package/dist/shell/StatusBar.d.ts +2 -0
  125. package/dist/shell/StatusBar.js +54 -0
  126. package/dist/shell/SutrataLogo.d.ts +8 -0
  127. package/dist/shell/SutrataLogo.js +7 -0
  128. package/dist/shell/Toast.d.ts +12 -0
  129. package/dist/shell/Toast.js +21 -0
  130. package/dist/shell/ToolbarButton.d.ts +11 -0
  131. package/dist/shell/ToolbarButton.js +4 -0
  132. package/dist/shell/VersionHistory.d.ts +12 -0
  133. package/dist/shell/VersionHistory.js +89 -0
  134. package/dist/shell/Watermark.d.ts +3 -0
  135. package/dist/shell/Watermark.js +10 -0
  136. package/dist/shell/fonts/fonts.css +1133 -0
  137. package/dist/shell/icons.d.ts +45 -0
  138. package/dist/shell/icons.js +51 -0
  139. package/dist/shell/toolbar-actions.d.ts +6 -0
  140. package/dist/shell/toolbar-actions.js +58 -0
  141. package/dist/source/SourceView.d.ts +1 -0
  142. package/dist/source/SourceView.js +106 -0
  143. package/dist/source/find-highlight-source.d.ts +14 -0
  144. package/dist/source/find-highlight-source.js +47 -0
  145. package/dist/source/sutra-lang.d.ts +2 -0
  146. package/dist/source/sutra-lang.js +97 -0
  147. package/dist/spellcheck/dictionaries.d.ts +9 -0
  148. package/dist/spellcheck/dictionaries.js +16 -0
  149. package/dist/spellcheck/hunspell.d.ts +23 -0
  150. package/dist/spellcheck/hunspell.js +94 -0
  151. package/dist/spellcheck/spellcheck-plugin.d.ts +18 -0
  152. package/dist/spellcheck/spellcheck-plugin.js +75 -0
  153. package/dist/styles/StyleDialog.d.ts +6 -0
  154. package/dist/styles/StyleDialog.js +63 -0
  155. package/dist/styles/builtin-styles.d.ts +43 -0
  156. package/dist/styles/builtin-styles.js +131 -0
  157. package/dist/styles/css-adapter.d.ts +32 -0
  158. package/dist/styles/css-adapter.js +71 -0
  159. package/dist/styles/docx-adapter.d.ts +7 -0
  160. package/dist/styles/docx-adapter.js +84 -0
  161. package/dist/styles/embed-fonts.d.ts +20 -0
  162. package/dist/styles/embed-fonts.js +81 -0
  163. package/dist/styles/pagination.d.ts +19 -0
  164. package/dist/styles/pagination.js +22 -0
  165. package/dist/styles/print-adapter.d.ts +6 -0
  166. package/dist/styles/print-adapter.js +288 -0
  167. package/dist/styles/registry.d.ts +6 -0
  168. package/dist/styles/registry.js +14 -0
  169. package/dist/styles/screenplay.css +3006 -0
  170. package/dist/styles/script-ranges.d.ts +6 -0
  171. package/dist/styles/script-ranges.js +17 -0
  172. package/dist/styles/types.d.ts +93 -0
  173. package/dist/styles/types.js +18 -0
  174. package/dist/styles/validate.d.ts +12 -0
  175. package/dist/styles/validate.js +105 -0
  176. package/dist/templates/templates.d.ts +10 -0
  177. package/dist/templates/templates.js +181 -0
  178. package/dist/titlepage/ExportDialog.d.ts +1 -0
  179. package/dist/titlepage/ExportDialog.js +152 -0
  180. package/dist/titlepage/TitlePageDialog.d.ts +6 -0
  181. package/dist/titlepage/TitlePageDialog.js +96 -0
  182. package/dist/titlepage/frontmatter-form.d.ts +56 -0
  183. package/dist/titlepage/frontmatter-form.js +245 -0
  184. package/dist/titlepage/languages.d.ts +10 -0
  185. package/dist/titlepage/languages.js +31 -0
  186. package/dist/types.d.ts +6 -0
  187. package/dist/types.js +1 -0
  188. package/package.json +47 -0
  189. package/scripts/copy-assets.mjs +16 -0
  190. package/scripts/generate-providers.mjs +127 -0
  191. package/src/ai/OnboardingDialog.tsx +390 -0
  192. package/src/ai/VoiceConfirmDialog.tsx +137 -0
  193. package/src/ai/VoiceToolbar.tsx +321 -0
  194. package/src/ai/ai-client.ts +596 -0
  195. package/src/ai/commands.ts +164 -0
  196. package/src/ai/generated-providers.json +3696 -0
  197. package/src/ai/prompts.ts +62 -0
  198. package/src/ai/voice-service.ts +96 -0
  199. package/src/autocomplete/AutocompletePlugin.ts +21 -0
  200. package/src/autocomplete/suggestions.ts +147 -0
  201. package/src/context/DocumentContext.tsx +648 -0
  202. package/src/desktop-api.d.ts +40 -0
  203. package/src/editor/EditorView.tsx +82 -0
  204. package/src/editor/SelectionMenu.tsx +152 -0
  205. package/src/editor/attr-pencil-plugin.ts +172 -0
  206. package/src/editor/character-decoration-plugin.ts +104 -0
  207. package/src/editor/editor-bus.ts +51 -0
  208. package/src/editor/find-highlight-plugin.ts +68 -0
  209. package/src/editor/frontmatter-field-view.ts +52 -0
  210. package/src/editor/frontmatter-field.ts +115 -0
  211. package/src/editor/grapheme-cursor.ts +99 -0
  212. package/src/editor/ime-plugin.ts +85 -0
  213. package/src/editor/insert-helper.ts +43 -0
  214. package/src/editor/keymap.ts +124 -0
  215. package/src/editor/language-class-plugin.ts +91 -0
  216. package/src/editor/plugins.ts +26 -0
  217. package/src/editor/prosemirror-to-sutra.ts +192 -0
  218. package/src/editor/scene-heading-view.ts +147 -0
  219. package/src/editor/schema.ts +156 -0
  220. package/src/editor/sutra-to-prosemirror.ts +156 -0
  221. package/src/extensions/storage-adapter.ts +65 -0
  222. package/src/extensions/types.ts +60 -0
  223. package/src/file/docx-exporter.ts +585 -0
  224. package/src/file/docx-importer.ts +313 -0
  225. package/src/file/print-paginator.ts +301 -0
  226. package/src/file/templates.ts +73 -0
  227. package/src/file/workflow-reports.ts +820 -0
  228. package/src/findreplace/FindReplace.tsx +242 -0
  229. package/src/findreplace/find-engine.ts +97 -0
  230. package/src/findreplace/scroll-to-match.ts +89 -0
  231. package/src/fonts/tofu-detect.ts +53 -0
  232. package/src/i18n/LanguageContext.tsx +207 -0
  233. package/src/i18n/language-detect.ts +73 -0
  234. package/src/i18n/useTranslation.tsx +74 -0
  235. package/src/index.ts +27 -0
  236. package/src/locales/bn.json +143 -0
  237. package/src/locales/en.json +143 -0
  238. package/src/locales/gu.json +143 -0
  239. package/src/locales/hi.json +143 -0
  240. package/src/locales/kn.json +143 -0
  241. package/src/locales/ml.json +143 -0
  242. package/src/locales/mr.json +143 -0
  243. package/src/locales/or.json +143 -0
  244. package/src/locales/pa.json +143 -0
  245. package/src/locales/si.json +143 -0
  246. package/src/locales/ta.json +143 -0
  247. package/src/locales/te.json +143 -0
  248. package/src/navigator/SceneNavigator.tsx +844 -0
  249. package/src/navigator/StatisticsDialog.tsx +249 -0
  250. package/src/navigator/scene-list.ts +84 -0
  251. package/src/settings/SettingsDialog.tsx +303 -0
  252. package/src/shell/AppBar.tsx +354 -0
  253. package/src/shell/AppShell.tsx +71 -0
  254. package/src/shell/ConfirmDialog.tsx +71 -0
  255. package/src/shell/ElementToolbar.tsx +175 -0
  256. package/src/shell/LanguageStatus.tsx +101 -0
  257. package/src/shell/StatusBar.tsx +135 -0
  258. package/src/shell/SutrataLogo.tsx +89 -0
  259. package/src/shell/Toast.tsx +46 -0
  260. package/src/shell/ToolbarButton.tsx +30 -0
  261. package/src/shell/VersionHistory.tsx +212 -0
  262. package/src/shell/Watermark.tsx +9 -0
  263. package/src/shell/fonts/fonts.css +1133 -0
  264. package/src/shell/icons.tsx +335 -0
  265. package/src/shell/toolbar-actions.ts +59 -0
  266. package/src/source/SourceView.tsx +111 -0
  267. package/src/source/find-highlight-source.ts +57 -0
  268. package/src/source/sutra-lang.ts +121 -0
  269. package/src/spellcheck/dictionaries.ts +21 -0
  270. package/src/spellcheck/hunspell.ts +110 -0
  271. package/src/spellcheck/spellcheck-plugin.ts +83 -0
  272. package/src/styles/StyleDialog.tsx +148 -0
  273. package/src/styles/builtin-styles.ts +139 -0
  274. package/src/styles/css-adapter.ts +76 -0
  275. package/src/styles/docx-adapter.ts +109 -0
  276. package/src/styles/embed-fonts.ts +81 -0
  277. package/src/styles/pagination.ts +27 -0
  278. package/src/styles/print-adapter.ts +288 -0
  279. package/src/styles/registry.ts +20 -0
  280. package/src/styles/screenplay.css +3006 -0
  281. package/src/styles/script-ranges.ts +17 -0
  282. package/src/styles/types.ts +104 -0
  283. package/src/styles/validate.ts +114 -0
  284. package/src/templates/templates.ts +194 -0
  285. package/src/titlepage/ExportDialog.tsx +494 -0
  286. package/src/titlepage/TitlePageDialog.tsx +312 -0
  287. package/src/titlepage/frontmatter-form.ts +280 -0
  288. package/src/titlepage/languages.ts +37 -0
  289. package/src/types.ts +7 -0
  290. package/tests/AppBar.test.tsx +35 -0
  291. package/tests/AppShell.test.tsx +22 -0
  292. package/tests/ElementToolbar.test.tsx +36 -0
  293. package/tests/LanguageStatus.test.tsx +129 -0
  294. package/tests/SelectionMenu.test.tsx +71 -0
  295. package/tests/ToolbarButton.test.tsx +24 -0
  296. package/tests/ai-onboarding.test.ts +102 -0
  297. package/tests/ai-status.test.tsx +72 -0
  298. package/tests/ai-structured.test.ts +94 -0
  299. package/tests/autocomplete/AutocompletePlugin.test.ts +20 -0
  300. package/tests/autocomplete/suggestions.test.ts +106 -0
  301. package/tests/document-context.test.tsx +565 -0
  302. package/tests/docx-importer.test.ts +160 -0
  303. package/tests/editor/character-decoration-plugin.test.ts +101 -0
  304. package/tests/editor/character-extension.test.ts +44 -0
  305. package/tests/editor/grapheme-cursor.test.ts +118 -0
  306. package/tests/editor/ime-plugin.test.ts +72 -0
  307. package/tests/editor/inline-note-bridge.test.ts +32 -0
  308. package/tests/editor/language-class-plugin.test.ts +265 -0
  309. package/tests/editor/title-page.test.ts +124 -0
  310. package/tests/embed-fonts.test.ts +17 -0
  311. package/tests/exporters.test.ts +365 -0
  312. package/tests/findreplace/FindReplace.test.tsx +391 -0
  313. package/tests/findreplace/find-engine.test.ts +86 -0
  314. package/tests/findreplace/scroll-to-match.test.ts +50 -0
  315. package/tests/fonts/tofu-detect.test.ts +122 -0
  316. package/tests/i18n/LanguageContext.test.tsx +146 -0
  317. package/tests/i18n/language-detect.test.ts +54 -0
  318. package/tests/i18n/locale-parity.test.ts +25 -0
  319. package/tests/i18n/useTranslation.test.tsx +45 -0
  320. package/tests/keymap.test.ts +18 -0
  321. package/tests/navigator/SceneNavigator.test.tsx +177 -0
  322. package/tests/navigator/StatisticsDialog.test.tsx +63 -0
  323. package/tests/navigator/scene-list.test.ts +71 -0
  324. package/tests/print-paginator.test.ts +82 -0
  325. package/tests/shell/VersionHistory.test.tsx +129 -0
  326. package/tests/spellcheck/hunspell.test.ts +82 -0
  327. package/tests/spellcheck/spellcheck-plugin.test.ts +56 -0
  328. package/tests/styles.test.ts +315 -0
  329. package/tests/templates/templates.test.ts +83 -0
  330. package/tests/titlepage/TitlePageDialog.test.tsx +90 -0
  331. package/tests/titlepage/apply-title-page.test.ts +61 -0
  332. package/tests/titlepage/frontmatter-form.test.ts +115 -0
  333. package/tests/toolbar-actions.test.ts +93 -0
  334. package/tests/voice-commands.test.ts +58 -0
  335. package/tsconfig.json +17 -0
  336. package/vitest.config.ts +11 -0
  337. package/vitest.setup.ts +65 -0
@@ -0,0 +1,494 @@
1
+ import { useState, useEffect } from 'react'
2
+ import { useDocument } from '../context/DocumentContext'
3
+ import { exportFountain, parse, romanizeSutra } from '@sutrata/parser'
4
+ import type { DocumentNode, RomanizeVariant } from '@sutrata/parser'
5
+ import { useTranslation } from '../i18n/useTranslation'
6
+ import { exportToDocx } from '../file/docx-exporter'
7
+ import {
8
+ extractWorkflowData,
9
+ exportOneLinerCsv,
10
+ exportShotListCsv,
11
+ exportCastReportCsv,
12
+ exportOneLinerDocx,
13
+ exportShotListDocx,
14
+ exportCastReportDocx,
15
+ openPrintPreview,
16
+ openScreenplayPrintPreview,
17
+ } from '../file/workflow-reports'
18
+ import { allStyles } from '../styles/registry'
19
+
20
+ type ExportFormat =
21
+ | 'fountain'
22
+ | 'docx'
23
+ | 'pdf'
24
+ | 'oneliner-print'
25
+ | 'oneliner-docx'
26
+ | 'oneliner-csv'
27
+ | 'shotlist-docx'
28
+ | 'shotlist-csv'
29
+ | 'cast-docx'
30
+ | 'cast-csv'
31
+
32
+ export function ExportDialog() {
33
+ const { ast, text, filePath, setExportVisible, resolvedStyle, customStyles, showToast } = useDocument()
34
+ const { t } = useTranslation()
35
+ const [tab, setTab] = useState<'screenplay' | 'reports'>('screenplay')
36
+ const [format, setFormat] = useState<ExportFormat>('pdf')
37
+ const [warnings, setWarnings] = useState<string[]>([])
38
+ const [isExporting, setIsExporting] = useState(false)
39
+ const [styleOverride, setStyleOverride] = useState('')
40
+ const [romanizeScope, setRomanizeScope] = useState<'off' | 'all' | 'dialogue'>('off')
41
+ const [romanizeVariant, setRomanizeVariant] = useState<RomanizeVariant>('strict')
42
+ const [romanizeReports, setRomanizeReports] = useState(false)
43
+ const [skipNotes, setSkipNotes] = useState(false)
44
+ const hasIndicText = /[\u0900-\u0DFF]/.test(text)
45
+ const styleChoices = allStyles(customStyles)
46
+ const showStylePicker = tab === 'screenplay' && (format === 'docx' || format === 'pdf')
47
+
48
+ useEffect(() => {
49
+ const onKey = (e: KeyboardEvent) => {
50
+ if (e.key === 'Escape') setExportVisible(false)
51
+ }
52
+ document.addEventListener('keydown', onKey)
53
+ return () => document.removeEventListener('keydown', onKey)
54
+ }, [setExportVisible])
55
+
56
+ const baseName = (filePath ?? 'screenplay').replace(/\.[^.]+$/, '')
57
+
58
+ /** The AST handed to the PDF/DOCX exporters: the document itself, or a romanized copy
59
+ * (Indic → ISO 15919, export only; the .sutra text is never changed). */
60
+ function screenplayAst(): { doc: DocumentNode; suffix: string } {
61
+ if (romanizeScope === 'off' || !hasIndicText) return { doc: ast, suffix: '' }
62
+ const romanized = romanizeSutra(text, { scope: romanizeScope, variant: romanizeVariant })
63
+ return { doc: parse(romanized), suffix: '_romanized' }
64
+ }
65
+
66
+ /** Workflow-report data, from a fully romanized copy when that option is on. */
67
+ function reportData(): { data: ReturnType<typeof extractWorkflowData>; suffix: string } {
68
+ if (!romanizeReports || !hasIndicText) return { data: extractWorkflowData(ast), suffix: '' }
69
+ const romanized = romanizeSutra(text, { scope: 'all', variant: romanizeVariant, notice: false })
70
+ return { data: extractWorkflowData(parse(romanized)), suffix: '_romanized' }
71
+ }
72
+
73
+ async function doExport() {
74
+ setIsExporting(true)
75
+ try {
76
+ const { data, suffix: reportSuffix } = reportData()
77
+ const reportTitleSuffix = reportSuffix ? ' (ROMANIZED)' : ''
78
+
79
+ if (format === 'fountain') {
80
+ const result = exportFountain(ast)
81
+ setWarnings(result.warnings)
82
+ download(`${baseName}.fountain`, result.text, 'text/plain')
83
+ showToast('Exported Fountain screenplay', 'success')
84
+ } else if (format === 'docx') {
85
+ setWarnings([])
86
+ const { doc, suffix } = screenplayAst()
87
+ const blob = await exportToDocx(doc, styleOverride || undefined, customStyles, skipNotes)
88
+ downloadBlob(`${baseName}${suffix}.docx`, blob)
89
+ showToast('Exported Word document (.docx)', 'success')
90
+ } else if (format === 'pdf') {
91
+ setWarnings([])
92
+ const { doc, suffix } = screenplayAst()
93
+ const title = `${baseName.toUpperCase()} - SCREENPLAY${suffix ? ' (ROMANIZED)' : ''}`
94
+ await openScreenplayPrintPreview(doc, title, styleOverride || undefined, customStyles, skipNotes)
95
+ showToast('Opened Screenplay Print Preview', 'info')
96
+ } else if (format === 'oneliner-print') {
97
+ setWarnings([])
98
+ await openPrintPreview(data.scenes, `${baseName.toUpperCase()} - ONE-LINER SCHEDULE${reportTitleSuffix}`)
99
+ showToast('Opened One-Liner Schedule', 'info')
100
+ } else if (format === 'oneliner-docx') {
101
+ setWarnings([])
102
+ const blob = await exportOneLinerDocx(data.scenes)
103
+ downloadBlob(`${baseName}_one_liner${reportSuffix}.docx`, blob)
104
+ showToast('Exported One-Liner DOCX', 'success')
105
+ } else if (format === 'oneliner-csv') {
106
+ setWarnings([])
107
+ const csv = exportOneLinerCsv(data.scenes)
108
+ download(`${baseName}_one_liner${reportSuffix}.csv`, csv, 'text/csv')
109
+ showToast('Exported One-Liner CSV', 'success')
110
+ } else if (format === 'shotlist-docx') {
111
+ setWarnings([])
112
+ const blob = await exportShotListDocx(data.scenes)
113
+ downloadBlob(`${baseName}_shot_list${reportSuffix}.docx`, blob)
114
+ showToast('Exported Shot List DOCX', 'success')
115
+ } else if (format === 'shotlist-csv') {
116
+ setWarnings([])
117
+ const csv = exportShotListCsv(data.scenes)
118
+ download(`${baseName}_shot_list${reportSuffix}.csv`, csv, 'text/csv')
119
+ showToast('Exported Shot List CSV', 'success')
120
+ } else if (format === 'cast-docx') {
121
+ setWarnings([])
122
+ const blob = await exportCastReportDocx(data.characters)
123
+ downloadBlob(`${baseName}_cast_report${reportSuffix}.docx`, blob)
124
+ showToast('Exported Cast Report DOCX', 'success')
125
+ } else if (format === 'cast-csv') {
126
+ setWarnings([])
127
+ const csv = exportCastReportCsv(data.characters)
128
+ download(`${baseName}_cast_report${reportSuffix}.csv`, csv, 'text/csv')
129
+ showToast('Exported Cast Report CSV', 'success')
130
+ }
131
+ } catch (err) {
132
+ console.error('Export failed', err)
133
+ setWarnings(['An error occurred during export. Please check the console.'])
134
+ showToast('Export failed. Check console for details.', 'error')
135
+ } finally {
136
+ setIsExporting(false)
137
+ }
138
+ }
139
+
140
+ function download(filename: string, content: string, mimeType: string) {
141
+ const data = mimeType === 'text/csv' ? ['\uFEFF', content] : [content]
142
+ const blob = new Blob(data, { type: `${mimeType};charset=utf-8` })
143
+ downloadBlob(filename, blob)
144
+ }
145
+
146
+ function downloadBlob(filename: string, blob: Blob) {
147
+ const url = URL.createObjectURL(blob)
148
+ const a = document.createElement('a')
149
+ a.href = url
150
+ a.download = filename
151
+ a.click()
152
+ URL.revokeObjectURL(url)
153
+ }
154
+
155
+ return (
156
+ <div
157
+ className="cs-export-overlay"
158
+ role="presentation"
159
+ onClick={e => {
160
+ if (e.target === e.currentTarget) setExportVisible(false)
161
+ }}
162
+ >
163
+ <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' }}>
164
+ <div className="cs-panel-header">
165
+ <span id="cs-export-title" className="cs-panel-title">{t('export.title')}</span>
166
+ <button className="cs-panel-close" onClick={() => setExportVisible(false)} aria-label="Close">
167
+ ×
168
+ </button>
169
+ </div>
170
+
171
+ <div className="cs-panel-body" style={{ overflowY: 'auto', flex: 1, padding: '16px' }}>
172
+ {/* Top Segmented Tabs: Screenplay vs Production Reports */}
173
+ <div className="cs-export-segmented" role="tablist" aria-label="Export category">
174
+ <button
175
+ type="button"
176
+ role="tab"
177
+ aria-selected={tab === 'screenplay'}
178
+ className={`cs-export-seg-btn${tab === 'screenplay' ? ' cs-export-seg-btn-active' : ''}`}
179
+ onClick={() => {
180
+ setTab('screenplay')
181
+ if (!['fountain', 'docx', 'pdf'].includes(format)) setFormat('pdf')
182
+ }}
183
+ >
184
+ Screenplay Formats
185
+ </button>
186
+ <button
187
+ type="button"
188
+ role="tab"
189
+ aria-selected={tab === 'reports'}
190
+ className={`cs-export-seg-btn${tab === 'reports' ? ' cs-export-seg-btn-active' : ''}`}
191
+ onClick={() => {
192
+ setTab('reports')
193
+ if (['fountain', 'docx', 'pdf'].includes(format)) setFormat('oneliner-print')
194
+ }}
195
+ >
196
+ Production Reports
197
+ </button>
198
+ </div>
199
+
200
+ {tab === 'screenplay' ? (
201
+ <div>
202
+ <div className="cs-export-card-grid">
203
+ {/* PDF / Print */}
204
+ <div
205
+ className={`cs-export-format-card${format === 'pdf' ? ' cs-export-format-card-selected' : ''}`}
206
+ onClick={() => setFormat('pdf')}
207
+ role="radio"
208
+ aria-checked={format === 'pdf'}
209
+ tabIndex={0}
210
+ onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') setFormat('pdf') }}
211
+ >
212
+ <input
213
+ type="radio"
214
+ name="format"
215
+ value="pdf"
216
+ checked={format === 'pdf'}
217
+ onChange={() => setFormat('pdf')}
218
+ style={{ marginTop: '2px' }}
219
+ />
220
+ <div>
221
+ <strong style={{ fontSize: '13px', display: 'block', color: 'var(--cs-ui-text-light-primary, #1c1b18)' }}>
222
+ PDF / Print Preview
223
+ </strong>
224
+ <span style={{ fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }}>
225
+ Print-ready industry screenplay layout styled according to your active visual template.
226
+ </span>
227
+ </div>
228
+ </div>
229
+
230
+ {/* Microsoft Word */}
231
+ <div
232
+ className={`cs-export-format-card${format === 'docx' ? ' cs-export-format-card-selected' : ''}`}
233
+ onClick={() => setFormat('docx')}
234
+ role="radio"
235
+ aria-checked={format === 'docx'}
236
+ tabIndex={0}
237
+ onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') setFormat('docx') }}
238
+ >
239
+ <input
240
+ type="radio"
241
+ name="format"
242
+ value="docx"
243
+ checked={format === 'docx'}
244
+ onChange={() => setFormat('docx')}
245
+ style={{ marginTop: '2px' }}
246
+ />
247
+ <div>
248
+ <strong style={{ fontSize: '13px', display: 'block', color: 'var(--cs-ui-text-light-primary, #1c1b18)' }}>
249
+ Microsoft Word (.docx)
250
+ </strong>
251
+ <span style={{ fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }}>
252
+ {t('export.docxDesc')}
253
+ </span>
254
+ </div>
255
+ </div>
256
+
257
+ {/* Fountain */}
258
+ <div
259
+ className={`cs-export-format-card${format === 'fountain' ? ' cs-export-format-card-selected' : ''}`}
260
+ onClick={() => setFormat('fountain')}
261
+ role="radio"
262
+ aria-checked={format === 'fountain'}
263
+ tabIndex={0}
264
+ onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') setFormat('fountain') }}
265
+ >
266
+ <input
267
+ type="radio"
268
+ name="format"
269
+ value="fountain"
270
+ checked={format === 'fountain'}
271
+ onChange={() => setFormat('fountain')}
272
+ style={{ marginTop: '2px' }}
273
+ />
274
+ <div>
275
+ <strong style={{ fontSize: '13px', display: 'block', color: 'var(--cs-ui-text-light-primary, #1c1b18)' }}>
276
+ Fountain (.fountain)
277
+ </strong>
278
+ <span style={{ fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }}>
279
+ {t('export.fountainDesc')}
280
+ </span>
281
+ </div>
282
+ </div>
283
+ </div>
284
+
285
+ {showStylePicker && (
286
+ <div className="cs-settings-row" style={{ marginTop: '14px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px' }}>
287
+ <label className="cs-settings-label" htmlFor="cs-export-style-select" style={{ fontSize: '12px' }}>
288
+ {t('export.style')}
289
+ </label>
290
+ <select
291
+ id="cs-export-style-select"
292
+ className="cs-settings-select"
293
+ value={styleOverride}
294
+ onChange={e => setStyleOverride(e.target.value)}
295
+ >
296
+ <option value="">{t('export.styleDefault')} ({resolvedStyle.name})</option>
297
+ {styleChoices.map(s => (
298
+ <option key={s.id} value={s.id}>{s.name}</option>
299
+ ))}
300
+ </select>
301
+ </div>
302
+ )}
303
+
304
+ {showStylePicker && (
305
+ <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' }}>
306
+ <input
307
+ id="cs-export-skip-notes"
308
+ type="checkbox"
309
+ checked={skipNotes}
310
+ onChange={e => setSkipNotes(e.target.checked)}
311
+ />
312
+ <label htmlFor="cs-export-skip-notes" className="cs-settings-label" style={{ fontSize: '12px', margin: 0 }}>
313
+ Skip notes ([[ ]])
314
+ </label>
315
+ </div>
316
+ )}
317
+
318
+ {showStylePicker && hasIndicText && (
319
+ <div className="cs-settings-row" style={{ marginTop: '8px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px', flexWrap: 'wrap', gap: '8px' }}>
320
+ <label className="cs-settings-label" htmlFor="cs-export-romanize-select" style={{ fontSize: '12px' }}>
321
+ Romanized copy (ISO 15919)
322
+ </label>
323
+ <select
324
+ id="cs-export-romanize-select"
325
+ className="cs-settings-select"
326
+ value={romanizeScope}
327
+ onChange={e => setRomanizeScope(e.target.value as 'off' | 'all' | 'dialogue')}
328
+ >
329
+ <option value="off">Off (native script)</option>
330
+ <option value="all">All text</option>
331
+ <option value="dialogue">Dialogue only</option>
332
+ </select>
333
+ {romanizeScope !== 'off' && (
334
+ <select
335
+ aria-label="Romanization variant"
336
+ className="cs-settings-select"
337
+ value={romanizeVariant}
338
+ onChange={e => setRomanizeVariant(e.target.value as RomanizeVariant)}
339
+ >
340
+ <option value="strict">Strict ISO 15919</option>
341
+ <option value="colloquial">Casual (no diacritics)</option>
342
+ </select>
343
+ )}
344
+ {romanizeScope !== 'off' && (
345
+ <span style={{ fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)', flexBasis: '100%' }}>
346
+ Your script is not changed. Page numbers in the romanized copy differ from the original.
347
+ </span>
348
+ )}
349
+ </div>
350
+ )}
351
+ </div>
352
+ ) : (
353
+ <div>
354
+ {/* One-Liner Box */}
355
+ <div className="cs-export-report-box">
356
+ <div className="cs-export-report-header">
357
+ <span className="cs-export-report-title">One-Liner Schedule</span>
358
+ <div className="cs-export-chips">
359
+ <button
360
+ type="button"
361
+ className={`cs-export-chip${format === 'oneliner-print' ? ' cs-export-chip-active' : ''}`}
362
+ onClick={() => setFormat('oneliner-print')}
363
+ >
364
+ Print View
365
+ </button>
366
+ <button
367
+ type="button"
368
+ className={`cs-export-chip${format === 'oneliner-docx' ? ' cs-export-chip-active' : ''}`}
369
+ onClick={() => setFormat('oneliner-docx')}
370
+ >
371
+ Word (.docx)
372
+ </button>
373
+ <button
374
+ type="button"
375
+ className={`cs-export-chip${format === 'oneliner-csv' ? ' cs-export-chip-active' : ''}`}
376
+ onClick={() => setFormat('oneliner-csv')}
377
+ >
378
+ Spreadsheet (.csv)
379
+ </button>
380
+ </div>
381
+ </div>
382
+ <div style={{ fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }}>
383
+ A scene-by-scene production stripboard breakdown with locations, cast, and timings.
384
+ </div>
385
+ </div>
386
+
387
+ {/* Shot List Box */}
388
+ <div className="cs-export-report-box">
389
+ <div className="cs-export-report-header">
390
+ <span className="cs-export-report-title">Shot List</span>
391
+ <div className="cs-export-chips">
392
+ <button
393
+ type="button"
394
+ className={`cs-export-chip${format === 'shotlist-docx' ? ' cs-export-chip-active' : ''}`}
395
+ onClick={() => setFormat('shotlist-docx')}
396
+ >
397
+ Word (.docx)
398
+ </button>
399
+ <button
400
+ type="button"
401
+ className={`cs-export-chip${format === 'shotlist-csv' ? ' cs-export-chip-active' : ''}`}
402
+ onClick={() => setFormat('shotlist-csv')}
403
+ >
404
+ Spreadsheet (.csv)
405
+ </button>
406
+ </div>
407
+ </div>
408
+ <div style={{ fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }}>
409
+ Camera shot specifications extracted from scene headings and production notes.
410
+ </div>
411
+ </div>
412
+
413
+ {/* Cast Breakdown Box */}
414
+ <div className="cs-export-report-box">
415
+ <div className="cs-export-report-header">
416
+ <span className="cs-export-report-title">Cast Breakdown</span>
417
+ <div className="cs-export-chips">
418
+ <button
419
+ type="button"
420
+ className={`cs-export-chip${format === 'cast-docx' ? ' cs-export-chip-active' : ''}`}
421
+ onClick={() => setFormat('cast-docx')}
422
+ >
423
+ Word (.docx)
424
+ </button>
425
+ <button
426
+ type="button"
427
+ className={`cs-export-chip${format === 'cast-csv' ? ' cs-export-chip-active' : ''}`}
428
+ onClick={() => setFormat('cast-csv')}
429
+ >
430
+ Spreadsheet (.csv)
431
+ </button>
432
+ </div>
433
+ </div>
434
+ <div style={{ fontSize: '11px', color: 'var(--cs-ui-text-light-secondary, #59574f)' }}>
435
+ Character occurrences, dialogue counts, and assigned actors list.
436
+ </div>
437
+ </div>
438
+
439
+ {hasIndicText && (
440
+ <div className="cs-settings-row" style={{ marginTop: '8px', padding: '10px', background: 'var(--cs-ui-bg-panel-subtle, #f4f2ea)', borderRadius: '6px', flexWrap: 'wrap', gap: '8px' }}>
441
+ <label className="cs-settings-label" htmlFor="cs-export-romanize-reports" style={{ fontSize: '12px' }}>
442
+ Romanized copy (ISO 15919)
443
+ </label>
444
+ <select
445
+ id="cs-export-romanize-reports"
446
+ className="cs-settings-select"
447
+ value={romanizeReports ? 'on' : 'off'}
448
+ onChange={e => setRomanizeReports(e.target.value === 'on')}
449
+ >
450
+ <option value="off">Off (native script)</option>
451
+ <option value="on">Romanize all text</option>
452
+ </select>
453
+ {romanizeReports && (
454
+ <select
455
+ aria-label="Romanization variant"
456
+ className="cs-settings-select"
457
+ value={romanizeVariant}
458
+ onChange={e => setRomanizeVariant(e.target.value as RomanizeVariant)}
459
+ >
460
+ <option value="strict">Strict ISO 15919</option>
461
+ <option value="colloquial">Casual (no diacritics)</option>
462
+ </select>
463
+ )}
464
+ </div>
465
+ )}
466
+ </div>
467
+ )}
468
+
469
+ {warnings.length > 0 && (
470
+ <div className="cs-export-warnings" style={{ background: '#fffbeb', border: '1px solid #fcd34d', padding: '10px', borderRadius: '4px', marginTop: '14px' }}>
471
+ <div className="cs-export-warn-title" style={{ fontWeight: 600, color: '#b45309', fontSize: '12px', marginBottom: '4px' }}>
472
+ {t('export.warnings')}
473
+ </div>
474
+ {warnings.map((w, i) => (
475
+ <div key={i} className="cs-export-warn-item" style={{ fontSize: '11px', color: '#59574f' }}>
476
+ • {w}
477
+ </div>
478
+ ))}
479
+ </div>
480
+ )}
481
+ </div>
482
+
483
+ <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)' }}>
484
+ <button type="button" className="cs-confirm-btn-cancel" onClick={() => setExportVisible(false)}>
485
+ {t('export.cancel')}
486
+ </button>
487
+ <button type="button" className="cs-confirm-btn-primary" onClick={doExport} disabled={isExporting}>
488
+ {isExporting ? 'Exporting...' : t('export.download')}
489
+ </button>
490
+ </div>
491
+ </div>
492
+ </div>
493
+ )
494
+ }