@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.
Files changed (337) hide show
  1. package/LICENSE +674 -0
  2. package/dist/ai/OnboardingDialog.d.ts +6 -0
  3. package/dist/ai/OnboardingDialog.js +115 -0
  4. package/dist/ai/VoiceConfirmDialog.d.ts +9 -0
  5. package/dist/ai/VoiceConfirmDialog.js +37 -0
  6. package/dist/ai/VoiceToolbar.d.ts +2 -0
  7. package/dist/ai/VoiceToolbar.js +177 -0
  8. package/dist/ai/ai-client.d.ts +71 -0
  9. package/dist/ai/ai-client.js +494 -0
  10. package/dist/ai/commands.d.ts +12 -0
  11. package/dist/ai/commands.js +140 -0
  12. package/dist/ai/generated-providers.json +3696 -0
  13. package/dist/ai/prompts.d.ts +25 -0
  14. package/dist/ai/prompts.js +59 -0
  15. package/dist/ai/voice-service.d.ts +15 -0
  16. package/dist/ai/voice-service.js +81 -0
  17. package/dist/autocomplete/AutocompletePlugin.d.ts +2 -0
  18. package/dist/autocomplete/AutocompletePlugin.js +19 -0
  19. package/dist/autocomplete/suggestions.d.ts +25 -0
  20. package/dist/autocomplete/suggestions.js +126 -0
  21. package/dist/context/DocumentContext.d.ts +68 -0
  22. package/dist/context/DocumentContext.js +605 -0
  23. package/dist/desktop-api.d.ts +40 -0
  24. package/dist/editor/EditorView.d.ts +2 -0
  25. package/dist/editor/EditorView.js +78 -0
  26. package/dist/editor/SelectionMenu.d.ts +2 -0
  27. package/dist/editor/SelectionMenu.js +102 -0
  28. package/dist/editor/attr-pencil-plugin.d.ts +17 -0
  29. package/dist/editor/attr-pencil-plugin.js +158 -0
  30. package/dist/editor/character-decoration-plugin.d.ts +26 -0
  31. package/dist/editor/character-decoration-plugin.js +77 -0
  32. package/dist/editor/character-view.d.ts +35 -0
  33. package/dist/editor/character-view.js +112 -0
  34. package/dist/editor/editor-bus.d.ts +14 -0
  35. package/dist/editor/editor-bus.js +39 -0
  36. package/dist/editor/find-highlight-plugin.d.ts +7 -0
  37. package/dist/editor/find-highlight-plugin.js +57 -0
  38. package/dist/editor/frontmatter-field-view.d.ts +10 -0
  39. package/dist/editor/frontmatter-field-view.js +48 -0
  40. package/dist/editor/frontmatter-field.d.ts +21 -0
  41. package/dist/editor/frontmatter-field.js +116 -0
  42. package/dist/editor/grapheme-cursor.d.ts +10 -0
  43. package/dist/editor/grapheme-cursor.js +104 -0
  44. package/dist/editor/ime-plugin.d.ts +18 -0
  45. package/dist/editor/ime-plugin.js +71 -0
  46. package/dist/editor/insert-helper.d.ts +1 -0
  47. package/dist/editor/insert-helper.js +42 -0
  48. package/dist/editor/keymap.d.ts +3 -0
  49. package/dist/editor/keymap.js +131 -0
  50. package/dist/editor/language-class-plugin.d.ts +7 -0
  51. package/dist/editor/language-class-plugin.js +75 -0
  52. package/dist/editor/plugins.d.ts +1 -0
  53. package/dist/editor/plugins.js +25 -0
  54. package/dist/editor/prosemirror-to-sutra.d.ts +2 -0
  55. package/dist/editor/prosemirror-to-sutra.js +208 -0
  56. package/dist/editor/scene-heading-view.d.ts +27 -0
  57. package/dist/editor/scene-heading-view.js +136 -0
  58. package/dist/editor/schema.d.ts +3 -0
  59. package/dist/editor/schema.js +135 -0
  60. package/dist/editor/sutra-to-prosemirror.d.ts +3 -0
  61. package/dist/editor/sutra-to-prosemirror.js +154 -0
  62. package/dist/editor/transliterate-input.d.ts +10 -0
  63. package/dist/editor/transliterate-input.js +88 -0
  64. package/dist/extensions/storage-adapter.d.ts +59 -0
  65. package/dist/extensions/storage-adapter.js +9 -0
  66. package/dist/extensions/types.d.ts +73 -0
  67. package/dist/extensions/types.js +12 -0
  68. package/dist/file/docx-exporter.d.ts +8 -0
  69. package/dist/file/docx-exporter.js +494 -0
  70. package/dist/file/docx-importer.d.ts +10 -0
  71. package/dist/file/docx-importer.js +283 -0
  72. package/dist/file/print-paginator.d.ts +29 -0
  73. package/dist/file/print-paginator.js +289 -0
  74. package/dist/file/templates.d.ts +2 -0
  75. package/dist/file/templates.js +66 -0
  76. package/dist/file/workflow-reports.d.ts +34 -0
  77. package/dist/file/workflow-reports.js +719 -0
  78. package/dist/findreplace/FindReplace.d.ts +2 -0
  79. package/dist/findreplace/FindReplace.js +164 -0
  80. package/dist/findreplace/find-engine.d.ts +29 -0
  81. package/dist/findreplace/find-engine.js +69 -0
  82. package/dist/findreplace/scroll-to-match.d.ts +29 -0
  83. package/dist/findreplace/scroll-to-match.js +74 -0
  84. package/dist/fonts/tofu-detect.d.ts +1 -0
  85. package/dist/fonts/tofu-detect.js +48 -0
  86. package/dist/i18n/LanguageContext.d.ts +10 -0
  87. package/dist/i18n/LanguageContext.js +174 -0
  88. package/dist/i18n/language-detect.d.ts +1 -0
  89. package/dist/i18n/language-detect.js +58 -0
  90. package/dist/i18n/useTranslation.d.ts +11 -0
  91. package/dist/i18n/useTranslation.js +60 -0
  92. package/dist/index.d.ts +11 -0
  93. package/dist/index.js +5 -0
  94. package/dist/locales/bn.json +143 -0
  95. package/dist/locales/en.json +143 -0
  96. package/dist/locales/gu.json +143 -0
  97. package/dist/locales/hi.json +143 -0
  98. package/dist/locales/kn.json +143 -0
  99. package/dist/locales/ml.json +143 -0
  100. package/dist/locales/mr.json +143 -0
  101. package/dist/locales/or.json +143 -0
  102. package/dist/locales/pa.json +143 -0
  103. package/dist/locales/si.json +143 -0
  104. package/dist/locales/ta.json +143 -0
  105. package/dist/locales/te.json +143 -0
  106. package/dist/navigator/SceneNavigator.d.ts +2 -0
  107. package/dist/navigator/SceneNavigator.js +612 -0
  108. package/dist/navigator/StatisticsDialog.d.ts +6 -0
  109. package/dist/navigator/StatisticsDialog.js +146 -0
  110. package/dist/navigator/scene-list.d.ts +16 -0
  111. package/dist/navigator/scene-list.js +70 -0
  112. package/dist/settings/SettingsDialog.d.ts +6 -0
  113. package/dist/settings/SettingsDialog.js +147 -0
  114. package/dist/shell/AppBar.d.ts +2 -0
  115. package/dist/shell/AppBar.js +34 -0
  116. package/dist/shell/AppShell.d.ts +2 -0
  117. package/dist/shell/AppShell.js +30 -0
  118. package/dist/shell/ConfirmDialog.d.ts +16 -0
  119. package/dist/shell/ConfirmDialog.js +23 -0
  120. package/dist/shell/ElementToolbar.d.ts +2 -0
  121. package/dist/shell/ElementToolbar.js +30 -0
  122. package/dist/shell/LanguageStatus.d.ts +2 -0
  123. package/dist/shell/LanguageStatus.js +65 -0
  124. package/dist/shell/StatusBar.d.ts +2 -0
  125. package/dist/shell/StatusBar.js +54 -0
  126. package/dist/shell/SutrataLogo.d.ts +8 -0
  127. package/dist/shell/SutrataLogo.js +7 -0
  128. package/dist/shell/Toast.d.ts +12 -0
  129. package/dist/shell/Toast.js +21 -0
  130. package/dist/shell/ToolbarButton.d.ts +11 -0
  131. package/dist/shell/ToolbarButton.js +4 -0
  132. package/dist/shell/VersionHistory.d.ts +12 -0
  133. package/dist/shell/VersionHistory.js +89 -0
  134. package/dist/shell/Watermark.d.ts +3 -0
  135. package/dist/shell/Watermark.js +10 -0
  136. package/dist/shell/fonts/fonts.css +1133 -0
  137. package/dist/shell/icons.d.ts +45 -0
  138. package/dist/shell/icons.js +51 -0
  139. package/dist/shell/toolbar-actions.d.ts +6 -0
  140. package/dist/shell/toolbar-actions.js +58 -0
  141. package/dist/source/SourceView.d.ts +1 -0
  142. package/dist/source/SourceView.js +106 -0
  143. package/dist/source/find-highlight-source.d.ts +14 -0
  144. package/dist/source/find-highlight-source.js +47 -0
  145. package/dist/source/sutra-lang.d.ts +2 -0
  146. package/dist/source/sutra-lang.js +97 -0
  147. package/dist/spellcheck/dictionaries.d.ts +9 -0
  148. package/dist/spellcheck/dictionaries.js +16 -0
  149. package/dist/spellcheck/hunspell.d.ts +23 -0
  150. package/dist/spellcheck/hunspell.js +94 -0
  151. package/dist/spellcheck/spellcheck-plugin.d.ts +18 -0
  152. package/dist/spellcheck/spellcheck-plugin.js +75 -0
  153. package/dist/styles/StyleDialog.d.ts +6 -0
  154. package/dist/styles/StyleDialog.js +63 -0
  155. package/dist/styles/builtin-styles.d.ts +43 -0
  156. package/dist/styles/builtin-styles.js +131 -0
  157. package/dist/styles/css-adapter.d.ts +32 -0
  158. package/dist/styles/css-adapter.js +71 -0
  159. package/dist/styles/docx-adapter.d.ts +7 -0
  160. package/dist/styles/docx-adapter.js +84 -0
  161. package/dist/styles/embed-fonts.d.ts +20 -0
  162. package/dist/styles/embed-fonts.js +81 -0
  163. package/dist/styles/pagination.d.ts +19 -0
  164. package/dist/styles/pagination.js +22 -0
  165. package/dist/styles/print-adapter.d.ts +6 -0
  166. package/dist/styles/print-adapter.js +288 -0
  167. package/dist/styles/registry.d.ts +6 -0
  168. package/dist/styles/registry.js +14 -0
  169. package/dist/styles/screenplay.css +3006 -0
  170. package/dist/styles/script-ranges.d.ts +6 -0
  171. package/dist/styles/script-ranges.js +17 -0
  172. package/dist/styles/types.d.ts +93 -0
  173. package/dist/styles/types.js +18 -0
  174. package/dist/styles/validate.d.ts +12 -0
  175. package/dist/styles/validate.js +105 -0
  176. package/dist/templates/templates.d.ts +10 -0
  177. package/dist/templates/templates.js +181 -0
  178. package/dist/titlepage/ExportDialog.d.ts +1 -0
  179. package/dist/titlepage/ExportDialog.js +152 -0
  180. package/dist/titlepage/TitlePageDialog.d.ts +6 -0
  181. package/dist/titlepage/TitlePageDialog.js +96 -0
  182. package/dist/titlepage/frontmatter-form.d.ts +56 -0
  183. package/dist/titlepage/frontmatter-form.js +245 -0
  184. package/dist/titlepage/languages.d.ts +10 -0
  185. package/dist/titlepage/languages.js +31 -0
  186. package/dist/types.d.ts +6 -0
  187. package/dist/types.js +1 -0
  188. package/package.json +47 -0
  189. package/scripts/copy-assets.mjs +16 -0
  190. package/scripts/generate-providers.mjs +127 -0
  191. package/src/ai/OnboardingDialog.tsx +390 -0
  192. package/src/ai/VoiceConfirmDialog.tsx +137 -0
  193. package/src/ai/VoiceToolbar.tsx +321 -0
  194. package/src/ai/ai-client.ts +596 -0
  195. package/src/ai/commands.ts +164 -0
  196. package/src/ai/generated-providers.json +3696 -0
  197. package/src/ai/prompts.ts +62 -0
  198. package/src/ai/voice-service.ts +96 -0
  199. package/src/autocomplete/AutocompletePlugin.ts +21 -0
  200. package/src/autocomplete/suggestions.ts +147 -0
  201. package/src/context/DocumentContext.tsx +648 -0
  202. package/src/desktop-api.d.ts +40 -0
  203. package/src/editor/EditorView.tsx +82 -0
  204. package/src/editor/SelectionMenu.tsx +152 -0
  205. package/src/editor/attr-pencil-plugin.ts +172 -0
  206. package/src/editor/character-decoration-plugin.ts +104 -0
  207. package/src/editor/editor-bus.ts +51 -0
  208. package/src/editor/find-highlight-plugin.ts +68 -0
  209. package/src/editor/frontmatter-field-view.ts +52 -0
  210. package/src/editor/frontmatter-field.ts +115 -0
  211. package/src/editor/grapheme-cursor.ts +99 -0
  212. package/src/editor/ime-plugin.ts +85 -0
  213. package/src/editor/insert-helper.ts +43 -0
  214. package/src/editor/keymap.ts +124 -0
  215. package/src/editor/language-class-plugin.ts +91 -0
  216. package/src/editor/plugins.ts +26 -0
  217. package/src/editor/prosemirror-to-sutra.ts +192 -0
  218. package/src/editor/scene-heading-view.ts +147 -0
  219. package/src/editor/schema.ts +156 -0
  220. package/src/editor/sutra-to-prosemirror.ts +156 -0
  221. package/src/extensions/storage-adapter.ts +65 -0
  222. package/src/extensions/types.ts +60 -0
  223. package/src/file/docx-exporter.ts +585 -0
  224. package/src/file/docx-importer.ts +313 -0
  225. package/src/file/print-paginator.ts +301 -0
  226. package/src/file/templates.ts +73 -0
  227. package/src/file/workflow-reports.ts +820 -0
  228. package/src/findreplace/FindReplace.tsx +242 -0
  229. package/src/findreplace/find-engine.ts +97 -0
  230. package/src/findreplace/scroll-to-match.ts +89 -0
  231. package/src/fonts/tofu-detect.ts +53 -0
  232. package/src/i18n/LanguageContext.tsx +207 -0
  233. package/src/i18n/language-detect.ts +73 -0
  234. package/src/i18n/useTranslation.tsx +74 -0
  235. package/src/index.ts +27 -0
  236. package/src/locales/bn.json +143 -0
  237. package/src/locales/en.json +143 -0
  238. package/src/locales/gu.json +143 -0
  239. package/src/locales/hi.json +143 -0
  240. package/src/locales/kn.json +143 -0
  241. package/src/locales/ml.json +143 -0
  242. package/src/locales/mr.json +143 -0
  243. package/src/locales/or.json +143 -0
  244. package/src/locales/pa.json +143 -0
  245. package/src/locales/si.json +143 -0
  246. package/src/locales/ta.json +143 -0
  247. package/src/locales/te.json +143 -0
  248. package/src/navigator/SceneNavigator.tsx +844 -0
  249. package/src/navigator/StatisticsDialog.tsx +249 -0
  250. package/src/navigator/scene-list.ts +84 -0
  251. package/src/settings/SettingsDialog.tsx +303 -0
  252. package/src/shell/AppBar.tsx +354 -0
  253. package/src/shell/AppShell.tsx +71 -0
  254. package/src/shell/ConfirmDialog.tsx +71 -0
  255. package/src/shell/ElementToolbar.tsx +175 -0
  256. package/src/shell/LanguageStatus.tsx +101 -0
  257. package/src/shell/StatusBar.tsx +135 -0
  258. package/src/shell/SutrataLogo.tsx +89 -0
  259. package/src/shell/Toast.tsx +46 -0
  260. package/src/shell/ToolbarButton.tsx +30 -0
  261. package/src/shell/VersionHistory.tsx +212 -0
  262. package/src/shell/Watermark.tsx +9 -0
  263. package/src/shell/fonts/fonts.css +1133 -0
  264. package/src/shell/icons.tsx +335 -0
  265. package/src/shell/toolbar-actions.ts +59 -0
  266. package/src/source/SourceView.tsx +111 -0
  267. package/src/source/find-highlight-source.ts +57 -0
  268. package/src/source/sutra-lang.ts +121 -0
  269. package/src/spellcheck/dictionaries.ts +21 -0
  270. package/src/spellcheck/hunspell.ts +110 -0
  271. package/src/spellcheck/spellcheck-plugin.ts +83 -0
  272. package/src/styles/StyleDialog.tsx +148 -0
  273. package/src/styles/builtin-styles.ts +139 -0
  274. package/src/styles/css-adapter.ts +76 -0
  275. package/src/styles/docx-adapter.ts +109 -0
  276. package/src/styles/embed-fonts.ts +81 -0
  277. package/src/styles/pagination.ts +27 -0
  278. package/src/styles/print-adapter.ts +288 -0
  279. package/src/styles/registry.ts +20 -0
  280. package/src/styles/screenplay.css +3006 -0
  281. package/src/styles/script-ranges.ts +17 -0
  282. package/src/styles/types.ts +104 -0
  283. package/src/styles/validate.ts +114 -0
  284. package/src/templates/templates.ts +194 -0
  285. package/src/titlepage/ExportDialog.tsx +494 -0
  286. package/src/titlepage/TitlePageDialog.tsx +312 -0
  287. package/src/titlepage/frontmatter-form.ts +280 -0
  288. package/src/titlepage/languages.ts +37 -0
  289. package/src/types.ts +7 -0
  290. package/tests/AppBar.test.tsx +35 -0
  291. package/tests/AppShell.test.tsx +22 -0
  292. package/tests/ElementToolbar.test.tsx +36 -0
  293. package/tests/LanguageStatus.test.tsx +129 -0
  294. package/tests/SelectionMenu.test.tsx +71 -0
  295. package/tests/ToolbarButton.test.tsx +24 -0
  296. package/tests/ai-onboarding.test.ts +102 -0
  297. package/tests/ai-status.test.tsx +72 -0
  298. package/tests/ai-structured.test.ts +94 -0
  299. package/tests/autocomplete/AutocompletePlugin.test.ts +20 -0
  300. package/tests/autocomplete/suggestions.test.ts +106 -0
  301. package/tests/document-context.test.tsx +565 -0
  302. package/tests/docx-importer.test.ts +160 -0
  303. package/tests/editor/character-decoration-plugin.test.ts +101 -0
  304. package/tests/editor/character-extension.test.ts +44 -0
  305. package/tests/editor/grapheme-cursor.test.ts +118 -0
  306. package/tests/editor/ime-plugin.test.ts +72 -0
  307. package/tests/editor/inline-note-bridge.test.ts +32 -0
  308. package/tests/editor/language-class-plugin.test.ts +265 -0
  309. package/tests/editor/title-page.test.ts +124 -0
  310. package/tests/embed-fonts.test.ts +17 -0
  311. package/tests/exporters.test.ts +365 -0
  312. package/tests/findreplace/FindReplace.test.tsx +391 -0
  313. package/tests/findreplace/find-engine.test.ts +86 -0
  314. package/tests/findreplace/scroll-to-match.test.ts +50 -0
  315. package/tests/fonts/tofu-detect.test.ts +122 -0
  316. package/tests/i18n/LanguageContext.test.tsx +146 -0
  317. package/tests/i18n/language-detect.test.ts +54 -0
  318. package/tests/i18n/locale-parity.test.ts +25 -0
  319. package/tests/i18n/useTranslation.test.tsx +45 -0
  320. package/tests/keymap.test.ts +18 -0
  321. package/tests/navigator/SceneNavigator.test.tsx +177 -0
  322. package/tests/navigator/StatisticsDialog.test.tsx +63 -0
  323. package/tests/navigator/scene-list.test.ts +71 -0
  324. package/tests/print-paginator.test.ts +82 -0
  325. package/tests/shell/VersionHistory.test.tsx +129 -0
  326. package/tests/spellcheck/hunspell.test.ts +82 -0
  327. package/tests/spellcheck/spellcheck-plugin.test.ts +56 -0
  328. package/tests/styles.test.ts +315 -0
  329. package/tests/templates/templates.test.ts +83 -0
  330. package/tests/titlepage/TitlePageDialog.test.tsx +90 -0
  331. package/tests/titlepage/apply-title-page.test.ts +61 -0
  332. package/tests/titlepage/frontmatter-form.test.ts +115 -0
  333. package/tests/toolbar-actions.test.ts +93 -0
  334. package/tests/voice-commands.test.ts +58 -0
  335. package/tsconfig.json +17 -0
  336. package/vitest.config.ts +11 -0
  337. package/vitest.setup.ts +65 -0
