@dxos/react-ui-editor 0.3.11-main.346d280 → 0.3.11-main.3a8a0ef
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/dist/lib/browser/index.mjs +111 -65
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/TextEditor/comments.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/comments.d.ts +4 -0
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/tasklist.d.ts +5 -1
- package/dist/types/src/extensions/tasklist.d.ts.map +1 -1
- package/dist/types/src/themes/default.d.ts.map +1 -1
- package/package.json +25 -25
- package/src/components/TextEditor/comments.stories.tsx +15 -1
- package/src/extensions/comments.ts +66 -1
- package/src/extensions/tasklist.ts +51 -75
- package/src/extensions/yjs/yjs.test.ts +1 -1
- package/src/themes/default.ts +0 -8
|
@@ -2,57 +2,39 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
WidgetType,
|
|
9
|
-
MatchDecorator,
|
|
10
|
-
ViewPlugin,
|
|
11
|
-
type DecorationSet,
|
|
12
|
-
type ViewUpdate,
|
|
13
|
-
} from '@codemirror/view';
|
|
14
|
-
|
|
15
|
-
// TODO(burdon): Reconcile with theme.
|
|
16
|
-
const styles = EditorView.baseTheme({
|
|
17
|
-
'& .cm-task-item': {
|
|
18
|
-
paddingLeft: '4px',
|
|
19
|
-
paddingRight: '8px',
|
|
20
|
-
},
|
|
21
|
-
});
|
|
5
|
+
import { syntaxTree } from '@codemirror/language';
|
|
6
|
+
import { RangeSetBuilder } from '@codemirror/state';
|
|
7
|
+
import { EditorView, Decoration, WidgetType, ViewPlugin, type DecorationSet, type ViewUpdate } from '@codemirror/view';
|
|
22
8
|
|
|
23
9
|
class CheckboxWidget extends WidgetType {
|
|
24
|
-
constructor(
|
|
25
|
-
private _checked: boolean,
|
|
26
|
-
private readonly _indent: number,
|
|
27
|
-
private readonly _onCheck?: (check: boolean) => void,
|
|
28
|
-
) {
|
|
10
|
+
constructor(private _checked: boolean) {
|
|
29
11
|
super();
|
|
30
12
|
}
|
|
31
13
|
|
|
32
14
|
override eq(other: this) {
|
|
33
|
-
return this._checked === other._checked
|
|
15
|
+
return this._checked === other._checked;
|
|
34
16
|
}
|
|
35
17
|
|
|
36
18
|
override toDOM(view: EditorView) {
|
|
37
|
-
const
|
|
38
|
-
wrap.className = 'cm-task-item';
|
|
39
|
-
wrap.setAttribute('aria-hidden', 'true');
|
|
40
|
-
wrap.style.setProperty('margin-left', this._indent * 24 + 'px');
|
|
41
|
-
|
|
42
|
-
const input = wrap.appendChild(document.createElement('input'));
|
|
19
|
+
const input = document.createElement('input');
|
|
43
20
|
input.type = 'checkbox';
|
|
44
21
|
input.checked = this._checked;
|
|
45
|
-
if (
|
|
22
|
+
if (view.state.readOnly) {
|
|
46
23
|
input.setAttribute('disabled', 'true');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
24
|
+
} else {
|
|
25
|
+
input.onmousedown = (event: Event) => {
|
|
26
|
+
const pos = view.posAtDOM(input);
|
|
27
|
+
const text = view.state.sliceDoc(pos, pos + 3);
|
|
28
|
+
if (text === (this._checked ? '[x]' : '[ ]')) {
|
|
29
|
+
view.dispatch({
|
|
30
|
+
changes: { from: pos + 1, to: pos + 2, insert: this._checked ? ' ' : 'x' },
|
|
31
|
+
});
|
|
32
|
+
event.preventDefault();
|
|
33
|
+
}
|
|
52
34
|
};
|
|
53
35
|
}
|
|
54
36
|
|
|
55
|
-
return
|
|
37
|
+
return input;
|
|
56
38
|
}
|
|
57
39
|
|
|
58
40
|
override ignoreEvent() {
|
|
@@ -60,60 +42,54 @@ class CheckboxWidget extends WidgetType {
|
|
|
60
42
|
}
|
|
61
43
|
}
|
|
62
44
|
|
|
45
|
+
const checkedDeco = Decoration.replace({ widget: new CheckboxWidget(true) });
|
|
46
|
+
const uncheckedDeco = Decoration.replace({ widget: new CheckboxWidget(false) });
|
|
47
|
+
|
|
63
48
|
export type TasklistOptions = {};
|
|
64
49
|
|
|
65
50
|
export const tasklist = (options: TasklistOptions = {}) => {
|
|
66
|
-
|
|
67
|
-
// Allows for indents to be greater than AST would allow.
|
|
68
|
-
const taskMatcher = new MatchDecorator({
|
|
69
|
-
regexp: /^(\s*)- \[([ xX])\]\s/g,
|
|
70
|
-
decoration: (match, view, pos) => {
|
|
71
|
-
const indent = Math.floor(match[1].length / 2);
|
|
72
|
-
const checked = match[2] === 'x' || match[2] === 'X';
|
|
73
|
-
return Decoration.replace({
|
|
74
|
-
widget: new CheckboxWidget(
|
|
75
|
-
checked,
|
|
76
|
-
indent,
|
|
77
|
-
view.state.readOnly
|
|
78
|
-
? undefined
|
|
79
|
-
: (checked) => {
|
|
80
|
-
const idx = pos + match[0].indexOf('[') + 1;
|
|
81
|
-
view.dispatch({
|
|
82
|
-
changes: {
|
|
83
|
-
from: idx,
|
|
84
|
-
to: idx + 1,
|
|
85
|
-
insert: checked ? 'x' : ' ',
|
|
86
|
-
},
|
|
87
|
-
// TODO(burdon): Restore cursor position? More useful to move to end of line (can indent).
|
|
88
|
-
selection: {
|
|
89
|
-
anchor: idx + 3,
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
},
|
|
93
|
-
),
|
|
94
|
-
});
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const tasks = ViewPlugin.fromClass(
|
|
51
|
+
return ViewPlugin.fromClass(
|
|
99
52
|
class {
|
|
100
|
-
|
|
53
|
+
decorations: DecorationSet;
|
|
54
|
+
|
|
101
55
|
constructor(view: EditorView) {
|
|
102
|
-
this.
|
|
56
|
+
this.decorations = buildDecorations(view);
|
|
103
57
|
}
|
|
104
58
|
|
|
105
59
|
update(update: ViewUpdate) {
|
|
106
|
-
|
|
60
|
+
if (update.docChanged || update.viewportChanged || update.selectionSet) {
|
|
61
|
+
this.decorations = buildDecorations(update.view);
|
|
62
|
+
}
|
|
107
63
|
}
|
|
108
64
|
},
|
|
109
65
|
{
|
|
110
|
-
decorations: (
|
|
66
|
+
decorations: (v) => v.decorations,
|
|
111
67
|
provide: (plugin) =>
|
|
112
68
|
EditorView.atomicRanges.of((view) => {
|
|
113
|
-
return view.plugin(plugin)?.
|
|
69
|
+
return view.plugin(plugin)?.decorations || Decoration.none;
|
|
114
70
|
}),
|
|
115
71
|
},
|
|
116
72
|
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const buildDecorations = (view: EditorView): DecorationSet => {
|
|
76
|
+
const builder = new RangeSetBuilder<Decoration>();
|
|
77
|
+
const { state } = view;
|
|
78
|
+
const cursor = state.selection.main.head;
|
|
79
|
+
|
|
80
|
+
for (const { from, to } of view.visibleRanges) {
|
|
81
|
+
syntaxTree(state).iterate({
|
|
82
|
+
enter: (node) => {
|
|
83
|
+
// Check if cursor is inside text.
|
|
84
|
+
if (node.name === 'TaskMarker' && (cursor < node.from || cursor > node.to)) {
|
|
85
|
+
const checked = state.doc.sliceString(node.from + 1, node.to - 1) === 'x';
|
|
86
|
+
builder.add(node.from, node.to, checked ? checkedDeco : uncheckedDeco);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
from,
|
|
90
|
+
to,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
117
93
|
|
|
118
|
-
return
|
|
94
|
+
return builder.finish();
|
|
119
95
|
};
|
|
@@ -12,7 +12,7 @@ import { type YText } from '@dxos/text-model';
|
|
|
12
12
|
describe('YJS', () => {
|
|
13
13
|
// https://docs.yjs.dev/api/shared-types/y.text
|
|
14
14
|
// https://github.com/yjs/yjs?tab=readme-ov-file#relative-positions
|
|
15
|
-
test('absolute position', () => {
|
|
15
|
+
test.skip('absolute position', () => {
|
|
16
16
|
const obj = new TextObject('hello world');
|
|
17
17
|
const index = obj.text.indexOf('world');
|
|
18
18
|
const relPos = Y.encodeRelativePosition(Y.createRelativePositionFromTypeIndex(obj.content as YText, index));
|
package/src/themes/default.ts
CHANGED