@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,335 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
type IconProps = { size?: number }
|
|
4
|
+
const base = (size = 16) => ({
|
|
5
|
+
width: size,
|
|
6
|
+
height: size,
|
|
7
|
+
viewBox: '0 0 16 16',
|
|
8
|
+
fill: 'none',
|
|
9
|
+
stroke: 'currentColor',
|
|
10
|
+
strokeWidth: 1.35,
|
|
11
|
+
strokeLinecap: 'round' as const,
|
|
12
|
+
strokeLinejoin: 'round' as const,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export const NavigatorIcon = ({ size }: IconProps) => (
|
|
16
|
+
<svg {...base(size)}>
|
|
17
|
+
<rect x="1.5" y="2" width="13" height="12" rx="1.5" />
|
|
18
|
+
<line x1="5.5" y1="2" x2="5.5" y2="14" />
|
|
19
|
+
</svg>
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
export const TitlePageIcon = ({ size }: IconProps) => (
|
|
23
|
+
<svg {...base(size)}>
|
|
24
|
+
<path d="M3 1.5h6l4 4V13.5a1 1 0 01-1 1H3a1 1 0 01-1-1v-11a1 1 0 011-1z" />
|
|
25
|
+
<polyline points="9 1.5 9 5.5 13 5.5" />
|
|
26
|
+
<line x1="5" y1="8" x2="11" y2="8" />
|
|
27
|
+
<line x1="5" y1="10.5" x2="9" y2="10.5" />
|
|
28
|
+
</svg>
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
export const NewIcon = ({ size }: IconProps) => (
|
|
32
|
+
<svg {...base(size)}>
|
|
33
|
+
<path d="M3 1.5h6l4 4V13.5a1 1 0 01-1 1H3a1 1 0 01-1-1v-11a1 1 0 011-1z" />
|
|
34
|
+
<polyline points="9 1.5 9 5.5 13 5.5" />
|
|
35
|
+
<line x1="8" y1="8.5" x2="8" y2="12" />
|
|
36
|
+
<line x1="6.25" y1="10.25" x2="9.75" y2="10.25" />
|
|
37
|
+
</svg>
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
export const OpenIcon = ({ size }: IconProps) => (
|
|
41
|
+
<svg {...base(size)}>
|
|
42
|
+
<path d="M1.5 3.5a1 1 0 011-1h3.5l1.5 1.5h7a1 1 0 011 1V6H1.5V3.5z" />
|
|
43
|
+
<path d="M1 6.5h14l-1.5 6.5a1 1 0 01-1 .7H3.5a1 1 0 01-1-.7L1 6.5z" />
|
|
44
|
+
</svg>
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
export const ImportDocxIcon = ({ size }: IconProps) => (
|
|
48
|
+
<svg {...base(size)}>
|
|
49
|
+
<path d="M3 1.5h6l4 4V13.5a1 1 0 01-1 1H3a1 1 0 01-1-1v-11a1 1 0 011-1z" />
|
|
50
|
+
<polyline points="9 1.5 9 5.5 13 5.5" />
|
|
51
|
+
<line x1="8" y1="7.5" x2="8" y2="11.5" />
|
|
52
|
+
<polyline points="6 9.5 8 11.5 10 9.5" />
|
|
53
|
+
</svg>
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
export const StyleIcon = ({ size }: IconProps) => (
|
|
57
|
+
<svg {...base(size)}>
|
|
58
|
+
<path d="M8 2a6 6 0 00-6 6c0 3.3 2.7 6 6 6 1 0 1.8-.8 1.8-1.8 0-.5-.2-.9-.5-1.2-.3-.4-.5-.9-.5-1.5 0-1.1.9-2 2-2h1.2C13.8 7.5 14 6.7 14 5.8 14 3.7 11.3 2 8 2z" />
|
|
59
|
+
<circle cx="5" cy="6" r="0.75" fill="currentColor" stroke="none" />
|
|
60
|
+
<circle cx="8" cy="4.8" r="0.75" fill="currentColor" stroke="none" />
|
|
61
|
+
<circle cx="11" cy="6" r="0.75" fill="currentColor" stroke="none" />
|
|
62
|
+
</svg>
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
export const SaveIcon = ({ size }: IconProps) => (
|
|
66
|
+
<svg {...base(size)}>
|
|
67
|
+
<path d="M2.5 2h8.5l2.5 2.5V13.5a1 1 0 01-1 1h-10a1 1 0 01-1-1V3a1 1 0 011-1z" />
|
|
68
|
+
<path d="M5 2v4h5V2" />
|
|
69
|
+
<path d="M4.5 9h7v5h-7z" />
|
|
70
|
+
</svg>
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
export const SaveAsIcon = ({ size = 16 }: IconProps) => (
|
|
74
|
+
<svg {...base(size)}>
|
|
75
|
+
<path d="M2.5 2h7.5l2.5 2.5V8" />
|
|
76
|
+
<path d="M5 2v3.5h4V2" />
|
|
77
|
+
<path d="M2.5 13.5a1 1 0 01-1-1V3a1 1 0 011-1" />
|
|
78
|
+
<path d="M1.5 12.5a1 1 0 001 1h5.5" />
|
|
79
|
+
<circle cx="12" cy="12" r="3" />
|
|
80
|
+
<line x1="12" y1="10.5" x2="12" y2="13.5" />
|
|
81
|
+
<line x1="10.5" y1="12" x2="13.5" y2="12" />
|
|
82
|
+
</svg>
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
export const UndoIcon = ({ size }: IconProps) => (
|
|
86
|
+
<svg {...base(size)}>
|
|
87
|
+
<path d="M2 7c0-2.8 2.2-5 5-5 2.5 0 4.6 1.8 5 4.2" />
|
|
88
|
+
<path d="M2 3v4h4" />
|
|
89
|
+
</svg>
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
export const RedoIcon = ({ size }: IconProps) => (
|
|
93
|
+
<svg {...base(size)}>
|
|
94
|
+
<path d="M14 7c0-2.8-2.2-5-5-5C6.5 2 4.4 3.8 4 6.2" />
|
|
95
|
+
<path d="M14 3v4h-4" />
|
|
96
|
+
</svg>
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
export const SceneIcon = ({ size }: IconProps) => (
|
|
100
|
+
<svg {...base(size)} viewBox="0 0 18 18">
|
|
101
|
+
<line x1="3" y1="5" x2="3" y2="13" />
|
|
102
|
+
<line x1="3" y1="9" x2="8" y2="9" />
|
|
103
|
+
<line x1="8" y1="5" x2="8" y2="13" />
|
|
104
|
+
<line x1="12" y1="5" x2="12" y2="13" />
|
|
105
|
+
<line x1="12" y1="9" x2="17" y2="9" />
|
|
106
|
+
<line x1="17" y1="5" x2="17" y2="13" />
|
|
107
|
+
</svg>
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
export const ActionIcon = ({ size }: IconProps) => (
|
|
111
|
+
<svg {...base(size)} viewBox="0 0 18 18">
|
|
112
|
+
<line x1="2" y1="5" x2="16" y2="5" />
|
|
113
|
+
<line x1="2" y1="8" x2="16" y2="8" />
|
|
114
|
+
<line x1="2" y1="11" x2="12" y2="11" />
|
|
115
|
+
<line x1="2" y1="14" x2="9" y2="14" />
|
|
116
|
+
</svg>
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
export const CharacterIcon = ({ size }: IconProps) => (
|
|
120
|
+
<svg {...base(size)} viewBox="0 0 18 18">
|
|
121
|
+
<circle cx="9" cy="6" r="3" />
|
|
122
|
+
<path d="M3 16c0-3.3 2.7-6 6-6s6 2.7 6 6" />
|
|
123
|
+
</svg>
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
export const DialogueIcon = ({ size }: IconProps) => (
|
|
127
|
+
<svg {...base(size)} viewBox="0 0 18 18">
|
|
128
|
+
<path d="M2 4h14a1 1 0 011 1v6a1 1 0 01-1 1H6l-4 3v-3H2a1 1 0 01-1-1V5a1 1 0 011-1z" />
|
|
129
|
+
</svg>
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
export const ParentheticalIcon = ({ size }: IconProps) => (
|
|
133
|
+
<svg {...base(size)} viewBox="0 0 18 18">
|
|
134
|
+
<path d="M6 4c-2 2-2 8 0 10" />
|
|
135
|
+
<path d="M12 4c2 2 2 8 0 10" />
|
|
136
|
+
</svg>
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
export const TransitionIcon = ({ size }: IconProps) => (
|
|
140
|
+
<svg {...base(size)} viewBox="0 0 18 18">
|
|
141
|
+
<polyline points="3,6 8,9 3,12" />
|
|
142
|
+
<polyline points="9,6 14,9 9,12" />
|
|
143
|
+
<line x1="15" y1="6" x2="15" y2="12" />
|
|
144
|
+
</svg>
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
export const CenteredIcon = ({ size }: IconProps) => (
|
|
148
|
+
<svg {...base(size)} viewBox="0 0 18 18">
|
|
149
|
+
<line x1="3" y1="5" x2="15" y2="5" />
|
|
150
|
+
<line x1="5" y1="9" x2="13" y2="9" />
|
|
151
|
+
<line x1="3" y1="13" x2="15" y2="13" />
|
|
152
|
+
</svg>
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
export const LyricsIcon = ({ size }: IconProps) => (
|
|
156
|
+
<svg {...base(size)} viewBox="0 0 18 18">
|
|
157
|
+
<circle cx="5" cy="13" r="2" />
|
|
158
|
+
<circle cx="13" cy="11" r="2" />
|
|
159
|
+
<line x1="7" y1="13" x2="7" y2="4" />
|
|
160
|
+
<line x1="15" y1="11" x2="15" y2="2" />
|
|
161
|
+
<line x1="7" y1="4" x2="15" y2="2" />
|
|
162
|
+
</svg>
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
export const NoteIcon = ({ size }: IconProps) => (
|
|
166
|
+
<svg {...base(size)} viewBox="0 0 18 18">
|
|
167
|
+
<rect x="2" y="3" width="11" height="12" rx="1" />
|
|
168
|
+
<line x1="5" y1="7" x2="10" y2="7" />
|
|
169
|
+
<line x1="5" y1="10" x2="10" y2="10" />
|
|
170
|
+
<line x1="5" y1="13" x2="8" y2="13" />
|
|
171
|
+
</svg>
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
export const PageBreakIcon = ({ size }: IconProps) => (
|
|
175
|
+
<svg {...base(size)} viewBox="0 0 18 18">
|
|
176
|
+
<line x1="2" y1="9" x2="16" y2="9" />
|
|
177
|
+
<line x1="2" y1="5" x2="16" y2="5" strokeDasharray="2 2" opacity="0.4" />
|
|
178
|
+
<line x1="2" y1="13" x2="16" y2="13" strokeDasharray="2 2" opacity="0.4" />
|
|
179
|
+
</svg>
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
export const SectionIcon = ({ size }: IconProps) => (
|
|
183
|
+
<svg {...base(size)} viewBox="0 0 18 18">
|
|
184
|
+
<line x1="3" y1="6" x2="15" y2="6" />
|
|
185
|
+
<line x1="3" y1="9" x2="15" y2="9" />
|
|
186
|
+
<line x1="6" y1="3" x2="6" y2="7" />
|
|
187
|
+
<line x1="12" y1="3" x2="12" y2="7" />
|
|
188
|
+
<line x1="6" y1="8" x2="6" y2="12" />
|
|
189
|
+
<line x1="12" y1="8" x2="12" y2="12" />
|
|
190
|
+
</svg>
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
export const BoldIcon = ({ size = 16 }: IconProps) => (
|
|
194
|
+
<svg width={size} height={size} viewBox="0 0 16 16" fill="currentColor">
|
|
195
|
+
<text x="3" y="13" fontFamily="Georgia,serif" fontSize="13" fontWeight="700">B</text>
|
|
196
|
+
</svg>
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
export const ItalicIcon = ({ size = 16 }: IconProps) => (
|
|
200
|
+
<svg width={size} height={size} viewBox="0 0 16 16" fill="currentColor">
|
|
201
|
+
<text x="5" y="13" fontFamily="Georgia,serif" fontSize="13" fontStyle="italic">I</text>
|
|
202
|
+
</svg>
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
export const UnderlineIcon = ({ size = 16 }: IconProps) => (
|
|
206
|
+
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth={1.3} strokeLinecap="round">
|
|
207
|
+
<text x="4" y="11" fontFamily="Georgia,serif" fontSize="11" fill="currentColor" stroke="none">U</text>
|
|
208
|
+
<line x1="3" y1="14" x2="13" y2="14" />
|
|
209
|
+
</svg>
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
export const SourceIcon = ({ size }: IconProps) => (
|
|
213
|
+
<svg {...base(size)}>
|
|
214
|
+
<polyline points="5,4 2,8 5,12" />
|
|
215
|
+
<polyline points="11,4 14,8 11,12" />
|
|
216
|
+
<line x1="9" y1="2" x2="7" y2="14" />
|
|
217
|
+
</svg>
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
export const FormattedIcon = ({ size }: IconProps) => (
|
|
221
|
+
<svg {...base(size)}>
|
|
222
|
+
<line x1="2" y1="4" x2="14" y2="4" />
|
|
223
|
+
<line x1="2" y1="7" x2="14" y2="7" />
|
|
224
|
+
<line x1="2" y1="10" x2="9" y2="10" />
|
|
225
|
+
</svg>
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
export const RibbonIcon = ({ size }: IconProps) => (
|
|
229
|
+
<svg {...base(size)}>
|
|
230
|
+
<rect x="1" y="4" width="4" height="4" rx="0.5" />
|
|
231
|
+
<rect x="6" y="4" width="4" height="4" rx="0.5" />
|
|
232
|
+
<rect x="11" y="4" width="4" height="4" rx="0.5" />
|
|
233
|
+
<line x1="1" y1="11" x2="15" y2="11" />
|
|
234
|
+
<line x1="1" y1="13.5" x2="10" y2="13.5" />
|
|
235
|
+
</svg>
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
export const ExportIcon = ({ size }: IconProps) => (
|
|
239
|
+
<svg {...base(size)}>
|
|
240
|
+
<path d="M2.5 7.5v5a1 1 0 001 1h9a1 1 0 001-1v-5" />
|
|
241
|
+
<line x1="8" y1="2" x2="8" y2="9.5" />
|
|
242
|
+
<polyline points="4.75 5.25 8 2 11.25 5.25" />
|
|
243
|
+
</svg>
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
export const SettingsIcon = ({ size }: IconProps) => (
|
|
247
|
+
<svg {...base(size)}>
|
|
248
|
+
<circle cx="8" cy="8" r="2.25" />
|
|
249
|
+
<path d="M6.9 1.8h2.2l.3 1.4a4.8 4.8 0 011.2.7l1.3-.6 1.5 1.5-.6 1.3c.3.4.5.8.7 1.2l1.4.3v2.2l-1.4.3a4.8 4.8 0 01-.7 1.2l.6 1.3-1.5 1.5-1.3-.6a4.8 4.8 0 01-1.2.7l-.3 1.4H6.9l-.3-1.4a4.8 4.8 0 01-1.2-.7l-1.3.6-1.5-1.5.6-1.3a4.8 4.8 0 01-.7-1.2l-1.4-.3V6.9l1.4-.3a4.8 4.8 0 01.7-1.2l-.6-1.3 1.5-1.5 1.3.6a4.8 4.8 0 011.2-.7l.3-1.4z" />
|
|
250
|
+
</svg>
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
export const OverflowIcon = ({ size = 14 }: IconProps) => (
|
|
254
|
+
<svg width={size} height={size} viewBox="0 0 16 16" fill="currentColor">
|
|
255
|
+
<circle cx="3" cy="8" r="1.5" />
|
|
256
|
+
<circle cx="8" cy="8" r="1.5" />
|
|
257
|
+
<circle cx="13" cy="8" r="1.5" />
|
|
258
|
+
</svg>
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
export const EyeIcon = ({ size }: IconProps) => (
|
|
262
|
+
<svg {...base(size)}>
|
|
263
|
+
<path d="M1 8s3-6 7-6 7 6 7 6-3 6-7 6-7-6-7-6z" />
|
|
264
|
+
<circle cx="8" cy="8" r="2.2" />
|
|
265
|
+
</svg>
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
export const EyeOffIcon = ({ size }: IconProps) => (
|
|
269
|
+
<svg {...base(size)}>
|
|
270
|
+
<path d="M1 8s3-6 7-6 7 6 7 6-3 6-7 6-7-6-7-6z" />
|
|
271
|
+
<circle cx="8" cy="8" r="2.2" />
|
|
272
|
+
<line x1="2" y1="2" x2="14" y2="14" />
|
|
273
|
+
</svg>
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
export const LockIcon = ({ size }: IconProps) => (
|
|
277
|
+
<svg {...base(size)}>
|
|
278
|
+
<rect x="3" y="7" width="10" height="7" rx="1.5" />
|
|
279
|
+
<path d="M4.5 7V4.5a3.5 3.5 0 017 0V7" />
|
|
280
|
+
</svg>
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
export const StatsIcon = ({ size }: IconProps) => (
|
|
284
|
+
<svg {...base(size)}>
|
|
285
|
+
<line x1="12" y1="14" x2="12" y2="4" />
|
|
286
|
+
<line x1="8" y1="14" x2="8" y2="7" />
|
|
287
|
+
<line x1="4" y1="14" x2="4" y2="10" />
|
|
288
|
+
</svg>
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
export const HistoryIcon = ({ size }: IconProps) => (
|
|
292
|
+
<svg {...base(size)}>
|
|
293
|
+
<circle cx="8" cy="8.5" r="6" />
|
|
294
|
+
<polyline points="8,5.5 8,8.5 10.5,10" />
|
|
295
|
+
</svg>
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
export const MicIcon = ({ size }: IconProps) => (
|
|
299
|
+
<svg {...base(size)}>
|
|
300
|
+
<rect x="5.5" y="2" width="5" height="8" rx="2.5" />
|
|
301
|
+
<path d="M3 7.5a5 5 0 0010 0" />
|
|
302
|
+
<line x1="8" y1="12.5" x2="8" y2="14.5" />
|
|
303
|
+
<line x1="5.5" y1="14.5" x2="10.5" y2="14.5" />
|
|
304
|
+
</svg>
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
export const SparklesIcon = ({ size }: IconProps) => (
|
|
308
|
+
<svg {...base(size)}>
|
|
309
|
+
<path d="M8 2l1.2 3.8L13 7l-3.8 1.2L8 12l-1.2-3.8L3 7l3.8-1.2L8 2z" />
|
|
310
|
+
<path d="M12.5 11l.6 1.9 1.9.6-1.9.6-.6 1.9-.6-1.9-1.9-.6 1.9-.6.6-1.9z" />
|
|
311
|
+
</svg>
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
export const ClockIcon = ({ size }: IconProps) => (
|
|
315
|
+
<svg {...base(size)}>
|
|
316
|
+
<circle cx="8" cy="8" r="6" />
|
|
317
|
+
<polyline points="8 4.5 8 8 10.5 9.5" />
|
|
318
|
+
</svg>
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
export const MenuIcon = ({ size }: IconProps) => (
|
|
322
|
+
<svg {...base(size)}>
|
|
323
|
+
<line x1="2.5" y1="4.5" x2="13.5" y2="4.5" />
|
|
324
|
+
<line x1="2.5" y1="8" x2="13.5" y2="8" />
|
|
325
|
+
<line x1="2.5" y1="11.5" x2="13.5" y2="11.5" />
|
|
326
|
+
</svg>
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
export const CloseIcon = ({ size }: IconProps) => (
|
|
330
|
+
<svg {...base(size)}>
|
|
331
|
+
<line x1="3.5" y1="3.5" x2="12.5" y2="12.5" />
|
|
332
|
+
<line x1="12.5" y1="3.5" x2="3.5" y2="12.5" />
|
|
333
|
+
</svg>
|
|
334
|
+
)
|
|
335
|
+
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Command } from 'prosemirror-state'
|
|
2
|
+
import { TextSelection } from 'prosemirror-state'
|
|
3
|
+
import { toggleMark } from 'prosemirror-commands'
|
|
4
|
+
import { undo, redo } from 'prosemirror-history'
|
|
5
|
+
import { schema } from '../editor/schema'
|
|
6
|
+
|
|
7
|
+
export function makeSetBlock(typeName: string, attrs: Record<string, unknown> = {}): Command {
|
|
8
|
+
return (state, dispatch) => {
|
|
9
|
+
const nodeType = schema.nodes[typeName]
|
|
10
|
+
if (!nodeType) return false
|
|
11
|
+
if (dispatch) {
|
|
12
|
+
const { from, to } = state.selection
|
|
13
|
+
const tr = state.tr
|
|
14
|
+
state.doc.nodesBetween(from, to, (node, pos) => {
|
|
15
|
+
if (node.isBlock && node.type.name !== 'doc') {
|
|
16
|
+
tr.setNodeMarkup(pos, nodeType, { ...node.attrs, ...attrs })
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
dispatch(tr)
|
|
20
|
+
}
|
|
21
|
+
return true
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function makeToggleMark(markName: string): Command {
|
|
26
|
+
return (state, dispatch) => {
|
|
27
|
+
const mark = schema.marks[markName]
|
|
28
|
+
if (!mark) return false
|
|
29
|
+
if (state.selection.empty) return false
|
|
30
|
+
return toggleMark(mark)(state, dispatch)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The "Note" toolbar button/shortcut does double duty: on an Action line it
|
|
35
|
+
// marks text as an inline [[ ]] note — the selection, or (with no selection)
|
|
36
|
+
// the whole line — leaving the line itself an Action. Inline notes aren't
|
|
37
|
+
// allowed on any other line type, so elsewhere it falls back to the original
|
|
38
|
+
// whole-block conversion to a standalone `note` block.
|
|
39
|
+
export function makeSetOrToggleNote(): Command {
|
|
40
|
+
return (state, dispatch) => {
|
|
41
|
+
const { $from, $to, empty } = state.selection
|
|
42
|
+
const inAction = $from.parent.type.name === 'action' && (empty || $from.sameParent($to))
|
|
43
|
+
if (!inAction) return makeSetBlock('note')(state, dispatch)
|
|
44
|
+
|
|
45
|
+
const mark = schema.marks['note_mark']
|
|
46
|
+
if (!mark) return false
|
|
47
|
+
|
|
48
|
+
let target = state
|
|
49
|
+
if (empty) {
|
|
50
|
+
const lineStart = $from.start()
|
|
51
|
+
const lineEnd = $from.end()
|
|
52
|
+
if (lineEnd <= lineStart) return false
|
|
53
|
+
target = state.apply(state.tr.setSelection(TextSelection.create(state.doc, lineStart, lineEnd)))
|
|
54
|
+
}
|
|
55
|
+
return toggleMark(mark)(target, dispatch)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { undo, redo }
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
import { EditorState } from '@codemirror/state'
|
|
3
|
+
import { EditorView, keymap } from '@codemirror/view'
|
|
4
|
+
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'
|
|
5
|
+
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language'
|
|
6
|
+
import { tags } from '@lezer/highlight'
|
|
7
|
+
import { sutraLanguage } from './sutra-lang'
|
|
8
|
+
import { useDocument } from '../context/DocumentContext'
|
|
9
|
+
import { setSourceView } from '../editor/editor-bus'
|
|
10
|
+
import { sourceFindHighlightExtension } from './find-highlight-source'
|
|
11
|
+
|
|
12
|
+
// Light theme matching the app's warm off-white palette.
|
|
13
|
+
// No internal scroll — the editor grows to content height and .cs-main scrolls.
|
|
14
|
+
const sutraTheme = EditorView.theme({
|
|
15
|
+
'&': {
|
|
16
|
+
width: '100%',
|
|
17
|
+
maxWidth: '960px',
|
|
18
|
+
fontSize: '12pt',
|
|
19
|
+
fontFamily: "'Courier New', Courier, monospace",
|
|
20
|
+
lineHeight: '1.5',
|
|
21
|
+
backgroundColor: 'transparent',
|
|
22
|
+
},
|
|
23
|
+
'.cm-scroller': { overflow: 'visible', lineHeight: '1.5' },
|
|
24
|
+
'.cm-content': { padding: '0', caretColor: '#1a1a18' },
|
|
25
|
+
'.cm-line': { padding: '0' },
|
|
26
|
+
'&.cm-focused': { outline: 'none' },
|
|
27
|
+
'.cm-cursor': { borderLeftColor: '#1a1a18' },
|
|
28
|
+
'.cm-selectionBackground, ::selection': { backgroundColor: '#b8d3f0' },
|
|
29
|
+
'&.cm-focused .cm-selectionBackground': { backgroundColor: '#b8d3f0' },
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const sutraHighlight = HighlightStyle.define([
|
|
33
|
+
// ## Scene heading — dark teal, bold
|
|
34
|
+
{ tag: tags.keyword, color: '#0a5c6a', fontWeight: 'bold' },
|
|
35
|
+
// # Section heading — medium slate
|
|
36
|
+
{ tag: tags.heading, color: '#4a5568', fontWeight: 'bold' },
|
|
37
|
+
// @Character — warm brown/amber
|
|
38
|
+
{ tag: tags.variableName, color: '#9c4221', fontWeight: 'bold' },
|
|
39
|
+
// Dialogue — dark ink, slightly indented feel via color
|
|
40
|
+
{ tag: tags.labelName, color: '#2a3568' },
|
|
41
|
+
// Parenthetical — muted gray-green
|
|
42
|
+
{ tag: tags.string, color: '#5a7a4a', fontStyle: 'italic' },
|
|
43
|
+
// >> Transition / centered — medium blue-gray
|
|
44
|
+
{ tag: tags.operator, color: '#4a5a7a', fontWeight: 'bold' },
|
|
45
|
+
// ~ Lyrics — purple
|
|
46
|
+
{ tag: tags.meta, color: '#6b46c1' },
|
|
47
|
+
// [[ Note ]] / <!-- comment --> — gray
|
|
48
|
+
{ tag: tags.comment, color: '#9ca3af', fontStyle: 'italic' },
|
|
49
|
+
// & metadata key — teal
|
|
50
|
+
{ tag: tags.attributeName, color: '#0694a2' },
|
|
51
|
+
// Frontmatter values — muted
|
|
52
|
+
{ tag: tags.attributeValue, color: '#6b7280' },
|
|
53
|
+
// === page break, sigils — light gray
|
|
54
|
+
{ tag: tags.punctuation, color: '#9ca3af' },
|
|
55
|
+
])
|
|
56
|
+
|
|
57
|
+
export function SourceView() {
|
|
58
|
+
const { text, setText } = useDocument()
|
|
59
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
60
|
+
const viewRef = useRef<EditorView | null>(null)
|
|
61
|
+
// Track externally-driven text so we don't feed our own updates back
|
|
62
|
+
const lastTextRef = useRef(text)
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (!containerRef.current) return
|
|
66
|
+
|
|
67
|
+
const view = new EditorView({
|
|
68
|
+
state: EditorState.create({
|
|
69
|
+
doc: text,
|
|
70
|
+
extensions: [
|
|
71
|
+
history(),
|
|
72
|
+
keymap.of([...defaultKeymap, ...historyKeymap]),
|
|
73
|
+
sutraLanguage,
|
|
74
|
+
sutraTheme,
|
|
75
|
+
syntaxHighlighting(sutraHighlight),
|
|
76
|
+
sourceFindHighlightExtension,
|
|
77
|
+
EditorView.lineWrapping,
|
|
78
|
+
EditorView.updateListener.of(update => {
|
|
79
|
+
if (update.docChanged) {
|
|
80
|
+
const newText = update.state.doc.toString()
|
|
81
|
+
lastTextRef.current = newText
|
|
82
|
+
setText(newText)
|
|
83
|
+
}
|
|
84
|
+
}),
|
|
85
|
+
],
|
|
86
|
+
}),
|
|
87
|
+
parent: containerRef.current,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
viewRef.current = view
|
|
91
|
+
setSourceView(view)
|
|
92
|
+
return () => {
|
|
93
|
+
setSourceView(null)
|
|
94
|
+
view.destroy()
|
|
95
|
+
viewRef.current = null
|
|
96
|
+
}
|
|
97
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
98
|
+
}, [])
|
|
99
|
+
|
|
100
|
+
// Sync external text changes (e.g. switching back from formatted view)
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
const view = viewRef.current
|
|
103
|
+
if (!view || text === lastTextRef.current) return
|
|
104
|
+
lastTextRef.current = text
|
|
105
|
+
view.dispatch({
|
|
106
|
+
changes: { from: 0, to: view.state.doc.length, insert: text },
|
|
107
|
+
})
|
|
108
|
+
}, [text])
|
|
109
|
+
|
|
110
|
+
return <div ref={containerRef} className="cs-source-view" />
|
|
111
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { StateEffect, StateField } from '@codemirror/state'
|
|
2
|
+
import { Decoration, EditorView } from '@codemirror/view'
|
|
3
|
+
import type { DecorationSet } from '@codemirror/view'
|
|
4
|
+
import { getSourceView } from '../editor/editor-bus'
|
|
5
|
+
|
|
6
|
+
export interface SourceMatch {
|
|
7
|
+
start: number
|
|
8
|
+
end: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const setMatchesEffect = StateEffect.define<{ matches: SourceMatch[]; activeIndex: number }>()
|
|
12
|
+
|
|
13
|
+
function buildDecorations(matches: SourceMatch[], activeIndex: number, docLength: number): DecorationSet {
|
|
14
|
+
if (matches.length === 0) return Decoration.none
|
|
15
|
+
const decos = matches
|
|
16
|
+
.filter(m => m.start >= 0 && m.end <= docLength && m.start < m.end)
|
|
17
|
+
.map((m, i) => Decoration.mark({ class: i === activeIndex ? 'cs-find-active' : 'cs-find-match' }).range(m.start, m.end))
|
|
18
|
+
return Decoration.set(decos, true)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const findHighlightField = StateField.define<DecorationSet>({
|
|
22
|
+
create() {
|
|
23
|
+
return Decoration.none
|
|
24
|
+
},
|
|
25
|
+
update(deco, tr) {
|
|
26
|
+
deco = deco.map(tr.changes)
|
|
27
|
+
for (const effect of tr.effects) {
|
|
28
|
+
if (effect.is(setMatchesEffect)) {
|
|
29
|
+
deco = buildDecorations(effect.value.matches, effect.value.activeIndex, tr.state.doc.length)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return deco
|
|
33
|
+
},
|
|
34
|
+
provide: field => EditorView.decorations.from(field),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
/** CodeMirror extension providing find-match highlighting in source view. */
|
|
38
|
+
export const sourceFindHighlightExtension = findHighlightField
|
|
39
|
+
|
|
40
|
+
/** Called by FindReplace whenever matches or active index change in source mode. */
|
|
41
|
+
export function setSourceFindHighlights(matches: SourceMatch[], activeIndex: number): void {
|
|
42
|
+
const view = getSourceView()
|
|
43
|
+
if (!view) return
|
|
44
|
+
view.dispatch({ effects: setMatchesEffect.of({ matches, activeIndex }) })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Called by FindReplace when the panel closes — clears all source-view highlights. */
|
|
48
|
+
export function clearSourceFindHighlights(): void {
|
|
49
|
+
setSourceFindHighlights([], 0)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Scroll the source view so the given match is visible. */
|
|
53
|
+
export function scrollToSourceMatch(match: SourceMatch): void {
|
|
54
|
+
const view = getSourceView()
|
|
55
|
+
if (!view) return
|
|
56
|
+
view.dispatch({ effects: EditorView.scrollIntoView(match.start, { y: 'center' }) })
|
|
57
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { StreamLanguage, LanguageSupport } from '@codemirror/language'
|
|
2
|
+
|
|
3
|
+
type State = {
|
|
4
|
+
inFrontmatter: boolean
|
|
5
|
+
lineNum: number
|
|
6
|
+
afterCharacter: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const sutra = StreamLanguage.define<State>({
|
|
10
|
+
name: 'sutra',
|
|
11
|
+
|
|
12
|
+
startState: () => ({ inFrontmatter: false, lineNum: 0, afterCharacter: false }),
|
|
13
|
+
|
|
14
|
+
token(stream, state) {
|
|
15
|
+
state.lineNum++
|
|
16
|
+
|
|
17
|
+
// Blank line — reset dialogue context, emit nothing
|
|
18
|
+
if (stream.match(/^\s*$/)) {
|
|
19
|
+
state.afterCharacter = false
|
|
20
|
+
return null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Frontmatter delimiter ---
|
|
24
|
+
if (state.lineNum === 1 && stream.match(/^---\s*$/)) {
|
|
25
|
+
state.inFrontmatter = true
|
|
26
|
+
return 'keyword'
|
|
27
|
+
}
|
|
28
|
+
if (state.inFrontmatter) {
|
|
29
|
+
if (stream.match(/^---\s*$/)) {
|
|
30
|
+
state.inFrontmatter = false
|
|
31
|
+
return 'keyword'
|
|
32
|
+
}
|
|
33
|
+
// key: value lines inside frontmatter
|
|
34
|
+
if (stream.match(/^[A-Za-z][A-Za-z ]*:/)) return 'attributeName'
|
|
35
|
+
stream.skipToEnd()
|
|
36
|
+
return 'string'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Page break ===
|
|
40
|
+
if (stream.match(/^===\s*$/)) {
|
|
41
|
+
state.afterCharacter = false
|
|
42
|
+
return 'punctuation'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Comment <!-- ... -->
|
|
46
|
+
if (stream.match(/^<!--[\s\S]*?-->\s*$/)) {
|
|
47
|
+
state.afterCharacter = false
|
|
48
|
+
return 'comment'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Note [[ ... ]]
|
|
52
|
+
if (stream.match(/^\[\[[\s\S]*?\]\]\s*$/)) {
|
|
53
|
+
state.afterCharacter = false
|
|
54
|
+
return 'comment'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Scene heading ## ...
|
|
58
|
+
if (stream.match(/^##/)) {
|
|
59
|
+
stream.skipToEnd()
|
|
60
|
+
state.afterCharacter = false
|
|
61
|
+
return 'keyword'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Section heading # ...
|
|
65
|
+
if (stream.match(/^#(?!#)/)) {
|
|
66
|
+
stream.skipToEnd()
|
|
67
|
+
state.afterCharacter = false
|
|
68
|
+
return 'heading'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Scene metadata & key: value
|
|
72
|
+
if (stream.match(/^&\s/)) {
|
|
73
|
+
stream.skipToEnd()
|
|
74
|
+
state.afterCharacter = false
|
|
75
|
+
return 'attributeName'
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Character cue @name ...
|
|
79
|
+
if (stream.match(/^@/)) {
|
|
80
|
+
stream.skipToEnd()
|
|
81
|
+
state.afterCharacter = true
|
|
82
|
+
return 'variableName'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Centered or transition >> ...
|
|
86
|
+
if (stream.match(/^>>/)) {
|
|
87
|
+
stream.skipToEnd()
|
|
88
|
+
state.afterCharacter = false
|
|
89
|
+
return 'operator'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Lyrics ~ ...
|
|
93
|
+
if (stream.match(/^~\s/)) {
|
|
94
|
+
stream.skipToEnd()
|
|
95
|
+
state.afterCharacter = false
|
|
96
|
+
return 'meta'
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Parenthetical (inside dialogue context)
|
|
100
|
+
if (state.afterCharacter && stream.match(/^\(/)) {
|
|
101
|
+
stream.skipToEnd()
|
|
102
|
+
return 'string'
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Dialogue (inside dialogue context) — styled differently from action
|
|
106
|
+
if (state.afterCharacter) {
|
|
107
|
+
stream.skipToEnd()
|
|
108
|
+
return 'labelName'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Action — unstyled
|
|
112
|
+
stream.skipToEnd()
|
|
113
|
+
return null
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
blankLine(state) {
|
|
117
|
+
state.afterCharacter = false
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
export const sutraLanguage = new LanguageSupport(sutra)
|