@tnotesjs/ui 0.1.5 → 0.2.1

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 (65) hide show
  1. package/README.md +30 -97
  2. package/dist/code/highlight.js +6 -15
  3. package/dist/components/Footprints/parse.js +1 -3
  4. package/dist/components/Mindmap/markdown.d.ts +1 -7
  5. package/dist/components/Mindmap/markdown.js +1 -15
  6. package/package.json +26 -9
  7. package/src/browser/clipboard.ts +16 -19
  8. package/src/browser/theme.ts +10 -15
  9. package/src/code/highlight.ts +89 -105
  10. package/src/components/Badge/Badge.vue +4 -4
  11. package/src/components/CodeBlock/CodeBlock.vue +43 -53
  12. package/src/components/CodeGroup/CodeGroup.vue +32 -35
  13. package/src/components/Footprints/Footprints.vue +2 -14
  14. package/src/components/Footprints/parse.ts +1 -3
  15. package/src/components/ImagePreview/ImagePreview.vue +101 -126
  16. package/src/components/Mermaid/Mermaid.vue +9 -12
  17. package/src/components/Mindmap/FocusBreadcrumbs.vue +31 -28
  18. package/src/components/Mindmap/InlineRuns.ts +25 -16
  19. package/src/components/Mindmap/Mindmap.vue +147 -81
  20. package/src/components/Mindmap/MindmapOutlineNode.vue +4 -1
  21. package/src/components/Mindmap/editor/AppIcon.vue +32 -11
  22. package/src/components/Mindmap/editor/CanvasContextMenu.vue +126 -49
  23. package/src/components/Mindmap/editor/LinkPopover.vue +101 -23
  24. package/src/components/Mindmap/editor/MarkdownView.vue +7 -4
  25. package/src/components/Mindmap/editor/MindmapView.vue +11 -3
  26. package/src/components/Mindmap/editor/OutlineView.vue +144 -58
  27. package/src/components/Mindmap/editor/RichInlineEditor.vue +93 -44
  28. package/src/components/Mindmap/editor/SelectionToolbar.vue +133 -41
  29. package/src/components/Mindmap/editor/canvasClipboard.ts +1 -5
  30. package/src/components/Mindmap/editor/imagePaste.ts +1 -1
  31. package/src/components/Mindmap/editor/mindmapClipboard.ts +17 -5
  32. package/src/components/Mindmap/editor/outlineDrag.ts +1 -1
  33. package/src/components/Mindmap/markdown.ts +10 -29
  34. package/src/components/WordList/RightClickMenu.vue +10 -35
  35. package/src/components/WordList/WordList.vue +19 -42
  36. package/src/entries/bilibili-video.ts +1 -1
  37. package/src/entries/excalidraw-editor.ts +20 -0
  38. package/src/entries/excalidraw-view.ts +15 -0
  39. package/src/entries/footprints.ts +1 -1
  40. package/src/entries/mermaid.ts +1 -1
  41. package/src/entries/mindmap.ts +1 -1
  42. package/src/entries/notes-table.ts +2 -2
  43. package/src/entries/word-list.ts +1 -1
  44. package/src/excalidraw/ExcalidrawSvg.vue +125 -0
  45. package/src/excalidraw/editorHost.ts +110 -0
  46. package/src/excalidraw/editorSession.ts +224 -0
  47. package/src/excalidraw/exporter.ts +50 -0
  48. package/src/excalidraw/focus.ts +33 -0
  49. package/src/excalidraw/fonts.ts +92 -0
  50. package/src/excalidraw/renderer.ts +105 -0
  51. package/src/excalidraw/scene.ts +93 -0
  52. package/src/index.ts +25 -33
  53. package/src/markdown/image.ts +4 -13
  54. package/src/styles/code.css +5 -5
  55. package/src/styles/prose.css +1 -1
  56. package/src/styles/swiper.css +1 -1
  57. package/src/styles/tokens.css +5 -6
  58. package/src/swiper/hydrate.ts +91 -102
  59. package/src/code/highlight.test.ts +0 -55
  60. package/src/components/Mermaid/Mermaid.test.ts +0 -54
  61. package/src/components/Mermaid/adoptLiveSvg.test.ts +0 -16
  62. package/src/components/Mindmap/FocusBreadcrumbs.test.ts +0 -265
  63. package/src/components/sharedMedia.test.ts +0 -139
  64. package/src/markdown/image.test.ts +0 -88
  65. package/src/swiper/hydrate.test.ts +0 -65
