@dxos/react-ui-editor 0.8.3-main.672df60 → 0.8.3-staging.0fa589b
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 +981 -377
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +1025 -420
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +981 -377
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/EditorToolbar/EditorToolbar.d.ts.map +1 -1
- package/dist/types/src/components/EditorToolbar/util.d.ts +2 -2
- package/dist/types/src/components/Popover/CommandMenu.d.ts +34 -0
- package/dist/types/src/components/Popover/CommandMenu.d.ts.map +1 -0
- package/dist/types/src/components/Popover/RefDropdownMenu.d.ts.map +1 -1
- package/dist/types/src/components/Popover/RefPopover.d.ts +19 -6
- package/dist/types/src/components/Popover/RefPopover.d.ts.map +1 -1
- package/dist/types/src/components/Popover/index.d.ts +1 -0
- package/dist/types/src/components/Popover/index.d.ts.map +1 -1
- package/dist/types/src/defaults.d.ts +0 -1
- package/dist/types/src/defaults.d.ts.map +1 -1
- package/dist/types/src/extensions/automerge/automerge.stories.d.ts.map +1 -1
- package/dist/types/src/extensions/command/action.d.ts.map +1 -1
- package/dist/types/src/extensions/command/command-menu.d.ts +20 -0
- package/dist/types/src/extensions/command/command-menu.d.ts.map +1 -0
- package/dist/types/src/extensions/command/command.d.ts.map +1 -1
- package/dist/types/src/extensions/command/{menu.d.ts → floating-menu.d.ts} +1 -1
- package/dist/types/src/extensions/command/floating-menu.d.ts.map +1 -0
- package/dist/types/src/extensions/command/hint.d.ts +5 -2
- package/dist/types/src/extensions/command/hint.d.ts.map +1 -1
- package/dist/types/src/extensions/command/index.d.ts +3 -1
- package/dist/types/src/extensions/command/index.d.ts.map +1 -1
- package/dist/types/src/extensions/command/placeholder.d.ts +10 -0
- package/dist/types/src/extensions/command/placeholder.d.ts.map +1 -0
- 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/useCommandMenu.d.ts +26 -0
- package/dist/types/src/extensions/command/useCommandMenu.d.ts.map +1 -0
- package/dist/types/src/extensions/factories.d.ts +1 -0
- package/dist/types/src/extensions/factories.d.ts.map +1 -1
- package/dist/types/src/extensions/markdown/bundle.d.ts.map +1 -1
- package/dist/types/src/extensions/outliner/tree.d.ts.map +1 -1
- package/dist/types/src/extensions/preview/preview.d.ts +12 -19
- package/dist/types/src/extensions/preview/preview.d.ts.map +1 -1
- package/dist/types/src/hooks/useTextEditor.d.ts +8 -9
- package/dist/types/src/hooks/useTextEditor.d.ts.map +1 -1
- package/dist/types/src/stories/CommandMenu.stories.d.ts +13 -0
- package/dist/types/src/stories/CommandMenu.stories.d.ts.map +1 -0
- package/dist/types/src/stories/Preview.stories.d.ts.map +1 -1
- package/dist/types/src/util/dom.d.ts +5 -0
- package/dist/types/src/util/dom.d.ts.map +1 -1
- package/dist/types/src/util/react.d.ts +2 -4
- package/dist/types/src/util/react.d.ts.map +1 -1
- package/package.json +31 -28
- package/src/components/EditorToolbar/EditorToolbar.tsx +5 -9
- package/src/components/Popover/CommandMenu.tsx +279 -0
- package/src/components/Popover/RefDropdownMenu.tsx +5 -3
- package/src/components/Popover/RefPopover.tsx +46 -22
- package/src/components/Popover/index.ts +1 -0
- package/src/defaults.ts +1 -6
- package/src/extensions/automerge/automerge.stories.tsx +5 -5
- package/src/extensions/command/action.ts +9 -2
- package/src/extensions/command/command-menu.ts +210 -0
- package/src/extensions/command/command.ts +8 -8
- package/src/extensions/command/{menu.ts → floating-menu.ts} +0 -4
- package/src/extensions/command/hint.ts +29 -9
- package/src/extensions/command/index.ts +3 -1
- package/src/extensions/command/placeholder.ts +113 -0
- package/src/extensions/command/state.ts +1 -2
- package/src/extensions/command/useCommandMenu.ts +118 -0
- package/src/extensions/factories.ts +4 -1
- package/src/extensions/markdown/bundle.ts +0 -2
- package/src/extensions/outliner/outliner.ts +0 -3
- package/src/extensions/outliner/tree.test.ts +13 -10
- package/src/extensions/outliner/tree.ts +5 -3
- package/src/extensions/preview/preview.ts +11 -89
- package/src/hooks/useTextEditor.ts +11 -12
- package/src/stories/Command.stories.tsx +1 -1
- package/src/stories/CommandMenu.stories.tsx +159 -0
- package/src/stories/Preview.stories.tsx +157 -78
- package/src/stories/components/util.tsx +2 -2
- package/src/util/dom.ts +20 -0
- package/src/util/react.tsx +3 -20
- package/dist/types/src/extensions/command/menu.d.ts.map +0 -1
@@ -2,11 +2,13 @@
|
|
2
2
|
// Copyright 2024 DXOS.org
|
3
3
|
//
|
4
4
|
|
5
|
-
import { type Extension } from '@codemirror/state';
|
5
|
+
import { Prec, type Extension } from '@codemirror/state';
|
6
6
|
import { EditorView, keymap } from '@codemirror/view';
|
7
7
|
|
8
|
+
import { isNonNullable } from '@dxos/util';
|
9
|
+
|
8
10
|
import { closeEffect, commandKeyBindings } from './action';
|
9
|
-
import {
|
11
|
+
import { hint, type HintOptions } from './hint';
|
10
12
|
import { commandConfig, commandState, type PopupOptions } from './state';
|
11
13
|
|
12
14
|
// TODO(burdon): Create knowledge base for CM notes and ideas.
|
@@ -18,17 +20,15 @@ export type CommandOptions = Partial<PopupOptions & HintOptions>;
|
|
18
20
|
|
19
21
|
export const command = (options: CommandOptions = {}): Extension => {
|
20
22
|
return [
|
21
|
-
keymap.of(commandKeyBindings),
|
23
|
+
Prec.highest(keymap.of(commandKeyBindings)),
|
22
24
|
commandConfig.of(options),
|
23
25
|
commandState,
|
24
|
-
options.onHint
|
25
|
-
EditorView.focusChangeEffect.of((_, focusing) =>
|
26
|
-
return focusing ? closeEffect.of(null) : null;
|
27
|
-
}),
|
26
|
+
options.onHint && hint(options),
|
27
|
+
EditorView.focusChangeEffect.of((_, focusing) => (focusing ? closeEffect.of(null) : null)),
|
28
28
|
EditorView.theme({
|
29
29
|
'.cm-tooltip': {
|
30
30
|
background: 'transparent',
|
31
31
|
},
|
32
32
|
}),
|
33
|
-
];
|
33
|
+
].filter(isNonNullable);
|
34
34
|
};
|
@@ -34,7 +34,6 @@ export const floatingMenu = (options: FloatingMenuOptions = {}) => [
|
|
34
34
|
{
|
35
35
|
const icon = document.createElement('dx-icon');
|
36
36
|
icon.setAttribute('icon', options.icon ?? 'ph--dots-three-vertical--regular');
|
37
|
-
|
38
37
|
const button = document.createElement('button');
|
39
38
|
button.appendChild(icon);
|
40
39
|
|
@@ -123,9 +122,6 @@ export const floatingMenu = (options: FloatingMenuOptions = {}) => [
|
|
123
122
|
'[data-has-focus] & .cm-ref-tag': {
|
124
123
|
opacity: '1',
|
125
124
|
},
|
126
|
-
'[data-is-attention-source] & .cm-ref-tag': {
|
127
|
-
opacity: '1',
|
128
|
-
},
|
129
125
|
'.cm-ref-tag button': {
|
130
126
|
display: 'grid',
|
131
127
|
alignItems: 'center',
|
@@ -1,5 +1,6 @@
|
|
1
1
|
//
|
2
2
|
// Copyright 2024 DXOS.org
|
3
|
+
// Based on https://github.com/codemirror/view/blob/main/src/placeholder.ts
|
3
4
|
//
|
4
5
|
|
5
6
|
import { RangeSetBuilder } from '@codemirror/state';
|
@@ -9,37 +10,56 @@ import { commandState } from './state';
|
|
9
10
|
import { clientRectsFor, flattenRect } from '../../util';
|
10
11
|
|
11
12
|
export type HintOptions = {
|
12
|
-
|
13
|
+
delay?: number;
|
14
|
+
onHint?: () => string | undefined;
|
13
15
|
};
|
14
16
|
|
15
|
-
export const
|
16
|
-
ViewPlugin.fromClass(
|
17
|
+
export const hint = ({ delay = 3_000, onHint }: HintOptions) => {
|
18
|
+
return ViewPlugin.fromClass(
|
17
19
|
class {
|
18
20
|
decorations = Decoration.none;
|
21
|
+
timeout: ReturnType<typeof setTimeout> | undefined;
|
22
|
+
|
19
23
|
update(update: ViewUpdate) {
|
24
|
+
if (this.timeout) {
|
25
|
+
clearTimeout(this.timeout);
|
26
|
+
this.timeout = undefined;
|
27
|
+
}
|
28
|
+
|
20
29
|
const builder = new RangeSetBuilder<Decoration>();
|
21
30
|
const cState = update.view.state.field(commandState, false);
|
22
31
|
if (!cState?.tooltip) {
|
23
32
|
const selection = update.view.state.selection.main;
|
24
33
|
const line = update.view.state.doc.lineAt(selection.from);
|
25
34
|
// Only show if blank line.
|
26
|
-
// TODO(burdon): Clashes with placeholder if pos === 0.
|
27
|
-
// TODO(burdon): Show after delay or if blank line above?
|
28
35
|
if (selection.from === selection.to && line.from === line.to) {
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
36
|
+
// Set timeout to add decoration after delay.
|
37
|
+
this.timeout = setTimeout(() => {
|
38
|
+
const hint = onHint?.();
|
39
|
+
if (hint) {
|
40
|
+
const builder = new RangeSetBuilder<Decoration>();
|
41
|
+
builder.add(selection.from, selection.to, Decoration.widget({ widget: new Hint(hint) }));
|
42
|
+
this.decorations = builder.finish();
|
43
|
+
update.view.update([]);
|
44
|
+
}
|
45
|
+
}, delay);
|
33
46
|
}
|
34
47
|
}
|
35
48
|
|
36
49
|
this.decorations = builder.finish();
|
37
50
|
}
|
51
|
+
|
52
|
+
destroy() {
|
53
|
+
if (this.timeout) {
|
54
|
+
clearTimeout(this.timeout);
|
55
|
+
}
|
56
|
+
}
|
38
57
|
},
|
39
58
|
{
|
40
59
|
provide: (plugin) => [EditorView.decorations.of((view) => view.plugin(plugin)?.decorations ?? Decoration.none)],
|
41
60
|
},
|
42
61
|
);
|
62
|
+
};
|
43
63
|
|
44
64
|
export class Hint extends WidgetType {
|
45
65
|
constructor(readonly content: string | HTMLElement) {
|
@@ -0,0 +1,113 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2025 DXOS.org
|
3
|
+
// Based on https://github.com/codemirror/view/blob/main/src/placeholder.ts
|
4
|
+
//
|
5
|
+
|
6
|
+
import { type Extension } from '@codemirror/state';
|
7
|
+
import { Decoration, EditorView, WidgetType, ViewPlugin, type ViewUpdate } from '@codemirror/view';
|
8
|
+
|
9
|
+
import { clientRectsFor, flattenRect } from '../../util';
|
10
|
+
|
11
|
+
type Content = string | HTMLElement | ((view: EditorView) => HTMLElement);
|
12
|
+
|
13
|
+
export type PlaceholderOptions = {
|
14
|
+
delay?: number;
|
15
|
+
content: Content;
|
16
|
+
};
|
17
|
+
|
18
|
+
export const placeholder = ({ delay = 3_000, content }: PlaceholderOptions): Extension => {
|
19
|
+
const plugin = ViewPlugin.fromClass(
|
20
|
+
class {
|
21
|
+
decorations = Decoration.none;
|
22
|
+
timeout: ReturnType<typeof setTimeout> | undefined;
|
23
|
+
|
24
|
+
update(update: ViewUpdate) {
|
25
|
+
if (this.timeout) {
|
26
|
+
window.clearTimeout(this.timeout);
|
27
|
+
this.timeout = undefined;
|
28
|
+
}
|
29
|
+
|
30
|
+
// Check if the active line (where cursor is) is empty.
|
31
|
+
const activeLine = update.view.state.doc.lineAt(update.view.state.selection.main.head);
|
32
|
+
const isEmpty = activeLine.text.trim() === '';
|
33
|
+
if (isEmpty) {
|
34
|
+
// Create widget decoration at the start of the current line.
|
35
|
+
const lineStart = activeLine.from;
|
36
|
+
this.timeout = setTimeout(() => {
|
37
|
+
this.decorations = Decoration.set([
|
38
|
+
Decoration.widget({
|
39
|
+
widget: new Placeholder(content),
|
40
|
+
side: 1,
|
41
|
+
}).range(lineStart),
|
42
|
+
]);
|
43
|
+
|
44
|
+
update.view.update([]);
|
45
|
+
}, delay);
|
46
|
+
}
|
47
|
+
|
48
|
+
this.decorations = Decoration.none;
|
49
|
+
}
|
50
|
+
|
51
|
+
destroy() {
|
52
|
+
if (this.timeout) {
|
53
|
+
clearTimeout(this.timeout);
|
54
|
+
}
|
55
|
+
}
|
56
|
+
},
|
57
|
+
{
|
58
|
+
provide: (plugin) => {
|
59
|
+
return [EditorView.decorations.of((view) => view.plugin(plugin)?.decorations ?? Decoration.none)];
|
60
|
+
},
|
61
|
+
},
|
62
|
+
);
|
63
|
+
|
64
|
+
return typeof content === 'string'
|
65
|
+
? [plugin, EditorView.contentAttributes.of({ 'aria-placeholder': content })]
|
66
|
+
: plugin;
|
67
|
+
};
|
68
|
+
|
69
|
+
class Placeholder extends WidgetType {
|
70
|
+
constructor(readonly content: Content) {
|
71
|
+
super();
|
72
|
+
}
|
73
|
+
|
74
|
+
toDOM(view: EditorView) {
|
75
|
+
const wrap = document.createElement('span');
|
76
|
+
wrap.className = 'cm-placeholder';
|
77
|
+
wrap.style.pointerEvents = 'none';
|
78
|
+
wrap.appendChild(
|
79
|
+
typeof this.content === 'string'
|
80
|
+
? document.createTextNode(this.content)
|
81
|
+
: typeof this.content === 'function'
|
82
|
+
? this.content(view)
|
83
|
+
: this.content.cloneNode(true),
|
84
|
+
);
|
85
|
+
wrap.setAttribute('aria-hidden', 'true');
|
86
|
+
return wrap;
|
87
|
+
}
|
88
|
+
|
89
|
+
override coordsAt(dom: HTMLElement) {
|
90
|
+
const rects = dom.firstChild ? clientRectsFor(dom.firstChild) : [];
|
91
|
+
if (!rects.length) {
|
92
|
+
return null;
|
93
|
+
}
|
94
|
+
|
95
|
+
const style = window.getComputedStyle(dom.parentNode as HTMLElement);
|
96
|
+
const rect = flattenRect(rects[0], style.direction !== 'rtl');
|
97
|
+
const lineHeight = parseInt(style.lineHeight);
|
98
|
+
if (rect.bottom - rect.top > lineHeight * 1.5) {
|
99
|
+
return {
|
100
|
+
left: rect.left,
|
101
|
+
right: rect.right,
|
102
|
+
top: rect.top,
|
103
|
+
bottom: rect.top + lineHeight,
|
104
|
+
};
|
105
|
+
}
|
106
|
+
|
107
|
+
return rect;
|
108
|
+
}
|
109
|
+
|
110
|
+
override ignoreEvent() {
|
111
|
+
return false;
|
112
|
+
}
|
113
|
+
}
|
@@ -17,7 +17,7 @@ export type PopupOptions = {
|
|
17
17
|
};
|
18
18
|
|
19
19
|
type CommandState = {
|
20
|
-
tooltip?: Tooltip
|
20
|
+
tooltip?: Tooltip;
|
21
21
|
};
|
22
22
|
|
23
23
|
export const commandState = StateField.define<CommandState>({
|
@@ -38,7 +38,6 @@ export const commandState = StateField.define<CommandState>({
|
|
38
38
|
strictSide: true,
|
39
39
|
create: (view: EditorView) => {
|
40
40
|
const root = document.createElement('div');
|
41
|
-
|
42
41
|
const tooltipView: TooltipView = {
|
43
42
|
dom: root,
|
44
43
|
mount: (view: EditorView) => {
|
@@ -0,0 +1,118 @@
|
|
1
|
+
//
|
2
|
+
// Copyright 2024 DXOS.org
|
3
|
+
//
|
4
|
+
|
5
|
+
import { type EditorView } from '@codemirror/view';
|
6
|
+
import { type RefObject, useCallback, useMemo, useRef, useState } from 'react';
|
7
|
+
|
8
|
+
import { type DxRefTag, type DxRefTagActivate } from '@dxos/lit-ui';
|
9
|
+
import { type MaybePromise } from '@dxos/util';
|
10
|
+
|
11
|
+
import { commandMenu, commandRangeEffect } from './command-menu';
|
12
|
+
import { type PlaceholderOptions } from './placeholder';
|
13
|
+
import { getItem, getNextItem, getPreviousItem, type CommandMenuGroup, type CommandMenuItem } from '../../components';
|
14
|
+
|
15
|
+
export type UseCommandMenuOptions = {
|
16
|
+
viewRef: RefObject<EditorView | undefined>;
|
17
|
+
trigger: string | string[];
|
18
|
+
placeholder?: Partial<PlaceholderOptions>;
|
19
|
+
getMenu: (trigger: string, query?: string) => MaybePromise<CommandMenuGroup[]>;
|
20
|
+
};
|
21
|
+
|
22
|
+
export const useCommandMenu = ({ viewRef, trigger, placeholder, getMenu }: UseCommandMenuOptions) => {
|
23
|
+
const triggerRef = useRef<DxRefTag | null>(null);
|
24
|
+
const currentRef = useRef<CommandMenuItem | null>(null);
|
25
|
+
const groupsRef = useRef<CommandMenuGroup[]>([]);
|
26
|
+
const [currentItem, setCurrentItem] = useState<string>();
|
27
|
+
const [open, setOpen] = useState(false);
|
28
|
+
const [_, refresh] = useState({});
|
29
|
+
|
30
|
+
const handleOpenChange = useCallback(
|
31
|
+
async (open: boolean, trigger?: string) => {
|
32
|
+
if (open && trigger) {
|
33
|
+
groupsRef.current = await getMenu(trigger);
|
34
|
+
}
|
35
|
+
setOpen(open);
|
36
|
+
if (!open) {
|
37
|
+
triggerRef.current = null;
|
38
|
+
setCurrentItem(undefined);
|
39
|
+
viewRef.current?.dispatch({ effects: [commandRangeEffect.of(null)] });
|
40
|
+
}
|
41
|
+
},
|
42
|
+
[getMenu],
|
43
|
+
);
|
44
|
+
|
45
|
+
const handleActivate = useCallback(
|
46
|
+
async (event: DxRefTagActivate) => {
|
47
|
+
const item = getItem(groupsRef.current, currentItem);
|
48
|
+
if (item) {
|
49
|
+
currentRef.current = item;
|
50
|
+
}
|
51
|
+
|
52
|
+
triggerRef.current = event.trigger;
|
53
|
+
const triggerKey = event.trigger.getAttribute('data-trigger');
|
54
|
+
if (!open && triggerKey) {
|
55
|
+
await handleOpenChange(true, triggerKey);
|
56
|
+
}
|
57
|
+
},
|
58
|
+
[open, handleOpenChange],
|
59
|
+
);
|
60
|
+
|
61
|
+
const handleSelect = useCallback((item: CommandMenuItem) => {
|
62
|
+
const view = viewRef.current;
|
63
|
+
if (!view) {
|
64
|
+
return;
|
65
|
+
}
|
66
|
+
|
67
|
+
const selection = view.state.selection.main;
|
68
|
+
void item.onSelect?.(view, selection.head);
|
69
|
+
}, []);
|
70
|
+
|
71
|
+
const serializedTrigger = Array.isArray(trigger) ? trigger.join(',') : trigger;
|
72
|
+
const _commandMenu = useMemo(() => {
|
73
|
+
return commandMenu({
|
74
|
+
trigger,
|
75
|
+
placeholder,
|
76
|
+
onClose: () => handleOpenChange(false),
|
77
|
+
onArrowDown: () => {
|
78
|
+
setCurrentItem((currentItem) => {
|
79
|
+
const next = getNextItem(groupsRef.current, currentItem);
|
80
|
+
currentRef.current = next;
|
81
|
+
return next.id;
|
82
|
+
});
|
83
|
+
},
|
84
|
+
onArrowUp: () => {
|
85
|
+
setCurrentItem((currentItem) => {
|
86
|
+
const previous = getPreviousItem(groupsRef.current, currentItem);
|
87
|
+
currentRef.current = previous;
|
88
|
+
return previous.id;
|
89
|
+
});
|
90
|
+
},
|
91
|
+
onEnter: () => {
|
92
|
+
if (currentRef.current) {
|
93
|
+
handleSelect(currentRef.current);
|
94
|
+
}
|
95
|
+
},
|
96
|
+
onTextChange: async (trigger, text) => {
|
97
|
+
groupsRef.current = await getMenu(trigger, text);
|
98
|
+
const firstItem = groupsRef.current.filter((group) => group.items.length > 0)[0]?.items[0];
|
99
|
+
if (firstItem) {
|
100
|
+
setCurrentItem(firstItem.id);
|
101
|
+
currentRef.current = firstItem;
|
102
|
+
}
|
103
|
+
refresh({});
|
104
|
+
},
|
105
|
+
});
|
106
|
+
}, [handleOpenChange, getMenu, serializedTrigger, placeholder]);
|
107
|
+
|
108
|
+
return {
|
109
|
+
commandMenu: _commandMenu,
|
110
|
+
currentItem,
|
111
|
+
groupsRef,
|
112
|
+
ref: triggerRef,
|
113
|
+
open,
|
114
|
+
onActivate: handleActivate,
|
115
|
+
onOpenChange: setOpen,
|
116
|
+
onSelect: handleSelect,
|
117
|
+
};
|
118
|
+
};
|
@@ -34,6 +34,7 @@ import { hexToHue, isNotFalsy } from '@dxos/util';
|
|
34
34
|
import { automerge } from './automerge';
|
35
35
|
import { SpaceAwarenessProvider, awareness } from './awareness';
|
36
36
|
import { focus } from './focus';
|
37
|
+
import { editorGutter, editorMonospace } from '../defaults';
|
37
38
|
import { type ThemeStyles, defaultTheme } from '../styles';
|
38
39
|
|
39
40
|
//
|
@@ -62,6 +63,7 @@ export type BasicExtensionsOptions = {
|
|
62
63
|
lineNumbers?: boolean;
|
63
64
|
/** If false then do not set a max-width or side margin on the editor. */
|
64
65
|
lineWrapping?: boolean;
|
66
|
+
monospace?: boolean;
|
65
67
|
placeholder?: string;
|
66
68
|
/** If true user cannot edit the text, but they can still select and copy it. */
|
67
69
|
readOnly?: boolean;
|
@@ -107,8 +109,9 @@ export const createBasicExtensions = (_props?: BasicExtensionsOptions): Extensio
|
|
107
109
|
props.focus && focus,
|
108
110
|
props.highlightActiveLine && highlightActiveLine(),
|
109
111
|
props.history && history(),
|
110
|
-
props.lineNumbers && lineNumbers(),
|
112
|
+
props.lineNumbers && [lineNumbers(), editorGutter],
|
111
113
|
props.lineWrapping && EditorView.lineWrapping,
|
114
|
+
props.monospace && editorMonospace,
|
112
115
|
props.placeholder && placeholder(props.placeholder),
|
113
116
|
props.readOnly !== undefined && EditorState.readOnly.of(props.readOnly),
|
114
117
|
props.scrollPastEnd && scrollPastEnd(),
|
@@ -7,7 +7,6 @@ import { defaultKeymap, indentWithTab } from '@codemirror/commands';
|
|
7
7
|
import { markdownLanguage, markdown } from '@codemirror/lang-markdown';
|
8
8
|
import { syntaxHighlighting } from '@codemirror/language';
|
9
9
|
import { languages } from '@codemirror/language-data';
|
10
|
-
import { lintKeymap } from '@codemirror/lint';
|
11
10
|
import { type Extension } from '@codemirror/state';
|
12
11
|
import { keymap } from '@codemirror/view';
|
13
12
|
|
@@ -66,7 +65,6 @@ export const createMarkdownExtensions = (options: MarkdownBundleOptions = {}): E
|
|
66
65
|
// https://codemirror.net/docs/ref/#commands.defaultKeymap
|
67
66
|
...defaultKeymap,
|
68
67
|
...completionKeymap,
|
69
|
-
...lintKeymap,
|
70
68
|
].filter(isNotFalsy),
|
71
69
|
),
|
72
70
|
];
|
@@ -161,9 +161,6 @@ const decorations = () => [
|
|
161
161
|
'[data-has-focus] & .cm-list-item-selected': {
|
162
162
|
borderColor: 'var(--dx-separator)',
|
163
163
|
},
|
164
|
-
'[data-is-attention-source] & .cm-list-item-selected': {
|
165
|
-
borderColor: 'var(--dx-separator)',
|
166
|
-
},
|
167
164
|
}),
|
168
165
|
),
|
169
166
|
];
|
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
|
6
6
|
import { EditorState } from '@codemirror/state';
|
7
|
-
import { describe, test } from 'vitest';
|
7
|
+
import { beforeEach, describe, test } from 'vitest';
|
8
8
|
|
9
9
|
import { outlinerTree, treeFacet, listItemToString, type Item } from './tree';
|
10
10
|
import { str } from '../../testing';
|
@@ -71,7 +71,11 @@ describe('tree (boundary conditions)', () => {
|
|
71
71
|
});
|
72
72
|
|
73
73
|
describe('tree (advanced)', () => {
|
74
|
-
|
74
|
+
let state: EditorState;
|
75
|
+
|
76
|
+
beforeEach(() => {
|
77
|
+
state = EditorState.create({ doc: str(...lines), extensions });
|
78
|
+
});
|
75
79
|
|
76
80
|
test('traverse', ({ expect }) => {
|
77
81
|
const tree = state.facet(treeFacet);
|
@@ -80,15 +84,15 @@ describe('tree (advanced)', () => {
|
|
80
84
|
console.log(listItemToString(item, level));
|
81
85
|
count++;
|
82
86
|
});
|
83
|
-
expect(count).
|
87
|
+
expect(count).to.eq(9);
|
84
88
|
});
|
85
89
|
|
86
90
|
test('continguous', ({ expect }) => {
|
87
91
|
const tree = state.facet(treeFacet);
|
88
92
|
const ranges: Range[] = [];
|
89
93
|
tree.traverse((item) => {
|
90
|
-
ranges.push(item.lineRange);
|
91
94
|
console.log(listItemToString(item));
|
95
|
+
ranges.push(item.lineRange);
|
92
96
|
});
|
93
97
|
|
94
98
|
// Check no gaps between ranges.
|
@@ -97,18 +101,17 @@ describe('tree (advanced)', () => {
|
|
97
101
|
for (let i = 0; i < ranges.length - 1; i++) {
|
98
102
|
const current = ranges[i];
|
99
103
|
const next = ranges[i + 1];
|
100
|
-
expect(current.to + 1).
|
104
|
+
expect(current.to + 1).to.eq(next.from);
|
101
105
|
}
|
102
106
|
});
|
103
107
|
|
104
108
|
test('find', ({ expect }) => {
|
105
109
|
const tree = state.facet(treeFacet);
|
106
|
-
|
107
110
|
expect(tree.find(0)).to.include({ type: 'task' });
|
108
111
|
expect(tree.find(state.doc.length)).to.include({ type: 'task' });
|
109
112
|
|
110
113
|
expect(tree.find(getPos(1))).to.include({ type: 'task' });
|
111
|
-
expect(tree.find(getPos(1))).
|
114
|
+
expect(tree.find(getPos(1))).to.eq(tree.find(getPos(1) + 4));
|
112
115
|
expect(tree.find(getPos(5))).to.include({ type: 'bullet' });
|
113
116
|
});
|
114
117
|
|
@@ -148,17 +151,17 @@ describe('tree (advanced)', () => {
|
|
148
151
|
const tree = state.facet(treeFacet);
|
149
152
|
{
|
150
153
|
const item = tree.find(getPos(0))!;
|
151
|
-
expect(tree.lastDescendant(item).index).
|
154
|
+
expect(tree.lastDescendant(item).index).to.eq(item.index);
|
152
155
|
}
|
153
156
|
{
|
154
157
|
const item = tree.find(getPos(1))!;
|
155
158
|
const last = tree.find(getPos(7))!;
|
156
|
-
expect(tree.lastDescendant(item).index).
|
159
|
+
expect(tree.lastDescendant(item).index).to.eq(last.index);
|
157
160
|
}
|
158
161
|
{
|
159
162
|
const item = tree.find(getPos(3))!;
|
160
163
|
const last = tree.find(getPos(6))!;
|
161
|
-
expect(tree.lastDescendant(item).index).
|
164
|
+
expect(tree.lastDescendant(item).index).to.eq(last.index);
|
162
165
|
}
|
163
166
|
});
|
164
167
|
});
|
@@ -69,8 +69,8 @@ export class Tree implements Item {
|
|
69
69
|
traverse<T = any>(cb: (item: Item, level: number) => T | void): T | undefined;
|
70
70
|
traverse<T = any>(item: Item, cb: (item: Item, level: number) => T | void): T | undefined;
|
71
71
|
traverse<T = any>(
|
72
|
-
itemOrCb: Item | ((item: Item, level: number) => T | void),
|
73
|
-
maybeCb?: (item: Item, level: number) => T | void,
|
72
|
+
itemOrCb: Item | ((item: Item, level: number) => T | undefined | void),
|
73
|
+
maybeCb?: (item: Item, level: number) => T | undefined | void,
|
74
74
|
): T | undefined {
|
75
75
|
if (typeof itemOrCb === 'function') {
|
76
76
|
return traverse<T>(this, itemOrCb);
|
@@ -83,7 +83,7 @@ export class Tree implements Item {
|
|
83
83
|
* Return the closest item.
|
84
84
|
*/
|
85
85
|
find(pos: number): Item | undefined {
|
86
|
-
return this.traverse((item) => (item.lineRange.from <= pos && item.lineRange.to >= pos ? item : undefined));
|
86
|
+
return this.traverse<Item>((item) => (item.lineRange.from <= pos && item.lineRange.to >= pos ? item : undefined));
|
87
87
|
}
|
88
88
|
|
89
89
|
/**
|
@@ -148,6 +148,8 @@ export const traverse = <T = any>(root: Item, cb: (item: Item, level: number) =>
|
|
148
148
|
return value;
|
149
149
|
}
|
150
150
|
}
|
151
|
+
|
152
|
+
return undefined;
|
151
153
|
};
|
152
154
|
|
153
155
|
return t(root, root.type === 'root' ? -1 : 0);
|