@pilotiq/tiptap 3.20.0 → 4.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/CHANGELOG.md +37 -0
- package/dist/extensions/{AiInlineDiffExtension.d.ts → InlineDiffExtension.d.ts} +23 -23
- package/dist/extensions/{AiInlineDiffExtension.js → InlineDiffExtension.js} +33 -33
- package/dist/extensions/{AiSuggestionExtension.d.ts → SuggestionChipExtension.d.ts} +29 -29
- package/dist/extensions/{AiSuggestionExtension.js → SuggestionChipExtension.js} +52 -52
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/react/CollabTextRenderer.js +16 -16
- package/dist/react/MarkdownEditor.js +17 -17
- package/dist/react/{AiSuggestionBanner.d.ts → SuggestionBanner.d.ts} +6 -6
- package/dist/react/{AiSuggestionBanner.js → SuggestionBanner.js} +5 -5
- package/dist/react/TiptapEditor.js +17 -17
- package/dist/react/{useAiInlineDiff.d.ts → useInlineDiff.d.ts} +13 -13
- package/dist/react/{useAiInlineDiff.js → useInlineDiff.js} +36 -19
- package/dist/react/{useAiSuggestionBridge.d.ts → useSuggestionBridge.d.ts} +7 -7
- package/dist/react/{useAiSuggestionBridge.js → useSuggestionBridge.js} +10 -10
- package/dist/surgicalOps.d.ts +15 -2
- package/dist/surgicalOps.js +50 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @pilotiq/tiptap
|
|
2
2
|
|
|
3
|
+
## 4.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 58794c1: Rename the AI suggestion/diff primitives to provider-neutral names. These are generic inline-diff machinery the package exposes — they contain no AI logic and can be driven by any producer (the actual AI lives in `@pilotiq-pro/ai`), so the `Ai*` prefix oversold them.
|
|
8
|
+
|
|
9
|
+
**Renamed exports** (no aliases — direct importers must update):
|
|
10
|
+
|
|
11
|
+
| Old | New |
|
|
12
|
+
| ---------------------------------------------------------------------- | ---------------------------------------------------------------- |
|
|
13
|
+
| `AiSuggestionExtension` | `SuggestionChipExtension` |
|
|
14
|
+
| `AiSuggestion` | `InlineSuggestion` |
|
|
15
|
+
| `AiSuggestionExtensionOptions` | `SuggestionChipExtensionOptions` |
|
|
16
|
+
| `aiSuggestionPluginKey` | `suggestionChipPluginKey` |
|
|
17
|
+
| `useAiSuggestionBridge` | `useSuggestionBridge` |
|
|
18
|
+
| `AiInlineDiffExtension` | `InlineDiffExtension` |
|
|
19
|
+
| `AiInlineDiffExtensionOptions` | `InlineDiffExtensionOptions` |
|
|
20
|
+
| `aiInlineDiffPluginKey` | `inlineDiffPluginKey` |
|
|
21
|
+
| `getAiInlineDiffState` | `getInlineDiffState` |
|
|
22
|
+
| `AiDiffDisplayMode` | `DiffDisplayMode` |
|
|
23
|
+
| `useAiInlineDiff` / `useIsAiInlineDiffActive` / `readAiDiffViewMarker` | `useInlineDiff` / `useIsInlineDiffActive` / `readDiffViewMarker` |
|
|
24
|
+
| `AiSuggestionBanner` / `useAiSuggestionBanner` | `SuggestionBanner` / `useSuggestionBanner` |
|
|
25
|
+
|
|
26
|
+
**Renamed editor commands**: `addAiSuggestion` → `addSuggestion`, `approveAiSuggestion` → `approveSuggestion`, `rejectAiSuggestion` → `rejectSuggestion`, `approveAllAiSuggestions` → `approveAllSuggestions`, `rejectAllAiSuggestions` → `rejectAllSuggestions`, `clearAiSuggestions` → `clearSuggestions`; `startAiInlineDiff` → `startInlineDiff`, `applySurgicalAiInlineDiff` → `applySurgicalInlineDiff`, `acceptAiInlineDiff` → `acceptInlineDiff`, `rejectAiInlineDiff` → `rejectInlineDiff`.
|
|
27
|
+
|
|
28
|
+
**Renamed CSS classes / DOM markers** (consumers with custom stylesheets must update; the injected defaults follow the new names automatically): `pilotiq-ai-suggestion-*` → `pilotiq-suggestion-*`, `pilotiq-ai-banner-*` → `pilotiq-suggestion-banner-*`, `pilotiq-ai-diff-*` → `pilotiq-diff-*`, and the matching `data-pilotiq-ai-*` attributes → `data-pilotiq-*`.
|
|
29
|
+
|
|
30
|
+
**Deliberately unchanged**: the cross-package field-config markers `data-ai-suggestions-mode` / `data-ai-diff-view` (written by `@pilotiq-pro/ai`'s `.aiSuggestionsMode()` / `.aiDiffView()` field API) stay `ai`-prefixed — they configure genuinely AI-specific behavior, not provider-neutral primitives.
|
|
31
|
+
|
|
32
|
+
### Minor Changes
|
|
33
|
+
|
|
34
|
+
- 0096a7f: Add an in-block text find→replace surgical op — `planReplaceText` plus a `replace_text` case in the inline-diff dispatch.
|
|
35
|
+
|
|
36
|
+
It swaps the first occurrence of a `search` string with `replace`, preserving the surrounding node structure. This lets a producer (e.g. `@pilotiq-pro/ai`) fix a word, number, or typo **inside** a custom block (alert / prosCons / faq / keyTakeaways) or a table cell without rebuilding the block as HTML — which `replace_block` would force, flattening the block. The op is index-free: the match position resolves at apply time, so it composes safely after the index-based block ops in a batch. Returns `null` when `search` isn't present, so a stale or guessed search string changes nothing rather than corrupting the doc.
|
|
37
|
+
|
|
38
|
+
New export: `planReplaceText`. Surgical meta op: `{ op: 'replace_text', search, replace }`.
|
|
39
|
+
|
|
3
40
|
## 3.20.0
|
|
4
41
|
|
|
5
42
|
### Minor Changes
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Inline-diff visualization for whole-field AI suggestions.
|
|
3
3
|
*
|
|
4
|
-
* Sibling to `
|
|
4
|
+
* Sibling to `SuggestionChipExtension`, which handles producer-supplied
|
|
5
5
|
* range suggestions (surgical edits with `meta.editorRange`) via the
|
|
6
6
|
* inline chip widget. This extension handles the *whole-field* case:
|
|
7
7
|
* the AI proposes a new document and the user reviews the structural
|
|
8
8
|
* delta (added paragraphs, deleted text, mark changes, etc.) before
|
|
9
|
-
* accepting or rejecting via the host-mounted `<
|
|
9
|
+
* accepting or rejecting via the host-mounted `<SuggestionBanner>`.
|
|
10
10
|
*
|
|
11
11
|
* Architecture:
|
|
12
|
-
* 1. `
|
|
12
|
+
* 1. `startInlineDiff(id, newDoc)` captures the current doc as the
|
|
13
13
|
* baseline, replaces the doc body with `newDoc`'s content (so the
|
|
14
14
|
* editor surface IS the proposed state), and initializes a
|
|
15
15
|
* `prosemirror-changeset` tracking the original-to-current
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
* through next to the insert point (the deleted content
|
|
25
25
|
* isn't in the current doc, so a widget is the only way to
|
|
26
26
|
* surface it)
|
|
27
|
-
* 4. `
|
|
27
|
+
* 4. `acceptInlineDiff()` clears the plugin state — the current
|
|
28
28
|
* doc is the accepted state.
|
|
29
|
-
* 5. `
|
|
29
|
+
* 5. `rejectInlineDiff()` replaces the doc back to the baseline
|
|
30
30
|
* via a single transaction and clears state.
|
|
31
31
|
*
|
|
32
32
|
* For Tiptap Pro parity. See `[[project_pilotiq_text_field_tiptap_rules]]`.
|
|
@@ -38,7 +38,7 @@ import type { Node as ProseMirrorNode, Slice } from '@tiptap/pm/model';
|
|
|
38
38
|
import { ChangeSet } from 'prosemirror-changeset';
|
|
39
39
|
declare module '@tiptap/core' {
|
|
40
40
|
interface Commands<ReturnType> {
|
|
41
|
-
|
|
41
|
+
inlineDiff: {
|
|
42
42
|
/**
|
|
43
43
|
* Start the inline-diff review session. Snapshots the current
|
|
44
44
|
* doc as the baseline, replaces the doc with `newDocSlice`'s
|
|
@@ -48,7 +48,7 @@ declare module '@tiptap/core' {
|
|
|
48
48
|
* banner / approve handlers can correlate the editor state with
|
|
49
49
|
* the queue entry.
|
|
50
50
|
*/
|
|
51
|
-
|
|
51
|
+
startInlineDiff: (id: string, newDocSlice: Slice, displayMode?: DiffDisplayMode) => ReturnType;
|
|
52
52
|
/**
|
|
53
53
|
* Start the inline-diff review session for a surgical edit.
|
|
54
54
|
* Snapshots the current doc as the baseline, then runs
|
|
@@ -62,11 +62,11 @@ declare module '@tiptap/core' {
|
|
|
62
62
|
* `delete_block` / `update_block_mark` AI ops. Returns false (no
|
|
63
63
|
* dispatch) when `applyFn` produced no doc change.
|
|
64
64
|
*/
|
|
65
|
-
|
|
65
|
+
applySurgicalInlineDiff: (id: string, applyFn: (tr: Transaction) => void, displayMode?: DiffDisplayMode) => ReturnType;
|
|
66
66
|
/** Clear diff state. Current doc IS the accepted state. */
|
|
67
|
-
|
|
67
|
+
acceptInlineDiff: () => ReturnType;
|
|
68
68
|
/** Revert doc to the captured baseline and clear diff state. */
|
|
69
|
-
|
|
69
|
+
rejectInlineDiff: () => ReturnType;
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -79,30 +79,30 @@ declare module '@tiptap/core' {
|
|
|
79
79
|
* full-width red row (`−` gutter) above the change. Suits markdown
|
|
80
80
|
* sources / structured text where lines are the meaningful unit.
|
|
81
81
|
*/
|
|
82
|
-
export type
|
|
82
|
+
export type DiffDisplayMode = 'inline' | 'lines';
|
|
83
83
|
interface DiffState {
|
|
84
84
|
id: string;
|
|
85
|
-
/** Original doc captured at `
|
|
85
|
+
/** Original doc captured at `startInlineDiff` time — used for revert. */
|
|
86
86
|
baseline: ProseMirrorNode;
|
|
87
87
|
/** ChangeSet accumulating diffs since baseline. */
|
|
88
88
|
changeset: ChangeSet;
|
|
89
|
-
/** Rendering mode for the decorations — see `
|
|
90
|
-
displayMode:
|
|
89
|
+
/** Rendering mode for the decorations — see `DiffDisplayMode`. */
|
|
90
|
+
displayMode: DiffDisplayMode;
|
|
91
91
|
}
|
|
92
|
-
export declare const
|
|
92
|
+
export declare const inlineDiffPluginKey: PluginKey<DiffState | null>;
|
|
93
93
|
/** Read the active diff state, if any. Public for hosts that want to
|
|
94
94
|
* branch their banner UI on "diff active" vs "diff inactive". */
|
|
95
|
-
export declare function
|
|
96
|
-
export interface
|
|
95
|
+
export declare function getInlineDiffState(state: EditorState): DiffState | null;
|
|
96
|
+
export interface InlineDiffExtensionOptions {
|
|
97
97
|
/**
|
|
98
98
|
* Class prefix for inline-diff decorations. Defaults to
|
|
99
|
-
* `'pilotiq-
|
|
100
|
-
* - `pilotiq-
|
|
101
|
-
* - `pilotiq-
|
|
102
|
-
* - `pilotiq-
|
|
99
|
+
* `'pilotiq-diff'`, producing:
|
|
100
|
+
* - `pilotiq-diff-inserted` (green-background span on new ranges)
|
|
101
|
+
* - `pilotiq-diff-deleted` (widget DOM root for deleted text)
|
|
102
|
+
* - `pilotiq-diff-deleted-text` (the strikethrough span inside)
|
|
103
103
|
*/
|
|
104
104
|
classPrefix?: string;
|
|
105
105
|
}
|
|
106
|
-
export declare const
|
|
106
|
+
export declare const InlineDiffExtension: Extension<InlineDiffExtensionOptions, any>;
|
|
107
107
|
export {};
|
|
108
|
-
//# sourceMappingURL=
|
|
108
|
+
//# sourceMappingURL=InlineDiffExtension.d.ts.map
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Inline-diff visualization for whole-field AI suggestions.
|
|
3
3
|
*
|
|
4
|
-
* Sibling to `
|
|
4
|
+
* Sibling to `SuggestionChipExtension`, which handles producer-supplied
|
|
5
5
|
* range suggestions (surgical edits with `meta.editorRange`) via the
|
|
6
6
|
* inline chip widget. This extension handles the *whole-field* case:
|
|
7
7
|
* the AI proposes a new document and the user reviews the structural
|
|
8
8
|
* delta (added paragraphs, deleted text, mark changes, etc.) before
|
|
9
|
-
* accepting or rejecting via the host-mounted `<
|
|
9
|
+
* accepting or rejecting via the host-mounted `<SuggestionBanner>`.
|
|
10
10
|
*
|
|
11
11
|
* Architecture:
|
|
12
|
-
* 1. `
|
|
12
|
+
* 1. `startInlineDiff(id, newDoc)` captures the current doc as the
|
|
13
13
|
* baseline, replaces the doc body with `newDoc`'s content (so the
|
|
14
14
|
* editor surface IS the proposed state), and initializes a
|
|
15
15
|
* `prosemirror-changeset` tracking the original-to-current
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
* through next to the insert point (the deleted content
|
|
25
25
|
* isn't in the current doc, so a widget is the only way to
|
|
26
26
|
* surface it)
|
|
27
|
-
* 4. `
|
|
27
|
+
* 4. `acceptInlineDiff()` clears the plugin state — the current
|
|
28
28
|
* doc is the accepted state.
|
|
29
|
-
* 5. `
|
|
29
|
+
* 5. `rejectInlineDiff()` replaces the doc back to the baseline
|
|
30
30
|
* via a single transaction and clears state.
|
|
31
31
|
*
|
|
32
32
|
* For Tiptap Pro parity. See `[[project_pilotiq_text_field_tiptap_rules]]`.
|
|
@@ -36,22 +36,22 @@ import { Plugin, PluginKey } from '@tiptap/pm/state';
|
|
|
36
36
|
import { Decoration, DecorationSet } from '@tiptap/pm/view';
|
|
37
37
|
import { DOMSerializer, Fragment } from '@tiptap/pm/model';
|
|
38
38
|
import { ChangeSet } from 'prosemirror-changeset';
|
|
39
|
-
export const
|
|
39
|
+
export const inlineDiffPluginKey = new PluginKey('pilotiqInlineDiff');
|
|
40
40
|
/** Read the active diff state, if any. Public for hosts that want to
|
|
41
41
|
* branch their banner UI on "diff active" vs "diff inactive". */
|
|
42
|
-
export function
|
|
43
|
-
return
|
|
42
|
+
export function getInlineDiffState(state) {
|
|
43
|
+
return inlineDiffPluginKey.getState(state) ?? null;
|
|
44
44
|
}
|
|
45
|
-
export const
|
|
46
|
-
name: '
|
|
45
|
+
export const InlineDiffExtension = Extension.create({
|
|
46
|
+
name: 'pilotiqInlineDiff',
|
|
47
47
|
addOptions() {
|
|
48
|
-
return { classPrefix: 'pilotiq-
|
|
48
|
+
return { classPrefix: 'pilotiq-diff' };
|
|
49
49
|
},
|
|
50
50
|
onCreate() {
|
|
51
51
|
// Mirror the chip CSS injection pattern. Idempotent via sentinel.
|
|
52
52
|
if (typeof document === 'undefined')
|
|
53
53
|
return;
|
|
54
|
-
const SENTINEL = 'data-pilotiq-
|
|
54
|
+
const SENTINEL = 'data-pilotiq-diff-styles';
|
|
55
55
|
if (document.head.querySelector(`style[${SENTINEL}]`))
|
|
56
56
|
return;
|
|
57
57
|
const prefix = this.options.classPrefix;
|
|
@@ -129,7 +129,7 @@ export const AiInlineDiffExtension = Extension.create({
|
|
|
129
129
|
},
|
|
130
130
|
addCommands() {
|
|
131
131
|
return {
|
|
132
|
-
|
|
132
|
+
startInlineDiff: (id, newDocSlice, displayMode) => ({ tr, state, dispatch }) => {
|
|
133
133
|
const baseline = state.doc;
|
|
134
134
|
const docEnd = state.doc.content.size;
|
|
135
135
|
// Replace the whole doc body with the proposed content. The
|
|
@@ -137,31 +137,31 @@ export const AiInlineDiffExtension = Extension.create({
|
|
|
137
137
|
// throws (callers should pre-validate via `editor.schema`).
|
|
138
138
|
tr.replaceRange(0, docEnd, newDocSlice);
|
|
139
139
|
const meta = { type: 'start', id, baseline, ...(displayMode ? { displayMode } : {}) };
|
|
140
|
-
tr.setMeta(
|
|
140
|
+
tr.setMeta(inlineDiffPluginKey, meta);
|
|
141
141
|
if (dispatch)
|
|
142
142
|
dispatch(tr);
|
|
143
143
|
return true;
|
|
144
144
|
},
|
|
145
|
-
|
|
145
|
+
applySurgicalInlineDiff: (id, applyFn, displayMode) => ({ tr, state, dispatch }) => {
|
|
146
146
|
const baseline = state.doc;
|
|
147
147
|
applyFn(tr);
|
|
148
148
|
if (!tr.docChanged)
|
|
149
149
|
return false;
|
|
150
150
|
const meta = { type: 'start', id, baseline, ...(displayMode ? { displayMode } : {}) };
|
|
151
|
-
tr.setMeta(
|
|
151
|
+
tr.setMeta(inlineDiffPluginKey, meta);
|
|
152
152
|
if (dispatch)
|
|
153
153
|
dispatch(tr);
|
|
154
154
|
return true;
|
|
155
155
|
},
|
|
156
|
-
|
|
156
|
+
acceptInlineDiff: () => ({ tr, dispatch }) => {
|
|
157
157
|
const meta = { type: 'clear' };
|
|
158
|
-
tr.setMeta(
|
|
158
|
+
tr.setMeta(inlineDiffPluginKey, meta);
|
|
159
159
|
if (dispatch)
|
|
160
160
|
dispatch(tr);
|
|
161
161
|
return true;
|
|
162
162
|
},
|
|
163
|
-
|
|
164
|
-
const ds =
|
|
163
|
+
rejectInlineDiff: () => ({ tr, state, dispatch }) => {
|
|
164
|
+
const ds = inlineDiffPluginKey.getState(state);
|
|
165
165
|
if (!ds)
|
|
166
166
|
return false;
|
|
167
167
|
const docEnd = state.doc.content.size;
|
|
@@ -170,7 +170,7 @@ export const AiInlineDiffExtension = Extension.create({
|
|
|
170
170
|
// doc replace).
|
|
171
171
|
tr.replaceWith(0, docEnd, ds.baseline.content);
|
|
172
172
|
const meta = { type: 'clear' };
|
|
173
|
-
tr.setMeta(
|
|
173
|
+
tr.setMeta(inlineDiffPluginKey, meta);
|
|
174
174
|
if (dispatch)
|
|
175
175
|
dispatch(tr);
|
|
176
176
|
return true;
|
|
@@ -181,11 +181,11 @@ export const AiInlineDiffExtension = Extension.create({
|
|
|
181
181
|
const ext = this;
|
|
182
182
|
return [
|
|
183
183
|
new Plugin({
|
|
184
|
-
key:
|
|
184
|
+
key: inlineDiffPluginKey,
|
|
185
185
|
state: {
|
|
186
186
|
init() { return null; },
|
|
187
187
|
apply(tr, value) {
|
|
188
|
-
const meta = tr.getMeta(
|
|
188
|
+
const meta = tr.getMeta(inlineDiffPluginKey);
|
|
189
189
|
if (meta?.type === 'start') {
|
|
190
190
|
// Baseline captured BEFORE the replaceRange step in this
|
|
191
191
|
// same transaction. The changeset's `addSteps` consumes
|
|
@@ -210,10 +210,10 @@ export const AiInlineDiffExtension = Extension.create({
|
|
|
210
210
|
},
|
|
211
211
|
props: {
|
|
212
212
|
decorations(state) {
|
|
213
|
-
const ds =
|
|
213
|
+
const ds = inlineDiffPluginKey.getState(state);
|
|
214
214
|
if (!ds)
|
|
215
215
|
return DecorationSet.empty;
|
|
216
|
-
return buildDiffDecorations(state, ds, ext.options.classPrefix ?? 'pilotiq-
|
|
216
|
+
return buildDiffDecorations(state, ds, ext.options.classPrefix ?? 'pilotiq-diff');
|
|
217
217
|
},
|
|
218
218
|
// While a LINES-mode diff is active, force the editor root to
|
|
219
219
|
// block layout. Some text surfaces style the root as a flex row
|
|
@@ -221,9 +221,9 @@ export const AiInlineDiffExtension = Extension.create({
|
|
|
221
221
|
// rows lay out as overflowing columns. Drops automatically on
|
|
222
222
|
// accept / reject.
|
|
223
223
|
attributes(state) {
|
|
224
|
-
const ds =
|
|
224
|
+
const ds = inlineDiffPluginKey.getState(state);
|
|
225
225
|
return ds?.displayMode === 'lines'
|
|
226
|
-
? { class: `${ext.options.classPrefix ?? 'pilotiq-
|
|
226
|
+
? { class: `${ext.options.classPrefix ?? 'pilotiq-diff'}-lines-active` }
|
|
227
227
|
: {};
|
|
228
228
|
},
|
|
229
229
|
},
|
|
@@ -246,7 +246,7 @@ function buildDiffDecorations(state, ds, prefix) {
|
|
|
246
246
|
if (toB > fromB) {
|
|
247
247
|
decos.push(Decoration.inline(fromB, toB, {
|
|
248
248
|
class: `${prefix}-inserted`,
|
|
249
|
-
'data-pilotiq-
|
|
249
|
+
'data-pilotiq-diff-id': ds.id,
|
|
250
250
|
}));
|
|
251
251
|
}
|
|
252
252
|
// Deleted content — pull from the baseline using the `fromA..toA`
|
|
@@ -257,7 +257,7 @@ function buildDiffDecorations(state, ds, prefix) {
|
|
|
257
257
|
decos.push(Decoration.widget(fromB, () => buildDeletedWidget(ds.baseline, change.fromA, change.toA, prefix, ds.id), {
|
|
258
258
|
side: -1,
|
|
259
259
|
ignoreSelection: true,
|
|
260
|
-
key: `pilotiq-
|
|
260
|
+
key: `pilotiq-diff:deleted:${change.fromA}:${change.toA}`,
|
|
261
261
|
}));
|
|
262
262
|
}
|
|
263
263
|
}
|
|
@@ -281,7 +281,7 @@ function buildDiffDecorations(state, ds, prefix) {
|
|
|
281
281
|
function buildDeletedWidget(baseline, fromA, toA, prefix, id) {
|
|
282
282
|
const root = document.createElement('span');
|
|
283
283
|
root.className = `${prefix}-deleted`;
|
|
284
|
-
root.setAttribute('data-pilotiq-
|
|
284
|
+
root.setAttribute('data-pilotiq-diff-id', id);
|
|
285
285
|
root.contentEditable = 'false';
|
|
286
286
|
// Walk the baseline's top-level blocks; for each one the deleted range
|
|
287
287
|
// touches, keep the whole node when fully covered, else cut it to the
|
|
@@ -356,7 +356,7 @@ function buildLineDiffDecorations(state, ds, prefix) {
|
|
|
356
356
|
decos.push(Decoration.widget(anchor, () => buildDeletedLinesWidget(nodes, schema, prefix, ds.id), {
|
|
357
357
|
side: -1,
|
|
358
358
|
ignoreSelection: true,
|
|
359
|
-
key: `pilotiq-
|
|
359
|
+
key: `pilotiq-diff:deleted-lines:${anchor}:${nodes.length}`,
|
|
360
360
|
}));
|
|
361
361
|
};
|
|
362
362
|
for (const tok of lcsBlockDiffTokens(baseBlocks.map(b => b.text), current.map(c => c.text))) {
|
|
@@ -370,7 +370,7 @@ function buildLineDiffDecorations(state, ds, prefix) {
|
|
|
370
370
|
flushRemoved(current[j].pos);
|
|
371
371
|
decos.push(Decoration.node(current[j].pos, current[j].pos + current[j].nodeSize, {
|
|
372
372
|
class: `${prefix}-inserted-line`,
|
|
373
|
-
'data-pilotiq-
|
|
373
|
+
'data-pilotiq-diff-id': ds.id,
|
|
374
374
|
}));
|
|
375
375
|
j++;
|
|
376
376
|
continue;
|
|
@@ -435,7 +435,7 @@ function lcsBlockDiffTokens(a, b) {
|
|
|
435
435
|
function buildDeletedLinesWidget(nodes, schema, prefix, id) {
|
|
436
436
|
const root = document.createElement('div');
|
|
437
437
|
root.className = `${prefix}-deleted-lines`;
|
|
438
|
-
root.setAttribute('data-pilotiq-
|
|
438
|
+
root.setAttribute('data-pilotiq-diff-id', id);
|
|
439
439
|
root.contentEditable = 'false';
|
|
440
440
|
const serializer = DOMSerializer.fromSchema(schema);
|
|
441
441
|
for (const node of nodes) {
|
|
@@ -8,7 +8,7 @@ import { PluginKey } from '@tiptap/pm/state';
|
|
|
8
8
|
* editor as-is when the suggestion is approved (the original range's marks
|
|
9
9
|
* are preserved on the inserted text node by ProseMirror).
|
|
10
10
|
*/
|
|
11
|
-
export interface
|
|
11
|
+
export interface InlineSuggestion {
|
|
12
12
|
/** Stable id; consumer-provided. Re-adding with the same id replaces the prior entry. */
|
|
13
13
|
id: string;
|
|
14
14
|
/** Inclusive document position the original range starts at. */
|
|
@@ -23,16 +23,16 @@ export interface AiSuggestion {
|
|
|
23
23
|
agentLabel?: string;
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
export interface
|
|
26
|
+
export interface SuggestionChipExtensionOptions {
|
|
27
27
|
/**
|
|
28
28
|
* Class prefix for both decoration spans and chip widgets. The package
|
|
29
29
|
* stays CSS-free — consumers ship the matching styles. Default
|
|
30
|
-
* `'pilotiq-
|
|
31
|
-
* - `pilotiq-
|
|
32
|
-
* - `pilotiq-
|
|
33
|
-
* - `pilotiq-
|
|
34
|
-
* - `pilotiq-
|
|
35
|
-
* - `pilotiq-
|
|
30
|
+
* `'pilotiq-suggestion'` produces classes:
|
|
31
|
+
* - `pilotiq-suggestion-original` (strikethrough on the original range)
|
|
32
|
+
* - `pilotiq-suggestion-chip` (root of the inline widget)
|
|
33
|
+
* - `pilotiq-suggestion-replacement` (the suggested-text preview span)
|
|
34
|
+
* - `pilotiq-suggestion-accept` (Approve button)
|
|
35
|
+
* - `pilotiq-suggestion-reject` (Reject button)
|
|
36
36
|
*/
|
|
37
37
|
classPrefix: string;
|
|
38
38
|
/**
|
|
@@ -40,52 +40,52 @@ export interface AiSuggestionExtensionOptions {
|
|
|
40
40
|
* `reject*`, `clear*`, or after a doc edit collapses a range. Lets the host
|
|
41
41
|
* mirror state into a React context (e.g. `PendingSuggestionsApi`).
|
|
42
42
|
*/
|
|
43
|
-
onChange?: (suggestions:
|
|
43
|
+
onChange?: (suggestions: InlineSuggestion[]) => void;
|
|
44
44
|
}
|
|
45
45
|
declare module '@tiptap/core' {
|
|
46
46
|
interface Commands<ReturnType> {
|
|
47
|
-
|
|
47
|
+
suggestionChip: {
|
|
48
48
|
/** Add or replace a suggestion (matched by id). */
|
|
49
|
-
|
|
49
|
+
addSuggestion: (suggestion: InlineSuggestion) => ReturnType;
|
|
50
50
|
/** Add or replace many suggestions in one transaction. */
|
|
51
|
-
|
|
51
|
+
addSuggestions: (suggestions: InlineSuggestion[]) => ReturnType;
|
|
52
52
|
/** Apply the replacement to the doc and drop the suggestion. */
|
|
53
|
-
|
|
53
|
+
approveSuggestion: (id: string) => ReturnType;
|
|
54
54
|
/** Drop the suggestion without touching the doc. */
|
|
55
|
-
|
|
55
|
+
rejectSuggestion: (id: string) => ReturnType;
|
|
56
56
|
/** Apply every replacement in highest-`from`-first order. */
|
|
57
|
-
|
|
57
|
+
approveAllSuggestions: () => ReturnType;
|
|
58
58
|
/** Drop every suggestion. */
|
|
59
|
-
|
|
60
|
-
/** Alias for `
|
|
61
|
-
|
|
59
|
+
rejectAllSuggestions: () => ReturnType;
|
|
60
|
+
/** Alias for `rejectAllSuggestions`. */
|
|
61
|
+
clearSuggestions: () => ReturnType;
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
interface PluginState {
|
|
66
|
-
suggestions: readonly
|
|
66
|
+
suggestions: readonly InlineSuggestion[];
|
|
67
67
|
}
|
|
68
|
-
export declare const
|
|
68
|
+
export declare const suggestionChipPluginKey: PluginKey<PluginState>;
|
|
69
69
|
/**
|
|
70
70
|
* Append or replace by id. Pure — exported for tests and so the same dedupe
|
|
71
71
|
* shape can drive consumer-side mirror state.
|
|
72
72
|
*/
|
|
73
|
-
export declare function upsertSuggestion(current: readonly
|
|
73
|
+
export declare function upsertSuggestion(current: readonly InlineSuggestion[], next: InlineSuggestion): InlineSuggestion[];
|
|
74
74
|
/** Append or replace many — semantically equivalent to a fold over `upsertSuggestion`. */
|
|
75
|
-
export declare function upsertSuggestions(current: readonly
|
|
75
|
+
export declare function upsertSuggestions(current: readonly InlineSuggestion[], nexts: readonly InlineSuggestion[]): InlineSuggestion[];
|
|
76
76
|
/** Remove by id. */
|
|
77
|
-
export declare function removeSuggestion(current: readonly
|
|
77
|
+
export declare function removeSuggestion(current: readonly InlineSuggestion[], id: string): InlineSuggestion[];
|
|
78
78
|
/**
|
|
79
79
|
* Remap survivors through a PM mapping; drop ranges that collapsed past
|
|
80
80
|
* each other (`to < from` after remap). Pure — exported for tests.
|
|
81
81
|
*/
|
|
82
|
-
export declare function remapSuggestions(suggestions: readonly
|
|
82
|
+
export declare function remapSuggestions(suggestions: readonly InlineSuggestion[], map: (pos: number, side: -1 | 1) => number): InlineSuggestion[];
|
|
83
83
|
/**
|
|
84
84
|
* Order suggestions for `approveAll` so the highest-`from` runs first;
|
|
85
85
|
* earlier-in-doc replacements then can't shift positions of later ones.
|
|
86
86
|
* Pure — exported for tests.
|
|
87
87
|
*/
|
|
88
|
-
export declare function sortForApproveAll(suggestions: readonly
|
|
88
|
+
export declare function sortForApproveAll(suggestions: readonly InlineSuggestion[]): InlineSuggestion[];
|
|
89
89
|
/**
|
|
90
90
|
* Editor extension that tracks AI-suggested edits as inline decorations with
|
|
91
91
|
* per-hunk Approve/Reject chips. The package is CSS-free — consumers wire
|
|
@@ -93,7 +93,7 @@ export declare function sortForApproveAll(suggestions: readonly AiSuggestion[]):
|
|
|
93
93
|
*
|
|
94
94
|
* Usage:
|
|
95
95
|
* ```ts
|
|
96
|
-
* editor.commands.
|
|
96
|
+
* editor.commands.addSuggestion({
|
|
97
97
|
* id: 'seo-1',
|
|
98
98
|
* from: 12,
|
|
99
99
|
* to: 18,
|
|
@@ -101,14 +101,14 @@ export declare function sortForApproveAll(suggestions: readonly AiSuggestion[]):
|
|
|
101
101
|
* source: { agentLabel: 'SEO' },
|
|
102
102
|
* })
|
|
103
103
|
* // …user clicks ✓ on the chip, or:
|
|
104
|
-
* editor.commands.
|
|
104
|
+
* editor.commands.approveSuggestion('seo-1')
|
|
105
105
|
* ```
|
|
106
106
|
*
|
|
107
107
|
* Mounted by default inside `TiptapEditor`; consumer code reaches it through
|
|
108
108
|
* the editor's command surface.
|
|
109
109
|
*/
|
|
110
|
-
export declare const
|
|
110
|
+
export declare const SuggestionChipExtension: Extension<SuggestionChipExtensionOptions, any>;
|
|
111
111
|
/** Bound `pos` into `[0, max]`; non-finite or negative input collapses to 0. */
|
|
112
112
|
export declare function clampPos(pos: number, max: number): number;
|
|
113
113
|
export {};
|
|
114
|
-
//# sourceMappingURL=
|
|
114
|
+
//# sourceMappingURL=SuggestionChipExtension.d.ts.map
|