@sutrata/editor 0.1.1 → 0.2.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 (268) hide show
  1. package/README.md +87 -0
  2. package/dist/ai/VoiceConfirmDialog.d.ts +1 -0
  3. package/dist/ai/VoiceConfirmDialog.js +6 -6
  4. package/dist/ai/VoiceToolbar.d.ts +1 -0
  5. package/dist/ai/VoiceToolbar.js +22 -16
  6. package/dist/ai/clean-response.d.ts +7 -0
  7. package/dist/ai/clean-response.js +58 -0
  8. package/dist/ai/format-transcript.d.ts +3 -0
  9. package/dist/ai/format-transcript.js +7 -0
  10. package/dist/ai/prompts.d.ts +8 -1
  11. package/dist/ai/prompts.js +34 -7
  12. package/dist/autocomplete/suggestions.js +1 -1
  13. package/dist/context/DocumentContext.d.ts +28 -4
  14. package/dist/context/DocumentContext.js +67 -37
  15. package/dist/editor/EditorView.js +32 -4
  16. package/dist/editor/SelectionMenu.js +5 -3
  17. package/dist/editor/external-decorations-plugin.d.ts +23 -0
  18. package/dist/editor/external-decorations-plugin.js +151 -0
  19. package/dist/editor/omitted-scene-plugin.d.ts +9 -0
  20. package/dist/editor/omitted-scene-plugin.js +44 -0
  21. package/dist/editor/plugins.js +4 -0
  22. package/dist/editor/private-metadata-plugin.d.ts +10 -0
  23. package/dist/editor/private-metadata-plugin.js +60 -0
  24. package/dist/editor/prosemirror-to-sutra.js +179 -74
  25. package/dist/editor/scene-heading-view.js +2 -0
  26. package/dist/editor/schema.js +69 -12
  27. package/dist/editor/sutra-to-prosemirror.d.ts +2 -0
  28. package/dist/editor/sutra-to-prosemirror.js +32 -34
  29. package/dist/extensions/ai-provider.d.ts +44 -0
  30. package/dist/extensions/ai-provider.js +27 -0
  31. package/dist/extensions/collab.d.ts +21 -0
  32. package/dist/extensions/collab.js +6 -0
  33. package/dist/extensions/command-registry.d.ts +49 -0
  34. package/dist/extensions/command-registry.js +28 -0
  35. package/dist/extensions/decorations.d.ts +48 -0
  36. package/dist/extensions/decorations.js +6 -0
  37. package/dist/extensions/export-registry.d.ts +59 -0
  38. package/dist/extensions/export-registry.js +10 -0
  39. package/dist/extensions/panel-registry.d.ts +30 -0
  40. package/dist/extensions/panel-registry.js +16 -0
  41. package/dist/extensions/registries.d.ts +15 -0
  42. package/dist/extensions/registries.js +12 -0
  43. package/dist/extensions/registry-store.d.ts +26 -0
  44. package/dist/extensions/registry-store.js +35 -0
  45. package/dist/extensions/session.d.ts +29 -0
  46. package/dist/extensions/session.js +20 -0
  47. package/dist/extensions/speech-provider.d.ts +35 -0
  48. package/dist/extensions/speech-provider.js +17 -0
  49. package/dist/file/builtin-exporters.d.ts +9 -0
  50. package/dist/file/builtin-exporters.js +72 -0
  51. package/dist/file/docx-exporter.js +7 -4
  52. package/dist/file/docx-importer.d.ts +9 -0
  53. package/dist/file/docx-importer.js +47 -19
  54. package/dist/file/scene-number.d.ts +5 -0
  55. package/dist/file/scene-number.js +8 -0
  56. package/dist/file/workflow-reports.js +13 -7
  57. package/dist/findreplace/FindReplace.js +5 -2
  58. package/dist/import/AIImportDialog.d.ts +11 -0
  59. package/dist/import/AIImportDialog.js +112 -0
  60. package/dist/import/ai-import.d.ts +51 -0
  61. package/dist/import/ai-import.js +200 -0
  62. package/dist/import/apply-import.d.ts +7 -0
  63. package/dist/import/apply-import.js +35 -0
  64. package/dist/import/extract.d.ts +17 -0
  65. package/dist/import/extract.js +18 -0
  66. package/dist/index.d.ts +17 -2
  67. package/dist/index.js +7 -0
  68. package/dist/locales/bn.json +8 -0
  69. package/dist/locales/en.json +8 -0
  70. package/dist/locales/gu.json +8 -0
  71. package/dist/locales/hi.json +8 -0
  72. package/dist/locales/kn.json +8 -0
  73. package/dist/locales/ml.json +8 -0
  74. package/dist/locales/mr.json +8 -0
  75. package/dist/locales/or.json +8 -0
  76. package/dist/locales/pa.json +8 -0
  77. package/dist/locales/si.json +8 -0
  78. package/dist/locales/ta.json +8 -0
  79. package/dist/locales/te.json +8 -0
  80. package/dist/navigator/SceneNavigator.js +125 -131
  81. package/dist/navigator/StatisticsDialog.js +5 -1
  82. package/dist/navigator/scene-edits.d.ts +18 -0
  83. package/dist/navigator/scene-edits.js +143 -0
  84. package/dist/navigator/scene-list.js +4 -4
  85. package/dist/navigator/scene-numbering.d.ts +37 -0
  86. package/dist/navigator/scene-numbering.js +88 -0
  87. package/dist/settings/SettingsDialog.js +8 -120
  88. package/dist/shell/AppBar.js +21 -2
  89. package/dist/shell/AppShell.js +18 -4
  90. package/dist/shell/ElementToolbar.d.ts +5 -0
  91. package/dist/shell/ElementToolbar.js +35 -25
  92. package/dist/shell/LanguageStatus.js +6 -0
  93. package/dist/shell/StatusBar.js +11 -6
  94. package/dist/shell/builtin-commands.d.ts +10 -0
  95. package/dist/shell/builtin-commands.js +43 -0
  96. package/dist/shell/icons.d.ts +6 -0
  97. package/dist/shell/icons.js +6 -0
  98. package/dist/shell/use-commands.d.ts +7 -0
  99. package/dist/shell/use-commands.js +24 -0
  100. package/dist/shell/use-panel-context.d.ts +3 -0
  101. package/dist/shell/use-panel-context.js +39 -0
  102. package/dist/source/SourceView.js +18 -1
  103. package/dist/styles/screenplay.css +113 -1
  104. package/dist/titlepage/ExportDialog.d.ts +5 -0
  105. package/dist/titlepage/ExportDialog.js +59 -96
  106. package/package.json +32 -5
  107. package/dist/ai/OnboardingDialog.d.ts +0 -6
  108. package/dist/ai/OnboardingDialog.js +0 -115
  109. package/dist/ai/ai-client.d.ts +0 -71
  110. package/dist/ai/ai-client.js +0 -494
  111. package/dist/ai/generated-providers.json +0 -3696
  112. package/dist/ai/voice-service.d.ts +0 -15
  113. package/dist/ai/voice-service.js +0 -81
  114. package/dist/editor/character-view.d.ts +0 -35
  115. package/dist/editor/character-view.js +0 -112
  116. package/dist/editor/transliterate-input.d.ts +0 -10
  117. package/dist/editor/transliterate-input.js +0 -88
  118. package/dist/extensions/types.d.ts +0 -73
  119. package/dist/extensions/types.js +0 -12
  120. package/scripts/copy-assets.mjs +0 -16
  121. package/scripts/generate-providers.mjs +0 -127
  122. package/src/ai/OnboardingDialog.tsx +0 -390
  123. package/src/ai/VoiceConfirmDialog.tsx +0 -137
  124. package/src/ai/VoiceToolbar.tsx +0 -321
  125. package/src/ai/ai-client.ts +0 -596
  126. package/src/ai/commands.ts +0 -164
  127. package/src/ai/generated-providers.json +0 -3696
  128. package/src/ai/prompts.ts +0 -62
  129. package/src/ai/voice-service.ts +0 -96
  130. package/src/autocomplete/AutocompletePlugin.ts +0 -21
  131. package/src/autocomplete/suggestions.ts +0 -147
  132. package/src/context/DocumentContext.tsx +0 -648
  133. package/src/desktop-api.d.ts +0 -40
  134. package/src/editor/EditorView.tsx +0 -82
  135. package/src/editor/SelectionMenu.tsx +0 -152
  136. package/src/editor/attr-pencil-plugin.ts +0 -172
  137. package/src/editor/character-decoration-plugin.ts +0 -104
  138. package/src/editor/editor-bus.ts +0 -51
  139. package/src/editor/find-highlight-plugin.ts +0 -68
  140. package/src/editor/frontmatter-field-view.ts +0 -52
  141. package/src/editor/frontmatter-field.ts +0 -115
  142. package/src/editor/grapheme-cursor.ts +0 -99
  143. package/src/editor/ime-plugin.ts +0 -85
  144. package/src/editor/insert-helper.ts +0 -43
  145. package/src/editor/keymap.ts +0 -124
  146. package/src/editor/language-class-plugin.ts +0 -91
  147. package/src/editor/plugins.ts +0 -26
  148. package/src/editor/prosemirror-to-sutra.ts +0 -192
  149. package/src/editor/scene-heading-view.ts +0 -147
  150. package/src/editor/schema.ts +0 -156
  151. package/src/editor/sutra-to-prosemirror.ts +0 -156
  152. package/src/extensions/storage-adapter.ts +0 -65
  153. package/src/extensions/types.ts +0 -60
  154. package/src/file/docx-exporter.ts +0 -585
  155. package/src/file/docx-importer.ts +0 -313
  156. package/src/file/print-paginator.ts +0 -301
  157. package/src/file/templates.ts +0 -73
  158. package/src/file/workflow-reports.ts +0 -820
  159. package/src/findreplace/FindReplace.tsx +0 -242
  160. package/src/findreplace/find-engine.ts +0 -97
  161. package/src/findreplace/scroll-to-match.ts +0 -89
  162. package/src/fonts/tofu-detect.ts +0 -53
  163. package/src/i18n/LanguageContext.tsx +0 -207
  164. package/src/i18n/language-detect.ts +0 -73
  165. package/src/i18n/useTranslation.tsx +0 -74
  166. package/src/index.ts +0 -27
  167. package/src/locales/bn.json +0 -143
  168. package/src/locales/en.json +0 -143
  169. package/src/locales/gu.json +0 -143
  170. package/src/locales/hi.json +0 -143
  171. package/src/locales/kn.json +0 -143
  172. package/src/locales/ml.json +0 -143
  173. package/src/locales/mr.json +0 -143
  174. package/src/locales/or.json +0 -143
  175. package/src/locales/pa.json +0 -143
  176. package/src/locales/si.json +0 -143
  177. package/src/locales/ta.json +0 -143
  178. package/src/locales/te.json +0 -143
  179. package/src/navigator/SceneNavigator.tsx +0 -844
  180. package/src/navigator/StatisticsDialog.tsx +0 -249
  181. package/src/navigator/scene-list.ts +0 -84
  182. package/src/settings/SettingsDialog.tsx +0 -303
  183. package/src/shell/AppBar.tsx +0 -354
  184. package/src/shell/AppShell.tsx +0 -71
  185. package/src/shell/ConfirmDialog.tsx +0 -71
  186. package/src/shell/ElementToolbar.tsx +0 -175
  187. package/src/shell/LanguageStatus.tsx +0 -101
  188. package/src/shell/StatusBar.tsx +0 -135
  189. package/src/shell/SutrataLogo.tsx +0 -89
  190. package/src/shell/Toast.tsx +0 -46
  191. package/src/shell/ToolbarButton.tsx +0 -30
  192. package/src/shell/VersionHistory.tsx +0 -212
  193. package/src/shell/Watermark.tsx +0 -9
  194. package/src/shell/fonts/fonts.css +0 -1133
  195. package/src/shell/icons.tsx +0 -335
  196. package/src/shell/toolbar-actions.ts +0 -59
  197. package/src/source/SourceView.tsx +0 -111
  198. package/src/source/find-highlight-source.ts +0 -57
  199. package/src/source/sutra-lang.ts +0 -121
  200. package/src/spellcheck/dictionaries.ts +0 -21
  201. package/src/spellcheck/hunspell.ts +0 -110
  202. package/src/spellcheck/spellcheck-plugin.ts +0 -83
  203. package/src/styles/StyleDialog.tsx +0 -148
  204. package/src/styles/builtin-styles.ts +0 -139
  205. package/src/styles/css-adapter.ts +0 -76
  206. package/src/styles/docx-adapter.ts +0 -109
  207. package/src/styles/embed-fonts.ts +0 -81
  208. package/src/styles/pagination.ts +0 -27
  209. package/src/styles/print-adapter.ts +0 -288
  210. package/src/styles/registry.ts +0 -20
  211. package/src/styles/screenplay.css +0 -3006
  212. package/src/styles/script-ranges.ts +0 -17
  213. package/src/styles/types.ts +0 -104
  214. package/src/styles/validate.ts +0 -114
  215. package/src/templates/templates.ts +0 -194
  216. package/src/titlepage/ExportDialog.tsx +0 -494
  217. package/src/titlepage/TitlePageDialog.tsx +0 -312
  218. package/src/titlepage/frontmatter-form.ts +0 -280
  219. package/src/titlepage/languages.ts +0 -37
  220. package/src/types.ts +0 -7
  221. package/tests/AppBar.test.tsx +0 -35
  222. package/tests/AppShell.test.tsx +0 -22
  223. package/tests/ElementToolbar.test.tsx +0 -36
  224. package/tests/LanguageStatus.test.tsx +0 -129
  225. package/tests/SelectionMenu.test.tsx +0 -71
  226. package/tests/ToolbarButton.test.tsx +0 -24
  227. package/tests/ai-onboarding.test.ts +0 -102
  228. package/tests/ai-status.test.tsx +0 -72
  229. package/tests/ai-structured.test.ts +0 -94
  230. package/tests/autocomplete/AutocompletePlugin.test.ts +0 -20
  231. package/tests/autocomplete/suggestions.test.ts +0 -106
  232. package/tests/document-context.test.tsx +0 -565
  233. package/tests/docx-importer.test.ts +0 -160
  234. package/tests/editor/character-decoration-plugin.test.ts +0 -101
  235. package/tests/editor/character-extension.test.ts +0 -44
  236. package/tests/editor/grapheme-cursor.test.ts +0 -118
  237. package/tests/editor/ime-plugin.test.ts +0 -72
  238. package/tests/editor/inline-note-bridge.test.ts +0 -32
  239. package/tests/editor/language-class-plugin.test.ts +0 -265
  240. package/tests/editor/title-page.test.ts +0 -124
  241. package/tests/embed-fonts.test.ts +0 -17
  242. package/tests/exporters.test.ts +0 -365
  243. package/tests/findreplace/FindReplace.test.tsx +0 -391
  244. package/tests/findreplace/find-engine.test.ts +0 -86
  245. package/tests/findreplace/scroll-to-match.test.ts +0 -50
  246. package/tests/fonts/tofu-detect.test.ts +0 -122
  247. package/tests/i18n/LanguageContext.test.tsx +0 -146
  248. package/tests/i18n/language-detect.test.ts +0 -54
  249. package/tests/i18n/locale-parity.test.ts +0 -25
  250. package/tests/i18n/useTranslation.test.tsx +0 -45
  251. package/tests/keymap.test.ts +0 -18
  252. package/tests/navigator/SceneNavigator.test.tsx +0 -177
  253. package/tests/navigator/StatisticsDialog.test.tsx +0 -63
  254. package/tests/navigator/scene-list.test.ts +0 -71
  255. package/tests/print-paginator.test.ts +0 -82
  256. package/tests/shell/VersionHistory.test.tsx +0 -129
  257. package/tests/spellcheck/hunspell.test.ts +0 -82
  258. package/tests/spellcheck/spellcheck-plugin.test.ts +0 -56
  259. package/tests/styles.test.ts +0 -315
  260. package/tests/templates/templates.test.ts +0 -83
  261. package/tests/titlepage/TitlePageDialog.test.tsx +0 -90
  262. package/tests/titlepage/apply-title-page.test.ts +0 -61
  263. package/tests/titlepage/frontmatter-form.test.ts +0 -115
  264. package/tests/toolbar-actions.test.ts +0 -93
  265. package/tests/voice-commands.test.ts +0 -58
  266. package/tsconfig.json +0 -17
  267. package/vitest.config.ts +0 -11
  268. package/vitest.setup.ts +0 -65
