@sutrata/editor 0.1.0 → 0.1.2

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 (152) hide show
  1. package/README.md +87 -0
  2. package/dist/ai/generated-providers.json +1 -1
  3. package/package.json +36 -9
  4. package/scripts/copy-assets.mjs +0 -16
  5. package/scripts/generate-providers.mjs +0 -127
  6. package/src/ai/OnboardingDialog.tsx +0 -390
  7. package/src/ai/VoiceConfirmDialog.tsx +0 -137
  8. package/src/ai/VoiceToolbar.tsx +0 -321
  9. package/src/ai/ai-client.ts +0 -596
  10. package/src/ai/commands.ts +0 -164
  11. package/src/ai/generated-providers.json +0 -3696
  12. package/src/ai/prompts.ts +0 -62
  13. package/src/ai/voice-service.ts +0 -96
  14. package/src/autocomplete/AutocompletePlugin.ts +0 -21
  15. package/src/autocomplete/suggestions.ts +0 -147
  16. package/src/context/DocumentContext.tsx +0 -648
  17. package/src/desktop-api.d.ts +0 -40
  18. package/src/editor/EditorView.tsx +0 -82
  19. package/src/editor/SelectionMenu.tsx +0 -152
  20. package/src/editor/attr-pencil-plugin.ts +0 -172
  21. package/src/editor/character-decoration-plugin.ts +0 -104
  22. package/src/editor/editor-bus.ts +0 -51
  23. package/src/editor/find-highlight-plugin.ts +0 -68
  24. package/src/editor/frontmatter-field-view.ts +0 -52
  25. package/src/editor/frontmatter-field.ts +0 -115
  26. package/src/editor/grapheme-cursor.ts +0 -99
  27. package/src/editor/ime-plugin.ts +0 -85
  28. package/src/editor/insert-helper.ts +0 -43
  29. package/src/editor/keymap.ts +0 -124
  30. package/src/editor/language-class-plugin.ts +0 -91
  31. package/src/editor/plugins.ts +0 -26
  32. package/src/editor/prosemirror-to-sutra.ts +0 -192
  33. package/src/editor/scene-heading-view.ts +0 -147
  34. package/src/editor/schema.ts +0 -156
  35. package/src/editor/sutra-to-prosemirror.ts +0 -156
  36. package/src/extensions/storage-adapter.ts +0 -65
  37. package/src/extensions/types.ts +0 -60
  38. package/src/file/docx-exporter.ts +0 -585
  39. package/src/file/docx-importer.ts +0 -313
  40. package/src/file/print-paginator.ts +0 -301
  41. package/src/file/templates.ts +0 -73
  42. package/src/file/workflow-reports.ts +0 -820
  43. package/src/findreplace/FindReplace.tsx +0 -242
  44. package/src/findreplace/find-engine.ts +0 -97
  45. package/src/findreplace/scroll-to-match.ts +0 -89
  46. package/src/fonts/tofu-detect.ts +0 -53
  47. package/src/i18n/LanguageContext.tsx +0 -207
  48. package/src/i18n/language-detect.ts +0 -73
  49. package/src/i18n/useTranslation.tsx +0 -74
  50. package/src/index.ts +0 -27
  51. package/src/locales/bn.json +0 -143
  52. package/src/locales/en.json +0 -143
  53. package/src/locales/gu.json +0 -143
  54. package/src/locales/hi.json +0 -143
  55. package/src/locales/kn.json +0 -143
  56. package/src/locales/ml.json +0 -143
  57. package/src/locales/mr.json +0 -143
  58. package/src/locales/or.json +0 -143
  59. package/src/locales/pa.json +0 -143
  60. package/src/locales/si.json +0 -143
  61. package/src/locales/ta.json +0 -143
  62. package/src/locales/te.json +0 -143
  63. package/src/navigator/SceneNavigator.tsx +0 -844
  64. package/src/navigator/StatisticsDialog.tsx +0 -249
  65. package/src/navigator/scene-list.ts +0 -84
  66. package/src/settings/SettingsDialog.tsx +0 -303
  67. package/src/shell/AppBar.tsx +0 -354
  68. package/src/shell/AppShell.tsx +0 -71
  69. package/src/shell/ConfirmDialog.tsx +0 -71
  70. package/src/shell/ElementToolbar.tsx +0 -175
  71. package/src/shell/LanguageStatus.tsx +0 -101
  72. package/src/shell/StatusBar.tsx +0 -135
  73. package/src/shell/SutrataLogo.tsx +0 -89
  74. package/src/shell/Toast.tsx +0 -46
  75. package/src/shell/ToolbarButton.tsx +0 -30
  76. package/src/shell/VersionHistory.tsx +0 -212
  77. package/src/shell/Watermark.tsx +0 -9
  78. package/src/shell/fonts/fonts.css +0 -1133
  79. package/src/shell/icons.tsx +0 -335
  80. package/src/shell/toolbar-actions.ts +0 -59
  81. package/src/source/SourceView.tsx +0 -111
  82. package/src/source/find-highlight-source.ts +0 -57
  83. package/src/source/sutra-lang.ts +0 -121
  84. package/src/spellcheck/dictionaries.ts +0 -21
  85. package/src/spellcheck/hunspell.ts +0 -110
  86. package/src/spellcheck/spellcheck-plugin.ts +0 -83
  87. package/src/styles/StyleDialog.tsx +0 -148
  88. package/src/styles/builtin-styles.ts +0 -139
  89. package/src/styles/css-adapter.ts +0 -76
  90. package/src/styles/docx-adapter.ts +0 -109
  91. package/src/styles/embed-fonts.ts +0 -81
  92. package/src/styles/pagination.ts +0 -27
  93. package/src/styles/print-adapter.ts +0 -288
  94. package/src/styles/registry.ts +0 -20
  95. package/src/styles/screenplay.css +0 -3006
  96. package/src/styles/script-ranges.ts +0 -17
  97. package/src/styles/types.ts +0 -104
  98. package/src/styles/validate.ts +0 -114
  99. package/src/templates/templates.ts +0 -194
  100. package/src/titlepage/ExportDialog.tsx +0 -494
  101. package/src/titlepage/TitlePageDialog.tsx +0 -312
  102. package/src/titlepage/frontmatter-form.ts +0 -280
  103. package/src/titlepage/languages.ts +0 -37
  104. package/src/types.ts +0 -7
  105. package/tests/AppBar.test.tsx +0 -35
  106. package/tests/AppShell.test.tsx +0 -22
  107. package/tests/ElementToolbar.test.tsx +0 -36
  108. package/tests/LanguageStatus.test.tsx +0 -129
  109. package/tests/SelectionMenu.test.tsx +0 -71
  110. package/tests/ToolbarButton.test.tsx +0 -24
  111. package/tests/ai-onboarding.test.ts +0 -102
  112. package/tests/ai-status.test.tsx +0 -72
  113. package/tests/ai-structured.test.ts +0 -94
  114. package/tests/autocomplete/AutocompletePlugin.test.ts +0 -20
  115. package/tests/autocomplete/suggestions.test.ts +0 -106
  116. package/tests/document-context.test.tsx +0 -565
  117. package/tests/docx-importer.test.ts +0 -160
  118. package/tests/editor/character-decoration-plugin.test.ts +0 -101
  119. package/tests/editor/character-extension.test.ts +0 -44
  120. package/tests/editor/grapheme-cursor.test.ts +0 -118
  121. package/tests/editor/ime-plugin.test.ts +0 -72
  122. package/tests/editor/inline-note-bridge.test.ts +0 -32
  123. package/tests/editor/language-class-plugin.test.ts +0 -265
  124. package/tests/editor/title-page.test.ts +0 -124
  125. package/tests/embed-fonts.test.ts +0 -17
  126. package/tests/exporters.test.ts +0 -365
  127. package/tests/findreplace/FindReplace.test.tsx +0 -391
  128. package/tests/findreplace/find-engine.test.ts +0 -86
  129. package/tests/findreplace/scroll-to-match.test.ts +0 -50
  130. package/tests/fonts/tofu-detect.test.ts +0 -122
  131. package/tests/i18n/LanguageContext.test.tsx +0 -146
  132. package/tests/i18n/language-detect.test.ts +0 -54
  133. package/tests/i18n/locale-parity.test.ts +0 -25
  134. package/tests/i18n/useTranslation.test.tsx +0 -45
  135. package/tests/keymap.test.ts +0 -18
  136. package/tests/navigator/SceneNavigator.test.tsx +0 -177
  137. package/tests/navigator/StatisticsDialog.test.tsx +0 -63
  138. package/tests/navigator/scene-list.test.ts +0 -71
  139. package/tests/print-paginator.test.ts +0 -82
  140. package/tests/shell/VersionHistory.test.tsx +0 -129
  141. package/tests/spellcheck/hunspell.test.ts +0 -82
  142. package/tests/spellcheck/spellcheck-plugin.test.ts +0 -56
  143. package/tests/styles.test.ts +0 -315
  144. package/tests/templates/templates.test.ts +0 -83
  145. package/tests/titlepage/TitlePageDialog.test.tsx +0 -90
  146. package/tests/titlepage/apply-title-page.test.ts +0 -61
  147. package/tests/titlepage/frontmatter-form.test.ts +0 -115
  148. package/tests/toolbar-actions.test.ts +0 -93
  149. package/tests/voice-commands.test.ts +0 -58
  150. package/tsconfig.json +0 -17
  151. package/vitest.config.ts +0 -11
  152. package/vitest.setup.ts +0 -65
