@milkdown/plugin-slash 6.5.4 → 7.0.0-next.0

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.
Files changed (42) hide show
  1. package/lib/index.d.ts +2 -13
  2. package/lib/index.d.ts.map +1 -1
  3. package/lib/index.es.js +80 -382
  4. package/lib/index.es.js.map +1 -1
  5. package/lib/slash-plugin.d.ts +11 -0
  6. package/lib/slash-plugin.d.ts.map +1 -0
  7. package/lib/slash-provider.d.ts +20 -0
  8. package/lib/slash-provider.d.ts.map +1 -0
  9. package/package.json +12 -8
  10. package/src/index.ts +2 -34
  11. package/src/slash-plugin.ts +31 -0
  12. package/src/slash-provider.ts +119 -0
  13. package/lib/config.d.ts +0 -21
  14. package/lib/config.d.ts.map +0 -1
  15. package/lib/item.d.ts +0 -12
  16. package/lib/item.d.ts.map +0 -1
  17. package/lib/prose-plugin/dropdown.d.ts +0 -8
  18. package/lib/prose-plugin/dropdown.d.ts.map +0 -1
  19. package/lib/prose-plugin/index.d.ts +0 -7
  20. package/lib/prose-plugin/index.d.ts.map +0 -1
  21. package/lib/prose-plugin/input.d.ts +0 -14
  22. package/lib/prose-plugin/input.d.ts.map +0 -1
  23. package/lib/prose-plugin/props.d.ts +0 -11
  24. package/lib/prose-plugin/props.d.ts.map +0 -1
  25. package/lib/prose-plugin/status.d.ts +0 -14
  26. package/lib/prose-plugin/status.d.ts.map +0 -1
  27. package/lib/prose-plugin/view.d.ts +0 -13
  28. package/lib/prose-plugin/view.d.ts.map +0 -1
  29. package/lib/style.d.ts +0 -3
  30. package/lib/style.d.ts.map +0 -1
  31. package/lib/utility.d.ts +0 -14
  32. package/lib/utility.d.ts.map +0 -1
  33. package/src/config.ts +0 -142
  34. package/src/item.ts +0 -21
  35. package/src/prose-plugin/dropdown.ts +0 -50
  36. package/src/prose-plugin/index.ts +0 -26
  37. package/src/prose-plugin/input.ts +0 -142
  38. package/src/prose-plugin/props.ts +0 -104
  39. package/src/prose-plugin/status.ts +0 -37
  40. package/src/prose-plugin/view.ts +0 -112
  41. package/src/style.ts +0 -76
  42. package/src/utility.ts +0 -85
