@ephia/dova-sdk 1.0.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 +89 -0
- package/dist/EphiaBinding-BvRmlqqC.d.ts +36 -0
- package/dist/EphiaFloatingButton-CxiF86VW.d.ts +65 -0
- package/dist/EphiaTextarea-B4_CAVUg.d.ts +183 -0
- package/dist/NativeBinding-ChG0GeSz.d.ts +53 -0
- package/dist/TargetBinding-BKGQwUMc.d.ts +89 -0
- package/dist/TiptapBinding-B-agfV2H.d.ts +45 -0
- package/dist/Transport-zdeA4Pou.d.ts +63 -0
- package/dist/audio-state-kZ3KSvux.d.ts +39 -0
- package/dist/chunk-35AJK2IO.js +1 -0
- package/dist/chunk-35AJK2IO.js.map +1 -0
- package/dist/chunk-3LXZODL4.js +886 -0
- package/dist/chunk-3LXZODL4.js.map +1 -0
- package/dist/chunk-5IK5TLSK.js +67 -0
- package/dist/chunk-5IK5TLSK.js.map +1 -0
- package/dist/chunk-7E43RY75.js +9 -0
- package/dist/chunk-7E43RY75.js.map +1 -0
- package/dist/chunk-A5UEXJ5R.js +183 -0
- package/dist/chunk-A5UEXJ5R.js.map +1 -0
- package/dist/chunk-AEE554FT.js +51 -0
- package/dist/chunk-AEE554FT.js.map +1 -0
- package/dist/chunk-DIEWY3IT.js +1332 -0
- package/dist/chunk-DIEWY3IT.js.map +1 -0
- package/dist/chunk-EGIAN7FH.js +18 -0
- package/dist/chunk-EGIAN7FH.js.map +1 -0
- package/dist/chunk-EMOEAPVU.js +486 -0
- package/dist/chunk-EMOEAPVU.js.map +1 -0
- package/dist/chunk-IDC7FHIZ.js +40 -0
- package/dist/chunk-IDC7FHIZ.js.map +1 -0
- package/dist/chunk-ITJFN3VM.js +601 -0
- package/dist/chunk-ITJFN3VM.js.map +1 -0
- package/dist/chunk-K24GNU27.js +22 -0
- package/dist/chunk-K24GNU27.js.map +1 -0
- package/dist/chunk-LXMCRXXF.js +778 -0
- package/dist/chunk-LXMCRXXF.js.map +1 -0
- package/dist/chunk-MJCEOOLW.js +122 -0
- package/dist/chunk-MJCEOOLW.js.map +1 -0
- package/dist/chunk-N7U5M3VZ.js +33 -0
- package/dist/chunk-N7U5M3VZ.js.map +1 -0
- package/dist/chunk-PSYX674B.js +27 -0
- package/dist/chunk-PSYX674B.js.map +1 -0
- package/dist/chunk-RFQRV7ML.js +33 -0
- package/dist/chunk-RFQRV7ML.js.map +1 -0
- package/dist/chunk-THNHRV2B.js +18 -0
- package/dist/chunk-THNHRV2B.js.map +1 -0
- package/dist/chunk-VSLGR64U.js +62 -0
- package/dist/chunk-VSLGR64U.js.map +1 -0
- package/dist/chunk-W2ZP674X.js +346 -0
- package/dist/chunk-W2ZP674X.js.map +1 -0
- package/dist/chunk-YWZUMUYE.js +695 -0
- package/dist/chunk-YWZUMUYE.js.map +1 -0
- package/dist/client-options-Uo6jXO8k.d.ts +64 -0
- package/dist/connection-state-Bk33YprE.d.ts +32 -0
- package/dist/core/bindings/index.d.ts +24 -0
- package/dist/core/bindings/index.js +1025 -0
- package/dist/core/bindings/index.js.map +1 -0
- package/dist/core/index.d.ts +383 -0
- package/dist/core/index.js +1284 -0
- package/dist/core/index.js.map +1 -0
- package/dist/createEphiaClient-BhdZ183V.d.ts +69 -0
- package/dist/devices/speechmike/index.d.ts +148 -0
- package/dist/devices/speechmike/index.js +40 -0
- package/dist/devices/speechmike/index.js.map +1 -0
- package/dist/headless/index.d.ts +10 -0
- package/dist/headless/index.js +25 -0
- package/dist/headless/index.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +119 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.d.ts +38 -0
- package/dist/react/index.js +70 -0
- package/dist/react/index.js.map +1 -0
- package/dist/rich-editor/index.d.ts +46 -0
- package/dist/rich-editor/index.js +13 -0
- package/dist/rich-editor/index.js.map +1 -0
- package/dist/schema-B2ycPlNB.d.ts +87 -0
- package/dist/session-APaXR48R.d.ts +12 -0
- package/dist/shared/index.d.ts +16 -0
- package/dist/shared/index.js +30 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/style.css +1093 -0
- package/dist/testing/index.d.ts +84 -0
- package/dist/testing/index.js +36 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/types-D5SXPSwR.d.ts +32 -0
- package/dist/ui/index.d.ts +30 -0
- package/dist/ui/index.js +34 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/useEphiaSpeechMike-CjD7DWnh.d.ts +64 -0
- package/package.json +110 -0
- package/src/core/audio/audio-worklet-source.ts +30 -0
- package/src/core/audio/index.ts +3 -0
- package/src/core/audio/voice-level-meter.test.ts +27 -0
- package/src/core/audio/voice-level-meter.ts +270 -0
- package/src/core/bindings/EphiaBinding.ts +41 -0
- package/src/core/bindings/SegmentBindingBridge.test.ts +422 -0
- package/src/core/bindings/SegmentBindingBridge.ts +377 -0
- package/src/core/bindings/TargetBinding.ts +142 -0
- package/src/core/bindings/adapters/NativeAdapter.test.ts +85 -0
- package/src/core/bindings/adapters/NativeAdapter.ts +216 -0
- package/src/core/bindings/adapters/ProseMirrorAdapter.ts +231 -0
- package/src/core/bindings/adapters/index.ts +2 -0
- package/src/core/bindings/binding-factory.ts +78 -0
- package/src/core/bindings/detect-editor-type.ts +87 -0
- package/src/core/bindings/index.ts +13 -0
- package/src/core/bindings/insertion-boundary.test.ts +38 -0
- package/src/core/bindings/insertion-boundary.ts +26 -0
- package/src/core/bindings/native/NativeBinding.test.ts +277 -0
- package/src/core/bindings/native/NativeBinding.ts +239 -0
- package/src/core/bindings/resolver.ts +18 -0
- package/src/core/bindings/targets/codemirror.binding.ts +293 -0
- package/src/core/bindings/targets/contenteditable.binding.ts +452 -0
- package/src/core/bindings/targets/index.ts +10 -0
- package/src/core/bindings/targets/monaco.binding.ts +315 -0
- package/src/core/bindings/targets/tiptap.binding.test.ts +417 -0
- package/src/core/bindings/targets/tiptap.binding.ts +1192 -0
- package/src/core/bindings/tiptap/TiptapBinding.test.ts +63 -0
- package/src/core/bindings/tiptap/TiptapBinding.ts +464 -0
- package/src/core/bindings/types.ts +41 -0
- package/src/core/client/EphiaAudioClient.ts +654 -0
- package/src/core/client/audio-capture.ts +263 -0
- package/src/core/client/client-options.ts +39 -0
- package/src/core/client/client-state.ts +18 -0
- package/src/core/client/constants.ts +23 -0
- package/src/core/client/session-api.ts +415 -0
- package/src/core/connection/connection-state.ts +78 -0
- package/src/core/connection/index.ts +6 -0
- package/src/core/index.ts +47 -0
- package/src/core/operations/textToDocumentOperations.test.ts +69 -0
- package/src/core/operations/textToDocumentOperations.ts +92 -0
- package/src/core/runtime/DictationRuntime.test.ts +578 -0
- package/src/core/runtime/DictationRuntime.ts +434 -0
- package/src/core/runtime/TranscriptApplier.test.ts +355 -0
- package/src/core/runtime/TranscriptApplier.ts +229 -0
- package/src/core/runtime/index.ts +18 -0
- package/src/core/session/index.ts +2 -0
- package/src/core/session/session-machine.test.ts +16 -0
- package/src/core/session/session-machine.ts +59 -0
- package/src/core/targets/EditorContextCollector.ts +71 -0
- package/src/core/targets/TargetManager.test.ts +194 -0
- package/src/core/targets/TargetManager.ts +194 -0
- package/src/core/targets/index.ts +10 -0
- package/src/core/text-processing/index.ts +11 -0
- package/src/core/text-processing/overlap.test.ts +35 -0
- package/src/core/text-processing/overlap.ts +101 -0
- package/src/core/text-processing/voice-formatting.normalizer.test.ts +132 -0
- package/src/core/text-processing/voice-formatting.normalizer.ts +284 -0
- package/src/core/transcript/client-transcript.reducer.ts +366 -0
- package/src/core/transcript/client-transcript.state.ts +25 -0
- package/src/core/transcript/index.ts +19 -0
- package/src/core/transcript/transcript.assembler.test.ts +205 -0
- package/src/core/transcript/transcript.assembler.ts +152 -0
- package/src/core/transcript/transcript.reducer.test.ts +199 -0
- package/src/core/transcript/transcript.reducer.ts +771 -0
- package/src/core/transcript/transcript.state.ts +123 -0
- package/src/core/transport/LiveKitTransport.publish.test.ts +226 -0
- package/src/core/transport/LiveKitTransport.ts +459 -0
- package/src/core/transport/MockTransport.ts +231 -0
- package/src/core/transport/Transport.ts +82 -0
- package/src/debug/sdk-debug-collector.ts +79 -0
- package/src/devices/index.ts +2 -0
- package/src/devices/speechmike/__tests__/EphiaSpeechMikeProvider.test.tsx +99 -0
- package/src/devices/speechmike/__tests__/speechmike-audio-resolver.test.ts +96 -0
- package/src/devices/speechmike/__tests__/speechmike-button-router.test.ts +66 -0
- package/src/devices/speechmike/__tests__/speechmike-device-manager.test.ts +201 -0
- package/src/devices/speechmike/__tests__/speechmike-led-controller.test.ts +68 -0
- package/src/devices/speechmike/browser.ts +80 -0
- package/src/devices/speechmike/constants.ts +74 -0
- package/src/devices/speechmike/dictation-support-loader.ts +81 -0
- package/src/devices/speechmike/index.ts +11 -0
- package/src/devices/speechmike/react/EphiaSpeechMikeContext.ts +34 -0
- package/src/devices/speechmike/react/EphiaSpeechMikeProvider.tsx +287 -0
- package/src/devices/speechmike/react/useEphiaSpeechMike.ts +26 -0
- package/src/devices/speechmike/speechmike-audio-resolver.ts +58 -0
- package/src/devices/speechmike/speechmike-button-router.ts +73 -0
- package/src/devices/speechmike/speechmike-device-manager.ts +461 -0
- package/src/devices/speechmike/speechmike-led-controller.ts +78 -0
- package/src/devices/speechmike/types.ts +96 -0
- package/src/dictation_support.d.ts +31 -0
- package/src/global.d.ts +10 -0
- package/src/headless/createEphiaClient.ts +220 -0
- package/src/headless/index.ts +18 -0
- package/src/index.ts +89 -0
- package/src/react/EphiaAuto.tsx +87 -0
- package/src/react/components/EphiaDictationButton.tsx +88 -0
- package/src/react/components/EphiaStatusBar.tsx +59 -0
- package/src/react/components/EphiaTextarea.tsx +295 -0
- package/src/react/ephia-react.css +318 -0
- package/src/react/hooks/targets/index.ts +3 -0
- package/src/react/hooks/targets/useEphiaCodemirror.ts +35 -0
- package/src/react/hooks/targets/useEphiaMonaco.ts +35 -0
- package/src/react/hooks/targets/useEphiaTiptap.ts +23 -0
- package/src/react/hooks/useEphia.lifecycle.test.tsx +389 -0
- package/src/react/hooks/useEphia.ts +367 -0
- package/src/react/hooks/useEphiaDiscardTarget.ts +53 -0
- package/src/react/hooks/useEphiaServerEvent.ts +33 -0
- package/src/react/hooks/useEphiaTarget.ts +47 -0
- package/src/react/hooks/useEphiaTranscript.ts +22 -0
- package/src/react/index.ts +58 -0
- package/src/react/provider/EphiaContext.ts +63 -0
- package/src/react/provider/EphiaInternalContext.ts +32 -0
- package/src/react/provider/EphiaProvider.tsx +373 -0
- package/src/react/registry/binding-factory.ts +7 -0
- package/src/react/registry/detect-editor-type.ts +2 -0
- package/src/react/registry/events.ts +37 -0
- package/src/react/registry/registries/CodeMirrorInstanceRegistry.ts +24 -0
- package/src/react/registry/registries/MonacoInstanceRegistry.ts +23 -0
- package/src/react/registry/registries/TargetRegistry.ts +327 -0
- package/src/react/registry/registries/TiptapInstanceRegistry.ts +43 -0
- package/src/react/registry/registries/index.ts +5 -0
- package/src/react/store/create-ephia-store.ts +36 -0
- package/src/react/store/types.ts +41 -0
- package/src/react/utils/flash-range.ts +24 -0
- package/src/react/utils/index.ts +1 -0
- package/src/rich-editor/adapters/tiptap.test.ts +86 -0
- package/src/rich-editor/adapters/tiptap.ts +23 -0
- package/src/rich-editor/index.ts +3 -0
- package/src/rich-editor/types.ts +24 -0
- package/src/rich-editor/use-ephia-rich-editor.test.tsx +202 -0
- package/src/rich-editor/use-ephia-rich-editor.ts +47 -0
- package/src/shared/config/endpoint.test.ts +45 -0
- package/src/shared/config/endpoint.ts +39 -0
- package/src/shared/config/schema.ts +32 -0
- package/src/shared/effective-text.ts +13 -0
- package/src/shared/errors/EphiaSdkError.ts +54 -0
- package/src/shared/errors/messages.ts +40 -0
- package/src/shared/index.ts +27 -0
- package/src/shared/state/audio-state.ts +45 -0
- package/src/shared/state/index.ts +2 -0
- package/src/shared/store/document-store.ts +32 -0
- package/src/shared/store/index.ts +2 -0
- package/src/shared/types/editors.ts +28 -0
- package/src/shared/types/session.ts +12 -0
- package/src/style.css +2 -0
- package/src/testing/index.tsx +60 -0
- package/src/ui/assets/ephia-logo.svg +4 -0
- package/src/ui/components/EphiaLogo.tsx +77 -0
- package/src/ui/index.ts +24 -0
- package/src/ui/primitives/Button.tsx +53 -0
- package/src/ui/primitives/Spinner.tsx +21 -0
- package/src/ui/primitives/index.ts +5 -0
- package/src/ui/recorder/EphiaFloatingButton.tsx +489 -0
- package/src/ui/recorder/MinimalProcessingBars.tsx +122 -0
- package/src/ui/recorder/StandardIntensityVisualizer.tsx +148 -0
- package/src/ui/recorder/appearance.ts +9 -0
- package/src/ui/recorder/index.ts +8 -0
- package/src/ui/theme.css +775 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import {
|
|
2
|
+
textToDocumentOperations
|
|
3
|
+
} from "./chunk-VSLGR64U.js";
|
|
4
|
+
|
|
5
|
+
// src/core/bindings/tiptap/TiptapBinding.ts
|
|
6
|
+
import { Mark } from "@tiptap/core";
|
|
7
|
+
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
8
|
+
|
|
9
|
+
// src/core/bindings/insertion-boundary.ts
|
|
10
|
+
function ensureMinimalAppendBoundary(leftText, incomingText) {
|
|
11
|
+
if (!leftText || !incomingText) return incomingText;
|
|
12
|
+
const left = leftText.at(-1);
|
|
13
|
+
const right = incomingText.at(0);
|
|
14
|
+
if (!left || !right) return incomingText;
|
|
15
|
+
if (/\s/.test(left)) return incomingText;
|
|
16
|
+
if (/\s/.test(right)) return incomingText;
|
|
17
|
+
if (/^[,.;:!?)\]}>]/.test(right)) return incomingText;
|
|
18
|
+
return ` ${incomingText}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/core/bindings/tiptap/TiptapBinding.ts
|
|
22
|
+
var ephiaV2Key = new PluginKey("ephiaV2");
|
|
23
|
+
function createEphiaV2Plugin() {
|
|
24
|
+
return new Plugin({
|
|
25
|
+
key: ephiaV2Key,
|
|
26
|
+
state: {
|
|
27
|
+
init() {
|
|
28
|
+
return { segments: {} };
|
|
29
|
+
},
|
|
30
|
+
apply(tr, value) {
|
|
31
|
+
const docSize = tr.doc.content.size;
|
|
32
|
+
const mapped = {};
|
|
33
|
+
for (const [id, pos] of Object.entries(value.segments)) {
|
|
34
|
+
const from = Math.min(Math.max(tr.mapping.map(pos.from), 0), docSize);
|
|
35
|
+
const to = Math.min(Math.max(tr.mapping.map(pos.to), 0), docSize);
|
|
36
|
+
if (from <= to && to <= docSize) mapped[id] = { from, to };
|
|
37
|
+
}
|
|
38
|
+
const meta = tr.getMeta(ephiaV2Key);
|
|
39
|
+
if (meta?.segments) {
|
|
40
|
+
for (const [id, pos] of Object.entries(meta.segments)) {
|
|
41
|
+
if (pos === null) delete mapped[id];
|
|
42
|
+
else mapped[id] = pos;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { segments: mapped };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function getV2State(editor) {
|
|
51
|
+
return ephiaV2Key.getState(editor.state) ?? { segments: {} };
|
|
52
|
+
}
|
|
53
|
+
function applySegmentMeta(tr, updates) {
|
|
54
|
+
const current = tr.getMeta(ephiaV2Key) ?? {};
|
|
55
|
+
current.segments = { ...current.segments ?? {}, ...updates };
|
|
56
|
+
tr.setMeta(ephiaV2Key, current);
|
|
57
|
+
}
|
|
58
|
+
var EphiaV2PreviewMark = Mark.create({
|
|
59
|
+
name: "ephiaV2Preview",
|
|
60
|
+
parseHTML: () => [{ tag: "span[data-ephia-preview]" }],
|
|
61
|
+
renderHTML: () => ["span", { "data-ephia-preview": "true", class: "ephia-text--preview" }, 0]
|
|
62
|
+
});
|
|
63
|
+
var EphiaV2CommittedMark = Mark.create({
|
|
64
|
+
name: "ephiaV2Committed",
|
|
65
|
+
parseHTML: () => [{ tag: "span[data-ephia-v2-committed]" }],
|
|
66
|
+
renderHTML: () => ["span", { "data-ephia-v2-committed": "true", class: "ephia-text--v2-committed" }, 0]
|
|
67
|
+
});
|
|
68
|
+
function getDocText(editor) {
|
|
69
|
+
return editor.state.doc.textBetween(0, editor.state.doc.content.size, "\n\n", "\n");
|
|
70
|
+
}
|
|
71
|
+
function getCommonPrefix(a, b) {
|
|
72
|
+
let i = 0;
|
|
73
|
+
while (i < a.length && i < b.length && a[i] === b[i]) i++;
|
|
74
|
+
return i;
|
|
75
|
+
}
|
|
76
|
+
function getCommonSuffix(a, b, prefixLen) {
|
|
77
|
+
let i = 0;
|
|
78
|
+
while (i < a.length - prefixLen && i < b.length - prefixLen && a[a.length - 1 - i] === b[b.length - 1 - i]) i++;
|
|
79
|
+
return i;
|
|
80
|
+
}
|
|
81
|
+
function insertOpsIntoTr(editor, tr, startCursor, text, mark) {
|
|
82
|
+
const ops = textToDocumentOperations(text);
|
|
83
|
+
let cursor = startCursor;
|
|
84
|
+
for (const op of ops) {
|
|
85
|
+
if (op.type === "insert_text") {
|
|
86
|
+
if (op.text) {
|
|
87
|
+
const node = mark ? editor.schema.text(op.text, [mark]) : editor.schema.text(op.text);
|
|
88
|
+
tr.insert(cursor, node);
|
|
89
|
+
cursor += op.text.length;
|
|
90
|
+
}
|
|
91
|
+
} else if (op.type === "line_break") {
|
|
92
|
+
const hardBreak = editor.schema.nodes.hardBreak;
|
|
93
|
+
if (hardBreak) {
|
|
94
|
+
tr.insert(cursor, hardBreak.create());
|
|
95
|
+
cursor += 1;
|
|
96
|
+
}
|
|
97
|
+
} else if (op.type === "paragraph_break") {
|
|
98
|
+
const hardBreak = editor.schema.nodes.hardBreak;
|
|
99
|
+
if (hardBreak) {
|
|
100
|
+
tr.insert(cursor, hardBreak.create());
|
|
101
|
+
cursor += 1;
|
|
102
|
+
tr.insert(cursor, hardBreak.create());
|
|
103
|
+
cursor += 1;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return cursor;
|
|
108
|
+
}
|
|
109
|
+
function smartReplaceRange(editor, tr, from, to, newText) {
|
|
110
|
+
const oldText = editor.state.doc.textBetween(from, to, "\n", "\n");
|
|
111
|
+
const prefixLen = getCommonPrefix(oldText, newText);
|
|
112
|
+
const suffixLen = getCommonSuffix(oldText, newText, prefixLen);
|
|
113
|
+
const deleteFrom = from + prefixLen;
|
|
114
|
+
const deleteTo = to - suffixLen;
|
|
115
|
+
const insertText = newText.slice(prefixLen, newText.length - suffixLen);
|
|
116
|
+
if (deleteFrom < deleteTo || insertText.length > 0) {
|
|
117
|
+
if (deleteFrom < deleteTo) tr.delete(deleteFrom, deleteTo);
|
|
118
|
+
insertOpsIntoTr(editor, tr, deleteFrom, insertText);
|
|
119
|
+
}
|
|
120
|
+
return { from, to: from + newText.length };
|
|
121
|
+
}
|
|
122
|
+
function insertTextAt(editor, tr, pos, text, mark) {
|
|
123
|
+
const end = insertOpsIntoTr(editor, tr, pos, text, mark);
|
|
124
|
+
return { from: pos, to: end };
|
|
125
|
+
}
|
|
126
|
+
function stripPreviewMark(editor, tr, from, to) {
|
|
127
|
+
const previewMarkType = editor.schema.marks.ephiaV2Preview;
|
|
128
|
+
if (previewMarkType) tr.removeMark(from, to, previewMarkType);
|
|
129
|
+
}
|
|
130
|
+
function applyPreviewMark(editor, tr, from, to) {
|
|
131
|
+
const previewMark = editor.schema.marks.ephiaV2Preview?.create();
|
|
132
|
+
if (previewMark && from < to) tr.addMark(from, to, previewMark);
|
|
133
|
+
}
|
|
134
|
+
var TiptapBinding = class {
|
|
135
|
+
kind = "tiptap";
|
|
136
|
+
identity;
|
|
137
|
+
editor;
|
|
138
|
+
warn;
|
|
139
|
+
knownSegmentIds = /* @__PURE__ */ new Set();
|
|
140
|
+
insertionPrefixes = /* @__PURE__ */ new Map();
|
|
141
|
+
visualTimeouts = /* @__PURE__ */ new Map();
|
|
142
|
+
previewSegmentId = null;
|
|
143
|
+
constructor(editor, options) {
|
|
144
|
+
this.editor = editor;
|
|
145
|
+
this.identity = editor;
|
|
146
|
+
this.warn = options?.warn ?? ((msg, d) => console.warn(msg, d));
|
|
147
|
+
}
|
|
148
|
+
attach() {
|
|
149
|
+
const plugin = createEphiaV2Plugin();
|
|
150
|
+
this.editor.registerPlugin(plugin);
|
|
151
|
+
}
|
|
152
|
+
detach() {
|
|
153
|
+
this.editor.unregisterPlugin(ephiaV2Key);
|
|
154
|
+
for (const t of this.visualTimeouts.values()) clearTimeout(t);
|
|
155
|
+
this.visualTimeouts.clear();
|
|
156
|
+
this.knownSegmentIds.clear();
|
|
157
|
+
this.insertionPrefixes.clear();
|
|
158
|
+
this.previewSegmentId = null;
|
|
159
|
+
}
|
|
160
|
+
getText() {
|
|
161
|
+
return getDocText(this.editor);
|
|
162
|
+
}
|
|
163
|
+
getEditorContext(targetId = "") {
|
|
164
|
+
const { state } = this.editor;
|
|
165
|
+
const docSize = state.doc.content.size;
|
|
166
|
+
const text = getDocText(this.editor);
|
|
167
|
+
const { from, to } = state.selection;
|
|
168
|
+
const hasSelection = from !== to;
|
|
169
|
+
return {
|
|
170
|
+
targetId,
|
|
171
|
+
documentEmpty: text.trim().length === 0,
|
|
172
|
+
insertionMode: hasSelection ? "replace_selection" : from >= docSize - 1 ? "append" : "middle_of_sentence",
|
|
173
|
+
leftContext: text.slice(Math.max(0, from - 400), from),
|
|
174
|
+
rightContext: text.slice(to, to + 200),
|
|
175
|
+
selectedText: hasSelection ? text.slice(from, to) : null,
|
|
176
|
+
cursorOffset: from
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
previewSegment(input) {
|
|
180
|
+
if (this.previewSegmentId && this.previewSegmentId !== input.id) {
|
|
181
|
+
this._clearPreview(this.previewSegmentId);
|
|
182
|
+
}
|
|
183
|
+
this.previewSegmentId = input.id;
|
|
184
|
+
const state = getV2State(this.editor);
|
|
185
|
+
const existing = state.segments[input.id];
|
|
186
|
+
const insertPos = existing?.to ?? this.editor.state.selection.from;
|
|
187
|
+
if (existing) {
|
|
188
|
+
const tr = this.editor.state.tr;
|
|
189
|
+
const safeText = this._textWithStableBoundary(input.id, input.text, existing.from);
|
|
190
|
+
const { from, to } = smartReplaceRange(this.editor, tr, existing.from, existing.to, safeText);
|
|
191
|
+
applyPreviewMark(this.editor, tr, from, to);
|
|
192
|
+
applySegmentMeta(tr, { [input.id]: { from, to } });
|
|
193
|
+
this.editor.view.dispatch(tr);
|
|
194
|
+
} else {
|
|
195
|
+
const tr = this.editor.state.tr;
|
|
196
|
+
const pos = insertPos;
|
|
197
|
+
const previewMark = this.editor.schema.marks.ephiaV2Preview?.create();
|
|
198
|
+
const safeText = this._textWithStableBoundary(input.id, input.text, pos);
|
|
199
|
+
const { from, to } = insertTextAt(this.editor, tr, pos, safeText, previewMark);
|
|
200
|
+
applySegmentMeta(tr, { [input.id]: { from, to } });
|
|
201
|
+
this.editor.view.dispatch(tr);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
upsertSegment(input) {
|
|
205
|
+
this._clearVisualTimeout(input.id);
|
|
206
|
+
if (this.previewSegmentId === input.id) {
|
|
207
|
+
this.previewSegmentId = null;
|
|
208
|
+
}
|
|
209
|
+
const state = getV2State(this.editor);
|
|
210
|
+
const existing = state.segments[input.id];
|
|
211
|
+
if (existing) {
|
|
212
|
+
const tr2 = this.editor.state.tr;
|
|
213
|
+
const safeText2 = this._textWithStableBoundary(input.id, input.text, existing.from);
|
|
214
|
+
const { from: from2, to: to2 } = smartReplaceRange(this.editor, tr2, existing.from, existing.to, safeText2);
|
|
215
|
+
stripPreviewMark(this.editor, tr2, from2, to2);
|
|
216
|
+
applySegmentMeta(tr2, { [input.id]: { from: from2, to: to2 } });
|
|
217
|
+
this.editor.view.dispatch(tr2);
|
|
218
|
+
this.knownSegmentIds.add(input.id);
|
|
219
|
+
this._scheduleVisualCleanup(input.id);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (this.knownSegmentIds.has(input.id)) {
|
|
223
|
+
this.warn("[ephia:tiptap] segment range lost; upsert ignored", {
|
|
224
|
+
segmentId: input.id,
|
|
225
|
+
stage: input.stage
|
|
226
|
+
});
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const { from: selFrom, to: selTo } = this.editor.state.selection;
|
|
230
|
+
const hasSelection = selFrom !== selTo;
|
|
231
|
+
const insertPos = selFrom;
|
|
232
|
+
const safeText = this._textWithStableBoundary(input.id, input.text, insertPos);
|
|
233
|
+
const tr = this.editor.state.tr;
|
|
234
|
+
if (hasSelection) {
|
|
235
|
+
tr.delete(selFrom, selTo);
|
|
236
|
+
}
|
|
237
|
+
const { from, to } = insertTextAt(this.editor, tr, insertPos, safeText);
|
|
238
|
+
applySegmentMeta(tr, { [input.id]: { from, to } });
|
|
239
|
+
this.editor.view.dispatch(tr);
|
|
240
|
+
this.knownSegmentIds.add(input.id);
|
|
241
|
+
this._scheduleVisualCleanup(input.id);
|
|
242
|
+
}
|
|
243
|
+
removeSegment(id) {
|
|
244
|
+
const state = getV2State(this.editor);
|
|
245
|
+
const existing = state.segments[id];
|
|
246
|
+
if (!existing) {
|
|
247
|
+
this.knownSegmentIds.add(id);
|
|
248
|
+
this.insertionPrefixes.delete(id);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (this.previewSegmentId === id) this.previewSegmentId = null;
|
|
252
|
+
this._clearVisualTimeout(id);
|
|
253
|
+
const tr = this.editor.state.tr;
|
|
254
|
+
tr.delete(existing.from, existing.to);
|
|
255
|
+
applySegmentMeta(tr, { [id]: null });
|
|
256
|
+
this.editor.view.dispatch(tr);
|
|
257
|
+
this.knownSegmentIds.add(id);
|
|
258
|
+
this.insertionPrefixes.delete(id);
|
|
259
|
+
}
|
|
260
|
+
removeSegments(ids) {
|
|
261
|
+
if (ids.length === 0) return;
|
|
262
|
+
const state = getV2State(this.editor);
|
|
263
|
+
const tr = this.editor.state.tr;
|
|
264
|
+
const updates = {};
|
|
265
|
+
const toRemove = ids.map((id) => ({ id, pos: state.segments[id] })).filter((x) => x.pos !== void 0).sort((a, b) => b.pos.from - a.pos.from);
|
|
266
|
+
for (const { id, pos } of toRemove) {
|
|
267
|
+
tr.delete(pos.from, pos.to);
|
|
268
|
+
updates[id] = null;
|
|
269
|
+
this.knownSegmentIds.add(id);
|
|
270
|
+
this.insertionPrefixes.delete(id);
|
|
271
|
+
if (this.previewSegmentId === id) this.previewSegmentId = null;
|
|
272
|
+
this._clearVisualTimeout(id);
|
|
273
|
+
}
|
|
274
|
+
for (const id of ids) {
|
|
275
|
+
this.knownSegmentIds.add(id);
|
|
276
|
+
this.insertionPrefixes.delete(id);
|
|
277
|
+
}
|
|
278
|
+
if (toRemove.length > 0) {
|
|
279
|
+
applySegmentMeta(tr, updates);
|
|
280
|
+
this.editor.view.dispatch(tr);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
// ------------------------------------------------------------------
|
|
284
|
+
// Internals
|
|
285
|
+
// ------------------------------------------------------------------
|
|
286
|
+
_clearPreview(id) {
|
|
287
|
+
const state = getV2State(this.editor);
|
|
288
|
+
const pos = state.segments[id];
|
|
289
|
+
if (!pos) return;
|
|
290
|
+
const tr = this.editor.state.tr;
|
|
291
|
+
tr.delete(pos.from, pos.to);
|
|
292
|
+
applySegmentMeta(tr, { [id]: null });
|
|
293
|
+
this.editor.view.dispatch(tr);
|
|
294
|
+
}
|
|
295
|
+
_textWithStableBoundary(id, text, insertPos) {
|
|
296
|
+
if (!text) return text;
|
|
297
|
+
const firstChar = text.at(0);
|
|
298
|
+
if (!firstChar || /\s/.test(firstChar) || /^[,.;:!?)\]}>]/.test(firstChar)) {
|
|
299
|
+
this.insertionPrefixes.set(id, "");
|
|
300
|
+
return text;
|
|
301
|
+
}
|
|
302
|
+
let prefix = this.insertionPrefixes.get(id);
|
|
303
|
+
if (prefix === void 0) {
|
|
304
|
+
const leftText = getDocText(this.editor).slice(0, insertPos);
|
|
305
|
+
const safeText = ensureMinimalAppendBoundary(leftText, text);
|
|
306
|
+
prefix = safeText.endsWith(text) ? safeText.slice(0, safeText.length - text.length) : "";
|
|
307
|
+
this.insertionPrefixes.set(id, prefix);
|
|
308
|
+
}
|
|
309
|
+
return `${prefix}${text}`;
|
|
310
|
+
}
|
|
311
|
+
_scheduleVisualCleanup(id) {
|
|
312
|
+
this._clearVisualTimeout(id);
|
|
313
|
+
const timer = setTimeout(() => {
|
|
314
|
+
this.visualTimeouts.delete(id);
|
|
315
|
+
this._removeVisualMarkForSegment(id);
|
|
316
|
+
}, 1200);
|
|
317
|
+
this.visualTimeouts.set(id, timer);
|
|
318
|
+
}
|
|
319
|
+
_clearVisualTimeout(id) {
|
|
320
|
+
const t = this.visualTimeouts.get(id);
|
|
321
|
+
if (t) {
|
|
322
|
+
clearTimeout(t);
|
|
323
|
+
this.visualTimeouts.delete(id);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
_removeVisualMarkForSegment(id) {
|
|
327
|
+
const pos = getV2State(this.editor).segments[id];
|
|
328
|
+
if (!pos) return;
|
|
329
|
+
const { state } = this.editor;
|
|
330
|
+
const previewMarkType = state.schema.marks.ephiaV2Preview;
|
|
331
|
+
const committedMarkType = state.schema.marks.ephiaV2Committed;
|
|
332
|
+
if (!previewMarkType && !committedMarkType) return;
|
|
333
|
+
const tr = state.tr;
|
|
334
|
+
if (previewMarkType) tr.removeMark(pos.from, pos.to, previewMarkType);
|
|
335
|
+
if (committedMarkType) tr.removeMark(pos.from, pos.to, committedMarkType);
|
|
336
|
+
this.editor.view.dispatch(tr);
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
export {
|
|
341
|
+
ensureMinimalAppendBoundary,
|
|
342
|
+
EphiaV2PreviewMark,
|
|
343
|
+
EphiaV2CommittedMark,
|
|
344
|
+
TiptapBinding
|
|
345
|
+
};
|
|
346
|
+
//# sourceMappingURL=chunk-W2ZP674X.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/bindings/tiptap/TiptapBinding.ts","../src/core/bindings/insertion-boundary.ts"],"sourcesContent":["import type { Editor } from \"@tiptap/core\";\nimport { Mark } from \"@tiptap/core\";\nimport { Plugin, PluginKey } from \"@tiptap/pm/state\";\nimport type { Transaction } from \"@tiptap/pm/state\";\nimport type { Mark as ProseMirrorMark } from \"@tiptap/pm/model\";\nimport type { EditorContext } from \"ephia-protocol\";\nimport type { EphiaBinding, PreviewSegmentInput, UpsertSegmentInput } from \"../EphiaBinding\";\nimport { textToDocumentOperations } from \"../../operations/textToDocumentOperations\";\nimport { ensureMinimalAppendBoundary } from \"../insertion-boundary\";\n\n// ---------------------------------------------------------------------------\n// ProseMirror Plugin V2 — tracks segment positions through transactions.\n// ---------------------------------------------------------------------------\n\ninterface SegmentPos {\n from: number;\n to: number;\n}\n\ninterface EphiaV2PluginState {\n segments: Record<string, SegmentPos>;\n}\n\nconst ephiaV2Key = new PluginKey<EphiaV2PluginState>(\"ephiaV2\");\n\ninterface SegmentMeta {\n segments?: Record<string, SegmentPos | null>;\n}\n\nfunction createEphiaV2Plugin(): Plugin {\n return new Plugin({\n key: ephiaV2Key,\n state: {\n init(): EphiaV2PluginState {\n return { segments: {} };\n },\n apply(tr, value): EphiaV2PluginState {\n const docSize = tr.doc.content.size;\n const mapped: EphiaV2PluginState[\"segments\"] = {};\n for (const [id, pos] of Object.entries(value.segments)) {\n const from = Math.min(Math.max(tr.mapping.map(pos.from), 0), docSize);\n const to = Math.min(Math.max(tr.mapping.map(pos.to), 0), docSize);\n if (from <= to && to <= docSize) mapped[id] = { from, to };\n }\n const meta = tr.getMeta(ephiaV2Key) as SegmentMeta | undefined;\n if (meta?.segments) {\n for (const [id, pos] of Object.entries(meta.segments)) {\n if (pos === null) delete mapped[id];\n else mapped[id] = pos;\n }\n }\n return { segments: mapped };\n },\n },\n });\n}\n\nfunction getV2State(editor: Editor): EphiaV2PluginState {\n return ephiaV2Key.getState(editor.state) ?? { segments: {} };\n}\n\nfunction applySegmentMeta(tr: Transaction, updates: Record<string, SegmentPos | null>): void {\n const current = (tr.getMeta(ephiaV2Key) as SegmentMeta | undefined) ?? {};\n current.segments = { ...(current.segments ?? {}), ...updates };\n tr.setMeta(ephiaV2Key, current);\n}\n\n// ---------------------------------------------------------------------------\n// Marks\n// ---------------------------------------------------------------------------\n\nexport const EphiaV2PreviewMark = Mark.create({\n name: \"ephiaV2Preview\",\n parseHTML: () => [{ tag: \"span[data-ephia-preview]\" }],\n renderHTML: () => [\"span\", { \"data-ephia-preview\": \"true\", class: \"ephia-text--preview\" }, 0],\n});\n\nexport const EphiaV2CommittedMark = Mark.create({\n name: \"ephiaV2Committed\",\n parseHTML: () => [{ tag: \"span[data-ephia-v2-committed]\" }],\n renderHTML: () => [\"span\", { \"data-ephia-v2-committed\": \"true\", class: \"ephia-text--v2-committed\" }, 0],\n});\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction getDocText(editor: Editor): string {\n return editor.state.doc.textBetween(0, editor.state.doc.content.size, \"\\n\\n\", \"\\n\");\n}\n\nfunction getCommonPrefix(a: string, b: string): number {\n let i = 0;\n while (i < a.length && i < b.length && a[i] === b[i]) i++;\n return i;\n}\n\nfunction getCommonSuffix(a: string, b: string, prefixLen: number): number {\n let i = 0;\n while (\n i < a.length - prefixLen &&\n i < b.length - prefixLen &&\n a[a.length - 1 - i] === b[b.length - 1 - i]\n ) i++;\n return i;\n}\n\n/**\n * Replace text in range [from, to] with newText using a smart diff to avoid\n * unnecessary delete+insert when only part of the text changed.\n */\nfunction insertOpsIntoTr(\n editor: Editor,\n tr: Transaction,\n startCursor: number,\n text: string,\n mark?: ProseMirrorMark,\n): number {\n const ops = textToDocumentOperations(text);\n let cursor = startCursor;\n for (const op of ops) {\n if (op.type === \"insert_text\") {\n if (op.text) {\n const node = mark\n ? editor.schema.text(op.text, [mark])\n : editor.schema.text(op.text);\n tr.insert(cursor, node);\n cursor += op.text.length;\n }\n } else if (op.type === \"line_break\") {\n const hardBreak = editor.schema.nodes.hardBreak;\n if (hardBreak) {\n tr.insert(cursor, hardBreak.create());\n cursor += 1;\n }\n } else if (op.type === \"paragraph_break\") {\n const hardBreak = editor.schema.nodes.hardBreak;\n if (hardBreak) {\n // Two hard breaks for paragraph separation in flat TipTap documents.\n tr.insert(cursor, hardBreak.create());\n cursor += 1;\n tr.insert(cursor, hardBreak.create());\n cursor += 1;\n }\n }\n }\n return cursor;\n}\n\nfunction smartReplaceRange(\n editor: Editor,\n tr: Transaction,\n from: number,\n to: number,\n newText: string\n): SegmentPos {\n const oldText = editor.state.doc.textBetween(from, to, \"\\n\", \"\\n\");\n const prefixLen = getCommonPrefix(oldText, newText);\n const suffixLen = getCommonSuffix(oldText, newText, prefixLen);\n\n const deleteFrom = from + prefixLen;\n const deleteTo = to - suffixLen;\n const insertText = newText.slice(prefixLen, newText.length - suffixLen);\n\n if (deleteFrom < deleteTo || insertText.length > 0) {\n if (deleteFrom < deleteTo) tr.delete(deleteFrom, deleteTo);\n insertOpsIntoTr(editor, tr, deleteFrom, insertText);\n }\n\n return { from, to: from + newText.length };\n}\n\nfunction insertTextAt(editor: Editor, tr: Transaction, pos: number, text: string, mark?: ProseMirrorMark): SegmentPos {\n const end = insertOpsIntoTr(editor, tr, pos, text, mark);\n return { from: pos, to: end };\n}\n\nfunction stripPreviewMark(\n editor: Editor,\n tr: Transaction,\n from: number,\n to: number,\n): void {\n const previewMarkType = editor.schema.marks.ephiaV2Preview;\n if (previewMarkType) tr.removeMark(from, to, previewMarkType);\n}\n\nfunction applyPreviewMark(\n editor: Editor,\n tr: Transaction,\n from: number,\n to: number,\n): void {\n const previewMark = editor.schema.marks.ephiaV2Preview?.create();\n if (previewMark && from < to) tr.addMark(from, to, previewMark);\n}\n\n// ---------------------------------------------------------------------------\n// TiptapBinding V2\n// ---------------------------------------------------------------------------\n\nexport type TiptapBindingOptions = {\n warn?: (msg: string, details?: Record<string, unknown>) => void;\n};\n\n/**\n * Binding V2 pour TipTap.\n *\n * Règles :\n * - même segmentId + upsertSegment = update in place (never append)\n * - segmentId connu mais range perdu = warning + no append\n * - removeSegments idempotent\n * - cleanup visuel ne supprime jamais l'identité segment\n * - le préfixe de boundary SDK est stable sur preview -> provisional -> canonical\n */\nexport class TiptapBinding implements EphiaBinding {\n readonly kind = \"tiptap\";\n readonly identity: Editor;\n\n private readonly editor: Editor;\n private readonly warn: NonNullable<TiptapBindingOptions[\"warn\"]>;\n private readonly knownSegmentIds = new Set<string>();\n private readonly insertionPrefixes = new Map<string, string>();\n private readonly visualTimeouts = new Map<string, ReturnType<typeof setTimeout>>();\n private previewSegmentId: string | null = null;\n\n constructor(editor: Editor, options?: TiptapBindingOptions) {\n this.editor = editor;\n this.identity = editor;\n this.warn = options?.warn ?? ((msg, d) => console.warn(msg, d));\n }\n\n attach(): void {\n const plugin = createEphiaV2Plugin();\n this.editor.registerPlugin(plugin);\n }\n\n detach(): void {\n this.editor.unregisterPlugin(ephiaV2Key);\n for (const t of this.visualTimeouts.values()) clearTimeout(t);\n this.visualTimeouts.clear();\n this.knownSegmentIds.clear();\n this.insertionPrefixes.clear();\n this.previewSegmentId = null;\n }\n\n getText(): string {\n return getDocText(this.editor);\n }\n\n getEditorContext(targetId = \"\"): EditorContext {\n const { state } = this.editor;\n const docSize = state.doc.content.size;\n const text = getDocText(this.editor);\n const { from, to } = state.selection;\n const hasSelection = from !== to;\n\n return {\n targetId,\n documentEmpty: text.trim().length === 0,\n insertionMode: hasSelection\n ? \"replace_selection\"\n : from >= docSize - 1\n ? \"append\"\n : \"middle_of_sentence\",\n leftContext: text.slice(Math.max(0, from - 400), from),\n rightContext: text.slice(to, to + 200),\n selectedText: hasSelection ? text.slice(from, to) : null,\n cursorOffset: from,\n };\n }\n\n previewSegment(input: PreviewSegmentInput): void {\n // Remove previous preview if different segment.\n if (this.previewSegmentId && this.previewSegmentId !== input.id) {\n this._clearPreview(this.previewSegmentId);\n }\n this.previewSegmentId = input.id;\n const state = getV2State(this.editor);\n const existing = state.segments[input.id];\n const insertPos = existing?.to ?? this.editor.state.selection.from;\n\n if (existing) {\n // Update existing preview range while preserving the first-insert boundary prefix.\n const tr = this.editor.state.tr;\n const safeText = this._textWithStableBoundary(input.id, input.text, existing.from);\n const { from, to } = smartReplaceRange(this.editor, tr, existing.from, existing.to, safeText);\n applyPreviewMark(this.editor, tr, from, to);\n applySegmentMeta(tr, { [input.id]: { from, to } });\n this.editor.view.dispatch(tr);\n } else {\n // Insert preview at cursor via textToDocumentOperations for consistent \\n handling.\n // Apply boundary safety for the first appearance of this segment.\n const tr = this.editor.state.tr;\n const pos = insertPos;\n const previewMark = this.editor.schema.marks.ephiaV2Preview?.create();\n const safeText = this._textWithStableBoundary(input.id, input.text, pos);\n const { from, to } = insertTextAt(this.editor, tr, pos, safeText, previewMark);\n applySegmentMeta(tr, { [input.id]: { from, to } });\n this.editor.view.dispatch(tr);\n }\n }\n\n upsertSegment(input: UpsertSegmentInput): void {\n this._clearVisualTimeout(input.id);\n\n if (this.previewSegmentId === input.id) {\n this.previewSegmentId = null;\n }\n\n const state = getV2State(this.editor);\n const existing = state.segments[input.id];\n\n if (existing) {\n // Update existing range while preserving the first-insert boundary prefix.\n const tr = this.editor.state.tr;\n const safeText = this._textWithStableBoundary(input.id, input.text, existing.from);\n const { from, to } = smartReplaceRange(this.editor, tr, existing.from, existing.to, safeText);\n stripPreviewMark(this.editor, tr, from, to);\n applySegmentMeta(tr, { [input.id]: { from, to } });\n this.editor.view.dispatch(tr);\n this.knownSegmentIds.add(input.id);\n this._scheduleVisualCleanup(input.id);\n return;\n }\n\n if (this.knownSegmentIds.has(input.id)) {\n this.warn(\"[ephia:tiptap] segment range lost; upsert ignored\", {\n segmentId: input.id,\n stage: input.stage,\n });\n return;\n }\n\n // New segment — apply minimal boundary safety before inserting at cursor.\n // Si une sélection est active, on la remplace par le texte dicté.\n const { from: selFrom, to: selTo } = this.editor.state.selection;\n const hasSelection = selFrom !== selTo;\n const insertPos = selFrom;\n const safeText = this._textWithStableBoundary(input.id, input.text, insertPos);\n const tr = this.editor.state.tr;\n if (hasSelection) {\n tr.delete(selFrom, selTo);\n }\n const { from, to } = insertTextAt(this.editor, tr, insertPos, safeText);\n applySegmentMeta(tr, { [input.id]: { from, to } });\n this.editor.view.dispatch(tr);\n this.knownSegmentIds.add(input.id);\n this._scheduleVisualCleanup(input.id);\n }\n\n removeSegment(id: string): void {\n const state = getV2State(this.editor);\n const existing = state.segments[id];\n if (!existing) {\n this.knownSegmentIds.add(id);\n this.insertionPrefixes.delete(id);\n return;\n }\n if (this.previewSegmentId === id) this.previewSegmentId = null;\n this._clearVisualTimeout(id);\n const tr = this.editor.state.tr;\n tr.delete(existing.from, existing.to);\n applySegmentMeta(tr, { [id]: null });\n this.editor.view.dispatch(tr);\n this.knownSegmentIds.add(id);\n this.insertionPrefixes.delete(id);\n }\n\n removeSegments(ids: string[]): void {\n if (ids.length === 0) return;\n const state = getV2State(this.editor);\n const tr = this.editor.state.tr;\n const updates: Record<string, SegmentPos | null> = {};\n\n // Process removals in reverse document order to keep positions valid.\n const toRemove = ids\n .map((id) => ({ id, pos: state.segments[id] }))\n .filter((x): x is { id: string; pos: SegmentPos } => x.pos !== undefined)\n .sort((a, b) => b.pos.from - a.pos.from);\n\n for (const { id, pos } of toRemove) {\n tr.delete(pos.from, pos.to);\n updates[id] = null;\n this.knownSegmentIds.add(id);\n this.insertionPrefixes.delete(id);\n if (this.previewSegmentId === id) this.previewSegmentId = null;\n this._clearVisualTimeout(id);\n }\n\n for (const id of ids) {\n this.knownSegmentIds.add(id);\n this.insertionPrefixes.delete(id);\n }\n\n if (toRemove.length > 0) {\n applySegmentMeta(tr, updates);\n this.editor.view.dispatch(tr);\n }\n }\n\n // ------------------------------------------------------------------\n // Internals\n // ------------------------------------------------------------------\n\n private _clearPreview(id: string): void {\n const state = getV2State(this.editor);\n const pos = state.segments[id];\n if (!pos) return;\n const tr = this.editor.state.tr;\n tr.delete(pos.from, pos.to);\n applySegmentMeta(tr, { [id]: null });\n this.editor.view.dispatch(tr);\n }\n\n private _textWithStableBoundary(id: string, text: string, insertPos: number): string {\n if (!text) return text;\n\n // Backend-provided leading whitespace/newlines are authoritative.\n // Do not prepend the SDK fallback prefix on top of them.\n const firstChar = text.at(0);\n if (!firstChar || /\\s/.test(firstChar) || /^[,.;:!?)\\]}>]/.test(firstChar)) {\n this.insertionPrefixes.set(id, \"\");\n return text;\n }\n\n let prefix = this.insertionPrefixes.get(id);\n if (prefix === undefined) {\n const leftText = getDocText(this.editor).slice(0, insertPos);\n const safeText = ensureMinimalAppendBoundary(leftText, text);\n prefix = safeText.endsWith(text) ? safeText.slice(0, safeText.length - text.length) : \"\";\n this.insertionPrefixes.set(id, prefix);\n }\n\n return `${prefix}${text}`;\n }\n\n private _scheduleVisualCleanup(id: string): void {\n this._clearVisualTimeout(id);\n const timer = setTimeout(() => {\n this.visualTimeouts.delete(id);\n this._removeVisualMarkForSegment(id);\n }, 1200);\n this.visualTimeouts.set(id, timer);\n }\n\n private _clearVisualTimeout(id: string): void {\n const t = this.visualTimeouts.get(id);\n if (t) { clearTimeout(t); this.visualTimeouts.delete(id); }\n }\n\n private _removeVisualMarkForSegment(id: string): void {\n const pos = getV2State(this.editor).segments[id];\n if (!pos) return;\n const { state } = this.editor;\n const previewMarkType = state.schema.marks.ephiaV2Preview;\n const committedMarkType = state.schema.marks.ephiaV2Committed;\n if (!previewMarkType && !committedMarkType) return;\n const tr = state.tr;\n if (previewMarkType) tr.removeMark(pos.from, pos.to, previewMarkType);\n if (committedMarkType) tr.removeMark(pos.from, pos.to, committedMarkType);\n this.editor.view.dispatch(tr);\n }\n}\n","/**\n * Safety net: ensures a minimal space is inserted between two words when the\n * backend sends a segment without any leading whitespace.\n *\n * Rules:\n * - SDK only adds a single space. Never a newline.\n * - Only applied on the *first* insertion of a segment, not on updates.\n * - Not applied if the left context already ends with whitespace.\n * - Not applied if the incoming text starts with whitespace or punctuation.\n */\nexport function ensureMinimalAppendBoundary(\n leftText: string,\n incomingText: string,\n): string {\n if (!leftText || !incomingText) return incomingText;\n\n const left = leftText.at(-1);\n const right = incomingText.at(0);\n\n if (!left || !right) return incomingText;\n if (/\\s/.test(left)) return incomingText;\n if (/\\s/.test(right)) return incomingText;\n if (/^[,.;:!?)\\]}>]/.test(right)) return incomingText;\n\n return ` ${incomingText}`;\n}\n"],"mappings":"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","names":["tr","safeText","from","to"]}
|