@sutrata/editor 0.1.2 → 0.3.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/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 +34 -5
- package/dist/context/DocumentContext.js +152 -51
- package/dist/editor/EditorView.js +35 -5
- package/dist/editor/SelectionMenu.js +5 -3
- package/dist/editor/editor-bus.d.ts +3 -0
- package/dist/editor/editor-bus.js +13 -0
- package/dist/editor/external-change.d.ts +11 -0
- package/dist/editor/external-change.js +105 -0
- package/dist/editor/external-decorations-plugin.d.ts +23 -0
- package/dist/editor/external-decorations-plugin.js +151 -0
- package/dist/editor/frontmatter-field-view.js +3 -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 +13 -0
- package/dist/editor/private-metadata-plugin.js +100 -0
- package/dist/editor/prosemirror-to-sutra.js +179 -74
- package/dist/editor/scene-heading-view.js +2 -0
- package/dist/editor/schema.js +74 -13
- 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 +32 -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/extensions/storage-adapter.d.ts +36 -1
- 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 +18 -3
- 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 +25 -4
- 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 +39 -3
- package/dist/styles/screenplay.css +114 -1
- package/dist/titlepage/ExportDialog.d.ts +5 -0
- package/dist/titlepage/ExportDialog.js +59 -96
- package/dist/titlepage/frontmatter-form.js +1 -1
- package/package.json +4 -4
- 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
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CommandContribution } from '../extensions/command-registry';
|
|
2
|
+
type Translate = (key: string) => string;
|
|
3
|
+
/**
|
|
4
|
+
* The editor's own commands, registered like an embedder's: undo/redo,
|
|
5
|
+
* element types (with their Alt+Shift shortcuts) and inline marks, all shown
|
|
6
|
+
* in the element toolbar. Mark and history keys are bound by the ProseMirror
|
|
7
|
+
* keymap, so they only carry a `shortcutHint`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function builtinCommands(t: Translate): CommandContribution[];
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { makeSetBlock, makeSetOrToggleNote, makeToggleMark, undo, redo } from './toolbar-actions';
|
|
3
|
+
import { runCommand, activeBlockType } from '../editor/editor-bus';
|
|
4
|
+
import { UndoIcon, RedoIcon, SceneIcon, ActionIcon, CharacterIcon, DialogueIcon, ParentheticalIcon, TransitionIcon, CenteredIcon, LyricsIcon, NoteIcon, PageBreakIcon, SectionIcon, BoldIcon, ItalicIcon, UnderlineIcon, } from './icons';
|
|
5
|
+
const ELEMENTS = [
|
|
6
|
+
{ type: 'scene_heading', sigil: '##', shortcut: 'Alt-Shift-H', Icon: SceneIcon, labelKey: 'toolbar.scene' },
|
|
7
|
+
{ type: 'action', sigil: '(plain)', shortcut: 'Alt-Shift-A', Icon: ActionIcon, labelKey: 'toolbar.action' },
|
|
8
|
+
{ type: 'character', sigil: '@', shortcut: 'Alt-Shift-C', Icon: CharacterIcon, labelKey: 'toolbar.character' },
|
|
9
|
+
{ type: 'dialogue', sigil: '(after @)', shortcut: 'Alt-Shift-D', Icon: DialogueIcon, labelKey: 'toolbar.dialogue' },
|
|
10
|
+
{ type: 'parenthetical', sigil: '( )', shortcut: 'Alt-Shift-P', Icon: ParentheticalIcon, labelKey: 'toolbar.parenthetical' },
|
|
11
|
+
{ type: 'transition', sigil: '>>', shortcut: 'Alt-Shift-X', Icon: TransitionIcon, labelKey: 'toolbar.transition' },
|
|
12
|
+
{ type: 'centered', sigil: '>> <<', Icon: CenteredIcon, labelKey: 'toolbar.centered' },
|
|
13
|
+
{ type: 'lyrics', sigil: '~', shortcut: 'Alt-Shift-L', Icon: LyricsIcon, labelKey: 'toolbar.lyrics' },
|
|
14
|
+
{ type: 'note', sigil: '[[ ]]', shortcut: 'Alt-Shift-N', Icon: NoteIcon, labelKey: 'toolbar.note' },
|
|
15
|
+
{ type: 'page_break', sigil: '===', Icon: PageBreakIcon, labelKey: 'toolbar.pageBreak' },
|
|
16
|
+
{ type: 'section', sigil: '#', Icon: SectionIcon, labelKey: 'toolbar.section' },
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* The editor's own commands, registered like an embedder's: undo/redo,
|
|
20
|
+
* element types (with their Alt+Shift shortcuts) and inline marks, all shown
|
|
21
|
+
* in the element toolbar. Mark and history keys are bound by the ProseMirror
|
|
22
|
+
* keymap, so they only carry a `shortcutHint`.
|
|
23
|
+
*/
|
|
24
|
+
export function builtinCommands(t) {
|
|
25
|
+
return [
|
|
26
|
+
{ id: 'history.undo', label: t('toolbar.undo'), group: 'history', menu: 'toolbar', shortcutHint: 'Mod-Z', icon: s => _jsx(UndoIcon, { size: s }), run: () => runCommand(undo) },
|
|
27
|
+
{ id: 'history.redo', label: t('toolbar.redo'), group: 'history', menu: 'toolbar', shortcutHint: 'Mod-Shift-Z', icon: s => _jsx(RedoIcon, { size: s }), run: () => runCommand(redo) },
|
|
28
|
+
...ELEMENTS.map((el) => ({
|
|
29
|
+
id: `element.${el.type}`,
|
|
30
|
+
label: t(el.labelKey),
|
|
31
|
+
group: 'elements',
|
|
32
|
+
menu: 'toolbar',
|
|
33
|
+
sigil: el.sigil,
|
|
34
|
+
shortcut: el.shortcut,
|
|
35
|
+
icon: s => _jsx(el.Icon, { size: s }),
|
|
36
|
+
isActive: () => activeBlockType() === el.type,
|
|
37
|
+
run: () => runCommand(el.type === 'note' ? makeSetOrToggleNote() : makeSetBlock(el.type)),
|
|
38
|
+
})),
|
|
39
|
+
{ id: 'mark.bold', label: t('toolbar.bold'), group: 'marks', menu: 'toolbar', shortcutHint: 'Mod-B', icon: s => _jsx(BoldIcon, { size: s }), run: () => runCommand(makeToggleMark('bold')) },
|
|
40
|
+
{ id: 'mark.italic', label: t('toolbar.italic'), group: 'marks', menu: 'toolbar', shortcutHint: 'Mod-I', icon: s => _jsx(ItalicIcon, { size: s }), run: () => runCommand(makeToggleMark('italic')) },
|
|
41
|
+
{ id: 'mark.underline', label: t('toolbar.underline'), group: 'marks', menu: 'toolbar', shortcutHint: 'Mod-U', icon: s => _jsx(UnderlineIcon, { size: s }), run: () => runCommand(makeToggleMark('underline')) },
|
|
42
|
+
];
|
|
43
|
+
}
|
package/dist/shell/icons.d.ts
CHANGED
|
@@ -35,6 +35,12 @@ export declare const OverflowIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
|
35
35
|
export declare const EyeIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
36
36
|
export declare const EyeOffIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
37
37
|
export declare const LockIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
38
|
+
/** Numbered list: "Renumber scenes". */
|
|
39
|
+
export declare const RenumberIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
40
|
+
/** Circle with a slash: "Omit scene". */
|
|
41
|
+
export declare const OmitIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
42
|
+
/** Curved arrow back: "Restore scene". */
|
|
43
|
+
export declare const RestoreIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
38
44
|
export declare const StatsIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
39
45
|
export declare const HistoryIcon: ({ size }: IconProps) => React.JSX.Element;
|
|
40
46
|
export declare const MicIcon: ({ size }: IconProps) => React.JSX.Element;
|
package/dist/shell/icons.js
CHANGED
|
@@ -42,6 +42,12 @@ export const OverflowIcon = ({ size = 14 }) => (_jsxs("svg", { width: size, heig
|
|
|
42
42
|
export const EyeIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("path", { d: "M1 8s3-6 7-6 7 6 7 6-3 6-7 6-7-6-7-6z" }), _jsx("circle", { cx: "8", cy: "8", r: "2.2" })] }));
|
|
43
43
|
export const EyeOffIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("path", { d: "M1 8s3-6 7-6 7 6 7 6-3 6-7 6-7-6-7-6z" }), _jsx("circle", { cx: "8", cy: "8", r: "2.2" }), _jsx("line", { x1: "2", y1: "2", x2: "14", y2: "14" })] }));
|
|
44
44
|
export const LockIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("rect", { x: "3", y: "7", width: "10", height: "7", rx: "1.5" }), _jsx("path", { d: "M4.5 7V4.5a3.5 3.5 0 017 0V7" })] }));
|
|
45
|
+
/** Numbered list: "Renumber scenes". */
|
|
46
|
+
export const RenumberIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("path", { d: "M2.5 3.5h1v3M2.5 6.5h2" }), _jsx("path", { d: "M2.5 10h2l-2 2.5h2" }), _jsx("path", { d: "M7 4.5h6.5M7 11.5h6.5" })] }));
|
|
47
|
+
/** Circle with a slash: "Omit scene". */
|
|
48
|
+
export const OmitIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("circle", { cx: "8", cy: "8", r: "5.5" }), _jsx("path", { d: "M4.2 11.8l7.6-7.6" })] }));
|
|
49
|
+
/** Curved arrow back: "Restore scene". */
|
|
50
|
+
export const RestoreIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("path", { d: "M3.5 6.5h6a3.5 3.5 0 010 7H6" }), _jsx("path", { d: "M6 3.5l-2.5 3 2.5 3" })] }));
|
|
45
51
|
export const StatsIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("line", { x1: "12", y1: "14", x2: "12", y2: "4" }), _jsx("line", { x1: "8", y1: "14", x2: "8", y2: "7" }), _jsx("line", { x1: "4", y1: "14", x2: "4", y2: "10" })] }));
|
|
46
52
|
export const HistoryIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("circle", { cx: "8", cy: "8.5", r: "6" }), _jsx("polyline", { points: "8,5.5 8,8.5 10.5,10" })] }));
|
|
47
53
|
export const MicIcon = ({ size }) => (_jsxs("svg", { ...base(size), children: [_jsx("rect", { x: "5.5", y: "2", width: "5", height: "8", rx: "2.5" }), _jsx("path", { d: "M3 7.5a5 5 0 0010 0" }), _jsx("line", { x1: "8", y1: "12.5", x2: "8", y2: "14.5" }), _jsx("line", { x1: "5.5", y1: "14.5", x2: "10.5", y2: "14.5" })] }));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CommandContext, CommandContribution } from '../extensions/command-registry';
|
|
2
|
+
/** Built-in + registered commands available in this session (read-only sessions keep only `readOnly` ones). */
|
|
3
|
+
export declare function useCommands(): CommandContribution[];
|
|
4
|
+
/** A CommandContext for the current moment. */
|
|
5
|
+
export declare function useCommandContext(): () => CommandContext;
|
|
6
|
+
/** `Alt-Shift-H` → `Alt+Shift+H` for tooltips. */
|
|
7
|
+
export declare function displayShortcut(shortcut: string | undefined): string | undefined;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useDocument } from '../context/DocumentContext';
|
|
3
|
+
import { useTranslation } from '../i18n/useTranslation';
|
|
4
|
+
import { useCanEdit } from '../extensions/session';
|
|
5
|
+
import { useCommandRegistry } from '../extensions/command-registry';
|
|
6
|
+
import { useRegistryList, withBuiltins } from '../extensions/registry-store';
|
|
7
|
+
import { getEditorView, getSourceView } from '../editor/editor-bus';
|
|
8
|
+
import { builtinCommands } from './builtin-commands';
|
|
9
|
+
/** Built-in + registered commands available in this session (read-only sessions keep only `readOnly` ones). */
|
|
10
|
+
export function useCommands() {
|
|
11
|
+
const { t } = useTranslation();
|
|
12
|
+
const canEdit = useCanEdit();
|
|
13
|
+
const registered = useRegistryList(useCommandRegistry());
|
|
14
|
+
return useMemo(() => withBuiltins(builtinCommands(t), registered).filter(c => canEdit || c.readOnly), [t, registered, canEdit]);
|
|
15
|
+
}
|
|
16
|
+
/** A CommandContext for the current moment. */
|
|
17
|
+
export function useCommandContext() {
|
|
18
|
+
const { mode, text, setText } = useDocument();
|
|
19
|
+
return () => ({ view: getEditorView(), sourceView: getSourceView(), mode, text, setText });
|
|
20
|
+
}
|
|
21
|
+
/** `Alt-Shift-H` → `Alt+Shift+H` for tooltips. */
|
|
22
|
+
export function displayShortcut(shortcut) {
|
|
23
|
+
return shortcut?.split(/[-+](?=.)/).join('+');
|
|
24
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { useDocument } from '../context/DocumentContext';
|
|
3
|
+
import { getEditorView, getSourceView, subscribe } from '../editor/editor-bus';
|
|
4
|
+
import { buildSceneList } from '../navigator/scene-list';
|
|
5
|
+
/** Id of the scene containing the caret (formatted or source editor), if it has one. */
|
|
6
|
+
function currentSceneId(text) {
|
|
7
|
+
const view = getEditorView();
|
|
8
|
+
if (view) {
|
|
9
|
+
const caret = view.state.selection.from;
|
|
10
|
+
let id = null;
|
|
11
|
+
view.state.doc.forEach((node, offset) => {
|
|
12
|
+
if (offset < caret && node.type.name === 'scene_heading')
|
|
13
|
+
id = node.attrs['id'] ?? null;
|
|
14
|
+
});
|
|
15
|
+
return id;
|
|
16
|
+
}
|
|
17
|
+
const source = getSourceView();
|
|
18
|
+
if (source) {
|
|
19
|
+
const caret = source.state.selection.main.head;
|
|
20
|
+
let id = null;
|
|
21
|
+
for (const scene of buildSceneList(text))
|
|
22
|
+
if (scene.textOffset <= caret)
|
|
23
|
+
id = scene.id;
|
|
24
|
+
return id;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
/** The context panels render with; re-computed on document and selection changes. */
|
|
29
|
+
export function usePanelContext() {
|
|
30
|
+
const { ast, text } = useDocument();
|
|
31
|
+
const [activeSceneId, setActiveSceneId] = useState(null);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const update = () => setActiveSceneId(currentSceneId(text));
|
|
34
|
+
update();
|
|
35
|
+
return subscribe(update);
|
|
36
|
+
}, [text]);
|
|
37
|
+
const sceneIds = useMemo(() => ast.children.flatMap(n => (n.type === 'scene-heading' && n.id ? [n.id] : [])), [ast]);
|
|
38
|
+
return useMemo(() => ({ ast, sceneIds, activeSceneId }), [ast, sceneIds, activeSceneId]);
|
|
39
|
+
}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef } from 'react';
|
|
3
|
-
import { EditorState } from '@codemirror/state';
|
|
3
|
+
import { Annotation, Compartment, EditorState, Transaction } from '@codemirror/state';
|
|
4
4
|
import { EditorView, keymap } from '@codemirror/view';
|
|
5
5
|
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
|
|
6
6
|
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
7
7
|
import { tags } from '@lezer/highlight';
|
|
8
8
|
import { sutraLanguage } from './sutra-lang';
|
|
9
9
|
import { useDocument } from '../context/DocumentContext';
|
|
10
|
-
import { setSourceView } from '../editor/editor-bus';
|
|
10
|
+
import { setSourceView, takeExternalText } from '../editor/editor-bus';
|
|
11
11
|
import { sourceFindHighlightExtension } from './find-highlight-source';
|
|
12
|
+
import { useCanEdit } from '../extensions/session';
|
|
13
|
+
/** Marks the transaction that mirrors an external text change into the view. */
|
|
14
|
+
const externalSync = Annotation.define();
|
|
12
15
|
// Light theme matching the app's warm off-white palette.
|
|
13
16
|
// No internal scroll — the editor grows to content height and .cs-main scrolls.
|
|
14
17
|
const sutraTheme = EditorView.theme({
|
|
@@ -54,6 +57,12 @@ const sutraHighlight = HighlightStyle.define([
|
|
|
54
57
|
]);
|
|
55
58
|
export function SourceView() {
|
|
56
59
|
const { text, setText } = useDocument();
|
|
60
|
+
// Read-only sessions: the view is not editable and doc changes other than
|
|
61
|
+
// mirroring external text are filtered out (see the transactionFilter).
|
|
62
|
+
const canEdit = useCanEdit();
|
|
63
|
+
const canEditRef = useRef(canEdit);
|
|
64
|
+
canEditRef.current = canEdit;
|
|
65
|
+
const editableCompartment = useRef(new Compartment());
|
|
57
66
|
const containerRef = useRef(null);
|
|
58
67
|
const viewRef = useRef(null);
|
|
59
68
|
// Track externally-driven text so we don't feed our own updates back
|
|
@@ -72,8 +81,11 @@ export function SourceView() {
|
|
|
72
81
|
syntaxHighlighting(sutraHighlight),
|
|
73
82
|
sourceFindHighlightExtension,
|
|
74
83
|
EditorView.lineWrapping,
|
|
84
|
+
editableCompartment.current.of([EditorView.editable.of(canEdit), EditorState.readOnly.of(!canEdit)]),
|
|
85
|
+
EditorState.transactionFilter.of(tr => tr.docChanged && !canEditRef.current && !tr.annotation(externalSync) ? [] : tr),
|
|
75
86
|
EditorView.updateListener.of(update => {
|
|
76
|
-
|
|
87
|
+
// Mirrored text (externalSync) is already the document's text.
|
|
88
|
+
if (update.docChanged && !update.transactions.some(t => t.annotation(externalSync))) {
|
|
77
89
|
const newText = update.state.doc.toString();
|
|
78
90
|
lastTextRef.current = newText;
|
|
79
91
|
setText(newText);
|
|
@@ -98,9 +110,33 @@ export function SourceView() {
|
|
|
98
110
|
if (!view || text === lastTextRef.current)
|
|
99
111
|
return;
|
|
100
112
|
lastTextRef.current = text;
|
|
113
|
+
if (takeExternalText(text)) {
|
|
114
|
+
// From storage: change only what differs, keep the cursor, not undoable.
|
|
115
|
+
const old = view.state.doc.toString();
|
|
116
|
+
let from = 0;
|
|
117
|
+
while (from < old.length && from < text.length && old[from] === text[from])
|
|
118
|
+
from++;
|
|
119
|
+
let endOld = old.length;
|
|
120
|
+
let endNew = text.length;
|
|
121
|
+
while (endOld > from && endNew > from && old[endOld - 1] === text[endNew - 1]) {
|
|
122
|
+
endOld--;
|
|
123
|
+
endNew--;
|
|
124
|
+
}
|
|
125
|
+
view.dispatch({
|
|
126
|
+
changes: { from, to: endOld, insert: text.slice(from, endNew) },
|
|
127
|
+
annotations: [externalSync.of(true), Transaction.addToHistory.of(false)],
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
101
131
|
view.dispatch({
|
|
102
132
|
changes: { from: 0, to: view.state.doc.length, insert: text },
|
|
133
|
+
annotations: externalSync.of(true),
|
|
103
134
|
});
|
|
104
135
|
}, [text]);
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
viewRef.current?.dispatch({
|
|
138
|
+
effects: editableCompartment.current.reconfigure([EditorView.editable.of(canEdit), EditorState.readOnly.of(!canEdit)]),
|
|
139
|
+
});
|
|
140
|
+
}, [canEdit]);
|
|
105
141
|
return _jsx("div", { ref: containerRef, className: "cs-source-view" });
|
|
106
142
|
}
|
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
--cs-ui-accent-badge: #d97706;
|
|
32
32
|
--cs-ui-accent-border: #fcd34d;
|
|
33
33
|
|
|
34
|
+
/* Warnings (e.g. a scene without an {#id}) — distinct from the amber accent */
|
|
35
|
+
--cs-ui-warning: #c2410c;
|
|
36
|
+
--cs-ui-warning-subtle: #fff7ed;
|
|
37
|
+
|
|
34
38
|
/* Screenplay note tokens */
|
|
35
39
|
--cs-note-bg: #fffbeb;
|
|
36
40
|
--cs-note-border-color: #d97706;
|
|
@@ -91,6 +95,45 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
|
|
|
91
95
|
flex-shrink: 0;
|
|
92
96
|
}
|
|
93
97
|
|
|
98
|
+
/* Sidebar with embedder tabs (only when PanelRegistry has 'sidebar' panels) */
|
|
99
|
+
.cs-sidebar {
|
|
100
|
+
width: min(33%,400px);
|
|
101
|
+
min-width: 180px;
|
|
102
|
+
max-width: 480px;
|
|
103
|
+
display: flex;
|
|
104
|
+
flex-direction: column;
|
|
105
|
+
overflow: hidden;
|
|
106
|
+
flex-shrink: 0;
|
|
107
|
+
border-right: 1px solid #ddd;
|
|
108
|
+
background: #f8f8f6;
|
|
109
|
+
}
|
|
110
|
+
.cs-sidebar > .cs-navigator { width: auto; min-width: 0; max-width: none; flex: 1; border-right: none; }
|
|
111
|
+
.cs-sidebar-tabs { display: flex; gap: 2px; padding: 4px 6px 0; border-bottom: 1px solid var(--cs-ui-border-light, #e2e0d6); }
|
|
112
|
+
.cs-sidebar-tab {
|
|
113
|
+
background: none; border: none; border-bottom: 2px solid transparent; padding: 6px 8px;
|
|
114
|
+
font-size: 12px; font-weight: 600; color: var(--cs-ui-text-light-secondary, #59574f); cursor: pointer;
|
|
115
|
+
}
|
|
116
|
+
.cs-sidebar-tab-active { color: var(--cs-ui-text-light-primary, #1c1b18); border-bottom-color: var(--cs-ui-accent, #c4760a); }
|
|
117
|
+
.cs-sidebar-panel { flex: 1; overflow: auto; padding: 8px 12px; font-size: 12px; }
|
|
118
|
+
|
|
119
|
+
/* Inspector (only when PanelRegistry has 'inspector' panels) */
|
|
120
|
+
.cs-inspector {
|
|
121
|
+
width: min(28%, 320px);
|
|
122
|
+
min-width: 200px;
|
|
123
|
+
flex-shrink: 0;
|
|
124
|
+
overflow-y: auto;
|
|
125
|
+
border-left: 1px solid var(--cs-ui-border-light, #e2e0d6);
|
|
126
|
+
background: var(--cs-ui-bg-panel, #fcfbfa);
|
|
127
|
+
font-size: 12px;
|
|
128
|
+
}
|
|
129
|
+
.cs-inspector-section { padding: 10px 12px; border-bottom: 1px solid var(--cs-ui-border-light-subtle, #eeece4); }
|
|
130
|
+
.cs-inspector-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--cs-ui-text-light-secondary, #59574f); margin-bottom: 6px; }
|
|
131
|
+
@media (max-width: 640px) {
|
|
132
|
+
.cs-inspector { display: none; }
|
|
133
|
+
}
|
|
134
|
+
.cs-ab-text-label, .cs-tb-text-label { font-size: 12px; font-weight: 600; }
|
|
135
|
+
.cs-ab-panel { display: inline-flex; align-items: center; }
|
|
136
|
+
|
|
94
137
|
.cs-nav-count {
|
|
95
138
|
background: #e0e0dd;
|
|
96
139
|
border-radius: 10px;
|
|
@@ -160,6 +203,36 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
|
|
|
160
203
|
border-bottom-color: #1976d2;
|
|
161
204
|
}
|
|
162
205
|
|
|
206
|
+
.cs-nav-scene-missing-id {
|
|
207
|
+
box-shadow: inset 3px 0 0 var(--cs-ui-warning, #c2410c);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.cs-nav-missing-id-icon {
|
|
211
|
+
font-size: 12px;
|
|
212
|
+
line-height: 1.4;
|
|
213
|
+
color: var(--cs-ui-warning, #c2410c);
|
|
214
|
+
flex-shrink: 0;
|
|
215
|
+
align-self: flex-start;
|
|
216
|
+
padding-left: 8px;
|
|
217
|
+
cursor: help;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/* Scene number needs renumbering (format spec §7.4) */
|
|
221
|
+
.cs-nav-scene-number-issue { box-shadow: inset 3px 0 0 var(--cs-ui-warning, #c2410c); }
|
|
222
|
+
.cs-nav-num-issue {
|
|
223
|
+
color: var(--cs-ui-warning, #c2410c) !important;
|
|
224
|
+
background: var(--cs-ui-warning-subtle, #fff7ed);
|
|
225
|
+
border-radius: 3px;
|
|
226
|
+
padding: 0 4px;
|
|
227
|
+
cursor: help;
|
|
228
|
+
}
|
|
229
|
+
.cs-nav-scene-side { display: flex; align-items: flex-start; gap: 2px; flex-shrink: 0; }
|
|
230
|
+
.cs-nav-omit-btn { opacity: 0; transition: opacity 0.1s; }
|
|
231
|
+
.cs-nav-item:hover .cs-nav-omit-btn,
|
|
232
|
+
.cs-nav-item:focus-within .cs-nav-omit-btn,
|
|
233
|
+
.cs-nav-omit-btn:focus-visible { opacity: 1; }
|
|
234
|
+
.cs-nav-scene-omitted .cs-nav-heading { text-decoration: line-through; opacity: 0.7; }
|
|
235
|
+
|
|
163
236
|
.cs-nav-scene-num {
|
|
164
237
|
font-size: 12px;
|
|
165
238
|
font-weight: 600;
|
|
@@ -434,6 +507,12 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
|
|
|
434
507
|
margin-left: max(10%, 8px);
|
|
435
508
|
}
|
|
436
509
|
|
|
510
|
+
/* Tool-private `x-` keys (format spec §7.1) stay in the text but are not shown */
|
|
511
|
+
.cs-scene-metadata[data-private],
|
|
512
|
+
.cs-fm-field[data-private] {
|
|
513
|
+
display: none;
|
|
514
|
+
}
|
|
515
|
+
|
|
437
516
|
/* Synopsis renders as italic prose, visually distinct from key-value metadata */
|
|
438
517
|
.cs-scene-metadata[data-key="synopsis"] {
|
|
439
518
|
font-size: 10pt;
|
|
@@ -2675,9 +2754,12 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
|
|
|
2675
2754
|
padding: 10px 12px;
|
|
2676
2755
|
font-size: 13px;
|
|
2677
2756
|
color: var(--cs-ui-text-light-primary, #1c1b18);
|
|
2678
|
-
min-height: 140px;
|
|
2679
2757
|
line-height: 1.5;
|
|
2680
2758
|
white-space: pre-wrap;
|
|
2759
|
+
/* Long selections scroll inside the pane; .cs-dialog clips overflow. */
|
|
2760
|
+
box-sizing: border-box;
|
|
2761
|
+
height: min(50vh, 420px);
|
|
2762
|
+
overflow-y: auto;
|
|
2681
2763
|
}
|
|
2682
2764
|
.cs-voice-box-formatted {
|
|
2683
2765
|
width: 100%;
|
|
@@ -2689,6 +2771,8 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
|
|
|
2689
2771
|
font-size: 13px;
|
|
2690
2772
|
font-family: 'Courier Prime', Courier, monospace;
|
|
2691
2773
|
color: var(--cs-ui-text-light-primary, #1c1b18);
|
|
2774
|
+
/* Same height as the raw pane so the two read side by side. */
|
|
2775
|
+
height: min(50vh, 420px);
|
|
2692
2776
|
min-height: 140px;
|
|
2693
2777
|
line-height: 1.4;
|
|
2694
2778
|
resize: vertical;
|
|
@@ -3004,3 +3088,32 @@ html, body, #root { height: 100%; margin: 0; padding: 0; overflow: hidden; }
|
|
|
3004
3088
|
.cs-tp-row > :first-child { grid-column: 1 / -1; }
|
|
3005
3089
|
.cs-tp-add { margin-left: 0; }
|
|
3006
3090
|
}
|
|
3091
|
+
|
|
3092
|
+
/* AI-assisted import dialog */
|
|
3093
|
+
.cs-ai-import-body { padding: 14px 16px; max-height: 80vh; overflow-y: auto; font-size: 13px; }
|
|
3094
|
+
.cs-ai-import-hint { margin: 0 0 10px; color: var(--cs-ui-text-light-secondary, #59574f); display: flex; align-items: center; flex-wrap: wrap; gap: 4px; }
|
|
3095
|
+
.cs-ai-import-error { background: #fef2f2; border: 1px solid #fecaca; color: #991b1b; padding: 8px 10px; border-radius: 4px; margin-bottom: 10px; }
|
|
3096
|
+
.cs-ai-import-paste { width: 100%; box-sizing: border-box; font-family: 'Courier New', monospace; font-size: 12px; padding: 8px; border: 1px solid var(--cs-ui-border-light, #e2e0d6); border-radius: 4px; }
|
|
3097
|
+
.cs-ai-import-policy { background: var(--cs-ui-bg-panel-subtle, #f4f2ea); border-radius: 4px; padding: 10px; font-size: 12px; margin-bottom: 10px; white-space: pre-wrap; }
|
|
3098
|
+
.cs-ai-import-agree { display: flex; gap: 8px; align-items: center; font-size: 12px; }
|
|
3099
|
+
.cs-ai-import-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }
|
|
3100
|
+
.cs-ai-import-warnings { margin-bottom: 10px; }
|
|
3101
|
+
.cs-ai-import-review { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
3102
|
+
.cs-ai-import-col-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--cs-ui-text-light-secondary, #59574f); margin-bottom: 6px; }
|
|
3103
|
+
.cs-ai-import-source, .cs-ai-import-block pre { white-space: pre-wrap; word-break: break-word; font-family: 'Courier New', monospace; font-size: 12px; margin: 0; }
|
|
3104
|
+
.cs-ai-import-source { max-height: 60vh; overflow: auto; background: var(--cs-ui-bg-panel-subtle, #f4f2ea); padding: 8px; border-radius: 4px; }
|
|
3105
|
+
.cs-ai-import-blocks { list-style: none; margin: 0; padding: 0; max-height: 60vh; overflow: auto; }
|
|
3106
|
+
.cs-ai-import-block { display: grid; grid-template-columns: 150px 1fr; gap: 8px; align-items: start; padding: 6px 0; border-bottom: 1px solid var(--cs-ui-border-light-subtle, #eeece4); }
|
|
3107
|
+
@media (max-width: 640px) {
|
|
3108
|
+
.cs-ai-import-review { grid-template-columns: 1fr; }
|
|
3109
|
+
.cs-ai-import-block { grid-template-columns: 1fr; }
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
/* Omitted scene (format spec §7.4) */
|
|
3113
|
+
.cs-scene-omitted .cs-scene-heading-text { text-decoration: line-through; opacity: 0.6; }
|
|
3114
|
+
.cs-omitted-badge {
|
|
3115
|
+
display: inline-block; margin-left: 12px; padding: 0 6px; border-radius: 3px;
|
|
3116
|
+
font-size: 0.75em; font-weight: 700; letter-spacing: 0.08em; text-decoration: none;
|
|
3117
|
+
color: var(--cs-ui-warning, #c2410c); background: var(--cs-ui-warning-subtle, #fff7ed);
|
|
3118
|
+
user-select: none; vertical-align: middle;
|
|
3119
|
+
}
|
|
@@ -1 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders every export format from one list: the editor's built-ins
|
|
3
|
+
* (file/builtin-exporters.ts) plus the embedder's ExportRegistry entries.
|
|
4
|
+
* Screenplay formats are cards; reports are grouped by `family` with chips.
|
|
5
|
+
*/
|
|
1
6
|
export declare function ExportDialog(): import("react").JSX.Element;
|