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