@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,417 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { Editor, Node } from "@tiptap/core";
|
|
5
|
+
import type { TargetBinding } from "../TargetBinding";
|
|
6
|
+
import { documentStore } from "../../../shared/store/document-store";
|
|
7
|
+
import {
|
|
8
|
+
createTiptapBinding,
|
|
9
|
+
EphiaCommittedMark,
|
|
10
|
+
EphiaPlaceholderMark,
|
|
11
|
+
EphiaPreviewMark,
|
|
12
|
+
EphiaRevisedMark,
|
|
13
|
+
} from "./tiptap.binding";
|
|
14
|
+
|
|
15
|
+
const DocumentNode = Node.create({
|
|
16
|
+
name: "doc",
|
|
17
|
+
topNode: true,
|
|
18
|
+
content: "block+",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const ParagraphNode = Node.create({
|
|
22
|
+
name: "paragraph",
|
|
23
|
+
group: "block",
|
|
24
|
+
content: "inline*",
|
|
25
|
+
parseHTML() {
|
|
26
|
+
return [{ tag: "p" }];
|
|
27
|
+
},
|
|
28
|
+
renderHTML() {
|
|
29
|
+
return ["p", 0];
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const TextNode = Node.create({
|
|
34
|
+
name: "text",
|
|
35
|
+
group: "inline",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const HardBreakNode = Node.create({
|
|
39
|
+
name: "hardBreak",
|
|
40
|
+
group: "inline",
|
|
41
|
+
inline: true,
|
|
42
|
+
selectable: false,
|
|
43
|
+
parseHTML() {
|
|
44
|
+
return [{ tag: "br" }];
|
|
45
|
+
},
|
|
46
|
+
renderHTML() {
|
|
47
|
+
return ["br"];
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function createEditor(
|
|
52
|
+
content = "<p></p>",
|
|
53
|
+
options: { hardBreak?: boolean } = {}
|
|
54
|
+
): Editor {
|
|
55
|
+
const includeHardBreak = options.hardBreak ?? true;
|
|
56
|
+
return new Editor({
|
|
57
|
+
element: document.createElement("div"),
|
|
58
|
+
extensions: [
|
|
59
|
+
DocumentNode,
|
|
60
|
+
ParagraphNode,
|
|
61
|
+
TextNode,
|
|
62
|
+
...(includeHardBreak ? [HardBreakNode] : []),
|
|
63
|
+
EphiaPreviewMark,
|
|
64
|
+
EphiaCommittedMark,
|
|
65
|
+
EphiaRevisedMark,
|
|
66
|
+
EphiaPlaceholderMark,
|
|
67
|
+
],
|
|
68
|
+
content,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function flushPartial(): Promise<void> {
|
|
73
|
+
await vi.advanceTimersByTimeAsync(60);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function flushStoreSync(): Promise<void> {
|
|
77
|
+
await vi.advanceTimersByTimeAsync(16);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const editors: Editor[] = [];
|
|
81
|
+
const bindings: TargetBinding[] = [];
|
|
82
|
+
|
|
83
|
+
type JsonNode = { type?: string; content?: JsonNode[] };
|
|
84
|
+
|
|
85
|
+
function countNodesByType(node: JsonNode | undefined, type: string): number {
|
|
86
|
+
if (!node) return 0;
|
|
87
|
+
return (
|
|
88
|
+
(node.type === type ? 1 : 0) +
|
|
89
|
+
(node.content?.reduce<number>((sum, child) => sum + countNodesByType(child, type), 0) ?? 0)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function firstParagraphContentTypes(editor: Editor): string[] {
|
|
94
|
+
const json = editor.getJSON();
|
|
95
|
+
return json.content?.[0]?.content?.map((node) => node.type ?? "") ?? [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
beforeEach(() => {
|
|
99
|
+
vi.useFakeTimers();
|
|
100
|
+
documentStore.getState().syncFromEditor("", []);
|
|
101
|
+
documentStore.getState().setSelection(null, null);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
afterEach(() => {
|
|
105
|
+
while (bindings.length) {
|
|
106
|
+
bindings.pop()?.detach();
|
|
107
|
+
}
|
|
108
|
+
while (editors.length) {
|
|
109
|
+
editors.pop()?.destroy();
|
|
110
|
+
}
|
|
111
|
+
vi.useRealTimers();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("createTiptapBinding", () => {
|
|
115
|
+
it("replaces fast partial updates in one stable preview range", async () => {
|
|
116
|
+
const editor = createEditor("<p></p>");
|
|
117
|
+
editors.push(editor);
|
|
118
|
+
const binding = createTiptapBinding(editor);
|
|
119
|
+
bindings.push(binding);
|
|
120
|
+
|
|
121
|
+
binding.beginSession!();
|
|
122
|
+
binding.insertPartial!("s1", "bonjour", 1);
|
|
123
|
+
binding.insertPartial!("s1", "bonjour docteur", 2);
|
|
124
|
+
expect(editor.getText()).toBe("");
|
|
125
|
+
await flushPartial();
|
|
126
|
+
|
|
127
|
+
expect(editor.getText()).toBe("bonjour docteur");
|
|
128
|
+
|
|
129
|
+
binding.commitFinal("s1", "bonjour docteur.");
|
|
130
|
+
|
|
131
|
+
expect(editor.getText()).toBe("bonjour docteur.");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("flushes a pending partial immediately on commit", async () => {
|
|
135
|
+
const editor = createEditor("<p></p>");
|
|
136
|
+
editors.push(editor);
|
|
137
|
+
const binding = createTiptapBinding(editor);
|
|
138
|
+
bindings.push(binding);
|
|
139
|
+
|
|
140
|
+
binding.beginSession!();
|
|
141
|
+
binding.insertPartial!("s1", "bonjour", 1);
|
|
142
|
+
binding.commitFinal("s1", "bonjour.");
|
|
143
|
+
|
|
144
|
+
expect(editor.getText()).toBe("bonjour.");
|
|
145
|
+
await flushPartial();
|
|
146
|
+
expect(editor.getText()).toBe("bonjour.");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("replaces the initial selection lazily on first insert", async () => {
|
|
150
|
+
const editor = createEditor("<p>hello old world</p>");
|
|
151
|
+
editors.push(editor);
|
|
152
|
+
const binding = createTiptapBinding(editor);
|
|
153
|
+
bindings.push(binding);
|
|
154
|
+
editor.commands.focus();
|
|
155
|
+
editor.commands.setTextSelection({ from: 7, to: 10 });
|
|
156
|
+
|
|
157
|
+
binding.beginSession!();
|
|
158
|
+
expect(editor.getText()).toBe("hello old world");
|
|
159
|
+
|
|
160
|
+
binding.insertPartial!("s1", "new", 1);
|
|
161
|
+
await flushPartial();
|
|
162
|
+
|
|
163
|
+
expect(editor.getText()).toBe("hello new world");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("removes absorbed preview segments on commit", async () => {
|
|
167
|
+
const editor = createEditor("<p></p>");
|
|
168
|
+
editors.push(editor);
|
|
169
|
+
const binding = createTiptapBinding(editor);
|
|
170
|
+
bindings.push(binding);
|
|
171
|
+
|
|
172
|
+
binding.beginSession!();
|
|
173
|
+
binding.insertPartial!("s1", "Bonjour", 1);
|
|
174
|
+
await flushPartial();
|
|
175
|
+
binding.insertPartial!("s2", "suite parasite", 1);
|
|
176
|
+
expect(editor.getText()).not.toContain("suite parasite");
|
|
177
|
+
|
|
178
|
+
binding.commitFinal("s1", "Bonjour.", { absorbedSegmentIds: ["s2"] });
|
|
179
|
+
await flushPartial();
|
|
180
|
+
|
|
181
|
+
expect(editor.getText()).toBe("Bonjour.");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("clearAll removes previews without deleting committed text", async () => {
|
|
185
|
+
const editor = createEditor("<p></p>");
|
|
186
|
+
editors.push(editor);
|
|
187
|
+
const binding = createTiptapBinding(editor);
|
|
188
|
+
bindings.push(binding);
|
|
189
|
+
|
|
190
|
+
binding.beginSession!();
|
|
191
|
+
binding.insertPartial!("s1", "Texte final", 1);
|
|
192
|
+
await flushPartial();
|
|
193
|
+
binding.commitFinal("s1", "Texte final");
|
|
194
|
+
binding.insertPartial!("s2", "preview", 1);
|
|
195
|
+
await flushPartial();
|
|
196
|
+
|
|
197
|
+
binding.clearAll?.();
|
|
198
|
+
|
|
199
|
+
expect(editor.getText()).toBe("Texte final");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("does not sync preview-only transactions to the document store", async () => {
|
|
203
|
+
const editor = createEditor("<p>Base</p>");
|
|
204
|
+
editors.push(editor);
|
|
205
|
+
const binding = createTiptapBinding(editor);
|
|
206
|
+
bindings.push(binding);
|
|
207
|
+
|
|
208
|
+
await flushStoreSync();
|
|
209
|
+
expect(documentStore.getState().content).toBe("Base");
|
|
210
|
+
|
|
211
|
+
binding.beginSession!();
|
|
212
|
+
binding.insertPartial!("s1", "preview", 1);
|
|
213
|
+
await flushPartial();
|
|
214
|
+
await flushStoreSync();
|
|
215
|
+
|
|
216
|
+
expect(editor.getText()).toBe("Base preview");
|
|
217
|
+
expect(documentStore.getState().content).toBe("Base");
|
|
218
|
+
|
|
219
|
+
binding.commitFinal("s1", "preview.");
|
|
220
|
+
await flushStoreSync();
|
|
221
|
+
|
|
222
|
+
expect(documentStore.getState().content).toBe("Base preview.");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("does not remove committed marks from a stale range after user edits", async () => {
|
|
226
|
+
const editor = createEditor("<p></p>");
|
|
227
|
+
editors.push(editor);
|
|
228
|
+
const binding = createTiptapBinding(editor);
|
|
229
|
+
bindings.push(binding);
|
|
230
|
+
|
|
231
|
+
binding.beginSession!();
|
|
232
|
+
binding.insertPartial!("s1", "Texte final", 1);
|
|
233
|
+
await flushPartial();
|
|
234
|
+
binding.commitFinal("s1", "Texte final");
|
|
235
|
+
|
|
236
|
+
editor.commands.setContent("<p>Texte modifié</p>");
|
|
237
|
+
await vi.advanceTimersByTimeAsync(1300);
|
|
238
|
+
|
|
239
|
+
expect(editor.getText()).toBe("Texte modifié");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("commits a single newline as a hardBreak node", () => {
|
|
243
|
+
const editor = createEditor("<p></p>");
|
|
244
|
+
editors.push(editor);
|
|
245
|
+
const binding = createTiptapBinding(editor);
|
|
246
|
+
bindings.push(binding);
|
|
247
|
+
|
|
248
|
+
binding.beginSession!();
|
|
249
|
+
binding.commitFinal("s1", "ligne 1\nligne 2");
|
|
250
|
+
|
|
251
|
+
expect(binding.getText()).toBe("ligne 1\nligne 2");
|
|
252
|
+
expect(countNodesByType(editor.getJSON(), "hardBreak")).toBe(1);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("replaces realtime committed segment when Small final arrives with same segmentId", () => {
|
|
256
|
+
const editor = createEditor("<p></p>");
|
|
257
|
+
editors.push(editor);
|
|
258
|
+
const binding = createTiptapBinding(editor);
|
|
259
|
+
bindings.push(binding);
|
|
260
|
+
|
|
261
|
+
binding.beginSession!();
|
|
262
|
+
binding.commitFinal(
|
|
263
|
+
"efde10b0",
|
|
264
|
+
"\nTechnique :\nacquisition hélicoïdale sans injection"
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
binding.commitFinal(
|
|
268
|
+
"efde10b0",
|
|
269
|
+
"\nTechnique :\nacquisition hélicoïdale sans injection de produit de contraste."
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
expect(binding.getText()).toBe(
|
|
273
|
+
"\nTechnique :\nacquisition hélicoïdale sans injection de produit de contraste."
|
|
274
|
+
);
|
|
275
|
+
expect(binding.getText()).not.toContain(
|
|
276
|
+
"acquisition hélicoïdale sans injection\nTechnique"
|
|
277
|
+
);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("keeps segment addressable after committed mark cleanup", async () => {
|
|
281
|
+
const editor = createEditor("<p></p>");
|
|
282
|
+
editors.push(editor);
|
|
283
|
+
const binding = createTiptapBinding(editor);
|
|
284
|
+
bindings.push(binding);
|
|
285
|
+
|
|
286
|
+
binding.beginSession!();
|
|
287
|
+
binding.commitFinal("efde10b0", "\nTechnique :\nacquisition");
|
|
288
|
+
|
|
289
|
+
await vi.advanceTimersByTimeAsync(1400);
|
|
290
|
+
|
|
291
|
+
binding.commitFinal(
|
|
292
|
+
"efde10b0",
|
|
293
|
+
"\nTechnique :\nacquisition hélicoïdale sans injection."
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
expect(binding.getText()).toBe(
|
|
297
|
+
"\nTechnique :\nacquisition hélicoïdale sans injection."
|
|
298
|
+
);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("does not let old realtime cleanup delete segment metadata after Small revision", async () => {
|
|
302
|
+
const editor = createEditor("<p></p>");
|
|
303
|
+
editors.push(editor);
|
|
304
|
+
const binding = createTiptapBinding(editor);
|
|
305
|
+
bindings.push(binding);
|
|
306
|
+
|
|
307
|
+
binding.beginSession!();
|
|
308
|
+
binding.commitFinal("efde10b0", "\nTechnique :\nacquisition");
|
|
309
|
+
|
|
310
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
311
|
+
|
|
312
|
+
binding.commitFinal(
|
|
313
|
+
"efde10b0",
|
|
314
|
+
"\nTechnique :\nacquisition hélicoïdale sans injection."
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
await vi.advanceTimersByTimeAsync(500);
|
|
318
|
+
|
|
319
|
+
binding.commitFinal(
|
|
320
|
+
"efde10b0",
|
|
321
|
+
"\nTechnique :\nacquisition hélicoïdale sans injection de produit de contraste."
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
expect(binding.getText()).toBe(
|
|
325
|
+
"\nTechnique :\nacquisition hélicoïdale sans injection de produit de contraste."
|
|
326
|
+
);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("does not clear an already committed segment with clearPartial", () => {
|
|
330
|
+
const editor = createEditor("<p></p>");
|
|
331
|
+
editors.push(editor);
|
|
332
|
+
const binding = createTiptapBinding(editor);
|
|
333
|
+
bindings.push(binding);
|
|
334
|
+
|
|
335
|
+
binding.beginSession!();
|
|
336
|
+
binding.commitFinal("efde10b0", "\nTechnique :\nacquisition");
|
|
337
|
+
binding.clearPartial("efde10b0");
|
|
338
|
+
|
|
339
|
+
expect(binding.getText()).toBe("\nTechnique :\nacquisition");
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("commits a paragraph break as two consecutive hardBreak nodes", () => {
|
|
343
|
+
const editor = createEditor("<p></p>");
|
|
344
|
+
editors.push(editor);
|
|
345
|
+
const binding = createTiptapBinding(editor);
|
|
346
|
+
bindings.push(binding);
|
|
347
|
+
|
|
348
|
+
binding.beginSession!();
|
|
349
|
+
binding.commitFinal("s1", "ligne 1\n\nligne 2");
|
|
350
|
+
|
|
351
|
+
expect(binding.getText()).toBe("ligne 1\n\nligne 2");
|
|
352
|
+
expect(firstParagraphContentTypes(editor)).toEqual([
|
|
353
|
+
"text",
|
|
354
|
+
"hardBreak",
|
|
355
|
+
"hardBreak",
|
|
356
|
+
"text",
|
|
357
|
+
]);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("preserves hardBreak nodes across incremental partial updates", async () => {
|
|
361
|
+
const editor = createEditor("<p></p>");
|
|
362
|
+
editors.push(editor);
|
|
363
|
+
const binding = createTiptapBinding(editor);
|
|
364
|
+
bindings.push(binding);
|
|
365
|
+
|
|
366
|
+
binding.beginSession!();
|
|
367
|
+
binding.insertPartial!("s1", "ligne 1\nligne 2", 1);
|
|
368
|
+
await flushPartial();
|
|
369
|
+
binding.insertPartial!("s1", "ligne 1\nligne 2 suite", 2);
|
|
370
|
+
await flushPartial();
|
|
371
|
+
|
|
372
|
+
expect(binding.getText()).toBe("ligne 1\nligne 2 suite");
|
|
373
|
+
expect(countNodesByType(editor.getJSON(), "hardBreak")).toBe(1);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("keeps the next committed segment positioned after a segment with a hardBreak", () => {
|
|
377
|
+
const editor = createEditor("<p></p>");
|
|
378
|
+
editors.push(editor);
|
|
379
|
+
const binding = createTiptapBinding(editor);
|
|
380
|
+
bindings.push(binding);
|
|
381
|
+
|
|
382
|
+
binding.beginSession!();
|
|
383
|
+
binding.commitFinal("s1", "ligne 1\nligne 2");
|
|
384
|
+
binding.commitFinal("s2", "suite");
|
|
385
|
+
|
|
386
|
+
expect(binding.getText()).toBe("ligne 1\nligne 2 suite");
|
|
387
|
+
expect(countNodesByType(editor.getJSON(), "hardBreak")).toBe(1);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("applies sequential insert_text and line_break operations in one transaction", () => {
|
|
391
|
+
const editor = createEditor("<p></p>");
|
|
392
|
+
editors.push(editor);
|
|
393
|
+
const binding = createTiptapBinding(editor);
|
|
394
|
+
bindings.push(binding);
|
|
395
|
+
|
|
396
|
+
binding.applyOperations?.([
|
|
397
|
+
{ id: "op1", type: "insert_text", position: "cursor", text: "ligne 1" },
|
|
398
|
+
{ id: "op2", type: "line_break", position: "cursor" },
|
|
399
|
+
{ id: "op3", type: "insert_text", position: "cursor", text: "ligne 2" },
|
|
400
|
+
]);
|
|
401
|
+
|
|
402
|
+
expect(binding.getText()).toBe("ligne 1\nligne 2");
|
|
403
|
+
expect(countNodesByType(editor.getJSON(), "hardBreak")).toBe(1);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("falls back to plain newline text when the schema has no hardBreak node", () => {
|
|
407
|
+
const editor = createEditor("<p></p>", { hardBreak: false });
|
|
408
|
+
editors.push(editor);
|
|
409
|
+
const binding = createTiptapBinding(editor);
|
|
410
|
+
bindings.push(binding);
|
|
411
|
+
|
|
412
|
+
binding.applyOperation({ id: "op1", type: "line_break", position: "cursor" });
|
|
413
|
+
|
|
414
|
+
expect(binding.getText()).toBe("\n");
|
|
415
|
+
expect(countNodesByType(editor.getJSON(), "hardBreak")).toBe(0);
|
|
416
|
+
});
|
|
417
|
+
});
|