package/src/ai/prompts.ts DELETED
@@ -1,62 +0,0 @@
1
- export const VOICE_FORMAT_SYSTEM_PROMPT = `You are Sutrata's AI screenplay formatter.
2
- Your task is to take a raw voice dictation transcript (which may contain explicit formatting keywords like "scene", "character", "dialogue", "parenthetical", "transition") and convert it into valid Sutra screenplay format.
3
-
4
- Sutra syntax rules:
5
- 1. Scene Headings: Start with "##" (e.g., "## INT. COFFEE SHOP - DAY"). If a scene number/ID is specified (e.g., "scene #2", "scene 2", or "scene 3A"), append it as a Sutra attribute ID at the end of the scene heading, like "{#2}" or "{#3A}" (e.g., "## EXT. SWIMMING POOL - DAY {#2}").
6
- 2. Characters: All-caps, alone on a line (e.g., "RAJ").
7
- 3. Parentheticals: In parentheses, on a line by itself under character (e.g., "(whispering)").
8
- 4. Dialogue: Indented text directly following a character or parenthetical.
9
- 5. Transitions: In uppercase, prefixed with ">" or ending with "TO:" (e.g., "> CUT TO:").
10
- 6. Lyrics: Prefixed with "~" (e.g., "~ Sing a song").
11
- 7. Action: Standard descriptive paragraph.
12
-
13
- CRITICAL INSTRUCTIONS:
14
- - Preserve Indic/Unicode characters and mixed-language code-switching exactly as transcribed. Do NOT translate non-English text to English.
15
- - Output ONLY the formatted Sutra text.
16
- - Do NOT include any explanations, steps, reasoning, bullet points, meta-commentary, or introductory/concluding text.
17
- - Do NOT output headings, drafts, prefixes, or suffixes.
18
- - Do NOT wrap in markdown code blocks like \`\`\`cine.
19
- - The output must contain nothing but the final formatted Sutra screenplay lines.
20
- `;
21
-
22
- /**
23
- * Combined synopsis+duration prompt. Merged into one call (and one JSON
24
- * response) instead of two separate requests to halve per-scene LLM cost.
25
- * Paired with SCENE_METADATA_SCHEMA — the response format instructions
26
- * (JSON-only, matching the schema) are appended by callAIStructured, not
27
- * hardcoded here, so the same prompt works across providers.
28
- */
29
- export const SCENE_METADATA_PROMPT = `[INSTRUCTION]
30
- Analyze the screenplay scene provided below and produce two fields:
31
- 1. "synopsis": A concise, 1-2 sentence synopsis of the main action and conflict.
32
- 2. "duration": The estimated onscreen duration of the scene, in "MM:SS" format (e.g. 01:30).
33
-
34
- [RULES FOR "synopsis"]
35
- 1. Match the language of the scene: If the scene is in Tamil, the synopsis MUST be written in Tamil. If the scene is in Hindi, the synopsis MUST be written in Hindi, etc. Do NOT translate to English.
36
- 2. Do NOT include any explanations, definitions, drafts, translations, or bullet points.
37
- 3. Do NOT include headings, prefixes (such as "Synopsis:" or "Draft:"), or suffixes.
38
- 4. Absolutely no English introduction, reasoning, or meta-commentary.
39
-
40
- [RULES FOR "duration"]
41
- 1. Dialogue speed: ~140 words per minute.
42
- 2. Action lines: ~4 seconds per line of description.
43
- 3. The value must be ONLY the estimated duration in "MM:SS" format — no calculations, explanations, breakdown, or notes.
44
- `;
45
-
46
- /** JSON schema for SCENE_METADATA_PROMPT's response — see callAIStructured. */
47
- export const SCENE_METADATA_SCHEMA = {
48
- type: 'object',
49
- properties: {
50
- synopsis: {
51
- type: 'string',
52
- description: "1-2 sentence synopsis of the scene's main action and conflict, in the scene's own language/script",
53
- },
54
- duration: {
55
- type: 'string',
56
- description: 'Estimated onscreen duration of the scene as MM:SS',
57
- },
58
- },
59
- required: ['synopsis', 'duration'],
60
- additionalProperties: false,
61
- } as const;
62
-
@@ -1,96 +0,0 @@
1
- export interface VoiceServiceCallbacks {
2
- onResult: (text: string, isFinal: boolean) => void;
3
- onError: (error: string) => void;
4
- onEnd: () => void;
5
- }
6
-
7
- export class VoiceService {
8
- private recognition: any = null;
9
- private isListening: boolean = false;
10
-
11
- constructor() {
12
- const SpeechRecognition =
13
- (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;
14
- if (SpeechRecognition) {
15
- this.recognition = new SpeechRecognition();
16
- this.recognition.continuous = true;
17
- this.recognition.interimResults = true;
18
- }
19
- }
20
-
21
- public isSupported(): boolean {
22
- return !!this.recognition;
23
- }
24
-
25
- public start(lang: string, callbacks: VoiceServiceCallbacks) {
26
- if (!this.recognition) {
27
- callbacks.onError('Web Speech API is not supported in this browser.');
28
- return;
29
- }
30
- if (this.isListening) {
31
- return;
32
- }
33
-
34
- this.recognition.lang = lang;
35
- this.isListening = true;
36
-
37
- this.recognition.onresult = (event: any) => {
38
- let interimTranscript = '';
39
- let finalTranscript = '';
40
-
41
- for (let i = event.resultIndex; i < event.results.length; ++i) {
42
- if (event.results[i].isFinal) {
43
- finalTranscript += event.results[i][0].transcript;
44
- } else {
45
- interimTranscript += event.results[i][0].transcript;
46
- }
47
- }
48
-
49
- const text = finalTranscript || interimTranscript;
50
- callbacks.onResult(text, finalTranscript.length > 0);
51
- };
52
-
53
- this.recognition.onerror = (event: any) => {
54
- // Ignore 'no-speech' error to prevent crashing, just log it
55
- if (event.error === 'no-speech') {
56
- console.warn('Speech recognition: no speech detected');
57
- return;
58
- }
59
- callbacks.onError(event.error || 'Speech recognition error');
60
- };
61
-
62
- this.recognition.onend = () => {
63
- this.isListening = false;
64
- callbacks.onEnd();
65
- };
66
-
67
- try {
68
- this.recognition.start();
69
- } catch (e: any) {
70
- callbacks.onError(e.message || String(e));
71
- }
72
- }
73
-
74
- public stop() {
75
- if (this.recognition && this.isListening) {
76
- try {
77
- this.recognition.stop();
78
- } catch (e) {
79
- console.error('Failed to stop recognition:', e);
80
- }
81
- this.isListening = false;
82
- }
83
- }
84
-
85
- public abort() {
86
- if (this.recognition && this.isListening) {
87
- try {
88
- this.recognition.abort();
89
- } catch (e) {
90
- console.error('Failed to abort recognition:', e);
91
- }
92
- this.isListening = false;
93
- }
94
- }
95
- }
96
- export const voiceServiceInstance = new VoiceService();
@@ -1,21 +0,0 @@
1
- import { Plugin } from 'prosemirror-state'
2
- import { EditorView } from 'prosemirror-view'
3
-
4
- // Character-cue autocomplete was disabled: the dropdown interrupted typing
5
- // more than it helped. This plugin is kept as a no-op registration point
6
- // in case popup-based suggestions are revisited for other cue types.
7
- export function createAutocompletePlugin() {
8
- return new Plugin({
9
- view(_editorView: EditorView) {
10
- return {
11
- update(_view: EditorView) {},
12
- destroy() {},
13
- }
14
- },
15
- props: {
16
- handleKeyDown(_view: EditorView, _event: KeyboardEvent): boolean {
17
- return false
18
- },
19
- },
20
- })
21
- }
@@ -1,147 +0,0 @@
1
- export interface Candidate {
2
- label: string
3
- type: 'character' | 'location' | 'transition' | 'metadata_key'
4
- }
5
-
6
- const PREDEFINED_TRANSITIONS = [
7
- 'CUT TO:', 'FADE IN:', 'FADE OUT:', 'DISSOLVE TO:', 'SMASH CUT TO:',
8
- 'MATCH CUT TO:', 'WIPE TO:', 'IRIS IN:', 'IRIS OUT:',
9
- ]
10
-
11
- const RESERVED_METADATA_KEYS = [
12
- 'status', 'tags', 'location', 'time', 'number', 'lang', 'mood',
13
- 'arc', 'note', 'locked',
14
- ]
15
-
16
- /**
17
- * Extract character candidates from screenplay text.
18
- * Sources: @CHARACTER cues in the text and {#characters} registry blocks.
19
- */
20
- export function buildCharacterCandidates(text: string): Candidate[] {
21
- const seen = new Set<string>()
22
- const candidates: Candidate[] = []
23
-
24
- // Scan for @CHARACTER lines (character block sigil)
25
- const lines = text.split('\n')
26
- for (const line of lines) {
27
- const trimmed = line.trim()
28
- if (trimmed.startsWith('@')) {
29
- const name = trimmed.slice(1).trim().toUpperCase()
30
- if (name && !seen.has(name)) {
31
- seen.add(name)
32
- candidates.push({ label: name, type: 'character' })
33
- }
34
- }
35
- }
36
-
37
- // Also scan for names in {#characters} registry blocks
38
- const charRegistryRegex = /\{#characters\}([\s\S]*?)\{\/characters\}/g
39
- let match
40
- while ((match = charRegistryRegex.exec(text)) !== null) {
41
- const block = match[1]
42
- if (block) {
43
- for (const line of block.split('\n')) {
44
- const name = line.trim().toUpperCase()
45
- if (name && !seen.has(name)) {
46
- seen.add(name)
47
- candidates.push({ label: name, type: 'character' })
48
- }
49
- }
50
- }
51
- }
52
-
53
- return candidates.sort((a, b) => a.label.localeCompare(b.label))
54
- }
55
-
56
- /**
57
- * Extract location candidates from scene headings and & location: metadata.
58
- */
59
- export function buildLocationCandidates(text: string): Candidate[] {
60
- const seen = new Set<string>()
61
- const candidates: Candidate[] = []
62
-
63
- const lines = text.split('\n')
64
- for (const line of lines) {
65
- const trimmed = line.trim()
66
-
67
- // Scene headings: ## INT. LIVING ROOM - DAY → location = "INT. LIVING ROOM"
68
- if (trimmed.startsWith('## ')) {
69
- const heading = trimmed.slice(3).trim()
70
- // Extract location part (before the dash)
71
- const dashIdx = heading.indexOf(' - ')
72
- const loc = dashIdx >= 0 ? heading.slice(0, dashIdx).trim() : heading
73
- if (loc && !seen.has(loc)) {
74
- seen.add(loc)
75
- candidates.push({ label: loc, type: 'location' })
76
- }
77
- }
78
-
79
- // & location: metadata
80
- if (trimmed.startsWith('& location:')) {
81
- const loc = trimmed.replace('& location:', '').trim()
82
- if (loc && !seen.has(loc)) {
83
- seen.add(loc)
84
- candidates.push({ label: loc, type: 'location' })
85
- }
86
- }
87
- }
88
-
89
- return candidates.sort((a, b) => a.label.localeCompare(b.label))
90
- }
91
-
92
- /**
93
- * Build transition candidates: predefined + used in text.
94
- */
95
- export function buildTransitionCandidates(text: string): Candidate[] {
96
- const seen = new Set<string>(PREDEFINED_TRANSITIONS)
97
- const candidates: Candidate[] = PREDEFINED_TRANSITIONS.map(t => ({ label: t, type: 'transition' as const }))
98
-
99
- const lines = text.split('\n')
100
- for (const line of lines) {
101
- const trimmed = line.trim()
102
- // Transition lines start with >>
103
- if (trimmed.startsWith('>> ')) {
104
- const trans = trimmed.slice(3).trim()
105
- if (trans && !seen.has(trans)) {
106
- seen.add(trans)
107
- candidates.push({ label: trans, type: 'transition' })
108
- }
109
- }
110
- }
111
-
112
- return candidates
113
- }
114
-
115
- /**
116
- * Build metadata key candidates: reserved + used in text.
117
- */
118
- export function buildMetadataKeyCandidates(text: string): Candidate[] {
119
- const seen = new Set<string>(RESERVED_METADATA_KEYS)
120
- const candidates: Candidate[] = RESERVED_METADATA_KEYS.map(k => ({ label: k, type: 'metadata_key' as const }))
121
-
122
- const lines = text.split('\n')
123
- for (const line of lines) {
124
- const trimmed = line.trim()
125
- if (trimmed.startsWith('& ')) {
126
- const keyMatch = /^& ([a-zA-Z_]+):/.exec(trimmed)
127
- if (keyMatch) {
128
- const key = keyMatch[1]
129
- if (key && !seen.has(key)) {
130
- seen.add(key)
131
- candidates.push({ label: key, type: 'metadata_key' })
132
- }
133
- }
134
- }
135
- }
136
-
137
- return candidates
138
- }
139
-
140
- /**
141
- * Filter candidates by prefix (case-insensitive).
142
- */
143
- export function filterCandidates(candidates: Candidate[], prefix: string): Candidate[] {
144
- if (!prefix) return candidates
145
- const lower = prefix.toLowerCase()
146
- return candidates.filter(c => c.label.toLowerCase().startsWith(lower))
147
- }