@kerebron/extension-codemirror 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +36 -0
- package/esm/ExtensionCodeMirror.d.ts +5 -0
- package/esm/ExtensionCodeMirror.d.ts.map +1 -0
- package/esm/ExtensionCodeMirror.js +4 -0
- package/esm/NodeCodeMirror.d.ts +27 -0
- package/esm/NodeCodeMirror.d.ts.map +1 -0
- package/esm/NodeCodeMirror.js +186 -0
- package/esm/NodeDocumentCode.d.ts +7 -0
- package/esm/NodeDocumentCode.d.ts.map +1 -0
- package/esm/NodeDocumentCode.js +34 -0
- package/esm/codeMirrorBlockNodeView.d.ts +6 -0
- package/esm/codeMirrorBlockNodeView.d.ts.map +1 -0
- package/esm/codeMirrorBlockNodeView.js +265 -0
- package/esm/defaults.d.ts +6 -0
- package/esm/defaults.d.ts.map +1 -0
- package/esm/defaults.js +57 -0
- package/esm/languageLoaders.d.ts +5 -0
- package/esm/languageLoaders.d.ts.map +1 -0
- package/esm/languageLoaders.js +113 -0
- package/esm/languages.d.ts +109 -0
- package/esm/languages.d.ts.map +1 -0
- package/esm/languages.js +110 -0
- package/esm/package.json +3 -0
- package/esm/types.d.ts +29 -0
- package/esm/types.d.ts.map +1 -0
- package/esm/types.js +1 -0
- package/esm/utils.d.ts +39 -0
- package/esm/utils.d.ts.map +1 -0
- package/esm/utils.js +199 -0
- package/esm/y-remote-selections.d.ts +14 -0
- package/esm/y-remote-selections.d.ts.map +1 -0
- package/esm/y-remote-selections.js +270 -0
- package/esm/y-sync.d.ts +11 -0
- package/esm/y-sync.d.ts.map +1 -0
- package/esm/y-sync.js +29 -0
- package/package.json +18 -0
package/esm/utils.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// From prosemirror guide
|
|
2
|
+
import { Selection, TextSelection, } from 'prosemirror-state';
|
|
3
|
+
import { setBlockType } from '@kerebron/editor/commands';
|
|
4
|
+
export const CodeBlockNodeName = 'code_block';
|
|
5
|
+
function nonEmpty(value) {
|
|
6
|
+
return value !== null && value !== undefined;
|
|
7
|
+
}
|
|
8
|
+
export function computeChange(oldVal, newVal) {
|
|
9
|
+
if (oldVal === newVal)
|
|
10
|
+
return null;
|
|
11
|
+
let start = 0;
|
|
12
|
+
let oldEnd = oldVal.length;
|
|
13
|
+
let newEnd = newVal.length;
|
|
14
|
+
while (start < oldEnd &&
|
|
15
|
+
oldVal.charCodeAt(start) === newVal.charCodeAt(start)) {
|
|
16
|
+
start += 1;
|
|
17
|
+
}
|
|
18
|
+
while (oldEnd > start &&
|
|
19
|
+
newEnd > start &&
|
|
20
|
+
oldVal.charCodeAt(oldEnd - 1) === newVal.charCodeAt(newEnd - 1)) {
|
|
21
|
+
oldEnd -= 1;
|
|
22
|
+
newEnd -= 1;
|
|
23
|
+
}
|
|
24
|
+
return { from: start, to: oldEnd, text: newVal.slice(start, newEnd) };
|
|
25
|
+
}
|
|
26
|
+
export const asProseMirrorSelection = (pmDoc, cmView, getPos) => {
|
|
27
|
+
const offset = (typeof getPos === 'function' ? getPos() || 0 : 0) + 1;
|
|
28
|
+
const anchor = cmView.state.selection.main.from + offset;
|
|
29
|
+
const head = cmView.state.selection.main.to + offset;
|
|
30
|
+
return TextSelection.create(pmDoc, anchor, head);
|
|
31
|
+
};
|
|
32
|
+
export const forwardSelection = (cmView, pmView, getPos) => {
|
|
33
|
+
if (!cmView.hasFocus)
|
|
34
|
+
return;
|
|
35
|
+
const selection = asProseMirrorSelection(pmView.state.doc, cmView, getPos);
|
|
36
|
+
if (!selection.eq(pmView.state.selection)) {
|
|
37
|
+
pmView.dispatch(pmView.state.tr.setSelection(selection));
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
export const valueChanged = (textUpdate, node, getPos, view) => {
|
|
41
|
+
const change = computeChange(node.textContent, textUpdate);
|
|
42
|
+
if (change && typeof getPos === 'function') {
|
|
43
|
+
const start = getPos() + 1;
|
|
44
|
+
let pmTr = view.state.tr;
|
|
45
|
+
pmTr = pmTr.replaceWith(start + change.from, start + change.to, change.text ? view.state.schema.text(change.text) : []);
|
|
46
|
+
view.dispatch(pmTr);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
export const maybeEscape = (unit, dir, cm, view, getPos) => {
|
|
50
|
+
const sel = cm.state.selection.main;
|
|
51
|
+
const line = cm.state.doc.lineAt(sel.from);
|
|
52
|
+
const lastLine = cm.state.doc.lines;
|
|
53
|
+
if (sel.to !== sel.from ||
|
|
54
|
+
line.number !== (dir < 0 ? 1 : lastLine) ||
|
|
55
|
+
(unit === 'char' && sel.from !== (dir < 0 ? 0 : line.to)) ||
|
|
56
|
+
typeof getPos !== 'function') {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
view.focus();
|
|
60
|
+
const node = view.state.doc.nodeAt(getPos());
|
|
61
|
+
if (!node)
|
|
62
|
+
return false;
|
|
63
|
+
const targetPos = getPos() + (dir < 0 ? 0 : node.nodeSize);
|
|
64
|
+
const selection = Selection.near(view.state.doc.resolve(targetPos), dir);
|
|
65
|
+
view.dispatch(view.state.tr.setSelection(selection).scrollIntoView());
|
|
66
|
+
view.focus();
|
|
67
|
+
return true;
|
|
68
|
+
};
|
|
69
|
+
export const backspaceHandler = (pmView, view) => {
|
|
70
|
+
const { selection } = view.state;
|
|
71
|
+
if (selection.main.empty && selection.main.from === 0) {
|
|
72
|
+
setBlockType(pmView.state.schema.nodes.paragraph)(pmView.state, pmView.dispatch);
|
|
73
|
+
setTimeout(() => pmView.focus(), 20);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
};
|
|
78
|
+
export const setMode = async (lang, cmView, settings, languageConf) => {
|
|
79
|
+
const support = await settings.languageLoaders?.[lang]?.();
|
|
80
|
+
if (support) {
|
|
81
|
+
cmView.dispatch({
|
|
82
|
+
effects: languageConf.reconfigure(support),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const isTheme = (theme) => {
|
|
87
|
+
if (!Array.isArray(theme)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return theme.every((item) => item !== undefined &&
|
|
91
|
+
typeof item.extension === 'object' && // or whatever type Extension is
|
|
92
|
+
typeof item.name === 'string');
|
|
93
|
+
};
|
|
94
|
+
export const setTheme = async (cmView, themeConfig, theme) => {
|
|
95
|
+
if (isTheme(theme)) {
|
|
96
|
+
cmView.dispatch({
|
|
97
|
+
effects: themeConfig.reconfigure(theme),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const arrowHandler = (dir) => (state, dispatch, view) => {
|
|
102
|
+
if (state.selection.empty && view?.endOfTextblock(dir)) {
|
|
103
|
+
const side = dir === 'left' || dir === 'up' ? -1 : 1;
|
|
104
|
+
const { $head } = state.selection;
|
|
105
|
+
const nextPos = Selection.near(state.doc.resolve(side > 0 ? $head.after() : $head.before()), side);
|
|
106
|
+
if (nextPos.$head &&
|
|
107
|
+
nextPos.$head.parent.type.name === CodeBlockNodeName) {
|
|
108
|
+
dispatch?.(state.tr.setSelection(nextPos));
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
};
|
|
114
|
+
export const createCodeBlock = (state, dispatch, attributes) => {
|
|
115
|
+
const { $from, $to } = state.selection;
|
|
116
|
+
//if we are in a CodeBlock node we do nothing
|
|
117
|
+
const parentNode = $from.node($from.depth);
|
|
118
|
+
if (parentNode && parentNode.type.name === CodeBlockNodeName) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
//if from and to in the same paragraph
|
|
122
|
+
if ($from.parentOffset === $to.parentOffset &&
|
|
123
|
+
$from.parent.type.name === 'paragraph') {
|
|
124
|
+
const text = $from.parent.textContent;
|
|
125
|
+
const tr = state.tr;
|
|
126
|
+
const newNode = state.schema.nodes[CodeBlockNodeName].createAndFill(attributes, text ? [state.schema.text($from.parent.textContent)] : []);
|
|
127
|
+
if (newNode && dispatch) {
|
|
128
|
+
const pos = $from.before($from.depth);
|
|
129
|
+
tr.delete(pos, pos + $from.parent.nodeSize - 1);
|
|
130
|
+
tr.insert(pos, newNode);
|
|
131
|
+
tr.setSelection(TextSelection.create(tr.doc, $from.pos));
|
|
132
|
+
dispatch(tr);
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
if (dispatch) {
|
|
137
|
+
const tr = state.tr;
|
|
138
|
+
const slice = state.doc.slice($from.before($from.depth), $to.after($to.depth), true);
|
|
139
|
+
const content = slice.content.textBetween(0, slice.content.size, '\n');
|
|
140
|
+
const newNode = state.schema.nodes[CodeBlockNodeName].createAndFill(attributes, state.schema.text(content));
|
|
141
|
+
if (newNode) {
|
|
142
|
+
tr.delete($from.before(slice.openStart + 1), $to.after(slice.openEnd + 1));
|
|
143
|
+
tr.insert($from.before(slice.openStart + 1), newNode);
|
|
144
|
+
tr.setSelection(TextSelection.create(tr.doc, $from.pos, $from.pos + newNode.nodeSize - 2));
|
|
145
|
+
dispatch(tr);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
};
|
|
150
|
+
export const removeCodeBlock = (state, dispatch) => {
|
|
151
|
+
const { $from } = state.selection;
|
|
152
|
+
const parentNode = $from.node($from.depth);
|
|
153
|
+
if (parentNode && parentNode.type.name === CodeBlockNodeName) {
|
|
154
|
+
const children = [];
|
|
155
|
+
parentNode.forEach((child) => {
|
|
156
|
+
children.push(child);
|
|
157
|
+
});
|
|
158
|
+
const childrenNodes = children
|
|
159
|
+
.map((child) => {
|
|
160
|
+
return state.schema.nodes.paragraph.createAndFill({}, [child]);
|
|
161
|
+
})
|
|
162
|
+
.filter(nonEmpty);
|
|
163
|
+
const tr = state.tr;
|
|
164
|
+
const pos = $from.before($from.depth);
|
|
165
|
+
tr.delete(pos, pos + parentNode.nodeSize - 1);
|
|
166
|
+
tr.insert(pos, childrenNodes);
|
|
167
|
+
tr.setSelection(TextSelection.create(tr.doc, $from.pos - 1));
|
|
168
|
+
if (dispatch) {
|
|
169
|
+
dispatch(tr.scrollIntoView());
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
};
|
|
174
|
+
export const toggleCodeBlock = (state, dispatch, attributes) => {
|
|
175
|
+
const { $from } = state.selection;
|
|
176
|
+
if ($from.pos === 0) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
const parentNode = $from.node($from.depth);
|
|
180
|
+
if (parentNode && parentNode.type.name === CodeBlockNodeName) {
|
|
181
|
+
return removeCodeBlock(state, dispatch);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
return createCodeBlock(state, dispatch, attributes);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
export const codeBlockArrowHandlers = {
|
|
188
|
+
ArrowLeft: arrowHandler('left'),
|
|
189
|
+
ArrowRight: arrowHandler('right'),
|
|
190
|
+
ArrowUp: arrowHandler('up'),
|
|
191
|
+
ArrowDown: arrowHandler('down'),
|
|
192
|
+
};
|
|
193
|
+
export const codeBlockToggleShortcut = {
|
|
194
|
+
'Mod-Alt-c': toggleCodeBlock,
|
|
195
|
+
};
|
|
196
|
+
export const codeBlockKeymap = {
|
|
197
|
+
...codeBlockToggleShortcut,
|
|
198
|
+
...codeBlockArrowHandlers,
|
|
199
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as cmView from '@codemirror/view';
|
|
2
|
+
import { YSyncConfig } from './y-sync.js';
|
|
3
|
+
export declare const yRemoteSelectionsTheme: any;
|
|
4
|
+
export declare class YRemoteSelectionsPluginValue {
|
|
5
|
+
conf: YSyncConfig;
|
|
6
|
+
private _listener;
|
|
7
|
+
private _awareness;
|
|
8
|
+
decorations: cmView.DecorationSet;
|
|
9
|
+
constructor(view: cmView.EditorView);
|
|
10
|
+
destroy(): void;
|
|
11
|
+
update(update: cmView.ViewUpdate): void;
|
|
12
|
+
}
|
|
13
|
+
export declare const yRemoteSelections: any;
|
|
14
|
+
//# sourceMappingURL=y-remote-selections.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"y-remote-selections.d.ts","sourceRoot":"","sources":["../src/y-remote-selections.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAS3C,OAAO,EAAE,WAAW,EAAc,MAAM,aAAa,CAAC;AAEtD,eAAO,MAAM,sBAAsB,KAuDjC,CAAC;AA8CH,qBAAa,4BAA4B;IACvC,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,CAAC,SAAS,CAQP;IACV,OAAO,CAAC,UAAU,CAA8B;IAChD,WAAW,EAAE,MAAM,CAAC,aAAa,CAAC;gBAEtB,IAAI,EAAE,MAAM,CAAC,UAAU;IAiBnC,OAAO;IAIP,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,UAAU;CA8HjC;AAED,eAAO,MAAM,iBAAiB,KAK7B,CAAC"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import * as cmView from '@codemirror/view';
|
|
2
|
+
import * as cmState from '@codemirror/state';
|
|
3
|
+
import * as dom from 'lib0/dom';
|
|
4
|
+
import * as pair from 'lib0/pair';
|
|
5
|
+
import * as math from 'lib0/math';
|
|
6
|
+
import { ySyncFacet } from './y-sync.js';
|
|
7
|
+
export const yRemoteSelectionsTheme = cmView.EditorView.baseTheme({
|
|
8
|
+
'.cm-ySelection': {},
|
|
9
|
+
'.cm-yLineSelection': {
|
|
10
|
+
padding: 0,
|
|
11
|
+
margin: '0px 2px 0px 4px',
|
|
12
|
+
},
|
|
13
|
+
'.cm-ySelectionCaret': {
|
|
14
|
+
position: 'relative',
|
|
15
|
+
borderLeft: '1px solid black',
|
|
16
|
+
borderRight: '1px solid black',
|
|
17
|
+
marginLeft: '-1px',
|
|
18
|
+
marginRight: '-1px',
|
|
19
|
+
boxSizing: 'border-box',
|
|
20
|
+
display: 'inline',
|
|
21
|
+
},
|
|
22
|
+
'.cm-ySelectionCaretDot': {
|
|
23
|
+
borderRadius: '50%',
|
|
24
|
+
position: 'absolute',
|
|
25
|
+
width: '.4em',
|
|
26
|
+
height: '.4em',
|
|
27
|
+
top: '-.2em',
|
|
28
|
+
left: '-.2em',
|
|
29
|
+
backgroundColor: 'inherit',
|
|
30
|
+
transition: 'transform .3s ease-in-out',
|
|
31
|
+
boxSizing: 'border-box',
|
|
32
|
+
},
|
|
33
|
+
'.cm-ySelectionCaret:hover > .cm-ySelectionCaretDot': {
|
|
34
|
+
transformOrigin: 'bottom center',
|
|
35
|
+
transform: 'scale(0)',
|
|
36
|
+
},
|
|
37
|
+
'.cm-ySelectionInfo': {
|
|
38
|
+
position: 'absolute',
|
|
39
|
+
top: '-1.05em',
|
|
40
|
+
left: '-1px',
|
|
41
|
+
fontSize: '.75em',
|
|
42
|
+
fontFamily: 'serif',
|
|
43
|
+
fontStyle: 'normal',
|
|
44
|
+
fontWeight: 'normal',
|
|
45
|
+
lineHeight: 'normal',
|
|
46
|
+
userSelect: 'none',
|
|
47
|
+
color: 'white',
|
|
48
|
+
paddingLeft: '2px',
|
|
49
|
+
paddingRight: '2px',
|
|
50
|
+
zIndex: 101,
|
|
51
|
+
transition: 'opacity .3s ease-in-out',
|
|
52
|
+
backgroundColor: 'inherit',
|
|
53
|
+
// these should be separate
|
|
54
|
+
opacity: 0,
|
|
55
|
+
transitionDelay: '0s',
|
|
56
|
+
whiteSpace: 'nowrap',
|
|
57
|
+
},
|
|
58
|
+
'.cm-ySelectionCaret:hover > .cm-ySelectionInfo': {
|
|
59
|
+
opacity: 1,
|
|
60
|
+
transitionDelay: '0s',
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
const yRemoteSelectionsAnnotation = cmState.Annotation.define();
|
|
64
|
+
class YRemoteCaretWidget extends cmView.WidgetType {
|
|
65
|
+
constructor(color, name) {
|
|
66
|
+
super();
|
|
67
|
+
Object.defineProperty(this, "color", {
|
|
68
|
+
enumerable: true,
|
|
69
|
+
configurable: true,
|
|
70
|
+
writable: true,
|
|
71
|
+
value: color
|
|
72
|
+
});
|
|
73
|
+
Object.defineProperty(this, "name", {
|
|
74
|
+
enumerable: true,
|
|
75
|
+
configurable: true,
|
|
76
|
+
writable: true,
|
|
77
|
+
value: name
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
toDOM() {
|
|
81
|
+
return (dom.element('span', [
|
|
82
|
+
pair.create('class', 'ProseMirror-yjs-cursor ProseMirror-widget'),
|
|
83
|
+
pair.create('style', `border-color: ${this.color}; position: fixed;`),
|
|
84
|
+
], [
|
|
85
|
+
dom.text('\u2060'),
|
|
86
|
+
dom.element('div', [
|
|
87
|
+
pair.create('style', `background-color: ${this.color}`),
|
|
88
|
+
], [
|
|
89
|
+
dom.text(this.name),
|
|
90
|
+
]),
|
|
91
|
+
dom.text('\u2060'),
|
|
92
|
+
]));
|
|
93
|
+
}
|
|
94
|
+
eq(widget) {
|
|
95
|
+
return widget.color === this.color;
|
|
96
|
+
}
|
|
97
|
+
compare(widget) {
|
|
98
|
+
return widget.color === this.color;
|
|
99
|
+
}
|
|
100
|
+
updateDOM() {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
get estimatedHeight() {
|
|
104
|
+
return -1;
|
|
105
|
+
}
|
|
106
|
+
ignoreEvent() {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export class YRemoteSelectionsPluginValue {
|
|
111
|
+
constructor(view) {
|
|
112
|
+
Object.defineProperty(this, "conf", {
|
|
113
|
+
enumerable: true,
|
|
114
|
+
configurable: true,
|
|
115
|
+
writable: true,
|
|
116
|
+
value: void 0
|
|
117
|
+
});
|
|
118
|
+
Object.defineProperty(this, "_listener", {
|
|
119
|
+
enumerable: true,
|
|
120
|
+
configurable: true,
|
|
121
|
+
writable: true,
|
|
122
|
+
value: void 0
|
|
123
|
+
});
|
|
124
|
+
Object.defineProperty(this, "_awareness", {
|
|
125
|
+
enumerable: true,
|
|
126
|
+
configurable: true,
|
|
127
|
+
writable: true,
|
|
128
|
+
value: void 0
|
|
129
|
+
});
|
|
130
|
+
Object.defineProperty(this, "decorations", {
|
|
131
|
+
enumerable: true,
|
|
132
|
+
configurable: true,
|
|
133
|
+
writable: true,
|
|
134
|
+
value: void 0
|
|
135
|
+
});
|
|
136
|
+
this.conf = view.state.facet(ySyncFacet);
|
|
137
|
+
this._listener = ({ added, updated, removed }, s, t) => {
|
|
138
|
+
const clients = added.concat(updated).concat(removed);
|
|
139
|
+
if (clients.findIndex((id) => id !== this.conf.awareness.doc.clientID) >= 0) {
|
|
140
|
+
view.dispatch({ annotations: [yRemoteSelectionsAnnotation.of([])] });
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
this._awareness = this.conf.awareness;
|
|
144
|
+
this._awareness.on('change', this._listener);
|
|
145
|
+
this.decorations = cmState.RangeSet.of([]);
|
|
146
|
+
}
|
|
147
|
+
destroy() {
|
|
148
|
+
this._awareness.off('change', this._listener);
|
|
149
|
+
}
|
|
150
|
+
update(update) {
|
|
151
|
+
const awareness = this.conf.awareness;
|
|
152
|
+
const decorations = [];
|
|
153
|
+
const localAwarenessState = this.conf.awareness.getLocalState();
|
|
154
|
+
// set local awareness state (update cursors)
|
|
155
|
+
if (localAwarenessState != null) {
|
|
156
|
+
const hasFocus = update.view.hasFocus &&
|
|
157
|
+
update.view.dom.ownerDocument.hasFocus();
|
|
158
|
+
const sel = hasFocus ? update.state.selection.main : null;
|
|
159
|
+
if (sel != null && 'function' === typeof this.conf.getPmPos) {
|
|
160
|
+
const nodePos = this.conf.getPmPos();
|
|
161
|
+
const currentAnchor = localAwarenessState['cm-cursor'] == null
|
|
162
|
+
? -1
|
|
163
|
+
: localAwarenessState['cm-cursor'].anchor - nodePos;
|
|
164
|
+
const currentHead = localAwarenessState['cm-cursor'] == null
|
|
165
|
+
? -1
|
|
166
|
+
: localAwarenessState['cm-cursor'].head - nodePos;
|
|
167
|
+
const anchor = nodePos + sel.anchor;
|
|
168
|
+
const head = nodePos + sel.head;
|
|
169
|
+
if (localAwarenessState['cm-cursor'] == null ||
|
|
170
|
+
(currentAnchor != anchor) || (currentHead != head)) {
|
|
171
|
+
awareness.setLocalStateField('cm-cursor', {
|
|
172
|
+
anchor,
|
|
173
|
+
head,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
else if (localAwarenessState['cm-cursor'] != null && hasFocus) {
|
|
178
|
+
awareness.setLocalStateField('cm-cursor', null);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// update decorations (remote selections)
|
|
182
|
+
awareness.getStates().forEach((state, clientId) => {
|
|
183
|
+
if (clientId === awareness.doc.clientID) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const cursor = state['cm-cursor'];
|
|
187
|
+
if (cursor == null || cursor.anchor == null || cursor.head == null) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if ('function' === typeof this.conf.getPmPos) {
|
|
191
|
+
const nodeAnchor = this.conf.getPmPos();
|
|
192
|
+
const nodeHead = this.conf.getPmPos() + this.conf.getNode().nodeSize;
|
|
193
|
+
if (cursor.anchor >= nodeAnchor && cursor.anchor <= nodeHead) {
|
|
194
|
+
const anchor = { index: cursor.anchor - nodeAnchor };
|
|
195
|
+
const head = { index: cursor.head - nodeAnchor };
|
|
196
|
+
try {
|
|
197
|
+
const { color = '#ffa500', name = `User: ${clientId}` } = state.user || {};
|
|
198
|
+
const colorLight = (state.user && state.user.colorLight) ||
|
|
199
|
+
color + '33';
|
|
200
|
+
const start = math.min(anchor.index, head.index);
|
|
201
|
+
const end = math.max(anchor.index, head.index);
|
|
202
|
+
const startLine = update.view.state.doc.lineAt(start);
|
|
203
|
+
const endLine = update.view.state.doc.lineAt(end);
|
|
204
|
+
if (startLine.number === endLine.number) {
|
|
205
|
+
// selected content in a single line.
|
|
206
|
+
decorations.push({
|
|
207
|
+
from: start,
|
|
208
|
+
to: end,
|
|
209
|
+
value: cmView.Decoration.mark({
|
|
210
|
+
attributes: { style: `background-color: ${colorLight}` },
|
|
211
|
+
class: 'cm-ySelection',
|
|
212
|
+
}),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
// selected content in multiple lines
|
|
217
|
+
// first, render text-selection in the first line
|
|
218
|
+
decorations.push({
|
|
219
|
+
from: start,
|
|
220
|
+
to: startLine.from + startLine.length,
|
|
221
|
+
value: cmView.Decoration.mark({
|
|
222
|
+
attributes: { style: `background-color: ${colorLight}` },
|
|
223
|
+
class: 'cm-ySelection',
|
|
224
|
+
}),
|
|
225
|
+
});
|
|
226
|
+
// render text-selection in the last line
|
|
227
|
+
decorations.push({
|
|
228
|
+
from: endLine.from,
|
|
229
|
+
to: end,
|
|
230
|
+
value: cmView.Decoration.mark({
|
|
231
|
+
attributes: { style: `background-color: ${colorLight}` },
|
|
232
|
+
class: 'cm-ySelection',
|
|
233
|
+
}),
|
|
234
|
+
});
|
|
235
|
+
for (let i = startLine.number + 1; i < endLine.number; i++) {
|
|
236
|
+
const linePos = update.view.state.doc.line(i).from;
|
|
237
|
+
decorations.push({
|
|
238
|
+
from: linePos,
|
|
239
|
+
to: linePos,
|
|
240
|
+
value: cmView.Decoration.line({
|
|
241
|
+
attributes: {
|
|
242
|
+
style: `background-color: ${colorLight}`,
|
|
243
|
+
class: 'cm-yLineSelection',
|
|
244
|
+
},
|
|
245
|
+
}),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
decorations.push({
|
|
250
|
+
from: head.index,
|
|
251
|
+
to: head.index,
|
|
252
|
+
value: cmView.Decoration.widget({
|
|
253
|
+
side: head.index - anchor.index > 0 ? -1 : 1, // the local cursor should be rendered outside the remote selection
|
|
254
|
+
block: false,
|
|
255
|
+
widget: new YRemoteCaretWidget(color, name),
|
|
256
|
+
}),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
console.warn(err, `User: ${clientId}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
this.decorations = cmView.Decoration.set(decorations, true);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
export const yRemoteSelections = cmView.ViewPlugin.fromClass(YRemoteSelectionsPluginValue, {
|
|
269
|
+
decorations: (v) => v.decorations,
|
|
270
|
+
});
|
package/esm/y-sync.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as cmState from '@codemirror/state';
|
|
2
|
+
import { Node } from 'prosemirror-model';
|
|
3
|
+
import * as awarenessProtocol from 'y-protocols/awareness';
|
|
4
|
+
export declare class YSyncConfig {
|
|
5
|
+
getNode: () => Node;
|
|
6
|
+
getPmPos: boolean | (() => number);
|
|
7
|
+
awareness: awarenessProtocol.Awareness;
|
|
8
|
+
constructor(getNode: () => Node, getPmPos: boolean | (() => number), awareness: awarenessProtocol.Awareness);
|
|
9
|
+
}
|
|
10
|
+
export declare const ySyncFacet: cmState.Facet<YSyncConfig, YSyncConfig>;
|
|
11
|
+
//# sourceMappingURL=y-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"y-sync.d.ts","sourceRoot":"","sources":["../src/y-sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,mBAAmB,CAAC;AAE7C,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,KAAK,iBAAiB,MAAM,uBAAuB,CAAC;AAE3D,qBAAa,WAAW;IAEb,OAAO,EAAE,MAAM,IAAI;IACnB,QAAQ,EAAE,OAAO,GAAG,CAAC,MAAM,MAAM,CAAC;IAClC,SAAS,EAAE,iBAAiB,CAAC,SAAS;gBAFtC,OAAO,EAAE,MAAM,IAAI,EACnB,QAAQ,EAAE,OAAO,GAAG,CAAC,MAAM,MAAM,CAAC,EAClC,SAAS,EAAE,iBAAiB,CAAC,SAAS;CAGhD;AAED,eAAO,MAAM,UAAU,EAAE,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,WAAW,CAK3D,CAAC"}
|
package/esm/y-sync.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as cmState from '@codemirror/state'; // eslint-disable-line
|
|
2
|
+
export class YSyncConfig {
|
|
3
|
+
constructor(getNode, getPmPos, awareness) {
|
|
4
|
+
Object.defineProperty(this, "getNode", {
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true,
|
|
8
|
+
value: getNode
|
|
9
|
+
});
|
|
10
|
+
Object.defineProperty(this, "getPmPos", {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
writable: true,
|
|
14
|
+
value: getPmPos
|
|
15
|
+
});
|
|
16
|
+
Object.defineProperty(this, "awareness", {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
configurable: true,
|
|
19
|
+
writable: true,
|
|
20
|
+
value: awareness
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export const ySyncFacet = cmState.Facet
|
|
25
|
+
.define({
|
|
26
|
+
combine(inputs) {
|
|
27
|
+
return inputs[inputs.length - 1];
|
|
28
|
+
},
|
|
29
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kerebron/extension-codemirror",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"module": "./esm/ExtensionCodeMirror.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": "./esm/ExtensionCodeMirror.js"
|
|
9
|
+
},
|
|
10
|
+
"./NodeDocumentCode": {
|
|
11
|
+
"import": "./esm/NodeDocumentCode.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/node": "^20.9.0"
|
|
16
|
+
},
|
|
17
|
+
"_generatedBy": "dnt@dev"
|
|
18
|
+
}
|