@sutrata/editor 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -0
- package/dist/ai/generated-providers.json +1 -1
- package/package.json +36 -9
- package/scripts/copy-assets.mjs +0 -16
- package/scripts/generate-providers.mjs +0 -127
- package/src/ai/OnboardingDialog.tsx +0 -390
- package/src/ai/VoiceConfirmDialog.tsx +0 -137
- package/src/ai/VoiceToolbar.tsx +0 -321
- package/src/ai/ai-client.ts +0 -596
- package/src/ai/commands.ts +0 -164
- package/src/ai/generated-providers.json +0 -3696
- package/src/ai/prompts.ts +0 -62
- package/src/ai/voice-service.ts +0 -96
- package/src/autocomplete/AutocompletePlugin.ts +0 -21
- package/src/autocomplete/suggestions.ts +0 -147
- package/src/context/DocumentContext.tsx +0 -648
- package/src/desktop-api.d.ts +0 -40
- package/src/editor/EditorView.tsx +0 -82
- package/src/editor/SelectionMenu.tsx +0 -152
- package/src/editor/attr-pencil-plugin.ts +0 -172
- package/src/editor/character-decoration-plugin.ts +0 -104
- package/src/editor/editor-bus.ts +0 -51
- package/src/editor/find-highlight-plugin.ts +0 -68
- package/src/editor/frontmatter-field-view.ts +0 -52
- package/src/editor/frontmatter-field.ts +0 -115
- package/src/editor/grapheme-cursor.ts +0 -99
- package/src/editor/ime-plugin.ts +0 -85
- package/src/editor/insert-helper.ts +0 -43
- package/src/editor/keymap.ts +0 -124
- package/src/editor/language-class-plugin.ts +0 -91
- package/src/editor/plugins.ts +0 -26
- package/src/editor/prosemirror-to-sutra.ts +0 -192
- package/src/editor/scene-heading-view.ts +0 -147
- package/src/editor/schema.ts +0 -156
- package/src/editor/sutra-to-prosemirror.ts +0 -156
- package/src/extensions/storage-adapter.ts +0 -65
- package/src/extensions/types.ts +0 -60
- package/src/file/docx-exporter.ts +0 -585
- package/src/file/docx-importer.ts +0 -313
- package/src/file/print-paginator.ts +0 -301
- package/src/file/templates.ts +0 -73
- package/src/file/workflow-reports.ts +0 -820
- package/src/findreplace/FindReplace.tsx +0 -242
- package/src/findreplace/find-engine.ts +0 -97
- package/src/findreplace/scroll-to-match.ts +0 -89
- package/src/fonts/tofu-detect.ts +0 -53
- package/src/i18n/LanguageContext.tsx +0 -207
- package/src/i18n/language-detect.ts +0 -73
- package/src/i18n/useTranslation.tsx +0 -74
- package/src/index.ts +0 -27
- package/src/locales/bn.json +0 -143
- package/src/locales/en.json +0 -143
- package/src/locales/gu.json +0 -143
- package/src/locales/hi.json +0 -143
- package/src/locales/kn.json +0 -143
- package/src/locales/ml.json +0 -143
- package/src/locales/mr.json +0 -143
- package/src/locales/or.json +0 -143
- package/src/locales/pa.json +0 -143
- package/src/locales/si.json +0 -143
- package/src/locales/ta.json +0 -143
- package/src/locales/te.json +0 -143
- package/src/navigator/SceneNavigator.tsx +0 -844
- package/src/navigator/StatisticsDialog.tsx +0 -249
- package/src/navigator/scene-list.ts +0 -84
- package/src/settings/SettingsDialog.tsx +0 -303
- package/src/shell/AppBar.tsx +0 -354
- package/src/shell/AppShell.tsx +0 -71
- package/src/shell/ConfirmDialog.tsx +0 -71
- package/src/shell/ElementToolbar.tsx +0 -175
- package/src/shell/LanguageStatus.tsx +0 -101
- package/src/shell/StatusBar.tsx +0 -135
- package/src/shell/SutrataLogo.tsx +0 -89
- package/src/shell/Toast.tsx +0 -46
- package/src/shell/ToolbarButton.tsx +0 -30
- package/src/shell/VersionHistory.tsx +0 -212
- package/src/shell/Watermark.tsx +0 -9
- package/src/shell/fonts/fonts.css +0 -1133
- package/src/shell/icons.tsx +0 -335
- package/src/shell/toolbar-actions.ts +0 -59
- package/src/source/SourceView.tsx +0 -111
- package/src/source/find-highlight-source.ts +0 -57
- package/src/source/sutra-lang.ts +0 -121
- package/src/spellcheck/dictionaries.ts +0 -21
- package/src/spellcheck/hunspell.ts +0 -110
- package/src/spellcheck/spellcheck-plugin.ts +0 -83
- package/src/styles/StyleDialog.tsx +0 -148
- package/src/styles/builtin-styles.ts +0 -139
- package/src/styles/css-adapter.ts +0 -76
- package/src/styles/docx-adapter.ts +0 -109
- package/src/styles/embed-fonts.ts +0 -81
- package/src/styles/pagination.ts +0 -27
- package/src/styles/print-adapter.ts +0 -288
- package/src/styles/registry.ts +0 -20
- package/src/styles/screenplay.css +0 -3006
- package/src/styles/script-ranges.ts +0 -17
- package/src/styles/types.ts +0 -104
- package/src/styles/validate.ts +0 -114
- package/src/templates/templates.ts +0 -194
- package/src/titlepage/ExportDialog.tsx +0 -494
- package/src/titlepage/TitlePageDialog.tsx +0 -312
- package/src/titlepage/frontmatter-form.ts +0 -280
- package/src/titlepage/languages.ts +0 -37
- package/src/types.ts +0 -7
- package/tests/AppBar.test.tsx +0 -35
- package/tests/AppShell.test.tsx +0 -22
- package/tests/ElementToolbar.test.tsx +0 -36
- package/tests/LanguageStatus.test.tsx +0 -129
- package/tests/SelectionMenu.test.tsx +0 -71
- package/tests/ToolbarButton.test.tsx +0 -24
- package/tests/ai-onboarding.test.ts +0 -102
- package/tests/ai-status.test.tsx +0 -72
- package/tests/ai-structured.test.ts +0 -94
- package/tests/autocomplete/AutocompletePlugin.test.ts +0 -20
- package/tests/autocomplete/suggestions.test.ts +0 -106
- package/tests/document-context.test.tsx +0 -565
- package/tests/docx-importer.test.ts +0 -160
- package/tests/editor/character-decoration-plugin.test.ts +0 -101
- package/tests/editor/character-extension.test.ts +0 -44
- package/tests/editor/grapheme-cursor.test.ts +0 -118
- package/tests/editor/ime-plugin.test.ts +0 -72
- package/tests/editor/inline-note-bridge.test.ts +0 -32
- package/tests/editor/language-class-plugin.test.ts +0 -265
- package/tests/editor/title-page.test.ts +0 -124
- package/tests/embed-fonts.test.ts +0 -17
- package/tests/exporters.test.ts +0 -365
- package/tests/findreplace/FindReplace.test.tsx +0 -391
- package/tests/findreplace/find-engine.test.ts +0 -86
- package/tests/findreplace/scroll-to-match.test.ts +0 -50
- package/tests/fonts/tofu-detect.test.ts +0 -122
- package/tests/i18n/LanguageContext.test.tsx +0 -146
- package/tests/i18n/language-detect.test.ts +0 -54
- package/tests/i18n/locale-parity.test.ts +0 -25
- package/tests/i18n/useTranslation.test.tsx +0 -45
- package/tests/keymap.test.ts +0 -18
- package/tests/navigator/SceneNavigator.test.tsx +0 -177
- package/tests/navigator/StatisticsDialog.test.tsx +0 -63
- package/tests/navigator/scene-list.test.ts +0 -71
- package/tests/print-paginator.test.ts +0 -82
- package/tests/shell/VersionHistory.test.tsx +0 -129
- package/tests/spellcheck/hunspell.test.ts +0 -82
- package/tests/spellcheck/spellcheck-plugin.test.ts +0 -56
- package/tests/styles.test.ts +0 -315
- package/tests/templates/templates.test.ts +0 -83
- package/tests/titlepage/TitlePageDialog.test.tsx +0 -90
- package/tests/titlepage/apply-title-page.test.ts +0 -61
- package/tests/titlepage/frontmatter-form.test.ts +0 -115
- package/tests/toolbar-actions.test.ts +0 -93
- package/tests/voice-commands.test.ts +0 -58
- package/tsconfig.json +0 -17
- package/vitest.config.ts +0 -11
- package/vitest.setup.ts +0 -65
package/src/shell/AppBar.tsx
DELETED
|
@@ -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' }}>&</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 & 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' }}>&</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
|
-
}
|
package/src/shell/AppShell.tsx
DELETED
|
@@ -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 & 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
|
-
}
|