Skip to content

Commit c52d9a8

Browse files
authored
fix: Paste partially over code prevents pasting PM nodes (outline#8836)
* fix: Paste over any inline code prevents pasting nodes closes outline#8825 * Add inclusive logic for isNodeActive
1 parent 588e5bc commit c52d9a8

4 files changed

Lines changed: 56 additions & 15 deletions

File tree

app/editor/extensions/PasteHandler.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export default class PasteHandler extends Extension {
8888

8989
// If the users selection is currently in a code block then paste
9090
// as plain text, ignore all formatting and HTML content.
91-
if (isInCode(state)) {
91+
if (isInCode(state, { inclusive: true })) {
9292
event.preventDefault();
9393
view.dispatch(state.tr.insertText(text));
9494
return true;

shared/editor/queries/isInCode.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ type Options = {
77
onlyBlock?: boolean;
88
/** Only check if the selection is inside a code mark. */
99
onlyMark?: boolean;
10+
/** If true then code must contain entire selection */
11+
inclusive?: boolean;
1012
};
1113

1214
/**
@@ -20,17 +22,29 @@ export function isInCode(state: EditorState, options?: Options): boolean {
2022
const { nodes, marks } = state.schema;
2123

2224
if (!options?.onlyMark) {
23-
if (nodes.code_block && isNodeActive(nodes.code_block)(state)) {
25+
if (
26+
nodes.code_block &&
27+
isNodeActive(nodes.code_block, undefined, {
28+
inclusive: options?.inclusive,
29+
})(state)
30+
) {
2431
return true;
2532
}
26-
if (nodes.code_fence && isNodeActive(nodes.code_fence)(state)) {
33+
if (
34+
nodes.code_fence &&
35+
isNodeActive(nodes.code_fence, undefined, {
36+
inclusive: options?.inclusive,
37+
})(state)
38+
) {
2739
return true;
2840
}
2941
}
3042

3143
if (!options?.onlyBlock) {
3244
if (marks.code_inline) {
33-
return isMarkActive(marks.code_inline)(state);
45+
return isMarkActive(marks.code_inline, undefined, {
46+
inclusive: options?.inclusive,
47+
})(state);
3448
}
3549
}
3650

shared/editor/queries/isMarkActive.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { getMarksBetween } from "./getMarksBetween";
66
type Options = {
77
/** Only return match if the range and attrs is exact */
88
exact?: boolean;
9+
/** If true then mark must contain entire selection */
10+
inclusive?: boolean;
911
};
1012

1113
/**
@@ -40,7 +42,8 @@ export const isMarkActive =
4042
Object.keys(attrs).every(
4143
(key) => mark.attrs[key] === attrs[key]
4244
)) &&
43-
(!options?.exact || (start === from && end === to))
45+
(!options?.exact || (start === from && end === to)) &&
46+
(!options?.inclusive || (start <= from && end >= to))
4447
);
4548
}
4649

shared/editor/queries/isNodeActive.ts

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,55 @@ import { EditorState } from "prosemirror-state";
33
import { Primitive } from "utility-types";
44
import { findParentNode } from "./findParentNode";
55

6+
type Options = {
7+
/** Only return match if the range and attrs is exact */
8+
exact?: boolean;
9+
/** If true then node must contain entire selection */
10+
inclusive?: boolean;
11+
};
12+
613
/**
714
* Checks if a node is active in the current selection or not.
815
*
916
* @param type The node type to check.
1017
* @param attrs The attributes to check.
18+
* @param options The options to use.
1119
* @returns A function that checks if a node is active in the current selection or not.
1220
*/
1321
export const isNodeActive =
14-
(type: NodeType, attrs: Record<string, Primitive> = {}) =>
15-
(state: EditorState) => {
22+
(type: NodeType, attrs?: Record<string, Primitive>, options?: Options) =>
23+
(state: EditorState): boolean => {
1624
if (!type) {
1725
return false;
1826
}
1927

20-
const nodeAfter = state.selection.$from.nodeAfter;
21-
let node = nodeAfter?.type === type ? nodeAfter : undefined;
28+
const { from, to } = state.selection;
29+
const nodeWithPos = findParentNode(
30+
(node) =>
31+
node.type === type &&
32+
(!attrs ||
33+
Object.keys(attrs).every((key) => node.attrs[key] === attrs[key]))
34+
)(state.selection);
35+
36+
if (!nodeWithPos) {
37+
return false;
38+
}
2239

23-
if (!node) {
24-
const parent = findParentNode((n) => n.type === type)(state.selection);
25-
node = parent?.node;
40+
if (options?.inclusive) {
41+
// Check if the node's position contains the entire selection
42+
return (
43+
nodeWithPos.pos <= from &&
44+
nodeWithPos.pos + nodeWithPos.node.nodeSize >= to
45+
);
2646
}
2747

28-
if (!Object.keys(attrs).length || !node) {
29-
return !!node;
48+
if (options?.exact) {
49+
// Check if node's range exactly matches selection
50+
return (
51+
nodeWithPos.pos === from &&
52+
nodeWithPos.pos + nodeWithPos.node.nodeSize === to
53+
);
3054
}
3155

32-
return node.hasMarkup(type, { ...node.attrs, ...attrs });
56+
return true;
3357
};

0 commit comments

Comments
 (0)