@jvs-milkdown/components 1.0.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 (192) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -0
  3. package/lib/__internal__/components/icon.d.ts +24 -0
  4. package/lib/__internal__/components/icon.d.ts.map +1 -0
  5. package/lib/__internal__/components/image-input.d.ts +17 -0
  6. package/lib/__internal__/components/image-input.d.ts.map +1 -0
  7. package/lib/__internal__/keep-alive.d.ts +2 -0
  8. package/lib/__internal__/keep-alive.d.ts.map +1 -0
  9. package/lib/__internal__/meta.d.ts +3 -0
  10. package/lib/__internal__/meta.d.ts.map +1 -0
  11. package/lib/__tests__/setup.d.ts +2 -0
  12. package/lib/__tests__/setup.d.ts.map +1 -0
  13. package/lib/code-block/config.d.ts +23 -0
  14. package/lib/code-block/config.d.ts.map +1 -0
  15. package/lib/code-block/index.d.ts +5 -0
  16. package/lib/code-block/index.d.ts.map +1 -0
  17. package/lib/code-block/index.js +4160 -0
  18. package/lib/code-block/index.js.map +1 -0
  19. package/lib/code-block/view/components/code-block.d.ts +16 -0
  20. package/lib/code-block/view/components/code-block.d.ts.map +1 -0
  21. package/lib/code-block/view/components/copy-button.d.ts +9 -0
  22. package/lib/code-block/view/components/copy-button.d.ts.map +1 -0
  23. package/lib/code-block/view/components/language-picker.d.ts +5 -0
  24. package/lib/code-block/view/components/language-picker.d.ts.map +1 -0
  25. package/lib/code-block/view/components/preview-panel.d.ts +9 -0
  26. package/lib/code-block/view/components/preview-panel.d.ts.map +1 -0
  27. package/lib/code-block/view/index.d.ts +3 -0
  28. package/lib/code-block/view/index.d.ts.map +1 -0
  29. package/lib/code-block/view/loader.d.ts +13 -0
  30. package/lib/code-block/view/loader.d.ts.map +1 -0
  31. package/lib/code-block/view/node-view.d.ts +40 -0
  32. package/lib/code-block/view/node-view.d.ts.map +1 -0
  33. package/lib/image-block/config.d.ts +16 -0
  34. package/lib/image-block/config.d.ts.map +1 -0
  35. package/lib/image-block/index.d.ts +7 -0
  36. package/lib/image-block/index.d.ts.map +1 -0
  37. package/lib/image-block/index.js +660 -0
  38. package/lib/image-block/index.js.map +1 -0
  39. package/lib/image-block/remark-plugin.d.ts +2 -0
  40. package/lib/image-block/remark-plugin.d.ts.map +1 -0
  41. package/lib/image-block/schema.d.ts +3 -0
  42. package/lib/image-block/schema.d.ts.map +1 -0
  43. package/lib/image-block/view/components/__tests__/image-viewer.onImageLoadError.spec.d.ts +2 -0
  44. package/lib/image-block/view/components/__tests__/image-viewer.onImageLoadError.spec.d.ts.map +1 -0
  45. package/lib/image-block/view/components/image-block.d.ts +18 -0
  46. package/lib/image-block/view/components/image-block.d.ts.map +1 -0
  47. package/lib/image-block/view/components/image-viewer.d.ts +3 -0
  48. package/lib/image-block/view/components/image-viewer.d.ts.map +1 -0
  49. package/lib/image-block/view/index.d.ts +3 -0
  50. package/lib/image-block/view/index.d.ts.map +1 -0
  51. package/lib/image-inline/components/image-inline.d.ts +18 -0
  52. package/lib/image-inline/components/image-inline.d.ts.map +1 -0
  53. package/lib/image-inline/config.d.ts +11 -0
  54. package/lib/image-inline/config.d.ts.map +1 -0
  55. package/lib/image-inline/index.d.ts +5 -0
  56. package/lib/image-inline/index.d.ts.map +1 -0
  57. package/lib/image-inline/index.js +377 -0
  58. package/lib/image-inline/index.js.map +1 -0
  59. package/lib/image-inline/view.d.ts +3 -0
  60. package/lib/image-inline/view.d.ts.map +1 -0
  61. package/lib/index.d.ts +2 -0
  62. package/lib/index.d.ts.map +1 -0
  63. package/lib/index.js +35 -0
  64. package/lib/index.js.map +1 -0
  65. package/lib/link-tooltip/command.d.ts +2 -0
  66. package/lib/link-tooltip/command.d.ts.map +1 -0
  67. package/lib/link-tooltip/configure.d.ts +3 -0
  68. package/lib/link-tooltip/configure.d.ts.map +1 -0
  69. package/lib/link-tooltip/edit/component.d.ts +11 -0
  70. package/lib/link-tooltip/edit/component.d.ts.map +1 -0
  71. package/lib/link-tooltip/edit/edit-configure.d.ts +3 -0
  72. package/lib/link-tooltip/edit/edit-configure.d.ts.map +1 -0
  73. package/lib/link-tooltip/edit/edit-view.d.ts +15 -0
  74. package/lib/link-tooltip/edit/edit-view.d.ts.map +1 -0
  75. package/lib/link-tooltip/index.d.ts +7 -0
  76. package/lib/link-tooltip/index.d.ts.map +1 -0
  77. package/lib/link-tooltip/index.js +2526 -0
  78. package/lib/link-tooltip/index.js.map +1 -0
  79. package/lib/link-tooltip/preview/component.d.ts +11 -0
  80. package/lib/link-tooltip/preview/component.d.ts.map +1 -0
  81. package/lib/link-tooltip/preview/preview-configure.d.ts +3 -0
  82. package/lib/link-tooltip/preview/preview-configure.d.ts.map +1 -0
  83. package/lib/link-tooltip/preview/preview-view.d.ts +14 -0
  84. package/lib/link-tooltip/preview/preview-view.d.ts.map +1 -0
  85. package/lib/link-tooltip/slices.d.ts +34 -0
  86. package/lib/link-tooltip/slices.d.ts.map +1 -0
  87. package/lib/link-tooltip/tooltips.d.ts +3 -0
  88. package/lib/link-tooltip/tooltips.d.ts.map +1 -0
  89. package/lib/link-tooltip/utils.d.ts +14 -0
  90. package/lib/link-tooltip/utils.d.ts.map +1 -0
  91. package/lib/list-item-block/component.d.ts +19 -0
  92. package/lib/list-item-block/component.d.ts.map +1 -0
  93. package/lib/list-item-block/config.d.ts +13 -0
  94. package/lib/list-item-block/config.d.ts.map +1 -0
  95. package/lib/list-item-block/index.d.ts +6 -0
  96. package/lib/list-item-block/index.d.ts.map +1 -0
  97. package/lib/list-item-block/index.js +2149 -0
  98. package/lib/list-item-block/index.js.map +1 -0
  99. package/lib/list-item-block/view.d.ts +3 -0
  100. package/lib/list-item-block/view.d.ts.map +1 -0
  101. package/lib/table-block/config.d.ts +8 -0
  102. package/lib/table-block/config.d.ts.map +1 -0
  103. package/lib/table-block/dnd/calc-drag-over.d.ts +3 -0
  104. package/lib/table-block/dnd/calc-drag-over.d.ts.map +1 -0
  105. package/lib/table-block/dnd/create-drag-handler.d.ts +5 -0
  106. package/lib/table-block/dnd/create-drag-handler.d.ts.map +1 -0
  107. package/lib/table-block/dnd/drag-over-handler.d.ts +3 -0
  108. package/lib/table-block/dnd/drag-over-handler.d.ts.map +1 -0
  109. package/lib/table-block/dnd/prepare-dnd-context.d.ts +3 -0
  110. package/lib/table-block/dnd/prepare-dnd-context.d.ts.map +1 -0
  111. package/lib/table-block/dnd/preview.d.ts +3 -0
  112. package/lib/table-block/dnd/preview.d.ts.map +1 -0
  113. package/lib/table-block/index.d.ts +5 -0
  114. package/lib/table-block/index.d.ts.map +1 -0
  115. package/lib/table-block/index.js +3961 -0
  116. package/lib/table-block/index.js.map +1 -0
  117. package/lib/table-block/view/component.d.ts +16 -0
  118. package/lib/table-block/view/component.d.ts.map +1 -0
  119. package/lib/table-block/view/drag.d.ts +7 -0
  120. package/lib/table-block/view/drag.d.ts.map +1 -0
  121. package/lib/table-block/view/index.d.ts +2 -0
  122. package/lib/table-block/view/index.d.ts.map +1 -0
  123. package/lib/table-block/view/operation.d.ts +11 -0
  124. package/lib/table-block/view/operation.d.ts.map +1 -0
  125. package/lib/table-block/view/pointer.d.ts +7 -0
  126. package/lib/table-block/view/pointer.d.ts.map +1 -0
  127. package/lib/table-block/view/types.d.ts +32 -0
  128. package/lib/table-block/view/types.d.ts.map +1 -0
  129. package/lib/table-block/view/utils.d.ts +21 -0
  130. package/lib/table-block/view/utils.d.ts.map +1 -0
  131. package/lib/table-block/view/view.d.ts +22 -0
  132. package/lib/table-block/view/view.d.ts.map +1 -0
  133. package/lib/tsconfig.tsbuildinfo +1 -0
  134. package/package.json +110 -0
  135. package/src/__internal__/components/icon.tsx +38 -0
  136. package/src/__internal__/components/image-input.tsx +182 -0
  137. package/src/__internal__/keep-alive.ts +3 -0
  138. package/src/__internal__/meta.ts +15 -0
  139. package/src/__tests__/setup.ts +6 -0
  140. package/src/code-block/config.ts +54 -0
  141. package/src/code-block/index.ts +12 -0
  142. package/src/code-block/view/components/code-block.tsx +170 -0
  143. package/src/code-block/view/components/copy-button.tsx +96 -0
  144. package/src/code-block/view/components/language-picker.tsx +239 -0
  145. package/src/code-block/view/components/preview-panel.tsx +79 -0
  146. package/src/code-block/view/index.ts +24 -0
  147. package/src/code-block/view/loader.ts +40 -0
  148. package/src/code-block/view/node-view.ts +310 -0
  149. package/src/image-block/config.ts +37 -0
  150. package/src/image-block/index.ts +18 -0
  151. package/src/image-block/remark-plugin.ts +51 -0
  152. package/src/image-block/schema.ts +71 -0
  153. package/src/image-block/view/components/__tests__/image-viewer.onImageLoadError.spec.tsx +42 -0
  154. package/src/image-block/view/components/image-block.tsx +80 -0
  155. package/src/image-block/view/components/image-viewer.tsx +186 -0
  156. package/src/image-block/view/index.ts +111 -0
  157. package/src/image-inline/components/image-inline.tsx +85 -0
  158. package/src/image-inline/config.ts +30 -0
  159. package/src/image-inline/index.ts +12 -0
  160. package/src/image-inline/view.ts +109 -0
  161. package/src/index.ts +1 -0
  162. package/src/link-tooltip/command.ts +19 -0
  163. package/src/link-tooltip/configure.ts +9 -0
  164. package/src/link-tooltip/edit/component.tsx +82 -0
  165. package/src/link-tooltip/edit/edit-configure.ts +29 -0
  166. package/src/link-tooltip/edit/edit-view.ts +165 -0
  167. package/src/link-tooltip/index.ts +19 -0
  168. package/src/link-tooltip/preview/component.tsx +87 -0
  169. package/src/link-tooltip/preview/preview-configure.ts +65 -0
  170. package/src/link-tooltip/preview/preview-view.ts +101 -0
  171. package/src/link-tooltip/slices.ts +69 -0
  172. package/src/link-tooltip/tooltips.ts +22 -0
  173. package/src/link-tooltip/utils.ts +56 -0
  174. package/src/list-item-block/component.tsx +133 -0
  175. package/src/list-item-block/config.ts +39 -0
  176. package/src/list-item-block/index.ts +13 -0
  177. package/src/list-item-block/view.ts +130 -0
  178. package/src/table-block/config.ts +53 -0
  179. package/src/table-block/dnd/calc-drag-over.ts +46 -0
  180. package/src/table-block/dnd/create-drag-handler.ts +99 -0
  181. package/src/table-block/dnd/drag-over-handler.ts +113 -0
  182. package/src/table-block/dnd/prepare-dnd-context.ts +46 -0
  183. package/src/table-block/dnd/preview.ts +58 -0
  184. package/src/table-block/index.ts +9 -0
  185. package/src/table-block/view/component.tsx +219 -0
  186. package/src/table-block/view/drag.ts +121 -0
  187. package/src/table-block/view/index.ts +1 -0
  188. package/src/table-block/view/operation.ts +148 -0
  189. package/src/table-block/view/pointer.ts +165 -0
  190. package/src/table-block/view/types.ts +35 -0
  191. package/src/table-block/view/utils.ts +192 -0
  192. package/src/table-block/view/view.ts +165 -0
