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