@tnotesjs/core 0.2.2 → 0.4.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 (47) hide show
  1. package/README.md +0 -1
  2. package/config/defaultConfig.ts +33 -4
  3. package/config/index.ts +6 -0
  4. package/core/NoteIndexCache.ts +7 -1
  5. package/core/NoteManager.ts +18 -30
  6. package/dist/{chunk-5Y5IYS6C.js → chunk-UJG3UU5X.js} +21 -5
  7. package/dist/{chunk-5QN44ES5.js → chunk-XCWFZLER.js} +48 -368
  8. package/dist/cli/index.js +26 -78
  9. package/dist/index.js +1 -1
  10. package/dist/vitepress/config/index.js +159 -18
  11. package/package.json +3 -6
  12. package/services/index.ts +0 -1
  13. package/services/init-sub-repo/initSubRepoLogic.ts +0 -1
  14. package/services/note/service.ts +0 -1
  15. package/types/config.ts +0 -3
  16. package/types/note.ts +0 -2
  17. package/vitepress/assets/icons/icon__cursor.svg +4 -0
  18. package/vitepress/assets/icons/index.ts +1 -0
  19. package/vitepress/components/Layout/AboutPanel.vue +0 -24
  20. package/vitepress/components/Layout/DocBeforeControls.vue +6 -14
  21. package/vitepress/components/Layout/DocFooter.vue +3 -3
  22. package/vitepress/components/Layout/Layout.vue +0 -30
  23. package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +20 -11
  24. package/vitepress/components/Layout/homeReadme.data.ts +0 -48
  25. package/vitepress/components/MindmapPreview/InlineRuns.ts +25 -0
  26. package/vitepress/components/MindmapPreview/MindmapOutlineNode.vue +62 -0
  27. package/vitepress/components/MindmapPreview/MindmapPreview.vue +287 -0
  28. package/vitepress/components/MindmapPreview/compat.test.ts +93 -0
  29. package/vitepress/components/MindmapPreview/compat.ts +128 -0
  30. package/vitepress/components/MindmapPreview/expandLevel.test.ts +40 -0
  31. package/vitepress/components/MindmapPreview/expandLevel.ts +28 -0
  32. package/vitepress/components/MindmapPreview/legacyCorpus.test.ts +57 -0
  33. package/vitepress/components/Settings/Settings.vue +76 -73
  34. package/vitepress/components/SidebarCard/FolderTreeItems.vue +16 -6
  35. package/vitepress/components/SidebarCard/SidebarCard.vue +21 -27
  36. package/vitepress/components/composables/useLocalIde.ts +83 -0
  37. package/vitepress/components/constants.ts +5 -6
  38. package/vitepress/components/utils/vscodePaths.test.ts +22 -0
  39. package/vitepress/components/utils/vscodePaths.ts +24 -2
  40. package/vitepress/configs/markdown.config.ts +77 -20
  41. package/vitepress/theme/index.ts +4 -2
  42. package/vitepress/theme/styles/doc-layout.scss +4 -4
  43. package/vitepress/theme/styles/index.scss +0 -1
  44. package/services/timestamp/index.ts +0 -7
  45. package/services/timestamp/service.ts +0 -465
  46. package/vitepress/components/MarkMap/MarkMap.vue +0 -625
  47. package/vitepress/theme/styles/components/markmap.scss +0 -122
@@ -4,7 +4,7 @@ vitepress/components/Layout/DocFooter.vue
4
4
 
5
5
  <template>
6
6
  <nav class="custom-doc-footer" v-if="prev || next">
7
- <div class="container">
7
+ <div class="pager-container">
8
8
  <div class="prev">
9
9
  <a v-if="prev" :href="prev.link" class="pager-link prev-link">
10
10
  <span class="desc">上一篇</span>