@@ -0,0 +1,148 @@
1
+ import type { Ctx } from '@jvs-milkdown/ctx'
2
+
3
+ import { commandsCtx, editorViewCtx } from '@jvs-milkdown/core'
4
+ import {
5
+ addColAfterCommand,
6
+ addColBeforeCommand,
7
+ addRowAfterCommand,
8
+ addRowBeforeCommand,
9
+ deleteSelectedCellsCommand,
10
+ selectColCommand,
11
+ selectRowCommand,
12
+ setAlignCommand,
13
+ } from '@jvs-milkdown/preset-gfm'
14
+
15
+ import type { Refs } from './types'
16
+
17
+ export function useOperation(
18
+ refs: Refs,
19
+ ctx?: Ctx,
20
+ getPos?: () => number | undefined
21
+ ) {
22
+ const {
23
+ xLineHandleRef,
24
+ contentWrapperRef,
25
+ colHandleRef,
26
+ rowHandleRef,
27
+ hoverIndex,
28
+ lineHoverIndex,
29
+ } = refs
30
+
31
+ const onAddRow = () => {
32
+ if (!ctx) return
33
+ const xHandle = xLineHandleRef.value
34
+ if (!xHandle) return
35
+
36
+ const [rowIndex] = lineHoverIndex.value!
37
+ if (rowIndex < 0) return
38
+
39
+ if (!ctx.get(editorViewCtx).editable) return
40
+
41
+ const rows = Array.from(
42
+ contentWrapperRef.value?.querySelectorAll('tr') ?? []
43
+ )
44
+ const commands = ctx.get(commandsCtx)
45
+ const pos = (getPos?.() ?? 0) + 1
46
+ if (rows.length === rowIndex) {
47
+ commands.call(selectRowCommand.key, { pos, index: rowIndex - 1 })
48
+ commands.call(addRowAfterCommand.key)
49
+ } else {
50
+ commands.call(selectRowCommand.key, { pos, index: rowIndex })
51
+ commands.call(addRowBeforeCommand.key)
52
+ }
53
+
54
+ commands.call(selectRowCommand.key, { pos, index: rowIndex })
55
+ xHandle.dataset.show = 'false'
56
+ }
57
+
58
+ const onAddCol = () => {
59
+ if (!ctx) return
60
+ const xHandle = xLineHandleRef.value
61
+ if (!xHandle) return
62
+
63
+ const [_, colIndex] = lineHoverIndex.value!
64
+ if (colIndex < 0) return
65
+
66
+ if (!ctx.get(editorViewCtx).editable) return
67
+
68
+ const cols = Array.from(
69
+ contentWrapperRef.value?.querySelector('tr')?.children ?? []
70
+ )
71
+ const commands = ctx.get(commandsCtx)
72
+
73
+ const pos = (getPos?.() ?? 0) + 1
74
+ if (cols.length === colIndex) {
75
+ commands.call(selectColCommand.key, { pos, index: colIndex - 1 })
76
+ commands.call(addColAfterCommand.key)
77
+ } else {
78
+ commands.call(selectColCommand.key, { pos, index: colIndex })
79
+ commands.call(addColBeforeCommand.key)
80
+ }
81
+
82
+ commands.call(selectColCommand.key, { pos, index: colIndex })
83
+ }
84
+
85
+ const selectCol = () => {
86
+ if (!ctx) return
87
+ const [_, colIndex] = hoverIndex.value!
88
+ const commands = ctx.get(commandsCtx)
89
+ const pos = (getPos?.() ?? 0) + 1
90
+ commands.call(selectColCommand.key, { pos, index: colIndex })
91
+ const buttonGroup =
92
+ colHandleRef.value?.querySelector<HTMLElement>('.button-group')
93
+ if (buttonGroup)
94
+ buttonGroup.dataset.show =
95
+ buttonGroup.dataset.show === 'true' ? 'false' : 'true'
96
+ }
97
+
98
+ const selectRow = () => {
99
+ if (!ctx) return
100
+ const [rowIndex, _] = hoverIndex.value!
101
+ const commands = ctx.get(commandsCtx)
102
+ const pos = (getPos?.() ?? 0) + 1
103
+ commands.call(selectRowCommand.key, { pos, index: rowIndex })
104
+ const buttonGroup =
105
+ rowHandleRef.value?.querySelector<HTMLElement>('.button-group')
106
+ if (buttonGroup && rowIndex > 0)
107
+ buttonGroup.dataset.show =
108
+ buttonGroup.dataset.show === 'true' ? 'false' : 'true'
109
+ }
110
+
111
+ const deleteSelected = (e: PointerEvent) => {
112
+ if (!ctx) return
113
+
114
+ if (!ctx.get(editorViewCtx).editable) return
115
+
116
+ e.preventDefault()
117
+ e.stopPropagation()
118
+ const commands = ctx.get(commandsCtx)
119
+ commands.call(deleteSelectedCellsCommand.key)
120
+ requestAnimationFrame(() => {
121
+ ctx.get(editorViewCtx).focus()
122
+ })
123
+ }
124
+
125
+ const onAlign =
126
+ (direction: 'left' | 'center' | 'right') => (e: PointerEvent) => {
127
+ if (!ctx) return
128
+
129
+ if (!ctx.get(editorViewCtx).editable) return
130
+
131
+ e.preventDefault()
132
+ e.stopPropagation()
133
+ const commands = ctx.get(commandsCtx)
134
+ commands.call(setAlignCommand.key, direction)
135
+ requestAnimationFrame(() => {
136
+ ctx.get(editorViewCtx).focus()
137
+ })
138
+ }
139
+
140
+ return {
141
+ onAddRow,
142
+ onAddCol,
143
+ selectCol,
144
+ selectRow,
145
+ deleteSelected,
146
+ onAlign,
147
+ }
148
+ }
@@ -0,0 +1,165 @@
1
+ import type { EditorView } from '@jvs-milkdown/prose/view'
2
+
3
+ import { computePosition, offset } from '@floating-ui/dom'
4
+ import { throttle } from 'lodash-es'
5
+
6
+ import type { Refs } from './types'
7
+
8
+ import {
9
+ computeColHandlePositionByIndex,
10
+ computeRowHandlePositionByIndex,
11
+ findPointerIndex,
12
+ getRelatedDOM,
13
+ } from './utils'
14
+
15
+ function createPointerMoveHandler(
16
+ refs: Refs,
17
+ view?: EditorView
18
+ ): (e: PointerEvent) => void {
19
+ return throttle((e: PointerEvent) => {
20
+ if (!view?.editable) return
21
+ const {
22
+ contentWrapperRef,
23
+ yLineHandleRef,
24
+ xLineHandleRef,
25
+ colHandleRef,
26
+ rowHandleRef,
27
+ hoverIndex,
28
+ lineHoverIndex,
29
+ } = refs
30
+ const yHandle = yLineHandleRef.value
31
+ if (!yHandle) return
32
+ const xHandle = xLineHandleRef.value
33
+ if (!xHandle) return
34
+ const content = contentWrapperRef.value
35
+ if (!content) return
36
+ const rowHandle = rowHandleRef.value
37
+ if (!rowHandle) return
38
+ const colHandle = colHandleRef.value
39
+ if (!colHandle) return
40
+
41
+ const index = findPointerIndex(e, view)
42
+ if (!index) return
43
+
44
+ const dom = getRelatedDOM(contentWrapperRef, index)
45
+ if (!dom) return
46
+
47
+ const [rowIndex, colIndex] = index
48
+ const boundary = dom.col.getBoundingClientRect()
49
+ const closeToBoundaryLeft = Math.abs(e.clientX - boundary.left) < 8
50
+ const closeToBoundaryRight = Math.abs(boundary.right - e.clientX) < 8
51
+ const closeToBoundaryTop = Math.abs(e.clientY - boundary.top) < 8
52
+ const closeToBoundaryBottom = Math.abs(boundary.bottom - e.clientY) < 8
53
+
54
+ const closeToBoundary =
55
+ closeToBoundaryLeft ||
56
+ closeToBoundaryRight ||
57
+ closeToBoundaryTop ||
58
+ closeToBoundaryBottom
59
+
60
+ const rowButtonGroup = rowHandle.querySelector<HTMLElement>('.button-group')
61
+ const colButtonGroup = colHandle.querySelector<HTMLElement>('.button-group')
62
+ if (rowButtonGroup) rowButtonGroup.dataset.show = 'false'
63
+ if (colButtonGroup) colButtonGroup.dataset.show = 'false'
64
+
65
+ if (closeToBoundary) {
66
+ const contentBoundary = content.getBoundingClientRect()
67
+ rowHandle.dataset.show = 'false'
68
+ colHandle.dataset.show = 'false'
69
+ xHandle.dataset.displayType = 'tool'
70
+ yHandle.dataset.displayType = 'tool'
71
+
72
+ const yHandleWidth = yHandle.getBoundingClientRect().width
73
+ const xHandleHeight = xHandle.getBoundingClientRect().height
74
+
75
+ // display vertical line handle
76
+ if (closeToBoundaryLeft || closeToBoundaryRight) {
77
+ lineHoverIndex.value![1] = closeToBoundaryLeft ? colIndex : colIndex + 1
78
+ computePosition(dom.col, yHandle, {
79
+ placement: closeToBoundaryLeft ? 'left' : 'right',
80
+ middleware: [offset(closeToBoundaryLeft ? -1 * yHandleWidth : 0)],
81
+ })
82
+ .then(({ x }) => {
83
+ yHandle.dataset.show = 'true'
84
+ Object.assign(yHandle.style, {
85
+ height: `${contentBoundary.height}px`,
86
+ left: `${x}px`,
87
+ })
88
+ })
89
+ .catch(console.error)
90
+ } else {
91
+ yHandle.dataset.show = 'false'
92
+ }
93
+
94
+ // display horizontal line handle
95
+ // won't display if the row is the header row
96
+ if (index[0] !== 0 && (closeToBoundaryTop || closeToBoundaryBottom)) {
97
+ lineHoverIndex.value![0] = closeToBoundaryTop ? rowIndex : rowIndex + 1
98
+ computePosition(dom.row, xHandle, {
99
+ placement: closeToBoundaryTop ? 'top' : 'bottom',
100
+ middleware: [offset(closeToBoundaryTop ? -1 * xHandleHeight : 0)],
101
+ })
102
+ .then(({ y }) => {
103
+ xHandle.dataset.show = 'true'
104
+ Object.assign(xHandle.style, {
105
+ width: `${contentBoundary.width}px`,
106
+ top: `${y}px`,
107
+ })
108
+ })
109
+ .catch(console.error)
110
+ } else {
111
+ xHandle.dataset.show = 'false'
112
+ }
113
+
114
+ return
115
+ }
116
+
117
+ lineHoverIndex.value = [-1, -1]
118
+
119
+ yHandle.dataset.show = 'false'
120
+ xHandle.dataset.show = 'false'
121
+ rowHandle.dataset.show = 'true'
122
+ colHandle.dataset.show = 'true'
123
+
124
+ computeRowHandlePositionByIndex({
125
+ refs,
126
+ index,
127
+ })
128
+ computeColHandlePositionByIndex({
129
+ refs,
130
+ index,
131
+ })
132
+ hoverIndex.value = index
133
+ }, 20)
134
+ }
135
+
136
+ function createPointerLeaveHandler(refs: Refs): () => void {
137
+ return () => {
138
+ const { rowHandleRef, colHandleRef, yLineHandleRef, xLineHandleRef } = refs
139
+ setTimeout(() => {
140
+ const rowHandle = rowHandleRef.value
141
+ if (!rowHandle) return
142
+ const colHandle = colHandleRef.value
143
+ if (!colHandle) return
144
+ const yHandle = yLineHandleRef.value
145
+ if (!yHandle) return
146
+ const xHandle = xLineHandleRef.value
147
+ if (!xHandle) return
148
+
149
+ rowHandle.dataset.show = 'false'
150
+ colHandle.dataset.show = 'false'
151
+ yHandle.dataset.show = 'false'
152
+ xHandle.dataset.show = 'false'
153
+ }, 200)
154
+ }
155
+ }
156
+
157
+ export function usePointerHandlers(refs: Refs, view?: EditorView) {
158
+ const pointerMove = createPointerMoveHandler(refs, view)
159
+ const pointerLeave = createPointerLeaveHandler(refs)
160
+
161
+ return {
162
+ pointerMove,
163
+ pointerLeave,
164
+ }
165
+ }
@@ -0,0 +1,35 @@
1
+ import type { Ref } from 'vue'
2
+
3
+ export type CellIndex = [row: number, col: number]
4
+
5
+ export interface DragInfo {
6
+ startCoords: [x: number, y: number]
7
+ startIndex: number
8
+ endIndex: number
9
+ type: 'row' | 'col'
10
+ }
11
+
12
+ export interface DragContext {
13
+ preview: HTMLDivElement
14
+ previewRoot: HTMLTableSectionElement
15
+ wrapper: HTMLDivElement
16
+ content: HTMLElement
17
+ contentRoot: HTMLTableSectionElement
18
+ yHandle: HTMLDivElement
19
+ xHandle: HTMLDivElement
20
+ colHandle: HTMLDivElement
21
+ rowHandle: HTMLDivElement
22
+ }
23
+
24
+ export interface Refs {
25
+ dragPreviewRef: Ref<HTMLDivElement | undefined>
26
+ tableWrapperRef: Ref<HTMLDivElement | undefined>
27
+ contentWrapperRef: Ref<HTMLElement | undefined>
28
+ yLineHandleRef: Ref<HTMLDivElement | undefined>
29
+ xLineHandleRef: Ref<HTMLDivElement | undefined>
30
+ colHandleRef: Ref<HTMLDivElement | undefined>
31
+ rowHandleRef: Ref<HTMLDivElement | undefined>
32
+ hoverIndex: Ref<CellIndex>
33
+ lineHoverIndex: Ref<CellIndex>
34
+ dragInfo: Ref<DragInfo | undefined>
35
+ }
@@ -0,0 +1,192 @@
1
+ import type { Node } from '@jvs-milkdown/prose/model'
2
+ import type { EditorView } from '@jvs-milkdown/prose/view'
3
+ import type { Ref } from 'vue'
4
+
5
+ import { computePosition } from '@floating-ui/dom'
6
+ import { findParent } from '@jvs-milkdown/prose'
7
+ import { CellSelection, findTable } from '@jvs-milkdown/prose/tables'
8
+
9
+ import type { CellIndex, Refs } from './types'
10
+
11
+ function findNodeIndex(parent: Node, child: Node) {
12
+ for (let i = 0; i < parent.childCount; i++) {
13
+ if (parent.child(i) === child) return i
14
+ }
15
+ return -1
16
+ }
17
+
18
+ export function findPointerIndex(
19
+ event: PointerEvent,
20
+ view?: EditorView
21
+ ): CellIndex | undefined {
22
+ if (!view) return
23
+
24
+ try {
25
+ const posAtCoords = view.posAtCoords({
26
+ left: event.clientX,
27
+ top: event.clientY,
28
+ })
29
+ if (!posAtCoords) return
30
+ const pos = posAtCoords?.inside
31
+ if (pos == null || pos < 0) return
32
+
33
+ const $pos = view.state.doc.resolve(pos)
34
+ const node = view.state.doc.nodeAt(pos)
35
+ if (!node) return
36
+
37
+ const cellType = ['table_cell', 'table_header']
38
+ const rowType = ['table_row', 'table_header_row']
39
+
40
+ const cell = cellType.includes(node.type.name)
41
+ ? node
42
+ : findParent((node) => cellType.includes(node.type.name))($pos)?.node
43
+ const row = findParent((node) => rowType.includes(node.type.name))(
44
+ $pos
45
+ )?.node
46
+ const table = findParent((node) => node.type.name === 'table')($pos)?.node
47
+ if (!cell || !row || !table) return
48
+
49
+ const columnIndex = findNodeIndex(row, cell)
50
+ const rowIndex = findNodeIndex(table, row)
51
+
52
+ return [rowIndex, columnIndex]
53
+ } catch {
54
+ return undefined
55
+ }
56
+ }
57
+
58
+ export function getRelatedDOM(
59
+ contentWrapperRef: Ref<HTMLElement | undefined>,
60
+ [rowIndex, columnIndex]: CellIndex
61
+ ) {
62
+ const content = contentWrapperRef.value
63
+ if (!content) return
64
+ const rows = content.querySelectorAll('tr')
65
+ const row = rows[rowIndex]
66
+ if (!row) return
67
+
68
+ const firstRow = rows[0]
69
+ if (!firstRow) return
70
+
71
+ const headerCol = firstRow.children[columnIndex]
72
+ if (!headerCol) return
73
+
74
+ const col = row.children[columnIndex]
75
+ if (!col) return
76
+
77
+ return {
78
+ row,
79
+ col,
80
+ headerCol,
81
+ }
82
+ }
83
+
84
+ export function recoveryStateBetweenUpdate(
85
+ refs: Refs,
86
+ view?: EditorView,
87
+ node?: Node
88
+ ) {
89
+ if (!node) return
90
+ if (!view) return
91
+ const { selection } = view.state
92
+ if (!(selection instanceof CellSelection)) return
93
+
94
+ const { $from } = selection
95
+ const table = findTable($from)
96
+ if (!table || table.node !== node) return
97
+
98
+ if (selection.isColSelection()) {
99
+ const { $head } = selection
100
+ const colIndex = $head.index($head.depth - 1)
101
+ computeColHandlePositionByIndex({
102
+ refs,
103
+ index: [0, colIndex],
104
+ before: (handleDOM) => {
105
+ handleDOM
106
+ .querySelector('.button-group')
107
+ ?.setAttribute('data-show', 'true')
108
+ },
109
+ })
110
+ return
111
+ }
112
+ if (selection.isRowSelection()) {
113
+ const { $head } = selection
114
+ const rowNode = findParent(
115
+ (node) =>
116
+ node.type.name === 'table_row' || node.type.name === 'table_header_row'
117
+ )($head)
118
+ if (!rowNode) return
119
+ const rowIndex = findNodeIndex(table.node, rowNode.node)
120
+ computeRowHandlePositionByIndex({
121
+ refs,
122
+ index: [rowIndex, 0],
123
+ before: (handleDOM) => {
124
+ if (rowIndex > 0)
125
+ handleDOM
126
+ .querySelector('.button-group')
127
+ ?.setAttribute('data-show', 'true')
128
+ },
129
+ })
130
+ }
131
+ }
132
+
133
+ interface ComputeHandlePositionByIndexProps {
134
+ refs: Refs
135
+ index: CellIndex
136
+ before?: (handleDOM: HTMLDivElement) => void
137
+ after?: (handleDOM: HTMLDivElement) => void
138
+ }
139
+
140
+ export function computeColHandlePositionByIndex({
141
+ refs,
142
+ index,
143
+ before,
144
+ after,
145
+ }: ComputeHandlePositionByIndexProps) {
146
+ const { contentWrapperRef, colHandleRef, hoverIndex } = refs
147
+ const colHandle = colHandleRef.value
148
+ if (!colHandle) return
149
+
150
+ hoverIndex.value = index
151
+ const dom = getRelatedDOM(contentWrapperRef, index)
152
+ if (!dom) return
153
+ const { headerCol: col } = dom
154
+ colHandle.dataset.show = 'true'
155
+ if (before) before(colHandle)
156
+ computePosition(col, colHandle, { placement: 'top' })
157
+ .then(({ x, y }) => {
158
+ Object.assign(colHandle.style, {
159
+ left: `${x}px`,
160
+ top: `${y}px`,
161
+ })
162
+ if (after) after(colHandle)
163
+ })
164
+ .catch(console.error)
165
+ }
166
+
167
+ export function computeRowHandlePositionByIndex({
168
+ refs,
169
+ index,
170
+ before,
171
+ after,
172
+ }: ComputeHandlePositionByIndexProps) {
173
+ const { contentWrapperRef, rowHandleRef, hoverIndex } = refs
174
+ const rowHandle = rowHandleRef.value
175
+ if (!rowHandle) return
176
+
177
+ hoverIndex.value = index
178
+ const dom = getRelatedDOM(contentWrapperRef, index)
179
+ if (!dom) return
180
+ const { row } = dom
181
+ rowHandle.dataset.show = 'true'
182
+ if (before) before(rowHandle)
183
+ computePosition(row, rowHandle, { placement: 'left' })
184
+ .then(({ x, y }) => {
185
+ Object.assign(rowHandle.style, {
186
+ left: `${x}px`,
187
+ top: `${y}px`,
188
+ })
189
+ if (after) after(rowHandle)
190
+ })
191
+ .catch(console.error)
192
+ }
@@ -0,0 +1,165 @@
1
+ import type { Ctx } from '@jvs-milkdown/ctx'
2
+ import type { Node } from '@jvs-milkdown/prose/model'
3
+ import type {
4
+ EditorView,
5
+ NodeView,
6
+ NodeViewConstructor,
7
+ ViewMutationRecord,
8
+ } from '@jvs-milkdown/prose/view'
9
+
10
+ import { tableSchema } from '@jvs-milkdown/preset-gfm'
11
+ import { findParent } from '@jvs-milkdown/prose'
12
+ import { NodeSelection, TextSelection } from '@jvs-milkdown/prose/state'
13
+ import { CellSelection } from '@jvs-milkdown/prose/tables'
14
+ import { $view } from '@jvs-milkdown/utils'
15
+ import { createApp, shallowRef, type App, type ShallowRef } from 'vue'
16
+
17
+ import { withMeta } from '../../__internal__/meta'
18
+ import { tableBlockConfig } from '../config'
19
+ import { TableBlock } from './component'
20
+
21
+ export class TableNodeView implements NodeView {
22
+ dom: HTMLElement
23
+ contentDOM: HTMLElement
24
+ app: App
25
+
26
+ nodeRef: ShallowRef<Node>
27
+
28
+ constructor(
29
+ public ctx: Ctx,
30
+ public node: Node,
31
+ public view: EditorView,
32
+ public getPos: () => number | undefined
33
+ ) {
34
+ const dom = document.createElement('div')
35
+ dom.className = 'milkdown-table-block'
36
+
37
+ const contentDOM = document.createElement('tbody')
38
+ this.contentDOM = contentDOM
39
+ contentDOM.setAttribute('data-content-dom', 'true')
40
+ contentDOM.classList.add('content-dom')
41
+ this.nodeRef = shallowRef(node)
42
+
43
+ const app = createApp(TableBlock, {
44
+ view,
45
+ ctx,
46
+ getPos,
47
+ config: ctx.get(tableBlockConfig.key),
48
+ onMount: (div: Element) => {
49
+ div.appendChild(contentDOM)
50
+ },
51
+ node: this.nodeRef,
52
+ })
53
+ app.mount(dom)
54
+ this.app = app
55
+
56
+ this.dom = dom
57
+ }
58
+
59
+ update(node: Node) {
60
+ if (node.type !== this.node.type) return false
61
+
62
+ if (node.sameMarkup(this.node) && node.content.eq(this.node.content))
63
+ return false
64
+
65
+ this.node = node
66
+ this.nodeRef.value = node
67
+
68
+ return true
69
+ }
70
+
71
+ #handleClick(event: PointerEvent) {
72
+ const view = this.view
73
+ if (!view.editable) return false
74
+
75
+ const { state, dispatch } = view
76
+ const pos = view.posAtCoords({ left: event.clientX, top: event.clientY })
77
+
78
+ if (!pos) return false
79
+
80
+ const $pos = state.doc.resolve(pos.inside)
81
+ const node = findParent(
82
+ (node) =>
83
+ node.type.name === 'table_cell' || node.type.name === 'table_header'
84
+ )($pos)
85
+
86
+ if (!node) return false
87
+
88
+ // if the selection is a text selection, and the current node is the same as the node, return false
89
+ if (state.selection instanceof TextSelection) {
90
+ const currentNode = findParent(
91
+ (node) =>
92
+ node.type.name === 'table_cell' || node.type.name === 'table_header'
93
+ )(state.selection.$from)
94
+ if (currentNode?.node === node.node) return false
95
+ }
96
+
97
+ const { from } = node
98
+
99
+ const selection = NodeSelection.create(state.doc, from + 1)
100
+ if (state.selection.eq(selection)) return false
101
+
102
+ if (state.selection instanceof CellSelection) {
103
+ setTimeout(() => {
104
+ dispatch(state.tr.setSelection(selection).scrollIntoView())
105
+ }, 20)
106
+ } else {
107
+ requestAnimationFrame(() => {
108
+ dispatch(state.tr.setSelection(selection).scrollIntoView())
109
+ })
110
+ }
111
+ return true
112
+ }
113
+
114
+ stopEvent(e: Event) {
115
+ if (e.type === 'drop' || e.type.startsWith('drag')) return true
116
+
117
+ if (e.type === 'mousedown' || e.type === 'pointerdown') {
118
+ if (e.target instanceof Element && e.target.closest('button')) return true
119
+
120
+ const target = e.target
121
+ if (
122
+ target instanceof HTMLElement &&
123
+ (target.closest('th') || target.closest('td'))
124
+ ) {
125
+ const event = e as PointerEvent
126
+ return this.#handleClick(event)
127
+ }
128
+ }
129
+
130
+ return false
131
+ }
132
+
133
+ ignoreMutation(mutation: ViewMutationRecord) {
134
+ if (!this.dom || !this.contentDOM) return true
135
+
136
+ if ((mutation.type as unknown) === 'selection') return false
137
+
138
+ if (this.contentDOM === mutation.target && mutation.type === 'attributes')
139
+ return true
140
+
141
+ if (this.contentDOM.contains(mutation.target)) return false
142
+
143
+ return true
144
+ }
145
+
146
+ destroy() {
147
+ this.app.unmount()
148
+ this.dom.remove()
149
+ this.contentDOM.remove()
150
+ }
151
+ }
152
+
153
+ export const tableBlockView = $view(
154
+ tableSchema.node,
155
+ (ctx): NodeViewConstructor => {
156
+ return (initialNode, view, getPos) => {
157
+ return new TableNodeView(ctx, initialNode, view, getPos)
158
+ }
159
+ }
160
+ )
161
+
162
+ withMeta(tableBlockView, {
163
+ displayName: 'NodeView<table-block>',
164
+ group: 'TableBlock',
165
+ })