Skip to content

Commit 02827a1

Browse files
committed
WIP: Replace zero-width spaces with block marks
1 parent 7d56b60 commit 02827a1

8 files changed

Lines changed: 268 additions & 190 deletions

File tree

.yarn/versions/a42fcf85.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
releases:
2+
"@handlewithcare/prosemirror-suggest-changes": minor

demo/main.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,26 @@ export const schema = new Schema({
3939
nodes: {
4040
...nodes,
4141
image: { ...nodes.image, group: "block", inline: false },
42-
doc: { ...nodes.doc, marks: "insertion deletion modification" },
42+
doc: {
43+
...nodes.doc,
44+
marks: "insertion deletion modification blockBoundarySuggestion",
45+
},
4346
ordered_list: {
4447
...orderedList,
4548
group: "block",
4649
content: "list_item+",
47-
marks: "insertion deletion modification",
50+
marks: "insertion deletion modification blockBoundarySuggestion",
4851
},
4952
bullet_list: {
5053
...bulletList,
5154
group: "block",
5255
content: "list_item+",
53-
marks: "insertion deletion modification",
56+
marks: "insertion deletion modification blockBoundarySuggestion",
5457
},
5558
list_item: {
5659
...listItem,
5760
content: "block+",
58-
marks: "insertion deletion modification",
61+
marks: "insertion deletion modification blockBoundarySuggestion",
5962
},
6063
},
6164
marks: addSuggestionMarks(marks),

src/commands.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,6 @@ function applySuggestionsToTransform(
100100
const insertionTo = insertionFrom + child.nodeSize;
101101
if (child.isInline) {
102102
tr.removeMark(insertionFrom, insertionTo, markTypeToApply);
103-
if (child.text === "\u200B") {
104-
tr.delete(insertionFrom, insertionTo);
105-
}
106103
} else {
107104
tr.removeNodeMark(insertionFrom, markTypeToApply);
108105
}

src/decorations.ts

Lines changed: 23 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import type { Node } from "prosemirror-model";
21
import type { EditorState } from "prosemirror-state";
32
import {
43
Decoration,
54
DecorationSet,
65
type DecorationSource,
76
} from "prosemirror-view";
87
import { getSuggestionMarks } from "./utils.js";
8+
import { type BoundarySuggestion } from "./schema.js";
99

1010
function pilcrow() {
1111
const span = document.createElement("span");
@@ -14,13 +14,12 @@ function pilcrow() {
1414
}
1515

1616
export function getSuggestionDecorations(state: EditorState): DecorationSource {
17-
const { deletion, insertion } = getSuggestionMarks(state.schema);
17+
const { deletion, insertion, blockBoundarySuggestion } = getSuggestionMarks(
18+
state.schema,
19+
);
1820

1921
const changeDecorations: Decoration[] = [];
20-
let lastParentNode: Node | null = null;
21-
let lastTextNode: Node | null = null;
22-
let lastTextNodeEndPos = 0;
23-
state.doc.descendants((node, pos, parent) => {
22+
state.doc.descendants((node, pos) => {
2423
if (node.isTextblock && node.childCount) {
2524
if (node.children.every((child) => deletion.isInSet(child.marks))) {
2625
changeDecorations.push(
@@ -37,65 +36,26 @@ export function getSuggestionDecorations(state: EditorState): DecorationSource {
3736
);
3837
}
3938
}
40-
if (node.type.name !== "text") return true;
41-
const currentDeletionMark = node.marks.find(
42-
(mark) => mark.type === deletion,
43-
);
44-
const currentInsertionMark = node.marks.find(
45-
(mark) => mark.type === insertion,
46-
);
4739

48-
const lastDeletionMark = lastTextNode?.marks.find(
49-
(mark) => mark.type === deletion,
50-
);
51-
const lastInsertionMark = lastTextNode?.marks.find(
52-
(mark) => mark.type === insertion,
53-
);
54-
const widgetPos = lastTextNodeEndPos;
55-
lastTextNode = node;
56-
lastTextNodeEndPos = pos + node.nodeSize;
57-
if (parent === lastParentNode) {
58-
lastParentNode = parent;
59-
return true;
60-
}
61-
lastParentNode = parent;
62-
if (
63-
(!currentDeletionMark || !lastDeletionMark) &&
64-
(!currentInsertionMark || !lastInsertionMark)
65-
) {
66-
return true;
67-
}
68-
if (
69-
currentDeletionMark?.attrs["id"] !== lastDeletionMark?.attrs["id"] &&
70-
currentInsertionMark?.attrs["id"] !== lastInsertionMark?.attrs["id"]
71-
) {
72-
return true;
73-
}
74-
if (currentDeletionMark) {
75-
changeDecorations.push(
76-
Decoration.widget(widgetPos, pilcrow, {
77-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
78-
key: currentDeletionMark.attrs["id"],
79-
marks: [
80-
deletion.create({
81-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
82-
id: currentDeletionMark.attrs["id"],
83-
}),
84-
],
85-
}),
86-
);
87-
}
88-
if (currentInsertionMark) {
40+
const boundarySuggestion = blockBoundarySuggestion.isInSet(node.marks)
41+
?.attrs as BoundarySuggestion | undefined;
42+
43+
if (!boundarySuggestion) return true;
44+
45+
if (boundarySuggestion.endType && boundarySuggestion.endId) {
46+
const markType =
47+
boundarySuggestion.endType === "insertion" ? insertion : deletion;
48+
49+
console.log(boundarySuggestion);
50+
8951
changeDecorations.push(
90-
Decoration.widget(widgetPos, pilcrow, {
91-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
92-
key: currentInsertionMark.attrs["id"],
93-
marks: [
94-
insertion.create({
95-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
96-
id: currentInsertionMark.attrs["id"],
97-
}),
98-
],
52+
Decoration.widget(pos + node.nodeSize - 1, pilcrow, {
53+
key:
54+
typeof boundarySuggestion.endId === "number"
55+
? boundarySuggestion.endId.toString()
56+
: boundarySuggestion.endId,
57+
58+
marks: [markType.create({ id: boundarySuggestion.endId })],
9959
}),
10060
);
10161
}

src/plugin.ts

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
import {
2-
type EditorState,
3-
Plugin,
4-
PluginKey,
5-
TextSelection,
6-
} from "prosemirror-state";
1+
import { type EditorState, Plugin, PluginKey } from "prosemirror-state";
72
import { getSuggestionDecorations } from "./decorations.js";
83

94
export const suggestChangesKey = new PluginKey<{ enabled: boolean }>(
@@ -28,44 +23,6 @@ export function suggestChanges() {
2823
},
2924
props: {
3025
decorations: getSuggestionDecorations,
31-
// Add a custom keydown handler that skips over any zero-width
32-
// spaces that we've inserted so that users aren't aware of them
33-
handleKeyDown(view, event) {
34-
if (
35-
event.key === "ArrowRight" &&
36-
view.state.selection instanceof TextSelection &&
37-
view.state.selection.empty &&
38-
view.state.selection.$cursor?.nodeAfter?.text?.startsWith("\u200B")
39-
) {
40-
view.dispatch(
41-
view.state.tr.setSelection(
42-
TextSelection.create(
43-
view.state.doc,
44-
view.state.selection.$cursor.pos + 1,
45-
),
46-
),
47-
);
48-
}
49-
50-
if (
51-
event.key === "ArrowLeft" &&
52-
view.state.selection instanceof TextSelection &&
53-
view.state.selection.empty &&
54-
view.state.selection.$cursor?.nodeBefore?.text?.endsWith("\u200B")
55-
) {
56-
view.dispatch(
57-
view.state.tr.setSelection(
58-
TextSelection.create(
59-
view.state.doc,
60-
view.state.selection.$cursor.pos - 1,
61-
),
62-
),
63-
);
64-
}
65-
66-
// Never block any other handlers from running after
67-
return false;
68-
},
6926
},
7027
});
7128
}

0 commit comments

Comments
 (0)