@milkdown/plugin-slash 4.14.2 → 5.1.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/README.md +42 -59
- package/lib/config.d.ts +20 -2
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +112 -81
- package/lib/config.js.map +1 -1
- package/lib/index.d.ts +22 -16
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +13 -18
- package/lib/index.js.map +1 -1
- package/lib/item.d.ts +2 -5
- package/lib/item.d.ts.map +1 -1
- package/lib/item.js +0 -2
- package/lib/item.js.map +1 -1
- package/lib/prose-plugin/dropdown.d.ts +6 -2
- package/lib/prose-plugin/dropdown.d.ts.map +1 -1
- package/lib/prose-plugin/dropdown.js +16 -23
- package/lib/prose-plugin/dropdown.js.map +1 -1
- package/lib/prose-plugin/index.d.ts +3 -4
- package/lib/prose-plugin/index.d.ts.map +1 -1
- package/lib/prose-plugin/index.js +4 -6
- package/lib/prose-plugin/index.js.map +1 -1
- package/lib/prose-plugin/input.d.ts +1 -2
- package/lib/prose-plugin/input.d.ts.map +1 -1
- package/lib/prose-plugin/input.js +18 -16
- package/lib/prose-plugin/input.js.map +1 -1
- package/lib/prose-plugin/props.d.ts +3 -3
- package/lib/prose-plugin/props.d.ts.map +1 -1
- package/lib/prose-plugin/props.js +21 -27
- package/lib/prose-plugin/props.js.map +1 -1
- package/lib/prose-plugin/status.d.ts +6 -12
- package/lib/prose-plugin/status.d.ts.map +1 -1
- package/lib/prose-plugin/status.js +16 -24
- package/lib/prose-plugin/status.js.map +1 -1
- package/lib/prose-plugin/view.d.ts +1 -2
- package/lib/prose-plugin/view.d.ts.map +1 -1
- package/lib/prose-plugin/view.js +6 -10
- package/lib/prose-plugin/view.js.map +1 -1
- package/lib/utility.d.ts +1 -2
- package/lib/utility.d.ts.map +1 -1
- package/lib/utility.js +0 -1
- package/lib/utility.js.map +1 -1
- package/package.json +3 -3
- package/src/config.ts +134 -82
- package/src/index.ts +17 -34
- package/src/item.ts +2 -7
- package/src/prose-plugin/dropdown.ts +22 -25
- package/src/prose-plugin/index.ts +7 -13
- package/src/prose-plugin/input.ts +19 -17
- package/src/prose-plugin/props.ts +26 -35
- package/src/prose-plugin/status.ts +19 -30
- package/src/prose-plugin/view.ts +6 -11
- package/src/utility.ts +1 -3
package/src/config.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
-
import { commandsCtx, themeToolCtx } from '@milkdown/core';
|
|
2
|
+
import { commandsCtx, schemaCtx, themeToolCtx } from '@milkdown/core';
|
|
3
|
+
import type { Ctx } from '@milkdown/ctx';
|
|
3
4
|
import {
|
|
4
5
|
InsertHr,
|
|
5
6
|
InsertImage,
|
|
@@ -11,86 +12,137 @@ import {
|
|
|
11
12
|
WrapInBulletList,
|
|
12
13
|
WrapInOrderedList,
|
|
13
14
|
} from '@milkdown/preset-gfm';
|
|
15
|
+
import { EditorState, Node } from '@milkdown/prose';
|
|
14
16
|
|
|
15
|
-
import
|
|
16
|
-
import { createDropdownItem
|
|
17
|
+
import { WrappedAction } from './item';
|
|
18
|
+
import { createDropdownItem } from './utility';
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
20
|
+
type Nullable<T> = T | null | undefined;
|
|
21
|
+
|
|
22
|
+
export type StatusConfig = {
|
|
23
|
+
placeholder?: Nullable<string>;
|
|
24
|
+
actions?: Nullable<WrappedAction[]>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type StatusConfigBuilderParams = {
|
|
28
|
+
content: string;
|
|
29
|
+
isTopLevel: boolean;
|
|
30
|
+
parentNode: Node;
|
|
31
|
+
state: EditorState;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type StatusConfigBuilder = (params: StatusConfigBuilderParams) => Nullable<StatusConfig>;
|
|
35
|
+
|
|
36
|
+
export type Config = (ctx: Ctx) => StatusConfigBuilder;
|
|
37
|
+
|
|
38
|
+
export const defaultActions = (ctx: Ctx, input = '/'): WrappedAction[] => {
|
|
39
|
+
const { nodes } = ctx.get(schemaCtx);
|
|
40
|
+
const actions: Array<WrappedAction & { keyword: string[]; typeName: string }> = [
|
|
41
|
+
{
|
|
42
|
+
id: 'h1',
|
|
43
|
+
dom: createDropdownItem(ctx.get(themeToolCtx), 'Large Heading', 'h1'),
|
|
44
|
+
command: () => ctx.get(commandsCtx).call(TurnIntoHeading, 1),
|
|
45
|
+
keyword: ['h1', 'large heading'],
|
|
46
|
+
typeName: 'heading',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'h2',
|
|
50
|
+
dom: createDropdownItem(ctx.get(themeToolCtx), 'Medium Heading', 'h2'),
|
|
51
|
+
command: () => ctx.get(commandsCtx).call(TurnIntoHeading, 2),
|
|
52
|
+
keyword: ['h2', 'medium heading'],
|
|
53
|
+
typeName: 'heading',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'h3',
|
|
57
|
+
dom: createDropdownItem(ctx.get(themeToolCtx), 'Small Heading', 'h3'),
|
|
58
|
+
command: () => ctx.get(commandsCtx).call(TurnIntoHeading, 3),
|
|
59
|
+
keyword: ['h3', 'small heading'],
|
|
60
|
+
typeName: 'heading',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 'bulletList',
|
|
64
|
+
dom: createDropdownItem(ctx.get(themeToolCtx), 'Bullet List', 'bulletList'),
|
|
65
|
+
command: () => ctx.get(commandsCtx).call(WrapInBulletList),
|
|
66
|
+
keyword: ['bullet list', 'ul'],
|
|
67
|
+
typeName: 'bullet_list',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'orderedList',
|
|
71
|
+
dom: createDropdownItem(ctx.get(themeToolCtx), 'Ordered List', 'orderedList'),
|
|
72
|
+
command: () => ctx.get(commandsCtx).call(WrapInOrderedList),
|
|
73
|
+
keyword: ['ordered list', 'ol'],
|
|
74
|
+
typeName: 'ordered_list',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'taskList',
|
|
78
|
+
dom: createDropdownItem(ctx.get(themeToolCtx), 'Task List', 'taskList'),
|
|
79
|
+
command: () => ctx.get(commandsCtx).call(TurnIntoTaskList),
|
|
80
|
+
keyword: ['task list', 'task'],
|
|
81
|
+
typeName: 'task_list_item',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'image',
|
|
85
|
+
dom: createDropdownItem(ctx.get(themeToolCtx), 'Image', 'image'),
|
|
86
|
+
command: () => ctx.get(commandsCtx).call(InsertImage),
|
|
87
|
+
keyword: ['image'],
|
|
88
|
+
typeName: 'image',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: 'blockquote',
|
|
92
|
+
dom: createDropdownItem(ctx.get(themeToolCtx), 'Quote', 'quote'),
|
|
93
|
+
command: () => ctx.get(commandsCtx).call(WrapInBlockquote),
|
|
94
|
+
keyword: ['quote', 'blockquote'],
|
|
95
|
+
typeName: 'blockquote',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: 'table',
|
|
99
|
+
dom: createDropdownItem(ctx.get(themeToolCtx), 'Table', 'table'),
|
|
100
|
+
command: () => ctx.get(commandsCtx).call(InsertTable),
|
|
101
|
+
keyword: ['table'],
|
|
102
|
+
typeName: 'table',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: 'code',
|
|
106
|
+
dom: createDropdownItem(ctx.get(themeToolCtx), 'Code Fence', 'code'),
|
|
107
|
+
command: () => ctx.get(commandsCtx).call(TurnIntoCodeFence),
|
|
108
|
+
keyword: ['code'],
|
|
109
|
+
typeName: 'fence',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'divider',
|
|
113
|
+
dom: createDropdownItem(ctx.get(themeToolCtx), 'Divide Line', 'divider'),
|
|
114
|
+
command: () => ctx.get(commandsCtx).call(InsertHr),
|
|
115
|
+
keyword: ['divider', 'hr'],
|
|
116
|
+
typeName: 'hr',
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
const userInput = input.slice(1).toLocaleLowerCase();
|
|
121
|
+
|
|
122
|
+
return actions
|
|
123
|
+
.filter((action) => !!nodes[action.typeName] && action.keyword.some((keyword) => keyword.includes(userInput)))
|
|
124
|
+
.map(({ keyword, typeName, ...action }) => action);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const defaultConfig: Config = (ctx) => {
|
|
128
|
+
return ({ content, isTopLevel }) => {
|
|
129
|
+
if (!isTopLevel) return null;
|
|
130
|
+
|
|
131
|
+
if (!content) {
|
|
132
|
+
return { placeholder: 'Type / to use the slash commands...' };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (content.startsWith('/')) {
|
|
136
|
+
return content === '/'
|
|
137
|
+
? {
|
|
138
|
+
placeholder: 'Type to filter...',
|
|
139
|
+
actions: defaultActions(ctx),
|
|
140
|
+
}
|
|
141
|
+
: {
|
|
142
|
+
actions: defaultActions(ctx, content),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return null;
|
|
147
|
+
};
|
|
148
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,46 +1,29 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
-
import {
|
|
3
|
-
import { AtomList, createProsePlugin, Utils } from '@milkdown/utils';
|
|
2
|
+
import { AtomList, createPlugin } from '@milkdown/utils';
|
|
4
3
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import type { Config } from './config';
|
|
5
|
+
import { defaultConfig } from './config';
|
|
7
6
|
import { createSlashPlugin } from './prose-plugin';
|
|
8
|
-
import { CursorStatus } from './prose-plugin/status';
|
|
9
7
|
|
|
10
|
-
export {
|
|
11
|
-
export {
|
|
12
|
-
export { createDropdownItem
|
|
13
|
-
|
|
14
|
-
export type SlashConfig = (utils: Utils) => WrappedAction[];
|
|
8
|
+
export type { Config, StatusConfig, StatusConfigBuilder, StatusConfigBuilderParams } from './config';
|
|
9
|
+
export { defaultActions, defaultConfig } from './config';
|
|
10
|
+
export { createDropdownItem } from './utility';
|
|
15
11
|
|
|
16
12
|
export type Options = {
|
|
17
|
-
|
|
18
|
-
config: SlashConfig;
|
|
19
|
-
placeholder: {
|
|
20
|
-
[CursorStatus.Empty]: string;
|
|
21
|
-
[CursorStatus.Slash]: string;
|
|
22
|
-
};
|
|
13
|
+
config: Config;
|
|
23
14
|
};
|
|
24
15
|
|
|
25
|
-
export const slashPlugin =
|
|
26
|
-
const slashConfig = options?.config ??
|
|
27
|
-
|
|
28
|
-
[CursorStatus.Empty]: 'Type / to use the slash commands...',
|
|
29
|
-
[CursorStatus.Slash]: 'Type to filter...',
|
|
30
|
-
...(options?.placeholder ?? {}),
|
|
31
|
-
};
|
|
32
|
-
const cfg = slashConfig(utils);
|
|
33
|
-
const shouldDisplay =
|
|
34
|
-
options?.shouldDisplay ??
|
|
35
|
-
((parent, state) => {
|
|
36
|
-
const isTopLevel = state.selection.$from.depth === 1;
|
|
37
|
-
return parent.node.childCount <= 1 && isTopLevel;
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const plugin = createSlashPlugin(utils, cfg, placeholder, shouldDisplay);
|
|
16
|
+
export const slashPlugin = createPlugin<string, Options>((utils, options) => {
|
|
17
|
+
const slashConfig = options?.config ?? defaultConfig;
|
|
18
|
+
|
|
41
19
|
return {
|
|
42
|
-
|
|
43
|
-
|
|
20
|
+
prosePlugins: (_, ctx) => {
|
|
21
|
+
const config = slashConfig(ctx);
|
|
22
|
+
|
|
23
|
+
const plugin = createSlashPlugin(utils, config);
|
|
24
|
+
|
|
25
|
+
return [plugin];
|
|
26
|
+
},
|
|
44
27
|
};
|
|
45
28
|
});
|
|
46
29
|
|
package/src/item.ts
CHANGED
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
-
import type { Command
|
|
2
|
+
import type { Command } from '@milkdown/prose';
|
|
3
3
|
|
|
4
4
|
import { cleanUpAndCreateNode } from './utility';
|
|
5
5
|
|
|
6
6
|
export type Action = {
|
|
7
7
|
id: string;
|
|
8
8
|
$: HTMLElement;
|
|
9
|
-
keyword: string[];
|
|
10
9
|
command: Command;
|
|
11
|
-
enable: (schema: Schema) => boolean;
|
|
12
10
|
};
|
|
13
11
|
|
|
14
|
-
export type WrappedAction = Pick<Action, '
|
|
15
|
-
enable: (schema: Schema) => boolean;
|
|
12
|
+
export type WrappedAction = Pick<Action, 'id'> & {
|
|
16
13
|
command: () => void;
|
|
17
14
|
dom: HTMLElement;
|
|
18
15
|
};
|
|
19
16
|
|
|
20
17
|
export const transformAction = (action: WrappedAction): Action => ({
|
|
21
18
|
id: action.id,
|
|
22
|
-
keyword: action.keyword,
|
|
23
19
|
$: action.dom,
|
|
24
20
|
command: cleanUpAndCreateNode(action.command),
|
|
25
|
-
enable: action.enable,
|
|
26
21
|
});
|
|
@@ -1,44 +1,41 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
2
|
import scrollIntoView from 'smooth-scroll-into-view-if-needed';
|
|
3
3
|
|
|
4
|
-
import { Action } from '../item';
|
|
5
4
|
import { Status } from './status';
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
type Listeners = {
|
|
7
|
+
mouseEnter: EventListener;
|
|
8
|
+
mouseLeave: EventListener;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const renderDropdown = (status: Status, dropdownElement: HTMLElement, listeners: Listeners): boolean => {
|
|
12
|
+
const { actions } = status.get();
|
|
9
13
|
|
|
10
|
-
if (!
|
|
14
|
+
if (!actions.length) {
|
|
11
15
|
dropdownElement.classList.add('hide');
|
|
12
16
|
return false;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (result) {
|
|
20
|
-
return true;
|
|
21
|
-
}
|
|
22
|
-
item.$.classList.add('hide');
|
|
23
|
-
return false;
|
|
24
|
-
})
|
|
25
|
-
.map((item) => {
|
|
26
|
-
item.$.classList.remove('hide');
|
|
27
|
-
return item;
|
|
28
|
-
});
|
|
19
|
+
dropdownElement.childNodes.forEach((child) => {
|
|
20
|
+
child.removeEventListener('mouseenter', listeners.mouseEnter);
|
|
21
|
+
child.removeEventListener('mouseleave', listeners.mouseLeave);
|
|
22
|
+
});
|
|
29
23
|
|
|
30
|
-
|
|
24
|
+
// Reset dropdownElement children
|
|
25
|
+
dropdownElement.textContent = '';
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
actions.forEach(({ $ }) => {
|
|
28
|
+
$.classList.remove('active');
|
|
29
|
+
$.addEventListener('mouseenter', listeners.mouseEnter);
|
|
30
|
+
$.addEventListener('mouseleave', listeners.mouseLeave);
|
|
31
|
+
dropdownElement.appendChild($);
|
|
32
|
+
});
|
|
36
33
|
|
|
37
34
|
dropdownElement.classList.remove('hide');
|
|
38
35
|
|
|
39
|
-
|
|
36
|
+
actions[0].$.classList.add('active');
|
|
40
37
|
requestAnimationFrame(() => {
|
|
41
|
-
scrollIntoView(
|
|
38
|
+
scrollIntoView(actions[0].$, {
|
|
42
39
|
scrollMode: 'if-needed',
|
|
43
40
|
block: 'nearest',
|
|
44
41
|
inline: 'nearest',
|
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
-
import {
|
|
2
|
+
import { Plugin, PluginKey } from '@milkdown/prose';
|
|
3
3
|
import { Utils } from '@milkdown/utils';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import type { StatusConfigBuilder } from '..';
|
|
6
6
|
import { createProps } from './props';
|
|
7
|
-
import { createStatus
|
|
7
|
+
import { createStatus } from './status';
|
|
8
8
|
import { createView } from './view';
|
|
9
9
|
|
|
10
10
|
export const key = 'MILKDOWN_PLUGIN_SLASH';
|
|
11
11
|
|
|
12
|
-
export const createSlashPlugin = (
|
|
13
|
-
|
|
14
|
-
items: WrappedAction[],
|
|
15
|
-
placeholder: Record<CursorStatus, string>,
|
|
16
|
-
shouldDisplay: (parent: NodeWithPos, state: EditorState) => boolean,
|
|
17
|
-
) => {
|
|
18
|
-
const status = createStatus();
|
|
19
|
-
const actions = items.map(transformAction);
|
|
12
|
+
export const createSlashPlugin = (utils: Utils, builder: StatusConfigBuilder) => {
|
|
13
|
+
const status = createStatus(builder);
|
|
20
14
|
|
|
21
15
|
return new Plugin({
|
|
22
16
|
key: new PluginKey(key),
|
|
23
|
-
props: createProps(status, utils
|
|
24
|
-
view: (view) => createView(status,
|
|
17
|
+
props: createProps(status, utils),
|
|
18
|
+
view: (view) => createView(status, view, utils),
|
|
25
19
|
});
|
|
26
20
|
};
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import { EditorView } from '@milkdown/prose';
|
|
4
4
|
import scrollIntoView from 'smooth-scroll-into-view-if-needed';
|
|
5
5
|
|
|
6
|
-
import { Action } from '../item';
|
|
7
6
|
import { Status } from './status';
|
|
8
7
|
|
|
9
8
|
export const createMouseManager = () => {
|
|
@@ -27,9 +26,10 @@ export const handleMouseMove = (mouseManager: MouseManager) => () => {
|
|
|
27
26
|
|
|
28
27
|
export const handleMouseEnter = (status: Status, mouseManager: MouseManager) => (e: MouseEvent) => {
|
|
29
28
|
if (mouseManager.isLock()) return;
|
|
30
|
-
const
|
|
29
|
+
const { actions } = status.get();
|
|
30
|
+
const active = actions.findIndex((x) => x.$.classList.contains('active'));
|
|
31
31
|
if (active >= 0) {
|
|
32
|
-
|
|
32
|
+
actions[active].$.classList.remove('active');
|
|
33
33
|
}
|
|
34
34
|
const { target } = e;
|
|
35
35
|
if (!(target instanceof HTMLElement)) return;
|
|
@@ -43,7 +43,7 @@ export const handleMouseLeave = () => (e: MouseEvent) => {
|
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
export const handleClick =
|
|
46
|
-
(status: Status,
|
|
46
|
+
(status: Status, view: EditorView, dropdownElement: HTMLElement) =>
|
|
47
47
|
(e: Event): void => {
|
|
48
48
|
const { target } = e;
|
|
49
49
|
if (!(target instanceof HTMLElement)) return;
|
|
@@ -54,11 +54,13 @@ export const handleClick =
|
|
|
54
54
|
e.preventDefault();
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
const
|
|
57
|
+
const { actions } = status.get();
|
|
58
|
+
|
|
59
|
+
const el = Object.values(actions).find(({ $ }) => $.contains(target));
|
|
58
60
|
if (!el) {
|
|
59
61
|
if (status.isEmpty()) return;
|
|
60
62
|
|
|
61
|
-
status.
|
|
63
|
+
status.clear();
|
|
62
64
|
dropdownElement.classList.add('hide');
|
|
63
65
|
stop();
|
|
64
66
|
|
|
@@ -75,18 +77,18 @@ export const handleKeydown =
|
|
|
75
77
|
if (!mouseManager.isLock()) mouseManager.lock();
|
|
76
78
|
|
|
77
79
|
const { key } = e;
|
|
78
|
-
if (
|
|
80
|
+
if (status.isEmpty()) return;
|
|
79
81
|
if (!['ArrowDown', 'ArrowUp', 'Enter', 'Escape'].includes(key)) return;
|
|
80
82
|
|
|
81
|
-
const {
|
|
83
|
+
const { actions } = status.get();
|
|
82
84
|
|
|
83
|
-
let active =
|
|
85
|
+
let active = actions.findIndex(({ $ }) => $.classList.contains('active'));
|
|
84
86
|
if (active < 0) active = 0;
|
|
85
87
|
|
|
86
88
|
const moveActive = (next: number) => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
scrollIntoView(
|
|
89
|
+
actions[active].$.classList.remove('active');
|
|
90
|
+
actions[next].$.classList.add('active');
|
|
91
|
+
scrollIntoView(actions[next].$, {
|
|
90
92
|
scrollMode: 'if-needed',
|
|
91
93
|
block: 'nearest',
|
|
92
94
|
inline: 'nearest',
|
|
@@ -94,14 +96,14 @@ export const handleKeydown =
|
|
|
94
96
|
};
|
|
95
97
|
|
|
96
98
|
if (key === 'ArrowDown') {
|
|
97
|
-
const next = active ===
|
|
99
|
+
const next = active === actions.length - 1 ? 0 : active + 1;
|
|
98
100
|
|
|
99
101
|
moveActive(next);
|
|
100
102
|
return;
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
if (key === 'ArrowUp') {
|
|
104
|
-
const next = active === 0 ?
|
|
106
|
+
const next = active === 0 ? actions.length - 1 : active - 1;
|
|
105
107
|
|
|
106
108
|
moveActive(next);
|
|
107
109
|
return;
|
|
@@ -110,11 +112,11 @@ export const handleKeydown =
|
|
|
110
112
|
if (key === 'Escape') {
|
|
111
113
|
if (status.isEmpty()) return;
|
|
112
114
|
|
|
113
|
-
status.
|
|
115
|
+
status.clear();
|
|
114
116
|
dropdownElement.classList.add('hide');
|
|
115
117
|
return;
|
|
116
118
|
}
|
|
117
119
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
actions[active].command(view.state, view.dispatch, view);
|
|
121
|
+
actions[active].$.classList.remove('active');
|
|
120
122
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
2
|
import { css } from '@emotion/css';
|
|
3
3
|
import { ThemeTool } from '@milkdown/core';
|
|
4
|
-
import { Decoration, DecorationSet, EditorState, EditorView, findParentNode
|
|
4
|
+
import { Decoration, DecorationSet, EditorState, EditorView, findParentNode } from '@milkdown/prose';
|
|
5
5
|
import { Utils } from '@milkdown/utils';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import type { Status } from './status';
|
|
8
8
|
|
|
9
9
|
export type Props = ReturnType<typeof createProps>;
|
|
10
10
|
|
|
@@ -29,19 +29,13 @@ const createSlashStyle = () => css`
|
|
|
29
29
|
}
|
|
30
30
|
`;
|
|
31
31
|
|
|
32
|
-
export const createProps = (
|
|
33
|
-
status: Status,
|
|
34
|
-
utils: Utils,
|
|
35
|
-
placeholder: Record<CursorStatus, string>,
|
|
36
|
-
shouldDisplay: (parent: NodeWithPos, state: EditorState) => boolean,
|
|
37
|
-
) => {
|
|
32
|
+
export const createProps = (status: Status, utils: Utils) => {
|
|
38
33
|
const emptyStyle = utils.getStyle(createEmptyStyle);
|
|
39
34
|
const slashStyle = utils.getStyle(createSlashStyle);
|
|
40
35
|
|
|
41
36
|
return {
|
|
42
37
|
handleKeyDown: (_: EditorView, event: Event) => {
|
|
43
|
-
|
|
44
|
-
if (cursorStatus !== CursorStatus.Slash || activeActions.length === 0) {
|
|
38
|
+
if (status.isEmpty()) {
|
|
45
39
|
return false;
|
|
46
40
|
}
|
|
47
41
|
if (!(event instanceof KeyboardEvent)) {
|
|
@@ -55,46 +49,43 @@ export const createProps = (
|
|
|
55
49
|
return true;
|
|
56
50
|
},
|
|
57
51
|
decorations: (state: EditorState) => {
|
|
58
|
-
const
|
|
52
|
+
const paragraph = findParentNode(({ type }) => type.name === 'paragraph')(state.selection);
|
|
59
53
|
|
|
60
|
-
if (
|
|
61
|
-
|
|
54
|
+
if (
|
|
55
|
+
!paragraph ||
|
|
56
|
+
paragraph.node.childCount > 1 ||
|
|
57
|
+
state.selection.$from.parentOffset !== paragraph.node.textContent.length
|
|
58
|
+
) {
|
|
59
|
+
status.clear();
|
|
62
60
|
return;
|
|
63
61
|
}
|
|
64
62
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
const { placeholder, actions } = status.update({
|
|
64
|
+
parentNode: state.selection.$from.node(state.selection.$from.depth - 1),
|
|
65
|
+
isTopLevel: state.selection.$from.depth === 1,
|
|
66
|
+
content: paragraph.node.textContent,
|
|
67
|
+
state,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (!placeholder) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
68
73
|
|
|
69
74
|
const createDecoration = (text: string, className: (string | undefined)[]) => {
|
|
70
|
-
const pos =
|
|
75
|
+
const pos = paragraph.pos;
|
|
71
76
|
return DecorationSet.create(state.doc, [
|
|
72
|
-
Decoration.node(pos, pos +
|
|
77
|
+
Decoration.node(pos, pos + paragraph.node.nodeSize, {
|
|
73
78
|
class: className.filter((x) => x).join(' '),
|
|
74
79
|
'data-text': text,
|
|
75
80
|
}),
|
|
76
81
|
]);
|
|
77
82
|
};
|
|
78
83
|
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
const text = placeholder[CursorStatus.Empty];
|
|
82
|
-
return createDecoration(text, [emptyStyle, 'empty-node']);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (isSlash) {
|
|
86
|
-
status.setSlash();
|
|
87
|
-
const text = placeholder[CursorStatus.Slash];
|
|
88
|
-
return createDecoration(text, [emptyStyle, slashStyle, 'empty-node', 'is-slash']);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (isSearch) {
|
|
92
|
-
status.setSlash(parent.node.textContent.slice(1));
|
|
93
|
-
return null;
|
|
84
|
+
if (actions.length) {
|
|
85
|
+
return createDecoration(placeholder, [emptyStyle, slashStyle, 'empty-node', 'is-slash']);
|
|
94
86
|
}
|
|
95
87
|
|
|
96
|
-
|
|
97
|
-
return null;
|
|
88
|
+
return createDecoration(placeholder, [emptyStyle, 'empty-node']);
|
|
98
89
|
},
|
|
99
90
|
};
|
|
100
91
|
};
|