@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.
@@ -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
- 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>;
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
- mouseManager.unlock();
25
- };
24
+ mouseManager.unlock()
25
+ }
26
26
 
27
27
  export const handleMouseEnter = (status: Status, mouseManager: MouseManager) => (e: MouseEvent) => {
28
- if (mouseManager.isLock()) return;
29
- const { actions } = status.get();
30
- const active = actions.findIndex((x) => x.$.classList.contains('active'));
31
- const active$ = actions[active];
32
- if (active$ && active >= 0) {
33
- active$.$.classList.remove('active');
34
- }
35
- const { target } = e;
36
- if (!(target instanceof HTMLElement)) return;
37
- target.classList.add('active');
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
- const { target } = e;
42
- if (!(target instanceof HTMLElement)) return;
43
- target.classList.remove('active');
44
- };
45
-
46
- export const handleClick =
47
- (status: Status, view: EditorView, dropdownElement: HTMLElement) =>
48
- (e: Event): void => {
49
- const { target } = e;
50
- if (!(target instanceof HTMLElement)) return;
51
- if (!view) return;
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
- e.stopPropagation();
55
- e.preventDefault();
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
- stop();
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
- moveActive(next);
113
- return;
114
- }
65
+ const el = Object.values(actions).find(({ $ }) => $.contains(target))
66
+ if (!el) {
67
+ if (status.isEmpty())
68
+ return
115
69
 
116
- if (key === 'Escape') {
117
- if (status.isEmpty()) return;
70
+ status.clear()
71
+ dropdownElement.classList.add('hide')
72
+ stop()
118
73
 
119
- status.clear();
120
- dropdownElement.classList.add('hide');
121
- return;
74
+ return
122
75
  }
123
76
 
124
- const active$ = actions[active];
125
- if (!active$) return;
126
- active$.command(view.state, view.dispatch, view);
127
- active$.$.classList.remove('active');
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, ThemeColor, ThemeFont, ThemeManager } from '@milkdown/core';
3
- import { findParentNode } from '@milkdown/prose';
4
- import { EditorState } from '@milkdown/prose/state';
5
- import { Decoration, DecorationSet, EditorView } from '@milkdown/prose/view';
6
- import { ThemeUtils } from '@milkdown/utils';
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
- const palette = (color: Color, opacity = 1) => themeManager.get(ThemeColor, [color, opacity]);
14
- const typography = themeManager.get(ThemeFont, 'typography');
15
+ const palette = (color: Color, opacity = 1) => themeManager.get(ThemeColor, [color, opacity])
16
+ const typography = themeManager.get(ThemeFont, 'typography')
15
17
 
16
- return css`
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
- return {
40
- handleKeyDown: (_: EditorView, event: Event) => {
41
- if (status.isEmpty()) {
42
- return false;
43
- }
44
- if (!(event instanceof KeyboardEvent)) {
45
- return false;
46
- }
47
-
48
- if (!['ArrowUp', 'ArrowDown', 'Enter'].includes(event.key)) {
49
- return false;
50
- }
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
-
86
- const createDecoration = (text: string, className: (string | undefined)[]) => {
87
- const pos = paragraph.pos;
88
- return DecorationSet.create(state.doc, [
89
- Decoration.node(pos, pos + paragraph.node.nodeSize, {
90
- class: className.filter((x) => x).join(' '),
91
- 'data-text': text,
92
- }),
93
- ]);
94
- };
95
-
96
- const emptyStyle = utils.getStyle((emotion) => createEmptyStyle(utils.themeManager, emotion));
97
- const slashStyle = utils.getStyle((emotion) => createSlashStyle(utils.themeManager, emotion));
98
-
99
- if (actions.length) {
100
- return createDecoration(placeholder, [emptyStyle, slashStyle, 'empty-node', 'is-slash']);
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, transformAction } from '../item';
2
+ import type { StatusConfigBuilder, StatusConfigBuilderParams } from '..'
3
+ import type { Action } from '../item'
4
+ import { transformAction } from '../item'
4
5
 
5
- export type StatusCtx = {
6
- placeholder: string | null;
7
- actions: Action[];
8
- };
6
+ export interface StatusCtx {
7
+ placeholder: string | null
8
+ actions: Action[]
9
+ }
9
10
 
10
11
  const createStatusCtx = (): StatusCtx => {
11
- return {
12
- placeholder: null,
13
- actions: [],
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
- const statusCtx = createStatusCtx();
21
+ const statusCtx = createStatusCtx()
21
22
 
22
- return {
23
- get: () => statusCtx,
24
- clear: () => {
25
- statusCtx.placeholder = null;
26
- statusCtx.actions = [];
27
- },
28
- update: (builderParams: StatusConfigBuilderParams) => {
29
- const config = builder(builderParams);
30
- statusCtx.placeholder = config?.placeholder ?? null;
31
- statusCtx.actions = (config?.actions ?? []).map(transformAction);
32
- return statusCtx;
33
- },
34
- isEmpty: () => statusCtx.actions.length === 0,
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
+ }