Skip to content

Commit ea31ecc

Browse files
authored
fix(Clipboard): correct handling of pasting into code (#678)
1 parent ad03230 commit ea31ecc

File tree

2 files changed

+71
-3
lines changed

2 files changed

+71
-3
lines changed

src/extensions/behavior/Clipboard/code.ts

+67-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1-
import type {Mark} from 'prosemirror-model';
2-
import type {EditorState} from 'prosemirror-state';
1+
import dd from 'ts-dedent';
2+
3+
import {getLoggerFromState} from '#core';
4+
import type {Mark} from '#pm/model';
5+
import {type EditorState, Plugin} from '#pm/state';
6+
7+
import {DataTransferType, isFilesOnly, isIosSafariShare} from './utils';
38

49
const isCodeMark = (mark: Mark) => mark.type.spec.code;
510

@@ -23,3 +28,63 @@ export function isInsideInlineCode(state: EditorState): boolean {
2328
}
2429
return fromHasCodeMark;
2530
}
31+
32+
/**
33+
* This plugin handles paste into any type of code: code block or code mark.
34+
* If selection is inside code, it always prevents execution of all next paste handlers.
35+
* It takes a value from following clipboard data types: uri-list, files or text data.
36+
*/
37+
export const handlePasteIntoCodePlugin = () => {
38+
return new Plugin({
39+
props: {
40+
handleDOMEvents: {
41+
paste(view, event) {
42+
if (!event.clipboardData) return false;
43+
const {clipboardData} = event;
44+
45+
const codeType = isInsideCode(view.state);
46+
if (!codeType) return false;
47+
48+
let text: string;
49+
let dataFormat: string;
50+
51+
if (isIosSafariShare(clipboardData)) {
52+
dataFormat = DataTransferType.UriList;
53+
text = clipboardData.getData(DataTransferType.UriList);
54+
} else if (isFilesOnly(clipboardData)) {
55+
dataFormat = DataTransferType.Files;
56+
text = Array.from(clipboardData.files)
57+
.map((file) => file.name)
58+
.join(' ');
59+
} else {
60+
dataFormat = DataTransferType.Text;
61+
text = dd(clipboardData.getData(DataTransferType.Text));
62+
}
63+
64+
if (codeType === 'inline') {
65+
text = text.replaceAll('\n', '↵');
66+
}
67+
68+
const {state, dispatch} = view;
69+
70+
getLoggerFromState(state).event({
71+
codeType,
72+
dataFormat,
73+
domEvent: 'paste',
74+
event: 'paste-into-code',
75+
dataTypes: clipboardData.types,
76+
});
77+
78+
event.preventDefault();
79+
dispatch(
80+
state.tr
81+
.replaceSelectionWith(state.schema.text(text), true)
82+
.scrollIntoView(),
83+
);
84+
85+
return true;
86+
},
87+
},
88+
},
89+
});
90+
};

src/extensions/behavior/Clipboard/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import type {ExtensionAuto} from '../../../core';
1+
import type {ExtensionAuto} from '#core';
22

33
import {type ClipboardPluginOptions, clipboard} from './clipboard';
4+
import {handlePasteIntoCodePlugin} from './code';
45
import * as clipboardUtils from './utils';
56

67
export {clipboardUtils};
@@ -22,4 +23,6 @@ export const Clipboard: ExtensionAuto<ClipboardOptions> = (builder, opts) => {
2223
}),
2324
builder.Priority.VeryLow,
2425
);
26+
27+
builder.addPlugin(handlePasteIntoCodePlugin, builder.Priority.Highest);
2528
};

0 commit comments

Comments
 (0)