@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
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { AlignmentType } from 'docx'
|
|
2
|
-
import type { IParagraphStyleOptions } from 'docx'
|
|
3
|
-
import { BODY_ELEMENT_KEYS, COVER_ELEMENT_KEYS } from './types.js'
|
|
4
|
-
import { KEEP_WITH_NEXT_ELEMENTS, KEEP_TOGETHER_ELEMENTS } from './pagination.js'
|
|
5
|
-
import type { ScreenplayStyleDefinition, ElementStyle, BodyElementKey, CoverElementKey } from './types.js'
|
|
6
|
-
|
|
7
|
-
/** Sutra element -> stable docx paragraph style id. Fixed forever: docx-importer.ts
|
|
8
|
-
* matches re-imported .docx files against these exact ids, regardless of appearance. */
|
|
9
|
-
const DOCX_STYLE_IDS: Record<BodyElementKey, string> = {
|
|
10
|
-
sceneHeading: 'CineSceneHeading',
|
|
11
|
-
action: 'CineAction',
|
|
12
|
-
character: 'CineCharacter',
|
|
13
|
-
parenthetical: 'CineParenthetical',
|
|
14
|
-
dialogue: 'CineDialogue',
|
|
15
|
-
transition: 'CineTransition',
|
|
16
|
-
centered: 'CineCentered',
|
|
17
|
-
lyrics: 'CineLyrics',
|
|
18
|
-
section: 'CineSection',
|
|
19
|
-
note: 'CineNote',
|
|
20
|
-
comment: 'CineComment',
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Cover-page element -> docx paragraph style id, referenced by docx-exporter.ts's
|
|
24
|
-
* title page. These must be emitted alongside the body styles: a paragraph naming a
|
|
25
|
-
* style the document doesn't define silently falls back to Word's Normal style
|
|
26
|
-
* (left-aligned 11pt black), which is exactly how the title page used to render. */
|
|
27
|
-
const DOCX_COVER_STYLE_IDS: Record<CoverElementKey, string> = {
|
|
28
|
-
title: 'CineTitle',
|
|
29
|
-
author: 'CineAuthor',
|
|
30
|
-
logline: 'CineLogline',
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const ALIGN_MAP = {
|
|
34
|
-
left: AlignmentType.LEFT,
|
|
35
|
-
center: AlignmentType.CENTER,
|
|
36
|
-
right: AlignmentType.RIGHT,
|
|
37
|
-
} as const
|
|
38
|
-
|
|
39
|
-
function pctToTwips(pct: number | undefined, contentWidthTwips: number): number | undefined {
|
|
40
|
-
if (pct === undefined) return undefined
|
|
41
|
-
return Math.round((pct / 100) * contentWidthTwips)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** docx's *style-level* paragraph options type omits `widowControl`, but the paragraph-
|
|
45
|
-
* property serializer it shares with `Paragraph` does emit it (verified: `<w:widowControl/>`
|
|
46
|
-
* lands in the generated word/styles.xml). Widen the type rather than drop the property. */
|
|
47
|
-
type StyleParagraphOptions = NonNullable<IParagraphStyleOptions['paragraph']> & { widowControl?: boolean }
|
|
48
|
-
|
|
49
|
-
/** `pagination` carries the screenplay page-break rules for body elements (see
|
|
50
|
-
* pagination.ts); cover-page elements pass none — a cover page is one page by
|
|
51
|
-
* construction. */
|
|
52
|
-
function elementToDocxStyle(
|
|
53
|
-
id: string,
|
|
54
|
-
el: ElementStyle,
|
|
55
|
-
fontFamily: string,
|
|
56
|
-
contentWidthTwips: number,
|
|
57
|
-
pagination: { keepNext?: boolean; keepLines?: boolean } = {},
|
|
58
|
-
): IParagraphStyleOptions {
|
|
59
|
-
const left = pctToTwips(el.marginLeftPct, contentWidthTwips)
|
|
60
|
-
const right = pctToTwips(el.marginRightPct, contentWidthTwips)
|
|
61
|
-
const paragraph: StyleParagraphOptions = {
|
|
62
|
-
alignment: el.align ? ALIGN_MAP[el.align] : undefined,
|
|
63
|
-
indent: (left !== undefined || right !== undefined) ? { left, right } : undefined,
|
|
64
|
-
spacing: {
|
|
65
|
-
before: el.spaceBeforePt !== undefined ? Math.round(el.spaceBeforePt * 20) : undefined,
|
|
66
|
-
after: el.spaceAfterPt !== undefined ? Math.round(el.spaceAfterPt * 20) : undefined,
|
|
67
|
-
},
|
|
68
|
-
keepNext: pagination.keepNext,
|
|
69
|
-
keepLines: pagination.keepLines,
|
|
70
|
-
// Stated rather than left to Word's default: action/dialogue stay breakable
|
|
71
|
-
// (no keepLines), so widow control is what stops a page break from stranding a
|
|
72
|
-
// single line of them on either side.
|
|
73
|
-
widowControl: true,
|
|
74
|
-
}
|
|
75
|
-
return {
|
|
76
|
-
id,
|
|
77
|
-
name: id,
|
|
78
|
-
run: {
|
|
79
|
-
font: fontFamily,
|
|
80
|
-
size: Math.round(el.fontSizePt * 2),
|
|
81
|
-
bold: el.bold || undefined,
|
|
82
|
-
italics: el.italic || undefined,
|
|
83
|
-
underline: el.underline ? {} : undefined,
|
|
84
|
-
color: el.color,
|
|
85
|
-
},
|
|
86
|
-
paragraph,
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** Builds the `paragraphStyles` array for `new Document({ styles: { paragraphStyles } })`
|
|
91
|
-
* from a style definition. `contentWidthTwips` is the page's printable width (page width
|
|
92
|
-
* minus left+right margins) — percentages are resolved against it, so indentation scales
|
|
93
|
-
* correctly with page size/margins instead of reusing one page format's fixed twips. */
|
|
94
|
-
export function styleToDocxParagraphStyles(
|
|
95
|
-
style: ScreenplayStyleDefinition,
|
|
96
|
-
contentWidthTwips: number,
|
|
97
|
-
): IParagraphStyleOptions[] {
|
|
98
|
-
return [
|
|
99
|
-
...BODY_ELEMENT_KEYS.map(key =>
|
|
100
|
-
elementToDocxStyle(DOCX_STYLE_IDS[key], style.elements[key], style.fontFamily, contentWidthTwips, {
|
|
101
|
-
keepNext: KEEP_WITH_NEXT_ELEMENTS.includes(key) || undefined,
|
|
102
|
-
keepLines: KEEP_TOGETHER_ELEMENTS.includes(key) || undefined,
|
|
103
|
-
})
|
|
104
|
-
),
|
|
105
|
-
...COVER_ELEMENT_KEYS.map(key =>
|
|
106
|
-
elementToDocxStyle(DOCX_COVER_STYLE_IDS[key], style.elements[key], style.fontFamily, contentWidthTwips)
|
|
107
|
-
),
|
|
108
|
-
]
|
|
109
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Embeds self-hosted @font-face rules (from shell/fonts/fonts.css, already loaded into
|
|
3
|
-
* the running app's own document) as data: URIs, for splicing into the PDF/print-preview
|
|
4
|
-
* HTML — a separate, standalone document that never sees the app's own stylesheets.
|
|
5
|
-
*
|
|
6
|
-
* Introspects the CSSOM (`document.styleSheets`) rather than hardcoding filenames/unicode
|
|
7
|
-
* ranges per family: whatever fonts.css declares for a family is exactly what gets embedded,
|
|
8
|
-
* so this can never drift out of sync with the actual font files bundled in public/fonts.
|
|
9
|
-
*
|
|
10
|
-
* Why embed at all instead of just linking `/fonts/...` from the print HTML: the native
|
|
11
|
-
* desktop PDF export writes the HTML to a temp file and loads it via a `file://` URL in a
|
|
12
|
-
* hidden webview, which cannot resolve a root-relative `/fonts/...` path back to the running
|
|
13
|
-
* app's own server/origin. A data: URI works regardless of the HTML's own location.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
function urlToDataUri(url: string): Promise<string> {
|
|
17
|
-
return fetch(url)
|
|
18
|
-
.then(res => {
|
|
19
|
-
if (!res.ok) throw new Error(`Failed to fetch font: ${url}`)
|
|
20
|
-
return res.blob()
|
|
21
|
-
})
|
|
22
|
-
.then(blob => new Promise<string>((resolve, reject) => {
|
|
23
|
-
const reader = new FileReader()
|
|
24
|
-
reader.onload = () => resolve(reader.result as string)
|
|
25
|
-
reader.onerror = () => reject(reader.error ?? new Error('Failed to read font file'))
|
|
26
|
-
reader.readAsDataURL(blob)
|
|
27
|
-
}))
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function findFontFaceRules(familyName: string): CSSFontFaceRule[] {
|
|
31
|
-
const rules: CSSFontFaceRule[] = []
|
|
32
|
-
for (const sheet of Array.from(document.styleSheets)) {
|
|
33
|
-
let cssRules: CSSRuleList
|
|
34
|
-
try {
|
|
35
|
-
cssRules = sheet.cssRules
|
|
36
|
-
} catch {
|
|
37
|
-
continue // cross-origin stylesheets throw on access; nothing we bundle is cross-origin
|
|
38
|
-
}
|
|
39
|
-
for (const rule of Array.from(cssRules)) {
|
|
40
|
-
if (rule instanceof CSSFontFaceRule) {
|
|
41
|
-
const family = rule.style.getPropertyValue('font-family').replace(/^['"]|['"]$/g, '')
|
|
42
|
-
if (family === familyName) rules.push(rule)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return rules
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Builds `@font-face` CSS text embedding every self-hosted rule for the given family
|
|
50
|
-
* names, as data: URIs. Families with no matching rules (e.g. web-safe fonts that were
|
|
51
|
-
* never in fonts.css) are silently skipped — the Google Fonts `<link>`/generic fallback
|
|
52
|
-
* stack in print-adapter.ts's font stack still applies. Fetch failures for an individual
|
|
53
|
-
* rule are likewise skipped rather than failing the whole export. */
|
|
54
|
-
export async function embedFontFacesFor(familyNames: string[]): Promise<string> {
|
|
55
|
-
const blocks: string[] = []
|
|
56
|
-
for (const family of familyNames) {
|
|
57
|
-
for (const rule of findFontFaceRules(family)) {
|
|
58
|
-
const srcRaw = rule.style.getPropertyValue('src')
|
|
59
|
-
const match = /url\(["']?([^"')]+)["']?\)/.exec(srcRaw)
|
|
60
|
-
if (!match) continue
|
|
61
|
-
try {
|
|
62
|
-
const dataUri = await urlToDataUri(match[1]!)
|
|
63
|
-
const weight = rule.style.getPropertyValue('font-weight') || '400'
|
|
64
|
-
const style = rule.style.getPropertyValue('font-style') || 'normal'
|
|
65
|
-
const unicodeRange = rule.style.getPropertyValue('unicode-range')
|
|
66
|
-
blocks.push(
|
|
67
|
-
'@font-face {\n' +
|
|
68
|
-
` font-family: '${family}';\n` +
|
|
69
|
-
` font-weight: ${weight};\n` +
|
|
70
|
-
` font-style: ${style};\n` +
|
|
71
|
-
` src: url(${dataUri}) format('woff2');\n` +
|
|
72
|
-
(unicodeRange ? ` unicode-range: ${unicodeRange};\n` : '') +
|
|
73
|
-
'}'
|
|
74
|
-
)
|
|
75
|
-
} catch {
|
|
76
|
-
// Skip; the Google Fonts link / generic fallback stack still covers this family.
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return blocks.join('\n')
|
|
81
|
-
}
|
package/src/styles/pagination.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { BodyElementKey } from './types.js'
|
|
2
|
-
|
|
3
|
-
/** Screenplay pagination rules — format conventions, not per-style visual choices, so
|
|
4
|
-
* they live here rather than in ScreenplayStyleDefinition: no style (built-in or
|
|
5
|
-
* user-imported) gets to end a page on a character cue. Consumed by both export
|
|
6
|
-
* paths, which express the same rule differently: DOCX via `keepNext`/`keepLines`
|
|
7
|
-
* paragraph properties (docx-adapter.ts), PDF/print via `break-after`/`break-inside`
|
|
8
|
-
* (print-adapter.ts). The live editor has no pagination, so it ignores these. */
|
|
9
|
-
|
|
10
|
-
/** Elements that must not be the last thing on a page — a scene heading or character
|
|
11
|
-
* cue stranded at the bottom with its action/dialogue overleaf is the classic
|
|
12
|
-
* screenplay pagination error. A parenthetical is included for the same reason: it
|
|
13
|
-
* belongs to the dialogue line that follows it. */
|
|
14
|
-
export const KEEP_WITH_NEXT_ELEMENTS: readonly BodyElementKey[] = [
|
|
15
|
-
'sceneHeading', 'character', 'parenthetical', 'section',
|
|
16
|
-
]
|
|
17
|
-
|
|
18
|
-
/** Elements short enough that splitting them across a page boundary is always wrong.
|
|
19
|
-
* Deliberately excludes action/dialogue/lyrics/note — those can legitimately run
|
|
20
|
-
* longer than a page, so they get orphan/widow control instead of an unbreakable box. */
|
|
21
|
-
export const KEEP_TOGETHER_ELEMENTS: readonly BodyElementKey[] = [
|
|
22
|
-
'sceneHeading', 'character', 'parenthetical', 'transition', 'section',
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
/** Minimum lines of a breakable element left on either side of a page boundary. Two is
|
|
26
|
-
* the standard screenplay/typography minimum (a single stranded line reads as an error). */
|
|
27
|
-
export const MIN_ORPHAN_WIDOW_LINES = 2
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
import type { ScreenplayStyleDefinition, ElementStyle } from './types.js'
|
|
2
|
-
import { MIN_ORPHAN_WIDOW_LINES } from './pagination.js'
|
|
3
|
-
|
|
4
|
-
function color(el: ElementStyle): string {
|
|
5
|
-
return el.color ? `#${el.color}` : 'black'
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function fontProps(el: ElementStyle): string {
|
|
9
|
-
const lines = [`font-size: ${el.fontSizePt}pt;`, `color: ${color(el)};`]
|
|
10
|
-
if (el.bold) lines.push('font-weight: bold;')
|
|
11
|
-
if (el.italic) lines.push('font-style: italic;')
|
|
12
|
-
if (el.underline) lines.push('text-decoration: underline;')
|
|
13
|
-
if (el.align) lines.push(`text-align: ${el.align};`)
|
|
14
|
-
return lines.join('\n ')
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Screenplay page-break rules for PDF/print — the CSS half of pagination.ts (DOCX gets
|
|
18
|
-
* the same rules as keepNext/keepLines). `page-break-*` is emitted alongside the modern
|
|
19
|
-
* `break-*` because print engines are uneven about which spelling they honor. */
|
|
20
|
-
const KEEP_WITH_NEXT = `
|
|
21
|
-
break-after: avoid;
|
|
22
|
-
page-break-after: avoid;
|
|
23
|
-
break-inside: avoid;
|
|
24
|
-
page-break-inside: avoid;`
|
|
25
|
-
const KEEP_TOGETHER = `
|
|
26
|
-
break-inside: avoid;
|
|
27
|
-
page-break-inside: avoid;`
|
|
28
|
-
/** For elements that may legitimately run longer than a page: don't forbid the break,
|
|
29
|
-
* just refuse to strand one line on either side of it. */
|
|
30
|
-
const ORPHAN_WIDOW = `
|
|
31
|
-
orphans: ${MIN_ORPHAN_WIDOW_LINES};
|
|
32
|
-
widows: ${MIN_ORPHAN_WIDOW_LINES};`
|
|
33
|
-
|
|
34
|
-
function marginProps(el: ElementStyle): string {
|
|
35
|
-
const lines: string[] = []
|
|
36
|
-
if (el.marginLeftPct !== undefined) lines.push(`margin-left: ${el.marginLeftPct}%;`)
|
|
37
|
-
if (el.marginRightPct !== undefined) lines.push(`margin-right: ${el.marginRightPct}%;`)
|
|
38
|
-
lines.push(`margin-top: ${el.spaceBeforePt ?? 0}pt;`)
|
|
39
|
-
lines.push(`margin-bottom: ${el.spaceAfterPt ?? 0}pt;`)
|
|
40
|
-
return lines.join('\n ')
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Builds the screenplay-specific portion of the PDF/print-preview `<style>` block
|
|
44
|
-
* (packages/app/src/file/workflow-reports.ts's buildScreenplayPrintHtml) from a style
|
|
45
|
-
* definition. UI chrome (print button, page-break rule, dual-dialogue column layout)
|
|
46
|
-
* stays hardcoded in workflow-reports.ts — only the parametrized visual properties live here. */
|
|
47
|
-
export function styleToPrintCss(style: ScreenplayStyleDefinition, pageFormat: 'letter' | 'a4' = 'letter'): string {
|
|
48
|
-
const e = style.elements
|
|
49
|
-
const contentWidthIn = (pageFormat === 'a4' ? 8.27 : 8.5) - style.page.marginLeftIn - style.page.marginRightIn
|
|
50
|
-
// The style's own font first, then its explicit per-script overrides (e.g. Traditional's
|
|
51
|
-
// Noto Serif Tamil) so they win over the generic Indic fallback that always follows — this
|
|
52
|
-
// HTML has no per-span language tagging, so one comprehensive stack has to do the work the
|
|
53
|
-
// editor's .cs-lang-* rules split across classes. Overrides are style-specific, so unlike
|
|
54
|
-
// the fixed Noto Sans fallback list they must come from the style itself, not be hardcoded —
|
|
55
|
-
// otherwise every style would render Indic scripts in whatever the *last*-defined style's
|
|
56
|
-
// overrides asked for.
|
|
57
|
-
const overrideFonts = Object.values(style.complexScriptFontOverrides ?? {})
|
|
58
|
-
// Noto Sans comes right after the style's own fonts: it is the Latin fallback (app spec
|
|
59
|
-
// §4.4) and its bundled file carries every ISO 15919 letter and combining mark, which the
|
|
60
|
-
// Indic families' Latin subsets do not. Its unicode-range stops at Latin, so Indic text
|
|
61
|
-
// still falls through to the script-specific faces below.
|
|
62
|
-
const fontStack = [...new Set([
|
|
63
|
-
`'${style.fontFamily}'`,
|
|
64
|
-
...overrideFonts.map(f => `'${f}'`),
|
|
65
|
-
"'Noto Sans'",
|
|
66
|
-
"'Noto Sans Tamil'", "'Noto Sans Devanagari'", "'Noto Sans Telugu'", "'Noto Sans Kannada'",
|
|
67
|
-
"'Noto Sans Malayalam'", "'Noto Sans Bengali'", "'Noto Sans Gujarati'", "'Noto Sans Gurmukhi'",
|
|
68
|
-
"'Noto Sans Oriya'", 'sans-serif',
|
|
69
|
-
])].join(', ')
|
|
70
|
-
return `
|
|
71
|
-
@page {
|
|
72
|
-
/* Stated, not left to the print dialog's default: print-paginator.ts fills
|
|
73
|
-
fixed-height page boxes, so the paper it fills them for has to be the paper
|
|
74
|
-
the browser actually prints on (and it's the same page: field the DOCX
|
|
75
|
-
export already honors). */
|
|
76
|
-
size: ${pageFormat === 'a4' ? 'A4' : 'letter'};
|
|
77
|
-
margin: ${style.page.marginTopIn}in ${style.page.marginRightIn}in ${style.page.marginBottomIn}in ${style.page.marginLeftIn}in;
|
|
78
|
-
}
|
|
79
|
-
body {
|
|
80
|
-
font-family: ${fontStack};
|
|
81
|
-
margin: 0;
|
|
82
|
-
padding: 0;
|
|
83
|
-
background: white;
|
|
84
|
-
color: black;
|
|
85
|
-
font-size: 11pt;
|
|
86
|
-
}
|
|
87
|
-
/* Cover layouts — one of these two classes is on .print-cover-page, picked by
|
|
88
|
-
the style's coverLayout (see types.ts). Both wrap the same markup: a
|
|
89
|
-
.print-cover-top block (title / by-line / logline) and a .print-cover-bottom
|
|
90
|
-
block (date / contact). */
|
|
91
|
-
.print-cover-centered {
|
|
92
|
-
height: 80vh;
|
|
93
|
-
display: flex;
|
|
94
|
-
flex-direction: column;
|
|
95
|
-
align-items: center;
|
|
96
|
-
justify-content: center;
|
|
97
|
-
text-align: center;
|
|
98
|
-
}
|
|
99
|
-
.print-cover-corners {
|
|
100
|
-
height: 88vh;
|
|
101
|
-
display: flex;
|
|
102
|
-
flex-direction: column;
|
|
103
|
-
justify-content: space-between;
|
|
104
|
-
text-align: left;
|
|
105
|
-
}
|
|
106
|
-
/* Down from the top edge, not glued to it — the reference script starts its
|
|
107
|
-
title roughly a quarter of the way down the page. */
|
|
108
|
-
.print-cover-corners .print-cover-top {
|
|
109
|
-
padding-top: 20%;
|
|
110
|
-
}
|
|
111
|
-
.print-cover-corners .print-cover-bottom {
|
|
112
|
-
align-self: flex-end;
|
|
113
|
-
padding-bottom: 6%;
|
|
114
|
-
max-width: 45%;
|
|
115
|
-
}
|
|
116
|
-
.print-cover-corners .print-cover-logline {
|
|
117
|
-
margin-top: 12pt;
|
|
118
|
-
max-width: 75%;
|
|
119
|
-
}
|
|
120
|
-
/* The corner block is its own visual group: the centered layout's big
|
|
121
|
-
separating margins would push it off the page bottom. */
|
|
122
|
-
.print-cover-corners .print-cover-date,
|
|
123
|
-
.print-cover-corners .print-cover-contact {
|
|
124
|
-
margin-top: 0;
|
|
125
|
-
}
|
|
126
|
-
.print-cover-title {
|
|
127
|
-
${fontProps(e.title)}
|
|
128
|
-
text-transform: uppercase;
|
|
129
|
-
margin-bottom: 20px;
|
|
130
|
-
}
|
|
131
|
-
.print-cover-by {
|
|
132
|
-
font-size: 12pt;
|
|
133
|
-
margin: 10px 0;
|
|
134
|
-
}
|
|
135
|
-
.print-cover-author {
|
|
136
|
-
${fontProps(e.author)}
|
|
137
|
-
}
|
|
138
|
-
.print-cover-logline {
|
|
139
|
-
${fontProps(e.logline)}
|
|
140
|
-
margin-top: 40px;
|
|
141
|
-
max-width: 600px;
|
|
142
|
-
white-space: pre-line;
|
|
143
|
-
}
|
|
144
|
-
.print-cover-date {
|
|
145
|
-
font-size: 10pt;
|
|
146
|
-
margin-top: 50px;
|
|
147
|
-
color: #777;
|
|
148
|
-
}
|
|
149
|
-
.print-cover-contact {
|
|
150
|
-
font-size: 11pt;
|
|
151
|
-
margin-top: 24px;
|
|
152
|
-
color: #888;
|
|
153
|
-
white-space: pre-line;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/* The preview lays out at exactly the printable width, on screen as well as on
|
|
157
|
-
paper. print-paginator.ts measures where the page breaks fall in the live
|
|
158
|
-
window, so if the on-screen line breaks differed from the printed ones (a
|
|
159
|
-
1280px-wide window wraps far later than a 6in column) every page it filled
|
|
160
|
-
would overflow its sheet when printed. */
|
|
161
|
-
#print-flow, #print-pages, .print-cover-page {
|
|
162
|
-
width: ${contentWidthIn}in;
|
|
163
|
-
margin-left: auto;
|
|
164
|
-
margin-right: auto;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/* One printed sheet of body content, built by print-paginator.ts: an exact
|
|
168
|
-
page-height box (set inline, from the paper size) so the browser puts exactly
|
|
169
|
-
one on each sheet, with the running header at its top. */
|
|
170
|
-
.print-page {
|
|
171
|
-
break-after: page;
|
|
172
|
-
page-break-after: always;
|
|
173
|
-
box-sizing: border-box;
|
|
174
|
-
overflow: visible;
|
|
175
|
-
}
|
|
176
|
-
.print-page:last-child {
|
|
177
|
-
break-after: auto;
|
|
178
|
-
page-break-after: auto;
|
|
179
|
-
}
|
|
180
|
-
.print-page-body {
|
|
181
|
-
position: relative;
|
|
182
|
-
display: flow-root; /* so a first child's margin-top counts inside the page */
|
|
183
|
-
}
|
|
184
|
-
.print-page-header {
|
|
185
|
-
display: flex;
|
|
186
|
-
justify-content: space-between;
|
|
187
|
-
align-items: baseline;
|
|
188
|
-
font-size: ${e.action.fontSizePt}pt;
|
|
189
|
-
font-style: italic;
|
|
190
|
-
margin-bottom: 24pt;
|
|
191
|
-
}
|
|
192
|
-
@media screen {
|
|
193
|
-
.print-page {
|
|
194
|
-
border-bottom: 1px dashed #ccc;
|
|
195
|
-
margin-bottom: 24px;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/* Screenplay items */
|
|
200
|
-
.print-scene-heading {
|
|
201
|
-
${fontProps(e.sceneHeading)}
|
|
202
|
-
text-transform: uppercase;
|
|
203
|
-
${marginProps(e.sceneHeading)}
|
|
204
|
-
display: flex;
|
|
205
|
-
justify-content: space-between;${KEEP_WITH_NEXT}
|
|
206
|
-
}
|
|
207
|
-
.print-action {
|
|
208
|
-
${fontProps(e.action)}
|
|
209
|
-
${marginProps(e.action)}
|
|
210
|
-
line-height: 1.4;${ORPHAN_WIDOW}
|
|
211
|
-
}
|
|
212
|
-
.print-character-block {
|
|
213
|
-
margin-top: ${e.character.spaceBeforePt ?? 0}pt;
|
|
214
|
-
margin-bottom: ${e.character.spaceAfterPt ?? 0}pt;
|
|
215
|
-
}
|
|
216
|
-
.print-character {
|
|
217
|
-
${fontProps(e.character)}
|
|
218
|
-
margin-left: ${e.character.marginLeftPct ?? 0}%;
|
|
219
|
-
margin-bottom: 2pt;${KEEP_WITH_NEXT}
|
|
220
|
-
}
|
|
221
|
-
.print-parenthetical {
|
|
222
|
-
${fontProps(e.parenthetical)}
|
|
223
|
-
margin-left: ${e.parenthetical.marginLeftPct ?? 0}%;
|
|
224
|
-
margin-right: ${e.parenthetical.marginRightPct ?? 0}%;
|
|
225
|
-
margin-bottom: 2pt;${KEEP_WITH_NEXT}
|
|
226
|
-
}
|
|
227
|
-
.print-dialogue {
|
|
228
|
-
${fontProps(e.dialogue)}
|
|
229
|
-
${marginProps(e.dialogue)}
|
|
230
|
-
line-height: 1.3;${ORPHAN_WIDOW}
|
|
231
|
-
}
|
|
232
|
-
.print-transition {
|
|
233
|
-
${fontProps(e.transition)}
|
|
234
|
-
${marginProps(e.transition)}
|
|
235
|
-
text-transform: uppercase;${KEEP_TOGETHER}
|
|
236
|
-
}
|
|
237
|
-
.print-centered {
|
|
238
|
-
${fontProps(e.centered)}
|
|
239
|
-
${marginProps(e.centered)}
|
|
240
|
-
}
|
|
241
|
-
.print-lyrics {
|
|
242
|
-
${fontProps(e.lyrics)}
|
|
243
|
-
${marginProps(e.lyrics)}${ORPHAN_WIDOW}
|
|
244
|
-
}
|
|
245
|
-
.print-note {
|
|
246
|
-
background: #fffbeb;
|
|
247
|
-
border-left: 2px solid #d97706;
|
|
248
|
-
padding: 4px 8px;
|
|
249
|
-
${fontProps(e.note)}
|
|
250
|
-
${marginProps(e.note)}
|
|
251
|
-
}
|
|
252
|
-
.print-comment {
|
|
253
|
-
${fontProps(e.comment)}
|
|
254
|
-
${marginProps(e.comment)}
|
|
255
|
-
}
|
|
256
|
-
.print-section {
|
|
257
|
-
${fontProps(e.section)}
|
|
258
|
-
${marginProps(e.section)}${KEEP_WITH_NEXT}
|
|
259
|
-
}
|
|
260
|
-
.print-page-break {
|
|
261
|
-
page-break-after: always;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/* Dual dialogue */
|
|
265
|
-
.print-dual-dialogue {
|
|
266
|
-
display: flex;
|
|
267
|
-
margin-top: 6pt;
|
|
268
|
-
margin-bottom: 6pt;
|
|
269
|
-
}
|
|
270
|
-
.print-dual-column {
|
|
271
|
-
flex: 1;
|
|
272
|
-
min-width: 0;
|
|
273
|
-
}
|
|
274
|
-
.print-left-column {
|
|
275
|
-
margin-right: 20px;
|
|
276
|
-
}
|
|
277
|
-
.print-right-column {
|
|
278
|
-
margin-left: 20px;
|
|
279
|
-
}
|
|
280
|
-
.print-dual-column .print-character {
|
|
281
|
-
margin-left: 10%;
|
|
282
|
-
}
|
|
283
|
-
.print-dual-column .print-parenthetical,
|
|
284
|
-
.print-dual-column .print-dialogue {
|
|
285
|
-
margin-left: 10%;
|
|
286
|
-
margin-right: 5%;
|
|
287
|
-
}`
|
|
288
|
-
}
|
package/src/styles/registry.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { ScreenplayStyleDefinition } from './types.js'
|
|
2
|
-
import { DEFAULT_STYLE_ID } from './types.js'
|
|
3
|
-
import { BUILTIN_STYLES, getBuiltinStyle } from './builtin-styles.js'
|
|
4
|
-
|
|
5
|
-
/** Resolves a style id (usually from frontmatter's `style:` field) to a definition.
|
|
6
|
-
* Falls back to the default style when the id is missing or unrecognized — e.g. a
|
|
7
|
-
* document referencing a custom style the current machine/browser hasn't imported. */
|
|
8
|
-
export function resolveStyle(
|
|
9
|
-
id: string | undefined,
|
|
10
|
-
customStyles: ScreenplayStyleDefinition[],
|
|
11
|
-
): ScreenplayStyleDefinition {
|
|
12
|
-
const wanted = id ?? DEFAULT_STYLE_ID
|
|
13
|
-
return getBuiltinStyle(wanted)
|
|
14
|
-
?? customStyles.find(s => s.id === wanted)
|
|
15
|
-
?? getBuiltinStyle(DEFAULT_STYLE_ID)!
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function allStyles(customStyles: ScreenplayStyleDefinition[]): ScreenplayStyleDefinition[] {
|
|
19
|
-
return [...BUILTIN_STYLES, ...customStyles]
|
|
20
|
-
}
|