@@ -6,9 +6,15 @@ import {
6
6
  parseMarkdown,
7
7
  richSelectionRect,
8
8
  serializeSubtree,
9
- wrapTextLines,
9
+ wrapTextLines
10
+ } from '@tnotesjs/mindmap-core'
11
+ import type {
12
+ InlineFormat,
13
+ InlineLink,
14
+ MindmapNode,
15
+ MindmapSession,
16
+ RichInlineEditorElement
10
17
  } from '@tnotesjs/mindmap-core'
11
- import type { InlineFormat, InlineLink, MindmapNode, MindmapSession, RichInlineEditorElement } from '@tnotesjs/mindmap-core'
12
18
  import LinkPopover from './LinkPopover.vue'
13
19
  import RichInlineEditor from './RichInlineEditor.vue'
14
20
  import SelectionToolbar from './SelectionToolbar.vue'
@@ -104,7 +110,7 @@ function measureTextBlockHeight(
104
110
  maxWidth: number,
105
111
  fontSize: number,
106
112
  lineHeight: number,
107
- minHeight: number,
113
+ minHeight: number
108
114
  ): number {
109
115
  const lines = wrapTextLines(text || ' ', maxWidth, (s) => measureOutlineText(s, fontSize))
110
116
  return Math.max(minHeight, lines.length * lineHeight + 8)
@@ -161,8 +167,7 @@ const titleHeight = computed(() => {
161
167
  const root = props.session.focusRootNode
162
168
  const draft = draftTextHeights.value.get(root.id)
163
169
  const textH =
164
- draft ??
165
- measureTextBlockHeight(layoutDisplayText(root), contentAreaWidth(), 28, TITLE_LINE, 56)
170
+ draft ?? measureTextBlockHeight(layoutDisplayText(root), contentAreaWidth(), 28, TITLE_LINE, 56)
166
171
  return textH + imageBlockHeight(root)
167
172
  })
168
173
 
@@ -349,7 +354,7 @@ function selectionPosition(input: RichInlineEditorElement): { left: number; top:
349
354
  const rect = richSelectionRect(input)
350
355
  return {
351
356
  left: Math.max(190, Math.min(window.innerWidth - 190, rect.left + rect.width / 2)),
352
- top: Math.max(58, rect.top - 8),
357
+ top: Math.max(58, rect.top - 8)
353
358
  }
354
359
  }
355
360
 
@@ -372,17 +377,21 @@ const selectedTextFormats = computed<Partial<Record<InlineFormat, boolean>>>(()
372
377
  const selection = textSelection.value
373
378
  if (!selection) return {}
374
379
  const formats: InlineFormat[] = ['bold', 'italic', 'underline', 'strike', 'highlight', 'code']
375
- return Object.fromEntries(formats.map((format) => [
376
- format,
377
- props.session.inlineFormatActive(selection.nodeId, selection.start, selection.end, format),
378
- ]))
380
+ return Object.fromEntries(
381
+ formats.map((format) => [
382
+ format,
383
+ props.session.inlineFormatActive(selection.nodeId, selection.start, selection.end, format)
384
+ ])
385
+ )
379
386
  })
380
387
 
