@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,75 @@
|
|
|
1
|
+
import { Plugin } from 'prosemirror-state';
|
|
2
|
+
import { Decoration, DecorationSet } from 'prosemirror-view';
|
|
3
|
+
import { detectScript } from '../i18n/language-detect';
|
|
4
|
+
/**
|
|
5
|
+
* ProseMirror plugin that applies language-specific CSS classes to block nodes
|
|
6
|
+
* based on detected or specified language (from metadata or inline tags)
|
|
7
|
+
*/
|
|
8
|
+
export function createLanguageClassPlugin() {
|
|
9
|
+
return new Plugin({
|
|
10
|
+
state: {
|
|
11
|
+
init(config, state) {
|
|
12
|
+
// Initialize decorations for the initial document
|
|
13
|
+
return computeDecorations(state.doc);
|
|
14
|
+
},
|
|
15
|
+
apply(tr, set, oldState, newState) {
|
|
16
|
+
// If document changed, recompute decorations
|
|
17
|
+
if (tr.docChanged) {
|
|
18
|
+
return computeDecorations(newState.doc);
|
|
19
|
+
}
|
|
20
|
+
// Otherwise, return the existing decoration set
|
|
21
|
+
return set;
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
props: {
|
|
25
|
+
decorations(state) {
|
|
26
|
+
return this.getState(state);
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Compute language decorations for the entire document
|
|
33
|
+
*/
|
|
34
|
+
function computeDecorations(doc) {
|
|
35
|
+
const decorations = [];
|
|
36
|
+
// Iterate through all block nodes and assign language class
|
|
37
|
+
doc.forEach((node, offset) => {
|
|
38
|
+
if (node.isBlock && node.type.name !== 'doc') {
|
|
39
|
+
const lang = resolveBlockLanguage(node);
|
|
40
|
+
const className = `cs-lang-${lang}`;
|
|
41
|
+
// Create a decoration (class marker) for this block
|
|
42
|
+
// Pass class in both attrs (for DOM rendering) and spec (for testing/reference)
|
|
43
|
+
const deco = Decoration.node(offset, offset + node.nodeSize, {
|
|
44
|
+
class: className,
|
|
45
|
+
}, {
|
|
46
|
+
class: className,
|
|
47
|
+
});
|
|
48
|
+
decorations.push(deco);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return DecorationSet.create(doc, decorations);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the language for a single block node
|
|
55
|
+
* Checks: node metadata → detected script → fallback to 'en'
|
|
56
|
+
*/
|
|
57
|
+
function resolveBlockLanguage(node) {
|
|
58
|
+
// Check for metadata lang attribute (e.g., lang: hi)
|
|
59
|
+
if (node.attrs?.lang) {
|
|
60
|
+
return node.attrs.lang;
|
|
61
|
+
}
|
|
62
|
+
// Check for inline {lang=…} tag in the node's text
|
|
63
|
+
if (node.textContent) {
|
|
64
|
+
const inlineMatch = /\{lang=([a-z]{2})\}/.exec(node.textContent);
|
|
65
|
+
if (inlineMatch?.[1]) {
|
|
66
|
+
return inlineMatch[1];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Detect from text content
|
|
70
|
+
if (node.textContent) {
|
|
71
|
+
return detectScript(node.textContent);
|
|
72
|
+
}
|
|
73
|
+
// Fallback to English
|
|
74
|
+
return 'en';
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildPlugins(): (import("prosemirror-state").Plugin<any> | import("prosemirror-state").Plugin<import("prosemirror-view").DecorationSet>)[];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { history } from 'prosemirror-history';
|
|
2
|
+
import { baseKeymap } from 'prosemirror-commands';
|
|
3
|
+
import { keymap } from 'prosemirror-keymap';
|
|
4
|
+
import { buildKeymap } from './keymap';
|
|
5
|
+
import { createLanguageClassPlugin } from './language-class-plugin';
|
|
6
|
+
import { createCharacterDecorationPlugin } from './character-decoration-plugin';
|
|
7
|
+
import { createImePluginWithRef } from './ime-plugin';
|
|
8
|
+
import { createAutocompletePlugin } from '../autocomplete/AutocompletePlugin';
|
|
9
|
+
import { createSpellcheckPlugin } from '../spellcheck/spellcheck-plugin';
|
|
10
|
+
import { createAttrPencilPlugin } from './attr-pencil-plugin';
|
|
11
|
+
import { createFindHighlightPlugin } from './find-highlight-plugin';
|
|
12
|
+
export function buildPlugins() {
|
|
13
|
+
return [
|
|
14
|
+
history(),
|
|
15
|
+
buildKeymap(),
|
|
16
|
+
keymap(baseKeymap),
|
|
17
|
+
createLanguageClassPlugin(),
|
|
18
|
+
createCharacterDecorationPlugin(),
|
|
19
|
+
createImePluginWithRef(),
|
|
20
|
+
createAutocompletePlugin(),
|
|
21
|
+
createSpellcheckPlugin(),
|
|
22
|
+
createAttrPencilPlugin(),
|
|
23
|
+
createFindHighlightPlugin(),
|
|
24
|
+
];
|
|
25
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { schema } from './schema';
|
|
2
|
+
function nodeText(node) {
|
|
3
|
+
return node.textContent;
|
|
4
|
+
}
|
|
5
|
+
function inlineToMarkdown(node) {
|
|
6
|
+
let result = '';
|
|
7
|
+
node.forEach(child => {
|
|
8
|
+
if (child.isText) {
|
|
9
|
+
let text = child.text ?? '';
|
|
10
|
+
if (child.marks.find(m => m.type === schema.marks['bold']))
|
|
11
|
+
text = `**${text}**`;
|
|
12
|
+
else if (child.marks.find(m => m.type === schema.marks['italic']))
|
|
13
|
+
text = `*${text}*`;
|
|
14
|
+
else if (child.marks.find(m => m.type === schema.marks['underline']))
|
|
15
|
+
text = `_${text}_`;
|
|
16
|
+
else if (child.marks.find(m => m.type === schema.marks['note_mark']))
|
|
17
|
+
text = `[[${text}]]`;
|
|
18
|
+
result += text;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Extract the text value of a frontmatter_field node, converting hard_break
|
|
25
|
+
* nodes back to '\n' so multi-line values round-trip correctly.
|
|
26
|
+
*/
|
|
27
|
+
function fieldText(node) {
|
|
28
|
+
let result = '';
|
|
29
|
+
node.forEach(child => {
|
|
30
|
+
if (child.isText)
|
|
31
|
+
result += child.text ?? '';
|
|
32
|
+
else if (child.type === schema.nodes['hard_break'])
|
|
33
|
+
result += '\n';
|
|
34
|
+
});
|
|
35
|
+
return result.trim();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* title_page node → frontmatter text block. Dotted keys ("title.hi") are
|
|
39
|
+
* reassembled into one-level nested maps (2-space indent, matching the
|
|
40
|
+
* parser's minimal-YAML frontmatter format). Fields with empty key AND
|
|
41
|
+
* empty value are skipped; returns '' if nothing remains.
|
|
42
|
+
* Multi-line values are serialized as YAML literal block scalars (|).
|
|
43
|
+
*/
|
|
44
|
+
function serializeTitlePage(node) {
|
|
45
|
+
const data = new Map();
|
|
46
|
+
node.forEach(field => {
|
|
47
|
+
if (field.type.name !== 'frontmatter_field')
|
|
48
|
+
return;
|
|
49
|
+
const key = (field.attrs['fmKey'] ?? '').trim();
|
|
50
|
+
const value = fieldText(field);
|
|
51
|
+
if (!key)
|
|
52
|
+
return;
|
|
53
|
+
const dot = key.indexOf('.');
|
|
54
|
+
if (dot > 0 && dot < key.length - 1) {
|
|
55
|
+
const parent = key.slice(0, dot);
|
|
56
|
+
const sub = key.slice(dot + 1);
|
|
57
|
+
const existing = data.get(parent);
|
|
58
|
+
const map = existing !== undefined && typeof existing === 'object' ? existing : {};
|
|
59
|
+
map[sub] = value;
|
|
60
|
+
data.set(parent, map);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
data.set(key, value);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
if (data.size === 0)
|
|
67
|
+
return '';
|
|
68
|
+
const lines = ['---'];
|
|
69
|
+
for (const [key, value] of data) {
|
|
70
|
+
if (typeof value === 'string') {
|
|
71
|
+
if (value === '') {
|
|
72
|
+
lines.push(`${key}:`);
|
|
73
|
+
}
|
|
74
|
+
else if (value.includes('\n')) {
|
|
75
|
+
// YAML literal block scalar: preserves newlines verbatim
|
|
76
|
+
lines.push(`${key}: |`);
|
|
77
|
+
for (const ln of value.split('\n'))
|
|
78
|
+
lines.push(` ${ln}`);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
lines.push(`${key}: ${value}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
lines.push(`${key}:`);
|
|
86
|
+
for (const [sub, sv] of Object.entries(value))
|
|
87
|
+
lines.push(` ${sub}: ${sv}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
lines.push('---');
|
|
91
|
+
return lines.join('\n') + '\n\n';
|
|
92
|
+
}
|
|
93
|
+
export function prosemirrorToSutra(doc) {
|
|
94
|
+
// Collect all top-level nodes into an array first
|
|
95
|
+
const pmNodes = [];
|
|
96
|
+
doc.forEach(node => pmNodes.push(node));
|
|
97
|
+
const blocks = [];
|
|
98
|
+
let i = 0;
|
|
99
|
+
let frontmatter = '';
|
|
100
|
+
if (pmNodes.length > 0 && pmNodes[0].type.name === 'title_page') {
|
|
101
|
+
frontmatter = serializeTitlePage(pmNodes[0]);
|
|
102
|
+
i = 1;
|
|
103
|
+
}
|
|
104
|
+
while (i < pmNodes.length) {
|
|
105
|
+
const node = pmNodes[i];
|
|
106
|
+
const type = node.type.name;
|
|
107
|
+
if (type === 'character') {
|
|
108
|
+
// Group: character cue + following dialogue/parenthetical lines (no blank line between them).
|
|
109
|
+
// Name, extension and dual-marker are all just typed text now — see
|
|
110
|
+
// character-decoration-plugin.ts — so the node's own text is the
|
|
111
|
+
// whole cue line, verbatim.
|
|
112
|
+
const lines = [`@${nodeText(node)}`];
|
|
113
|
+
i++;
|
|
114
|
+
while (i < pmNodes.length) {
|
|
115
|
+
const next = pmNodes[i];
|
|
116
|
+
const nextType = next.type.name;
|
|
117
|
+
if (nextType === 'dialogue') {
|
|
118
|
+
lines.push(inlineToMarkdown(next));
|
|
119
|
+
i++;
|
|
120
|
+
}
|
|
121
|
+
else if (nextType === 'parenthetical') {
|
|
122
|
+
lines.push(`( ${nodeText(next)} )`);
|
|
123
|
+
i++;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
blocks.push(lines.join('\n'));
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (type === 'scene_heading') {
|
|
133
|
+
const id = node.attrs['id'] ? ` {#${node.attrs['id']}}` : '';
|
|
134
|
+
// Group heading + optional synopsis + metadata lines with single newlines (no blank
|
|
135
|
+
// line between them), so the parser recognises them as one heading block.
|
|
136
|
+
const headingLines = [`## ${nodeText(node)}${id}`];
|
|
137
|
+
i++;
|
|
138
|
+
while (i < pmNodes.length && pmNodes[i].type.name === 'scene_metadata') {
|
|
139
|
+
const m = pmNodes[i];
|
|
140
|
+
headingLines.push(`& ${m.attrs['metaKey']}: ${nodeText(m)}`);
|
|
141
|
+
i++;
|
|
142
|
+
}
|
|
143
|
+
blocks.push(headingLines.join('\n'));
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (type === 'scene_metadata') {
|
|
147
|
+
// Orphan metadata (outside a heading group) — emit as-is
|
|
148
|
+
blocks.push(`& ${node.attrs['metaKey']}: ${nodeText(node)}`);
|
|
149
|
+
i++;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (type === 'section') {
|
|
153
|
+
const id = node.attrs['id'] ? ` {#${node.attrs['id']}}` : '';
|
|
154
|
+
blocks.push(`# ${nodeText(node)}${id}`);
|
|
155
|
+
i++;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (type === 'action') {
|
|
159
|
+
blocks.push(inlineToMarkdown(node));
|
|
160
|
+
i++;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (type === 'dialogue') {
|
|
164
|
+
// Orphan dialogue (should not occur after the grouping above, but handle gracefully)
|
|
165
|
+
blocks.push(inlineToMarkdown(node));
|
|
166
|
+
i++;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (type === 'parenthetical') {
|
|
170
|
+
// Orphan parenthetical
|
|
171
|
+
blocks.push(`( ${nodeText(node)} )`);
|
|
172
|
+
i++;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (type === 'transition') {
|
|
176
|
+
blocks.push(`>> ${nodeText(node)}`);
|
|
177
|
+
i++;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (type === 'centered') {
|
|
181
|
+
blocks.push(`>> ${nodeText(node)} <<`);
|
|
182
|
+
i++;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (type === 'lyrics') {
|
|
186
|
+
blocks.push(`~ ${inlineToMarkdown(node)}`);
|
|
187
|
+
i++;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (type === 'note') {
|
|
191
|
+
blocks.push(`[[ ${nodeText(node)} ]]`);
|
|
192
|
+
i++;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (type === 'comment') {
|
|
196
|
+
blocks.push(`<!-- ${nodeText(node)} -->`);
|
|
197
|
+
i++;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (type === 'page_break') {
|
|
201
|
+
blocks.push('===');
|
|
202
|
+
i++;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
i++; // unknown node type — skip
|
|
206
|
+
}
|
|
207
|
+
return frontmatter + blocks.join('\n\n') + '\n';
|
|
208
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Node as PmNode } from 'prosemirror-model';
|
|
2
|
+
import type { EditorView, NodeView, ViewMutationRecord } from 'prosemirror-view';
|
|
3
|
+
/**
|
|
4
|
+
* NodeView for scene_heading. Renders the heading text (ProseMirror-managed)
|
|
5
|
+
* plus an editable scene-number input on the right. Tab from inside the text
|
|
6
|
+
* area or a click on the number triggers focus of the input.
|
|
7
|
+
*
|
|
8
|
+
* The id value is stored purely as a node attribute — the PM content model
|
|
9
|
+
* only contains the heading text.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SceneHeadingView implements NodeView {
|
|
12
|
+
dom: HTMLElement;
|
|
13
|
+
contentDOM: HTMLElement;
|
|
14
|
+
private input;
|
|
15
|
+
private pmView;
|
|
16
|
+
private getPos;
|
|
17
|
+
constructor(node: PmNode, view: EditorView, getPos: () => number | undefined);
|
|
18
|
+
/** Focus the id input — called by Tab keymap handler */
|
|
19
|
+
focusInput(): void;
|
|
20
|
+
private commit;
|
|
21
|
+
private onInputKeydown;
|
|
22
|
+
private onInputBlur;
|
|
23
|
+
update(node: PmNode): boolean;
|
|
24
|
+
destroy(): void;
|
|
25
|
+
stopEvent(event: Event): boolean;
|
|
26
|
+
ignoreMutation(mutation: ViewMutationRecord): boolean;
|
|
27
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { TextSelection } from 'prosemirror-state';
|
|
2
|
+
/**
|
|
3
|
+
* NodeView for scene_heading. Renders the heading text (ProseMirror-managed)
|
|
4
|
+
* plus an editable scene-number input on the right. Tab from inside the text
|
|
5
|
+
* area or a click on the number triggers focus of the input.
|
|
6
|
+
*
|
|
7
|
+
* The id value is stored purely as a node attribute — the PM content model
|
|
8
|
+
* only contains the heading text.
|
|
9
|
+
*/
|
|
10
|
+
export class SceneHeadingView {
|
|
11
|
+
dom;
|
|
12
|
+
contentDOM;
|
|
13
|
+
input;
|
|
14
|
+
pmView;
|
|
15
|
+
getPos;
|
|
16
|
+
constructor(node, view, getPos) {
|
|
17
|
+
this.pmView = view;
|
|
18
|
+
this.getPos = getPos;
|
|
19
|
+
// Outer wrapper — flex row, same as CSS sets for .cs-scene-heading
|
|
20
|
+
this.dom = document.createElement('div');
|
|
21
|
+
this.dom.className = 'cs-scene-heading';
|
|
22
|
+
if (node.attrs['id']) {
|
|
23
|
+
this.dom.dataset['sceneId'] = node.attrs['id'];
|
|
24
|
+
}
|
|
25
|
+
// Left: ProseMirror manages text content here
|
|
26
|
+
this.contentDOM = document.createElement('span');
|
|
27
|
+
this.contentDOM.className = 'cs-scene-heading-text';
|
|
28
|
+
this.dom.appendChild(this.contentDOM);
|
|
29
|
+
// Right: scene number input
|
|
30
|
+
this.input = document.createElement('input');
|
|
31
|
+
this.input.type = 'text';
|
|
32
|
+
this.input.className = 'cs-scene-id-input';
|
|
33
|
+
this.input.placeholder = '#';
|
|
34
|
+
this.input.value = node.attrs['id'] ?? '';
|
|
35
|
+
this.input.setAttribute('aria-label', 'Scene number');
|
|
36
|
+
this.input.spellcheck = false;
|
|
37
|
+
this.input.addEventListener('keydown', this.onInputKeydown);
|
|
38
|
+
this.input.addEventListener('blur', this.onInputBlur);
|
|
39
|
+
// Prevent PM from handling clicks inside the input
|
|
40
|
+
this.input.addEventListener('mousedown', (e) => e.stopPropagation());
|
|
41
|
+
this.dom.appendChild(this.input);
|
|
42
|
+
}
|
|
43
|
+
/** Focus the id input — called by Tab keymap handler */
|
|
44
|
+
focusInput() {
|
|
45
|
+
this.input.focus();
|
|
46
|
+
this.input.select();
|
|
47
|
+
}
|
|
48
|
+
commit() {
|
|
49
|
+
const raw = this.input.value.trim();
|
|
50
|
+
const id = raw === '' ? null : raw;
|
|
51
|
+
const pos = this.getPos();
|
|
52
|
+
if (pos === undefined)
|
|
53
|
+
return;
|
|
54
|
+
const node = this.pmView.state.doc.nodeAt(pos);
|
|
55
|
+
if (!node)
|
|
56
|
+
return;
|
|
57
|
+
if (node.attrs['id'] === id)
|
|
58
|
+
return;
|
|
59
|
+
const tr = this.pmView.state.tr.setNodeMarkup(pos, undefined, { ...node.attrs, id });
|
|
60
|
+
this.pmView.dispatch(tr);
|
|
61
|
+
}
|
|
62
|
+
onInputKeydown = (e) => {
|
|
63
|
+
if (e.key === 'Escape') {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
e.stopPropagation();
|
|
66
|
+
this.input.value = this.pmView.state.doc.nodeAt(this.getPos() ?? 0)?.attrs['id'] ?? '';
|
|
67
|
+
this.pmView.focus();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (e.key === 'ArrowUp' || (e.key === 'Tab' && e.shiftKey)) {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
e.stopPropagation();
|
|
73
|
+
this.commit();
|
|
74
|
+
const pos = this.getPos();
|
|
75
|
+
if (pos !== undefined) {
|
|
76
|
+
const node = this.pmView.state.doc.nodeAt(pos);
|
|
77
|
+
if (node) {
|
|
78
|
+
const endOfText = pos + node.nodeSize - 1;
|
|
79
|
+
const sel = TextSelection.near(this.pmView.state.doc.resolve(endOfText), -1);
|
|
80
|
+
this.pmView.dispatch(this.pmView.state.tr.setSelection(sel));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
this.pmView.focus();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (e.key === 'Enter' || e.key === 'Tab' || e.key === 'ArrowDown') {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
e.stopPropagation();
|
|
89
|
+
this.commit();
|
|
90
|
+
// Move cursor to the start of the node after the scene heading
|
|
91
|
+
const pos = this.getPos();
|
|
92
|
+
if (pos !== undefined) {
|
|
93
|
+
const node = this.pmView.state.doc.nodeAt(pos);
|
|
94
|
+
if (node) {
|
|
95
|
+
const afterPos = pos + node.nodeSize;
|
|
96
|
+
const { doc } = this.pmView.state;
|
|
97
|
+
if (afterPos <= doc.content.size) {
|
|
98
|
+
const sel = TextSelection.near(doc.resolve(afterPos), 1);
|
|
99
|
+
this.pmView.dispatch(this.pmView.state.tr.setSelection(sel));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
this.pmView.focus();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
onInputBlur = () => {
|
|
108
|
+
this.commit();
|
|
109
|
+
};
|
|
110
|
+
update(node) {
|
|
111
|
+
if (node.type.name !== 'scene_heading')
|
|
112
|
+
return false;
|
|
113
|
+
const id = node.attrs['id'] ?? '';
|
|
114
|
+
// Keep DOM attr in sync
|
|
115
|
+
if (id)
|
|
116
|
+
this.dom.dataset['sceneId'] = id;
|
|
117
|
+
else
|
|
118
|
+
delete this.dom.dataset['sceneId'];
|
|
119
|
+
// Only update input if it doesn't have focus (avoid clobbering in-progress edits)
|
|
120
|
+
if (document.activeElement !== this.input) {
|
|
121
|
+
this.input.value = id;
|
|
122
|
+
}
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
destroy() {
|
|
126
|
+
this.input.removeEventListener('keydown', this.onInputKeydown);
|
|
127
|
+
this.input.removeEventListener('blur', this.onInputBlur);
|
|
128
|
+
}
|
|
129
|
+
stopEvent(event) {
|
|
130
|
+
// Let PM handle events inside contentDOM; swallow events on the input
|
|
131
|
+
return event.target === this.input || (this.input.contains(event.target));
|
|
132
|
+
}
|
|
133
|
+
ignoreMutation(mutation) {
|
|
134
|
+
return this.input.contains(mutation.target) || mutation.target === this.input;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { Schema } from 'prosemirror-model';
|
|
2
|
+
const nodes = {
|
|
3
|
+
doc: { content: 'title_page? block+' },
|
|
4
|
+
// Frontmatter rendered as an editable card at the top of the document.
|
|
5
|
+
// Only valid as the first child of doc (not in the block group).
|
|
6
|
+
title_page: {
|
|
7
|
+
content: 'frontmatter_field+',
|
|
8
|
+
toDOM: () => ['div', { class: 'cs-title-page-block' }, 0],
|
|
9
|
+
parseDOM: [{ tag: 'div.cs-title-page-block' }],
|
|
10
|
+
},
|
|
11
|
+
frontmatter_field: {
|
|
12
|
+
content: '(text | hard_break)*',
|
|
13
|
+
marks: '',
|
|
14
|
+
attrs: { fmKey: { default: '' } },
|
|
15
|
+
toDOM: (node) => ['div', { class: 'cs-fm-field', 'data-key': node.attrs['fmKey'] }, 0],
|
|
16
|
+
parseDOM: [{ tag: 'div.cs-fm-field', getAttrs: (dom) => ({ fmKey: dom.dataset['key'] ?? '' }) }],
|
|
17
|
+
},
|
|
18
|
+
scene_heading: {
|
|
19
|
+
group: 'block',
|
|
20
|
+
content: 'inline*',
|
|
21
|
+
attrs: { id: { default: null } },
|
|
22
|
+
toDOM: (node) => {
|
|
23
|
+
const attrs = { class: 'cs-scene-heading' };
|
|
24
|
+
if (node.attrs['id'])
|
|
25
|
+
attrs['data-scene-id'] = node.attrs['id'];
|
|
26
|
+
return ['div', attrs, 0];
|
|
27
|
+
},
|
|
28
|
+
parseDOM: [{ tag: 'div.cs-scene-heading', getAttrs: (dom) => ({ id: dom.dataset['sceneId'] ?? null }) }],
|
|
29
|
+
},
|
|
30
|
+
action: {
|
|
31
|
+
group: 'block',
|
|
32
|
+
content: 'inline*',
|
|
33
|
+
toDOM: () => ['div', { class: 'cs-action' }, 0],
|
|
34
|
+
parseDOM: [{ tag: 'div.cs-action' }],
|
|
35
|
+
},
|
|
36
|
+
character: {
|
|
37
|
+
group: 'block',
|
|
38
|
+
content: 'inline*',
|
|
39
|
+
toDOM: () => ['div', { class: 'cs-character' }, 0],
|
|
40
|
+
parseDOM: [{ tag: 'div.cs-character' }],
|
|
41
|
+
},
|
|
42
|
+
dialogue: {
|
|
43
|
+
group: 'block',
|
|
44
|
+
content: 'inline*',
|
|
45
|
+
toDOM: () => ['div', { class: 'cs-dialogue' }, 0],
|
|
46
|
+
parseDOM: [{ tag: 'div.cs-dialogue' }],
|
|
47
|
+
},
|
|
48
|
+
parenthetical: {
|
|
49
|
+
group: 'block',
|
|
50
|
+
content: 'inline*',
|
|
51
|
+
toDOM: () => ['div', { class: 'cs-parenthetical' }, 0],
|
|
52
|
+
parseDOM: [{ tag: 'div.cs-parenthetical' }],
|
|
53
|
+
},
|
|
54
|
+
transition: {
|
|
55
|
+
group: 'block',
|
|
56
|
+
content: 'inline*',
|
|
57
|
+
toDOM: () => ['div', { class: 'cs-transition' }, 0],
|
|
58
|
+
parseDOM: [{ tag: 'div.cs-transition' }],
|
|
59
|
+
},
|
|
60
|
+
centered: {
|
|
61
|
+
group: 'block',
|
|
62
|
+
content: 'inline*',
|
|
63
|
+
toDOM: () => ['div', { class: 'cs-centered' }, 0],
|
|
64
|
+
parseDOM: [{ tag: 'div.cs-centered' }],
|
|
65
|
+
},
|
|
66
|
+
lyrics: {
|
|
67
|
+
group: 'block',
|
|
68
|
+
content: 'inline*',
|
|
69
|
+
toDOM: () => ['div', { class: 'cs-lyrics' }, 0],
|
|
70
|
+
parseDOM: [{ tag: 'div.cs-lyrics' }],
|
|
71
|
+
},
|
|
72
|
+
scene_metadata: {
|
|
73
|
+
group: 'block',
|
|
74
|
+
content: 'inline*',
|
|
75
|
+
attrs: { metaKey: { default: '' } },
|
|
76
|
+
toDOM: (node) => ['div', { class: 'cs-scene-metadata', 'data-key': node.attrs['metaKey'] }, 0],
|
|
77
|
+
parseDOM: [{ tag: 'div.cs-scene-metadata', getAttrs: (dom) => ({ metaKey: dom.dataset['key'] ?? '' }) }],
|
|
78
|
+
},
|
|
79
|
+
note: {
|
|
80
|
+
group: 'block',
|
|
81
|
+
content: 'inline*',
|
|
82
|
+
toDOM: () => ['div', { class: 'cs-note' }, 0],
|
|
83
|
+
parseDOM: [{ tag: 'div.cs-note' }],
|
|
84
|
+
},
|
|
85
|
+
comment: {
|
|
86
|
+
group: 'block',
|
|
87
|
+
content: 'inline*',
|
|
88
|
+
toDOM: () => ['div', { class: 'cs-comment' }, 0],
|
|
89
|
+
parseDOM: [{ tag: 'div.cs-comment' }],
|
|
90
|
+
},
|
|
91
|
+
page_break: {
|
|
92
|
+
group: 'block',
|
|
93
|
+
toDOM: () => ['div', { class: 'cs-page-break' }],
|
|
94
|
+
parseDOM: [{ tag: 'div.cs-page-break' }],
|
|
95
|
+
},
|
|
96
|
+
section: {
|
|
97
|
+
group: 'block',
|
|
98
|
+
content: 'inline*',
|
|
99
|
+
attrs: { level: { default: 1 }, id: { default: null } },
|
|
100
|
+
toDOM: (node) => [`h${node.attrs['level'] + 1}`, { class: 'cs-section' }, 0],
|
|
101
|
+
parseDOM: [
|
|
102
|
+
{ tag: 'h2.cs-section', getAttrs: () => ({ level: 1 }) },
|
|
103
|
+
{ tag: 'h3.cs-section', getAttrs: () => ({ level: 2 }) },
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
hard_break: {
|
|
107
|
+
inline: true,
|
|
108
|
+
group: 'inline',
|
|
109
|
+
selectable: false,
|
|
110
|
+
toDOM: () => ['br'],
|
|
111
|
+
parseDOM: [{ tag: 'br' }],
|
|
112
|
+
},
|
|
113
|
+
text: { group: 'inline' },
|
|
114
|
+
};
|
|
115
|
+
const marks = {
|
|
116
|
+
bold: {
|
|
117
|
+
toDOM: () => ['strong', 0],
|
|
118
|
+
parseDOM: [{ tag: 'strong' }],
|
|
119
|
+
},
|
|
120
|
+
italic: {
|
|
121
|
+
toDOM: () => ['em', 0],
|
|
122
|
+
parseDOM: [{ tag: 'em' }],
|
|
123
|
+
},
|
|
124
|
+
underline: {
|
|
125
|
+
toDOM: () => ['u', 0],
|
|
126
|
+
parseDOM: [{ tag: 'u' }],
|
|
127
|
+
},
|
|
128
|
+
// Named note_mark, not note, since a block-level `note` node already exists
|
|
129
|
+
// (whole-line [[ ]] notes) and ProseMirror forbids a name being both.
|
|
130
|
+
note_mark: {
|
|
131
|
+
toDOM: () => ['span', { class: 'cs-inline-note' }, 0],
|
|
132
|
+
parseDOM: [{ tag: 'span.cs-inline-note' }],
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
export const schema = new Schema({ nodes, marks });
|