@milkdown/plugin-slash 6.5.0 → 6.5.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/lib/config.d.ts +11 -11
- package/lib/config.d.ts.map +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.es.js +170 -161
- package/lib/index.es.js.map +1 -1
- package/lib/item.d.ts +3 -3
- package/lib/item.d.ts.map +1 -1
- package/lib/prose-plugin/dropdown.d.ts +3 -3
- package/lib/prose-plugin/dropdown.d.ts.map +1 -1
- package/lib/prose-plugin/index.d.ts +2 -2
- package/lib/prose-plugin/index.d.ts.map +1 -1
- package/lib/prose-plugin/input.d.ts +3 -3
- package/lib/prose-plugin/input.d.ts.map +1 -1
- package/lib/prose-plugin/props.d.ts +5 -4
- package/lib/prose-plugin/props.d.ts.map +1 -1
- package/lib/prose-plugin/status.d.ts +5 -5
- package/lib/prose-plugin/status.d.ts.map +1 -1
- package/lib/prose-plugin/view.d.ts +4 -4
- package/lib/prose-plugin/view.d.ts.map +1 -1
- package/lib/style.d.ts +1 -1
- package/lib/style.d.ts.map +1 -1
- package/lib/utility.d.ts +3 -3
- package/lib/utility.d.ts.map +1 -1
- package/package.json +15 -15
- package/src/config.ts +126 -121
- package/src/index.ts +25 -24
- package/src/item.ts +14 -14
- package/src/prose-plugin/dropdown.ts +45 -45
- package/src/prose-plugin/index.ts +19 -18
- package/src/prose-plugin/input.ts +122 -108
- package/src/prose-plugin/props.ts +79 -81
- package/src/prose-plugin/status.ts +29 -28
- package/src/prose-plugin/view.ts +104 -80
- package/src/style.ts +21 -18
- package/src/utility.ts +63 -64
|
@@ -1,128 +1,142 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
2
|
|
|
3
|
-
import { EditorView } from '@milkdown/prose/view'
|
|
4
|
-
import scrollIntoView from 'smooth-scroll-into-view-if-needed'
|
|
3
|
+
import type { EditorView } from '@milkdown/prose/view'
|
|
4
|
+
import scrollIntoView from 'smooth-scroll-into-view-if-needed'
|
|
5
5
|
|
|
6
|
-
import { Status } from './status'
|
|
6
|
+
import type { Status } from './status'
|
|
7
7
|
|
|
8
8
|
export const createMouseManager = () => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
export type MouseManager = ReturnType<typeof createMouseManager
|
|
9
|
+
let mouseLock = false
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
isLock: () => mouseLock,
|
|
13
|
+
lock: () => {
|
|
14
|
+
mouseLock = true
|
|
15
|
+
},
|
|
16
|
+
unlock: () => {
|
|
17
|
+
mouseLock = false
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export type MouseManager = ReturnType<typeof createMouseManager>
|
|
22
22
|
|
|
23
23
|
export const handleMouseMove = (mouseManager: MouseManager) => () => {
|
|
24
|
-
|
|
25
|
-
}
|
|
24
|
+
mouseManager.unlock()
|
|
25
|
+
}
|
|
26
26
|
|
|
27
27
|
export const handleMouseEnter = (status: Status, mouseManager: MouseManager) => (e: MouseEvent) => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
if (mouseManager.isLock())
|
|
29
|
+
return
|
|
30
|
+
const { actions } = status.get()
|
|
31
|
+
const active = actions.findIndex(x => x.$.classList.contains('active'))
|
|
32
|
+
const active$ = actions[active]
|
|
33
|
+
if (active$ && active >= 0)
|
|
34
|
+
active$.$.classList.remove('active')
|
|
35
|
+
|
|
36
|
+
const { target } = e
|
|
37
|
+
if (!(target instanceof HTMLElement))
|
|
38
|
+
return
|
|
39
|
+
target.classList.add('active')
|
|
40
|
+
}
|
|
39
41
|
|
|
40
42
|
export const handleMouseLeave = () => (e: MouseEvent) => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (!
|
|
43
|
+
const { target } = e
|
|
44
|
+
if (!(target instanceof HTMLElement))
|
|
45
|
+
return
|
|
46
|
+
target.classList.remove('active')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const handleClick
|
|
50
|
+
= (status: Status, view: EditorView, dropdownElement: HTMLElement) =>
|
|
51
|
+
(e: Event): void => {
|
|
52
|
+
const { target } = e
|
|
53
|
+
if (!(target instanceof HTMLElement))
|
|
54
|
+
return
|
|
55
|
+
if (!view)
|
|
56
|
+
return
|
|
52
57
|
|
|
53
58
|
const stop = () => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const { actions } = status.get();
|
|
59
|
-
|
|
60
|
-
const el = Object.values(actions).find(({ $ }) => $.contains(target));
|
|
61
|
-
if (!el) {
|
|
62
|
-
if (status.isEmpty()) return;
|
|
63
|
-
|
|
64
|
-
status.clear();
|
|
65
|
-
dropdownElement.classList.add('hide');
|
|
66
|
-
stop();
|
|
67
|
-
|
|
68
|
-
return;
|
|
59
|
+
e.stopPropagation()
|
|
60
|
+
e.preventDefault()
|
|
69
61
|
}
|
|
70
62
|
|
|
71
|
-
|
|
72
|
-
el.command(view.state, view.dispatch, view);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
export const handleKeydown =
|
|
76
|
-
(status: Status, view: EditorView, dropdownElement: HTMLElement, mouseManager: MouseManager) => (e: Event) => {
|
|
77
|
-
if (!(e instanceof KeyboardEvent)) return;
|
|
78
|
-
if (!mouseManager.isLock()) mouseManager.lock();
|
|
79
|
-
|
|
80
|
-
const { key } = e;
|
|
81
|
-
if (status.isEmpty()) return;
|
|
82
|
-
if (!['ArrowDown', 'ArrowUp', 'Enter', 'Escape'].includes(key)) return;
|
|
83
|
-
|
|
84
|
-
const { actions } = status.get();
|
|
85
|
-
|
|
86
|
-
let active = actions.findIndex(({ $ }) => $.classList.contains('active'));
|
|
87
|
-
if (active < 0) active = 0;
|
|
88
|
-
|
|
89
|
-
const moveActive = (next: number) => {
|
|
90
|
-
const active$ = actions[active];
|
|
91
|
-
const next$ = actions[next];
|
|
92
|
-
if (!active$ || !next$) return;
|
|
93
|
-
active$.$.classList.remove('active');
|
|
94
|
-
next$.$.classList.add('active');
|
|
95
|
-
scrollIntoView(next$.$, {
|
|
96
|
-
scrollMode: 'if-needed',
|
|
97
|
-
block: 'nearest',
|
|
98
|
-
inline: 'nearest',
|
|
99
|
-
});
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
if (key === 'ArrowDown') {
|
|
103
|
-
const next = active === actions.length - 1 ? 0 : active + 1;
|
|
104
|
-
|
|
105
|
-
moveActive(next);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (key === 'ArrowUp') {
|
|
110
|
-
const next = active === 0 ? actions.length - 1 : active - 1;
|
|
63
|
+
const { actions } = status.get()
|
|
111
64
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
65
|
+
const el = Object.values(actions).find(({ $ }) => $.contains(target))
|
|
66
|
+
if (!el) {
|
|
67
|
+
if (status.isEmpty())
|
|
68
|
+
return
|
|
115
69
|
|
|
116
|
-
|
|
117
|
-
|
|
70
|
+
status.clear()
|
|
71
|
+
dropdownElement.classList.add('hide')
|
|
72
|
+
stop()
|
|
118
73
|
|
|
119
|
-
|
|
120
|
-
dropdownElement.classList.add('hide');
|
|
121
|
-
return;
|
|
74
|
+
return
|
|
122
75
|
}
|
|
123
76
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
77
|
+
stop()
|
|
78
|
+
el.command(view.state, view.dispatch, view)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const handleKeydown
|
|
82
|
+
= (status: Status, view: EditorView, dropdownElement: HTMLElement, mouseManager: MouseManager) => (e: Event) => {
|
|
83
|
+
if (!(e instanceof KeyboardEvent))
|
|
84
|
+
return
|
|
85
|
+
if (!mouseManager.isLock())
|
|
86
|
+
mouseManager.lock()
|
|
87
|
+
|
|
88
|
+
const { key } = e
|
|
89
|
+
if (status.isEmpty())
|
|
90
|
+
return
|
|
91
|
+
if (!['ArrowDown', 'ArrowUp', 'Enter', 'Escape'].includes(key))
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
const { actions } = status.get()
|
|
95
|
+
|
|
96
|
+
let active = actions.findIndex(({ $ }) => $.classList.contains('active'))
|
|
97
|
+
if (active < 0)
|
|
98
|
+
active = 0
|
|
99
|
+
|
|
100
|
+
const moveActive = (next: number) => {
|
|
101
|
+
const active$ = actions[active]
|
|
102
|
+
const next$ = actions[next]
|
|
103
|
+
if (!active$ || !next$)
|
|
104
|
+
return
|
|
105
|
+
active$.$.classList.remove('active')
|
|
106
|
+
next$.$.classList.add('active')
|
|
107
|
+
scrollIntoView(next$.$, {
|
|
108
|
+
scrollMode: 'if-needed',
|
|
109
|
+
block: 'nearest',
|
|
110
|
+
inline: 'nearest',
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (key === 'ArrowDown') {
|
|
115
|
+
const next = active === actions.length - 1 ? 0 : active + 1
|
|
116
|
+
|
|
117
|
+
moveActive(next)
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (key === 'ArrowUp') {
|
|
122
|
+
const next = active === 0 ? actions.length - 1 : active - 1
|
|
123
|
+
|
|
124
|
+
moveActive(next)
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (key === 'Escape') {
|
|
129
|
+
if (status.isEmpty())
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
status.clear()
|
|
133
|
+
dropdownElement.classList.add('hide')
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const active$ = actions[active]
|
|
138
|
+
if (!active$)
|
|
139
|
+
return
|
|
140
|
+
active$.command(view.state, view.dispatch, view)
|
|
141
|
+
active$.$.classList.remove('active')
|
|
142
|
+
}
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
-
import { Color, Emotion,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import type { Color, Emotion, ThemeManager } from '@milkdown/core'
|
|
3
|
+
import { ThemeColor, ThemeFont } from '@milkdown/core'
|
|
4
|
+
import { findParentNode } from '@milkdown/prose'
|
|
5
|
+
import type { EditorState } from '@milkdown/prose/state'
|
|
6
|
+
import type { EditorView } from '@milkdown/prose/view'
|
|
7
|
+
import { Decoration, DecorationSet } from '@milkdown/prose/view'
|
|
8
|
+
import type { ThemeUtils } from '@milkdown/utils'
|
|
7
9
|
|
|
8
|
-
import type { Status } from './status'
|
|
10
|
+
import type { Status } from './status'
|
|
9
11
|
|
|
10
|
-
export type Props = ReturnType<typeof createProps
|
|
12
|
+
export type Props = ReturnType<typeof createProps>
|
|
11
13
|
|
|
12
14
|
const createEmptyStyle = (themeManager: ThemeManager, { css }: Emotion) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
const palette = (color: Color, opacity = 1) => themeManager.get(ThemeColor, [color, opacity])
|
|
16
|
+
const typography = themeManager.get(ThemeFont, 'typography')
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
return css`
|
|
17
19
|
position: relative;
|
|
18
20
|
&::before {
|
|
19
21
|
position: absolute;
|
|
@@ -26,81 +28,77 @@ const createEmptyStyle = (themeManager: ThemeManager, { css }: Emotion) => {
|
|
|
26
28
|
display: flex;
|
|
27
29
|
align-items: center;
|
|
28
30
|
}
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
+
`
|
|
32
|
+
}
|
|
31
33
|
|
|
32
34
|
const createSlashStyle = (_: ThemeManager, { css }: Emotion) => css`
|
|
33
35
|
&::before {
|
|
34
36
|
left: 8px;
|
|
35
37
|
}
|
|
36
|
-
|
|
38
|
+
`
|
|
37
39
|
|
|
38
40
|
export const createProps = (status: Status, utils: ThemeUtils) => {
|
|
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
|
-
paragraph.node.
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return createDecoration(placeholder, [emptyStyle, 'empty-node']);
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
};
|
|
41
|
+
return {
|
|
42
|
+
handleKeyDown: (_: EditorView, event: Event) => {
|
|
43
|
+
if (status.isEmpty())
|
|
44
|
+
return false
|
|
45
|
+
|
|
46
|
+
if (!(event instanceof KeyboardEvent))
|
|
47
|
+
return false
|
|
48
|
+
|
|
49
|
+
if (!['ArrowUp', 'ArrowDown', 'Enter'].includes(event.key))
|
|
50
|
+
return false
|
|
51
|
+
|
|
52
|
+
return true
|
|
53
|
+
},
|
|
54
|
+
decorations: (state: EditorState) => {
|
|
55
|
+
const paragraph = findParentNode(({ type }) => type.name === 'paragraph')(state.selection)
|
|
56
|
+
const uploadPlugin = state.plugins.find(
|
|
57
|
+
x => (x as unknown as { key: string }).key === 'MILKDOWN_UPLOAD$',
|
|
58
|
+
)
|
|
59
|
+
const decorations: DecorationSet = uploadPlugin?.getState(state)
|
|
60
|
+
if (decorations != null && decorations.find(state.selection.from, state.selection.to).length > 0) {
|
|
61
|
+
status.clear()
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
!paragraph
|
|
67
|
+
|| paragraph.node.childCount > 1
|
|
68
|
+
|| state.selection.$from.parentOffset !== paragraph.node.textContent.length
|
|
69
|
+
|| (paragraph.node.firstChild && paragraph.node.firstChild.type.name !== 'text')
|
|
70
|
+
) {
|
|
71
|
+
status.clear()
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const { placeholder, actions } = status.update({
|
|
76
|
+
parentNode: state.selection.$from.node(state.selection.$from.depth - 1),
|
|
77
|
+
isTopLevel: state.selection.$from.depth === 1,
|
|
78
|
+
content: paragraph.node.textContent,
|
|
79
|
+
state,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
if (!placeholder)
|
|
83
|
+
return null
|
|
84
|
+
|
|
85
|
+
const createDecoration = (text: string, className: (string | undefined)[]) => {
|
|
86
|
+
const pos = paragraph.pos
|
|
87
|
+
return DecorationSet.create(state.doc, [
|
|
88
|
+
Decoration.node(pos, pos + paragraph.node.nodeSize, {
|
|
89
|
+
'class': className.filter(x => x).join(' '),
|
|
90
|
+
'data-text': text,
|
|
91
|
+
}),
|
|
92
|
+
])
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const emptyStyle = utils.getStyle(emotion => createEmptyStyle(utils.themeManager, emotion))
|
|
96
|
+
const slashStyle = utils.getStyle(emotion => createSlashStyle(utils.themeManager, emotion))
|
|
97
|
+
|
|
98
|
+
if (actions.length)
|
|
99
|
+
return createDecoration(placeholder, [emptyStyle, slashStyle, 'empty-node', 'is-slash'])
|
|
100
|
+
|
|
101
|
+
return createDecoration(placeholder, [emptyStyle, 'empty-node'])
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -1,36 +1,37 @@
|
|
|
1
1
|
/* Copyright 2021, Milkdown by Mirone. */
|
|
2
|
-
import { StatusConfigBuilder, StatusConfigBuilderParams } from '..'
|
|
3
|
-
import { Action
|
|
2
|
+
import type { StatusConfigBuilder, StatusConfigBuilderParams } from '..'
|
|
3
|
+
import type { Action } from '../item'
|
|
4
|
+
import { transformAction } from '../item'
|
|
4
5
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
6
|
+
export interface StatusCtx {
|
|
7
|
+
placeholder: string | null
|
|
8
|
+
actions: Action[]
|
|
9
|
+
}
|
|
9
10
|
|
|
10
11
|
const createStatusCtx = (): StatusCtx => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
12
|
+
return {
|
|
13
|
+
placeholder: null,
|
|
14
|
+
actions: [],
|
|
15
|
+
}
|
|
16
|
+
}
|
|
16
17
|
|
|
17
|
-
export type Status = ReturnType<typeof createStatus
|
|
18
|
+
export type Status = ReturnType<typeof createStatus>
|
|
18
19
|
|
|
19
20
|
export const createStatus = (builder: StatusConfigBuilder) => {
|
|
20
|
-
|
|
21
|
+
const statusCtx = createStatusCtx()
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
23
|
+
return {
|
|
24
|
+
get: () => statusCtx,
|
|
25
|
+
clear: () => {
|
|
26
|
+
statusCtx.placeholder = null
|
|
27
|
+
statusCtx.actions = []
|
|
28
|
+
},
|
|
29
|
+
update: (builderParams: StatusConfigBuilderParams) => {
|
|
30
|
+
const config = builder(builderParams)
|
|
31
|
+
statusCtx.placeholder = config?.placeholder ?? null
|
|
32
|
+
statusCtx.actions = (config?.actions ?? []).map(transformAction)
|
|
33
|
+
return statusCtx
|
|
34
|
+
},
|
|
35
|
+
isEmpty: () => statusCtx.actions.length === 0,
|
|
36
|
+
}
|
|
37
|
+
}
|