@dxos/react-ui-editor 0.8.2-main.fbd8ed0 → 0.8.2-staging.4d6ad0f
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 +1731 -926
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +3 -64
- package/dist/lib/browser/testing/index.mjs.map +4 -4
- package/dist/lib/node/index.cjs +1912 -1111
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +3 -75
- package/dist/lib/node/testing/index.cjs.map +4 -4
- package/dist/lib/node-esm/index.mjs +1731 -926
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +3 -64
- package/dist/lib/node-esm/testing/index.mjs.map +4 -4
- package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/index.d.ts +1 -1
- package/dist/types/src/components/EditorToolbar/index.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/util.d.ts +4 -6
- package/dist/types/src/components/EditorToolbar/util.d.ts.map +1 -1
- package/dist/types/src/components/Popover/RefDropdownMenu.d.ts +21 -0
- package/dist/types/src/components/Popover/RefDropdownMenu.d.ts.map +1 -0
- package/dist/types/src/{testing → components/Popover}/RefPopover.d.ts +1 -1
- package/dist/types/src/components/Popover/RefPopover.d.ts.map +1 -0
- package/dist/types/src/components/Popover/index.d.ts +3 -0
- package/dist/types/src/components/Popover/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/defaults.d.ts +2 -5
- package/dist/types/src/defaults.d.ts.map +1 -1
- package/dist/types/src/extensions/annotations.d.ts +4 -1
- package/dist/types/src/extensions/annotations.d.ts.map +1 -1
- package/dist/types/src/extensions/autocomplete.d.ts +1 -2
- package/dist/types/src/extensions/autocomplete.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/sync.d.ts.map +1 -1
- package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +1 -1
- package/dist/types/src/extensions/awareness/awareness.d.ts.map +1 -1
- package/dist/types/src/extensions/command/command.d.ts +1 -2
- package/dist/types/src/extensions/command/command.d.ts.map +1 -1
- package/dist/types/src/extensions/command/hint.d.ts +14 -2
- package/dist/types/src/extensions/command/hint.d.ts.map +1 -1
- package/dist/types/src/extensions/command/index.d.ts +2 -0
- package/dist/types/src/extensions/command/index.d.ts.map +1 -1
- package/dist/types/src/extensions/command/menu.d.ts +7 -8
- package/dist/types/src/extensions/command/menu.d.ts.map +1 -1
- package/dist/types/src/extensions/command/state.d.ts +1 -1
- package/dist/types/src/extensions/command/state.d.ts.map +1 -1
- package/dist/types/src/extensions/command/typeahead.d.ts +17 -0
- package/dist/types/src/extensions/command/typeahead.d.ts.map +1 -0
- package/dist/types/src/extensions/comments.d.ts +2 -12
- package/dist/types/src/extensions/comments.d.ts.map +1 -1
- package/dist/types/src/extensions/factories.d.ts +4 -0
- package/dist/types/src/extensions/factories.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +2 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -1
- package/dist/types/src/extensions/json.d.ts +7 -0
- package/dist/types/src/extensions/json.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/{editorAction.d.ts → action.d.ts} +1 -1
- package/dist/types/src/extensions/markdown/action.d.ts.map +1 -0
- package/dist/types/src/extensions/markdown/bundle.d.ts +2 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/index.d.ts +1 -2
- package/dist/types/src/extensions/markdown/index.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/styles.d.ts.map +1 -1
- package/dist/types/src/extensions/outliner/commands.d.ts +9 -0
- package/dist/types/src/extensions/outliner/commands.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/editor.d.ts +5 -0
- package/dist/types/src/extensions/outliner/editor.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/editor.test.d.ts +2 -0
- package/dist/types/src/extensions/outliner/editor.test.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/index.d.ts +3 -0
- package/dist/types/src/extensions/outliner/index.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/outliner.d.ts +10 -0
- package/dist/types/src/extensions/outliner/outliner.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/outliner.test.d.ts +2 -0
- package/dist/types/src/extensions/outliner/outliner.test.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/selection.d.ts +12 -0
- package/dist/types/src/extensions/outliner/selection.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/tree.d.ts +79 -0
- package/dist/types/src/extensions/outliner/tree.d.ts.map +1 -0
- package/dist/types/src/extensions/outliner/tree.test.d.ts +2 -0
- package/dist/types/src/extensions/outliner/tree.test.d.ts.map +1 -0
- package/dist/types/src/stories/Command.stories.d.ts +7 -0
- package/dist/types/src/stories/Command.stories.d.ts.map +1 -0
- package/dist/types/src/stories/{TextEditorComments.stories.d.ts → Comments.stories.d.ts} +3 -3
- package/dist/types/src/stories/Comments.stories.d.ts.map +1 -0
- package/dist/types/src/stories/EditorToolbar.stories.d.ts +12 -0
- package/dist/types/src/stories/EditorToolbar.stories.d.ts.map +1 -0
- package/dist/types/src/stories/{TextEditorSpecial.stories.d.ts → Experimental.stories.d.ts} +3 -6
- package/dist/types/src/stories/Experimental.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Markdown.stories.d.ts +46 -0
- package/dist/types/src/stories/Markdown.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Outliner.stories.d.ts +26 -0
- package/dist/types/src/stories/Outliner.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Preview.stories.d.ts +10 -0
- package/dist/types/src/stories/Preview.stories.d.ts.map +1 -0
- package/dist/types/src/stories/{TextEditorBasic.stories.d.ts → TextEditor.stories.d.ts} +9 -39
- package/dist/types/src/stories/TextEditor.stories.d.ts.map +1 -0
- package/dist/types/src/stories/{story-utils.d.ts → util.d.ts} +6 -6
- package/dist/types/src/stories/util.d.ts.map +1 -0
- package/dist/types/src/styles/theme.d.ts.map +1 -1
- package/dist/types/src/styles/tokens.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +1 -1
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/util.d.ts +2 -0
- package/dist/types/src/testing/util.d.ts.map +1 -0
- package/package.json +40 -34
- package/src/components/EditorToolbar/EditorToolbar.tsx +81 -57
- package/src/components/EditorToolbar/index.ts +7 -1
- package/src/components/EditorToolbar/util.ts +3 -4
- package/src/components/Popover/RefDropdownMenu.tsx +77 -0
- package/src/{testing → components/Popover}/RefPopover.tsx +5 -4
- package/src/components/Popover/index.ts +6 -0
- package/src/components/index.ts +1 -0
- package/src/defaults.ts +10 -13
- package/src/extensions/annotations.ts +41 -64
- package/src/extensions/autocomplete.ts +5 -6
- package/src/extensions/automerge/automerge.stories.tsx +2 -7
- package/src/extensions/automerge/automerge.test.tsx +3 -2
- package/src/extensions/automerge/sync.ts +3 -3
- package/src/extensions/awareness/awareness-provider.ts +4 -4
- package/src/extensions/awareness/awareness.ts +7 -7
- package/src/extensions/blast.ts +9 -9
- package/src/extensions/command/command.ts +1 -3
- package/src/extensions/command/hint.ts +7 -7
- package/src/extensions/command/index.ts +2 -0
- package/src/extensions/command/menu.ts +43 -49
- package/src/extensions/command/typeahead.ts +116 -0
- package/src/extensions/comments.ts +4 -69
- package/src/extensions/factories.ts +13 -0
- package/src/extensions/index.ts +2 -0
- package/src/extensions/json.ts +56 -0
- package/src/extensions/markdown/bundle.ts +13 -9
- package/src/extensions/markdown/decorate.ts +7 -7
- package/src/extensions/markdown/image.ts +2 -2
- package/src/extensions/markdown/index.ts +1 -2
- package/src/extensions/markdown/styles.ts +2 -1
- package/src/extensions/markdown/table.ts +3 -3
- package/src/extensions/outliner/commands.ts +242 -0
- package/src/extensions/outliner/editor.test.ts +33 -0
- package/src/extensions/outliner/editor.ts +180 -0
- package/src/extensions/outliner/index.ts +6 -0
- package/src/extensions/outliner/outliner.test.ts +99 -0
- package/src/extensions/outliner/outliner.ts +162 -0
- package/src/extensions/outliner/selection.ts +50 -0
- package/src/extensions/outliner/tree.test.ts +164 -0
- package/src/extensions/outliner/tree.ts +315 -0
- package/src/extensions/preview/preview.ts +5 -5
- package/src/stories/Command.stories.tsx +97 -0
- package/src/stories/{TextEditorComments.stories.tsx → Comments.stories.tsx} +13 -14
- package/src/{components/EditorToolbar → stories}/EditorToolbar.stories.tsx +26 -20
- package/src/stories/{TextEditorSpecial.stories.tsx → Experimental.stories.tsx} +9 -30
- package/src/stories/Markdown.stories.tsx +121 -0
- package/src/stories/Outliner.stories.tsx +108 -0
- package/src/stories/{TextEditorPreview.stories.tsx → Preview.stories.tsx} +46 -136
- package/src/stories/TextEditor.stories.tsx +256 -0
- package/src/stories/{story-utils.tsx → util.tsx} +21 -22
- package/src/styles/theme.ts +12 -5
- package/src/styles/tokens.ts +1 -2
- package/src/testing/index.ts +1 -1
- package/src/testing/util.ts +5 -0
- package/dist/types/src/components/EditorToolbar/EditorToolbar.stories.d.ts +0 -53
- package/dist/types/src/components/EditorToolbar/EditorToolbar.stories.d.ts.map +0 -1
- package/dist/types/src/components/EditorToolbar/comment.d.ts +0 -18
- package/dist/types/src/components/EditorToolbar/comment.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/editorAction.d.ts.map +0 -1
- package/dist/types/src/extensions/markdown/outliner.d.ts +0 -12
- package/dist/types/src/extensions/markdown/outliner.d.ts.map +0 -1
- package/dist/types/src/stories/TextEditorBasic.stories.d.ts.map +0 -1
- package/dist/types/src/stories/TextEditorComments.stories.d.ts.map +0 -1
- package/dist/types/src/stories/TextEditorPreview.stories.d.ts +0 -13
- package/dist/types/src/stories/TextEditorPreview.stories.d.ts.map +0 -1
- package/dist/types/src/stories/TextEditorSpecial.stories.d.ts.map +0 -1
- package/dist/types/src/stories/story-utils.d.ts.map +0 -1
- package/dist/types/src/testing/RefPopover.d.ts.map +0 -1
- package/src/components/EditorToolbar/comment.ts +0 -30
- package/src/extensions/markdown/outliner.ts +0 -235
- package/src/stories/TextEditorBasic.stories.tsx +0 -333
- /package/src/extensions/markdown/{editorAction.ts → action.ts} +0 -0
@@ -95,17 +95,17 @@ export class RemoteSelectionsDecorator implements PluginValue {
|
|
95
95
|
});
|
96
96
|
}
|
97
97
|
|
98
|
-
destroy() {
|
98
|
+
destroy(): void {
|
99
99
|
void this._ctx.dispose();
|
100
100
|
this._provider.close();
|
101
101
|
}
|
102
102
|
|
103
|
-
update(update: ViewUpdate) {
|
103
|
+
update(update: ViewUpdate): void {
|
104
104
|
this._updateLocalSelection(update.view);
|
105
105
|
this._updateRemoteSelections(update.view);
|
106
106
|
}
|
107
107
|
|
108
|
-
private _updateLocalSelection(view: EditorView) {
|
108
|
+
private _updateLocalSelection(view: EditorView): void {
|
109
109
|
const hasFocus = view.hasFocus && view.dom.ownerDocument.hasFocus();
|
110
110
|
const { anchor = undefined, head = undefined } = hasFocus ? view.state.selection.main : {};
|
111
111
|
if (this._lastAnchor === anchor && this._lastHead === head) {
|
@@ -125,7 +125,7 @@ export class RemoteSelectionsDecorator implements PluginValue {
|
|
125
125
|
);
|
126
126
|
}
|
127
127
|
|
128
|
-
private _updateRemoteSelections(view: EditorView) {
|
128
|
+
private _updateRemoteSelections(view: EditorView): void {
|
129
129
|
const decorations: Range<Decoration>[] = [
|
130
130
|
// TODO(burdon): Factor out for testing.
|
131
131
|
// {
|
@@ -239,11 +239,11 @@ class RemoteCaretWidget extends WidgetType {
|
|
239
239
|
return span;
|
240
240
|
}
|
241
241
|
|
242
|
-
override updateDOM() {
|
242
|
+
override updateDOM(): boolean {
|
243
243
|
return false;
|
244
244
|
}
|
245
245
|
|
246
|
-
override eq(widget: this) {
|
246
|
+
override eq(widget: this): boolean {
|
247
247
|
return widget._color === this._color;
|
248
248
|
}
|
249
249
|
|
@@ -251,7 +251,7 @@ class RemoteCaretWidget extends WidgetType {
|
|
251
251
|
return -1;
|
252
252
|
}
|
253
253
|
|
254
|
-
override ignoreEvent() {
|
254
|
+
override ignoreEvent(): boolean {
|
255
255
|
return true;
|
256
256
|
}
|
257
257
|
}
|
package/src/extensions/blast.ts
CHANGED
@@ -136,7 +136,7 @@ class Blaster {
|
|
136
136
|
return this._node;
|
137
137
|
}
|
138
138
|
|
139
|
-
initialize() {
|
139
|
+
initialize(): void {
|
140
140
|
// console.log('initialize');
|
141
141
|
invariant(!this._canvas && !this._ctx);
|
142
142
|
|
@@ -155,7 +155,7 @@ class Blaster {
|
|
155
155
|
this.resize();
|
156
156
|
}
|
157
157
|
|
158
|
-
destroy() {
|
158
|
+
destroy(): void {
|
159
159
|
this.stop();
|
160
160
|
// console.log('destroy');
|
161
161
|
if (this._canvas) {
|
@@ -165,7 +165,7 @@ class Blaster {
|
|
165
165
|
}
|
166
166
|
}
|
167
167
|
|
168
|
-
resize() {
|
168
|
+
resize(): void {
|
169
169
|
if (this._node.parentElement && this._canvas) {
|
170
170
|
const { offsetLeft: x, offsetTop: y, offsetWidth: width, offsetHeight: height } = this._node.parentElement;
|
171
171
|
this._canvas.style.top = `${y}px`;
|
@@ -175,20 +175,20 @@ class Blaster {
|
|
175
175
|
}
|
176
176
|
}
|
177
177
|
|
178
|
-
start() {
|
178
|
+
start(): void {
|
179
179
|
// console.log('start');
|
180
180
|
invariant(this._canvas && this._ctx);
|
181
181
|
this._running = true;
|
182
182
|
this.loop();
|
183
183
|
}
|
184
184
|
|
185
|
-
stop() {
|
185
|
+
stop(): void {
|
186
186
|
// console.log('stop');
|
187
187
|
this._running = false;
|
188
188
|
this._node.style.transform = 'translate(0px, 0px)';
|
189
189
|
}
|
190
190
|
|
191
|
-
loop() {
|
191
|
+
loop(): void {
|
192
192
|
if (!this._running || !this._canvas || !this._ctx) {
|
193
193
|
return;
|
194
194
|
}
|
@@ -230,7 +230,7 @@ class Blaster {
|
|
230
230
|
}
|
231
231
|
}, 100);
|
232
232
|
|
233
|
-
drawParticles() {
|
233
|
+
drawParticles(): void {
|
234
234
|
for (let i = this._particles.length; i--; i > 0) {
|
235
235
|
const particle = this._particles[i];
|
236
236
|
if (!particle) {
|
@@ -282,7 +282,7 @@ class Effect1 extends Effect {
|
|
282
282
|
};
|
283
283
|
}
|
284
284
|
|
285
|
-
update(ctx: CanvasRenderingContext2D, particle: Particle) {
|
285
|
+
update(ctx: CanvasRenderingContext2D, particle: Particle): void {
|
286
286
|
particle.vy += this._options.particleGravity;
|
287
287
|
particle.x += particle.vx;
|
288
288
|
particle.y += particle.vy;
|
@@ -313,7 +313,7 @@ class Effect2 extends Effect {
|
|
313
313
|
};
|
314
314
|
}
|
315
315
|
|
316
|
-
update(ctx: CanvasRenderingContext2D, particle: Particle) {
|
316
|
+
update(ctx: CanvasRenderingContext2D, particle: Particle): void {
|
317
317
|
particle.vy += this._options.particleGravity;
|
318
318
|
particle.x += particle.vx;
|
319
319
|
particle.y += particle.vy;
|
@@ -7,7 +7,6 @@ import { EditorView, keymap } from '@codemirror/view';
|
|
7
7
|
|
8
8
|
import { closeEffect, commandKeyBindings } from './action';
|
9
9
|
import { hintViewPlugin, type HintOptions } from './hint';
|
10
|
-
import { floatingMenu, type FloatingMenuOptions } from './menu';
|
11
10
|
import { commandConfig, commandState, type PopupOptions } from './state';
|
12
11
|
|
13
12
|
// TODO(burdon): Create knowledge base for CM notes and ideas.
|
@@ -15,14 +14,13 @@ import { commandConfig, commandState, type PopupOptions } from './state';
|
|
15
14
|
// https://github.com/saminzadeh/codemirror-extension-inline-suggestion
|
16
15
|
// https://github.com/ChromeDevTools/devtools-frontend/blob/main/front_end/ui/components/text_editor/config.ts#L370
|
17
16
|
|
18
|
-
export type CommandOptions = Partial<PopupOptions &
|
17
|
+
export type CommandOptions = Partial<PopupOptions & HintOptions>;
|
19
18
|
|
20
19
|
export const command = (options: CommandOptions = {}): Extension => {
|
21
20
|
return [
|
22
21
|
keymap.of(commandKeyBindings),
|
23
22
|
commandConfig.of(options),
|
24
23
|
commandState,
|
25
|
-
options.renderMenu ? floatingMenu({ renderMenu: options.renderMenu }) : [],
|
26
24
|
options.onHint ? hintViewPlugin({ onHint: options.onHint }) : [],
|
27
25
|
EditorView.focusChangeEffect.of((_, focusing) => {
|
28
26
|
return focusing ? closeEffect.of(null) : null;
|
@@ -15,7 +15,7 @@ export type HintOptions = {
|
|
15
15
|
export const hintViewPlugin = ({ onHint }: HintOptions) =>
|
16
16
|
ViewPlugin.fromClass(
|
17
17
|
class {
|
18
|
-
|
18
|
+
decorations = Decoration.none;
|
19
19
|
update(update: ViewUpdate) {
|
20
20
|
const builder = new RangeSetBuilder<Decoration>();
|
21
21
|
const cState = update.view.state.field(commandState, false);
|
@@ -28,25 +28,25 @@ export const hintViewPlugin = ({ onHint }: HintOptions) =>
|
|
28
28
|
if (selection.from === selection.to && line.from === line.to) {
|
29
29
|
const hint = onHint();
|
30
30
|
if (hint) {
|
31
|
-
builder.add(selection.from, selection.to, Decoration.widget({ widget: new
|
31
|
+
builder.add(selection.from, selection.to, Decoration.widget({ widget: new Hint(hint) }));
|
32
32
|
}
|
33
33
|
}
|
34
34
|
}
|
35
35
|
|
36
|
-
this.
|
36
|
+
this.decorations = builder.finish();
|
37
37
|
}
|
38
38
|
},
|
39
39
|
{
|
40
|
-
provide: (plugin) => [EditorView.decorations.of((view) => view.plugin(plugin)?.
|
40
|
+
provide: (plugin) => [EditorView.decorations.of((view) => view.plugin(plugin)?.decorations ?? Decoration.none)],
|
41
41
|
},
|
42
42
|
);
|
43
43
|
|
44
|
-
class
|
44
|
+
export class Hint extends WidgetType {
|
45
45
|
constructor(readonly content: string | HTMLElement) {
|
46
46
|
super();
|
47
47
|
}
|
48
48
|
|
49
|
-
toDOM() {
|
49
|
+
toDOM(): HTMLSpanElement {
|
50
50
|
const wrap = document.createElement('span');
|
51
51
|
wrap.className = 'cm-placeholder';
|
52
52
|
wrap.style.pointerEvents = 'none';
|
@@ -76,7 +76,7 @@ class CommandHint extends WidgetType {
|
|
76
76
|
return rect;
|
77
77
|
}
|
78
78
|
|
79
|
-
override ignoreEvent() {
|
79
|
+
override ignoreEvent(): boolean {
|
80
80
|
return false;
|
81
81
|
}
|
82
82
|
}
|
@@ -2,41 +2,44 @@
|
|
2
2
|
// Copyright 2024 DXOS.org
|
3
3
|
//
|
4
4
|
|
5
|
-
import { type
|
5
|
+
import { type EditorView, ViewPlugin, type ViewUpdate } from '@codemirror/view';
|
6
6
|
|
7
|
-
import { closeEffect,
|
8
|
-
import { type RenderCallback } from '../../types';
|
7
|
+
import { closeEffect, openEffect } from './action';
|
9
8
|
|
10
9
|
export type FloatingMenuOptions = {
|
11
|
-
|
10
|
+
icon?: string;
|
11
|
+
height?: number;
|
12
|
+
padding?: number;
|
12
13
|
};
|
13
14
|
|
14
|
-
|
15
|
-
// TODO(burdon): Hide when dialog is open.
|
16
|
-
export const floatingMenu = (options: FloatingMenuOptions) =>
|
15
|
+
export const floatingMenu = (options: FloatingMenuOptions = {}) => [
|
17
16
|
ViewPlugin.fromClass(
|
18
17
|
class {
|
19
|
-
button: HTMLElement;
|
20
18
|
view: EditorView;
|
19
|
+
tag: HTMLElement;
|
21
20
|
rafId: number | null = null;
|
22
21
|
|
23
22
|
constructor(view: EditorView) {
|
24
23
|
this.view = view;
|
25
24
|
|
26
|
-
// Position context
|
25
|
+
// Position context.
|
27
26
|
const container = view.scrollDOM;
|
28
27
|
if (getComputedStyle(container).position === 'static') {
|
29
28
|
container.style.position = 'relative';
|
30
29
|
}
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
this.button.style.position = 'absolute';
|
35
|
-
this.button.style.zIndex = '10';
|
36
|
-
this.button.style.display = 'none';
|
31
|
+
const icon = document.createElement('dx-icon');
|
32
|
+
icon.setAttribute('icon', options.icon ?? 'ph--dots-three-outline--regular');
|
37
33
|
|
38
|
-
|
39
|
-
|
34
|
+
const button = document.createElement('button');
|
35
|
+
button.appendChild(icon);
|
36
|
+
button.classList.add('grid', 'items-center', 'justify-center', 'w-8', 'h-8');
|
37
|
+
|
38
|
+
// TODO(burdon): Custom tag/styles?
|
39
|
+
this.tag = document.createElement('dx-ref-tag');
|
40
|
+
this.tag.classList.add('border-none', 'fixed', 'p-0');
|
41
|
+
this.tag.appendChild(button);
|
42
|
+
container.appendChild(this.tag);
|
40
43
|
|
41
44
|
// Listen for scroll events.
|
42
45
|
container.addEventListener('scroll', this.scheduleUpdate.bind(this));
|
@@ -46,58 +49,49 @@ export const floatingMenu = (options: FloatingMenuOptions) =>
|
|
46
49
|
update(update: ViewUpdate) {
|
47
50
|
// TODO(burdon): Timer to fade in/out.
|
48
51
|
if (update.transactions.some((tr) => tr.effects.some((effect) => effect.is(openEffect)))) {
|
49
|
-
this.
|
52
|
+
this.tag.style.display = 'none';
|
50
53
|
} else if (update.transactions.some((tr) => tr.effects.some((effect) => effect.is(closeEffect)))) {
|
51
|
-
this.
|
54
|
+
this.tag.style.display = 'block';
|
52
55
|
} else if (update.selectionSet || update.viewportChanged || update.docChanged || update.geometryChanged) {
|
53
56
|
this.scheduleUpdate();
|
54
57
|
}
|
55
58
|
}
|
56
59
|
|
57
|
-
scheduleUpdate() {
|
58
|
-
if (this.rafId != null) {
|
59
|
-
cancelAnimationFrame(this.rafId);
|
60
|
-
}
|
61
|
-
|
62
|
-
this.rafId = requestAnimationFrame(this.updateButtonPosition.bind(this));
|
63
|
-
}
|
64
|
-
|
65
60
|
updateButtonPosition() {
|
66
|
-
const
|
67
|
-
const lineBlock: BlockInfo = this.view.lineBlockAt(pos);
|
68
|
-
const domInfo = this.view.domAtPos(lineBlock.from);
|
61
|
+
const { x, width } = this.view.contentDOM.getBoundingClientRect();
|
69
62
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
}
|
75
|
-
|
76
|
-
if (!node) {
|
77
|
-
this.button.style.display = 'none';
|
63
|
+
const pos = this.view.state.selection.main.head;
|
64
|
+
const line = this.view.lineBlockAt(pos);
|
65
|
+
const coords = this.view.coordsAtPos(line.from);
|
66
|
+
if (!coords) {
|
78
67
|
return;
|
79
68
|
}
|
80
69
|
|
81
|
-
const
|
82
|
-
const
|
70
|
+
const lineHeight = coords.bottom - coords.top;
|
71
|
+
const dy = (lineHeight - (options.height ?? 32)) / 2;
|
83
72
|
|
84
|
-
|
85
|
-
const
|
86
|
-
|
73
|
+
const offsetTop = coords.top + dy;
|
74
|
+
const offsetLeft = x + width + (options.padding ?? 8);
|
75
|
+
|
76
|
+
this.tag.style.top = `${offsetTop}px`;
|
77
|
+
this.tag.style.left = `${offsetLeft}px`;
|
78
|
+
this.tag.style.display = 'block';
|
79
|
+
}
|
87
80
|
|
88
|
-
|
89
|
-
|
81
|
+
scheduleUpdate() {
|
82
|
+
if (this.rafId != null) {
|
83
|
+
cancelAnimationFrame(this.rafId);
|
84
|
+
}
|
90
85
|
|
91
|
-
this.
|
92
|
-
this.button.style.left = `${offsetLeft}px`;
|
93
|
-
this.button.style.display = 'block';
|
86
|
+
this.rafId = requestAnimationFrame(this.updateButtonPosition.bind(this));
|
94
87
|
}
|
95
88
|
|
96
89
|
destroy() {
|
97
|
-
this.
|
90
|
+
this.tag.remove();
|
98
91
|
if (this.rafId != null) {
|
99
92
|
cancelAnimationFrame(this.rafId);
|
100
93
|
}
|
101
94
|
}
|
102
95
|
},
|
103
|
-
)
|
96
|
+
),
|
97
|
+
];
|
@@ -0,0 +1,116 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2025 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import { EditorSelection, Prec, RangeSetBuilder, type Extension } from '@codemirror/state';
|
6
|
+
import {
|
7
|
+
type Command,
|
8
|
+
Decoration,
|
9
|
+
type DecorationSet,
|
10
|
+
type EditorView,
|
11
|
+
keymap,
|
12
|
+
ViewPlugin,
|
13
|
+
type ViewUpdate,
|
14
|
+
} from '@codemirror/view';
|
15
|
+
|
16
|
+
import { Hint } from './hint';
|
17
|
+
|
18
|
+
export type TypeaheadContext = { line: string };
|
19
|
+
|
20
|
+
// TODO(burdon): Option to complete only at end of line?
|
21
|
+
export type TypeaheadOptions = {
|
22
|
+
onComplete?: (context: TypeaheadContext) => string | undefined;
|
23
|
+
};
|
24
|
+
|
25
|
+
/**
|
26
|
+
* CodeMirror extension for typeahead completion.
|
27
|
+
*/
|
28
|
+
export const typeahead = ({ onComplete }: TypeaheadOptions = {}): Extension => {
|
29
|
+
let hint: string | undefined;
|
30
|
+
|
31
|
+
const complete: Command = (view: EditorView) => {
|
32
|
+
if (!hint) {
|
33
|
+
return false;
|
34
|
+
}
|
35
|
+
|
36
|
+
const selection = view.state.selection.main;
|
37
|
+
view.dispatch({
|
38
|
+
changes: [{ from: selection.from, to: selection.to, insert: hint }],
|
39
|
+
selection: EditorSelection.cursor(selection.from + hint.length),
|
40
|
+
});
|
41
|
+
|
42
|
+
return true;
|
43
|
+
};
|
44
|
+
|
45
|
+
return [
|
46
|
+
ViewPlugin.fromClass(
|
47
|
+
class {
|
48
|
+
decorations: DecorationSet = Decoration.none;
|
49
|
+
update(update: ViewUpdate) {
|
50
|
+
const builder = new RangeSetBuilder<Decoration>();
|
51
|
+
const selection = update.view.state.selection.main;
|
52
|
+
const line = update.view.state.doc.lineAt(selection.from);
|
53
|
+
|
54
|
+
// TODO(burdon): Check at end of line and matches start of previous word.
|
55
|
+
// TODO(burdon): Context grammar.
|
56
|
+
if (selection.from === selection.to && selection.from === line.to) {
|
57
|
+
const str = update.state.sliceDoc(line.from, selection.from);
|
58
|
+
hint = onComplete?.({ line: str });
|
59
|
+
if (hint) {
|
60
|
+
builder.add(selection.from, selection.to, Decoration.widget({ widget: new Hint(hint) }));
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
this.decorations = builder.finish();
|
65
|
+
}
|
66
|
+
},
|
67
|
+
{
|
68
|
+
decorations: (v) => v.decorations,
|
69
|
+
},
|
70
|
+
),
|
71
|
+
|
72
|
+
// Keys.
|
73
|
+
Prec.highest(
|
74
|
+
keymap.of([
|
75
|
+
{
|
76
|
+
key: 'Tab',
|
77
|
+
preventDefault: true,
|
78
|
+
run: complete,
|
79
|
+
},
|
80
|
+
{
|
81
|
+
key: 'ArrowRight',
|
82
|
+
preventDefault: true,
|
83
|
+
run: complete,
|
84
|
+
},
|
85
|
+
]),
|
86
|
+
),
|
87
|
+
];
|
88
|
+
};
|
89
|
+
|
90
|
+
/**
|
91
|
+
* Util to match current line to a static list of completions.
|
92
|
+
*/
|
93
|
+
export const staticCompletion =
|
94
|
+
(completions: string[], defaultCompletion?: string) =>
|
95
|
+
({ line }: TypeaheadContext) => {
|
96
|
+
if (line.length === 0 && defaultCompletion) {
|
97
|
+
return defaultCompletion;
|
98
|
+
}
|
99
|
+
|
100
|
+
const words = line.split(/\s+/).filter(Boolean);
|
101
|
+
if (words.length) {
|
102
|
+
const word = words.at(-1)!;
|
103
|
+
for (const completion of completions) {
|
104
|
+
const match = matchCompletion(completion, word);
|
105
|
+
if (match) {
|
106
|
+
return match;
|
107
|
+
}
|
108
|
+
}
|
109
|
+
}
|
110
|
+
};
|
111
|
+
|
112
|
+
export const matchCompletion = (completion: string, word: string): string | undefined => {
|
113
|
+
if (completion.length > word.length && completion.startsWith(word)) {
|
114
|
+
return completion.slice(word.length);
|
115
|
+
}
|
116
|
+
};
|
@@ -3,14 +3,7 @@
|
|
3
3
|
//
|
4
4
|
|
5
5
|
import { invertedEffects } from '@codemirror/commands';
|
6
|
-
import {
|
7
|
-
type ChangeDesc,
|
8
|
-
type EditorState,
|
9
|
-
type Extension,
|
10
|
-
StateEffect,
|
11
|
-
StateField,
|
12
|
-
type Text,
|
13
|
-
} from '@codemirror/state';
|
6
|
+
import { type ChangeDesc, type Extension, StateEffect, StateField, type Text } from '@codemirror/state';
|
14
7
|
import {
|
15
8
|
hoverTooltip,
|
16
9
|
keymap,
|
@@ -22,17 +15,15 @@ import {
|
|
22
15
|
ViewPlugin,
|
23
16
|
} from '@codemirror/view';
|
24
17
|
import sortBy from 'lodash.sortby';
|
25
|
-
import { useEffect
|
18
|
+
import { useEffect } from 'react';
|
26
19
|
|
27
20
|
import { debounce, type CleanupFn } from '@dxos/async';
|
28
|
-
import { type Live } from '@dxos/live-object';
|
29
21
|
import { log } from '@dxos/log';
|
30
22
|
import { isNonNullable } from '@dxos/util';
|
31
23
|
|
32
24
|
import { documentId } from './selection';
|
33
|
-
import { type EditorToolbarState } from '../components';
|
34
25
|
import { type RenderCallback, type Comment, type Range } from '../types';
|
35
|
-
import { Cursor,
|
26
|
+
import { Cursor, singleValueFacet, callbackWrapper } from '../util';
|
36
27
|
|
37
28
|
//
|
38
29
|
// State management.
|
@@ -156,7 +147,7 @@ const commentsDecorations = EditorView.decorations.compute([commentsState], (sta
|
|
156
147
|
return Decoration.set(decorations);
|
157
148
|
});
|
158
149
|
|
159
|
-
const commentClickedEffect = StateEffect.define<string>();
|
150
|
+
export const commentClickedEffect = StateEffect.define<string>();
|
160
151
|
|
161
152
|
const handleCommentClick = EditorView.domEventHandlers({
|
162
153
|
click: (event, view) => {
|
@@ -545,30 +536,6 @@ export const scrollThreadIntoView = (view: EditorView, id: string, center = true
|
|
545
536
|
}
|
546
537
|
};
|
547
538
|
|
548
|
-
/**
|
549
|
-
* Query the editor state for the active formatting at the selection.
|
550
|
-
*/
|
551
|
-
export const selectionOverlapsComment = (state: EditorState): boolean => {
|
552
|
-
// May not be defined if thread plugin not installed.
|
553
|
-
const commentState = state.field(commentsState, false);
|
554
|
-
if (commentState === undefined) {
|
555
|
-
return false;
|
556
|
-
}
|
557
|
-
|
558
|
-
const { selection } = state;
|
559
|
-
for (const range of selection.ranges) {
|
560
|
-
if (commentState.comments.some(({ range: commentRange }) => overlap(commentRange, range))) {
|
561
|
-
return true;
|
562
|
-
}
|
563
|
-
}
|
564
|
-
|
565
|
-
return false;
|
566
|
-
};
|
567
|
-
|
568
|
-
const hasActiveSelection = (state: EditorState): boolean => {
|
569
|
-
return state.selection.ranges.some((range) => !range.empty);
|
570
|
-
};
|
571
|
-
|
572
539
|
/**
|
573
540
|
* Manages external comment synchronization for the editor.
|
574
541
|
* This class subscribes to external comment updates and applies them to the editor view.
|
@@ -606,19 +573,6 @@ export const createExternalCommentSync = (
|
|
606
573
|
},
|
607
574
|
);
|
608
575
|
|
609
|
-
export const useCommentState = (state: Live<EditorToolbarState>): Extension => {
|
610
|
-
return useMemo(
|
611
|
-
() =>
|
612
|
-
EditorView.updateListener.of((update) => {
|
613
|
-
if (update.docChanged || update.selectionSet) {
|
614
|
-
state.comment = selectionOverlapsComment(update.state);
|
615
|
-
state.selection = hasActiveSelection(update.state);
|
616
|
-
}
|
617
|
-
}),
|
618
|
-
[state],
|
619
|
-
);
|
620
|
-
};
|
621
|
-
|
622
576
|
/**
|
623
577
|
* @deprecated This hook will be removed in future versions. Use the new comment sync extension instead.
|
624
578
|
* Update comments state field.
|
@@ -636,22 +590,3 @@ export const useComments = (view: EditorView | null | undefined, id: string, com
|
|
636
590
|
}
|
637
591
|
});
|
638
592
|
};
|
639
|
-
|
640
|
-
/**
|
641
|
-
* Hook provides an extension to listen for comment clicks and invoke a handler.
|
642
|
-
*/
|
643
|
-
export const useCommentClickListener = (onCommentClick: (commentId: string) => void): Extension => {
|
644
|
-
return useMemo(
|
645
|
-
() =>
|
646
|
-
EditorView.updateListener.of((update) => {
|
647
|
-
update.transactions.forEach((transaction) => {
|
648
|
-
transaction.effects.forEach((effect) => {
|
649
|
-
if (effect.is(commentClickedEffect)) {
|
650
|
-
onCommentClick(effect.value);
|
651
|
-
}
|
652
|
-
});
|
653
|
-
});
|
654
|
-
}),
|
655
|
-
[onCommentClick],
|
656
|
-
);
|
657
|
-
};
|
@@ -11,6 +11,7 @@ import { oneDarkHighlightStyle } from '@codemirror/theme-one-dark';
|
|
11
11
|
import {
|
12
12
|
EditorView,
|
13
13
|
type KeyBinding,
|
14
|
+
ViewPlugin,
|
14
15
|
drawSelection,
|
15
16
|
dropCursor,
|
16
17
|
highlightActiveLine,
|
@@ -59,6 +60,7 @@ export type BasicExtensionsOptions = {
|
|
59
60
|
indentWithTab?: boolean;
|
60
61
|
keymap?: null | 'default' | 'standard';
|
61
62
|
lineNumbers?: boolean;
|
63
|
+
/** If false then do not set a max-width or side margin on the editor. */
|
62
64
|
lineWrapping?: boolean;
|
63
65
|
placeholder?: string;
|
64
66
|
/** If true user cannot edit the text, but they can still select and copy it. */
|
@@ -149,6 +151,9 @@ export type ThemeExtensionsOptions = {
|
|
149
151
|
editor?: {
|
150
152
|
className?: string;
|
151
153
|
};
|
154
|
+
scroll?: {
|
155
|
+
className?: string;
|
156
|
+
};
|
152
157
|
content?: {
|
153
158
|
className?: string;
|
154
159
|
};
|
@@ -179,6 +184,14 @@ export const createThemeExtensions = ({
|
|
179
184
|
(themeMode === 'dark' ? syntaxHighlighting(oneDarkHighlightStyle) : syntaxHighlighting(defaultHighlightStyle)),
|
180
185
|
slots.editor?.className && EditorView.editorAttributes.of({ class: slots.editor.className }),
|
181
186
|
slots.content?.className && EditorView.contentAttributes.of({ class: slots.content.className }),
|
187
|
+
slots.scroll?.className &&
|
188
|
+
ViewPlugin.fromClass(
|
189
|
+
class {
|
190
|
+
constructor(view: EditorView) {
|
191
|
+
view.scrollDOM.classList.add(slots.scroll.className);
|
192
|
+
}
|
193
|
+
},
|
194
|
+
),
|
182
195
|
].filter(isNotFalsy);
|
183
196
|
};
|
184
197
|
|
package/src/extensions/index.ts
CHANGED
@@ -14,10 +14,12 @@ export * from './dnd';
|
|
14
14
|
export * from './factories';
|
15
15
|
export * from './focus';
|
16
16
|
export * from './folding';
|
17
|
+
export * from './json';
|
17
18
|
export * from './listener';
|
18
19
|
export * from './markdown';
|
19
20
|
export * from './mention';
|
20
21
|
export * from './modes';
|
22
|
+
export * from './outliner';
|
21
23
|
export * from './preview';
|
22
24
|
export * from './selection';
|
23
25
|
export * from './typewriter';
|