smartrte-react 0.3.5 → 1.0.0-beta.10
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/CHANGELOG.md +55 -0
- package/README.md +205 -14
- package/dist/adapters/domChecklistCommandBridge.js +1 -1
- package/dist/adapters/domCommandBridge.d.ts +2 -2
- package/dist/adapters/domCommandBridge.js +2 -2
- package/dist/adapters/domInlineAtomCommandBridge.d.ts +10 -9
- package/dist/adapters/domInlineAtomCommandBridge.js +154 -50
- package/dist/adapters/domInlineImageCommandBridge.d.ts +7 -7
- package/dist/adapters/domInlineImageCommandBridge.js +6 -72
- package/dist/adapters/domMoveCommandBridge.js +1 -1
- package/dist/adapters/domSelectionBridge.d.ts +3 -3
- package/dist/adapters/domSmartDocument.d.ts +4 -4
- package/dist/adapters/domSmartDocument.js +2 -2
- package/dist/adapters/domTableCommandBridge.d.ts +42 -16
- package/dist/adapters/domTableCommandBridge.js +203 -173
- package/dist/adapters/internalFlags.d.ts +0 -8
- package/dist/adapters/internalFlags.js +0 -24
- package/dist/adapters/legacyListShadowComparator.d.ts +1 -1
- package/dist/adapters/legacyListShadowComparator.js +1 -1
- package/dist/adapters/pdfPrint.d.ts +8 -0
- package/dist/adapters/pdfPrint.js +22 -0
- package/dist/adapters/shadowMode.d.ts +3 -3
- package/dist/adapters/shadowMode.js +1 -1
- package/dist/canonicalEditorRuntime.d.ts +65 -1
- package/dist/canonicalEditorRuntime.js +63 -5
- package/dist/capabilityPresets.d.ts +27 -0
- package/dist/capabilityPresets.js +21 -0
- package/dist/collabTransport.d.ts +66 -0
- package/dist/collabTransport.js +1 -0
- package/dist/commentProvider.d.ts +24 -0
- package/dist/commentProvider.js +1 -0
- package/dist/components/CanonicalAuthorityEditor.d.ts +56 -1
- package/dist/components/CanonicalAuthorityEditor.js +1304 -116
- package/dist/components/ClassicEditorAuthority.d.ts +93 -22
- package/dist/components/ClassicEditorAuthority.js +11 -43
- package/dist/components/ColorPickerPopover.d.ts +39 -0
- package/dist/components/ColorPickerPopover.js +287 -0
- package/dist/components/CommentMarkers.d.ts +25 -0
- package/dist/components/CommentMarkers.js +105 -0
- package/dist/components/CommentThreadPanel.d.ts +26 -0
- package/dist/components/CommentThreadPanel.js +47 -0
- package/dist/components/ContextMenu.d.ts +23 -0
- package/dist/components/ContextMenu.js +141 -0
- package/dist/components/FormulaLibraryPopover.d.ts +16 -0
- package/dist/components/FormulaLibraryPopover.js +107 -0
- package/dist/components/LinkEditorPopover.d.ts +3 -1
- package/dist/components/LinkEditorPopover.js +22 -6
- package/dist/components/MediaDetailsPopover.d.ts +35 -0
- package/dist/components/MediaDetailsPopover.js +136 -0
- package/dist/components/MediaManager.d.ts +4 -20
- package/dist/components/MediaManager.js +40 -11
- package/dist/components/MediaOverlay.d.ts +27 -0
- package/dist/components/MediaOverlay.js +228 -0
- package/dist/components/SpecialCharacterPopover.d.ts +18 -0
- package/dist/components/SpecialCharacterPopover.js +99 -0
- package/dist/components/StructuralSuggestionMarkers.d.ts +20 -0
- package/dist/components/StructuralSuggestionMarkers.js +56 -0
- package/dist/components/SuggestionPanel.d.ts +26 -0
- package/dist/components/SuggestionPanel.js +33 -0
- package/dist/components/TableBorderPopover.d.ts +59 -0
- package/dist/components/TableBorderPopover.js +149 -0
- package/dist/components/TableResizeHandles.d.ts +25 -0
- package/dist/components/TableResizeHandles.js +323 -0
- package/dist/components/TableSizePickerPopover.d.ts +16 -0
- package/dist/components/TableSizePickerPopover.js +126 -0
- package/dist/components/ToolbarPrimitives.d.ts +161 -0
- package/dist/components/ToolbarPrimitives.js +237 -0
- package/dist/components/VersionHistoryPanel.d.ts +9 -0
- package/dist/components/VersionHistoryPanel.js +157 -0
- package/dist/components/fixedPositioning.d.ts +25 -0
- package/dist/components/fixedPositioning.js +42 -0
- package/dist/formulaLibrary.d.ts +31 -0
- package/dist/formulaLibrary.js +90 -0
- package/dist/index.d.ts +25 -24
- package/dist/index.js +15 -11
- package/dist/mediaManagerAdapter.d.ts +12 -0
- package/dist/mediaManagerAdapter.js +34 -0
- package/dist/mediaProvider.d.ts +21 -1
- package/dist/specialCharacters.d.ts +22 -0
- package/dist/specialCharacters.js +86 -0
- package/dist/standalone/classic-editor-embed.js +19 -12
- package/dist/suggestionProvider.d.ts +17 -0
- package/dist/suggestionProvider.js +1 -0
- package/dist/theme.d.ts +1 -1
- package/dist/theme.js +219 -30
- package/dist/toolbarTools.d.ts +95 -0
- package/dist/toolbarTools.js +17 -0
- package/dist/versionProvider.d.ts +29 -0
- package/dist/versionProvider.js +1 -0
- package/package.json +28 -14
- package/dist/adapters/canonicalInlineCommandBridge.d.ts +0 -15
- package/dist/adapters/canonicalInlineCommandBridge.js +0 -269
- package/dist/adapters/canonicalListCommandBridge.d.ts +0 -26
- package/dist/adapters/canonicalListCommandBridge.js +0 -430
- package/dist/adapters/coreBold.d.ts +0 -5
- package/dist/adapters/coreBold.js +0 -3
- package/dist/adapters/documentFormats.d.ts +0 -26
- package/dist/adapters/documentFormats.js +0 -49
- package/dist/adapters/docxFormat.d.ts +0 -5
- package/dist/adapters/docxFormat.js +0 -272
- package/dist/adapters/domBlockCommandBridge.d.ts +0 -19
- package/dist/adapters/domBlockCommandBridge.js +0 -79
- package/dist/adapters/inlineMarkCoreExecution.d.ts +0 -9
- package/dist/adapters/inlineMarkCoreExecution.js +0 -25
- package/dist/adapters/pdfFormat.d.ts +0 -23
- package/dist/adapters/pdfFormat.js +0 -204
- package/dist/adapters/portableDocxAtoms.d.ts +0 -4
- package/dist/adapters/portableDocxAtoms.js +0 -58
- package/dist/adapters/styledDocxFormat.d.ts +0 -8
- package/dist/adapters/styledDocxFormat.js +0 -190
- package/dist/builtInFormatDefinitions.d.ts +0 -5
- package/dist/builtInFormatDefinitions.js +0 -82
- package/dist/canonicalAuthorityFlag.d.ts +0 -32
- package/dist/canonicalAuthorityFlag.js +0 -49
- package/dist/canonicalClipboardRuntime.d.ts +0 -15
- package/dist/canonicalClipboardRuntime.js +0 -76
- package/dist/components/ClassicEditor.d.ts +0 -76
- package/dist/components/ClassicEditor.js +0 -6729
- package/dist/editorController.d.ts +0 -78
- package/dist/editorController.js +0 -298
- package/dist/formatFidelity.d.ts +0 -14
- package/dist/formatFidelity.js +0 -107
- package/dist/formatRuntime.d.ts +0 -50
- package/dist/formatRuntime.js +0 -20
- package/dist/pluginRuntime.d.ts +0 -68
- package/dist/pluginRuntime.js +0 -78
- package/dist/standalone/editor.js +0 -1068
- package/dist/test-harness/atomShadowComparator.d.ts +0 -14
- package/dist/test-harness/atomShadowComparator.js +0 -63
- package/dist/test-harness/blockShadowComparator.d.ts +0 -30
- package/dist/test-harness/blockShadowComparator.js +0 -140
- package/dist/test-harness/clipboardShadowComparator.d.ts +0 -12
- package/dist/test-harness/clipboardShadowComparator.js +0 -46
- package/dist/test-harness/inlineShadowComparator.d.ts +0 -32
- package/dist/test-harness/inlineShadowComparator.js +0 -152
- package/dist/test-harness/legacyAtomEngine.d.ts +0 -10
- package/dist/test-harness/legacyAtomEngine.js +0 -15
- package/dist/test-harness/legacyBlockEngine.d.ts +0 -31
- package/dist/test-harness/legacyBlockEngine.js +0 -31
- package/dist/test-harness/legacyClipboardEngine.d.ts +0 -14
- package/dist/test-harness/legacyClipboardEngine.js +0 -67
- package/dist/test-harness/legacyInlineEngine.d.ts +0 -19
- package/dist/test-harness/legacyInlineEngine.js +0 -49
- package/dist/test-harness/legacyTableEngine.d.ts +0 -12
- package/dist/test-harness/legacyTableEngine.js +0 -25
- package/dist/test-harness/tableShadowComparator.d.ts +0 -29
- package/dist/test-harness/tableShadowComparator.js +0 -101
|
@@ -1,30 +1,6 @@
|
|
|
1
|
-
const markFlagNames = {
|
|
2
|
-
bold: "coreBold",
|
|
3
|
-
italic: "coreItalic",
|
|
4
|
-
underline: "coreUnderline",
|
|
5
|
-
superscript: "coreSuperscript",
|
|
6
|
-
subscript: "coreSubscript",
|
|
7
|
-
};
|
|
8
|
-
const legacyMarkFlagNames = {
|
|
9
|
-
bold: "__SMART_RTE_CORE_BOLD__",
|
|
10
|
-
italic: "__SMART_RTE_CORE_ITALIC__",
|
|
11
|
-
underline: "__SMART_RTE_CORE_UNDERLINE__",
|
|
12
|
-
superscript: "__SMART_RTE_CORE_SUPERSCRIPT__",
|
|
13
|
-
subscript: "__SMART_RTE_CORE_SUBSCRIPT__",
|
|
14
|
-
};
|
|
15
1
|
const getGlobal = () => globalThis;
|
|
16
2
|
const readBoolean = (value) => typeof value === "boolean" ? value : undefined;
|
|
17
3
|
export const getSmartRteInternalFlags = () => getGlobal().__SMART_RTE_INTERNAL_FLAGS__ || {};
|
|
18
|
-
export const isCoreInlineMarkFlagEnabled = (mark) => {
|
|
19
|
-
const flags = getSmartRteInternalFlags();
|
|
20
|
-
const individual = readBoolean(flags[markFlagNames[mark]]);
|
|
21
|
-
if (individual !== undefined)
|
|
22
|
-
return individual;
|
|
23
|
-
const grouped = readBoolean(flags.coreInlineMarks);
|
|
24
|
-
if (grouped !== undefined)
|
|
25
|
-
return grouped;
|
|
26
|
-
return getGlobal()[legacyMarkFlagNames[mark]] === true;
|
|
27
|
-
};
|
|
28
4
|
export const isShadowModeFlagEnabled = () => {
|
|
29
5
|
const configured = readBoolean(getSmartRteInternalFlags().shadowMode);
|
|
30
6
|
if (configured !== undefined)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { applyOperations, compareShadowDocuments, createTransactionMap, createList, createScopeIndex, foundationSchema, indentList, moveListItems, outdentList, setListChecked, setListPreset, setListStyle, shadowLogRecord, unwrapList, isTextNode, } from "smartrte-core";
|
|
1
|
+
import { applyOperations, compareShadowDocuments, createTransactionMap, createList, createScopeIndex, foundationSchema, indentList, moveListItems, outdentList, setListChecked, setListPreset, setListStyle, shadowLogRecord, unwrapList, isTextNode, } from "smartrte-core/foundation";
|
|
2
2
|
import { applyTransaction as applyLegacyTransaction, indentListItems as legacyIndentListItems, moveBlocks as legacyMoveBlocks, outdentListItems as legacyOutdentListItems, setChecklistItemChecked as legacySetChecklistItemChecked, toggleList as legacyToggleList, } from "smartrte-core/legacy";
|
|
3
3
|
const p = (id, text) => ({ type: "paragraph", id, children: [{ type: "text", text }] });
|
|
4
4
|
const item = (id, text, nested) => ({ type: "list_item", id, children: [p(`${id}-p`, text), ...(nested ? [nested] : [])] });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SmartDocument } from "smartrte-core/foundation";
|
|
2
|
+
/**
|
|
3
|
+
* The only genuinely browser-specific piece of PDF export: opening a print
|
|
4
|
+
* window is a windowing operation, not format logic, so it stays here
|
|
5
|
+
* rather than in packages/core. buildPdfPrintDocument (the actual HTML
|
|
6
|
+
* content) is framework-agnostic and lives in core.
|
|
7
|
+
*/
|
|
8
|
+
export declare const printSmartDocumentAsPdf: (document: SmartDocument, hostWindow: Window, delayMs?: number) => boolean;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { buildPdfPrintDocument } from "smartrte-core/foundation";
|
|
2
|
+
/**
|
|
3
|
+
* The only genuinely browser-specific piece of PDF export: opening a print
|
|
4
|
+
* window is a windowing operation, not format logic, so it stays here
|
|
5
|
+
* rather than in packages/core. buildPdfPrintDocument (the actual HTML
|
|
6
|
+
* content) is framework-agnostic and lives in core.
|
|
7
|
+
*/
|
|
8
|
+
export const printSmartDocumentAsPdf = (document, hostWindow, delayMs = 150) => {
|
|
9
|
+
const printWindow = hostWindow.open("", "_blank", "width=900,height=700");
|
|
10
|
+
if (!printWindow)
|
|
11
|
+
return false;
|
|
12
|
+
printWindow.addEventListener("load", () => {
|
|
13
|
+
printWindow.setTimeout(() => {
|
|
14
|
+
printWindow.focus();
|
|
15
|
+
printWindow.print();
|
|
16
|
+
}, delayMs);
|
|
17
|
+
}, { once: true });
|
|
18
|
+
printWindow.document.open();
|
|
19
|
+
printWindow.document.write(buildPdfPrintDocument(document));
|
|
20
|
+
printWindow.document.close();
|
|
21
|
+
return true;
|
|
22
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type LegacyCommandContext, type SmartCommand, type SmartEditorState, type LegacySmartTransaction } from "smartrte-core/legacy";
|
|
2
2
|
export interface ShadowComparison {
|
|
3
3
|
commandId: string;
|
|
4
4
|
matches: boolean;
|
|
@@ -15,9 +15,9 @@ export declare const reportShadowDifference: (comparison: ShadowComparison) => v
|
|
|
15
15
|
*/
|
|
16
16
|
export declare const runShadowCommand: <Input>(args: {
|
|
17
17
|
command: SmartCommand<Input>;
|
|
18
|
-
context:
|
|
18
|
+
context: LegacyCommandContext;
|
|
19
19
|
input?: Input;
|
|
20
20
|
state: SmartEditorState;
|
|
21
21
|
legacyHtml: string;
|
|
22
22
|
serialize: (state: SmartEditorState) => string;
|
|
23
|
-
}) =>
|
|
23
|
+
}) => LegacySmartTransaction | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { applyTransaction, normalizeCompatibilityHtml, } from "smartrte-core";
|
|
1
|
+
import { applyTransaction, normalizeCompatibilityHtml, } from "smartrte-core/legacy";
|
|
2
2
|
import { isShadowModeFlagEnabled } from "./internalFlags.js";
|
|
3
3
|
const semanticHtml = (html) => normalizeCompatibilityHtml(html)
|
|
4
4
|
.replace(/<(\/?)b(?=[\s>])/g, "<$1strong")
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type CanonicalInputPipeline, type ClipboardDiagnosticReport, type CanonicalSubtreeRenderer, type FoundationEditor, type PersistedEditorDocument, type SmartMark, type SmartOperation, type SmartSelection, type SmartTransaction } from "smartrte-core/foundation";
|
|
1
|
+
import { type CanonicalInputPipeline, type ClipboardDiagnosticReport, type CanonicalSubtreeRenderer, type DocumentVersion, type FoundationEditor, type PersistedEditorDocument, type SmartMark, type SmartOperation, type SmartSelection, type SmartTransaction } from "smartrte-core/foundation";
|
|
2
|
+
import { type EditorCapabilityPreset } from "./capabilityPresets.js";
|
|
2
3
|
export interface SmartEditorCheckpoint {
|
|
3
4
|
envelope: PersistedEditorDocument;
|
|
4
5
|
selection: SmartSelection;
|
|
@@ -22,6 +23,14 @@ export interface SmartEditorHandle {
|
|
|
22
23
|
executeOperations(operations: readonly SmartOperation[], opts?: ExecuteOperationsOptions): void;
|
|
23
24
|
createCheckpoint(): SmartEditorCheckpoint;
|
|
24
25
|
restoreCheckpoint(checkpoint: SmartEditorCheckpoint): void;
|
|
26
|
+
saveVersion(opts?: SaveVersionOptions): DocumentVersion;
|
|
27
|
+
restoreVersion(version: DocumentVersion, opts?: {
|
|
28
|
+
keepSelection?: boolean;
|
|
29
|
+
}): void;
|
|
30
|
+
}
|
|
31
|
+
export interface SaveVersionOptions {
|
|
32
|
+
label?: string;
|
|
33
|
+
authorId?: string;
|
|
25
34
|
}
|
|
26
35
|
export interface ExecuteOperationsOptions {
|
|
27
36
|
historyGroup?: string;
|
|
@@ -37,6 +46,28 @@ export interface CanonicalEditorRuntimeOptions {
|
|
|
37
46
|
onChange?: (change: SmartEditorChange) => void;
|
|
38
47
|
onHtmlChange?: (html: string) => void;
|
|
39
48
|
onClipboardDiagnostic?: (report: ClipboardDiagnosticReport) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Renderer-integrated content-visibility (Phase 11 Tier 3, opt-in,
|
|
51
|
+
* default off) - only applies content-visibility:auto to a top-level
|
|
52
|
+
* block the renderer's own diff already proved untouched this render
|
|
53
|
+
* pass and that isn't the actively-selected block. See
|
|
54
|
+
* surface/renderer.ts's syncContentVisibility for why this differs from
|
|
55
|
+
* the disproven naive per-block experiment.
|
|
56
|
+
*/
|
|
57
|
+
contentVisibility?: boolean;
|
|
58
|
+
/** Which built-in plugins this instance is constructed with - see capabilityPresets.ts. Defaults to "full" (every plugin), matching this runtime's only behavior before this option existed. */
|
|
59
|
+
preset?: EditorCapabilityPreset;
|
|
60
|
+
/**
|
|
61
|
+
* Bakes real KaTeX-rendered HTML into `onHtmlChange`'s formula elements
|
|
62
|
+
* instead of leaving them as empty placeholders (docs/bugs/
|
|
63
|
+
* formula-not-rendered-in-static-html-consumers.md). Off by default -
|
|
64
|
+
* the live editing surface already renders formulas correctly on its
|
|
65
|
+
* own via surface/renderer.ts's imperative katex.render() calls, which
|
|
66
|
+
* this option has no effect on; it exists purely for consumers that take
|
|
67
|
+
* this HTML string and display it *without* also running KaTeX against
|
|
68
|
+
* it themselves (e.g. a read-only preview panel built from saved HTML).
|
|
69
|
+
*/
|
|
70
|
+
renderFormulaHtml?: boolean;
|
|
40
71
|
}
|
|
41
72
|
/** Persistent, React-independent owner for one product editor instance. */
|
|
42
73
|
export declare class CanonicalEditorRuntime implements SmartEditorHandle {
|
|
@@ -49,6 +80,8 @@ export declare class CanonicalEditorRuntime implements SmartEditorHandle {
|
|
|
49
80
|
private onChange?;
|
|
50
81
|
private onHtmlChange?;
|
|
51
82
|
private readonly onClipboardDiagnostic?;
|
|
83
|
+
private readonly contentVisibility;
|
|
84
|
+
private readonly renderFormulaHtml;
|
|
52
85
|
private htmlChangeTimer;
|
|
53
86
|
private pendingHtmlDocument;
|
|
54
87
|
constructor(options?: CanonicalEditorRuntimeOptions);
|
|
@@ -67,6 +100,37 @@ export declare class CanonicalEditorRuntime implements SmartEditorHandle {
|
|
|
67
100
|
executeOperations(operations: readonly SmartOperation[], opts?: ExecuteOperationsOptions): void;
|
|
68
101
|
createCheckpoint(): SmartEditorCheckpoint;
|
|
69
102
|
restoreCheckpoint(checkpoint: SmartEditorCheckpoint): void;
|
|
103
|
+
/**
|
|
104
|
+
* A pure read of current state, like `createCheckpoint` - never touches
|
|
105
|
+
* the undo stack, never a `transact()` call. The runtime doesn't call a
|
|
106
|
+
* `VersionProvider` itself (same division as `mediaProvider.upload`
|
|
107
|
+
* being called directly from the React component, not the runtime) -
|
|
108
|
+
* this just builds the payload a caller then hands to a provider.
|
|
109
|
+
*/
|
|
110
|
+
saveVersion(opts?: SaveVersionOptions): DocumentVersion;
|
|
111
|
+
/**
|
|
112
|
+
* Reuses `replaceValue` (the same "load a full snapshot" mechanism
|
|
113
|
+
* `restoreCheckpoint` already uses) rather than replaying the version as
|
|
114
|
+
* a sequence of operations - a version can be arbitrarily old, and
|
|
115
|
+
* `replaceState` already validates/repairs an incoming document the same
|
|
116
|
+
* way loading any document does. Deliberately does NOT reuse the
|
|
117
|
+
* version's own stored `revision`: `replaceState` sets the live
|
|
118
|
+
* revision counter to whatever it's given verbatim, so restoring an old
|
|
119
|
+
* version's envelope as-is would roll a monotonic counter backward -
|
|
120
|
+
* colliding with `StaleTransactionError`'s bookkeeping and this
|
|
121
|
+
* runtime's own `isDirty()` comparison. Always re-stamps to
|
|
122
|
+
* `currentRevision + 1` instead. Not undoable, same as checkpoint
|
|
123
|
+
* restore today (`replaceState`'s emitted transaction always sets
|
|
124
|
+
* `addToHistory: false`). Per the Phase 12a spec's explicit
|
|
125
|
+
* recommendation, restore is non-destructive of version history at the
|
|
126
|
+
* UI layer: the caller (`VersionHistoryPanel`) saves a new version
|
|
127
|
+
* recording the restored state immediately after calling this, rather
|
|
128
|
+
* than this method silently discarding anything - the version list only
|
|
129
|
+
* ever grows, never rewrites.
|
|
130
|
+
*/
|
|
131
|
+
restoreVersion(version: DocumentVersion, opts?: {
|
|
132
|
+
keepSelection?: boolean;
|
|
133
|
+
}): void;
|
|
70
134
|
/** Instrumentation for product-path composition and takeover tests. */
|
|
71
135
|
get surface(): {
|
|
72
136
|
root: HTMLElement;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { createFoundationEditor, applyOperations, createInputPipeline, createSubtreeRenderer, createTransactionMap, foundationSchema, isTextNode, parseCanonicalListHtml, serializeCanonicalListHtml, } from "smartrte-core/foundation";
|
|
1
|
+
import { createFoundationEditor, applyOperations, createInputPipeline, createNodeId, createSubtreeRenderer, createTransactionMap, foundationSchema, isTextNode, parseCanonicalListHtml, serializeCanonicalListHtml, } from "smartrte-core/foundation";
|
|
2
|
+
import { capabilityPresetRegistry } from "./capabilityPresets.js";
|
|
2
3
|
const nodeAtPath = (root, path) => {
|
|
3
4
|
let node = root;
|
|
4
5
|
for (const index of path) {
|
|
@@ -29,7 +30,11 @@ const firstTextSelection = (document) => {
|
|
|
29
30
|
return null;
|
|
30
31
|
const spec = foundationSchema.nodes[node.type];
|
|
31
32
|
const children = node.children || [];
|
|
32
|
-
|
|
33
|
+
// Atomic nodes (e.g. block_image) never accept a text caret inside them,
|
|
34
|
+
// even when they report an empty children array - only a genuine
|
|
35
|
+
// text-content container (an empty or all-inline block) is a valid
|
|
36
|
+
// first-text-selection target.
|
|
37
|
+
if (spec?.group === "block" && !spec.atomic && (children.length === 0 || children.every((child) => isTextNode(child) || foundationSchema.nodes[child.type]?.group === "inline")))
|
|
33
38
|
return { path, offset: 0 };
|
|
34
39
|
for (let index = 0; index < children.length; index += 1) {
|
|
35
40
|
const found = visit(children[index], [...path, index]);
|
|
@@ -78,15 +83,23 @@ export class CanonicalEditorRuntime {
|
|
|
78
83
|
this.htmlChangeTimer = null;
|
|
79
84
|
this.pendingHtmlDocument = null;
|
|
80
85
|
const envelope = envelopeFrom(options.initialValue);
|
|
86
|
+
// "full" (the default) intentionally passes no schema/commands/etc., so
|
|
87
|
+
// FoundationEditor's own defaulting (`options.schema || foundationSchema`)
|
|
88
|
+
// applies - every existing consumer who never sets `preset` is
|
|
89
|
+
// byte-for-byte unaffected by this option existing.
|
|
90
|
+
const registry = options.preset && options.preset !== "full" ? capabilityPresetRegistry(options.preset) : null;
|
|
81
91
|
this.editor = createFoundationEditor({
|
|
82
92
|
document: envelope.document,
|
|
83
93
|
revision: envelope.revision,
|
|
84
94
|
selection: firstTextSelection(envelope.document),
|
|
95
|
+
...(registry ? { schema: registry.schema, commands: registry.commands, keyboardShortcuts: registry.keyboardShortcuts, contextMenu: registry.contextMenu } : {}),
|
|
85
96
|
});
|
|
86
97
|
this.savedRevision = envelope.revision;
|
|
87
98
|
this.onChange = options.onChange;
|
|
88
99
|
this.onHtmlChange = options.onHtmlChange;
|
|
89
100
|
this.onClipboardDiagnostic = options.onClipboardDiagnostic;
|
|
101
|
+
this.contentVisibility = options.contentVisibility === true;
|
|
102
|
+
this.renderFormulaHtml = options.renderFormulaHtml === true;
|
|
90
103
|
}
|
|
91
104
|
setCallbacks(onChange, onHtmlChange) {
|
|
92
105
|
this.onChange = onChange;
|
|
@@ -108,7 +121,7 @@ export class CanonicalEditorRuntime {
|
|
|
108
121
|
const pending = this.pendingHtmlDocument;
|
|
109
122
|
this.pendingHtmlDocument = null;
|
|
110
123
|
if (pending && this.onHtmlChange)
|
|
111
|
-
this.onHtmlChange(serializeCanonicalListHtml(pending, { clean: true }));
|
|
124
|
+
this.onHtmlChange(serializeCanonicalListHtml(pending, { clean: true, renderFormulaHtml: this.renderFormulaHtml }));
|
|
112
125
|
};
|
|
113
126
|
if (view) {
|
|
114
127
|
// Transitional HTML serialization is intentionally debounced outside the
|
|
@@ -124,7 +137,7 @@ export class CanonicalEditorRuntime {
|
|
|
124
137
|
return;
|
|
125
138
|
this.unmount();
|
|
126
139
|
this.root = root;
|
|
127
|
-
this.renderer = createSubtreeRenderer(root);
|
|
140
|
+
this.renderer = createSubtreeRenderer(root, { contentVisibility: this.contentVisibility });
|
|
128
141
|
this.pipeline = createInputPipeline(this.editor, this.renderer, root, { onClipboardDiagnostic: this.onClipboardDiagnostic });
|
|
129
142
|
this.unsubscribe = this.editor.subscribe((transaction, state) => {
|
|
130
143
|
this.renderer?.render(this.editor.document, this.editor.selection);
|
|
@@ -251,7 +264,14 @@ export class CanonicalEditorRuntime {
|
|
|
251
264
|
addToHistory: opts.addToHistory ?? true,
|
|
252
265
|
...(opts.historyGroup ? { historyGroup: opts.historyGroup } : {}),
|
|
253
266
|
});
|
|
254
|
-
|
|
267
|
+
// Skipped for a non-history (preview) operation: focusing the main
|
|
268
|
+
// editor surface would steal keyboard/pointer focus away from whatever
|
|
269
|
+
// UI is driving the preview (e.g. ColorPickerPopover's native color
|
|
270
|
+
// input mid-drag) on every single preview frame - breaking Escape-to-
|
|
271
|
+
// cancel and, in a real browser, potentially interrupting the drag
|
|
272
|
+
// itself. A real, history-eligible commit still focuses as before.
|
|
273
|
+
if (opts.addToHistory ?? true)
|
|
274
|
+
this.focus();
|
|
255
275
|
}
|
|
256
276
|
createCheckpoint() {
|
|
257
277
|
return {
|
|
@@ -268,6 +288,44 @@ export class CanonicalEditorRuntime {
|
|
|
268
288
|
});
|
|
269
289
|
this.savedRevision = checkpoint.savedRevision;
|
|
270
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* A pure read of current state, like `createCheckpoint` - never touches
|
|
293
|
+
* the undo stack, never a `transact()` call. The runtime doesn't call a
|
|
294
|
+
* `VersionProvider` itself (same division as `mediaProvider.upload`
|
|
295
|
+
* being called directly from the React component, not the runtime) -
|
|
296
|
+
* this just builds the payload a caller then hands to a provider.
|
|
297
|
+
*/
|
|
298
|
+
saveVersion(opts = {}) {
|
|
299
|
+
return { id: createNodeId(), createdAt: Date.now(), envelope: this.getValue(), ...(opts.label ? { label: opts.label } : {}), ...(opts.authorId ? { authorId: opts.authorId } : {}) };
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Reuses `replaceValue` (the same "load a full snapshot" mechanism
|
|
303
|
+
* `restoreCheckpoint` already uses) rather than replaying the version as
|
|
304
|
+
* a sequence of operations - a version can be arbitrarily old, and
|
|
305
|
+
* `replaceState` already validates/repairs an incoming document the same
|
|
306
|
+
* way loading any document does. Deliberately does NOT reuse the
|
|
307
|
+
* version's own stored `revision`: `replaceState` sets the live
|
|
308
|
+
* revision counter to whatever it's given verbatim, so restoring an old
|
|
309
|
+
* version's envelope as-is would roll a monotonic counter backward -
|
|
310
|
+
* colliding with `StaleTransactionError`'s bookkeeping and this
|
|
311
|
+
* runtime's own `isDirty()` comparison. Always re-stamps to
|
|
312
|
+
* `currentRevision + 1` instead. Not undoable, same as checkpoint
|
|
313
|
+
* restore today (`replaceState`'s emitted transaction always sets
|
|
314
|
+
* `addToHistory: false`). Per the Phase 12a spec's explicit
|
|
315
|
+
* recommendation, restore is non-destructive of version history at the
|
|
316
|
+
* UI layer: the caller (`VersionHistoryPanel`) saves a new version
|
|
317
|
+
* recording the restored state immediately after calling this, rather
|
|
318
|
+
* than this method silently discarding anything - the version list only
|
|
319
|
+
* ever grows, never rewrites.
|
|
320
|
+
*/
|
|
321
|
+
restoreVersion(version, opts = {}) {
|
|
322
|
+
const restamped = {
|
|
323
|
+
schemaVersion: version.envelope.schemaVersion,
|
|
324
|
+
revision: this.getRevision() + 1,
|
|
325
|
+
document: version.envelope.document,
|
|
326
|
+
};
|
|
327
|
+
this.replaceValue(restamped, opts);
|
|
328
|
+
}
|
|
271
329
|
/** Instrumentation for product-path composition and takeover tests. */
|
|
272
330
|
get surface() { return { root: this.root, renderer: this.renderer, pipeline: this.pipeline }; }
|
|
273
331
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type PluginRegistry } from "smartrte-core/foundation";
|
|
2
|
+
/**
|
|
3
|
+
* A named, host-facing restriction of which built-in plugins an editor
|
|
4
|
+
* instance is constructed with - a small extension of Phase 10's existing
|
|
5
|
+
* plugin enable/disable mechanism (`createPluginRegistry`,
|
|
6
|
+
* `docs/PLUGIN_ARCHITECTURE.md`), not new architecture. `FoundationEditor`
|
|
7
|
+
* already accepted a custom `schema`/`commands`/`keyboardShortcuts`/
|
|
8
|
+
* `contextMenu` (`editor.ts`'s `FoundationEditorOptions`) since Phase 10 -
|
|
9
|
+
* nothing here builds new core capability, it just gives the react layer a
|
|
10
|
+
* convenient, named way to reach for it.
|
|
11
|
+
*
|
|
12
|
+
* This is an editor-construction-time, host/integrator-level setting - not
|
|
13
|
+
* end-user-facing toolbar customization. There is no UI for a person typing
|
|
14
|
+
* in the editor to change their own preset.
|
|
15
|
+
*
|
|
16
|
+
* Real-world driver: Sootr's question-style content (MCQ, Anomaly, PYEQ)
|
|
17
|
+
* deliberately excludes tables (`enableTable={false}` in every one of those
|
|
18
|
+
* call sites - see `docs/SOOTR_MIGRATION_READINESS.md` gap #3), while its
|
|
19
|
+
* study-material block editor wants everything. Two presets, not more,
|
|
20
|
+
* because that is the one dimension any real caller has ever needed to
|
|
21
|
+
* vary; nothing in this codebase's history has asked to disable media or
|
|
22
|
+
* formula independently of each other (they're both owned by the single
|
|
23
|
+
* "atom" plugin today, which would need splitting to support that -
|
|
24
|
+
* deliberately out of scope here, since nothing needs it yet).
|
|
25
|
+
*/
|
|
26
|
+
export type EditorCapabilityPreset = "simple" | "full";
|
|
27
|
+
export declare const capabilityPresetRegistry: (preset: EditorCapabilityPreset) => PluginRegistry;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { baseSchema, builtInPlugins, createPluginRegistry, foundationSchema } from "smartrte-core/foundation";
|
|
2
|
+
/** Plugin ids to exclude for each preset - "full" excludes nothing, matching today's only behavior exactly (so every existing consumer who never sets `preset` is 100% unaffected). */
|
|
3
|
+
const PRESET_EXCLUDED_PLUGIN_IDS = {
|
|
4
|
+
simple: ["table"],
|
|
5
|
+
full: [],
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Registries are real work to build (schema construction + validation, per
|
|
9
|
+
* `createPluginRegistry`) - computed once per preset at module load, not
|
|
10
|
+
* per editor instance, since the preset set is small and fixed.
|
|
11
|
+
*/
|
|
12
|
+
const registryCache = new Map();
|
|
13
|
+
export const capabilityPresetRegistry = (preset) => {
|
|
14
|
+
const cached = registryCache.get(preset);
|
|
15
|
+
if (cached)
|
|
16
|
+
return cached;
|
|
17
|
+
const excluded = new Set(PRESET_EXCLUDED_PLUGIN_IDS[preset]);
|
|
18
|
+
const registry = createPluginRegistry(builtInPlugins.filter((plugin) => !excluded.has(plugin.id)), { baseSchema, schemaVersion: foundationSchema.version });
|
|
19
|
+
registryCache.set(preset, registry);
|
|
20
|
+
return registry;
|
|
21
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { SmartSelection, SmartTransaction } from "smartrte-core/foundation";
|
|
2
|
+
/**
|
|
3
|
+
* A remote collaborator's live cursor/selection, mirroring
|
|
4
|
+
* `DocumentVersion`'s "reuse the existing shape, add nothing new" approach:
|
|
5
|
+
* `selection` is the same `SmartSelection` the editor already tracks
|
|
6
|
+
* locally, so it can be mapped through a rebase with the exact same
|
|
7
|
+
* `mapOperation`-based machinery the annotation-range and comment
|
|
8
|
+
* primitives already use (`FoundationTransactionMap`), not a bespoke
|
|
9
|
+
* position type needing its own mapping logic.
|
|
10
|
+
*
|
|
11
|
+
* This is a data shape only - rendering a remote cursor from this
|
|
12
|
+
* (a colored caret, a name label, etc.) is host/UI work, the same boundary
|
|
13
|
+
* `MediaProvider` draws around actual upload UI: the package defines what
|
|
14
|
+
* a presence update *is*, not how it looks on screen.
|
|
15
|
+
*/
|
|
16
|
+
export interface PresenceUpdate {
|
|
17
|
+
readonly authorId: string;
|
|
18
|
+
readonly selection?: SmartSelection;
|
|
19
|
+
readonly lastActiveAt: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Host-owned realtime transport boundary, mirroring `MediaProvider`'s and
|
|
23
|
+
* `VersionProvider`'s shape and contract: the host implements the actual
|
|
24
|
+
* connection (WebSocket, WebRTC, long-polling, whatever - nothing here
|
|
25
|
+
* assumes a specific transport), the package never holds a socket or
|
|
26
|
+
* server credential itself. There is deliberately no shipped default
|
|
27
|
+
* implementation, exactly like `MediaProvider`/`VersionProvider` - when a
|
|
28
|
+
* host doesn't supply one, the editor behaves exactly as it does today:
|
|
29
|
+
* single-writer, `dispatch()`'s rebase path is never triggered because
|
|
30
|
+
* `baseRevision` always matches (see `FoundationEditor.dispatch`).
|
|
31
|
+
*
|
|
32
|
+
* This interface is Phase 12b-client's contract only - no implementation
|
|
33
|
+
* of it ships from this package. The actual server/socket work
|
|
34
|
+
* (12b-server/transport) is a separate effort building *against* this
|
|
35
|
+
* contract, not part of it.
|
|
36
|
+
*/
|
|
37
|
+
export interface CollabTransport {
|
|
38
|
+
/**
|
|
39
|
+
* Submit a local transaction. `accepted: false` (e.g. a server-side
|
|
40
|
+
* permissions check failing) is a distinct outcome from a rebase
|
|
41
|
+
* conflict - see `revision`, present only when accepted, for the
|
|
42
|
+
* transaction's resulting revision as recorded by the transport's own
|
|
43
|
+
* source of truth.
|
|
44
|
+
*/
|
|
45
|
+
sendTransaction(transaction: SmartTransaction): Promise<{
|
|
46
|
+
accepted: boolean;
|
|
47
|
+
revision?: number;
|
|
48
|
+
}>;
|
|
49
|
+
/** Fires for every transaction the transport delivers from another author, in commit order. Returns an unsubscribe function. */
|
|
50
|
+
onRemoteTransaction(handler: (transaction: SmartTransaction, revision: number) => void): () => void;
|
|
51
|
+
/** Fires whenever another author's presence changes (selection moved, went idle, etc.). Returns an unsubscribe function. */
|
|
52
|
+
onPresenceUpdate(handler: (presence: PresenceUpdate) => void): () => void;
|
|
53
|
+
/** Broadcast this client's own presence. Fire-and-forget - unlike sendTransaction, presence has no durability or ordering guarantee to report back. */
|
|
54
|
+
sendPresence(update: PresenceUpdate): void;
|
|
55
|
+
/**
|
|
56
|
+
* Transactions committed since `sinceRevision`, oldest first - what a
|
|
57
|
+
* client needs to catch up after a disconnect, or what
|
|
58
|
+
* `FoundationEditor.dispatch`'s own rebase path would ask for if this
|
|
59
|
+
* editor's local revision log had already evicted that far back (see
|
|
60
|
+
* `FoundationEditorOptions.revisionLogLimit`). An empty array means
|
|
61
|
+
* nothing has changed since; the transport signaling "that revision no
|
|
62
|
+
* longer exists" (e.g. server-side history compaction) is a
|
|
63
|
+
* transport-specific error, not modeled here.
|
|
64
|
+
*/
|
|
65
|
+
getRevisionHistory(sinceRevision: number): Promise<readonly SmartTransaction[]>;
|
|
66
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CommentThread } from "smartrte-core/foundation";
|
|
2
|
+
/**
|
|
3
|
+
* Host-owned comment-storage boundary, mirroring `VersionProvider`/
|
|
4
|
+
* `MediaProvider`'s shape and "the editor never persists anything itself"
|
|
5
|
+
* contract. Unlike `VersionProvider`, there is no separate cheap-listing
|
|
6
|
+
* projection: a `CommentThread` (an id, a range, a handful of short replies)
|
|
7
|
+
* is already small enough that a host's `list()` can return full threads
|
|
8
|
+
* directly, with no equivalent of `VersionProvider`'s envelope/entry split
|
|
9
|
+
* needed to avoid shipping large payloads up front.
|
|
10
|
+
*
|
|
11
|
+
* `save` both creates a new thread and persists an updated one (a reply
|
|
12
|
+
* added, `resolved` toggled) - the caller always passes the complete
|
|
13
|
+
* `CommentThread` it wants stored, so there is one code path for both,
|
|
14
|
+
* exactly like `CommentReply`'s own "one path for adding a message"
|
|
15
|
+
* decision in `foundation/comments/types.ts`. There is deliberately no
|
|
16
|
+
* shipped default implementation, exactly like `VersionProvider`/
|
|
17
|
+
* `MediaProvider` - when a host doesn't supply one, the comments feature is
|
|
18
|
+
* simply unavailable.
|
|
19
|
+
*/
|
|
20
|
+
export interface CommentProvider {
|
|
21
|
+
list(): Promise<readonly CommentThread[]>;
|
|
22
|
+
save(thread: CommentThread): Promise<void>;
|
|
23
|
+
remove(threadId: string): Promise<void>;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,19 +1,74 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { type PersistedEditorDocument, type ClipboardDiagnosticReport } from "smartrte-core/foundation";
|
|
3
3
|
import type { MediaProvider } from "../mediaProvider.js";
|
|
4
|
+
import type { VersionProvider } from "../versionProvider.js";
|
|
5
|
+
import type { CommentProvider } from "../commentProvider.js";
|
|
6
|
+
import type { SuggestionProvider } from "../suggestionProvider.js";
|
|
4
7
|
import { type MediaPickerComponent } from "./MediaPicker.js";
|
|
8
|
+
import type { EditorCapabilityPreset } from "../capabilityPresets.js";
|
|
9
|
+
import { type ToolbarTools } from "../toolbarTools.js";
|
|
5
10
|
import { CanonicalEditorRuntime, type SmartEditorChange, type SmartEditorHandle } from "../canonicalEditorRuntime.js";
|
|
6
11
|
export interface CanonicalAuthorityEditorProps {
|
|
7
12
|
/** Initial value only. Later replacements must use SmartEditorHandle.replaceValue. */
|
|
8
13
|
defaultValue?: string | PersistedEditorDocument;
|
|
14
|
+
/**
|
|
15
|
+
* Which built-in plugins this instance is constructed with - see
|
|
16
|
+
* capabilityPresets.ts. Construction-time only, matching `defaultValue`'s
|
|
17
|
+
* own uncontrolled-after-mount contract (the underlying runtime is
|
|
18
|
+
* created once and retained; changing this prop on an already-mounted
|
|
19
|
+
* instance has no effect). Defaults to "full" - every existing consumer
|
|
20
|
+
* that never sets this is unaffected.
|
|
21
|
+
*/
|
|
22
|
+
preset?: EditorCapabilityPreset;
|
|
9
23
|
onChange?: (change: SmartEditorChange) => void;
|
|
10
24
|
/** Transitional serialization callback for hosts that still persist HTML. */
|
|
11
25
|
onHtmlChange?: (html: string) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Bakes real KaTeX-rendered HTML into onHtmlChange's formula elements
|
|
28
|
+
* instead of leaving them as empty placeholders - see
|
|
29
|
+
* canonicalEditorRuntime.ts's CanonicalEditorRuntimeOptions for the full
|
|
30
|
+
* rationale. Construction-time only, same contract as `preset`.
|
|
31
|
+
*/
|
|
32
|
+
renderFormulaHtml?: boolean;
|
|
12
33
|
onClipboardDiagnostic?: (report: ClipboardDiagnosticReport) => void;
|
|
13
34
|
/** Host-owned upload/search/remove boundary for canonical media insertion. */
|
|
14
35
|
mediaProvider?: MediaProvider;
|
|
15
|
-
/** Replaceable picker; the default only selects a local file. */
|
|
36
|
+
/** Replaceable picker; the default only selects a local file. Used for video/audio, and for images when mediaManager is false. */
|
|
16
37
|
mediaPicker?: MediaPickerComponent;
|
|
38
|
+
/**
|
|
39
|
+
* Phase 11.5 §2.1: a library/search/duplicate-detection picker for
|
|
40
|
+
* images, replacing the bare file-input default. Defaults to true
|
|
41
|
+
* (MediaManager, adapted via mediaManagerAdapterFrom) whenever
|
|
42
|
+
* mediaProvider is supplied; set to false to keep the simple mediaPicker
|
|
43
|
+
* for images too.
|
|
44
|
+
*/
|
|
45
|
+
mediaManager?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Per-tool toolbar visibility - "developers should be able to hide tools
|
|
48
|
+
* which are not for their use" (docs/bugs/per-tool-toolbar-visibility.md).
|
|
49
|
+
* Every tool defaults to visible; a host only needs to name the ones they
|
|
50
|
+
* want off (`tools={{ video: false, versionHistory: false }}`). Composes
|
|
51
|
+
* with, never overrides, the deeper existing gates: a tool whose required
|
|
52
|
+
* provider is absent (`versionProvider`/`commentProvider`/
|
|
53
|
+
* `suggestionProvider`/`mediaProvider`) or whose plugin was excluded via
|
|
54
|
+
* `preset` (e.g. `insertTable` under `preset="simple"`) still won't
|
|
55
|
+
* render even if explicitly set to `true` here - see ToolbarTools.ts's
|
|
56
|
+
* own doc comment for the full contract and scoping decisions.
|
|
57
|
+
*/
|
|
58
|
+
tools?: Partial<ToolbarTools>;
|
|
59
|
+
/** Host-owned save/list/load/remove boundary for document version history. */
|
|
60
|
+
versionProvider?: VersionProvider;
|
|
61
|
+
/** Host-owned save/list/remove boundary for comment threads. Absent hides the comment toolbar buttons and markers entirely, mirroring versionProvider's absent-disables-the-feature contract. */
|
|
62
|
+
commentProvider?: CommentProvider;
|
|
63
|
+
/**
|
|
64
|
+
* Host-owned save/list/remove boundary for *structural* suggestions only
|
|
65
|
+
* (see suggestionProvider.ts) - inline suggestions need no provider since
|
|
66
|
+
* they are marks embedded in the document itself. Absent hides the
|
|
67
|
+
* suggestion toolbar buttons, markers, and panel entirely.
|
|
68
|
+
*/
|
|
69
|
+
suggestionProvider?: SuggestionProvider;
|
|
70
|
+
/** Attributed to new comment replies and suggestions. Defaults to "anonymous" when absent. */
|
|
71
|
+
authorId?: string;
|
|
17
72
|
placeholder?: string;
|
|
18
73
|
minHeight?: number | string;
|
|
19
74
|
maxHeight?: number | string;
|