@jvs-milkdown/components 1.2.4 → 1.2.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jvs-milkdown/components",
3
- "version": "1.2.4",
3
+ "version": "1.2.7",
4
4
  "keywords": [
5
5
  "milkdown",
6
6
  "milkdown plugin"
@@ -50,15 +50,15 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@floating-ui/dom": "^1.5.1",
53
- "@jvs-milkdown/core": "^1.2.4",
54
- "@jvs-milkdown/ctx": "^1.2.4",
55
- "@jvs-milkdown/exception": "^1.2.4",
56
- "@jvs-milkdown/plugin-tooltip": "^1.2.4",
57
- "@jvs-milkdown/preset-commonmark": "^1.2.4",
58
- "@jvs-milkdown/preset-gfm": "^1.2.4",
59
- "@jvs-milkdown/prose": "^1.2.4",
60
- "@jvs-milkdown/transformer": "^1.2.4",
61
- "@jvs-milkdown/utils": "^1.2.4",
53
+ "@jvs-milkdown/core": "^1.2.7",
54
+ "@jvs-milkdown/ctx": "^1.2.7",
55
+ "@jvs-milkdown/exception": "^1.2.7",
56
+ "@jvs-milkdown/plugin-tooltip": "^1.2.7",
57
+ "@jvs-milkdown/preset-commonmark": "^1.2.7",
58
+ "@jvs-milkdown/preset-gfm": "^1.2.7",
59
+ "@jvs-milkdown/prose": "^1.2.7",
60
+ "@jvs-milkdown/transformer": "^1.2.7",
61
+ "@jvs-milkdown/utils": "^1.2.7",
62
62
  "@types/lodash-es": "^4.17.12",
63
63
  "clsx": "^2.0.0",
64
64
  "dompurify": "^3.2.5",
@@ -96,14 +96,16 @@ export const ImageInput = defineComponent<ImageInputProps>({
96
96
  const isValidUrl = (url: string) => {
97
97
  if (!url) return false
98
98
  const trimmedUrl = url.trim()
99
- return /^(https?:\/\/|\/|\.\.?\/|data:image\/|blob:|[a-zA-Z0-9-]+\.[a-zA-Z]+)/i.test(trimmedUrl)
99
+ return /^(https?:\/\/|\/|\.\.?\/|data:image\/|blob:|[a-zA-Z0-9-]+\.[a-zA-Z]+)/i.test(
100
+ trimmedUrl
101
+ )
100
102
  }
101
103
 
102
104
  const onKeydown = (e: KeyboardEvent) => {
103
105
  if (e.key === 'Enter') {
104
106
  const val = linkInputRef.value?.value?.trim() ?? ''
105
107
 
106
- // Stop bubbling so ProseMirror doesn't handle the Enter key
108
+ // Stop bubbling so ProseMirror doesn't handle the Enter key
107
109
  // regardless of whether the URL is valid or not.
108
110
  e.preventDefault()
109
111
  e.stopPropagation()
@@ -185,15 +187,22 @@ export const ImageInput = defineComponent<ImageInputProps>({
185
187
  src={currentLink.value}
186
188
  alt=""
187
189
  onError={(e) =>
188
- Promise.resolve(onImageLoadError?.(e)).catch(() => { })
190
+ Promise.resolve(onImageLoadError?.(e)).catch(() => {})
189
191
  }
190
192
  />
191
193
  </div>
192
194
  )}
193
195
  <div
194
- class={clsx('confirm', !isValidUrl(currentLink.value) && 'disabled')}
196
+ class={clsx(
197
+ 'confirm',
198
+ !isValidUrl(currentLink.value) && 'disabled'
199
+ )}
195
200
  onClick={() => onConfirmLinkInput()}
196
- style={!isValidUrl(currentLink.value) ? { opacity: 0.5, cursor: 'not-allowed' } : undefined}
201
+ style={
202
+ !isValidUrl(currentLink.value)
203
+ ? { opacity: 0.5, cursor: 'not-allowed' }
204
+ : undefined
205
+ }
197
206
  >
198
207
  <Icon icon={confirmButton} />
199
208
  </div>
@@ -7,9 +7,6 @@ export interface ImageBlockConfig {
7
7
  captionIcon: string | undefined
8
8
  cropIcon: string | undefined
9
9
  borderIcon: string | undefined
10
- confirmIcon: string | undefined
11
- cancelIcon: string | undefined
12
- resetCropIcon: string | undefined
13
10
  uploadButton: string | undefined
14
11
  confirmButton: string | undefined
15
12
  uploadPlaceholderText: string
@@ -26,9 +23,6 @@ export const defaultImageBlockConfig: ImageBlockConfig = {
26
23
  captionIcon: '💬',
27
24
  cropIcon: '✂️',
28
25
  borderIcon: '🔲',
29
- confirmIcon: '✓',
30
- cancelIcon: '✕',
31
- resetCropIcon: '↩',
32
26
  uploadButton: 'Upload file',
33
27
  confirmButton: 'Confirm ⏎',
34
28
  uploadPlaceholderText: 'or paste the image link ...',
@@ -21,8 +21,6 @@ function toImageBlock(
21
21
  })
22
22
  }
23
23
 
24
-
25
-
26
24
  /// A paste rule that converts standalone inline images to image-block nodes.
27
25
  /// Handles the slice structures produced when pasting HTML from external pages
28
26
  /// or from another instance of the same editor:
@@ -1,4 +1,4 @@
1
- import { defineComponent, ref, h, Fragment } from 'vue'
1
+ import { defineComponent, ref, h, Fragment, onMounted, onUnmounted } from 'vue'
2
2
 
3
3
  import type { MilkdownImageBlockProps } from './image-block'
4
4
 
@@ -263,6 +263,7 @@ export const ImageViewer = defineComponent<MilkdownImageBlockProps>({
263
263
  }
264
264
 
265
265
  const applyCrop = () => {
266
+ if (!isCropping.value) return
266
267
  setAttr('cropTop', localCrop.value.top)
267
268
  setAttr('cropLeft', localCrop.value.left)
268
269
  setAttr('cropWidth', localCrop.value.width)
@@ -270,10 +271,22 @@ export const ImageViewer = defineComponent<MilkdownImageBlockProps>({
270
271
  isCropping.value = false
271
272
  }
272
273
 
273
- const cancelCrop = () => {
274
- isCropping.value = false
274
+ // Global listener for clicking outside to apply crop
275
+ const handleGlobalPointerDown = (e: PointerEvent) => {
276
+ if (!isCropping.value) return
277
+ if (wrapperRef.value && !wrapperRef.value.contains(e.target as Node)) {
278
+ applyCrop()
279
+ }
275
280
  }
276
281
 
282
+ onMounted(() => {
283
+ window.addEventListener('pointerdown', handleGlobalPointerDown)
284
+ })
285
+
286
+ onUnmounted(() => {
287
+ window.removeEventListener('pointerdown', handleGlobalPointerDown)
288
+ })
289
+
277
290
  const resetCrop = () => {
278
291
  setAttr('cropTop', 0)
279
292
  setAttr('cropLeft', 0)
@@ -507,7 +520,7 @@ export const ImageViewer = defineComponent<MilkdownImageBlockProps>({
507
520
  resetCrop()
508
521
  }}
509
522
  >
510
- <Icon icon={config.resetCropIcon ?? '↩'} />
523
+ <Icon icon={'↩'} />
511
524
  </div>
512
525
  ) : null}
513
526
  {config.borderIcon ? (
@@ -655,9 +668,16 @@ export const ImageViewer = defineComponent<MilkdownImageBlockProps>({
655
668
 
656
669
  {/* Crop overlay */}
657
670
  {isCropping.value ? (
658
- <div class="crop-overlay" onClick={(e) => e.stopPropagation()}>
671
+ <div
672
+ class="crop-overlay"
673
+ onClick={(e) => {
674
+ e.stopPropagation()
675
+ applyCrop()
676
+ }}
677
+ >
659
678
  <div
660
679
  class="crop-box"
680
+ onClick={(e) => e.stopPropagation()}
661
681
  style={{
662
682
  top: `${(localCrop.value.top * 100).toFixed(2)}%`,
663
683
  left: `${(localCrop.value.left * 100).toFixed(2)}%`,
@@ -726,27 +746,6 @@ export const ImageViewer = defineComponent<MilkdownImageBlockProps>({
726
746
  }
727
747
  />
728
748
  </div>
729
- {/* Bottom toolbar */}
730
- <div class="crop-toolbar">
731
- <div
732
- class="crop-toolbar-btn"
733
- onClick={(e: MouseEvent) => {
734
- e.stopPropagation()
735
- applyCrop()
736
- }}
737
- >
738
- <Icon icon={config.confirmIcon ?? '✓'} />
739
- </div>
740
- <div
741
- class="crop-toolbar-btn"
742
- onClick={(e: MouseEvent) => {
743
- e.stopPropagation()
744
- cancelCrop()
745
- }}
746
- >
747
- <Icon icon={config.cancelIcon ?? '✕'} />
748
- </div>
749
- </div>
750
749
  </div>
751
750
  ) : null}
752
751
 
@@ -45,7 +45,9 @@ export const EditLink = defineComponent<EditLinkProps>({
45
45
  if (!url) return false
46
46
  const trimmedUrl = url.trim()
47
47
  // Accept standard http/https links, relative links, anchors, or mailto
48
- return /^(https?:\/\/|\/|\.\.?\/|mailto:|#|[a-zA-Z0-9-]+\.[a-zA-Z]+)/i.test(trimmedUrl)
48
+ return /^(https?:\/\/|\/|\.\.?\/|mailto:|#|[a-zA-Z0-9-]+\.[a-zA-Z]+)/i.test(
49
+ trimmedUrl
50
+ )
49
51
  }
50
52
 
51
53
  const onConfirmEdit = () => {
@@ -84,7 +86,10 @@ export const EditLink = defineComponent<EditLinkProps>({
84
86
  />
85
87
  {link.value ? (
86
88
  <Icon
87
- class={clsx('button confirm', !isValidUrl(link.value) && 'disabled')}
89
+ class={clsx(
90
+ 'button confirm',
91
+ !isValidUrl(link.value) && 'disabled'
92
+ )}
88
93
  icon={config.value.confirmButton}
89
94
  onClick={onConfirmEdit}
90
95
  />
@@ -400,7 +400,7 @@ export const TableBlock = defineComponent<TableBlockProps>({
400
400
  if (isCellSelection(selection)) {
401
401
  const selAny = selection as any
402
402
  const rect = selectedRect(view.state)
403
-
403
+
404
404
  if (selAny.isColSelection()) {
405
405
  if (
406
406
  activeColIndex.value >= rect.left &&
@@ -1,10 +1,10 @@
1
1
  import type { Node } from '@jvs-milkdown/prose/model'
2
+ import type { CellSelection } from '@jvs-milkdown/prose/tables'
2
3
  import type { EditorView } from '@jvs-milkdown/prose/view'
3
4
  import type { Ref } from 'vue'
4
5
 
5
6
  import { findParent } from '@jvs-milkdown/prose'
6
7
  import { findTable } from '@jvs-milkdown/prose/tables'
7
- import type { CellSelection } from '@jvs-milkdown/prose/tables'
8
8
 
9
9
  import type { CellIndex, Refs } from './types'
10
10