381
388
  const multiSelectionPosition = computed(() => {
382
389
  if (props.version < 0 || props.session.selectionIds.size <= 1 || textSelection.value) return null
383
390
  const id = props.session.selectedNode?.id
384
391
  if (!id) return null
385
- const row = containerRef.value?.querySelector(`.outline-row[data-node-id="${id}"]`) as HTMLElement | null
392
+ const row = containerRef.value?.querySelector(
393
+ `.outline-row[data-node-id="${id}"]`
394
+ ) as HTMLElement | null
386
395
  if (!row) return null
387
396
  const rect = row.getBoundingClientRect()
388
397
  if (rect.width === 0 && rect.height === 0) {
@@ -391,7 +400,7 @@ const multiSelectionPosition = computed(() => {
391
400
  if (rect.bottom < 48 || rect.top > window.innerHeight) return null
392
401
  return {
393
402
  left: Math.max(180, Math.min(window.innerWidth - 180, rect.left + rect.width / 2)),
394
- top: Math.max(58, rect.top - 8),
403
+ top: Math.max(58, rect.top - 8)
395
404
  }
396
405
  })
397
406
 
@@ -474,7 +483,7 @@ function openSelectionLinkEditor() {
474
483
  start: selection.start,
475
484
  end: selection.end,
476
485
  url: 'https://',
477
- position: { left: selection.position.left, top: selection.position.top + 12 },
486
+ position: { left: selection.position.left, top: selection.position.top + 12 }
478
487
  }
479
488
  nextTick(() => linkPopoverRef.value?.focusInput())
480
489
  }
@@ -603,7 +612,7 @@ function syncDraftText(node: MindmapNode, depth: number | null, text: string) {
603
612
  maxW,
604
613
  isTitle ? 28 : 15,
605
614
  isTitle ? TITLE_LINE : TEXT_LINE,
606
- isTitle ? 56 : ROW_HEIGHT,
615
+ isTitle ? 56 : ROW_HEIGHT
607
616
  )
608
617
  if (draftTextHeights.value.get(node.id) !== h) {
609
618
  const next = new Map(draftTextHeights.value)
@@ -651,10 +660,13 @@ function caretVisualBoundary(input: RichInlineEditorElement): { first: boolean;
651
660
  'tabSize',
652
661
  'whiteSpace',
653
662
  'wordBreak',
654
- 'overflowWrap',
663
+ 'overflowWrap'
655
664
  ] as const
656
665
  for (const property of copied) {
657
- mirror.style.setProperty(property.replace(/[A-Z]/g, (ch) => `-${ch.toLowerCase()}`), style[property] as string)
666
+ mirror.style.setProperty(
667
+ property.replace(/[A-Z]/g, (ch) => `-${ch.toLowerCase()}`),
668
+ style[property] as string
669
+ )
658
670
  }
659
671
  mirror.style.position = 'fixed'
660
672
  mirror.style.left = '-10000px'
@@ -696,7 +708,11 @@ function inlineFormatShortcut(event: KeyboardEvent, hasSelection = true): Inline
696
708
  return null
697
709
  }
698
710
 
699
- function applyInputFormatShortcut(node: MindmapNode, input: RichInlineEditorElement, format: InlineFormat) {
711
+ function applyInputFormatShortcut(
712
+ node: MindmapNode,
713
+ input: RichInlineEditorElement,
714
+ format: InlineFormat
715
+ ) {
700
716
  const caretStart = input.selectionStart ?? 0
701
717
  const caretEnd = input.selectionEnd ?? 0
702
718
  if (input.value.length === 0) return
@@ -713,7 +729,12 @@ function applyInputFormatShortcut(node: MindmapNode, input: RichInlineEditorElem
713
729
  editor?.setSelectionRange(caretStart, caretStart)
714
730
  })
715
731
  } else {
716
- const selection: TextSelectionState = { nodeId: node.id, start, end, position: selectionPosition(input) }
732
+ const selection: TextSelectionState = {
733
+ nodeId: node.id,
734
+ start,
735
+ end,
736
+ position: selectionPosition(input)
737
+ }
717
738
  textSelection.value = selection
718
739
  restoreTextSelection(selection)
719
740
  }
@@ -736,7 +757,12 @@ function applyInputClearFormats(node: MindmapNode, input: RichInlineEditorElemen
736
757
  editor?.setSelectionRange(caretStart, caretStart)
737
758
  })
738
759
  } else {
739
- const selection: TextSelectionState = { nodeId: node.id, start, end, position: selectionPosition(input) }
760
+ const selection: TextSelectionState = {
761
+ nodeId: node.id,
762
+ start,
763
+ end,
764
+ position: selectionPosition(input)
765
+ }
740
766
  textSelection.value = selection
741
767
  restoreTextSelection(selection)
742
768
  }