@@ -0,0 +1,78 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from 'react';
3
+ import { EditorState } from 'prosemirror-state';
4
+ import { EditorView as PmEditorView } from 'prosemirror-view';
5
+ import { parse } from '@sutrata/parser';
6
+ import { schema } from './schema';
7
+ import { buildPlugins } from './plugins';
8
+ import { sutraToProsemirror } from './sutra-to-prosemirror';
9
+ import { prosemirrorToSutra } from './prosemirror-to-sutra';
10
+ import { setEditorView, emit } from './editor-bus';
11
+ import { useDocument } from '../context/DocumentContext';
12
+ import { SceneHeadingView } from './scene-heading-view';
13
+ import { FrontmatterFieldView } from './frontmatter-field-view';
14
+ import { applyStyleVars } from '../styles/css-adapter';
15
+ export function EditorView() {
16
+ const { text, setText, resolvedStyle } = useDocument();
17
+ const mountRef = useRef(null);
18
+ const viewRef = useRef(null);
19
+ const textRef = useRef(text);
20
+ textRef.current = text;
21
+ // When true, the next text change originated from the editor itself —
22
+ // the sync effect must skip it to avoid clobbering in-flight transactions.
23
+ const selfChangeRef = useRef(false);
24
+ // Mount once: create ProseMirror view
25
+ useEffect(() => {
26
+ if (!mountRef.current)
27
+ return;
28
+ const doc = sutraToProsemirror(parse(textRef.current));
29
+ const state = EditorState.create({ doc, schema, plugins: buildPlugins() });
30
+ const view = new PmEditorView(mountRef.current, {
31
+ state,
32
+ nodeViews: {
33
+ scene_heading: (node, view, getPos) => new SceneHeadingView(node, view, getPos),
34
+ frontmatter_field: (node, view, getPos) => new FrontmatterFieldView(node, view, getPos),
35
+ },
36
+ dispatchTransaction(tr) {
37
+ const newState = view.state.apply(tr);
38
+ view.updateState(newState);
39
+ if (tr.docChanged) {
40
+ const newText = prosemirrorToSutra(newState.doc);
41
+ if (newText !== textRef.current) {
42
+ selfChangeRef.current = true;
43
+ setText(newText);
44
+ }
45
+ }
46
+ emit();
47
+ },
48
+ });
49
+ viewRef.current = view;
50
+ setEditorView(view);
51
+ return () => { setEditorView(null); view.destroy(); viewRef.current = null; };
52
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
53
+ // Sync external text changes into ProseMirror (e.g. switching from source mode).
54
+ // Skip when the editor itself produced the change — it already has the right state.
55
+ useEffect(() => {
56
+ if (selfChangeRef.current) {
57
+ selfChangeRef.current = false;
58
+ return;
59
+ }
60
+ const view = viewRef.current;
61
+ if (!view)
62
+ return;
63
+ const currentText = prosemirrorToSutra(view.state.doc);
64
+ if (currentText !== text) {
65
+ const doc = sutraToProsemirror(parse(text));
66
+ const state = EditorState.create({ doc, schema, plugins: buildPlugins() });
67
+ view.updateState(state);
68
+ }
69
+ }, [text]);
70
+ // Apply the document's resolved style as CSS custom properties on the mount element.
71
+ // Works identically for built-in and user-imported styles — both are plain
72
+ // ScreenplayStyleDefinition objects by the time they reach here.
73
+ useEffect(() => {
74
+ if (mountRef.current)
75
+ applyStyleVars(mountRef.current, resolvedStyle);
76
+ }, [resolvedStyle]);
77
+ return _jsx("div", { ref: mountRef, className: "cs-editor-mount" });
78
+ }
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare function SelectionMenu(): React.JSX.Element | null;
@@ -0,0 +1,102 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { useLanguage } from '../i18n/LanguageContext';
4
+ import { useDocument } from '../context/DocumentContext';
5
+ import { insertSutra } from './insert-helper';
6
+ import { VoiceConfirmDialog } from '../ai/VoiceConfirmDialog';
7
+ import { SparklesIcon } from '../shell/icons';
8
+ const VOICE_LANG_MAP = {
9
+ en: 'en-US',
10
+ hi: 'hi-IN',
11
+ ta: 'ta-IN',
12
+ te: 'te-IN',
13
+ ml: 'ml-IN',
14
+ kn: 'kn-IN',
15
+ bn: 'bn-IN',
16
+ gu: 'gu-IN',
17
+ mr: 'mr-IN',
18
+ };
19
+ function isSelectionInEditor(sel) {
20
+ if (sel.rangeCount === 0)
21
+ return false;
22
+ const range = sel.getRangeAt(0);
23
+ let node = range.commonAncestorContainer;
24
+ while (node) {
25
+ if (node instanceof HTMLElement) {
26
+ if (node.classList.contains('ProseMirror') || node.classList.contains('cm-content')) {
27
+ return true;
28
+ }
29
+ }
30
+ node = node.parentNode;
31
+ }
32
+ return false;
33
+ }
34
+ export function SelectionMenu() {
35
+ const { currentLanguage } = useLanguage();
36
+ const { mode } = useDocument();
37
+ const [menuPosition, setMenuPosition] = useState(null);
38
+ const [selectedText, setSelectedText] = useState('');
39
+ const [showConfirm, setShowConfirm] = useState(false);
40
+ useEffect(() => {
41
+ const handleSelectionChange = () => {
42
+ if (showConfirm)
43
+ return;
44
+ const sel = window.getSelection();
45
+ if (!sel || sel.isCollapsed || !isSelectionInEditor(sel)) {
46
+ setMenuPosition(null);
47
+ setSelectedText('');
48
+ return;
49
+ }
50
+ const text = sel.toString().trim();
51
+ if (!text) {
52
+ setMenuPosition(null);
53
+ setSelectedText('');
54
+ return;
55
+ }
56
+ const range = sel.getRangeAt(0);
57
+ const rect = range.getBoundingClientRect();
58
+ setMenuPosition({
59
+ x: rect.left + rect.width / 2,
60
+ y: rect.top - 8,
61
+ });
62
+ setSelectedText(sel.toString());
63
+ };
64
+ document.addEventListener('selectionchange', handleSelectionChange);
65
+ return () => {
66
+ document.removeEventListener('selectionchange', handleSelectionChange);
67
+ };
68
+ }, [showConfirm]);
69
+ const handleCut = (e) => {
70
+ e.preventDefault();
71
+ e.stopPropagation();
72
+ document.execCommand('cut');
73
+ setMenuPosition(null);
74
+ };
75
+ const handleCopy = (e) => {
76
+ e.preventDefault();
77
+ e.stopPropagation();
78
+ document.execCommand('copy');
79
+ setMenuPosition(null);
80
+ };
81
+ const handleFormat = (e) => {
82
+ e.preventDefault();
83
+ e.stopPropagation();
84
+ setShowConfirm(true);
85
+ setMenuPosition(null);
86
+ };
87
+ if (showConfirm) {
88
+ const langCode = VOICE_LANG_MAP[currentLanguage] || 'en-US';
89
+ return (_jsx(VoiceConfirmDialog, { rawTranscript: selectedText, langCode: langCode, onConfirm: (formattedText) => {
90
+ insertSutra(formattedText, mode);
91
+ setShowConfirm(false);
92
+ }, onCancel: () => {
93
+ setShowConfirm(false);
94
+ } }));
95
+ }
96
+ if (!menuPosition)
97
+ return null;
98
+ return (_jsxs("div", { className: "cs-selection-menu", style: {
99
+ left: `${menuPosition.x}px`,
100
+ top: `${menuPosition.y}px`,
101
+ }, role: "toolbar", "aria-label": "Text Selection Actions", children: [_jsxs("button", { type: "button", className: "cs-selection-btn cs-selection-btn-format", onClick: handleFormat, title: "Format selected text using AI", children: [_jsx(SparklesIcon, { size: 12 }), _jsx("span", { children: "Format" })] }), _jsx("div", { className: "cs-selection-sep" }), _jsx("button", { type: "button", className: "cs-selection-btn", onClick: handleCut, children: "Cut" }), _jsx("button", { type: "button", className: "cs-selection-btn", onClick: handleCopy, children: "Copy" })] }));
102
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Attribute pencil plugin — uniform inline editing affordance for scene
3
+ * attributes and title-page frontmatter.
4
+ *
5
+ * A pencil widget (visible on hover) sits at the end of every scene heading
6
+ * and at the top-right of the title page card.
7
+ *
8
+ * - Scene heading: opens a dropdown of attribute keys not yet present (plus a
9
+ * custom-key input). Picking one inserts the node via a normal PM
10
+ * transaction and places the cursor in the empty value, so there is no full
11
+ * state rebuild and no cursor jump.
12
+ * - Title page: opens the sectioned title page form (TitlePageDialog), where
13
+ * frontmatter fields are added and edited.
14
+ */
15
+ import { Plugin, PluginKey } from 'prosemirror-state';
16
+ export declare const attrPencilKey: PluginKey<any>;
17
+ export declare function createAttrPencilPlugin(): Plugin;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Attribute pencil plugin — uniform inline editing affordance for scene
3
+ * attributes and title-page frontmatter.
4
+ *
5
+ * A pencil widget (visible on hover) sits at the end of every scene heading
6
+ * and at the top-right of the title page card.
7
+ *
8
+ * - Scene heading: opens a dropdown of attribute keys not yet present (plus a
9
+ * custom-key input). Picking one inserts the node via a normal PM
10
+ * transaction and places the cursor in the empty value, so there is no full
11
+ * state rebuild and no cursor jump.
12
+ * - Title page: opens the sectioned title page form (TitlePageDialog), where
13
+ * frontmatter fields are added and edited.
14
+ */
15
+ import { Plugin, PluginKey, TextSelection } from 'prosemirror-state';
16
+ import { Decoration, DecorationSet } from 'prosemirror-view';
17
+ import { schema } from './schema';
18
+ import { openTitlePageForm } from './editor-bus';
19
+ const SCENE_META_KEYS = ['synopsis', 'status', 'tags', 'location', 'time', 'lang'];
20
+ let openMenu = null;
21
+ function closeMenu() {
22
+ if (openMenu) {
23
+ openMenu.remove();
24
+ openMenu = null;
25
+ document.removeEventListener('mousedown', onDocMousedown);
26
+ }
27
+ }
28
+ function onDocMousedown(e) {
29
+ if (openMenu && !openMenu.contains(e.target))
30
+ closeMenu();
31
+ }
32
+ function showMenu(anchor, options, onPick) {
33
+ closeMenu();
34
+ const menu = document.createElement('div');
35
+ menu.className = 'cs-attr-menu';
36
+ for (const key of options) {
37
+ const item = document.createElement('button');
38
+ item.type = 'button';
39
+ item.className = 'cs-attr-menu-item';
40
+ item.textContent = `+ ${key}`;
41
+ item.addEventListener('mousedown', e => {
42
+ e.preventDefault();
43
+ closeMenu();
44
+ onPick(key);
45
+ });
46
+ menu.appendChild(item);
47
+ }
48
+ const customRow = document.createElement('div');
49
+ customRow.className = 'cs-attr-menu-custom';
50
+ const input = document.createElement('input');
51
+ input.type = 'text';
52
+ input.placeholder = 'custom key…';
53
+ input.addEventListener('keydown', e => {
54
+ e.stopPropagation();
55
+ if (e.key === 'Enter') {
56
+ const k = input.value.trim();
57
+ if (k) {
58
+ closeMenu();
59
+ onPick(k);
60
+ }
61
+ }
62
+ else if (e.key === 'Escape') {
63
+ closeMenu();
64
+ }
65
+ });
66
+ input.addEventListener('mousedown', e => e.stopPropagation());
67
+ customRow.appendChild(input);
68
+ menu.appendChild(customRow);
69
+ const rect = anchor.getBoundingClientRect();
70
+ menu.style.left = `${rect.left}px`;
71
+ menu.style.top = `${rect.bottom + 4}px`;
72
+ document.body.appendChild(menu);
73
+ openMenu = menu;
74
+ // Defer so the click that opened the menu doesn't immediately close it
75
+ setTimeout(() => document.addEventListener('mousedown', onDocMousedown), 0);
76
+ input.focus();
77
+ }
78
+ /** Insert an attribute node and place the cursor inside its (empty) content. */
79
+ function insertNode(view, pos, node) {
80
+ const tr = view.state.tr.insert(pos, node);
81
+ view.dispatch(tr.setSelection(TextSelection.near(tr.doc.resolve(pos + 1))));
82
+ view.focus();
83
+ }
84
+ /**
85
+ * For a scene heading at top-level index `index`, compute which attribute
86
+ * keys already exist and where a new attribute line should be inserted
87
+ * (after the heading, synopsis, and existing metadata lines).
88
+ */
89
+ function sceneAttrContext(view, headingPos) {
90
+ const doc = view.state.doc;
91
+ const $heading = doc.resolve(headingPos);
92
+ const headingNode = doc.nodeAt(headingPos);
93
+ let pos = headingPos + headingNode.nodeSize;
94
+ let index = $heading.index(0) + 1;
95
+ const presentKeys = new Set();
96
+ while (index < doc.childCount) {
97
+ const sibling = doc.child(index);
98
+ if (sibling.type.name === 'scene_metadata') {
99
+ presentKeys.add(sibling.attrs['metaKey']);
100
+ }
101
+ else {
102
+ break;
103
+ }
104
+ pos += sibling.nodeSize;
105
+ index++;
106
+ }
107
+ return { insertPos: pos, presentKeys };
108
+ }
109
+ function makePencil(className, title, onClick) {
110
+ const btn = document.createElement('button');
111
+ btn.type = 'button';
112
+ btn.className = className;
113
+ btn.title = title;
114
+ btn.textContent = '✎';
115
+ btn.contentEditable = 'false';
116
+ btn.addEventListener('mousedown', e => {
117
+ e.preventDefault();
118
+ e.stopPropagation();
119
+ onClick(btn);
120
+ });
121
+ return btn;
122
+ }
123
+ export const attrPencilKey = new PluginKey('attrPencil');
124
+ export function createAttrPencilPlugin() {
125
+ return new Plugin({
126
+ key: attrPencilKey,
127
+ props: {
128
+ decorations(state) {
129
+ const decos = [];
130
+ state.doc.forEach((node, offset) => {
131
+ if (node.type.name === 'scene_heading') {
132
+ // Widget inside the heading, after its text
133
+ decos.push(Decoration.widget(offset + node.nodeSize - 1, view => makePencil('cs-attr-pencil', 'Add scene attribute', btn => {
134
+ const ctx = sceneAttrContext(view, offset);
135
+ const options = SCENE_META_KEYS.filter(k => !ctx.presentKeys.has(k));
136
+ showMenu(btn, options, key => {
137
+ const fresh = sceneAttrContext(view, offset);
138
+ insertNode(view, fresh.insertPos, schema.nodes['scene_metadata'].create({ metaKey: key }));
139
+ });
140
+ }), { side: 1 }));
141
+ }
142
+ if (node.type.name === 'title_page') {
143
+ // Title page fields are added and edited in the title page form; the pencil
144
+ // (top-right of the card, positioned by CSS) opens it.
145
+ decos.push(Decoration.widget(offset + 1, () => makePencil('cs-attr-pencil cs-tp-pencil', 'Edit title page', () => {
146
+ closeMenu();
147
+ openTitlePageForm();
148
+ }), { side: -1, key: 'tp-pencil' }));
149
+ }
150
+ });
151
+ return DecorationSet.create(state.doc, decos);
152
+ },
153
+ },
154
+ view() {
155
+ return { destroy: closeMenu };
156
+ },
157
+ });
158
+ }
@@ -0,0 +1,26 @@
1
+ import { Plugin } from 'prosemirror-state';
2
+ import { DecorationSet } from 'prosemirror-view';
3
+ /**
4
+ * ProseMirror plugin that styles a character cue's trailing extension
5
+ * "(V.O.)"/"(O.S.)" and dual-dialogue "^" marker live, purely from the text
6
+ * pattern — there's no dedicated UI to set these; per the format spec, any
7
+ * content in parentheses at the end of a character line is an extension,
8
+ * and a trailing "^" marks dual dialogue. No node attrs or persisted marks
9
+ * are involved — decorations are recomputed from scratch on every doc
10
+ * change, the same way createLanguageClassPlugin() does for script classes.
11
+ */
12
+ export declare function createCharacterDecorationPlugin(): Plugin<DecorationSet>;
13
+ interface DecorationRanges {
14
+ parenStart?: number;
15
+ parenEnd?: number;
16
+ dualStart?: number;
17
+ dualEnd?: number;
18
+ }
19
+ /**
20
+ * Finds the trailing "(...)"-group span and/or trailing "^" span in a
21
+ * character cue's text, as character offsets into `text`. Either, both, or
22
+ * neither may be present. The "^" is checked (and stripped from
23
+ * consideration) first, since it comes after any extension groups.
24
+ */
25
+ export declare function findCharacterDecorationRanges(text: string): DecorationRanges;
26
+ export {};
@@ -0,0 +1,77 @@
1
+ import { Plugin } from 'prosemirror-state';
2
+ import { Decoration, DecorationSet } from 'prosemirror-view';
3
+ /**
4
+ * ProseMirror plugin that styles a character cue's trailing extension
5
+ * "(V.O.)"/"(O.S.)" and dual-dialogue "^" marker live, purely from the text
6
+ * pattern — there's no dedicated UI to set these; per the format spec, any
7
+ * content in parentheses at the end of a character line is an extension,
8
+ * and a trailing "^" marks dual dialogue. No node attrs or persisted marks
9
+ * are involved — decorations are recomputed from scratch on every doc
10
+ * change, the same way createLanguageClassPlugin() does for script classes.
11
+ */
12
+ export function createCharacterDecorationPlugin() {
13
+ return new Plugin({
14
+ state: {
15
+ init(_config, state) {
16
+ return computeDecorations(state.doc);
17
+ },
18
+ apply(tr, set, _oldState, newState) {
19
+ if (tr.docChanged) {
20
+ return computeDecorations(newState.doc);
21
+ }
22
+ return set;
23
+ },
24
+ },
25
+ props: {
26
+ decorations(state) {
27
+ return this.getState(state);
28
+ },
29
+ },
30
+ });
31
+ }
32
+ /**
33
+ * Finds the trailing "(...)"-group span and/or trailing "^" span in a
34
+ * character cue's text, as character offsets into `text`. Either, both, or
35
+ * neither may be present. The "^" is checked (and stripped from
36
+ * consideration) first, since it comes after any extension groups.
37
+ */
38
+ export function findCharacterDecorationRanges(text) {
39
+ const ranges = {};
40
+ let working = text;
41
+ const dualMatch = /\s(\^)\s*$/.exec(text);
42
+ if (dualMatch) {
43
+ ranges.dualStart = dualMatch.index + dualMatch[0].indexOf('^');
44
+ ranges.dualEnd = ranges.dualStart + 1;
45
+ working = text.slice(0, dualMatch.index);
46
+ }
47
+ const parenMatch = /(\s+\([^()]*\))+\s*$/.exec(working);
48
+ if (parenMatch) {
49
+ const matched = parenMatch[0];
50
+ const leadingWs = matched.length - matched.trimStart().length;
51
+ ranges.parenStart = parenMatch.index + leadingWs;
52
+ ranges.parenEnd = parenMatch.index + matched.trimEnd().length;
53
+ }
54
+ return ranges;
55
+ }
56
+ function computeDecorations(doc) {
57
+ const decorations = [];
58
+ doc.forEach((node, pos) => {
59
+ if (node.type.name !== 'character')
60
+ return;
61
+ const text = node.textContent;
62
+ if (!text)
63
+ return;
64
+ const { parenStart, parenEnd, dualStart, dualEnd } = findCharacterDecorationRanges(text);
65
+ // +1: text content starts one position inside the block node
66
+ const contentStart = pos + 1;
67
+ if (parenStart !== undefined && parenEnd !== undefined) {
68
+ // class passed in both attrs (DOM rendering) and spec (testing/reference) —
69
+ // matches language-class-plugin.ts's convention.
70
+ decorations.push(Decoration.inline(contentStart + parenStart, contentStart + parenEnd, { class: 'cs-character-extension' }, { class: 'cs-character-extension' }));
71
+ }
72
+ if (dualStart !== undefined && dualEnd !== undefined) {
73
+ decorations.push(Decoration.inline(contentStart + dualStart, contentStart + dualEnd, { class: 'cs-character-dual-marker' }, { class: 'cs-character-dual-marker' }));
74
+ }
75
+ });
76
+ return DecorationSet.create(doc, decorations);
77
+ }
@@ -0,0 +1,35 @@
1
+ import type { Node as PmNode } from 'prosemirror-model';
2
+ import type { EditorView, NodeView, ViewMutationRecord } from 'prosemirror-view';
3
+ /**
4
+ * NodeView for character. Renders the name text (ProseMirror-managed) plus
5
+ * an editable extension input — "(V.O.)", "(O.S.)", etc. — on the right.
6
+ *
7
+ * Without this, character used the schema's plain static toDOM spec
8
+ * (['div', {class:'cs-character'}, 0]), whose content hole only renders
9
+ * PM's managed inline content (the name). extension is a real, populated
10
+ * node attr (captured correctly by the parser and carried correctly by
11
+ * sutra-to-prosemirror.ts/prosemirror-to-sutra.ts both ways) but nothing
12
+ * ever painted it to the screen — it was silently invisible. Mirrors
13
+ * SceneHeadingView's pattern for the same kind of "attr needs its own
14
+ * visible, editable-but-outside-the-PM-content-model widget" problem.
15
+ *
16
+ * The extension value is stored (and round-tripped to Sutra text) *with*
17
+ * its parentheses, e.g. "(V.O.)" — matching what the parser's CHARACTER
18
+ * regex captures — so a typed value without parens is auto-wrapped on commit.
19
+ */
20
+ export declare class CharacterView implements NodeView {
21
+ dom: HTMLElement;
22
+ contentDOM: HTMLElement;
23
+ private input;
24
+ private pmView;
25
+ private getPos;
26
+ constructor(node: PmNode, view: EditorView, getPos: () => number | undefined);
27
+ private commit;
28
+ private focusEditorAfter;
29
+ private onInputKeydown;
30
+ private onInputBlur;
31
+ update(node: PmNode): boolean;
32
+ destroy(): void;
33
+ stopEvent(event: Event): boolean;
34
+ ignoreMutation(mutation: ViewMutationRecord): boolean;
35
+ }
@@ -0,0 +1,112 @@
1
+ import { TextSelection } from 'prosemirror-state';
2
+ /**
3
+ * NodeView for character. Renders the name text (ProseMirror-managed) plus
4
+ * an editable extension input — "(V.O.)", "(O.S.)", etc. — on the right.
5
+ *
6
+ * Without this, character used the schema's plain static toDOM spec
7
+ * (['div', {class:'cs-character'}, 0]), whose content hole only renders
8
+ * PM's managed inline content (the name). extension is a real, populated
9
+ * node attr (captured correctly by the parser and carried correctly by
10
+ * sutra-to-prosemirror.ts/prosemirror-to-sutra.ts both ways) but nothing
11
+ * ever painted it to the screen — it was silently invisible. Mirrors
12
+ * SceneHeadingView's pattern for the same kind of "attr needs its own
13
+ * visible, editable-but-outside-the-PM-content-model widget" problem.
14
+ *
15
+ * The extension value is stored (and round-tripped to Sutra text) *with*
16
+ * its parentheses, e.g. "(V.O.)" — matching what the parser's CHARACTER
17
+ * regex captures — so a typed value without parens is auto-wrapped on commit.
18
+ */
19
+ export class CharacterView {
20
+ dom;
21
+ contentDOM;
22
+ input;
23
+ pmView;
24
+ getPos;
25
+ constructor(node, view, getPos) {
26
+ this.pmView = view;
27
+ this.getPos = getPos;
28
+ this.dom = document.createElement('div');
29
+ this.dom.className = 'cs-character';
30
+ this.contentDOM = document.createElement('span');
31
+ this.contentDOM.className = 'cs-character-text';
32
+ this.dom.appendChild(this.contentDOM);
33
+ this.input = document.createElement('input');
34
+ this.input.type = 'text';
35
+ this.input.className = 'cs-character-extension';
36
+ this.input.placeholder = '(V.O.)';
37
+ this.input.value = node.attrs['extension'] ?? '';
38
+ this.input.setAttribute('aria-label', 'Character extension, e.g. (V.O.) or (O.S.)');
39
+ this.input.spellcheck = false;
40
+ this.input.addEventListener('keydown', this.onInputKeydown);
41
+ this.input.addEventListener('blur', this.onInputBlur);
42
+ this.input.addEventListener('mousedown', (e) => e.stopPropagation());
43
+ this.dom.appendChild(this.input);
44
+ }
45
+ commit() {
46
+ const raw = this.input.value.trim();
47
+ let extension = raw === '' ? null : raw;
48
+ if (extension && !(extension.startsWith('(') && extension.endsWith(')'))) {
49
+ extension = `(${extension})`;
50
+ }
51
+ this.input.value = extension ?? '';
52
+ const pos = this.getPos();
53
+ if (pos === undefined)
54
+ return;
55
+ const node = this.pmView.state.doc.nodeAt(pos);
56
+ if (!node)
57
+ return;
58
+ if (node.attrs['extension'] === extension)
59
+ return;
60
+ const tr = this.pmView.state.tr.setNodeMarkup(pos, undefined, { ...node.attrs, extension });
61
+ this.pmView.dispatch(tr);
62
+ }
63
+ focusEditorAfter() {
64
+ const pos = this.getPos();
65
+ if (pos === undefined)
66
+ return;
67
+ const node = this.pmView.state.doc.nodeAt(pos);
68
+ if (!node)
69
+ return;
70
+ const endOfText = pos + node.nodeSize - 1;
71
+ const sel = TextSelection.near(this.pmView.state.doc.resolve(endOfText), -1);
72
+ this.pmView.dispatch(this.pmView.state.tr.setSelection(sel));
73
+ this.pmView.focus();
74
+ }
75
+ onInputKeydown = (e) => {
76
+ if (e.key === 'Escape') {
77
+ e.preventDefault();
78
+ e.stopPropagation();
79
+ this.input.value = this.pmView.state.doc.nodeAt(this.getPos() ?? 0)?.attrs['extension'] ?? '';
80
+ this.pmView.focus();
81
+ return;
82
+ }
83
+ if (e.key === 'Enter' || e.key === 'Tab') {
84
+ e.preventDefault();
85
+ e.stopPropagation();
86
+ this.commit();
87
+ this.focusEditorAfter();
88
+ return;
89
+ }
90
+ };
91
+ onInputBlur = () => {
92
+ this.commit();
93
+ };
94
+ update(node) {
95
+ if (node.type.name !== 'character')
96
+ return false;
97
+ if (document.activeElement !== this.input) {
98
+ this.input.value = node.attrs['extension'] ?? '';
99
+ }
100
+ return true;
101
+ }
102
+ destroy() {
103
+ this.input.removeEventListener('keydown', this.onInputKeydown);
104
+ this.input.removeEventListener('blur', this.onInputBlur);
105
+ }
106
+ stopEvent(event) {
107
+ return event.target === this.input || this.input.contains(event.target);
108
+ }
109
+ ignoreMutation(mutation) {
110
+ return this.input.contains(mutation.target) || mutation.target === this.input;
111
+ }
112
+ }
@@ -0,0 +1,14 @@
1
+ import type { EditorView } from 'prosemirror-view';
2
+ import type { Command } from 'prosemirror-state';
3
+ import type { EditorView as CmEditorView } from '@codemirror/view';
4
+ export declare function setEditorView(view: EditorView | null): void;
5
+ export declare function getEditorView(): EditorView | null;
6
+ export declare function setSourceView(view: CmEditorView | null): void;
7
+ export declare function getSourceView(): CmEditorView | null;
8
+ export declare function runCommand(cmd: Command): void;
9
+ export declare function activeBlockType(): string | null;
10
+ export declare function subscribe(fn: () => void): () => void;
11
+ export declare function emit(): void;
12
+ /** Window event that opens the title page form (TitlePageDialog, mounted by AppShell). */
13
+ export declare const OPEN_TITLE_PAGE_EVENT = "cs:open-title-page";
14
+ export declare function openTitlePageForm(): void;
@@ -0,0 +1,39 @@
1
+ let currentView = null;
2
+ let currentSourceView = null;
3
+ const listeners = new Set();
4
+ export function setEditorView(view) {
5
+ currentView = view;
6
+ emit();
7
+ }
8
+ export function getEditorView() {
9
+ return currentView;
10
+ }
11
+ export function setSourceView(view) {
12
+ currentSourceView = view;
13
+ }
14
+ export function getSourceView() {
15
+ return currentSourceView;
16
+ }
17
+ export function runCommand(cmd) {
18
+ if (!currentView)
19
+ return;
20
+ cmd(currentView.state, currentView.dispatch, currentView);
21
+ currentView.focus();
22
+ }
23
+ export function activeBlockType() {
24
+ if (!currentView)
25
+ return null;
26
+ return currentView.state.selection.$from.parent.type.name;
27
+ }
28
+ export function subscribe(fn) {
29
+ listeners.add(fn);
30
+ return () => listeners.delete(fn);
31
+ }
32
+ export function emit() {
33
+ listeners.forEach(fn => fn());
34
+ }
35
+ /** Window event that opens the title page form (TitlePageDialog, mounted by AppShell). */
36
+ export const OPEN_TITLE_PAGE_EVENT = 'cs:open-title-page';
37
+ export function openTitlePageForm() {
38
+ window.dispatchEvent(new CustomEvent(OPEN_TITLE_PAGE_EVENT));
39
+ }
@@ -0,0 +1,7 @@
1
+ import { Plugin } from 'prosemirror-state';
2
+ import type { PmMatch } from '../findreplace/scroll-to-match';
3
+ /** Called by FindReplace whenever matches or active index change. */
4
+ export declare function setFindHighlights(matches: PmMatch[], activeIndex: number): void;
5
+ /** Called by FindReplace when the panel closes — clears all highlights. */
6
+ export declare function clearFindHighlights(): void;
7
+ export declare function createFindHighlightPlugin(): Plugin;