@sutrata/editor 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/README.md +87 -0
  2. package/dist/ai/VoiceConfirmDialog.d.ts +1 -0
  3. package/dist/ai/VoiceConfirmDialog.js +6 -6
  4. package/dist/ai/VoiceToolbar.d.ts +1 -0
  5. package/dist/ai/VoiceToolbar.js +22 -16
  6. package/dist/ai/clean-response.d.ts +7 -0
  7. package/dist/ai/clean-response.js +58 -0
  8. package/dist/ai/format-transcript.d.ts +3 -0
  9. package/dist/ai/format-transcript.js +7 -0
  10. package/dist/ai/prompts.d.ts +8 -1
  11. package/dist/ai/prompts.js +34 -7
  12. package/dist/autocomplete/suggestions.js +1 -1
  13. package/dist/context/DocumentContext.d.ts +28 -4
  14. package/dist/context/DocumentContext.js +67 -37
  15. package/dist/editor/EditorView.js +32 -4
  16. package/dist/editor/SelectionMenu.js +5 -3
  17. package/dist/editor/external-decorations-plugin.d.ts +23 -0
  18. package/dist/editor/external-decorations-plugin.js +151 -0
  19. package/dist/editor/omitted-scene-plugin.d.ts +9 -0
  20. package/dist/editor/omitted-scene-plugin.js +44 -0
  21. package/dist/editor/plugins.js +4 -0
  22. package/dist/editor/private-metadata-plugin.d.ts +10 -0
  23. package/dist/editor/private-metadata-plugin.js +60 -0
  24. package/dist/editor/prosemirror-to-sutra.js +179 -74
  25. package/dist/editor/scene-heading-view.js +2 -0
  26. package/dist/editor/schema.js +69 -12
  27. package/dist/editor/sutra-to-prosemirror.d.ts +2 -0
  28. package/dist/editor/sutra-to-prosemirror.js +32 -34
  29. package/dist/extensions/ai-provider.d.ts +44 -0
  30. package/dist/extensions/ai-provider.js +27 -0
  31. package/dist/extensions/collab.d.ts +21 -0
  32. package/dist/extensions/collab.js +6 -0
  33. package/dist/extensions/command-registry.d.ts +49 -0
  34. package/dist/extensions/command-registry.js +28 -0
  35. package/dist/extensions/decorations.d.ts +48 -0
  36. package/dist/extensions/decorations.js +6 -0
  37. package/dist/extensions/export-registry.d.ts +59 -0
  38. package/dist/extensions/export-registry.js +10 -0
  39. package/dist/extensions/panel-registry.d.ts +30 -0
  40. package/dist/extensions/panel-registry.js +16 -0
  41. package/dist/extensions/registries.d.ts +15 -0
  42. package/dist/extensions/registries.js +12 -0
  43. package/dist/extensions/registry-store.d.ts +26 -0
  44. package/dist/extensions/registry-store.js +35 -0
  45. package/dist/extensions/session.d.ts +29 -0
  46. package/dist/extensions/session.js +20 -0
  47. package/dist/extensions/speech-provider.d.ts +35 -0
  48. package/dist/extensions/speech-provider.js +17 -0
  49. package/dist/file/builtin-exporters.d.ts +9 -0
  50. package/dist/file/builtin-exporters.js +72 -0
  51. package/dist/file/docx-exporter.js +7 -4
  52. package/dist/file/docx-importer.d.ts +9 -0
  53. package/dist/file/docx-importer.js +47 -19
  54. package/dist/file/scene-number.d.ts +5 -0
  55. package/dist/file/scene-number.js +8 -0
  56. package/dist/file/workflow-reports.js +13 -7
  57. package/dist/findreplace/FindReplace.js +5 -2
  58. package/dist/import/AIImportDialog.d.ts +11 -0
  59. package/dist/import/AIImportDialog.js +112 -0
  60. package/dist/import/ai-import.d.ts +51 -0
  61. package/dist/import/ai-import.js +200 -0
  62. package/dist/import/apply-import.d.ts +7 -0
  63. package/dist/import/apply-import.js +35 -0
  64. package/dist/import/extract.d.ts +17 -0
  65. package/dist/import/extract.js +18 -0
  66. package/dist/index.d.ts +17 -2
  67. package/dist/index.js +7 -0
  68. package/dist/locales/bn.json +8 -0
  69. package/dist/locales/en.json +8 -0
  70. package/dist/locales/gu.json +8 -0
  71. package/dist/locales/hi.json +8 -0
  72. package/dist/locales/kn.json +8 -0
  73. package/dist/locales/ml.json +8 -0
  74. package/dist/locales/mr.json +8 -0
  75. package/dist/locales/or.json +8 -0
  76. package/dist/locales/pa.json +8 -0
  77. package/dist/locales/si.json +8 -0
  78. package/dist/locales/ta.json +8 -0
  79. package/dist/locales/te.json +8 -0
  80. package/dist/navigator/SceneNavigator.js +125 -131
  81. package/dist/navigator/StatisticsDialog.js +5 -1
  82. package/dist/navigator/scene-edits.d.ts +18 -0
  83. package/dist/navigator/scene-edits.js +143 -0
  84. package/dist/navigator/scene-list.js +4 -4
  85. package/dist/navigator/scene-numbering.d.ts +37 -0
  86. package/dist/navigator/scene-numbering.js +88 -0
  87. package/dist/settings/SettingsDialog.js +8 -120
  88. package/dist/shell/AppBar.js +21 -2
  89. package/dist/shell/AppShell.js +18 -4
  90. package/dist/shell/ElementToolbar.d.ts +5 -0
  91. package/dist/shell/ElementToolbar.js +35 -25
  92. package/dist/shell/LanguageStatus.js +6 -0
  93. package/dist/shell/StatusBar.js +11 -6
  94. package/dist/shell/builtin-commands.d.ts +10 -0
  95. package/dist/shell/builtin-commands.js +43 -0
  96. package/dist/shell/icons.d.ts +6 -0
  97. package/dist/shell/icons.js +6 -0
  98. package/dist/shell/use-commands.d.ts +7 -0
  99. package/dist/shell/use-commands.js +24 -0
  100. package/dist/shell/use-panel-context.d.ts +3 -0
  101. package/dist/shell/use-panel-context.js +39 -0
  102. package/dist/source/SourceView.js +18 -1
  103. package/dist/styles/screenplay.css +113 -1
  104. package/dist/titlepage/ExportDialog.d.ts +5 -0
  105. package/dist/titlepage/ExportDialog.js +59 -96
  106. package/package.json +32 -5
  107. package/dist/ai/OnboardingDialog.d.ts +0 -6
  108. package/dist/ai/OnboardingDialog.js +0 -115
  109. package/dist/ai/ai-client.d.ts +0 -71
  110. package/dist/ai/ai-client.js +0 -494
  111. package/dist/ai/generated-providers.json +0 -3696
  112. package/dist/ai/voice-service.d.ts +0 -15
  113. package/dist/ai/voice-service.js +0 -81
  114. package/dist/editor/character-view.d.ts +0 -35
  115. package/dist/editor/character-view.js +0 -112
  116. package/dist/editor/transliterate-input.d.ts +0 -10
  117. package/dist/editor/transliterate-input.js +0 -88
  118. package/dist/extensions/types.d.ts +0 -73
  119. package/dist/extensions/types.js +0 -12
  120. package/scripts/copy-assets.mjs +0 -16
  121. package/scripts/generate-providers.mjs +0 -127
  122. package/src/ai/OnboardingDialog.tsx +0 -390
  123. package/src/ai/VoiceConfirmDialog.tsx +0 -137
  124. package/src/ai/VoiceToolbar.tsx +0 -321
  125. package/src/ai/ai-client.ts +0 -596
  126. package/src/ai/commands.ts +0 -164
  127. package/src/ai/generated-providers.json +0 -3696
  128. package/src/ai/prompts.ts +0 -62
  129. package/src/ai/voice-service.ts +0 -96
  130. package/src/autocomplete/AutocompletePlugin.ts +0 -21
  131. package/src/autocomplete/suggestions.ts +0 -147
  132. package/src/context/DocumentContext.tsx +0 -648
  133. package/src/desktop-api.d.ts +0 -40
  134. package/src/editor/EditorView.tsx +0 -82
  135. package/src/editor/SelectionMenu.tsx +0 -152
  136. package/src/editor/attr-pencil-plugin.ts +0 -172
  137. package/src/editor/character-decoration-plugin.ts +0 -104
  138. package/src/editor/editor-bus.ts +0 -51
  139. package/src/editor/find-highlight-plugin.ts +0 -68
  140. package/src/editor/frontmatter-field-view.ts +0 -52
  141. package/src/editor/frontmatter-field.ts +0 -115
  142. package/src/editor/grapheme-cursor.ts +0 -99
  143. package/src/editor/ime-plugin.ts +0 -85
  144. package/src/editor/insert-helper.ts +0 -43
  145. package/src/editor/keymap.ts +0 -124
  146. package/src/editor/language-class-plugin.ts +0 -91
  147. package/src/editor/plugins.ts +0 -26
  148. package/src/editor/prosemirror-to-sutra.ts +0 -192
  149. package/src/editor/scene-heading-view.ts +0 -147
  150. package/src/editor/schema.ts +0 -156
  151. package/src/editor/sutra-to-prosemirror.ts +0 -156
  152. package/src/extensions/storage-adapter.ts +0 -65
  153. package/src/extensions/types.ts +0 -60
  154. package/src/file/docx-exporter.ts +0 -585
  155. package/src/file/docx-importer.ts +0 -313
  156. package/src/file/print-paginator.ts +0 -301
  157. package/src/file/templates.ts +0 -73
  158. package/src/file/workflow-reports.ts +0 -820
  159. package/src/findreplace/FindReplace.tsx +0 -242
  160. package/src/findreplace/find-engine.ts +0 -97
  161. package/src/findreplace/scroll-to-match.ts +0 -89
  162. package/src/fonts/tofu-detect.ts +0 -53
  163. package/src/i18n/LanguageContext.tsx +0 -207
  164. package/src/i18n/language-detect.ts +0 -73
  165. package/src/i18n/useTranslation.tsx +0 -74
  166. package/src/index.ts +0 -27
  167. package/src/locales/bn.json +0 -143
  168. package/src/locales/en.json +0 -143
  169. package/src/locales/gu.json +0 -143
  170. package/src/locales/hi.json +0 -143
  171. package/src/locales/kn.json +0 -143
  172. package/src/locales/ml.json +0 -143
  173. package/src/locales/mr.json +0 -143
  174. package/src/locales/or.json +0 -143
  175. package/src/locales/pa.json +0 -143
  176. package/src/locales/si.json +0 -143
  177. package/src/locales/ta.json +0 -143
  178. package/src/locales/te.json +0 -143
  179. package/src/navigator/SceneNavigator.tsx +0 -844
  180. package/src/navigator/StatisticsDialog.tsx +0 -249
  181. package/src/navigator/scene-list.ts +0 -84
  182. package/src/settings/SettingsDialog.tsx +0 -303
  183. package/src/shell/AppBar.tsx +0 -354
  184. package/src/shell/AppShell.tsx +0 -71
  185. package/src/shell/ConfirmDialog.tsx +0 -71
  186. package/src/shell/ElementToolbar.tsx +0 -175
  187. package/src/shell/LanguageStatus.tsx +0 -101
  188. package/src/shell/StatusBar.tsx +0 -135
  189. package/src/shell/SutrataLogo.tsx +0 -89
  190. package/src/shell/Toast.tsx +0 -46
  191. package/src/shell/ToolbarButton.tsx +0 -30
  192. package/src/shell/VersionHistory.tsx +0 -212
  193. package/src/shell/Watermark.tsx +0 -9
  194. package/src/shell/fonts/fonts.css +0 -1133
  195. package/src/shell/icons.tsx +0 -335
  196. package/src/shell/toolbar-actions.ts +0 -59
  197. package/src/source/SourceView.tsx +0 -111
  198. package/src/source/find-highlight-source.ts +0 -57
  199. package/src/source/sutra-lang.ts +0 -121
  200. package/src/spellcheck/dictionaries.ts +0 -21
  201. package/src/spellcheck/hunspell.ts +0 -110
  202. package/src/spellcheck/spellcheck-plugin.ts +0 -83
  203. package/src/styles/StyleDialog.tsx +0 -148
  204. package/src/styles/builtin-styles.ts +0 -139
  205. package/src/styles/css-adapter.ts +0 -76
  206. package/src/styles/docx-adapter.ts +0 -109
  207. package/src/styles/embed-fonts.ts +0 -81
  208. package/src/styles/pagination.ts +0 -27
  209. package/src/styles/print-adapter.ts +0 -288
  210. package/src/styles/registry.ts +0 -20
  211. package/src/styles/screenplay.css +0 -3006
  212. package/src/styles/script-ranges.ts +0 -17
  213. package/src/styles/types.ts +0 -104
  214. package/src/styles/validate.ts +0 -114
  215. package/src/templates/templates.ts +0 -194
  216. package/src/titlepage/ExportDialog.tsx +0 -494
  217. package/src/titlepage/TitlePageDialog.tsx +0 -312
  218. package/src/titlepage/frontmatter-form.ts +0 -280
  219. package/src/titlepage/languages.ts +0 -37
  220. package/src/types.ts +0 -7
  221. package/tests/AppBar.test.tsx +0 -35
  222. package/tests/AppShell.test.tsx +0 -22
  223. package/tests/ElementToolbar.test.tsx +0 -36
  224. package/tests/LanguageStatus.test.tsx +0 -129
  225. package/tests/SelectionMenu.test.tsx +0 -71
  226. package/tests/ToolbarButton.test.tsx +0 -24
  227. package/tests/ai-onboarding.test.ts +0 -102
  228. package/tests/ai-status.test.tsx +0 -72
  229. package/tests/ai-structured.test.ts +0 -94
  230. package/tests/autocomplete/AutocompletePlugin.test.ts +0 -20
  231. package/tests/autocomplete/suggestions.test.ts +0 -106
  232. package/tests/document-context.test.tsx +0 -565
  233. package/tests/docx-importer.test.ts +0 -160
  234. package/tests/editor/character-decoration-plugin.test.ts +0 -101
  235. package/tests/editor/character-extension.test.ts +0 -44
  236. package/tests/editor/grapheme-cursor.test.ts +0 -118
  237. package/tests/editor/ime-plugin.test.ts +0 -72
  238. package/tests/editor/inline-note-bridge.test.ts +0 -32
  239. package/tests/editor/language-class-plugin.test.ts +0 -265
  240. package/tests/editor/title-page.test.ts +0 -124
  241. package/tests/embed-fonts.test.ts +0 -17
  242. package/tests/exporters.test.ts +0 -365
  243. package/tests/findreplace/FindReplace.test.tsx +0 -391
  244. package/tests/findreplace/find-engine.test.ts +0 -86
  245. package/tests/findreplace/scroll-to-match.test.ts +0 -50
  246. package/tests/fonts/tofu-detect.test.ts +0 -122
  247. package/tests/i18n/LanguageContext.test.tsx +0 -146
  248. package/tests/i18n/language-detect.test.ts +0 -54
  249. package/tests/i18n/locale-parity.test.ts +0 -25
  250. package/tests/i18n/useTranslation.test.tsx +0 -45
  251. package/tests/keymap.test.ts +0 -18
  252. package/tests/navigator/SceneNavigator.test.tsx +0 -177
  253. package/tests/navigator/StatisticsDialog.test.tsx +0 -63
  254. package/tests/navigator/scene-list.test.ts +0 -71
  255. package/tests/print-paginator.test.ts +0 -82
  256. package/tests/shell/VersionHistory.test.tsx +0 -129
  257. package/tests/spellcheck/hunspell.test.ts +0 -82
  258. package/tests/spellcheck/spellcheck-plugin.test.ts +0 -56
  259. package/tests/styles.test.ts +0 -315
  260. package/tests/templates/templates.test.ts +0 -83
  261. package/tests/titlepage/TitlePageDialog.test.tsx +0 -90
  262. package/tests/titlepage/apply-title-page.test.ts +0 -61
  263. package/tests/titlepage/frontmatter-form.test.ts +0 -115
  264. package/tests/toolbar-actions.test.ts +0 -93
  265. package/tests/voice-commands.test.ts +0 -58
  266. package/tsconfig.json +0 -17
  267. package/vitest.config.ts +0 -11
  268. package/vitest.setup.ts +0 -65