@@ -1,90 +0,0 @@
1
- import { useEffect } from 'react'
2
- import { render, screen, fireEvent } from '@testing-library/react'
3
- import { DocumentProvider, useDocument } from '../../src/context/DocumentContext'
4
- import { TranslationProvider } from '../../src/i18n/useTranslation'
5
- import { TitlePageDialog } from '../../src/titlepage/TitlePageDialog'
6
-
7
- const SRC = `---
8
- title: The Last Train
9
- date: June 2026
10
- lang: hi
11
- ---
12
-
13
- ## INT. STATION - NIGHT
14
- `
15
-
16
- let latestText = ''
17
-
18
- function Harness({ onClose }: { onClose: () => void }) {
19
- const { text, setText, setMode } = useDocument()
20
- useEffect(() => { setMode('source'); setText(SRC) }, []) // eslint-disable-line react-hooks/exhaustive-deps
21
- latestText = text
22
- return text === SRC || text.includes('title:') ? <TitlePageDialog onClose={onClose} /> : null
23
- }
24
-
25
- function renderDialog() {
26
- const onClose = vi.fn()
27
- render(
28
- <TranslationProvider>
29
- <DocumentProvider>
30
- <Harness onClose={onClose} />
31
- </DocumentProvider>
32
- </TranslationProvider>,
33
- )
34
- return onClose
35
- }
36
-
37
- describe('TitlePageDialog', () => {
38
- it('shows a tab per section and opens on Title & Story, pre-filled from frontmatter', () => {
39
- renderDialog()
40
- for (const name of ['Title & Story', 'Credits', 'Draft', 'Contact & Rights', 'Document Settings', 'Other Fields']) {
41
- expect(screen.getByRole('tab', { name })).toBeInTheDocument()
42
- }
43
- expect(screen.getByRole('tab', { name: 'Title & Story' })).toHaveAttribute('aria-selected', 'true')
44
- expect(screen.getByLabelText('Title')).toHaveValue('The Last Train')
45
- })
46
-
47
- it('switches panels on tab click, hiding fields from other tabs', () => {
48
- // The inactive panel's fields stay in the DOM (form state must survive a tab
49
- // switch), but the panel is hidden via the `hidden` attribute — jest-dom's
50
- // toBeVisible() honors that (and ancestor `hidden`), so it's used instead of a
51
- // presence check.
52
- renderDialog()
53
- expect(screen.getByLabelText('Primary language')).not.toBeVisible()
54
- fireEvent.click(screen.getByRole('tab', { name: 'Document Settings' }))
55
- expect(screen.getByRole('tab', { name: 'Document Settings' })).toHaveAttribute('aria-selected', 'true')
56
- expect(screen.getByLabelText('Primary language')).toBeVisible()
57
- expect(screen.getByLabelText('Primary language')).toHaveValue('hi')
58
- expect(screen.getByLabelText('Title')).not.toBeVisible()
59
- })
60
-
61
- it('keeps a free-text date as text and offers the date picker', () => {
62
- renderDialog()
63
- fireEvent.click(screen.getByRole('tab', { name: 'Draft' }))
64
- const date = screen.getByLabelText('Date')
65
- expect(date).toHaveValue('June 2026')
66
- expect(date).not.toHaveAttribute('type', 'date')
67
- fireEvent.click(screen.getByRole('button', { name: 'Pick a date' }))
68
- expect(screen.getByLabelText('Date')).toHaveAttribute('type', 'date')
69
- })
70
-
71
- it('offers style and language dropdowns and writes the choices to the frontmatter', () => {
72
- const onClose = renderDialog()
73
- fireEvent.click(screen.getByRole('tab', { name: 'Document Settings' }))
74
- fireEvent.change(screen.getByLabelText('Screenplay style'), { target: { value: 'traditional' } })
75
- fireEvent.change(screen.getByLabelText('Other languages'), { target: { value: 'en' } })
76
- fireEvent.change(screen.getByLabelText('Page size'), { target: { value: 'A4' } })
77
- fireEvent.click(screen.getByRole('button', { name: 'Save' }))
78
- expect(onClose).toHaveBeenCalled()
79
- expect(latestText).toContain('style: traditional')
80
- expect(latestText).toContain('lang-secondary: [en]')
81
- expect(latestText).toContain('page: A4')
82
- expect(latestText).toContain('date: June 2026')
83
- })
84
-
85
- it('does not change the text when saved without edits', () => {
86
- renderDialog()
87
- fireEvent.click(screen.getByRole('button', { name: 'Save' }))
88
- expect(latestText).toBe(SRC)
89
- })
90
- })
@@ -1,61 +0,0 @@
1
- import { EditorState } from 'prosemirror-state'
2
- import { EditorView } from 'prosemirror-view'
3
- import { history, undo } from 'prosemirror-history'
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 { applyTitlePageText } from '../../src/editor/frontmatter-field'
9
- import { readTitlePage, writeTitlePage } from '../../src/titlepage/frontmatter-form'
10
-
11
- const SRC = `---
12
- title: The Last Train
13
- author: Meera
14
- ---
15
-
16
- ## INT. STATION - NIGHT
17
-
18
- Action line.
19
- `
20
-
21
- function mount(text: string) {
22
- const state = EditorState.create({ doc: sutraToProsemirror(parse(text)), schema, plugins: [history()] })
23
- return new EditorView(document.createElement('div'), { state })
24
- }
25
-
26
- describe('applyTitlePageText (formatted mode)', () => {
27
- it('swaps the title page in one undoable transaction and leaves the body alone', () => {
28
- const view = mount(SRC)
29
- const f = readTitlePage(parse(SRC).frontmatter!.data)
30
- const next = writeTitlePage(SRC, f, { ...f, author: 'Vikram', style: 'traditional' })
31
- const setText = vi.fn()
32
-
33
- applyTitlePageText(view, next, setText)
34
-
35
- expect(setText).not.toHaveBeenCalled()
36
- const out = prosemirrorToSutra(view.state.doc)
37
- expect(out).toContain('author: Vikram')
38
- expect(out).toContain('style: traditional')
39
- expect(out).toContain('Action line.')
40
-
41
- undo(view.state, view.dispatch)
42
- expect(prosemirrorToSutra(view.state.doc)).toContain('author: Meera')
43
- view.destroy()
44
- })
45
-
46
- it('inserts a title page when the document had none', () => {
47
- const body = '## INT. ROOM - DAY\n\nAction.\n'
48
- const view = mount(body)
49
- const f = readTitlePage(undefined)
50
- applyTitlePageText(view, writeTitlePage(body, f, { ...f, title: 'New Film' }), vi.fn())
51
- expect(view.state.doc.firstChild!.type.name).toBe('title_page')
52
- expect(prosemirrorToSutra(view.state.doc)).toContain('title: New Film')
53
- view.destroy()
54
- })
55
-
56
- it('falls back to setting the text when no editor view is mounted', () => {
57
- const setText = vi.fn()
58
- applyTitlePageText(null, 'X', setText)
59
- expect(setText).toHaveBeenCalledWith('X')
60
- })
61
- })
@@ -1,115 +0,0 @@
1
- import { parse } from '@sutrata/parser'
2
- import { readTitlePage, writeTitlePage, splitList } from '../../src/titlepage/frontmatter-form'
3
-
4
- const SRC = `---
5
- format: sutra/1.0
6
- title:
7
- hi: आख़िरी ट्रेन
8
- en: The Last Train
9
- author: रंगी पराता
10
- date: June 2026
11
- contact: |
12
- rangi@example.com
13
- +91 98xxx xxxxx
14
- lang: hi
15
- lang-secondary: [en]
16
- producer: Big Films
17
- characters:
18
- JOHN: Actor One
19
- ---
20
-
21
- ## INT. STATION - NIGHT
22
- `
23
-
24
- const read = (text: string) => readTitlePage(parse(text).frontmatter?.data as Record<string, unknown>)
25
-
26
- describe('readTitlePage', () => {
27
- it('reads the multilingual title with the primary language first', () => {
28
- const f = read(SRC)
29
- expect(f.title).toBe('आख़िरी ट्रेन')
30
- expect(f.altTitles).toEqual([{ lang: 'en', text: 'The Last Train' }])
31
- expect(f.lang).toBe('hi')
32
- expect(f.langSecondary).toEqual(['en'])
33
- expect(f.contact).toBe('rangi@example.com\n+91 98xxx xxxxx')
34
- })
35
-
36
- it('lists simple unknown keys as custom rows, hides format and nested maps', () => {
37
- const f = read(SRC)
38
- expect(f.custom).toEqual([{ key: 'producer', value: 'Big Films' }])
39
- })
40
-
41
- it('splits list values in every shape', () => {
42
- expect(splitList('[en, sa]')).toEqual(['en', 'sa'])
43
- expect(splitList('en')).toEqual(['en'])
44
- expect(splitList(['ta'])).toEqual(['ta'])
45
- })
46
- })
47
-
48
- describe('writeTitlePage', () => {
49
- it('leaves the text byte-identical when nothing changed', () => {
50
- const f = read(SRC)
51
- expect(writeTitlePage(SRC, f, { ...f })).toBe(SRC)
52
- })
53
-
54
- it('rewrites only the changed key and keeps everything else in place', () => {
55
- const f = read(SRC)
56
- const out = writeTitlePage(SRC, f, { ...f, author: 'Meera Nair' })
57
- expect(out).toBe(SRC.replace('author: रंगी पराता', 'author: Meera Nair'))
58
- })
59
-
60
- it('appends new keys in canonical order before the closing fence', () => {
61
- const f = read(SRC)
62
- const out = writeTitlePage(SRC, f, { ...f, style: 'traditional', page: 'A4', draft: 'Second Draft' })
63
- const fm = out.split('---')[1]!
64
- expect(fm.trimEnd().split('\n').slice(-3)).toEqual(['draft: Second Draft', 'page: A4', 'style: traditional'])
65
- expect(parse(out).frontmatter!.data['style']).toBe('traditional')
66
- })
67
-
68
- it('removes keys that were cleared', () => {
69
- const f = read(SRC)
70
- const out = writeTitlePage(SRC, f, { ...f, contact: '' })
71
- expect(out).not.toContain('contact')
72
- expect(out).not.toContain('rangi@example.com')
73
- expect(out).toContain('author: रंगी पराता')
74
- })
75
-
76
- it('writes the multilingual title as a nested map and a plain title as a scalar', () => {
77
- const f = read(SRC)
78
- const both = writeTitlePage(SRC, f, { ...f, altTitles: [...f.altTitles, { lang: 'ta', text: 'கடைசி ரயில்' }] })
79
- expect(parse(both).frontmatter!.data['title']).toEqual({ hi: 'आख़िरी ट्रेन', en: 'The Last Train', ta: 'கடைசி ரயில்' })
80
- const single = writeTitlePage(SRC, f, { ...f, altTitles: [] })
81
- expect(parse(single).frontmatter!.data['title']).toBe('आख़िरी ट्रेन')
82
- })
83
-
84
- it('writes secondary languages as a flow list and multi-line contact as a block', () => {
85
- const f = read(SRC)
86
- const out = writeTitlePage(SRC, f, { ...f, langSecondary: ['en', 'sa'], contact: 'a@b.c\nPhone' })
87
- expect(out).toContain('lang-secondary: [en, sa]')
88
- expect(out).toContain('contact: |\n a@b.c\n Phone')
89
- expect(parse(out).frontmatter!.data['contact']).toBe('a@b.c\nPhone')
90
- })
91
-
92
- it('edits, removes and adds custom fields, never touching nested unknown maps', () => {
93
- const f = read(SRC)
94
- const out = writeTitlePage(SRC, f, { ...f, custom: [{ key: 'studio', value: 'Lotus' }] })
95
- expect(out).not.toContain('producer:')
96
- expect(out).toContain('studio: Lotus')
97
- expect(out).toContain('characters:\n JOHN: Actor One')
98
- expect(out).toContain('format: sutra/1.0')
99
- })
100
-
101
- it('creates a frontmatter block when the document has none', () => {
102
- const body = '## INT. ROOM - DAY\n'
103
- const f = read(body)
104
- const out = writeTitlePage(body, f, { ...f, title: 'New Film', date: '2026-09-19' })
105
- expect(out).toBe('---\ntitle: New Film\ndate: 2026-09-19\n---\n\n## INT. ROOM - DAY\n')
106
- })
107
-
108
- it('preserves CRLF line endings', () => {
109
- const crlf = SRC.replace(/\n/g, '\r\n')
110
- const f = read(crlf)
111
- const out = writeTitlePage(crlf, f, { ...f, author: 'X' })
112
- expect(out).toContain('author: X\r\n')
113
- expect(out.replace(/\r\n/g, '')).not.toContain('\n')
114
- })
115
- })
@@ -1,93 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { EditorState, TextSelection } from 'prosemirror-state'
3
- import { schema } from '../src/editor/schema'
4
- import { makeSetBlock, makeSetOrToggleNote, makeToggleMark } from '../src/shell/toolbar-actions'
5
-
6
- function stateWith(typeName: string, text: string) {
7
- const node = schema.nodes[typeName]!.create(null, text ? schema.text(text) : undefined)
8
- const doc = schema.nodes['doc']!.create(null, node)
9
- let state = EditorState.create({ schema, doc })
10
- state = state.apply(state.tr.setSelection(TextSelection.create(state.doc, 1)))
11
- return state
12
- }
13
-
14
- describe('makeSetBlock', () => {
15
- it('changes the current block type', () => {
16
- const state = stateWith('action', 'hello')
17
- let next = state
18
- makeSetBlock('character')(state, (tr) => { next = state.apply(tr) })
19
- expect(next.doc.firstChild!.type.name).toBe('character')
20
- })
21
- })
22
-
23
- describe('makeToggleMark', () => {
24
- it('returns false on empty selection (does nothing)', () => {
25
- const state = stateWith('action', 'hello')
26
- const result = makeToggleMark('bold')(state, () => {})
27
- expect(result).toBe(false)
28
- })
29
-
30
- it('applies the note_mark to a selected range, so an inline note can be created from the WYSIWYG toolbar', () => {
31
- const state = stateWith('action', 'Add the rain here please')
32
- const withSelection = state.apply(
33
- state.tr.setSelection(TextSelection.create(state.doc, 1, 1 + 'Add the rain'.length))
34
- )
35
- let next = withSelection
36
- const applied = makeToggleMark('note_mark')(withSelection, (tr) => { next = withSelection.apply(tr) })
37
- expect(applied).toBe(true)
38
-
39
- const markType = schema.marks['note_mark']!
40
- let hasMark = false
41
- next.doc.firstChild!.descendants(node => {
42
- if (node.isText && node.marks.some(m => m.type === markType)) hasMark = true
43
- })
44
- expect(hasMark).toBe(true)
45
- })
46
- })
47
-
48
- function noteMarkedText(doc: EditorState['doc']): string {
49
- const markType = schema.marks['note_mark']!
50
- let text = ''
51
- doc.firstChild!.descendants(node => {
52
- if (node.isText && node.marks.some(m => m.type === markType)) text += node.text
53
- })
54
- return text
55
- }
56
-
57
- describe('makeSetOrToggleNote', () => {
58
- it('marks only the selected text as a note on an Action line, leaving the rest of the line intact', () => {
59
- const state = stateWith('action', 'Add the rain here please')
60
- const withSelection = state.apply(
61
- state.tr.setSelection(TextSelection.create(state.doc, 1, 1 + 'Add the rain'.length))
62
- )
63
- let next = withSelection
64
- const applied = makeSetOrToggleNote()(withSelection, (tr) => { next = withSelection.apply(tr) })
65
-
66
- expect(applied).toBe(true)
67
- expect(next.doc.firstChild!.type.name).toBe('action')
68
- expect(noteMarkedText(next.doc)).toBe('Add the rain')
69
- expect(next.doc.textContent).toBe('Add the rain here please')
70
- })
71
-
72
- it('marks the whole line as a note on an Action line with no selection', () => {
73
- const state = stateWith('action', 'Add the rain here please')
74
- let next = state
75
- const applied = makeSetOrToggleNote()(state, (tr) => { next = state.apply(tr) })
76
-
77
- expect(applied).toBe(true)
78
- expect(next.doc.firstChild!.type.name).toBe('action')
79
- expect(noteMarkedText(next.doc)).toBe('Add the rain here please')
80
- })
81
-
82
- it('falls back to converting the whole block to a note element on any line other than Action', () => {
83
- const state = stateWith('dialogue', 'We should go home now.')
84
- const withSelection = state.apply(
85
- state.tr.setSelection(TextSelection.create(state.doc, 1, 1 + 'We should'.length))
86
- )
87
- let next = withSelection
88
- const applied = makeSetOrToggleNote()(withSelection, (tr) => { next = withSelection.apply(tr) })
89
-
90
- expect(applied).toBe(true)
91
- expect(next.doc.firstChild!.type.name).toBe('note')
92
- })
93
- })
@@ -1,58 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { parseVoiceCommand } from '../src/ai/commands';
3
-
4
- describe('Voice Commands Pre-processor', () => {
5
- it('should parse English commands correctly', () => {
6
- const result1 = parseVoiceCommand('new scene INT. HALLWAYS - DAY', 'en-US');
7
- expect(result1.type).toBe('scene');
8
- expect(result1.text).toBe('INT. HALLWAYS - DAY');
9
- expect(result1.isLiteral).toBe(false);
10
-
11
- const result2 = parseVoiceCommand('character RAJ', 'en-US');
12
- expect(result2.type).toBe('character');
13
- expect(result2.text).toBe('RAJ');
14
-
15
- const result3 = parseVoiceCommand('dialogue Hello world', 'en-US');
16
- expect(result3.type).toBe('dialogue');
17
- expect(result3.text).toBe('Hello world');
18
- });
19
-
20
- it('should parse Hindi commands correctly', () => {
21
- const result1 = parseVoiceCommand('नया दृश्य अंतःपुर - दिन', 'hi-IN');
22
- expect(result1.type).toBe('scene');
23
- expect(result1.text).toBe('अंतःपुर - दिन');
24
-
25
- const result2 = parseVoiceCommand('पात्र राहुल', 'hi-IN');
26
- expect(result2.type).toBe('character');
27
- expect(result2.text).toBe('राहुल');
28
- });
29
-
30
- it('should parse Tamil commands correctly', () => {
31
- const result1 = parseVoiceCommand('புதிய காட்சி தெரு - பகல்', 'ta-IN');
32
- expect(result1.type).toBe('scene');
33
- expect(result1.text).toBe('தெரு - பகல்');
34
-
35
- const result2 = parseVoiceCommand('வசனம் வணக்கம் உலகமே', 'ta-IN');
36
- expect(result2.type).toBe('dialogue');
37
- expect(result2.text).toBe('வணக்கம் உலகமே');
38
- });
39
-
40
- it('should handle literal bypass keyword to escape commands', () => {
41
- const result1 = parseVoiceCommand('literal new scene heading', 'en-US');
42
- expect(result1.type).toBe('action');
43
- expect(result1.text).toBe('new scene heading');
44
- expect(result1.isLiteral).toBe(true);
45
-
46
- const result2 = parseVoiceCommand('शब्दशः नया दृश्य', 'hi-IN');
47
- expect(result2.type).toBe('action');
48
- expect(result2.text).toBe('नया दृश्य');
49
- expect(result2.isLiteral).toBe(true);
50
- });
51
-
52
- it('should default to action type for plain speech', () => {
53
- const result = parseVoiceCommand('The sun rises slowly over the mountains.', 'en-US');
54
- expect(result.type).toBe('action');
55
- expect(result.text).toBe('The sun rises slowly over the mountains.');
56
- expect(result.isLiteral).toBe(false);
57
- });
58
- });
package/tsconfig.json DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
5
- "module": "ESNext",
6
- "moduleResolution": "bundler",
7
- "jsx": "react-jsx",
8
- "strict": true,
9
- "noUncheckedIndexedAccess": true,
10
- "declaration": true,
11
- "outDir": "./dist",
12
- "rootDir": "./src",
13
- "resolveJsonModule": true,
14
- "skipLibCheck": true
15
- },
16
- "include": ["src"]
17
- }
package/vitest.config.ts DELETED
@@ -1,11 +0,0 @@
1
- import { defineConfig } from 'vitest/config'
2
- import react from '@vitejs/plugin-react'
3
-
4
- export default defineConfig({
5
- plugins: [react()],
6
- test: {
7
- environment: 'jsdom',
8
- globals: true,
9
- setupFiles: ['./vitest.setup.ts'],
10
- },
11
- })
package/vitest.setup.ts DELETED
@@ -1,65 +0,0 @@
1
- import '@testing-library/jest-dom/vitest'
2
- import { vi } from 'vitest'
3
-
4
- // jsdom doesn't implement URL.createObjectURL / revokeObjectURL
5
- if (typeof URL.createObjectURL === 'undefined') {
6
- URL.createObjectURL = vi.fn(() => 'blob:mock')
7
- URL.revokeObjectURL = vi.fn()
8
- }
9
-
10
- // Mock canvas for jsdom environment - jsdom doesn't fully implement canvas API
11
- if (typeof window !== 'undefined') {
12
- const mockCanvas2DContext = {
13
- fillStyle: 'black',
14
- font: '12px monospace',
15
- fillText: vi.fn(),
16
- clearRect: vi.fn(),
17
- getImageData: vi.fn((x, y, width, height) => {
18
- // Return a mock ImageData object
19
- const data = new Uint8ClampedArray(width * height * 4)
20
- return {
21
- data,
22
- width,
23
- height,
24
- colorSpace: 'srgb',
25
- }
26
- }),
27
- }
28
-
29
- // Mock HTMLCanvasElement.prototype.getContext
30
- HTMLCanvasElement.prototype.getContext = vi.fn(function (
31
- contextType: string
32
- ) {
33
- if (contextType === '2d') {
34
- return { ...mockCanvas2DContext }
35
- }
36
- return null
37
- })
38
- }
39
-
40
- // Mock localStorage for jsdom / Node 22 compatibility
41
- const createStorageMock = () => {
42
- let store: Record<string, string> = {}
43
- return {
44
- getItem: vi.fn((key: string) => store[key] ?? null),
45
- setItem: vi.fn((key: string, value: string) => { store[key] = String(value) }),
46
- removeItem: vi.fn((key: string) => { delete store[key] }),
47
- clear: vi.fn(() => { store = {} }),
48
- get length() { return Object.keys(store).length },
49
- key: vi.fn((i: number) => Object.keys(store)[i] ?? null),
50
- }
51
- }
52
-
53
- const storageMock = createStorageMock()
54
- if (typeof window !== 'undefined') {
55
- Object.defineProperty(window, 'localStorage', {
56
- value: storageMock,
57
- writable: true,
58
- configurable: true,
59
- })
60
- }
61
- Object.defineProperty(globalThis, 'localStorage', {
62
- value: storageMock,
63
- writable: true,
64
- configurable: true,
65
- })