-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsetMark.ts
More file actions
110 lines (100 loc) · 3 KB
/
setMark.ts
File metadata and controls
110 lines (100 loc) · 3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import { CommentMark } from "@blocknote/core/comments";
import { ServerBlockNoteEditor } from "@blocknote/server-util";
import { Document } from "@hocuspocus/server";
import { EditorState, TextSelection } from "prosemirror-state";
import {
initProseMirrorDoc,
relativePositionToAbsolutePosition,
updateYFragment,
} from "y-prosemirror";
import * as Y from "yjs";
/**
* Sets a mark in the yjs document based on a yjs selection
*/
export function setMark(
doc: Document,
fragment: Y.XmlFragment,
yjsSelection: {
anchor: any;
head: any;
},
markName: string,
markAttributes: any
) {
// needed to get the pmSchema
// if you use a BlockNote custom schema, make sure to pass it to the create options
const editor = ServerBlockNoteEditor.create({
_extensions: { comment: CommentMark },
});
// get the prosemirror document
const { doc: pNode, meta } = initProseMirrorDoc(
fragment,
editor.editor.pmSchema as any
);
// get the prosemirror positions based on the yjs positions
// we need to get this from yjs because other users might have made changes in between
const anchor = relativePositionToAbsolutePosition(
doc,
fragment,
yjsSelection.anchor,
meta.mapping
);
const head = relativePositionToAbsolutePosition(
doc,
fragment,
yjsSelection.head,
meta.mapping
);
// now, let's create the mark in the prosemirror document
const state = EditorState.create({
doc: pNode,
schema: editor.editor.pmSchema as any,
selection: TextSelection.create(pNode, anchor!, head!),
});
const tr = setMarkInProsemirror(
editor.editor.pmSchema.marks[markName],
markAttributes,
state
);
// finally, update the yjs document with the new prosemirror document
updateYFragment(doc, fragment, tr.doc, meta);
}
// based on https://github.com/ueberdosis/tiptap/blob/f3258d9ee5fb7979102fe63434f6ea4120507311/packages/core/src/commands/setMark.ts#L66
export const setMarkInProsemirror = (
type: any,
attributes = {},
state: EditorState
) => {
let tr = state.tr;
const { selection } = state;
const { ranges } = selection;
ranges.forEach((range) => {
const from = range.$from.pos;
const to = range.$to.pos;
state.doc.nodesBetween(from, to, (node, pos) => {
const trimmedFrom = Math.max(pos, from);
const trimmedTo = Math.min(pos + node.nodeSize, to);
const someHasMark = node.marks.find((mark) => mark.type === type);
// if there is already a mark of this type
// we know that we have to merge its attributes
// otherwise we add a fresh new mark
if (someHasMark) {
node.marks.forEach((mark) => {
if (type === mark.type) {
tr = tr.addMark(
trimmedFrom,
trimmedTo,
type.create({
...mark.attrs,
...attributes,
})
);
}
});
} else {
tr = tr.addMark(trimmedFrom, trimmedTo, type.create(attributes));
}
});
});
return tr;
};