Skip to content

Commit 7728b1f

Browse files
authored
fix: copy-pasted text in suggestion mode (#3576)
* fix: track SuperDoc slice paste in suggestion mode
1 parent 633849a commit 7728b1f

2 files changed

Lines changed: 65 additions & 23 deletions

File tree

packages/super-editor/src/editors/v1/extensions/track-changes/trackChangesHelpers/trackChangesHelpers.test.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,37 @@ describe('trackChangesHelpers', () => {
700700
expect(result).toBe(tr);
701701
});
702702

703+
it('trackedTransaction does not treat addToHistory as tracking intent by itself', () => {
704+
const state = createState(createDocWithText('abc'));
705+
const tr = state.tr.insertText('!', 1);
706+
tr.setMeta('addToHistory', false);
707+
708+
const result = trackedTransaction({ tr, state, user });
709+
710+
expect(result).toBe(tr);
711+
});
712+
713+
it('trackedTransaction tracks SuperDoc slice paste transactions and preserves paste metadata', () => {
714+
const state = createState(createDocWithText('abc'));
715+
const tr = state.tr.insertText('Pasted', 2);
716+
tr.setMeta('superdocSlicePaste', true);
717+
tr.setMeta('paste', true);
718+
tr.setMeta('uiEvent', 'paste');
719+
720+
const tracked = trackedTransaction({ tr, state, user });
721+
const nextState = state.apply(tracked);
722+
723+
expect(tracked).not.toBe(tr);
724+
expect(tracked.getMeta('superdocSlicePaste')).toBe(true);
725+
expect(tracked.getMeta('paste')).toBe(true);
726+
expect(tracked.getMeta('uiEvent')).toBe('paste');
727+
728+
const hasTrackedPastedText = documentHelpers
729+
.findInlineNodes(nextState.doc)
730+
.some(({ node }) => node.text === 'Pasted' && node.marks.some((mark) => mark.type.name === TrackInsertMarkName));
731+
expect(hasTrackedPastedText).toBe(true);
732+
});
733+
703734
it('trackedTransaction skips Yjs-origin transactions', () => {
704735
const state = createState(createDocWithText('abc'));
705736
const tr = state.tr.insertText('!', 1);

packages/super-editor/src/editors/v1/extensions/track-changes/trackChangesHelpers/trackedTransaction.js

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,29 @@ const DEAD_KEY_PLACEHOLDER_MARKS = new Map([
3232
['¨', '\u0308'],
3333
]);
3434

35+
const TRACKABLE_META_KEYS = [
36+
'inputType',
37+
'uiEvent',
38+
'paste',
39+
'pointer',
40+
'composition',
41+
'superdocSlicePaste',
42+
'forceTrackChanges',
43+
'protectTrackedReviewState',
44+
];
45+
46+
const PASSTHROUGH_META_KEYS = [
47+
'inputType',
48+
'uiEvent',
49+
'paste',
50+
'pointer',
51+
'composition',
52+
'addToHistory',
53+
'superdocSlicePaste',
54+
];
55+
56+
const ALLOWED_META_KEYS = new Set([...TRACKABLE_META_KEYS, ySyncPluginKey.key]);
57+
3558
const getTextNodeAtPos = ({ doc, pos }) => {
3659
let found = null;
3760

@@ -275,6 +298,15 @@ const mergeTrackChangesMeta = (tr, extraMeta) => {
275298
tr.setMeta(TrackChangesBasePluginKey, { ...existingMeta, ...extraMeta });
276299
};
277300

301+
const copyPassthroughMeta = (sourceTr, targetTr) => {
302+
PASSTHROUGH_META_KEYS.forEach((key) => {
303+
const value = sourceTr.getMeta(key);
304+
if (value !== undefined) {
305+
targetTr.setMeta(key, value);
306+
}
307+
});
308+
};
309+
278310
const getPendingDeadKeyPlaceholder = ({ tr, newTr, user }) => {
279311
if (!isCompositionTransaction(tr) || tr.steps.length !== 1) {
280312
return null;
@@ -343,18 +375,11 @@ const getPendingDeadKeyPlaceholder = ({ tr, newTr, user }) => {
343375
* transaction ready to dispatch.
344376
*/
345377
export const trackedTransaction = ({ tr, state, user, replacements = 'paired' }) => {
346-
const onlyInputTypeMeta = ['inputType', 'uiEvent', 'paste', 'pointer', 'composition'];
347378
const notAllowedMeta = ['historyUndo', 'historyRedo', 'acceptReject'];
348379
const isProgrammaticInput = tr.getMeta('inputType') === 'programmatic';
349380
const ySyncMeta = tr.getMeta(ySyncPluginKey);
350381
const pendingDeadKeyPlaceholder = TrackChangesBasePluginKey.getState(state)?.pendingDeadKeyPlaceholder ?? null;
351-
const allowedMeta = new Set([
352-
...onlyInputTypeMeta,
353-
ySyncPluginKey.key,
354-
'forceTrackChanges',
355-
'protectTrackedReviewState',
356-
]);
357-
const hasDisallowedMeta = tr.meta && Object.keys(tr.meta).some((meta) => !allowedMeta.has(meta));
382+
const hasDisallowedMeta = tr.meta && Object.keys(tr.meta).some((meta) => !ALLOWED_META_KEYS.has(meta));
358383

359384
if (
360385
ySyncMeta?.isChangeOrigin || // Skip Yjs-origin transactions (remote/rehydration).
@@ -442,21 +467,7 @@ export const trackedTransaction = ({ tr, state, user, replacements = 'paired' })
442467
}
443468
});
444469

445-
if (tr.getMeta('inputType')) {
446-
newTr.setMeta('inputType', tr.getMeta('inputType'));
447-
}
448-
449-
if (tr.getMeta('uiEvent')) {
450-
newTr.setMeta('uiEvent', tr.getMeta('uiEvent'));
451-
}
452-
453-
if (tr.getMeta('composition') !== undefined) {
454-
newTr.setMeta('composition', tr.getMeta('composition'));
455-
}
456-
457-
if (tr.getMeta('addToHistory') !== undefined) {
458-
newTr.setMeta('addToHistory', tr.getMeta('addToHistory'));
459-
}
470+
copyPassthroughMeta(tr, newTr);
460471

461472
mergeTrackChangesMeta(newTr, {
462473
pendingDeadKeyPlaceholder: getPendingDeadKeyPlaceholder({ tr, newTr, user }),

0 commit comments

Comments
 (0)