@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,265 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { EditorState } from 'prosemirror-state'
|
|
3
|
+
import { schema } from '../../src/editor/schema'
|
|
4
|
+
import { createLanguageClassPlugin } from '../../src/editor/language-class-plugin'
|
|
5
|
+
|
|
6
|
+
describe('language-class-plugin', () => {
|
|
7
|
+
function createStateWithPlugin(docContent: any) {
|
|
8
|
+
const plugin = createLanguageClassPlugin()
|
|
9
|
+
const state = EditorState.create({
|
|
10
|
+
doc: schema.node('doc', null, [schema.nodeFromJSON(docContent)]),
|
|
11
|
+
plugins: [plugin],
|
|
12
|
+
})
|
|
13
|
+
return { state, plugin }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function collectDecorations(decorationSet: any): any[] {
|
|
17
|
+
// DecorationSet.find() returns an array of Decoration objects
|
|
18
|
+
return decorationSet.find()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
it('applies .cs-lang-en to English text block', () => {
|
|
22
|
+
const { state, plugin } = createStateWithPlugin({
|
|
23
|
+
type: 'action',
|
|
24
|
+
content: [{ type: 'text', text: 'Hello world' }],
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const decorationSet = plugin.getState(state)
|
|
28
|
+
expect(decorationSet).toBeDefined()
|
|
29
|
+
|
|
30
|
+
const decorations = collectDecorations(decorationSet)
|
|
31
|
+
expect(decorations.length).toBeGreaterThan(0)
|
|
32
|
+
|
|
33
|
+
const hasEnglishClass = decorations.some((d: any) =>
|
|
34
|
+
d.spec?.class?.includes('cs-lang-en')
|
|
35
|
+
)
|
|
36
|
+
expect(hasEnglishClass).toBe(true)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('applies .cs-lang-ta to Tamil text block', () => {
|
|
40
|
+
const { state, plugin } = createStateWithPlugin({
|
|
41
|
+
type: 'dialogue',
|
|
42
|
+
content: [{ type: 'text', text: 'இது ஒரு தமிழ் வாக்கியம்' }],
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const decorationSet = plugin.getState(state)
|
|
46
|
+
const decorations = collectDecorations(decorationSet)
|
|
47
|
+
expect(decorations.length).toBeGreaterThan(0)
|
|
48
|
+
|
|
49
|
+
// Check that a decoration with Tamil class exists
|
|
50
|
+
const hasTamilClass = decorations.some((d: any) =>
|
|
51
|
+
d.spec?.class?.includes('cs-lang-ta')
|
|
52
|
+
)
|
|
53
|
+
expect(hasTamilClass).toBe(true)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('applies .cs-lang-hi to Hindi/Devanagari text block', () => {
|
|
57
|
+
const { state, plugin } = createStateWithPlugin({
|
|
58
|
+
type: 'character',
|
|
59
|
+
content: [{ type: 'text', text: 'यह एक हिंदी वाक्य है' }],
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const decorationSet = plugin.getState(state)
|
|
63
|
+
const decorations = collectDecorations(decorationSet)
|
|
64
|
+
|
|
65
|
+
const hasHindiClass = decorations.some((d: any) =>
|
|
66
|
+
d.spec?.class?.includes('cs-lang-hi')
|
|
67
|
+
)
|
|
68
|
+
expect(hasHindiClass).toBe(true)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('respects inline {lang=…} tag override', () => {
|
|
72
|
+
const { state, plugin } = createStateWithPlugin({
|
|
73
|
+
type: 'parenthetical',
|
|
74
|
+
content: [{ type: 'text', text: 'English text {lang=ml}' }],
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const decorationSet = plugin.getState(state)
|
|
78
|
+
const decorations = collectDecorations(decorationSet)
|
|
79
|
+
|
|
80
|
+
const hasMalayalamClass = decorations.some((d: any) =>
|
|
81
|
+
d.spec?.class?.includes('cs-lang-ml')
|
|
82
|
+
)
|
|
83
|
+
expect(hasMalayalamClass).toBe(true)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('updates classes on doc change', () => {
|
|
87
|
+
const { state: initialState, plugin } = createStateWithPlugin({
|
|
88
|
+
type: 'action',
|
|
89
|
+
content: [{ type: 'text', text: 'Hello world' }],
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
let state = initialState
|
|
93
|
+
let decorationSet = plugin.getState(state)
|
|
94
|
+
let decorations = collectDecorations(decorationSet)
|
|
95
|
+
|
|
96
|
+
const englishCount = decorations.filter((d: any) =>
|
|
97
|
+
d.spec?.class?.includes('cs-lang-en')
|
|
98
|
+
).length
|
|
99
|
+
expect(englishCount).toBeGreaterThan(0)
|
|
100
|
+
|
|
101
|
+
// Update the doc - select all text in the block and replace with Tamil
|
|
102
|
+
// The block content starts at position 1 (after the opening tag)
|
|
103
|
+
// Find the end of the text node
|
|
104
|
+
const selection = state.selection
|
|
105
|
+
const from = 1 // start of content in the block
|
|
106
|
+
const to = state.doc.content.content[0].nodeSize - 1 // end of action node minus closing
|
|
107
|
+
|
|
108
|
+
const tr = state.tr.deleteRange(from, to).insertText('இது ஒரு தமிழ் வாக்கியம்', from)
|
|
109
|
+
const newState = state.apply(tr)
|
|
110
|
+
decorationSet = plugin.getState(newState)
|
|
111
|
+
decorations = collectDecorations(decorationSet)
|
|
112
|
+
|
|
113
|
+
// Should now have Tamil decoration
|
|
114
|
+
const hasTamilClass = decorations.some((d: any) =>
|
|
115
|
+
d.spec?.class?.includes('cs-lang-ta')
|
|
116
|
+
)
|
|
117
|
+
expect(hasTamilClass).toBe(true)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('defaults to .cs-lang-en for empty blocks', () => {
|
|
121
|
+
const { state, plugin } = createStateWithPlugin({
|
|
122
|
+
type: 'dialogue',
|
|
123
|
+
content: [],
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const decorationSet = plugin.getState(state)
|
|
127
|
+
const decorations = collectDecorations(decorationSet)
|
|
128
|
+
|
|
129
|
+
const hasEnglishClass = decorations.some((d: any) =>
|
|
130
|
+
d.spec?.class?.includes('cs-lang-en')
|
|
131
|
+
)
|
|
132
|
+
expect(hasEnglishClass).toBe(true)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('handles multiple blocks with different languages', () => {
|
|
136
|
+
const plugin = createLanguageClassPlugin()
|
|
137
|
+
const state = EditorState.create({
|
|
138
|
+
doc: schema.node('doc', null, [
|
|
139
|
+
schema.node('action', null, [schema.text('Hello world')]),
|
|
140
|
+
schema.node('dialogue', null, [schema.text('இது ஒரு தமிழ் வாக்கியம்')]),
|
|
141
|
+
schema.node('character', null, [schema.text('यह एक हिंदी वाक्य है')]),
|
|
142
|
+
]),
|
|
143
|
+
plugins: [plugin],
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const decorationSet = plugin.getState(state)
|
|
147
|
+
const decorations = collectDecorations(decorationSet)
|
|
148
|
+
|
|
149
|
+
const englishCount = decorations.filter((d: any) =>
|
|
150
|
+
d.spec?.class?.includes('cs-lang-en')
|
|
151
|
+
).length
|
|
152
|
+
const tamilCount = decorations.filter((d: any) =>
|
|
153
|
+
d.spec?.class?.includes('cs-lang-ta')
|
|
154
|
+
).length
|
|
155
|
+
const hindiCount = decorations.filter((d: any) =>
|
|
156
|
+
d.spec?.class?.includes('cs-lang-hi')
|
|
157
|
+
).length
|
|
158
|
+
|
|
159
|
+
expect(englishCount).toBe(1)
|
|
160
|
+
expect(tamilCount).toBe(1)
|
|
161
|
+
expect(hindiCount).toBe(1)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('class persists across selection changes', () => {
|
|
165
|
+
const { state: initialState, plugin } = createStateWithPlugin({
|
|
166
|
+
type: 'dialogue',
|
|
167
|
+
content: [{ type: 'text', text: 'இது ஒரு தமிழ் வாக்கியம்' }],
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
const decorationSet1 = plugin.getState(initialState)
|
|
171
|
+
const decorations1 = collectDecorations(decorationSet1)
|
|
172
|
+
|
|
173
|
+
// Change selection without changing doc
|
|
174
|
+
const tr = initialState.tr.setSelection(initialState.selection)
|
|
175
|
+
const newState = initialState.apply(tr)
|
|
176
|
+
const decorationSet2 = plugin.getState(newState)
|
|
177
|
+
const decorations2 = collectDecorations(decorationSet2)
|
|
178
|
+
|
|
179
|
+
// Decorations should remain the same
|
|
180
|
+
const class1 = decorations1[0]?.spec?.class
|
|
181
|
+
const class2 = decorations2[0]?.spec?.class
|
|
182
|
+
|
|
183
|
+
expect(class1).toBe(class2)
|
|
184
|
+
expect(class1).toContain('cs-lang-ta')
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('applies Telugu language class correctly', () => {
|
|
188
|
+
const { state, plugin } = createStateWithPlugin({
|
|
189
|
+
type: 'parenthetical',
|
|
190
|
+
content: [{ type: 'text', text: 'ఇది ఒక నమూనా లైన్' }],
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
const decorationSet = plugin.getState(state)
|
|
194
|
+
const decorations = collectDecorations(decorationSet)
|
|
195
|
+
|
|
196
|
+
const hasTeluguClass = decorations.some((d: any) =>
|
|
197
|
+
d.spec?.class?.includes('cs-lang-te')
|
|
198
|
+
)
|
|
199
|
+
expect(hasTeluguClass).toBe(true)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('applies Kannada language class correctly', () => {
|
|
203
|
+
const { state, plugin } = createStateWithPlugin({
|
|
204
|
+
type: 'centered',
|
|
205
|
+
content: [{ type: 'text', text: 'ಇದು ಒಂದು ಮಾದರಿ ಲೈನ್' }],
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
const decorationSet = plugin.getState(state)
|
|
209
|
+
const decorations = collectDecorations(decorationSet)
|
|
210
|
+
|
|
211
|
+
const hasKannadaClass = decorations.some((d: any) =>
|
|
212
|
+
d.spec?.class?.includes('cs-lang-kn')
|
|
213
|
+
)
|
|
214
|
+
expect(hasKannadaClass).toBe(true)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('applies Malayalam language class correctly', () => {
|
|
218
|
+
const { state, plugin } = createStateWithPlugin({
|
|
219
|
+
type: 'lyrics',
|
|
220
|
+
content: [{ type: 'text', text: 'ഇത് ഒരു സാമ്പിൾ ലൈൻ ആണ്' }],
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
const decorationSet = plugin.getState(state)
|
|
224
|
+
const decorations = collectDecorations(decorationSet)
|
|
225
|
+
|
|
226
|
+
const hasMalayalamClass = decorations.some((d: any) =>
|
|
227
|
+
d.spec?.class?.includes('cs-lang-ml')
|
|
228
|
+
)
|
|
229
|
+
expect(hasMalayalamClass).toBe(true)
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('does not apply classes to non-block nodes', () => {
|
|
233
|
+
const plugin = createLanguageClassPlugin()
|
|
234
|
+
const state = EditorState.create({
|
|
235
|
+
doc: schema.node('doc', null, [
|
|
236
|
+
schema.node('action', null, [schema.text('Some text')]),
|
|
237
|
+
]),
|
|
238
|
+
plugins: [plugin],
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
const decorationSet = plugin.getState(state)
|
|
242
|
+
const decorations = collectDecorations(decorationSet)
|
|
243
|
+
|
|
244
|
+
// Should have exactly 1 decoration (for the action block, not for inline text nodes)
|
|
245
|
+
expect(decorations.length).toBe(1)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('skips decorating the doc node itself', () => {
|
|
249
|
+
const { state, plugin } = createStateWithPlugin({
|
|
250
|
+
type: 'action',
|
|
251
|
+
content: [{ type: 'text', text: 'Hello' }],
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
const decorationSet = plugin.getState(state)
|
|
255
|
+
const decorations = collectDecorations(decorationSet)
|
|
256
|
+
|
|
257
|
+
// Should not have a decoration for the doc node (offset 0)
|
|
258
|
+
const hasDocDecoration = decorations.some((d: any) =>
|
|
259
|
+
d.from === 0 && d.to === state.doc.nodeSize
|
|
260
|
+
)
|
|
261
|
+
// The decoration should be for the action block, not the entire doc
|
|
262
|
+
expect(decorations.length).toBeGreaterThan(0)
|
|
263
|
+
expect(hasDocDecoration).toBe(false)
|
|
264
|
+
})
|
|
265
|
+
})
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest'
|
|
2
|
+
import { EditorState, TextSelection } from 'prosemirror-state'
|
|
3
|
+
import { EditorView } from 'prosemirror-view'
|
|
4
|
+
import { parse } from '@sutrata/parser'
|
|
5
|
+
import { schema } from '../../src/editor/schema'
|
|
6
|
+
import { sutraToProsemirror } from '../../src/editor/sutra-to-prosemirror'
|
|
7
|
+
import { prosemirrorToSutra } from '../../src/editor/prosemirror-to-sutra'
|
|
8
|
+
import { keymapBindings } from '../../src/editor/keymap'
|
|
9
|
+
import { createAttrPencilPlugin } from '../../src/editor/attr-pencil-plugin'
|
|
10
|
+
|
|
11
|
+
const SAMPLE = `---
|
|
12
|
+
title:
|
|
13
|
+
en: The Long Rain
|
|
14
|
+
hi: लंबी बारिश
|
|
15
|
+
author: Sivaraj
|
|
16
|
+
custom_key: kept
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## INT. KITCHEN - NIGHT
|
|
20
|
+
|
|
21
|
+
Action line.
|
|
22
|
+
`
|
|
23
|
+
|
|
24
|
+
describe('title_page bridge (frontmatter in the editor)', () => {
|
|
25
|
+
it('renders frontmatter as a title_page node with dotted keys for nested maps', () => {
|
|
26
|
+
const doc = sutraToProsemirror(parse(SAMPLE))
|
|
27
|
+
const first = doc.firstChild!
|
|
28
|
+
expect(first.type.name).toBe('title_page')
|
|
29
|
+
const keys: string[] = []
|
|
30
|
+
first.forEach(f => keys.push(f.attrs['fmKey'] as string))
|
|
31
|
+
expect(keys).toEqual(['title.en', 'title.hi', 'author', 'custom_key'])
|
|
32
|
+
expect(first.child(0).textContent).toBe('The Long Rain')
|
|
33
|
+
expect(first.child(1).textContent).toBe('लंबी बारिश')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('round-trips frontmatter through the editor bridge (fixes WYSIWYG frontmatter loss)', () => {
|
|
37
|
+
const doc = sutraToProsemirror(parse(SAMPLE))
|
|
38
|
+
const out = prosemirrorToSutra(doc)
|
|
39
|
+
const reparsed = parse(out)
|
|
40
|
+
expect(reparsed.frontmatter).not.toBeNull()
|
|
41
|
+
expect(reparsed.frontmatter!.data).toEqual({
|
|
42
|
+
title: { en: 'The Long Rain', hi: 'लंबी बारिश' },
|
|
43
|
+
author: 'Sivaraj',
|
|
44
|
+
custom_key: 'kept',
|
|
45
|
+
})
|
|
46
|
+
expect(out).toContain('## INT. KITCHEN - NIGHT')
|
|
47
|
+
expect(out).toContain('Action line.')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('does not invent a frontmatter block when none exists', () => {
|
|
51
|
+
const doc = sutraToProsemirror(parse('## Scene 1\n\nAction.\n'))
|
|
52
|
+
expect(doc.firstChild!.type.name).toBe('scene_heading')
|
|
53
|
+
expect(prosemirrorToSutra(doc)).not.toContain('---')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('Enter inside a frontmatter field moves to the next field without splitting', () => {
|
|
57
|
+
const doc = sutraToProsemirror(parse(SAMPLE))
|
|
58
|
+
let state = EditorState.create({ doc, schema })
|
|
59
|
+
// Position 2 = inside the first field's content
|
|
60
|
+
state = state.apply(state.tr.setSelection(TextSelection.create(state.doc, 2)))
|
|
61
|
+
expect(state.selection.$from.parent.attrs['fmKey']).toBe('title.en')
|
|
62
|
+
|
|
63
|
+
let after: EditorState | null = null
|
|
64
|
+
const handled = keymapBindings['Enter']!(state, tr => { after = state.apply(tr) })
|
|
65
|
+
expect(handled).toBe(true)
|
|
66
|
+
expect(after!.doc.eq(state.doc)).toBe(true) // no split, no new node
|
|
67
|
+
expect(after!.selection.$from.parent.type.name).toBe('frontmatter_field')
|
|
68
|
+
expect(after!.selection.$from.parent.attrs['fmKey']).toBe('title.hi')
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
describe('attr pencil plugin', () => {
|
|
73
|
+
let view: EditorView | null = null
|
|
74
|
+
|
|
75
|
+
afterEach(() => {
|
|
76
|
+
view?.destroy()
|
|
77
|
+
view = null
|
|
78
|
+
document.querySelector('.cs-attr-menu')?.remove()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
function mount(text: string): EditorView {
|
|
82
|
+
const doc = sutraToProsemirror(parse(text))
|
|
83
|
+
const state = EditorState.create({ doc, schema, plugins: [createAttrPencilPlugin()] })
|
|
84
|
+
view = new EditorView(document.createElement('div'), { state })
|
|
85
|
+
return view
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
it('renders a pencil widget on scene headings and the title page card', () => {
|
|
89
|
+
const v = mount(SAMPLE)
|
|
90
|
+
const pencils = v.dom.querySelectorAll('.cs-attr-pencil')
|
|
91
|
+
expect(pencils.length).toBe(2)
|
|
92
|
+
expect(v.dom.querySelectorAll('.cs-tp-pencil').length).toBe(1)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('scene pencil menu inserts a metadata line with the picked key', () => {
|
|
96
|
+
const v = mount(SAMPLE)
|
|
97
|
+
const pencil = [...v.dom.querySelectorAll('.cs-attr-pencil')]
|
|
98
|
+
.find(p => !p.classList.contains('cs-tp-pencil'))!
|
|
99
|
+
pencil.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))
|
|
100
|
+
|
|
101
|
+
const menu = document.querySelector('.cs-attr-menu')
|
|
102
|
+
expect(menu).not.toBeNull()
|
|
103
|
+
const statusItem = [...menu!.querySelectorAll('.cs-attr-menu-item')]
|
|
104
|
+
.find(i => i.textContent === '+ status')!
|
|
105
|
+
statusItem.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))
|
|
106
|
+
|
|
107
|
+
const out = prosemirrorToSutra(v.state.doc)
|
|
108
|
+
expect(out).toContain('## INT. KITCHEN - NIGHT\n& status:')
|
|
109
|
+
// Cursor placed inside the new (empty) metadata node
|
|
110
|
+
expect(v.state.selection.$from.parent.type.name).toBe('scene_metadata')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('title page pencil opens the title page form instead of an add-field menu', () => {
|
|
114
|
+
const v = mount(SAMPLE)
|
|
115
|
+
const pencil = v.dom.querySelector('.cs-tp-pencil')!
|
|
116
|
+
let opened = false
|
|
117
|
+
window.addEventListener('cs:open-title-page', () => { opened = true })
|
|
118
|
+
|
|
119
|
+
pencil.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))
|
|
120
|
+
|
|
121
|
+
expect(opened).toBe(true)
|
|
122
|
+
expect(document.querySelector('.cs-attr-menu')).toBeNull()
|
|
123
|
+
})
|
|
124
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { embedFontFacesFor } from '../src/styles/embed-fonts'
|
|
3
|
+
|
|
4
|
+
describe('embed-fonts', () => {
|
|
5
|
+
it('returns an empty string and does not throw when no matching @font-face rules are loaded', async () => {
|
|
6
|
+
// jsdom's test document never loads fonts.css via a real <link>, so this exercises the
|
|
7
|
+
// "nothing found" path — the real embedding behavior is verified end-to-end against an
|
|
8
|
+
// actual browser (see the multi-style implementation notes), since it depends on
|
|
9
|
+
// document.styleSheets/fetch/FileReader, none of which jsdom meaningfully implements.
|
|
10
|
+
const css = await embedFontFacesFor(['Noto Serif', 'Noto Serif Tamil'])
|
|
11
|
+
expect(css).toBe('')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('does not throw for an empty family list', async () => {
|
|
15
|
+
await expect(embedFontFacesFor([])).resolves.toBe('')
|
|
16
|
+
})
|
|
17
|
+
})
|