@@ -1,26 +0,0 @@
1
- /* Copyright 2021, Milkdown by Mirone. */
2
- import { Plugin, PluginKey } from '@milkdown/prose/state'
3
- import type { ThemeUtils } from '@milkdown/utils'
4
-
5
- import type { StatusConfigBuilder } from '..'
6
- import { createProps } from './props'
7
- import { createStatus } from './status'
8
- import type { CalcPosition } from './view'
9
- import { createView } from './view'
10
-
11
- export const key = 'MILKDOWN_SLASH'
12
-
13
- export const createSlashPlugin = (
14
- utils: ThemeUtils,
15
- builder: StatusConfigBuilder,
16
- className: string,
17
- calcPosition: CalcPosition,
18
- ) => {
19
- const status = createStatus(builder)
20
-
21
- return new Plugin({
22
- key: new PluginKey(key),
23
- props: createProps(status, utils),
24
- view: view => createView(status, view, utils, className, calcPosition),
25
- })
26
- }
@@ -1,142 +0,0 @@
1
- /* Copyright 2021, Milkdown by Mirone. */
2
-
3
- import type { EditorView } from '@milkdown/prose/view'
4
- import scrollIntoView from 'smooth-scroll-into-view-if-needed'
5
-
6
- import type { Status } from './status'
7
-
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>
22
-
23
- export const handleMouseMove = (mouseManager: MouseManager) => () => {
24
- mouseManager.unlock()
25
- }
26
-
27
- export const handleMouseEnter = (status: Status, mouseManager: MouseManager) => (e: MouseEvent) => {
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
- }
41
-
42
- export const handleMouseLeave = () => (e: MouseEvent) => {
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
57
-
58
- const stop = () => {
59
- e.stopPropagation()
60
- e.preventDefault()
61
- }
62
-
63
- const { actions } = status.get()
64
-
65
- const el = Object.values(actions).find(({ $ }) => $.contains(target))
66
- if (!el) {
67
- if (status.isEmpty())
68
- return
69
-
70
- status.clear()
71
- dropdownElement.classList.add('hide')
72
- stop()
73
-
74
- return
75
- }
76
-
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,104 +0,0 @@
1
- /* Copyright 2021, Milkdown by Mirone. */
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'
9
-
10
- import type { Status } from './status'
11
-
12
- export type Props = ReturnType<typeof createProps>
13
-
14
- const createEmptyStyle = (themeManager: ThemeManager, { css }: Emotion) => {
15
- const palette = (color: Color, opacity = 1) => themeManager.get(ThemeColor, [color, opacity])
16
- const typography = themeManager.get(ThemeFont, 'typography')
17
-
18
- return css`
19
- position: relative;
20
- &::before {
21
- position: absolute;
22
- cursor: text;
23
- font-family: ${typography};
24
- font-size: 14px;
25
- color: ${palette('neutral', 0.6)};
26
- content: attr(data-text);
27
- height: 100%;
28
- display: flex;
29
- align-items: center;
30
- }
31
- `
32
- }
33
-
34
- const createSlashStyle = (_: ThemeManager, { css }: Emotion) => css`
35
- &::before {
36
- left: 8px;
37
- }
38
- `
39
-
40
- export const createProps = (status: Status, utils: ThemeUtils) => {
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,37 +0,0 @@
1
- /* Copyright 2021, Milkdown by Mirone. */
2
- import type { StatusConfigBuilder, StatusConfigBuilderParams } from '..'
3
- import type { Action } from '../item'
4
- import { transformAction } from '../item'
5
-
6
- export interface StatusCtx {
7
- placeholder: string | null
8
- actions: Action[]
9
- }
10
-
11
- const createStatusCtx = (): StatusCtx => {
12
- return {
13
- placeholder: null,
14
- actions: [],
15
- }
16
- }
17
-
18
- export type Status = ReturnType<typeof createStatus>
19
-
20
- export const createStatus = (builder: StatusConfigBuilder) => {
21
- const statusCtx = createStatusCtx()
22
-
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
- }
@@ -1,112 +0,0 @@
1
- /* Copyright 2021, Milkdown by Mirone. */
2
- import { missingRootElement } from '@milkdown/exception'
3
- import { calculateNodePosition } from '@milkdown/prose'
4
- import type { EditorView } from '@milkdown/prose/view'
5
- import type { ThemeUtils } from '@milkdown/utils'
6
-
7
- import { createDropdown } from '../utility'
8
- import { renderDropdown } from './dropdown'
9
- import {
10
- createMouseManager,
11
- handleClick,
12
- handleKeydown,
13
- handleMouseEnter,
14
- handleMouseLeave,
15
- handleMouseMove,
16
- } from './input'
17
- import type { Status } from './status'
18
-
19
- export const defaultCalcPosition = (view: EditorView, dropdownElement: HTMLElement) => {
20
- calculateNodePosition(view, dropdownElement, (selected, target, parent) => {
21
- const $editor = dropdownElement.parentElement
22
- if (!$editor)
23
- throw missingRootElement()
24
-
25
- let left = selected.left - parent.left
26
-
27
- if (left < 0)
28
- left = 0
29
-
30
- let direction: 'top' | 'bottom'
31
- let maxHeight: number | undefined
32
- const selectedToTop = selected.top - parent.top
33
- const selectedToBottom = parent.height + parent.top - selected.bottom
34
- if (selectedToBottom >= target.height + 28) {
35
- direction = 'bottom'
36
- }
37
- else if (selectedToTop >= target.height + 28) {
38
- direction = 'top'
39
- }
40
- else if (selectedToBottom >= selectedToTop) {
41
- direction = 'bottom'
42
- maxHeight = selectedToBottom - 28
43
- }
44
- else {
45
- direction = 'top'
46
- maxHeight = selectedToTop - 28
47
- }
48
- if (selectedToTop < 0 || selectedToBottom < 0) {
49
- maxHeight = parent.height - selected.height - 28
50
- if (maxHeight > target.height)
51
- maxHeight = undefined
52
- }
53
-
54
- const top
55
- = direction === 'top'
56
- ? selected.top - parent.top - (maxHeight ?? target.height) - 14 + $editor.scrollTop
57
- : selected.bottom - parent.top + 14 + $editor.scrollTop
58
-
59
- dropdownElement.style.maxHeight = maxHeight !== undefined && maxHeight > 0 ? `${maxHeight}px` : ''
60
-
61
- return [top, left]
62
- })
63
- }
64
-
65
- export type CalcPosition = (view: EditorView, dropdownElement: HTMLElement) => void
66
-
67
- export const createView = (
68
- status: Status,
69
- view: EditorView,
70
- utils: ThemeUtils,
71
- className: string,
72
- calcPosition: CalcPosition,
73
- ) => {
74
- const wrapper = view.dom.parentNode
75
- if (!wrapper)
76
- return {}
77
-
78
- const dropdownElement = createDropdown(utils, className)
79
- const mouseManager = createMouseManager()
80
- wrapper.appendChild(dropdownElement)
81
-
82
- const _mouseMove = handleMouseMove(mouseManager)
83
- const _mouseDown = handleClick(status, view, dropdownElement)
84
- const _keydown = handleKeydown(status, view, dropdownElement, mouseManager)
85
- const _mouseEnter = handleMouseEnter(status, mouseManager)
86
- const _mouseLeave = handleMouseLeave()
87
-
88
- wrapper.addEventListener('mousemove', _mouseMove)
89
- wrapper.addEventListener('mousedown', _mouseDown)
90
- wrapper.addEventListener('keydown', _keydown)
91
-
92
- return {
93
- update: (view: EditorView) => {
94
- const show = renderDropdown(status, dropdownElement, {
95
- mouseEnter: _mouseEnter as EventListener,
96
- mouseLeave: _mouseLeave as EventListener,
97
- })
98
-
99
- if (!show)
100
- return
101
-
102
- calcPosition(view, dropdownElement)
103
- },
104
-
105
- destroy: () => {
106
- wrapper.removeEventListener('mousemove', _mouseMove)
107
- wrapper.removeEventListener('mousedown', _mouseDown)
108
- wrapper.removeEventListener('keydown', _keydown)
109
- dropdownElement.remove()
110
- },
111
- }
112
- }
package/src/style.ts DELETED
@@ -1,76 +0,0 @@
1
- /* Copyright 2021, Milkdown by Mirone. */
2
- import type {
3
- Color,
4
- Emotion,
5
- ThemeManager,
6
- } from '@milkdown/core'
7
- import {
8
- ThemeBorder,
9
- ThemeColor,
10
- ThemeFont,
11
- ThemeScrollbar,
12
- ThemeShadow,
13
- ThemeSize,
14
- } from '@milkdown/core'
15
-
16
- const itemStyle = (themeManager: ThemeManager, { css }: Emotion) => {
17
- const palette = (color: Color, opacity = 1) => themeManager.get(ThemeColor, [color, opacity])
18
- return css`
19
- .slash-dropdown-item {
20
- display: flex;
21
- gap: 32px;
22
- height: 48px;
23
- padding: 0 16px;
24
- align-items: center;
25
- justify-content: flex-start;
26
- cursor: pointer;
27
- line-height: 48px;
28
- font-family: ${themeManager.get(ThemeFont, 'typography')};
29
- font-size: 14px;
30
-
31
- transition: all 0.2s ease-in-out;
32
-
33
- &,
34
- .icon {
35
- color: ${palette('neutral', 0.87)};
36
- transition: all 0.2s ease-in-out;
37
- }
38
-
39
- &.hide {
40
- display: none;
41
- }
42
-
43
- &.active {
44
- background: ${palette('secondary', 0.12)};
45
- &,
46
- .icon {
47
- color: ${palette('primary')};
48
- }
49
- }
50
- }
51
- `
52
- }
53
-
54
- export const injectStyle = (themeManager: ThemeManager, emotion: Emotion) => {
55
- const palette = (color: Color, opacity = 1) => themeManager.get(ThemeColor, [color, opacity])
56
-
57
- return emotion.css`
58
- width: 320px;
59
- min-height: 48px;
60
- max-height: 320px;
61
- overflow-y: auto;
62
- border-radius: ${themeManager.get(ThemeSize, 'radius')};
63
- position: absolute;
64
- background: ${palette('surface')};
65
-
66
- ${themeManager.get(ThemeBorder, undefined)}
67
- ${themeManager.get(ThemeShadow, undefined)}
68
- ${themeManager.get(ThemeScrollbar, undefined)}
69
-
70
- &.hide {
71
- display: none;
72
- }
73
-
74
- ${itemStyle(themeManager, emotion)}
75
- `
76
- }
package/src/utility.ts DELETED
@@ -1,85 +0,0 @@
1
- /* Copyright 2021, Milkdown by Mirone. */
2
- import type { ThemeManager } from '@milkdown/core'
3
- import { ThemeIcon } from '@milkdown/core'
4
- import type { Icon } from '@milkdown/design-system'
5
- import { missingIcon } from '@milkdown/exception'
6
- import type { Node } from '@milkdown/prose/model'
7
- import type { Command } from '@milkdown/prose/state'
8
- import type { ThemeUtils } from '@milkdown/utils'
9
-
10
- import { injectStyle } from './style'
11
-
12
- export const createDropdown = (utils: ThemeUtils, className: string) => {
13
- const div = document.createElement('div')
14
- div.setAttribute('role', 'listbox')
15
- div.setAttribute('tabindex', '-1')
16
- utils.themeManager.onFlush(() => {
17
- const style = utils.getStyle(emotion => injectStyle(utils.themeManager, emotion))
18
-
19
- if (style)
20
- div.classList.add(style)
21
- })
22
-
23
- div.classList.add(utils.getClassName({}, className), 'hide')
24
-
25
- return div
26
- }
27
-
28
- interface ItemOptions {
29
- textClassName: string
30
- }
31
- export const createDropdownItem = (
32
- themeManager: ThemeManager,
33
- text: string,
34
- icon: Icon,
35
- options?: Partial<ItemOptions>,
36
- ) => {
37
- const textClassName = options?.textClassName ?? 'text'
38
-
39
- const div = document.createElement('div')
40
- div.setAttribute('role', 'option')
41
- div.classList.add('slash-dropdown-item')
42
-
43
- const iconSpan = themeManager.get(ThemeIcon, icon)
44
-
45
- if (!iconSpan)
46
- throw missingIcon(icon)
47
-
48
- const textSpan = document.createElement('span')
49
- textSpan.textContent = text
50
- textSpan.className = textClassName
51
-
52
- div.appendChild(iconSpan.dom)
53
- div.appendChild(textSpan)
54
-
55
- return div
56
- }
57
-
58
- export const getDepth = (node: Node) => {
59
- let cur = node
60
- let depth = 0
61
- while (cur.childCount) {
62
- cur = cur.child(0)
63
- depth += 1
64
- }
65
-
66
- return depth
67
- }
68
-
69
- const cleanUp: Command = (state, dispatch) => {
70
- const { selection } = state
71
- const { $from } = selection
72
- const tr = state.tr.deleteRange($from.start(), $from.pos)
73
- dispatch?.(tr)
74
- return false
75
- }
76
-
77
- export const cleanUpAndCreateNode
78
- = (createCommand: () => void): Command =>
79
- (state, dispatch, view) => {
80
- if (view) {
81
- cleanUp(state, dispatch, view)
82
- createCommand()
83
- }
84
- return true
85
- }