@@ -147,7 +147,7 @@ const next = computed(() => {
147
147
  padding-top: 32px;
148
148
  }
149
149
 
150
- .container {
150
+ .pager-container {
151
151
  display: flex;
152
152
  gap: 16px;
153
153
  }
@@ -219,7 +219,7 @@ const next = computed(() => {
219
219
 
220
220
  /* 响应式调整 */
221
221
  @media (max-width: 768px) {
222
- .container {
222
+ .pager-container {
223
223
  flex-direction: column;
224
224
  gap: 12px;
225
225
  }
@@ -32,10 +32,6 @@ vitepress/components/Layout/Layout.vue
32
32
  :vscode-notes-dir="vscodeNotesDir"
33
33
  :is-home-readme="isHomeReadme"
34
34
  :current-note-id="currentNoteId"
35
- :created_at="created_at"
36
- :updated_at="updated_at"
37
- :home-readme-created-at="homeReadmeCreatedAt"
38
- :home-readme-updated-at="homeReadmeUpdatedAt"
39
35
  :time-modal-open="timeModalOpen"
40
36
  :all-collapsed="allCollapsed"
41
37
  @open-time-modal="openTimeModal"
@@ -65,8 +61,6 @@ vitepress/components/Layout/Layout.vue
65
61
  v-model:editable-note-status="editableNoteStatus"
66
62
  v-model:editable-discussions-enabled="editableDiscussionsEnabled"
67
63
  v-model:title-error="titleError"
68
- :modal-created-at="modalCreatedAt"
69
- :modal-updated-at="modalUpdatedAt"
70
64
  :modal-github-url="modalGithubUrl"
71
65
  :modal-github-page-url="modalGithubPageUrl"
72
66
  :completion-percentage="completionPercentage"
@@ -116,10 +110,6 @@ vitepress/components/Layout/Layout.vue
116
110
  </AboutModal>
117
111
  </template>
118
112
  <template #doc-footer-before>
119
- <!-- <div class="footer-time-info">
120
- <p title="首次提交时间">首次提交时间:{{ formatDate(created_at) }}</p>
121
- <p title="最近提交时间">最近提交时间:{{ formatDate(updated_at) }}</p>
122
- </div> -->
123
113
  </template>
124
114
  <template #doc-after>
125
115
  <!-- 自定义 DocFooter -->
@@ -335,8 +325,6 @@ function getEmptyNoteConfig() {
335
325
  const isDiscussionsVisible = computed(
336
326
  () => currentNoteConfig.value.enableDiscussions,
337
327
  );
338
- const updated_at = computed(() => currentNoteConfig.value.updated_at);
339
- const created_at = computed(() => currentNoteConfig.value.created_at);
340
328
 
341
329
  // 判断是否为首页 README.md
342
330
  const isHomeReadme = computed(() => vpData.page.value.filePath === "README.md");
@@ -349,10 +337,6 @@ const completionPercentage = computed(() => {
349
337
  return Math.round((doneNotesLen.value / totalNotesLen.value) * 100);
350
338
  });
351
339
 
352
- // 首页 README.md 的时间戳
353
- const homeReadmeCreatedAt = computed(() => readmeData?.created_at);
354
- const homeReadmeUpdatedAt = computed(() => readmeData?.updated_at);
355
-
356
340
  // #region - Composables
357
341
  // 404 重定向
358
342
  const { showNotFound, decodedCurrentPath, initRedirectCheck } =
@@ -509,20 +493,6 @@ const modalGithubPageUrl = computed(() => {
509
493
  return "";
510
494
  });
511
495
 
512
- // modal 中显示的创建时间
513
- const modalCreatedAt = computed(() => {
514
- return modalIsHomeReadme.value
515
- ? homeReadmeCreatedAt.value
516
- : modalNoteConfig.value.created_at;
517
- });
518
-
519
- // modal 中显示的更新时间
520
- const modalUpdatedAt = computed(() => {
521
- return modalIsHomeReadme.value
522
- ? homeReadmeUpdatedAt.value
523
- : modalNoteConfig.value.updated_at;
524
- });
525
-
526
496
  function openTimeModal() {
527
497
  sidebarAboutNoteId.value = null;
528
498
  timeModalOpen.value = true;
@@ -1,40 +1,49 @@
1
1
  /**
2
2
  * vitepress/components/Layout/composables/useVSCodeIntegration.ts
3
3
  *
4
- * VSCode 集成和 GitHub 链接拦截
4
+ * 本地 IDE 集成和 GitHub 链接拦截
5
5
  */
6
6
 
7
7
  import { useData } from 'vitepress'
8
- import { ref } from 'vue'
8
+ import { computed, ref, watch } from 'vue'
9
9
 
10
+ import { useLocalIde } from '../../composables/useLocalIde'
10
11
  import { NOTES_DIR_KEY } from '../../constants'
11
- import {
12
- resolveNoteReadmePath,
13
- toVscodeFileUrl,
14
- } from '../../utils/vscodePaths'
12
+ import { resolveNoteReadmePath, toIdeFileUrl } from '../../utils/vscodePaths'
15
13
 
16
14
  import type { Router } from 'vitepress'
17
15
  import type { Ref, ComputedRef } from 'vue'
18
16
 
19
-
20
17
  /**
21
- * VSCode 集成和 GitHub 链接拦截
18
+ * 本地 IDE 集成和 GitHub 链接拦截
22
19
  */
23
20
  export function useVSCodeIntegration() {
24
21
  const vpData = useData()
25
- const vscodeNotesDir = ref('')
22
+ const { ide } = useLocalIde()
23
+ const noteLocalPath = ref('')
24
+
25
+ const vscodeNotesDir = computed(() => {
26
+ if (!noteLocalPath.value) return ''
27
+ // 显式依赖 ide,确保切换 IDE 后 href 更新
28
+ return toIdeFileUrl(noteLocalPath.value, ide.value)
29
+ })
26
30
 
27
- // 更新 VSCode 笔记目录
31
+ // 更新当前页面对应的本地笔记路径
28
32
  const updateVscodeNoteDir = () => {
29
33
  if (typeof window !== 'undefined') {
30
34
  const notesDir = localStorage.getItem(NOTES_DIR_KEY)
31
35
  const notePath = notesDir
32
36
  ? resolveNoteReadmePath(notesDir, vpData.page.value.relativePath)
33
37
  : null
34
- vscodeNotesDir.value = notePath ? toVscodeFileUrl(notePath) : ''
38
+ noteLocalPath.value = notePath || ''
35
39
  }
36
40
  }
37
41
 
42
+ watch(ide, () => {
43
+ // IDE 切换后重新解析路径(无需刷新页面)
44
+ updateVscodeNoteDir()
45
+ })
46
+
38
47
  // 拦截 home README 中的笔记链接,将 GitHub 链接转换为站点内跳转
39
48
  const interceptHomeReadmeLinks = (
40
49
  isHomeReadme: Ref<boolean> | ComputedRef<boolean>,
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * vitepress/components/Layout/homeReadme.data.ts
3
3
  */
4
- import { execSync } from 'node:child_process'
5
4
  import fs from 'node:fs'
6
5
  import path from 'node:path'
7
6
 
@@ -12,8 +11,6 @@ interface ReadmeData {
12
11
  doneNotesID: string[]
13
12
  doneNotesLen: number
14
13
  totalNotesLen: number
15
- created_at?: number
16
- updated_at?: number
17
14
  }
18
15
 
19
16
  export default {
@@ -28,15 +25,10 @@ export default {
28
25
 
29
26
  watchedFiles.forEach((file) => {
30
27
  if (file.endsWith('README.md')) {
31
- // console.log('file:', file) // => file: README.md
32
-
33
28
  const fileContent = fs.readFileSync(file, 'utf-8')
34
29
  const doneNotesID = getDoneNotesID(fileContent)
35
30
  const doneNotesLen = doneNotesID.length
36
31
 
37
- // 获取 git 仓库的时间戳
38
- const timestamps = getGitTimestamps()
39
-
40
32
  // 计算总笔记数
41
33
  const notesDir = path.join(path.dirname(file), 'notes')
42
34
  const totalNotesLen = getTotalNotesCount(notesDir)
@@ -46,7 +38,6 @@ export default {
46
38
  doneNotesID,
47
39
  doneNotesLen,
48
40
  totalNotesLen,
49
- ...timestamps,
50
41
  }
51
42
  }
52
43
  })
@@ -74,45 +65,6 @@ function getTotalNotesCount(notesDir: string): number {
74
65
  }
75
66
  }
76
67
 
77
- /**
78
- * 获取 git 仓库的时间戳
79
- * @returns 包含 created_at 和 updated_at 的对象
80
- */
81
- function getGitTimestamps(): {
82
- created_at?: number
83
- updated_at?: number
84
- } {
85
- const now = Date.now()
86
- let created_at = now
87
- let updated_at = now
88
-
89
- try {
90
- // 仓库的首次提交时间(最早的提交)
91
- // 先获取所有提交,然后取第一行
92
- const createdStdout = execSync(`git log --reverse --format=%ct`, {
93
- encoding: 'utf-8',
94
- })
95
- const createdTs = createdStdout.toString().trim().split('\n')[0]
96
- if (createdTs) {
97
- created_at = parseInt(createdTs, 10) * 1000
98
- }
99
-
100
- // 仓库的最近一次提交时间
101
- const updatedStdout = execSync(`git log -1 --format=%ct`, {
102
- encoding: 'utf-8',
103
- })
104
- const updatedTs = updatedStdout.toString().trim()
105
- if (updatedTs) {
106
- updated_at = parseInt(updatedTs, 10) * 1000
107
- }
108
-
109
- return { created_at, updated_at }
110
- } catch (error) {
111
- console.error(`获取 git 仓库时间戳失败:`, error)
112
- return { created_at, updated_at }
113
- }
114
- }
115
-
116
68
  /**
117
69
  * 返回已完成的笔记的 ID 列表
118
70
  * @param fileContent 文件内容
@@ -0,0 +1,25 @@
1
+ import { parseInlineSegments } from '@tnotesjs/mindmap-core'
2
+ import { defineComponent, h } from 'vue'
3
+
4
+ export default defineComponent({
5
+ name: 'MindmapInlineRuns',
6
+ props: {
7
+ raw: { type: String, default: '' },
8
+ },
9
+ setup(props) {
10
+ return () => h('span', { class: 'mindmap-inline-runs' }, parseInlineSegments(props.raw).map((segment) => {
11
+ const classes = Object.entries(segment.marks)
12
+ .filter(([, enabled]) => enabled)
13
+ .map(([name]) => `is-${name}`)
14
+ if (segment.link) {
15
+ return h('a', {
16
+ class: ['mindmap-inline-run', 'is-link', ...classes],
17
+ href: segment.link.url,
18
+ target: '_blank',
19
+ rel: 'noopener noreferrer',
20
+ }, segment.text)
21
+ }
22
+ return h('span', { class: ['mindmap-inline-run', ...classes] }, segment.text)
23
+ }))
24
+ },
25
+ })
@@ -0,0 +1,62 @@
1
+ <script setup lang="ts">
2
+ import InlineRuns from './InlineRuns'
3
+
4
+ import type { MindmapNode } from '@tnotesjs/mindmap-core'
5
+
6
+ defineOptions({ name: 'MindmapOutlineNode' })
7
+
8
+ defineProps<{
9
+ node: MindmapNode
10
+ version: number
11
+ root?: boolean
12
+ }>()
13
+
14
+ defineEmits<{
15
+ toggle: [id: string]
16
+ }>()
17
+ </script>
18
+
19
+ <template>
20
+ <li class="mindmap-outline-node" :class="{ 'is-root': root, 'is-done': node.content.checked === true }">
21
+ <div class="mindmap-outline-row">
22
+ <button
23
+ v-if="node.children.length > 0"
24
+ type="button"
25
+ class="mindmap-outline-toggle"
26
+ :aria-label="node.collapsed ? '展开主题' : '折叠主题'"
27
+ :aria-expanded="!node.collapsed"
28
+ @click="$emit('toggle', node.id)"
29
+ >
30
+ {{ node.collapsed ? '›' : '⌄' }}
31
+ </button>
32
+ <span v-else class="mindmap-outline-leaf" aria-hidden="true">•</span>
33
+ <input
34
+ v-if="node.content.checked !== null && !root"
35
+ class="mindmap-outline-checkbox"
36
+ type="checkbox"
37
+ :checked="node.content.checked"
38
+ disabled
39
+ aria-label="只读待办状态"
40
+ />
41
+ <span class="mindmap-outline-label">
42
+ <InlineRuns :raw="node.content.image ? node.content.text : node.content.raw" />
43
+ </span>
44
+ </div>
45
+ <img
46
+ v-if="node.content.image"
47
+ class="mindmap-outline-image"
48
+ :src="node.content.image.src"
49
+ :alt="node.content.image.alt"
50
+ :style="node.content.image.width ? { width: `${node.content.image.width}px` } : undefined"
51
+ />
52
+ <ul v-if="node.children.length > 0 && !node.collapsed" class="mindmap-outline-children">
53
+ <MindmapOutlineNode
54
+ v-for="child in node.children"
55
+ :key="child.id"
56
+ :node="child"
57
+ :version="version"
58
+ @toggle="$emit('toggle', $event)"
59
+ />
60
+ </ul>
61
+ </li>
62
+ </template>
@@ -0,0 +1,287 @@
1
+ <script setup lang="ts">
2
+ import { CanvasViewer, MindmapSession } from '@tnotesjs/mindmap-core'
3
+ import { onContentUpdated, useData } from 'vitepress'
4
+ import { computed, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'
5
+
6
+ import { normalizeMindmapMarkdown } from './compat'
7
+ import { applyInitialExpandLevel } from './expandLevel'
8
+ import MindmapOutlineNode from './MindmapOutlineNode.vue'
9
+
10
+ type PreviewView = 'mindmap' | 'outline' | 'source'
11
+
12
+ const props = withDefaults(defineProps<{
13
+ content?: string
14
+ initialExpandLevel?: number
15
+ }>(), {
16
+ content: '',
17
+ initialExpandLevel: 3,
18
+ })
19
+
20
+ const { isDark } = useData()
21
+ const activeView = ref<PreviewView>('mindmap')
22
+ const canvasHost = ref<HTMLElement | null>(null)
23
+ const session = shallowRef<MindmapSession | null>(null)
24
+ const renderVersion = ref(0)
25
+ let viewer: CanvasViewer | null = null
26
+ let mounted = false
27
+
28
+ function decodeContent(value: string): string {
29
+ try {
30
+ return decodeURIComponent(value)
31
+ } catch {
32
+ return value
33
+ }
34
+ }
35
+
36
+ const normalizedContent = computed(() => normalizeMindmapMarkdown(decodeContent(props.content)))
37
+
38
+ function createViewer(): void {
39
+ if (!mounted || !canvasHost.value || !session.value || viewer) return
40
+ viewer = new CanvasViewer(canvasHost.value, session.value, {
41
+ theme: isDark.value ? 'dark' : 'light',
42
+ })
43
+ }
44
+
45
+ function rebuildSession(): void {
46
+ viewer?.destroy()
47
+ viewer = null
48
+ const next = new MindmapSession({
49
+ markdown: normalizedContent.value,
50
+ fileName: 'mindmap-preview.tn-mindmap.md',
51
+ })
52
+ applyInitialExpandLevel(next, props.initialExpandLevel)
53
+ const invalidate = () => { renderVersion.value += 1 }
54
+ next.on('collapseChange', invalidate)
55
+ next.on('focusChange', invalidate)
56
+ next.on('change', invalidate)
57
+ session.value = next
58
+ renderVersion.value += 1
59
+ void nextTick(createViewer)
60
+ }
61
+
62
+ function setView(view: PreviewView): void {
63
+ activeView.value = view
64
+ if (view === 'mindmap') void nextTick(() => viewer?.zoomToFit())
65
+ }
66
+
67
+ function toggleNode(id: string): void {
68
+ session.value?.toggleCollapse(id)
69
+ }
70
+
71
+ function exitFocusTo(index: number): void {
72
+ session.value?.exitFocusTo(index)
73
+ }
74
+
75
+ watch([normalizedContent, () => props.initialExpandLevel], rebuildSession, { immediate: true })
76
+ watch(isDark, (dark) => viewer?.setTheme(dark ? 'dark' : 'light'))
77
+
78
+ onMounted(() => {
79
+ mounted = true
80
+ createViewer()
81
+ })
82
+
83
+ onContentUpdated(() => {
84
+ if (activeView.value === 'mindmap') void nextTick(() => viewer?.zoomToFit())
85
+ })
86
+
87
+ onBeforeUnmount(() => {
88
+ mounted = false
89
+ viewer?.destroy()
90
+ viewer = null
91
+ })
92
+ </script>
93
+
94
+ <template>
95
+ <section class="mindmap-preview" :class="{ 'is-dark': isDark }">
96
+ <header class="mindmap-preview-header">
97
+ <nav class="mindmap-preview-tabs" aria-label="脑图预览视图">
98
+ <button
99
+ v-for="item in ([['mindmap', '脑图'], ['outline', '大纲'], ['source', '源码']] as const)"
100
+ :key="item[0]"
101
+ type="button"
102
+ :class="{ 'is-active': activeView === item[0] }"
103
+ @click="setView(item[0])"
104
+ >
105
+ {{ item[1] }}
106
+ </button>
107
+ </nav>
108
+ <button v-if="activeView === 'mindmap'" type="button" class="mindmap-fit" @click="viewer?.zoomToFit()">
109
+ 适应视口
110
+ </button>
111
+ </header>
112
+
113
+ <nav v-if="session && session.focusPath.length > 0" class="mindmap-focus-path" aria-label="当前主题路径">
114
+ <button type="button" @click="exitFocusTo(0)">全部</button>
115
+ <template v-for="(node, index) in session.focusPath" :key="node.id">
116
+ <span aria-hidden="true">/</span>
117
+ <button type="button" @click="exitFocusTo(index + 1)">{{ node.content.text }}</button>
118
+ </template>
119
+ </nav>
120
+
121
+ <div v-show="activeView === 'mindmap'" ref="canvasHost" class="mindmap-canvas-host" />
122
+
123
+ <div v-if="activeView === 'outline' && session" class="mindmap-outline" :data-version="renderVersion">
124
+ <ul class="mindmap-outline-root">
125
+ <MindmapOutlineNode
126
+ :node="session.focusRootNode"
127
+ :version="renderVersion"
128
+ root
129
+ @toggle="toggleNode"
130
+ />
131
+ </ul>
132
+ </div>
133
+
134
+ <pre v-if="activeView === 'source'" class="mindmap-source"><code>{{ normalizedContent }}</code></pre>
135
+ </section>
136
+ </template>
137
+
138
+ <style scoped lang="scss">
139
+ .mindmap-preview {
140
+ --mindmap-panel: var(--vp-c-bg-soft);
141
+ --mindmap-border: var(--vp-c-divider);
142
+ margin: 1.5rem 0;
143
+ overflow: hidden;
144
+ border: 1px solid var(--mindmap-border);
145
+ border-radius: 10px;
146
+ background: var(--vp-c-bg);
147
+ }
148
+
149
+ .mindmap-preview-header {
150
+ display: flex;
151
+ align-items: center;
152
+ justify-content: space-between;
153
+ min-height: 42px;
154
+ padding: 5px 8px;
155
+ border-bottom: 1px solid var(--mindmap-border);
156
+ background: var(--mindmap-panel);
157
+ }
158
+
159
+ .mindmap-preview-tabs {
160
+ display: flex;
161
+ gap: 3px;
162
+
163
+ button {
164
+ padding: 5px 12px;
165
+ border-radius: 6px;
166
+ color: var(--vp-c-text-2);
167
+ font-size: 13px;
168
+ font-weight: 600;
169
+
170
+ &:hover,
171
+ &.is-active {
172
+ color: var(--vp-c-text-1);
173
+ background: var(--vp-c-bg);
174
+ }
175
+ }
176
+ }
177
+
178
+ .mindmap-fit,
179
+ .mindmap-focus-path button {
180
+ color: var(--vp-c-text-2);
181
+ font-size: 12px;
182
+
183
+ &:hover { color: var(--vp-c-brand-1); }
184
+ }
185
+
186
+ .mindmap-focus-path {
187
+ display: flex;
188
+ gap: 6px;
189
+ align-items: center;
190
+ padding: 6px 12px;
191
+ overflow-x: auto;
192
+ border-bottom: 1px solid var(--mindmap-border);
193
+ white-space: nowrap;
194
+ color: var(--vp-c-text-3);
195
+ }
196
+
197
+ .mindmap-canvas-host {
198
+ position: relative;
199
+ width: 100%;
200
+ height: 440px;
201
+ overflow: hidden;
202
+ background: var(--vp-c-bg);
203
+ touch-action: none;
204
+ user-select: none;
205
+ }
206
+
207
+ .mindmap-canvas-host:deep(.mm-canvas),
208
+ .mindmap-canvas-host:deep(.mm-overlay) {
209
+ position: absolute;
210
+ inset: 0;
211
+ width: 100%;
212
+ height: 100%;
213
+ }
214
+
215
+ .mindmap-canvas-host:deep(.mm-overlay) { pointer-events: none; }
216
+ .mindmap-canvas-host:deep(.mm-editor) { outline: none; }
217
+
218
+ .mindmap-outline {
219
+ max-height: 560px;
220
+ padding: 18px 22px 22px;
221
+ overflow: auto;
222
+ }
223
+
224
+ .mindmap-outline-root,
225
+ .mindmap-outline :deep(ul) {
226
+ margin: 0;
227
+ padding: 0;
228
+ list-style: none;
229
+ }
230
+
231
+ .mindmap-outline :deep(.mindmap-outline-children) {
232
+ margin-left: 10px;
233
+ padding-left: 19px;
234
+ border-left: 1px solid var(--vp-c-divider);
235
+ }
236
+
237
+ .mindmap-outline :deep(.mindmap-outline-row) {
238
+ display: flex;
239
+ align-items: flex-start;
240
+ gap: 7px;
241
+ min-height: 30px;
242
+ padding: 3px 0;
243
+ color: var(--vp-c-text-1);
244
+ line-height: 24px;
245
+ }
246
+
247
+ .mindmap-outline :deep(.mindmap-outline-toggle),
248
+ .mindmap-outline :deep(.mindmap-outline-leaf) {
249
+ flex: 0 0 18px;
250
+ width: 18px;
251
+ color: var(--vp-c-text-3);
252
+ text-align: center;
253
+ }
254
+
255
+ .mindmap-outline :deep(.mindmap-outline-toggle:hover) { color: var(--vp-c-brand-1); }
256
+ .mindmap-outline :deep(.mindmap-outline-checkbox) { margin-top: 5px; }
257
+ .mindmap-outline :deep(.mindmap-outline-label) { min-width: 0; overflow-wrap: anywhere; }
258
+ .mindmap-outline :deep(.mindmap-outline-node.is-root > .mindmap-outline-row) { font-size: 18px; font-weight: 700; }
259
+ .mindmap-outline :deep(.mindmap-outline-node.is-done > .mindmap-outline-row .mindmap-outline-label) { opacity: .58; text-decoration: line-through; }
260
+ .mindmap-outline :deep(.mindmap-outline-image) { display: block; max-width: min(100%, 560px); max-height: 360px; margin: 5px 0 12px 25px; border-radius: 6px; }
261
+ .mindmap-outline :deep(.is-bold) { font-weight: 700; }
262
+ .mindmap-outline :deep(.is-italic) { font-style: italic; }
263
+ .mindmap-outline :deep(.is-underline) { text-decoration: underline; }
264
+ .mindmap-outline :deep(.is-strike) { text-decoration: line-through; }
265
+ .mindmap-outline :deep(.is-highlight) { padding: 0 2px; border-radius: 2px; background: #ffe56b; color: #252525; }
266
+ .mindmap-outline :deep(.is-code) { padding: 1px 5px; border-radius: 4px; background: var(--vp-c-bg-soft); color: var(--vp-c-danger-1); font-family: var(--vp-font-family-mono); }
267
+ .mindmap-outline :deep(.is-link) { color: var(--vp-c-brand-1); text-decoration: underline; text-underline-offset: 3px; }
268
+
269
+ .mindmap-source {
270
+ max-height: 560px;
271
+ margin: 0;
272
+ padding: 18px 22px;
273
+ overflow: auto;
274
+ border-radius: 0;
275
+ background: var(--vp-code-block-bg);
276
+ color: var(--vp-code-block-color);
277
+ font-size: 13px;
278
+ line-height: 1.65;
279
+ white-space: pre;
280
+ }
281
+
282
+ @media (max-width: 768px) {
283
+ .mindmap-canvas-host { height: 360px; }
284
+ .mindmap-preview-tabs button { padding-inline: 9px; }
285
+ .mindmap-outline { padding-inline: 12px; }
286
+ }
287
+ </style>