@@ -1,354 +0,0 @@
1
- import React, { useState } from 'react'
2
- import { useDocument } from '../context/DocumentContext'
3
- import { useTranslation } from '../i18n/useTranslation'
4
- import {
5
- NavigatorIcon, TitlePageIcon, NewIcon, OpenIcon, SaveIcon, SaveAsIcon,
6
- SourceIcon, FormattedIcon, RibbonIcon, ExportIcon, SettingsIcon, ImportDocxIcon, StyleIcon,
7
- MicIcon, MenuIcon, CloseIcon,
8
- } from './icons'
9
- import { SutrataLogo } from './SutrataLogo'
10
- import { getTemplate } from '../templates/templates'
11
- import { openTitlePageForm } from '../titlepage/TitlePageDialog'
12
-
13
- interface AbBtnProps {
14
- label: string
15
- shortcut?: string
16
- active?: boolean
17
- variant?: 'primary' | 'secondary'
18
- onClick: () => void
19
- children: React.ReactNode
20
- }
21
-
22
- function AbBtn({ label, shortcut, active, variant, onClick, children }: AbBtnProps) {
23
- const variantClass = variant === 'primary' ? ' cs-ab-btn-primary' : variant === 'secondary' ? ' cs-ab-btn-secondary' : ''
24
- return (
25
- <button
26
- type="button"
27
- aria-label={label}
28
- className={`cs-ab-btn${active ? ' cs-ab-active' : ''}${variantClass}`}
29
- onClick={onClick}
30
- >
31
- {children}
32
- <span className="cs-tb-tip" role="tooltip">
33
- <span>{label}</span>
34
- {shortcut ? <span className="cs-tb-key">{shortcut}</span> : null}
35
- </span>
36
- </button>
37
- )
38
- }
39
-
40
- export function AppBar() {
41
- const {
42
- mode, setMode, isDirty, filePath, ribbonVisible, setRibbonVisible,
43
- navVisible, setNavVisible, exportVisible, setExportVisible,
44
- settingsVisible, setSettingsVisible, metadataVisible, setMetadataVisible,
45
- voiceActive, setVoiceActive, newDocument, openFile, saveFile, saveFileAs, setText,
46
- importDocx, styleVisible, setStyleVisible, showConfirm, showToast,
47
- } = useDocument()
48
- const { t, locale } = useTranslation()
49
- const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
50
- const name = filePath ?? 'untitled.sutra'
51
- const nextMode = mode === 'formatted' ? 'source' : 'formatted'
52
-
53
- const handleNew = () => {
54
- newDocument(getTemplate(locale))
55
- }
56
-
57
- const handleImportDocx = async () => {
58
- try {
59
- const warnings = await importDocx()
60
- if (warnings && warnings.length) {
61
- showToast(warnings.join(' • '), 'warn')
62
- }
63
- } catch (err) {
64
- showToast(err instanceof Error ? err.message : 'Failed to import the Word document.', 'error')
65
- }
66
- }
67
-
68
- return (
69
- <header className="cs-app-bar" role="banner" aria-label="Application header">
70
- {/* Left zone: Brand mark, Navigator toggle, and Document identifier */}
71
- <div className="cs-ab-group cs-ab-left">
72
- <div className="cs-ab-brand" title="Sutrata Multilingual Screenplay Editor">
73
- <SutrataLogo size={20} showText={false} />
74
- <span className="cs-ab-brand-title">Sutrata</span>
75
- </div>
76
- <span className="cs-ab-sep cs-ab-desktop-only" />
77
- <AbBtn
78
- label={t('appbar.navigator')}
79
- shortcut="Ctrl+Shift+B"
80
- active={navVisible}
81
- onClick={() => setNavVisible(!navVisible)}
82
- >
83
- <NavigatorIcon size={18} />
84
- </AbBtn>
85
- <span className="cs-ab-desktop-only">
86
- <AbBtn label={t('appbar.titlePage')} onClick={openTitlePageForm}>
87
- <TitlePageIcon size={18} />
88
- </AbBtn>
89
- </span>
90
- <div className="cs-ab-file-pill" title={filePath ?? 'untitled.sutra'}>
91
- <span className="cs-ab-file-name">{name}</span>
92
- {isDirty && <span className="cs-ab-dirty" title="Unsaved edits" />}
93
- </div>
94
- </div>
95
-
96
- <span className="cs-ab-spacer cs-ab-desktop-only" />
97
-
98
- {/* Center zone: View Mode, Ribbon, Metadata (Desktop only) */}
99
- <div className="cs-ab-group cs-ab-desktop-group">
100
- <AbBtn
101
- label={mode === 'formatted' ? t('appbar.switchToSource') : t('appbar.switchToFormatted')}
102
- shortcut="Ctrl+Shift+E"
103
- onClick={() => setMode(nextMode)}
104
- >
105
- {mode === 'formatted' ? <SourceIcon size={18} /> : <FormattedIcon size={18} />}
106
- </AbBtn>
107
- <AbBtn
108
- label={ribbonVisible ? t('appbar.hideToolbar') : t('appbar.showToolbar')}
109
- shortcut="Ctrl+Shift+H"
110
- active={ribbonVisible}
111
- onClick={() => setRibbonVisible(!ribbonVisible)}
112
- >
113
- <RibbonIcon size={18} />
114
- </AbBtn>
115
- <AbBtn
116
- label="Voice Dictation"
117
- shortcut="Ctrl+Shift+V"
118
- active={voiceActive}
119
- onClick={() => setVoiceActive(!voiceActive)}
120
- >
121
- <MicIcon size={18} />
122
- </AbBtn>
123
- <AbBtn
124
- label={metadataVisible ? 'Hide Metadata' : 'Show Metadata'}
125
- active={metadataVisible}
126
- onClick={() => setMetadataVisible(!metadataVisible)}
127
- >
128
- <span style={{ fontWeight: 700, fontSize: '13px' }}>&amp;</span>
129
- </AbBtn>
130
- </div>
131
-
132
- <span className="cs-ab-spacer cs-ab-desktop-only" />
133
-
134
- {/* Right zone: File Operations, Save, Export, Settings (Desktop only) */}
135
- <div className="cs-ab-group cs-ab-desktop-group">
136
- <AbBtn label={t('appbar.new')} onClick={handleNew}>
137
- <NewIcon size={18} />
138
- </AbBtn>
139
- <AbBtn label={t('appbar.open')} shortcut="Ctrl+O" onClick={() => { void openFile() }}>
140
- <OpenIcon size={18} />
141
- </AbBtn>
142
- <AbBtn label={t('appbar.importDocx')} onClick={() => { void handleImportDocx() }}>
143
- <ImportDocxIcon size={18} />
144
- </AbBtn>
145
- <span className="cs-ab-sep" />
146
- <AbBtn
147
- label={t('appbar.save')}
148
- shortcut="Ctrl+S"
149
- variant="primary"
150
- onClick={() => { void saveFile() }}
151
- >
152
- <SaveIcon size={17} />
153
- </AbBtn>
154
- <AbBtn label={t('appbar.saveAs')} shortcut="Ctrl+Shift+S" onClick={() => { void saveFileAs() }}>
155
- <SaveAsIcon size={17} />
156
- </AbBtn>
157
- <span className="cs-ab-sep" />
158
- <AbBtn
159
- label={t('appbar.export')}
160
- variant="secondary"
161
- active={exportVisible}
162
- onClick={() => setExportVisible(!exportVisible)}
163
- >
164
- <ExportIcon size={18} />
165
- </AbBtn>
166
- <AbBtn label={t('appbar.style')} active={styleVisible} onClick={() => setStyleVisible(!styleVisible)}>
167
- <StyleIcon size={18} />
168
- </AbBtn>
169
- <AbBtn label={t('settings.title')} active={settingsVisible} onClick={() => setSettingsVisible(!settingsVisible)}>
170
- <SettingsIcon size={18} />
171
- </AbBtn>
172
- </div>
173
-
174
- {/* Mobile controls zone */}
175
- <div className="cs-ab-group cs-ab-mobile-controls">
176
- <AbBtn
177
- label={t('appbar.save')}
178
- variant={isDirty ? 'primary' : undefined}
179
- onClick={() => { void saveFile() }}
180
- >
181
- <SaveIcon size={17} />
182
- </AbBtn>
183
- <AbBtn
184
- label="Menu"
185
- active={mobileMenuOpen}
186
- onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
187
- >
188
- {mobileMenuOpen ? <CloseIcon size={18} /> : <MenuIcon size={18} />}
189
- </AbBtn>
190
- </div>
191
-
192
- {/* Mobile Menu Drawer */}
193
- {mobileMenuOpen && (
194
- <>
195
- <div
196
- className="cs-mobile-menu-backdrop"
197
- aria-hidden="true"
198
- onClick={() => setMobileMenuOpen(false)}
199
- />
200
- <div
201
- className="cs-mobile-menu-drawer"
202
- role="dialog"
203
- aria-modal="true"
204
- aria-label="Application Menu"
205
- >
206
- <div className="cs-mm-header">
207
- <div className="cs-mm-brand">
208
- <SutrataLogo size={22} showText={false} />
209
- <span>Sutrata</span>
210
- </div>
211
- <button
212
- type="button"
213
- className="cs-mm-close"
214
- aria-label="Close menu"
215
- onClick={() => setMobileMenuOpen(false)}
216
- >
217
- <CloseIcon size={18} />
218
- </button>
219
- </div>
220
-
221
- <div className="cs-mm-file-info">
222
- <span className="cs-mm-file-name">{name}</span>
223
- {isDirty && <span className="cs-ab-dirty" title="Unsaved edits" />}
224
- </div>
225
-
226
- <div className="cs-mm-section">
227
- <div className="cs-mm-section-title">Views &amp; Tools</div>
228
- <button
229
- type="button"
230
- className="cs-mm-item"
231
- onClick={() => { setMode(nextMode); setMobileMenuOpen(false) }}
232
- >
233
- {mode === 'formatted' ? <SourceIcon size={18} /> : <FormattedIcon size={18} />}
234
- <span>{mode === 'formatted' ? t('appbar.switchToSource') : t('appbar.switchToFormatted')}</span>
235
- </button>
236
- <button
237
- type="button"
238
- className={`cs-mm-item${navVisible ? ' cs-mm-active' : ''}`}
239
- onClick={() => { setNavVisible(!navVisible); setMobileMenuOpen(false) }}
240
- >
241
- <NavigatorIcon size={18} />
242
- <span>{t('appbar.navigator')}</span>
243
- </button>
244
- <button
245
- type="button"
246
- className={`cs-mm-item${ribbonVisible ? ' cs-mm-active' : ''}`}
247
- onClick={() => { setRibbonVisible(!ribbonVisible); setMobileMenuOpen(false) }}
248
- >
249
- <RibbonIcon size={18} />
250
- <span>{ribbonVisible ? t('appbar.hideToolbar') : t('appbar.showToolbar')}</span>
251
- </button>
252
- <button
253
- type="button"
254
- className={`cs-mm-item${voiceActive ? ' cs-mm-active' : ''}`}
255
- onClick={() => { setVoiceActive(!voiceActive); setMobileMenuOpen(false) }}
256
- >
257
- <MicIcon size={18} />
258
- <span>Voice Dictation</span>
259
- </button>
260
- <button
261
- type="button"
262
- className={`cs-mm-item${metadataVisible ? ' cs-mm-active' : ''}`}
263
- onClick={() => { setMetadataVisible(!metadataVisible); setMobileMenuOpen(false) }}
264
- >
265
- <span style={{ fontWeight: 700, width: 18, textAlign: 'center' }}>&amp;</span>
266
- <span>{metadataVisible ? 'Hide Metadata' : 'Show Metadata'}</span>
267
- </button>
268
- </div>
269
-
270
- <div className="cs-mm-section">
271
- <div className="cs-mm-section-title">File Operations</div>
272
- <button
273
- type="button"
274
- className="cs-mm-item"
275
- onClick={() => { openTitlePageForm(); setMobileMenuOpen(false) }}
276
- >
277
- <TitlePageIcon size={18} />
278
- <span>{t('appbar.titlePage')}</span>
279
- </button>
280
- <button
281
- type="button"
282
- className="cs-mm-item"
283
- onClick={() => { handleNew(); setMobileMenuOpen(false) }}
284
- >
285
- <NewIcon size={18} />
286
- <span>{t('appbar.new')}</span>
287
- </button>
288
- <button
289
- type="button"
290
- className="cs-mm-item"
291
- onClick={() => { void openFile(); setMobileMenuOpen(false) }}
292
- >
293
- <OpenIcon size={18} />
294
- <span>{t('appbar.open')}</span>
295
- </button>
296
- <button
297
- type="button"
298
- className="cs-mm-item"
299
- onClick={() => { void handleImportDocx(); setMobileMenuOpen(false) }}
300
- >
301
- <ImportDocxIcon size={18} />
302
- <span>{t('appbar.importDocx')}</span>
303
- </button>
304
- <button
305
- type="button"
306
- className="cs-mm-item cs-mm-primary"
307
- onClick={() => { void saveFile(); setMobileMenuOpen(false) }}
308
- >
309
- <SaveIcon size={18} />
310
- <span>{t('appbar.save')}</span>
311
- </button>
312
- <button
313
- type="button"
314
- className="cs-mm-item"
315
- onClick={() => { void saveFileAs(); setMobileMenuOpen(false) }}
316
- >
317
- <SaveAsIcon size={18} />
318
- <span>{t('appbar.saveAs')}</span>
319
- </button>
320
- <button
321
- type="button"
322
- className="cs-mm-item"
323
- onClick={() => { setExportVisible(true); setMobileMenuOpen(false) }}
324
- >
325
- <ExportIcon size={18} />
326
- <span>{t('appbar.export')}</span>
327
- </button>
328
- </div>
329
-
330
- <div className="cs-mm-section">
331
- <div className="cs-mm-section-title">Preferences</div>
332
- <button
333
- type="button"
334
- className="cs-mm-item"
335
- onClick={() => { setStyleVisible(true); setMobileMenuOpen(false) }}
336
- >
337
- <StyleIcon size={18} />
338
- <span>{t('appbar.style')}</span>
339
- </button>
340
- <button
341
- type="button"
342
- className="cs-mm-item"
343
- onClick={() => { setSettingsVisible(true); setMobileMenuOpen(false) }}
344
- >
345
- <SettingsIcon size={18} />
346
- <span>{t('settings.title')}</span>
347
- </button>
348
- </div>
349
- </div>
350
- </>
351
- )}
352
- </header>
353
- )
354
- }
@@ -1,71 +0,0 @@
1
- import React, { useEffect, useState } from 'react'
2
- import { AppBar } from './AppBar'
3
- import { ElementToolbar } from './ElementToolbar'
4
- import { StatusBar } from './StatusBar'
5
- import { EditorView } from '../editor/EditorView'
6
- import { SourceView } from '../source/SourceView'
7
- import { useDocument } from '../context/DocumentContext'
8
- import { SceneNavigator } from '../navigator/SceneNavigator'
9
- import { FindReplace } from '../findreplace/FindReplace'
10
- import { ExportDialog } from '../titlepage/ExportDialog'
11
- import { TitlePageDialog, OPEN_TITLE_PAGE_EVENT } from '../titlepage/TitlePageDialog'
12
- import { SettingsDialog } from '../settings/SettingsDialog'
13
- import { StyleDialog } from '../styles/StyleDialog'
14
- import { Watermark } from './Watermark'
15
- import { ConfirmDialog } from './ConfirmDialog'
16
- import { ToastContainer } from './Toast'
17
- import { VoiceToolbar } from '../ai/VoiceToolbar'
18
- import { OnboardingDialog } from '../ai/OnboardingDialog'
19
- import { SelectionMenu } from '../editor/SelectionMenu'
20
-
21
- export function AppShell() {
22
- const {
23
- mode, ribbonVisible, navVisible, setNavVisible, exportVisible, settingsVisible,
24
- setSettingsVisible, styleVisible, setStyleVisible, metadataVisible,
25
- voiceActive, aiOnboardingVisible, setAIOnboardingVisible,
26
- confirmModal, closeConfirm, toasts, dismissToast,
27
- } = useDocument()
28
- const [titlePageVisible, setTitlePageVisible] = useState(false)
29
-
30
- useEffect(() => {
31
- const open = () => setTitlePageVisible(true)
32
- window.addEventListener(OPEN_TITLE_PAGE_EVENT, open)
33
- return () => window.removeEventListener(OPEN_TITLE_PAGE_EVENT, open)
34
- }, [])
35
-
36
- return (
37
- <div className={`cs-shell ${metadataVisible ? '' : 'cs-hide-metadata'}`}>
38
- <Watermark />
39
- <AppBar />
40
- {voiceActive && <VoiceToolbar />}
41
- {ribbonVisible && <ElementToolbar />}
42
- <div className="cs-shell-body">
43
- {navVisible && (
44
- <>
45
- <div
46
- className="cs-nav-backdrop"
47
- aria-hidden="true"
48
- onClick={() => setNavVisible(false)}
49
- />
50
- <SceneNavigator />
51
- </>
52
- )}
53
- <div className="cs-editor-area">
54
- <main className="cs-main">
55
- {mode === 'formatted' ? <EditorView /> : <SourceView />}
56
- </main>
57
- </div>
58
- </div>
59
- <StatusBar />
60
- <FindReplace />
61
- {exportVisible && <ExportDialog />}
62
- {titlePageVisible && <TitlePageDialog onClose={() => setTitlePageVisible(false)} />}
63
- {settingsVisible && <SettingsDialog onClose={() => setSettingsVisible(false)} />}
64
- {styleVisible && <StyleDialog onClose={() => setStyleVisible(false)} />}
65
- {aiOnboardingVisible && <OnboardingDialog onClose={() => setAIOnboardingVisible(false)} />}
66
- {confirmModal && <ConfirmDialog options={confirmModal} onClose={closeConfirm} />}
67
- <ToastContainer toasts={toasts} onDismiss={dismissToast} />
68
- <SelectionMenu />
69
- </div>
70
- )
71
- }
@@ -1,71 +0,0 @@
1
- import React, { useEffect, useRef } from 'react'
2
-
3
- export interface ConfirmOptions {
4
- title: string
5
- message: string
6
- confirmLabel?: string
7
- cancelLabel?: string
8
- destructive?: boolean
9
- onConfirm: () => void
10
- onCancel?: () => void
11
- }
12
-
13
- interface ConfirmDialogProps {
14
- options: ConfirmOptions
15
- onClose: () => void
16
- }
17
-
18
- export function ConfirmDialog({ options, onClose }: ConfirmDialogProps) {
19
- const confirmBtnRef = useRef<HTMLButtonElement>(null)
20
-
21
- useEffect(() => {
22
- confirmBtnRef.current?.focus()
23
- const onKey = (e: KeyboardEvent) => {
24
- if (e.key === 'Escape') {
25
- options.onCancel?.()
26
- onClose()
27
- }
28
- }
29
- window.addEventListener('keydown', onKey)
30
- return () => window.removeEventListener('keydown', onKey)
31
- }, [options, onClose])
32
-
33
- return (
34
- <div className="cs-confirm-overlay" role="presentation" onClick={() => { options.onCancel?.(); onClose() }}>
35
- <div
36
- className="cs-confirm-dialog"
37
- role="alertdialog"
38
- aria-modal="true"
39
- aria-labelledby="cs-confirm-title"
40
- aria-describedby="cs-confirm-desc"
41
- onClick={(e) => e.stopPropagation()}
42
- >
43
- <div id="cs-confirm-title" className="cs-confirm-title">{options.title}</div>
44
- <div id="cs-confirm-desc" className="cs-confirm-msg">{options.message}</div>
45
- <div className="cs-confirm-actions">
46
- <button
47
- type="button"
48
- className="cs-confirm-btn-cancel"
49
- onClick={() => {
50
- options.onCancel?.()
51
- onClose()
52
- }}
53
- >
54
- {options.cancelLabel ?? 'Cancel'}
55
- </button>
56
- <button
57
- ref={confirmBtnRef}
58
- type="button"
59
- className={options.destructive ? 'cs-confirm-btn-danger' : 'cs-confirm-btn-primary'}
60
- onClick={() => {
61
- options.onConfirm()
62
- onClose()
63
- }}
64
- >
65
- {options.confirmLabel ?? 'Confirm'}
66
- </button>
67
- </div>
68
- </div>
69
- </div>
70
- )
71
- }
@@ -1,175 +0,0 @@
1
- import React, { useEffect, useState } from 'react'
2
- import { ToolbarButton } from './ToolbarButton'
3
- import { makeSetBlock, makeSetOrToggleNote, makeToggleMark, undo, redo } from './toolbar-actions'
4
- import { runCommand, activeBlockType, subscribe } from '../editor/editor-bus'
5
- import { useTranslation } from '../i18n/useTranslation'
6
- import {
7
- UndoIcon, RedoIcon, SceneIcon, ActionIcon, CharacterIcon, DialogueIcon, ParentheticalIcon,
8
- TransitionIcon, CenteredIcon, LyricsIcon, NoteIcon, PageBreakIcon, SectionIcon, BoldIcon, ItalicIcon, UnderlineIcon, OverflowIcon,
9
- CloseIcon,
10
- } from './icons'
11
-
12
- interface ElementDef {
13
- type: string
14
- label: string
15
- sigil: string
16
- shortcut: string
17
- Icon: React.FC<{ size?: number }>
18
- translationKey: string
19
- }
20
-
21
- const ELEMENTS: ElementDef[] = [
22
- { type: 'scene_heading', label: 'Scene Heading', sigil: '##', shortcut: 'Alt+Shift+H', Icon: SceneIcon, translationKey: 'toolbar.scene' },
23
- { type: 'action', label: 'Action', sigil: '(plain)', shortcut: 'Alt+Shift+A', Icon: ActionIcon, translationKey: 'toolbar.action' },
24
- { type: 'character', label: 'Character', sigil: '@', shortcut: 'Alt+Shift+C', Icon: CharacterIcon, translationKey: 'toolbar.character' },
25
- { type: 'dialogue', label: 'Dialogue', sigil: '(after @)', shortcut: 'Alt+Shift+D', Icon: DialogueIcon, translationKey: 'toolbar.dialogue' },
26
- { type: 'parenthetical', label: 'Parenthetical', sigil: '( )', shortcut: 'Alt+Shift+P', Icon: ParentheticalIcon, translationKey: 'toolbar.parenthetical' },
27
- { type: 'transition', label: 'Transition', sigil: '>>', shortcut: 'Alt+Shift+X', Icon: TransitionIcon, translationKey: 'toolbar.transition' },
28
- { type: 'centered', label: 'Centered', sigil: '>> <<', shortcut: '', Icon: CenteredIcon, translationKey: 'toolbar.centered' },
29
- { type: 'lyrics', label: 'Lyrics', sigil: '~', shortcut: 'Alt+Shift+L', Icon: LyricsIcon, translationKey: 'toolbar.lyrics' },
30
- { type: 'note', label: 'Note', sigil: '[[ ]]', shortcut: 'Alt+Shift+N', Icon: NoteIcon, translationKey: 'toolbar.note' },
31
- { type: 'page_break', label: 'Page Break', sigil: '===', shortcut: '', Icon: PageBreakIcon, translationKey: 'toolbar.pageBreak' },
32
- { type: 'section', label: 'Section', sigil: '#', shortcut: '', Icon: SectionIcon, translationKey: 'toolbar.section' },
33
- ]
34
-
35
- export function ElementToolbar() {
36
- const [activeType, setActiveType] = useState<string | null>(null)
37
- const [overflowOpen, setOverflowOpen] = useState(false)
38
- const { t } = useTranslation()
39
-
40
- useEffect(() => subscribe(() => setActiveType(activeBlockType())), [])
41
-
42
- return (
43
- <>
44
- <div className="cs-element-toolbar" role="toolbar" aria-label="Editing toolbar">
45
- <ToolbarButton label={t('toolbar.undo')} shortcut="Mod-Z" onClick={() => runCommand(undo)}><UndoIcon size={20} /></ToolbarButton>
46
- <ToolbarButton label={t('toolbar.redo')} shortcut="Mod-Shift-Z" onClick={() => runCommand(redo)}><RedoIcon size={20} /></ToolbarButton>
47
- <span className="cs-tb-sep" />
48
- {ELEMENTS.map(el => (
49
- <ToolbarButton
50
- key={el.type}
51
- label={t(el.translationKey)}
52
- sigil={el.sigil}
53
- shortcut={el.shortcut || undefined}
54
- active={activeType === el.type}
55
- onClick={() => runCommand(el.type === 'note' ? makeSetOrToggleNote() : makeSetBlock(el.type))}
56
- >
57
- <el.Icon size={20} />
58
- </ToolbarButton>
59
- ))}
60
- <span className="cs-tb-sep" />
61
- <ToolbarButton label={t('toolbar.bold')} shortcut="Mod-B" onClick={() => runCommand(makeToggleMark('bold'))}><BoldIcon size={20} /></ToolbarButton>
62
- <ToolbarButton label={t('toolbar.italic')} shortcut="Mod-I" onClick={() => runCommand(makeToggleMark('italic'))}><ItalicIcon size={20} /></ToolbarButton>
63
- <ToolbarButton label={t('toolbar.underline')} shortcut="Mod-U" onClick={() => runCommand(makeToggleMark('underline'))}><UnderlineIcon size={20} /></ToolbarButton>
64
- <button
65
- type="button"
66
- className={`cs-tb-overflow${overflowOpen ? ' cs-tb-overflow-active' : ''}`}
67
- aria-label="More"
68
- aria-expanded={overflowOpen}
69
- onClick={() => setOverflowOpen(!overflowOpen)}
70
- >
71
- <OverflowIcon size={18} />
72
- </button>
73
- </div>
74
-
75
- {/* Mobile Overflow Bottom Sheet */}
76
- {overflowOpen && (
77
- <>
78
- <div
79
- className="cs-tb-overflow-backdrop"
80
- aria-hidden="true"
81
- onClick={() => setOverflowOpen(false)}
82
- />
83
- <div
84
- className="cs-tb-overflow-sheet"
85
- role="dialog"
86
- aria-modal="true"
87
- aria-label="Screenplay Elements & Formatting"
88
- >
89
- <div className="cs-tb-overflow-header">
90
- <span className="cs-tb-overflow-title">Formatting &amp; Elements</span>
91
- <button
92
- type="button"
93
- className="cs-tb-overflow-close"
94
- aria-label="Close"
95
- onClick={() => setOverflowOpen(false)}
96
- >
97
- <CloseIcon size={18} />
98
- </button>
99
- </div>
100
-
101
- <div className="cs-tb-overflow-section-label">Inline Formatting</div>
102
- <div className="cs-tb-overflow-inline-grid">
103
- <button
104
- type="button"
105
- className="cs-tb-overflow-item"
106
- onClick={() => { runCommand(makeToggleMark('bold')); setOverflowOpen(false) }}
107
- >
108
- <BoldIcon size={18} />
109
- <span>{t('toolbar.bold')}</span>
110
- </button>
111
- <button
112
- type="button"
113
- className="cs-tb-overflow-item"
114
- onClick={() => { runCommand(makeToggleMark('italic')); setOverflowOpen(false) }}
115
- >
116
- <ItalicIcon size={18} />
117
- <span>{t('toolbar.italic')}</span>
118
- </button>
119
- <button
120
- type="button"
121
- className="cs-tb-overflow-item"
122
- onClick={() => { runCommand(makeToggleMark('underline')); setOverflowOpen(false) }}
123
- >
124
- <UnderlineIcon size={18} />
125
- <span>{t('toolbar.underline')}</span>
126
- </button>
127
- <button
128
- type="button"
129
- className="cs-tb-overflow-item"
130
- onClick={() => { runCommand(undo); setOverflowOpen(false) }}
131
- >
132
- <UndoIcon size={18} />
133
- <span>{t('toolbar.undo')}</span>
134
- </button>
135
- <button
136
- type="button"
137
- className="cs-tb-overflow-item"
138
- onClick={() => { runCommand(redo); setOverflowOpen(false) }}
139
- >
140
- <RedoIcon size={18} />
141
- <span>{t('toolbar.redo')}</span>
142
- </button>
143
- </div>
144
-
145
- <div className="cs-tb-overflow-section-label">Screenplay Elements</div>
146
- <div className="cs-tb-overflow-list">
147
- {ELEMENTS.map(el => (
148
- <button
149
- key={el.type}
150
- type="button"
151
- className={`cs-tb-overflow-list-item${activeType === el.type ? ' cs-tb-overflow-item-active' : ''}`}
152
- onClick={() => {
153
- runCommand(el.type === 'note' ? makeSetOrToggleNote() : makeSetBlock(el.type))
154
- setOverflowOpen(false)
155
- }}
156
- >
157
- <div className="cs-tb-overflow-list-icon">
158
- <el.Icon size={18} />
159
- </div>
160
- <div className="cs-tb-overflow-list-text">
161
- <span className="cs-tb-overflow-list-name">{t(el.translationKey)}</span>
162
- <span className="cs-tb-overflow-list-sigil">{el.sigil}</span>
163
- </div>
164
- {activeType === el.type && (
165
- <span className="cs-tb-overflow-current-badge">Active</span>
166
- )}
167
- </button>
168
- ))}
169
- </div>
170
- </div>
171
- </>
172
- )}
173
- </>
174
- )
175
- }