@@ -1017,7 +1043,8 @@ function onEditKeydown(node: MindmapNode, e: KeyboardEvent) {
1017
1043
  const prevLen = prevRow.node.content.text.length
1018
1044
  session.transact((doc) => {
1019
1045
  doc.updateDisplayText(prevRow.node, prevRow.node.content.text + value)
1020
- for (const c of [...node.children]) doc.move(c, prevRow.node, prevRow.node.children.length)
1046
+ for (const c of [...node.children])
1047
+ doc.move(c, prevRow.node, prevRow.node.children.length)
1021
1048
  doc.remove(node)
1022
1049
  })
1023
1050
  session.select(prevId)
@@ -1121,7 +1148,9 @@ function selectedRoots(): MindmapNode[] {
1121
1148
  }
1122
1149
 
1123
1150
  function serializeSelection(): string {
1124
- return selectedRoots().map((node) => serializeSubtree(node)).join('\n')
1151
+ return selectedRoots()
1152
+ .map((node) => serializeSubtree(node))
1153
+ .join('\n')
1125
1154
  }
1126
1155
 
1127
1156
  function selectAllRows() {
@@ -1130,7 +1159,7 @@ function selectAllRows() {
1130
1159
  props.session.selectMany(
1131
1160
  list.map((row) => row.node.id),
1132
1161
  list[list.length - 1].node.id,
1133
- list[0].node.id,
1162
+ list[0].node.id
1134
1163
  )
1135
1164
  containerRef.value?.focus()
1136
1165
  }
@@ -1178,7 +1207,8 @@ function extendSelectionTo(index: number) {
1178
1207
  const list = rows.value
1179
1208
  const target = list[index]
1180
1209
  if (!target) return
1181
- const anchorId = props.session.selectionAnchor?.id ?? props.session.selectedNode?.id ?? target.node.id
1210
+ const anchorId =
1211
+ props.session.selectionAnchor?.id ?? props.session.selectedNode?.id ?? target.node.id
1182
1212
  const anchorIndex = list.findIndex((row) => row.node.id === anchorId)
1183
1213
  const normalizedAnchor = anchorIndex >= 0 ? anchorIndex : index
1184
1214
  const from = Math.min(normalizedAnchor, index)
@@ -1186,7 +1216,7 @@ function extendSelectionTo(index: number) {
1186
1216
  props.session.selectMany(
1187
1217
  list.slice(from, to + 1).map((row) => row.node.id),
1188
1218
  target.node.id,
1189
- list[normalizedAnchor].node.id,
1219
+ list[normalizedAnchor].node.id
1190
1220
  )
1191
1221
  scrollRowIntoView(index)
1192
1222
  }
@@ -1196,7 +1226,12 @@ function extendSelection(direction: -1 | 1) {
1196
1226
  if (list.length === 0) return
1197
1227
  const selected = props.session.selectedNode
1198
1228
  const current = selected ? list.findIndex((row) => row.node.id === selected.id) : -1
1199
- const next = current < 0 ? (direction > 0 ? 0 : list.length - 1) : Math.max(0, Math.min(list.length - 1, current + direction))
1229
+ const next =
1230
+ current < 0
1231
+ ? direction > 0
1232
+ ? 0
1233
+ : list.length - 1
1234
+ : Math.max(0, Math.min(list.length - 1, current + direction))
1200
1235
  extendSelectionTo(next)
1201
1236
  }
1202
1237
 
@@ -1264,9 +1299,10 @@ function onCopy(node: MindmapNode, e: ClipboardEvent) {
1264
1299
  const input = e.currentTarget as RichInlineEditorElement
1265
1300
  if (input.selectionStart !== input.selectionEnd) return
1266
1301
  e.preventDefault()
1267
- const text = props.session.selectionIds.size > 1 && props.session.selectionIds.has(node.id)
1268
- ? serializeSelection()
1269
- : serializeSubtree(node)
1302
+ const text =
1303
+ props.session.selectionIds.size > 1 && props.session.selectionIds.has(node.id)
1304
+ ? serializeSelection()
1305
+ : serializeSubtree(node)
1270
1306
  e.clipboardData?.setData('text/plain', text)
1271
1307
  }
1272
1308
 
@@ -1369,8 +1405,8 @@ function onInlineLinkEnter(node: MindmapNode, link: InlineLink, event: MouseEven
1369
1405
  url: link.url,
1370
1406
  position: {
1371
1407
  left: Math.max(220, Math.min(window.innerWidth - 220, rect.left + rect.width / 2)),
1372
- top: Math.min(window.innerHeight - 54, rect.bottom + 6),
1373
- },
1408
+ top: Math.min(window.innerHeight - 54, rect.bottom + 6)
1409
+ }
1374
1410
  }
1375
1411
  }
1376
1412
 
@@ -1433,7 +1469,7 @@ function onImageResizePointerDown(node: MindmapNode, e: PointerEvent) {
1433
1469
  imgResize = {
1434
1470
  id: node.id,
1435
1471
  startX: e.clientX,
1436
- startW: node.content.image.width ?? DEFAULT_OUTLINE_IMG_W,
1472
+ startW: node.content.image.width ?? DEFAULT_OUTLINE_IMG_W
1437
1473
  }
1438
1474
  suppressRowClick = true
1439
1475
  window.addEventListener('pointermove', onImageResizeMove)
@@ -1442,7 +1478,10 @@ function onImageResizePointerDown(node: MindmapNode, e: PointerEvent) {
1442
1478
 
1443
1479
  function onImageResizeMove(e: PointerEvent) {
1444
1480
  if (!imgResize) return
1445
- const next = Math.max(MIN_IMG_W, Math.min(MAX_IMG_W, Math.round(imgResize.startW + (e.clientX - imgResize.startX))))
1481
+ const next = Math.max(
1482
+ MIN_IMG_W,
1483
+ Math.min(MAX_IMG_W, Math.round(imgResize.startW + (e.clientX - imgResize.startX)))
1484
+ )
1446
1485
  const map = new Map(liveImageWidth.value)
1447
1486
  map.set(imgResize.id, next)
1448
1487
  liveImageWidth.value = map
@@ -1626,7 +1665,8 @@ let stopRangeSelect: (() => void) | null = null
1626
1665
  function onRowPointerDown(row: Row, e: PointerEvent) {
1627
1666
  if (e.button !== 0) return
1628
1667
  const target = e.target as HTMLElement | null
1629
- if (target?.closest('.row-gutter, .row-checkbox, .row-badge, .row-image, .row-image-handle')) return
1668
+ if (target?.closest('.row-gutter, .row-checkbox, .row-badge, .row-image, .row-image-handle'))
1669
+ return
1630
1670
 
1631
1671
  if (e.shiftKey) {
1632
1672
  e.preventDefault()
@@ -1675,7 +1715,7 @@ function onRowPointerDown(row: Row, e: PointerEvent) {
1675
1715
  props.session.selectMany(
1676
1716
  selected.map((item) => item.node.id),
1677
1717
  rows.value[index]?.node.id ?? null,
1678
- rows.value[startIndex]?.node.id ?? null,
1718
+ rows.value[startIndex]?.node.id ?? null
1679
1719
  )
1680
1720
  }
1681
1721
  const onUp = (ev: PointerEvent) => {
@@ -1754,7 +1794,7 @@ function onGripPointerDown(node: MindmapNode, e: PointerEvent) {
1754
1794
  dragging: false,
1755
1795
  pointerX: e.clientX,
1756
1796
  pointerY: e.clientY,
1757
- indicator: null,
1797
+ indicator: null
1758
1798
  }
1759
1799
  let finished = false
1760
1800
  const startX = e.clientX
@@ -1826,7 +1866,9 @@ function calcDropIndicator(clientX: number, clientY: number, dragId: string): Dr
1826
1866
  const idx = rowIndexAt(clientY)
1827
1867
  if (idx < 0) return null
1828
1868
  const hit = list[idx]
1829
- const hitEl = el.querySelector(`.outline-row[data-node-id="${hit.node.id}"]`) as HTMLElement | null
1869
+ const hitEl = el.querySelector(
1870
+ `.outline-row[data-node-id="${hit.node.id}"]`
1871
+ ) as HTMLElement | null
1830
1872
  if (!hitEl) return null
1831
1873
  const hitRect = hitEl.getBoundingClientRect()
1832
1874
  const midY = (hitRect.top + hitRect.bottom) / 2
@@ -1856,7 +1898,9 @@ function calcDropIndicator(clientX: number, clientY: number, dragId: string): Dr
1856
1898
  }
1857
1899
  const anchor = list[anchorIdx]
1858
1900
 
1859
- const anchorEl = el.querySelector(`.outline-row[data-node-id="${anchor.node.id}"]`) as HTMLElement | null
1901
+ const anchorEl = el.querySelector(
1902
+ `.outline-row[data-node-id="${anchor.node.id}"]`
1903
+ ) as HTMLElement | null
1860
1904
  const anchorRect = (anchorEl ?? hitEl).getBoundingClientRect()
1861
1905
  const indentEdge = anchorRect.left + anchor.depth * INDENT
1862
1906
  const hasVisibleKids = anchor.node.children.some((c) => c.id !== dragId) && !anchor.node.collapsed
@@ -1873,7 +1917,7 @@ function calcDropIndicator(clientX: number, clientY: number, dragId: string): Dr
1873
1917
  clientX,
1874
1918
  indentEdge,
1875
1919
  anchor.depth,
1876
- INDENT,
1920
+ INDENT
1877
1921
  )
1878
1922
  const place = resolved.node
1879
1923
  const depth = resolved.depth
@@ -1890,7 +1934,7 @@ function lineIndicator(
1890
1934
  type: 'before' | 'after' | 'child',
1891
1935
  row: Row,
1892
1936
  lineDepth: number,
1893
- spacer: HTMLElement,
1937
+ spacer: HTMLElement
1894
1938
  ): DropIndicator {
1895
1939
  const top =
1896
1940
  type === 'before'
@@ -1906,7 +1950,7 @@ function lineIndicator(
1906
1950
  top,
1907
1951
  left: lineLeft,
1908
1952
  width,
1909
- guideDepth: type === 'child' ? row.depth : null,
1953
+ guideDepth: type === 'child' ? row.depth : null
1910
1954
  }
1911
1955
  }
1912
1956
 
@@ -1942,7 +1986,15 @@ function onDragUp() {
1942
1986
  @copy="onContainerCopy"
1943
1987
  @cut="onContainerCut"
1944
1988
  >
1945
- <div v-if="focusRoot" class="outline-title" :class="{ 'is-selected': focusRoot.id === selectedId, 'has-image': !!focusRoot.content.image }" :style="{ minHeight: `${titleHeight}px` }">
1989
+ <div
1990
+ v-if="focusRoot"
1991
+ class="outline-title"
1992
+ :class="{
1993
+ 'is-selected': focusRoot.id === selectedId,
1994
+ 'has-image': !!focusRoot.content.image
1995
+ }"
1996
+ :style="{ minHeight: `${titleHeight}px` }"
1997
+ >
1946
1998
  <RichInlineEditor
1947
1999
  class="title-input"
1948
2000
  :class="{ 'is-view-mode': focusedId !== focusRoot.id }"
@@ -1999,7 +2051,7 @@ function onDragUp() {
1999
2051
  'is-matched': matches.has(row.node.id),
2000
2052
  'is-hovered': hoveredId === row.node.id,
2001
2053
  'has-image': !!row.node.content.image,
2002
- 'is-drag-source': drag?.dragging && drag.id === row.node.id,
2054
+ 'is-drag-source': drag?.dragging && drag.id === row.node.id
2003
2055
  }"
2004
2056
  :style="{ top: `${row.top}px`, height: `${row.height}px` }"
2005
2057
  @mouseenter="hoveredId = row.node.id"
@@ -2035,25 +2087,32 @@ function onDragUp() {
2035
2087
  class="row-collapse"
2036
2088
  :class="{
2037
2089
  collapsed: row.node.collapsed,
2038
- visible: hoveredId === row.node.id || row.node.collapsed,
2090
+ visible: hoveredId === row.node.id || row.node.collapsed
2091
+ }"
2092
+ :style="{
2093
+ left: `${row.depth > 0 ? (row.depth - 1) * INDENT + BULLET_CENTER - COLLAPSE_LEAD / 2 : 0}px`
2039
2094
  }"
2040
- :style="{ left: `${row.depth > 0 ? (row.depth - 1) * INDENT + BULLET_CENTER - COLLAPSE_LEAD / 2 : 0}px` }"
2041
2095
  title="折叠/展开"
2042
2096
  @click="onArrowClick(row.node, $event)"
2043
2097
  @pointerdown.stop="onGripPointerDown(row.node, $event)"
2044
2098
  />
2045
2099
  <span
2046
2100
  class="row-bullet"
2047
- :class="{ 'has-children': row.node.children.length > 0, collapsed: row.node.collapsed }"
2101
+ :class="{
2102
+ 'has-children': row.node.children.length > 0,
2103
+ collapsed: row.node.collapsed
2104
+ }"
2048
2105
  :style="{ left: `${COLLAPSE_LEAD + row.depth * INDENT}px` }"
2049
2106
  title="点击进入主题"
2050
2107
  @click="onBulletClick(row.node, $event)"
2051
2108
  @pointerdown.stop="onGripPointerDown(row.node, $event)"
2052
2109
  >
2053
2110
  <i class="bullet-dot" />
2054
- <span v-if="row.node.collapsed && row.node.children.length > 0" class="bullet-count">{{
2055
- descendantCount(row.node)
2056
- }}</span>
2111
+ <span
2112
+ v-if="row.node.collapsed && row.node.children.length > 0"
2113
+ class="bullet-count"
2114
+ >{{ descendantCount(row.node) }}</span
2115
+ >
2057
2116
  </span>
2058
2117
  </span>
2059
2118
 
@@ -2067,14 +2126,16 @@ function onDragUp() {
2067
2126
  :title="row.node.content.checked ? '标记为未完成' : '标记为已完成'"
2068
2127
  @click="onCheckboxClick(row.node, $event)"
2069
2128
  >
2070
- <svg v-if="row.node.content.checked" viewBox="0 0 16 16" aria-hidden="true"><path d="m3.5 8 3 3 6-6" /></svg>
2129
+ <svg v-if="row.node.content.checked" viewBox="0 0 16 16" aria-hidden="true">
2130
+ <path d="m3.5 8 3 3 6-6" />
2131
+ </svg>
2071
2132
  </button>
2072
2133
 
2073
2134
  <RichInlineEditor
2074
2135
  class="row-input"
2075
2136
  :class="{
2076
2137
  'is-task-done': row.node.content.checked === true,
2077
- 'is-view-mode': focusedId !== row.node.id,
2138
+ 'is-view-mode': focusedId !== row.node.id
2078
2139
  }"
2079
2140
  :editor-id="row.node.id"
2080
2141
  :raw="row.node.content.image ? row.node.content.text : row.node.content.raw"
@@ -2134,7 +2195,7 @@ function onDragUp() {
2134
2195
  :style="{
2135
2196
  top: `${drag.indicator.top}px`,
2136
2197
  left: `${drag.indicator.left}px`,
2137
- width: `${drag.indicator.width}px`,
2198
+ width: `${drag.indicator.width}px`
2138
2199
  }"
2139
2200
  />
2140
2201
  </div>
@@ -2147,7 +2208,13 @@ function onDragUp() {
2147
2208
  <i class="bullet-dot" />
2148
2209
  </div>
2149
2210
 
2150
- <input ref="imagePickerRef" type="file" accept="image/*" hidden @change="onSelectionImagePicked" />
2211
+ <input
2212
+ ref="imagePickerRef"
2213
+ type="file"
2214
+ accept="image/*"
2215
+ hidden
2216
+ @change="onSelectionImagePicked"
2217
+ />
2151
2218
 
2152
2219
  <SelectionToolbar
2153
2220
  v-if="textSelection && !linkEditor"
@@ -2236,7 +2303,9 @@ function onDragUp() {
2236
2303
  color: var(--mm-text-dim);
2237
2304
  font-weight: 500;
2238
2305
  }
2239
- .title-input.is-view-mode { cursor: text; }
2306
+ .title-input.is-view-mode {
2307
+ cursor: text;
2308
+ }
2240
2309
  .title-image {
2241
2310
  flex: none;
2242
2311
  padding: 0 0 8px;
@@ -2454,16 +2523,31 @@ function onDragUp() {
2454
2523
  background: transparent;
2455
2524
  cursor: pointer;
2456
2525
  color: var(--mm-text-dim);
2457
- transition: border-color .12s, background .12s, box-shadow .12s;
2526
+ transition:
2527
+ border-color 0.12s,
2528
+ background 0.12s,
2529
+ box-shadow 0.12s;
2458
2530
  }
2459
2531
  .row-checkbox.checked {
2460
2532
  border-color: var(--mm-accent);
2461
2533
  background: var(--mm-accent);
2462
2534
  color: white;
2463
2535
  }
2464
- .row-checkbox:hover { border-color: var(--mm-accent); }
2465
- .row-checkbox:focus-visible { box-shadow: 0 0 0 2px color-mix(in srgb, var(--mm-accent) 28%, transparent); }
2466
- .row-checkbox svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
2536
+ .row-checkbox:hover {
2537
+ border-color: var(--mm-accent);
2538
+ }
2539
+ .row-checkbox:focus-visible {
2540
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--mm-accent) 28%, transparent);
2541
+ }
2542
+ .row-checkbox svg {
2543
+ width: 14px;
2544
+ height: 14px;
2545
+ fill: none;
2546
+ stroke: currentColor;
2547
+ stroke-width: 2;
2548
+ stroke-linecap: round;
2549
+ stroke-linejoin: round;
2550
+ }
2467
2551
  .row-input {
2468
2552
  flex: 1;
2469
2553
  min-width: 60px;
@@ -2486,7 +2570,9 @@ function onDragUp() {
2486
2570
  background: transparent;
2487
2571
  box-shadow: none;
2488
2572
  }
2489
- .row-input.is-view-mode { cursor: text; }
2573
+ .row-input.is-view-mode {
2574
+ cursor: text;
2575
+ }
2490
2576
  .row-input.is-task-done {
2491
2577
  text-decoration: line-through;
2492
2578
  color: var(--mm-text-dim);