@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.
- package/README.md +87 -0
- package/dist/ai/VoiceConfirmDialog.d.ts +1 -0
- package/dist/ai/VoiceConfirmDialog.js +6 -6
- package/dist/ai/VoiceToolbar.d.ts +1 -0
- package/dist/ai/VoiceToolbar.js +22 -16
- package/dist/ai/clean-response.d.ts +7 -0
- package/dist/ai/clean-response.js +58 -0
- package/dist/ai/format-transcript.d.ts +3 -0
- package/dist/ai/format-transcript.js +7 -0
- package/dist/ai/prompts.d.ts +8 -1
- package/dist/ai/prompts.js +34 -7
- package/dist/autocomplete/suggestions.js +1 -1
- package/dist/context/DocumentContext.d.ts +28 -4
- package/dist/context/DocumentContext.js +67 -37
- package/dist/editor/EditorView.js +32 -4
- package/dist/editor/SelectionMenu.js +5 -3
- package/dist/editor/external-decorations-plugin.d.ts +23 -0
- package/dist/editor/external-decorations-plugin.js +151 -0
- package/dist/editor/omitted-scene-plugin.d.ts +9 -0
- package/dist/editor/omitted-scene-plugin.js +44 -0
- package/dist/editor/plugins.js +4 -0
- package/dist/editor/private-metadata-plugin.d.ts +10 -0
- package/dist/editor/private-metadata-plugin.js +60 -0
- package/dist/editor/prosemirror-to-sutra.js +179 -74
- package/dist/editor/scene-heading-view.js +2 -0
- package/dist/editor/schema.js +69 -12
- package/dist/editor/sutra-to-prosemirror.d.ts +2 -0
- package/dist/editor/sutra-to-prosemirror.js +32 -34
- package/dist/extensions/ai-provider.d.ts +44 -0
- package/dist/extensions/ai-provider.js +27 -0
- package/dist/extensions/collab.d.ts +21 -0
- package/dist/extensions/collab.js +6 -0
- package/dist/extensions/command-registry.d.ts +49 -0
- package/dist/extensions/command-registry.js +28 -0
- package/dist/extensions/decorations.d.ts +48 -0
- package/dist/extensions/decorations.js +6 -0
- package/dist/extensions/export-registry.d.ts +59 -0
- package/dist/extensions/export-registry.js +10 -0
- package/dist/extensions/panel-registry.d.ts +30 -0
- package/dist/extensions/panel-registry.js +16 -0
- package/dist/extensions/registries.d.ts +15 -0
- package/dist/extensions/registries.js +12 -0
- package/dist/extensions/registry-store.d.ts +26 -0
- package/dist/extensions/registry-store.js +35 -0
- package/dist/extensions/session.d.ts +29 -0
- package/dist/extensions/session.js +20 -0
- package/dist/extensions/speech-provider.d.ts +35 -0
- package/dist/extensions/speech-provider.js +17 -0
- package/dist/file/builtin-exporters.d.ts +9 -0
- package/dist/file/builtin-exporters.js +72 -0
- package/dist/file/docx-exporter.js +7 -4
- package/dist/file/docx-importer.d.ts +9 -0
- package/dist/file/docx-importer.js +47 -19
- package/dist/file/scene-number.d.ts +5 -0
- package/dist/file/scene-number.js +8 -0
- package/dist/file/workflow-reports.js +13 -7
- package/dist/findreplace/FindReplace.js +5 -2
- package/dist/import/AIImportDialog.d.ts +11 -0
- package/dist/import/AIImportDialog.js +112 -0
- package/dist/import/ai-import.d.ts +51 -0
- package/dist/import/ai-import.js +200 -0
- package/dist/import/apply-import.d.ts +7 -0
- package/dist/import/apply-import.js +35 -0
- package/dist/import/extract.d.ts +17 -0
- package/dist/import/extract.js +18 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.js +7 -0
- package/dist/locales/bn.json +8 -0
- package/dist/locales/en.json +8 -0
- package/dist/locales/gu.json +8 -0
- package/dist/locales/hi.json +8 -0
- package/dist/locales/kn.json +8 -0
- package/dist/locales/ml.json +8 -0
- package/dist/locales/mr.json +8 -0
- package/dist/locales/or.json +8 -0
- package/dist/locales/pa.json +8 -0
- package/dist/locales/si.json +8 -0
- package/dist/locales/ta.json +8 -0
- package/dist/locales/te.json +8 -0
- package/dist/navigator/SceneNavigator.js +125 -131
- package/dist/navigator/StatisticsDialog.js +5 -1
- package/dist/navigator/scene-edits.d.ts +18 -0
- package/dist/navigator/scene-edits.js +143 -0
- package/dist/navigator/scene-list.js +4 -4
- package/dist/navigator/scene-numbering.d.ts +37 -0
- package/dist/navigator/scene-numbering.js +88 -0
- package/dist/settings/SettingsDialog.js +8 -120
- package/dist/shell/AppBar.js +21 -2
- package/dist/shell/AppShell.js +18 -4
- package/dist/shell/ElementToolbar.d.ts +5 -0
- package/dist/shell/ElementToolbar.js +35 -25
- package/dist/shell/LanguageStatus.js +6 -0
- package/dist/shell/StatusBar.js +11 -6
- package/dist/shell/builtin-commands.d.ts +10 -0
- package/dist/shell/builtin-commands.js +43 -0
- package/dist/shell/icons.d.ts +6 -0
- package/dist/shell/icons.js +6 -0
- package/dist/shell/use-commands.d.ts +7 -0
- package/dist/shell/use-commands.js +24 -0
- package/dist/shell/use-panel-context.d.ts +3 -0
- package/dist/shell/use-panel-context.js +39 -0
- package/dist/source/SourceView.js +18 -1
- package/dist/styles/screenplay.css +113 -1
- package/dist/titlepage/ExportDialog.d.ts +5 -0
- package/dist/titlepage/ExportDialog.js +59 -96
- package/package.json +32 -5
- package/dist/ai/OnboardingDialog.d.ts +0 -6
- package/dist/ai/OnboardingDialog.js +0 -115
- package/dist/ai/ai-client.d.ts +0 -71
- package/dist/ai/ai-client.js +0 -494
- package/dist/ai/generated-providers.json +0 -3696
- package/dist/ai/voice-service.d.ts +0 -15
- package/dist/ai/voice-service.js +0 -81
- package/dist/editor/character-view.d.ts +0 -35
- package/dist/editor/character-view.js +0 -112
- package/dist/editor/transliterate-input.d.ts +0 -10
- package/dist/editor/transliterate-input.js +0 -88
- package/dist/extensions/types.d.ts +0 -73
- package/dist/extensions/types.js +0 -12
- package/scripts/copy-assets.mjs +0 -16
- package/scripts/generate-providers.mjs +0 -127
- package/src/ai/OnboardingDialog.tsx +0 -390
- package/src/ai/VoiceConfirmDialog.tsx +0 -137
- package/src/ai/VoiceToolbar.tsx +0 -321
- package/src/ai/ai-client.ts +0 -596
- package/src/ai/commands.ts +0 -164
- package/src/ai/generated-providers.json +0 -3696
- package/src/ai/prompts.ts +0 -62
- package/src/ai/voice-service.ts +0 -96
- package/src/autocomplete/AutocompletePlugin.ts +0 -21
- package/src/autocomplete/suggestions.ts +0 -147
- package/src/context/DocumentContext.tsx +0 -648
- package/src/desktop-api.d.ts +0 -40
- package/src/editor/EditorView.tsx +0 -82
- package/src/editor/SelectionMenu.tsx +0 -152
- package/src/editor/attr-pencil-plugin.ts +0 -172
- package/src/editor/character-decoration-plugin.ts +0 -104
- package/src/editor/editor-bus.ts +0 -51
- package/src/editor/find-highlight-plugin.ts +0 -68
- package/src/editor/frontmatter-field-view.ts +0 -52
- package/src/editor/frontmatter-field.ts +0 -115
- package/src/editor/grapheme-cursor.ts +0 -99
- package/src/editor/ime-plugin.ts +0 -85
- package/src/editor/insert-helper.ts +0 -43
- package/src/editor/keymap.ts +0 -124
- package/src/editor/language-class-plugin.ts +0 -91
- package/src/editor/plugins.ts +0 -26
- package/src/editor/prosemirror-to-sutra.ts +0 -192
- package/src/editor/scene-heading-view.ts +0 -147
- package/src/editor/schema.ts +0 -156
- package/src/editor/sutra-to-prosemirror.ts +0 -156
- package/src/extensions/storage-adapter.ts +0 -65
- package/src/extensions/types.ts +0 -60
- package/src/file/docx-exporter.ts +0 -585
- package/src/file/docx-importer.ts +0 -313
- package/src/file/print-paginator.ts +0 -301
- package/src/file/templates.ts +0 -73
- package/src/file/workflow-reports.ts +0 -820
- package/src/findreplace/FindReplace.tsx +0 -242
- package/src/findreplace/find-engine.ts +0 -97
- package/src/findreplace/scroll-to-match.ts +0 -89
- package/src/fonts/tofu-detect.ts +0 -53
- package/src/i18n/LanguageContext.tsx +0 -207
- package/src/i18n/language-detect.ts +0 -73
- package/src/i18n/useTranslation.tsx +0 -74
- package/src/index.ts +0 -27
- package/src/locales/bn.json +0 -143
- package/src/locales/en.json +0 -143
- package/src/locales/gu.json +0 -143
- package/src/locales/hi.json +0 -143
- package/src/locales/kn.json +0 -143
- package/src/locales/ml.json +0 -143
- package/src/locales/mr.json +0 -143
- package/src/locales/or.json +0 -143
- package/src/locales/pa.json +0 -143
- package/src/locales/si.json +0 -143
- package/src/locales/ta.json +0 -143
- package/src/locales/te.json +0 -143
- package/src/navigator/SceneNavigator.tsx +0 -844
- package/src/navigator/StatisticsDialog.tsx +0 -249
- package/src/navigator/scene-list.ts +0 -84
- package/src/settings/SettingsDialog.tsx +0 -303
- package/src/shell/AppBar.tsx +0 -354
- package/src/shell/AppShell.tsx +0 -71
- package/src/shell/ConfirmDialog.tsx +0 -71
- package/src/shell/ElementToolbar.tsx +0 -175
- package/src/shell/LanguageStatus.tsx +0 -101
- package/src/shell/StatusBar.tsx +0 -135
- package/src/shell/SutrataLogo.tsx +0 -89
- package/src/shell/Toast.tsx +0 -46
- package/src/shell/ToolbarButton.tsx +0 -30
- package/src/shell/VersionHistory.tsx +0 -212
- package/src/shell/Watermark.tsx +0 -9
- package/src/shell/fonts/fonts.css +0 -1133
- package/src/shell/icons.tsx +0 -335
- package/src/shell/toolbar-actions.ts +0 -59
- package/src/source/SourceView.tsx +0 -111
- package/src/source/find-highlight-source.ts +0 -57
- package/src/source/sutra-lang.ts +0 -121
- package/src/spellcheck/dictionaries.ts +0 -21
- package/src/spellcheck/hunspell.ts +0 -110
- package/src/spellcheck/spellcheck-plugin.ts +0 -83
- package/src/styles/StyleDialog.tsx +0 -148
- package/src/styles/builtin-styles.ts +0 -139
- package/src/styles/css-adapter.ts +0 -76
- package/src/styles/docx-adapter.ts +0 -109
- package/src/styles/embed-fonts.ts +0 -81
- package/src/styles/pagination.ts +0 -27
- package/src/styles/print-adapter.ts +0 -288
- package/src/styles/registry.ts +0 -20
- package/src/styles/screenplay.css +0 -3006
- package/src/styles/script-ranges.ts +0 -17
- package/src/styles/types.ts +0 -104
- package/src/styles/validate.ts +0 -114
- package/src/templates/templates.ts +0 -194
- package/src/titlepage/ExportDialog.tsx +0 -494
- package/src/titlepage/TitlePageDialog.tsx +0 -312
- package/src/titlepage/frontmatter-form.ts +0 -280
- package/src/titlepage/languages.ts +0 -37
- package/src/types.ts +0 -7
- package/tests/AppBar.test.tsx +0 -35
- package/tests/AppShell.test.tsx +0 -22
- package/tests/ElementToolbar.test.tsx +0 -36
- package/tests/LanguageStatus.test.tsx +0 -129
- package/tests/SelectionMenu.test.tsx +0 -71
- package/tests/ToolbarButton.test.tsx +0 -24
- package/tests/ai-onboarding.test.ts +0 -102
- package/tests/ai-status.test.tsx +0 -72
- package/tests/ai-structured.test.ts +0 -94
- package/tests/autocomplete/AutocompletePlugin.test.ts +0 -20
- package/tests/autocomplete/suggestions.test.ts +0 -106
- package/tests/document-context.test.tsx +0 -565
- package/tests/docx-importer.test.ts +0 -160
- package/tests/editor/character-decoration-plugin.test.ts +0 -101
- package/tests/editor/character-extension.test.ts +0 -44
- package/tests/editor/grapheme-cursor.test.ts +0 -118
- package/tests/editor/ime-plugin.test.ts +0 -72
- package/tests/editor/inline-note-bridge.test.ts +0 -32
- package/tests/editor/language-class-plugin.test.ts +0 -265
- package/tests/editor/title-page.test.ts +0 -124
- package/tests/embed-fonts.test.ts +0 -17
- package/tests/exporters.test.ts +0 -365
- package/tests/findreplace/FindReplace.test.tsx +0 -391
- package/tests/findreplace/find-engine.test.ts +0 -86
- package/tests/findreplace/scroll-to-match.test.ts +0 -50
- package/tests/fonts/tofu-detect.test.ts +0 -122
- package/tests/i18n/LanguageContext.test.tsx +0 -146
- package/tests/i18n/language-detect.test.ts +0 -54
- package/tests/i18n/locale-parity.test.ts +0 -25
- package/tests/i18n/useTranslation.test.tsx +0 -45
- package/tests/keymap.test.ts +0 -18
- package/tests/navigator/SceneNavigator.test.tsx +0 -177
- package/tests/navigator/StatisticsDialog.test.tsx +0 -63
- package/tests/navigator/scene-list.test.ts +0 -71
- package/tests/print-paginator.test.ts +0 -82
- package/tests/shell/VersionHistory.test.tsx +0 -129
- package/tests/spellcheck/hunspell.test.ts +0 -82
- package/tests/spellcheck/spellcheck-plugin.test.ts +0 -56
- package/tests/styles.test.ts +0 -315
- package/tests/templates/templates.test.ts +0 -83
- package/tests/titlepage/TitlePageDialog.test.tsx +0 -90
- package/tests/titlepage/apply-title-page.test.ts +0 -61
- package/tests/titlepage/frontmatter-form.test.ts +0 -115
- package/tests/toolbar-actions.test.ts +0 -93
- package/tests/voice-commands.test.ts +0 -58
- package/tsconfig.json +0 -17
- package/vitest.config.ts +0 -11
- package/vitest.setup.ts +0 -65
package/tests/exporters.test.ts
DELETED
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { parse, romanizeSutra } from '@sutrata/parser'
|
|
3
|
-
import { exportToDocx } from '../src/file/docx-exporter'
|
|
4
|
-
import {
|
|
5
|
-
extractWorkflowData,
|
|
6
|
-
exportOneLinerCsv,
|
|
7
|
-
exportShotListCsv,
|
|
8
|
-
exportCastReportCsv,
|
|
9
|
-
exportOneLinerDocx,
|
|
10
|
-
exportShotListDocx,
|
|
11
|
-
exportCastReportDocx,
|
|
12
|
-
openScreenplayPrintPreview,
|
|
13
|
-
} from '../src/file/workflow-reports'
|
|
14
|
-
import JSZip from 'jszip'
|
|
15
|
-
|
|
16
|
-
const testSutra = `---
|
|
17
|
-
title: Test Movie
|
|
18
|
-
author: Writer Name
|
|
19
|
-
logline: A movie about testing.
|
|
20
|
-
date: 2026-06-13
|
|
21
|
-
lang: en
|
|
22
|
-
characters:
|
|
23
|
-
JOHN: Actor One
|
|
24
|
-
MARY: Actor Two
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Scene 1
|
|
28
|
-
& number: 1
|
|
29
|
-
& synopsis: John and Mary talk in a room.
|
|
30
|
-
& status: Done
|
|
31
|
-
& est-duration: 2m
|
|
32
|
-
& shots: Wide shot of the room
|
|
33
|
-
& shots: Close up on John
|
|
34
|
-
|
|
35
|
-
@JOHN
|
|
36
|
-
Hello Mary.
|
|
37
|
-
|
|
38
|
-
@MARY
|
|
39
|
-
(smiling)
|
|
40
|
-
Hi John.
|
|
41
|
-
|
|
42
|
-
@JOHN
|
|
43
|
-
(dialogue side-by-side)
|
|
44
|
-
Dual dialogue left.
|
|
45
|
-
|
|
46
|
-
@MARY ^
|
|
47
|
-
(dialogue side-by-side)
|
|
48
|
-
Dual dialogue right.
|
|
49
|
-
|
|
50
|
-
Action line here.
|
|
51
|
-
|
|
52
|
-
[[ Note text ]]
|
|
53
|
-
|
|
54
|
-
<!-- Comment text -->
|
|
55
|
-
|
|
56
|
-
===
|
|
57
|
-
|
|
58
|
-
*Lyrics here*
|
|
59
|
-
|
|
60
|
-
`
|
|
61
|
-
|
|
62
|
-
describe('Exporters', () => {
|
|
63
|
-
const ast = parse(testSutra)
|
|
64
|
-
|
|
65
|
-
it('exportToDocx generates a Blob', async () => {
|
|
66
|
-
const blob = await exportToDocx(ast)
|
|
67
|
-
expect(blob).toBeInstanceOf(Blob)
|
|
68
|
-
expect(blob.size).toBeGreaterThan(0)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('uses Noto Serif Tamil, forced italic, for Tamil runs under the traditional style, but Noto Sans Tamil (not italic) elsewhere', async () => {
|
|
72
|
-
const tamilDoc = parse('## Scene 1\n\nஇது ஒரு தமிழ் வாக்கியம்.\n')
|
|
73
|
-
|
|
74
|
-
const traditionalBlob = await exportToDocx(tamilDoc, 'traditional')
|
|
75
|
-
const traditionalXml = await (await JSZip.loadAsync(await traditionalBlob.arrayBuffer())).file('word/document.xml')!.async('text')
|
|
76
|
-
expect(traditionalXml).toContain('w:ascii="Noto Serif Tamil"')
|
|
77
|
-
expect(traditionalXml).not.toContain('Noto Sans Tamil')
|
|
78
|
-
// forced italic even though this is a plain action line with no *emphasis* markup
|
|
79
|
-
expect(traditionalXml).toMatch(/<w:rPr>\s*<w:rFonts w:ascii="Noto Serif Tamil"[^/]*\/>\s*<w:i\/>/)
|
|
80
|
-
|
|
81
|
-
const classicBlob = await exportToDocx(tamilDoc, 'classic')
|
|
82
|
-
const classicXml = await (await JSZip.loadAsync(await classicBlob.arrayBuffer())).file('word/document.xml')!.async('text')
|
|
83
|
-
expect(classicXml).toContain('w:ascii="Noto Sans Tamil"')
|
|
84
|
-
expect(classicXml).not.toMatch(/<w:rPr>\s*<w:rFonts w:ascii="Noto Sans Tamil"[^/]*\/>\s*<w:i\/>/)
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('never treats plain English text as the document default language\'s script (regression: lang: ta used to force every English run to Tamil\'s font/italics)', async () => {
|
|
88
|
-
const mixedDoc = parse('---\ntitle: Eagle\nlang: ta\n---\n\n## EXT. COURT - DAY\n\nஇது ஒரு தமிழ் வாக்கியம்.\n')
|
|
89
|
-
const blob = await exportToDocx(mixedDoc, 'traditional')
|
|
90
|
-
const xml = await (await JSZip.loadAsync(await blob.arrayBuffer())).file('word/document.xml')!.async('text')
|
|
91
|
-
|
|
92
|
-
// "EAGLE" (title) and "EXT. COURT - DAY" (scene heading) are pure English: they must
|
|
93
|
-
// get no run-level font override at all (inheriting the paragraph style's own font,
|
|
94
|
-
// i.e. Noto Serif) rather than being forced into Tamil's font/italics.
|
|
95
|
-
const englishRunPattern = /<w:rPr>\s*<w:b\/>\s*<w:bCs\/>\s*<w:i w:val="false"\/>\s*<w:iCs w:val="false"\/>\s*<\/w:rPr>\s*<w:t xml:space="preserve">EXT\. COURT - DAY<\/w:t>/
|
|
96
|
-
expect(xml).toMatch(englishRunPattern)
|
|
97
|
-
|
|
98
|
-
// The actual Tamil line still gets Noto Serif Tamil + forced italic.
|
|
99
|
-
expect(xml).toMatch(/<w:rPr>\s*<w:rFonts w:ascii="Noto Serif Tamil"[^/]*\/>\s*<w:i\/>/)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('does not force plain Latin runs to Noto Sans when the style uses a different body font', async () => {
|
|
103
|
-
// Regression: createRuns used to compare the Indic-fallback font against the
|
|
104
|
-
// active style's fontFamily and override the run whenever they differed —
|
|
105
|
-
// which incorrectly forced every Latin run to the fallback's own "Noto Sans"
|
|
106
|
-
// default instead of leaving it to inherit the paragraph style's real font
|
|
107
|
-
// (e.g. Courier Prime for 'industry-courier').
|
|
108
|
-
const blob = await exportToDocx(ast, 'industry-courier')
|
|
109
|
-
const buffer = await blob.arrayBuffer()
|
|
110
|
-
const zip = await JSZip.loadAsync(buffer)
|
|
111
|
-
const xml = await zip.file('word/document.xml')!.async('text')
|
|
112
|
-
expect(xml).not.toContain('Noto Sans')
|
|
113
|
-
expect(xml).toContain('CineAction')
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('extractWorkflowData correctly extracts scenes and characters', () => {
|
|
117
|
-
const data = extractWorkflowData(ast)
|
|
118
|
-
|
|
119
|
-
expect(data.scenes).toHaveLength(1)
|
|
120
|
-
const s = data.scenes[0]!
|
|
121
|
-
expect(s.number).toBe('1')
|
|
122
|
-
expect(s.heading).toBe('SCENE 1')
|
|
123
|
-
expect(s.synopsis).toBe('John and Mary talk in a room.')
|
|
124
|
-
expect(s.status).toBe('Done')
|
|
125
|
-
expect(s.estDuration).toBe('2m')
|
|
126
|
-
expect(s.shots).toEqual(['Wide shot of the room', 'Close up on John'])
|
|
127
|
-
|
|
128
|
-
// Character occurrences
|
|
129
|
-
expect(data.characters).toHaveLength(2)
|
|
130
|
-
const johnStats = data.characters.find(c => c.name === 'JOHN')!
|
|
131
|
-
expect(johnStats).toBeDefined()
|
|
132
|
-
expect(johnStats.actor).toBe('Actor One')
|
|
133
|
-
expect(johnStats.sceneCount).toBe(1)
|
|
134
|
-
expect(johnStats.scenes[0]).toContain('SCENE 1')
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it('exportOneLinerCsv generates correct headers and rows', () => {
|
|
138
|
-
const { scenes } = extractWorkflowData(ast)
|
|
139
|
-
const csv = exportOneLinerCsv(scenes)
|
|
140
|
-
expect(csv).toContain('Scene #,Heading,Synopsis,Actors,Status,Est-Duration')
|
|
141
|
-
expect(csv).toContain('"1","SCENE 1","John and Mary talk in a room.","","Done","2m"')
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
it('exportShotListCsv generates correct headers and rows', () => {
|
|
145
|
-
const { scenes } = extractWorkflowData(ast)
|
|
146
|
-
const csv = exportShotListCsv(scenes)
|
|
147
|
-
expect(csv).toContain('Scene #,Heading,Actors,Shot Details')
|
|
148
|
-
expect(csv).toContain('"1","SCENE 1","","Wide shot of the room"')
|
|
149
|
-
expect(csv).toContain('"1","SCENE 1","","Close up on John"')
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
it('exportCastReportCsv generates correct headers and rows', () => {
|
|
153
|
-
const { characters } = extractWorkflowData(ast)
|
|
154
|
-
const csv = exportCastReportCsv(characters)
|
|
155
|
-
expect(csv).toContain('Character,Actor Name,Scene Occurrences,Scenes List')
|
|
156
|
-
expect(csv).toContain('"JOHN","Actor One",1,"1: SCENE 1"')
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
it('exportOneLinerDocx generates a Blob', async () => {
|
|
160
|
-
const { scenes } = extractWorkflowData(ast)
|
|
161
|
-
const blob = await exportOneLinerDocx(scenes)
|
|
162
|
-
expect(blob).toBeInstanceOf(Blob)
|
|
163
|
-
expect(blob.size).toBeGreaterThan(0)
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
it('exportShotListDocx generates a Blob', async () => {
|
|
167
|
-
const { scenes } = extractWorkflowData(ast)
|
|
168
|
-
const blob = await exportShotListDocx(scenes)
|
|
169
|
-
expect(blob).toBeInstanceOf(Blob)
|
|
170
|
-
expect(blob.size).toBeGreaterThan(0)
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
it('exportCastReportDocx generates a Blob', async () => {
|
|
174
|
-
const { characters } = extractWorkflowData(ast)
|
|
175
|
-
const blob = await exportCastReportDocx(characters)
|
|
176
|
-
expect(blob).toBeInstanceOf(Blob)
|
|
177
|
-
expect(blob.size).toBeGreaterThan(0)
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
it('includes a print-watermark div only when frontmatter.watermark is set', async () => {
|
|
181
|
-
const originalOpen = window.open
|
|
182
|
-
let written = ''
|
|
183
|
-
window.open = () => ({ document: { write: (html: string) => { written = html }, close: () => {} } } as any)
|
|
184
|
-
|
|
185
|
-
try {
|
|
186
|
-
await openScreenplayPrintPreview(parse(testSutra))
|
|
187
|
-
expect(written).not.toContain('class="print-watermark"')
|
|
188
|
-
|
|
189
|
-
const watermarked = parse(testSutra.replace('lang: en', 'lang: en\nwatermark: CONFIDENTIAL DRAFT'))
|
|
190
|
-
await openScreenplayPrintPreview(watermarked)
|
|
191
|
-
expect(written).toContain('class="print-watermark"')
|
|
192
|
-
expect(written).toContain('CONFIDENTIAL DRAFT')
|
|
193
|
-
} finally {
|
|
194
|
-
window.open = originalOpen
|
|
195
|
-
}
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
it('wraps Tamil text in <i> in the print/PDF HTML under the traditional style, but not under classic (regression: print export had no per-span language tagging, so italicScripts had no effect there even though it already worked in the editor/DOCX)', async () => {
|
|
199
|
-
const originalOpen = window.open
|
|
200
|
-
let written = ''
|
|
201
|
-
window.open = () => ({ document: { write: (html: string) => { written = html }, close: () => {} } } as any)
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
const tamilDoc = parse('## Scene 1\n\nஇது ஒரு தமிழ் வாக்கியம்.\n')
|
|
205
|
-
|
|
206
|
-
await openScreenplayPrintPreview(tamilDoc, 'Screenplay', 'traditional')
|
|
207
|
-
expect(written).toMatch(/<i>[^<]*இது ஒரு தமிழ் வாக்கியம்[^<]*<\/i>/)
|
|
208
|
-
|
|
209
|
-
await openScreenplayPrintPreview(tamilDoc, 'Screenplay', 'classic')
|
|
210
|
-
expect(written).not.toContain('<i>')
|
|
211
|
-
} finally {
|
|
212
|
-
window.open = originalOpen
|
|
213
|
-
}
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
it('lays the corner cover out top-left/bottom-right for traditional, and keeps the centered one for classic', async () => {
|
|
217
|
-
const originalOpen = window.open
|
|
218
|
-
let written = ''
|
|
219
|
-
window.open = () => ({ document: { write: (html: string) => { written = html }, close: () => {} } } as any)
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
await openScreenplayPrintPreview(ast, 'Screenplay', 'traditional')
|
|
223
|
-
expect(written).toContain('class="print-cover-page print-cover-corners"')
|
|
224
|
-
// One "by <author>" line in the top block, not the centered layout's split pair.
|
|
225
|
-
expect(written).toContain('<div class="print-cover-author">by Writer Name</div>')
|
|
226
|
-
expect(written).not.toContain('<div class="print-cover-by">')
|
|
227
|
-
|
|
228
|
-
await openScreenplayPrintPreview(ast, 'Screenplay', 'classic')
|
|
229
|
-
expect(written).toContain('class="print-cover-page print-cover-centered"')
|
|
230
|
-
expect(written).toContain('<div class="print-cover-by">by</div>')
|
|
231
|
-
} finally {
|
|
232
|
-
window.open = originalOpen
|
|
233
|
-
}
|
|
234
|
-
})
|
|
235
|
-
|
|
236
|
-
it('puts the docx corner cover\'s date/contact in an indented bottom block (traditional) but centers them (classic)', async () => {
|
|
237
|
-
const paragraphs = async (styleId: string) => {
|
|
238
|
-
const zip = await JSZip.loadAsync(await (await exportToDocx(ast, styleId)).arrayBuffer())
|
|
239
|
-
const doc = await zip.file('word/document.xml')!.async('string')
|
|
240
|
-
const cover = doc.slice(0, doc.indexOf('w:br w:type="page"'))
|
|
241
|
-
return (cover.match(/<w:p>.*?<\/w:p>/gs) ?? []).map(p => ({
|
|
242
|
-
text: [...p.matchAll(/<w:t[^>]*>(.*?)<\/w:t>/g)].map(m => m[1]).join(''),
|
|
243
|
-
indent: p.match(/w:ind w:left="(\d+)"/)?.[1],
|
|
244
|
-
centered: p.includes('w:jc w:val="center"'),
|
|
245
|
-
})).filter(p => p.text)
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const corners = await paragraphs('traditional')
|
|
249
|
-
expect(corners.map(p => p.text)).toContain('by Writer Name')
|
|
250
|
-
const cornerDate = corners.find(p => p.text === '2026-06-13')!
|
|
251
|
-
expect(Number(cornerDate.indent)).toBeGreaterThan(0)
|
|
252
|
-
expect(cornerDate.centered).toBe(false)
|
|
253
|
-
|
|
254
|
-
const centered = await paragraphs('classic')
|
|
255
|
-
expect(centered.map(p => p.text)).toContain('by') // on its own line
|
|
256
|
-
expect(centered.map(p => p.text)).not.toContain('by Writer Name')
|
|
257
|
-
expect(centered.find(p => p.text === '2026-06-13')!.centered).toBe(true)
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
it('hands the print document a paginator and the scene tags its headers need', async () => {
|
|
261
|
-
const originalOpen = window.open
|
|
262
|
-
let written = ''
|
|
263
|
-
window.open = () => ({ document: { write: (html: string) => { written = html }, close: () => {} } } as any)
|
|
264
|
-
|
|
265
|
-
try {
|
|
266
|
-
await openScreenplayPrintPreview(parse('## INT. ROOM - DAY {#8}\n\nAction line.\n'))
|
|
267
|
-
expect(written).toContain('<div id="print-pages">')
|
|
268
|
-
expect(written).toContain('<div id="print-flow">')
|
|
269
|
-
// The heading marks where scene 8 starts; its action blocks only say they belong to it.
|
|
270
|
-
expect(written).toContain('data-scene="8" data-scene-start="8"')
|
|
271
|
-
expect(written).toMatch(/<div data-scene="8" class="print-action"/)
|
|
272
|
-
expect(written).toContain('paginatePrintDocument')
|
|
273
|
-
expect(written).toContain('"continuedSuffix":"CONTINUED:"')
|
|
274
|
-
} finally {
|
|
275
|
-
window.open = originalOpen
|
|
276
|
-
}
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
it('labels scenes by position when they carry no number of their own', async () => {
|
|
280
|
-
const originalOpen = window.open
|
|
281
|
-
let written = ''
|
|
282
|
-
window.open = () => ({ document: { write: (html: string) => { written = html }, close: () => {} } } as any)
|
|
283
|
-
|
|
284
|
-
try {
|
|
285
|
-
await openScreenplayPrintPreview(parse('## INT. ROOM - DAY\n\nOne.\n\n## EXT. ROAD - DAY\n\nTwo.\n'))
|
|
286
|
-
expect(written).toContain('data-scene="1" data-scene-start="1"')
|
|
287
|
-
expect(written).toContain('data-scene="2" data-scene-start="2"')
|
|
288
|
-
} finally {
|
|
289
|
-
window.open = originalOpen
|
|
290
|
-
}
|
|
291
|
-
})
|
|
292
|
-
|
|
293
|
-
it('gives the docx a "p<n>" page-number header and leaves the cover page bare', async () => {
|
|
294
|
-
const zip = await JSZip.loadAsync(await (await exportToDocx(ast)).arrayBuffer())
|
|
295
|
-
const headers = await Promise.all(
|
|
296
|
-
Object.keys(zip.files).filter(f => /header\d*\.xml$/.test(f)).map(f => zip.file(f)!.async('string'))
|
|
297
|
-
)
|
|
298
|
-
const running = headers.find(h => h.includes('PAGE'))!
|
|
299
|
-
expect(running).toContain('<w:t xml:space="preserve">p</w:t>')
|
|
300
|
-
expect(running).toContain('<w:instrText xml:space="preserve">PAGE</w:instrText>')
|
|
301
|
-
expect(running).toContain('w:jc w:val="right"')
|
|
302
|
-
// Cover page: its own (empty) header, and numbered 0 so the script itself starts at p1.
|
|
303
|
-
const doc = await zip.file('word/document.xml')!.async('string')
|
|
304
|
-
expect(doc).toContain('<w:titlePg/>')
|
|
305
|
-
expect(doc).toContain('<w:pgNumType w:start="0"/>')
|
|
306
|
-
expect(headers.some(h => !h.includes('PAGE'))).toBe(true)
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
it('openScreenplayPrintPreview runs without error', () => {
|
|
310
|
-
const originalOpen = window.open
|
|
311
|
-
let mockDoc = {
|
|
312
|
-
write: () => {},
|
|
313
|
-
close: () => {}
|
|
314
|
-
}
|
|
315
|
-
window.open = () => ({ document: mockDoc } as any)
|
|
316
|
-
|
|
317
|
-
try {
|
|
318
|
-
expect(() => openScreenplayPrintPreview(ast)).not.toThrow()
|
|
319
|
-
} finally {
|
|
320
|
-
window.open = originalOpen
|
|
321
|
-
}
|
|
322
|
-
})
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
describe('romanized export', () => {
|
|
326
|
-
const hindi = `---
|
|
327
|
-
title: आख़िरी ट्रेन
|
|
328
|
-
lang: hi
|
|
329
|
-
lang-secondary: [en]
|
|
330
|
-
---
|
|
331
|
-
|
|
332
|
-
## INT. रेलवे स्टेशन - रात {#sc1}
|
|
333
|
-
|
|
334
|
-
@विक्रम
|
|
335
|
-
हम ट्रेन छूट गए, है ना?
|
|
336
|
-
`
|
|
337
|
-
|
|
338
|
-
it('feeds a romanized copy through the DOCX exporter', async () => {
|
|
339
|
-
const doc = parse(romanizeSutra(hindi, { scope: 'all', variant: 'strict' }))
|
|
340
|
-
const blob = await exportToDocx(doc)
|
|
341
|
-
const xml = await (await JSZip.loadAsync(await blob.arrayBuffer())).file('word/document.xml')!.async('text')
|
|
342
|
-
expect(xml).toContain('hama ṭrēna chūṭa gaē, hai nā?')
|
|
343
|
-
expect(xml).toContain('VIKRAM') // name uses primary-language convention (cue uppercased by the exporter)
|
|
344
|
-
expect(xml).toContain('ROMANIZED COPY')
|
|
345
|
-
expect(xml).not.toContain('PAGE REFERENCE')
|
|
346
|
-
expect(xml).not.toMatch(/[\u0900-\u097F]/) // no Devanagari left in the body
|
|
347
|
-
})
|
|
348
|
-
|
|
349
|
-
it('romanizes workflow reports from a fully romanized copy', () => {
|
|
350
|
-
const src = hindi.replace('{#sc1}\n', '{#sc1}\n& synopsis: विक्रम ट्रेन छूटने का सच जानता है।\n')
|
|
351
|
-
const data = extractWorkflowData(parse(romanizeSutra(src, { scope: 'all', variant: 'readable', notice: false })))
|
|
352
|
-
const csv = exportOneLinerCsv(data.scenes)
|
|
353
|
-
expect(csv).toContain('INT. RELAVE SṬEŚAN - RĀT') // report uppercases headings; readable drops only word-final vowels
|
|
354
|
-
expect(csv).toContain('Vikram ṭren chūṭane kā sac jānatā hai.')
|
|
355
|
-
expect(exportCastReportCsv(data.characters)).toContain('"VIKRAM"') // cast report uppercases names
|
|
356
|
-
})
|
|
357
|
-
|
|
358
|
-
it('dialogue scope leaves headings in native script', async () => {
|
|
359
|
-
const doc = parse(romanizeSutra(hindi, { scope: 'dialogue', variant: 'readable' }))
|
|
360
|
-
const blob = await exportToDocx(doc)
|
|
361
|
-
const xml = await (await JSZip.loadAsync(await blob.arrayBuffer())).file('word/document.xml')!.async('text')
|
|
362
|
-
expect(xml).toContain('रेलवे स्टेशन')
|
|
363
|
-
expect(xml).toContain('ham ṭren chūṭ gae, hai nā?')
|
|
364
|
-
})
|
|
365
|
-
})
|