@dxos/react-ui-editor 0.8.3-main.7f5a14c → 0.8.3
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 +371 -375
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +502 -511
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +371 -375
- 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/Popover/RefDropdownMenu.d.ts.map +1 -1
- package/dist/types/src/components/Popover/RefPopover.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/floating-menu.d.ts +7 -0
- 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/index.d.ts +0 -1
- package/dist/types/src/extensions/index.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/stories/CommandMenu.stories.d.ts +5 -4
- package/dist/types/src/stories/CommandMenu.stories.d.ts.map +1 -1
- 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 -31
- package/src/components/EditorToolbar/EditorToolbar.tsx +5 -9
- package/src/components/Popover/RefDropdownMenu.tsx +5 -3
- package/src/components/Popover/RefPopover.tsx +5 -3
- package/src/defaults.ts +0 -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/floating-menu.ts +133 -0
- 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/index.ts +0 -1
- package/src/extensions/markdown/bundle.ts +0 -2
- package/src/extensions/outliner/tree.test.ts +13 -10
- package/src/extensions/outliner/tree.ts +5 -3
- package/src/extensions/preview/preview.ts +11 -86
- package/src/stories/Command.stories.tsx +1 -1
- package/src/stories/CommandMenu.stories.tsx +35 -19
- package/src/stories/Preview.stories.tsx +134 -57
- 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 +0 -47
- package/dist/types/src/extensions/command/menu.d.ts.map +0 -1
- package/dist/types/src/extensions/placeholder.d.ts +0 -4
- package/dist/types/src/extensions/placeholder.d.ts.map +0 -1
- package/src/extensions/command/menu.ts +0 -439
- package/src/extensions/placeholder.ts +0 -82
@@ -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
|
+
};
|
package/src/extensions/index.ts
CHANGED
@@ -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
|
];
|
@@ -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);
|
@@ -16,8 +16,6 @@ import {
|
|
16
16
|
import { Decoration, type DecorationSet, EditorView, WidgetType } from '@codemirror/view';
|
17
17
|
import { type SyntaxNode } from '@lezer/common';
|
18
18
|
|
19
|
-
import { type RenderCallback } from '../../types';
|
20
|
-
|
21
19
|
export type PreviewLinkRef = {
|
22
20
|
suggest?: boolean;
|
23
21
|
block?: boolean;
|
@@ -31,32 +29,13 @@ export type PreviewLinkTarget = {
|
|
31
29
|
object?: any;
|
32
30
|
};
|
33
31
|
|
34
|
-
|
35
|
-
| {
|
36
|
-
type: 'insert';
|
37
|
-
link: PreviewLinkRef;
|
38
|
-
target: PreviewLinkTarget;
|
39
|
-
}
|
40
|
-
| {
|
41
|
-
type: 'delete';
|
42
|
-
link: PreviewLinkRef;
|
43
|
-
};
|
44
|
-
|
32
|
+
// TODO(wittjosiah): Remove.
|
45
33
|
// TODO(burdon): Handle error.
|
46
34
|
export type PreviewLookup = (link: PreviewLinkRef) => Promise<PreviewLinkTarget | null | undefined>;
|
47
35
|
|
48
|
-
export type PreviewActionHandler = (action: PreviewAction) => void;
|
49
|
-
|
50
|
-
export type PreviewRenderProps = {
|
51
|
-
readonly: boolean;
|
52
|
-
link: PreviewLinkRef;
|
53
|
-
onAction: PreviewActionHandler;
|
54
|
-
onLookup?: PreviewLookup;
|
55
|
-
};
|
56
|
-
|
57
36
|
export type PreviewOptions = {
|
58
|
-
|
59
|
-
|
37
|
+
addBlockContainer?: (link: PreviewLinkRef, el: HTMLElement) => void;
|
38
|
+
removeBlockContainer?: (link: PreviewLinkRef) => void;
|
60
39
|
};
|
61
40
|
|
62
41
|
/**
|
@@ -74,14 +53,6 @@ export const preview = (options: PreviewOptions = {}): Extension => {
|
|
74
53
|
EditorView.atomicRanges.of((view) => view.state.field(field)),
|
75
54
|
],
|
76
55
|
}),
|
77
|
-
|
78
|
-
EditorView.theme({
|
79
|
-
'.cm-preview-block': {
|
80
|
-
'--dx-card-spacing-inline': 'var(--dx-trimMd)',
|
81
|
-
'--dx-card-spacing-block': 'var(--dx-trimMd)',
|
82
|
-
marginInline: 'calc(-1*var(--dx-trimMd))',
|
83
|
-
},
|
84
|
-
}),
|
85
56
|
];
|
86
57
|
};
|
87
58
|
|
@@ -92,7 +63,7 @@ export const preview = (options: PreviewOptions = {}): Extension => {
|
|
92
63
|
* ![Label][dxn:echo:123] Block reference
|
93
64
|
* ![Label][?dxn:echo:123] Suggestion
|
94
65
|
*/
|
95
|
-
const getLinkRef = (state: EditorState, node: SyntaxNode): PreviewLinkRef | undefined => {
|
66
|
+
export const getLinkRef = (state: EditorState, node: SyntaxNode): PreviewLinkRef | undefined => {
|
96
67
|
const mark = node.getChild('LinkMark');
|
97
68
|
const label = node.getChild('LinkLabel');
|
98
69
|
if (mark && label) {
|
@@ -141,7 +112,7 @@ const buildDecorations = (state: EditorState, options: PreviewOptions) => {
|
|
141
112
|
//
|
142
113
|
case 'Image': {
|
143
114
|
const link = getLinkRef(state, node.node);
|
144
|
-
if (options.
|
115
|
+
if (options.addBlockContainer && options.removeBlockContainer && link) {
|
145
116
|
builder.add(
|
146
117
|
node.from,
|
147
118
|
node.to,
|
@@ -211,58 +182,12 @@ class PreviewBlockWidget extends WidgetType {
|
|
211
182
|
|
212
183
|
override toDOM(view: EditorView): HTMLDivElement {
|
213
184
|
const root = document.createElement('div');
|
214
|
-
root.classList.add('cm-preview-block');
|
215
|
-
|
216
|
-
// TODO(burdon): Inject handler.
|
217
|
-
const handleAction: PreviewActionHandler = (action) => {
|
218
|
-
const pos = view.posAtDOM(root);
|
219
|
-
const node = syntaxTree(view.state).resolve(pos + 1).node.parent;
|
220
|
-
if (!node) {
|
221
|
-
return;
|
222
|
-
}
|
223
|
-
|
224
|
-
const link = getLinkRef(view.state, node);
|
225
|
-
if (link?.ref !== action.link.ref) {
|
226
|
-
return;
|
227
|
-
}
|
228
|
-
|
229
|
-
switch (action.type) {
|
230
|
-
// TODO(burdon): Should we dispatch to the view or mutate the document? (i.e., handle externally?)
|
231
|
-
// Insert ref text.
|
232
|
-
case 'insert': {
|
233
|
-
view.dispatch({
|
234
|
-
changes: {
|
235
|
-
from: node.from,
|
236
|
-
to: node.to,
|
237
|
-
insert: action.target.text,
|
238
|
-
},
|
239
|
-
});
|
240
|
-
break;
|
241
|
-
}
|
242
|
-
// Remove ref.
|
243
|
-
case 'delete': {
|
244
|
-
view.dispatch({
|
245
|
-
changes: {
|
246
|
-
from: node.from,
|
247
|
-
to: node.to,
|
248
|
-
},
|
249
|
-
});
|
250
|
-
break;
|
251
|
-
}
|
252
|
-
}
|
253
|
-
};
|
254
|
-
|
255
|
-
this._options.renderBlock!(
|
256
|
-
root,
|
257
|
-
{
|
258
|
-
readonly: view.state.readOnly,
|
259
|
-
link: this._link,
|
260
|
-
onAction: handleAction,
|
261
|
-
onLookup: this._options.onLookup,
|
262
|
-
},
|
263
|
-
view,
|
264
|
-
);
|
265
|
-
|
185
|
+
root.classList.add('cm-preview-block', 'density-coarse');
|
186
|
+
this._options.addBlockContainer?.(this._link, root);
|
266
187
|
return root;
|
267
188
|
}
|
189
|
+
|
190
|
+
override destroy() {
|
191
|
+
this._options.removeBlockContainer?.(this._link);
|
192
|
+
}
|
268
193
|
}
|
@@ -16,24 +16,25 @@ import { withLayout, withTheme, type Meta } from '@dxos/storybook-utils';
|
|
16
16
|
|
17
17
|
import { EditorStory, names } from './components';
|
18
18
|
import {
|
19
|
-
coreSlashCommands,
|
20
|
-
filterItems,
|
21
|
-
RefPopover,
|
22
|
-
type CommandMenuGroup,
|
23
19
|
CommandMenu,
|
20
|
+
type CommandMenuGroup,
|
24
21
|
type CommandMenuItem,
|
22
|
+
RefPopover,
|
23
|
+
coreSlashCommands,
|
24
|
+
filterItems,
|
25
25
|
insertAtCursor,
|
26
26
|
insertAtLineStart,
|
27
27
|
linkSlashCommands,
|
28
28
|
} from '../components';
|
29
29
|
import { useCommandMenu, type UseCommandMenuOptions } from '../extensions';
|
30
30
|
import { str } from '../testing';
|
31
|
+
import { createElement } from '../util';
|
31
32
|
|
32
33
|
const generator: ValueGenerator = faker as any;
|
33
34
|
|
34
|
-
type
|
35
|
+
type StoryProps = Omit<UseCommandMenuOptions, 'viewRef'> & { text: string };
|
35
36
|
|
36
|
-
const
|
37
|
+
const DefaultStory = ({ text, ...options }: StoryProps) => {
|
37
38
|
const viewRef = useRef<EditorView>();
|
38
39
|
const { commandMenu, groupsRef, currentItem, onSelect, ...props } = useCommandMenu({ viewRef, ...options });
|
39
40
|
|
@@ -62,30 +63,45 @@ const groups: CommandMenuGroup[] = [
|
|
62
63
|
},
|
63
64
|
];
|
64
65
|
|
65
|
-
const meta: Meta<
|
66
|
+
const meta: Meta<StoryProps> = {
|
66
67
|
title: 'ui/react-ui-editor/CommandMenu',
|
67
68
|
decorators: [withTheme, withLayout({ fullscreen: true })],
|
68
|
-
render: (args) => <
|
69
|
-
parameters: {
|
69
|
+
render: (args) => <DefaultStory {...args} />,
|
70
|
+
parameters: {
|
71
|
+
layout: 'fullscreen',
|
72
|
+
},
|
70
73
|
};
|
71
74
|
|
72
75
|
export default meta;
|
73
76
|
|
74
|
-
|
77
|
+
type Story = StoryObj<StoryProps>;
|
78
|
+
|
79
|
+
// TODO(burdon): Not working.
|
80
|
+
export const Slash: Story = {
|
75
81
|
args: {
|
76
|
-
trigger: '/',
|
77
|
-
getGroups: (query) =>
|
78
|
-
filterItems(groups, (item) =>
|
79
|
-
query ? (item.label as string).toLowerCase().includes(query.toLowerCase()) : true,
|
80
|
-
),
|
81
82
|
text: str('# Slash', '', names.join(' '), ''),
|
83
|
+
trigger: '/',
|
84
|
+
placeholder: {
|
85
|
+
content: () => {
|
86
|
+
return createElement('div', undefined, [
|
87
|
+
createElement('span', { text: 'Press' }),
|
88
|
+
createElement('span', { className: 'border border-separator rounded-sm mx-1 px-1', text: '/' }),
|
89
|
+
createElement('span', { text: 'for commands' }),
|
90
|
+
]);
|
91
|
+
},
|
92
|
+
},
|
93
|
+
getMenu: (text) => {
|
94
|
+
return filterItems(groups, (item) =>
|
95
|
+
text ? (item.label as string).toLowerCase().includes(text.toLowerCase()) : true,
|
96
|
+
);
|
97
|
+
},
|
82
98
|
},
|
83
99
|
};
|
84
100
|
|
85
|
-
export const Link:
|
101
|
+
export const Link: Story = {
|
86
102
|
render: (args) => {
|
87
103
|
const { space } = useClientProvider();
|
88
|
-
const
|
104
|
+
const getMenu = useCallback(
|
89
105
|
async (trigger: string, query?: string): Promise<CommandMenuGroup[]> => {
|
90
106
|
if (trigger === '/') {
|
91
107
|
return filterItems(groups, (item) =>
|
@@ -121,11 +137,11 @@ export const Link: StoryObj<Args> = {
|
|
121
137
|
[space],
|
122
138
|
);
|
123
139
|
|
124
|
-
return <
|
140
|
+
return <DefaultStory {...args} getMenu={getMenu} />;
|
125
141
|
},
|
126
142
|
args: {
|
127
|
-
trigger: ['/', '@'],
|
128
143
|
text: str('# Link', '', names.join(' '), ''),
|
144
|
+
trigger: ['/', '@'],
|
129
145
|
},
|
130
146
|
decorators: [
|
131
147
|
withClientProvider({
|