@tnotesjs/ui 0.2.1 → 0.3.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.
- package/README.md +81 -0
- package/dist/code/highlight.d.ts +39 -1
- package/dist/code/highlight.js +54 -1
- package/package.json +19 -6
- package/src/code/collapse.ts +79 -0
- package/src/code/highlight.ts +12 -0
- package/src/components/CodeBlock/CodeBlock.vue +39 -5
- package/src/components/CodeGroup/CodeGroup.vue +70 -7
- package/src/components/Mindmap/Mindmap.vue +7 -6
- package/src/components/Mindmap/editor/CanvasContextMenu.vue +77 -68
- package/src/components/Mindmap/editor/CollapseMenu.vue +113 -0
- package/src/components/Mindmap/editor/IconButton.vue +63 -0
- package/src/components/Mindmap/editor/LinkPopover.vue +50 -46
- package/src/components/Mindmap/editor/MarkdownView.vue +5 -3
- package/src/components/Mindmap/editor/MindmapView.vue +11 -1
- package/src/components/Mindmap/editor/SearchBar.vue +672 -0
- package/src/components/Mindmap/editor/SelectionToolbar.vue +1 -1
- package/src/entries/excalidraw-view.ts +5 -0
- package/src/entries/mindmap-editor.ts +37 -0
- package/src/excalidraw/exporter.ts +1 -0
- package/src/excalidraw/fonts.ts +21 -1
- package/src/styles/code.css +74 -2
- package/src/styles/prose.css +45 -0
- package/src/styles/tokens.css +6 -0
- package/src/components/Mindmap/editor/canvasClipboard.ts +0 -2
|
@@ -5,6 +5,12 @@ import { isApplePlatform, primaryShortcut } from './platform'
|
|
|
5
5
|
const props = withDefaults(
|
|
6
6
|
defineProps<{
|
|
7
7
|
position: { left: number; top: number }
|
|
8
|
+
/**
|
|
9
|
+
* 浮层挂载点。默认 `undefined` = 就地渲染,供嵌在笔记里的宿主(Desk / SSG)
|
|
10
|
+
* 使用——菜单是 `position: fixed`,teleport 到 body 会脱离笔记的层叠上下文。
|
|
11
|
+
* 自带整页外壳的宿主(Web / VS Code webview)传 `'body'`。
|
|
12
|
+
*/
|
|
13
|
+
teleportTo?: string
|
|
8
14
|
multiple?: boolean
|
|
9
15
|
canInsertSibling?: boolean
|
|
10
16
|
canInsertParent?: boolean
|
|
@@ -16,6 +22,7 @@ const props = withDefaults(
|
|
|
16
22
|
canFocus?: boolean
|
|
17
23
|
}>(),
|
|
18
24
|
{
|
|
25
|
+
teleportTo: undefined,
|
|
19
26
|
multiple: false,
|
|
20
27
|
canInsertSibling: true,
|
|
21
28
|
canInsertParent: true,
|
|
@@ -96,85 +103,87 @@ onBeforeUnmount(() => {
|
|
|
96
103
|
</script>
|
|
97
104
|
|
|
98
105
|
<template>
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
<Teleport :to="teleportTo" :disabled="!teleportTo">
|
|
107
|
+
<div
|
|
108
|
+
ref="menu"
|
|
109
|
+
class="canvas-context-menu"
|
|
110
|
+
:style="style"
|
|
111
|
+
role="menu"
|
|
112
|
+
:aria-label="multiple ? '多主题右键菜单' : '主题右键菜单'"
|
|
113
|
+
@contextmenu.prevent
|
|
114
|
+
>
|
|
115
|
+
<template v-if="!multiple">
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
role="menuitem"
|
|
119
|
+
:disabled="!canInsertSibling"
|
|
120
|
+
@click="act('insertSibling')"
|
|
121
|
+
>
|
|
122
|
+
<span>插入同级主题</span><kbd>Enter</kbd>
|
|
123
|
+
</button>
|
|
124
|
+
<button type="button" role="menuitem" @click="act('insertChild')">
|
|
125
|
+
<span>插入下级主题</span><kbd>Tab</kbd>
|
|
126
|
+
</button>
|
|
127
|
+
<button
|
|
128
|
+
type="button"
|
|
129
|
+
role="menuitem"
|
|
130
|
+
:disabled="!canInsertParent"
|
|
131
|
+
@click="act('insertParent')"
|
|
132
|
+
>
|
|
133
|
+
<span>插入上级主题</span><kbd>{{ shiftTab }}</kbd>
|
|
134
|
+
</button>
|
|
135
|
+
<span class="context-divider" />
|
|
136
|
+
</template>
|
|
137
|
+
|
|
138
|
+
<button type="button" role="menuitem" @click="act('copy')">
|
|
139
|
+
<span>复制</span><kbd>{{ primaryShortcut('C') }}</kbd>
|
|
140
|
+
</button>
|
|
141
|
+
<button type="button" role="menuitem" :disabled="!canCut" @click="act('cut')">
|
|
142
|
+
<span>剪切</span><kbd>{{ primaryShortcut('X') }}</kbd>
|
|
143
|
+
</button>
|
|
144
|
+
|
|
145
|
+
<template v-if="!multiple">
|
|
146
|
+
<button type="button" role="menuitem" @click="act('paste')">
|
|
147
|
+
<span>粘贴</span><kbd>{{ primaryShortcut('V') }}</kbd>
|
|
148
|
+
</button>
|
|
149
|
+
<button type="button" role="menuitem" :disabled="!canDuplicate" @click="act('duplicate')">
|
|
150
|
+
<span>创建副本</span><kbd>{{ primaryShortcut('D') }}</kbd>
|
|
151
|
+
</button>
|
|
152
|
+
<button type="button" role="menuitem" :disabled="!canDeleteOnly" @click="act('deleteOnly')">
|
|
153
|
+
<span>仅删除当前主题</span>
|
|
154
|
+
</button>
|
|
155
|
+
</template>
|
|
156
|
+
|
|
108
157
|
<button
|
|
109
158
|
type="button"
|
|
110
159
|
role="menuitem"
|
|
111
|
-
|
|
112
|
-
|
|
160
|
+
class="danger"
|
|
161
|
+
:disabled="!canDeleteTree"
|
|
162
|
+
@click="act('deleteTree')"
|
|
113
163
|
>
|
|
114
|
-
<span
|
|
115
|
-
|
|
116
|
-
<button type="button" role="menuitem" @click="act('insertChild')">
|
|
117
|
-
<span>插入下级主题</span><kbd>Tab</kbd>
|
|
164
|
+
<span>{{ multiple ? '删除所选主题及下级主题' : '删除当前主题及下级主题' }}</span
|
|
165
|
+
><kbd>Delete</kbd>
|
|
118
166
|
</button>
|
|
167
|
+
<span class="context-divider" />
|
|
119
168
|
<button
|
|
120
169
|
type="button"
|
|
121
170
|
role="menuitem"
|
|
122
|
-
:disabled="!
|
|
123
|
-
@click="act('
|
|
171
|
+
:disabled="!canToggleSiblings"
|
|
172
|
+
@click="act('toggleSiblings')"
|
|
124
173
|
>
|
|
125
|
-
<span
|
|
174
|
+
<span>展开/折叠同级主题</span><kbd>{{ primaryShortcut('.', { shift: true }) }}</kbd>
|
|
126
175
|
</button>
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
</button>
|
|
136
|
-
|
|
137
|
-
<template v-if="!multiple">
|
|
138
|
-
<button type="button" role="menuitem" @click="act('paste')">
|
|
139
|
-
<span>粘贴</span><kbd>{{ primaryShortcut('V') }}</kbd>
|
|
140
|
-
</button>
|
|
141
|
-
<button type="button" role="menuitem" :disabled="!canDuplicate" @click="act('duplicate')">
|
|
142
|
-
<span>创建副本</span><kbd>{{ primaryShortcut('D') }}</kbd>
|
|
143
|
-
</button>
|
|
144
|
-
<button type="button" role="menuitem" :disabled="!canDeleteOnly" @click="act('deleteOnly')">
|
|
145
|
-
<span>仅删除当前主题</span>
|
|
176
|
+
<button
|
|
177
|
+
v-if="!multiple"
|
|
178
|
+
type="button"
|
|
179
|
+
role="menuitem"
|
|
180
|
+
:disabled="!canFocus"
|
|
181
|
+
@click="act('focus')"
|
|
182
|
+
>
|
|
183
|
+
<span>进入此主题</span><kbd>{{ primaryShortcut(']') }}</kbd>
|
|
146
184
|
</button>
|
|
147
|
-
</
|
|
148
|
-
|
|
149
|
-
<button
|
|
150
|
-
type="button"
|
|
151
|
-
role="menuitem"
|
|
152
|
-
class="danger"
|
|
153
|
-
:disabled="!canDeleteTree"
|
|
154
|
-
@click="act('deleteTree')"
|
|
155
|
-
>
|
|
156
|
-
<span>{{ multiple ? '删除所选主题及下级主题' : '删除当前主题及下级主题' }}</span
|
|
157
|
-
><kbd>Delete</kbd>
|
|
158
|
-
</button>
|
|
159
|
-
<span class="context-divider" />
|
|
160
|
-
<button
|
|
161
|
-
type="button"
|
|
162
|
-
role="menuitem"
|
|
163
|
-
:disabled="!canToggleSiblings"
|
|
164
|
-
@click="act('toggleSiblings')"
|
|
165
|
-
>
|
|
166
|
-
<span>展开/折叠同级主题</span><kbd>{{ primaryShortcut('.', { shift: true }) }}</kbd>
|
|
167
|
-
</button>
|
|
168
|
-
<button
|
|
169
|
-
v-if="!multiple"
|
|
170
|
-
type="button"
|
|
171
|
-
role="menuitem"
|
|
172
|
-
:disabled="!canFocus"
|
|
173
|
-
@click="act('focus')"
|
|
174
|
-
>
|
|
175
|
-
<span>进入此主题</span><kbd>{{ primaryShortcut(']') }}</kbd>
|
|
176
|
-
</button>
|
|
177
|
-
</div>
|
|
185
|
+
</div>
|
|
186
|
+
</Teleport>
|
|
178
187
|
</template>
|
|
179
188
|
|
|
180
189
|
<style scoped>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
3
|
+
import IconButton from './IconButton.vue'
|
|
4
|
+
import { isApplePlatform } from './platform'
|
|
5
|
+
|
|
6
|
+
const emit = defineEmits<{
|
|
7
|
+
all: []
|
|
8
|
+
level: [level: 1 | 2 | 3]
|
|
9
|
+
}>()
|
|
10
|
+
|
|
11
|
+
const root = ref<HTMLElement>()
|
|
12
|
+
const open = ref(false)
|
|
13
|
+
const modifier = computed(() => (isApplePlatform() ? '⌘⌥' : 'Ctrl+Alt+'))
|
|
14
|
+
const allShortcut = computed(() => (isApplePlatform() ? '⌘⌥⇧.' : 'Ctrl+Alt+Shift+.'))
|
|
15
|
+
|
|
16
|
+
function chooseAll() {
|
|
17
|
+
emit('all')
|
|
18
|
+
open.value = false
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function chooseLevel(level: 1 | 2 | 3) {
|
|
22
|
+
emit('level', level)
|
|
23
|
+
open.value = false
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function onDocumentPointerDown(event: PointerEvent) {
|
|
27
|
+
if (open.value && !root.value?.contains(event.target as Node)) open.value = false
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function onDocumentKeydown(event: KeyboardEvent) {
|
|
31
|
+
if (event.key === 'Escape') open.value = false
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
onMounted(() => {
|
|
35
|
+
document.addEventListener('pointerdown', onDocumentPointerDown)
|
|
36
|
+
document.addEventListener('keydown', onDocumentKeydown)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
onBeforeUnmount(() => {
|
|
40
|
+
document.removeEventListener('pointerdown', onDocumentPointerDown)
|
|
41
|
+
document.removeEventListener('keydown', onDocumentKeydown)
|
|
42
|
+
})
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<template>
|
|
46
|
+
<div ref="root" class="collapse-menu">
|
|
47
|
+
<IconButton icon="collapse" label="展开/折叠主题" :active="open" @click="open = !open" />
|
|
48
|
+
<div v-if="open" class="collapse-menu-popover" role="menu" aria-label="展开/折叠主题">
|
|
49
|
+
<div class="collapse-menu-title">展开/折叠主题</div>
|
|
50
|
+
<button type="button" role="menuitem" @click="chooseAll">
|
|
51
|
+
<span>全部主题</span><kbd>{{ allShortcut }}</kbd>
|
|
52
|
+
</button>
|
|
53
|
+
<button
|
|
54
|
+
v-for="level in [1, 2, 3] as const"
|
|
55
|
+
:key="level"
|
|
56
|
+
type="button"
|
|
57
|
+
role="menuitem"
|
|
58
|
+
@click="chooseLevel(level)"
|
|
59
|
+
>
|
|
60
|
+
<span>{{ level }} 级主题</span><kbd>{{ modifier }}{{ level }}</kbd>
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</template>
|
|
65
|
+
|
|
66
|
+
<style scoped>
|
|
67
|
+
.collapse-menu {
|
|
68
|
+
position: relative;
|
|
69
|
+
}
|
|
70
|
+
.collapse-menu-popover {
|
|
71
|
+
position: absolute;
|
|
72
|
+
z-index: 85;
|
|
73
|
+
top: calc(100% + 8px);
|
|
74
|
+
right: 0;
|
|
75
|
+
width: 260px;
|
|
76
|
+
padding: 8px;
|
|
77
|
+
border: 1px solid var(--mm-border);
|
|
78
|
+
border-radius: 12px;
|
|
79
|
+
background: var(--mm-panel-bg);
|
|
80
|
+
color: var(--mm-text);
|
|
81
|
+
box-shadow: 0 14px 38px rgb(0 0 0 / 0.24);
|
|
82
|
+
}
|
|
83
|
+
.collapse-menu-title {
|
|
84
|
+
padding: 7px 11px 9px;
|
|
85
|
+
color: var(--mm-text-dim);
|
|
86
|
+
font-size: 12px;
|
|
87
|
+
}
|
|
88
|
+
.collapse-menu-popover button {
|
|
89
|
+
display: flex;
|
|
90
|
+
width: 100%;
|
|
91
|
+
min-height: 42px;
|
|
92
|
+
align-items: center;
|
|
93
|
+
justify-content: space-between;
|
|
94
|
+
padding: 0 11px;
|
|
95
|
+
border: 0;
|
|
96
|
+
border-radius: 7px;
|
|
97
|
+
background: transparent;
|
|
98
|
+
color: inherit;
|
|
99
|
+
font: inherit;
|
|
100
|
+
text-align: left;
|
|
101
|
+
cursor: pointer;
|
|
102
|
+
}
|
|
103
|
+
.collapse-menu-popover button:hover,
|
|
104
|
+
.collapse-menu-popover button:focus-visible {
|
|
105
|
+
background: var(--mm-hover);
|
|
106
|
+
outline: none;
|
|
107
|
+
}
|
|
108
|
+
.collapse-menu-popover kbd {
|
|
109
|
+
color: var(--mm-text-dim);
|
|
110
|
+
font: inherit;
|
|
111
|
+
font-size: 12px;
|
|
112
|
+
}
|
|
113
|
+
</style>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import AppIcon from './AppIcon.vue'
|
|
3
|
+
|
|
4
|
+
withDefaults(
|
|
5
|
+
defineProps<{
|
|
6
|
+
icon: string
|
|
7
|
+
label: string
|
|
8
|
+
size?: number
|
|
9
|
+
active?: boolean
|
|
10
|
+
danger?: boolean
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
}>(),
|
|
13
|
+
{ size: 18, active: false, danger: false, disabled: false }
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
defineEmits<{ click: [event: MouseEvent] }>()
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<button
|
|
21
|
+
type="button"
|
|
22
|
+
class="icon-button"
|
|
23
|
+
:class="{ active, danger }"
|
|
24
|
+
:title="label"
|
|
25
|
+
:aria-label="label"
|
|
26
|
+
:disabled="disabled"
|
|
27
|
+
@click="$emit('click', $event)"
|
|
28
|
+
>
|
|
29
|
+
<AppIcon :name="icon" :size="size" />
|
|
30
|
+
</button>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<style scoped>
|
|
34
|
+
.icon-button {
|
|
35
|
+
display: inline-flex;
|
|
36
|
+
width: 32px;
|
|
37
|
+
height: 32px;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: center;
|
|
40
|
+
padding: 0;
|
|
41
|
+
border: 0;
|
|
42
|
+
border-radius: 7px;
|
|
43
|
+
background: transparent;
|
|
44
|
+
color: var(--mm-text-dim);
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
}
|
|
47
|
+
.icon-button:hover:not(:disabled),
|
|
48
|
+
.icon-button.active {
|
|
49
|
+
background: var(--mm-hover);
|
|
50
|
+
color: var(--mm-text);
|
|
51
|
+
}
|
|
52
|
+
.icon-button.danger {
|
|
53
|
+
color: #d84d57;
|
|
54
|
+
}
|
|
55
|
+
.icon-button:focus-visible {
|
|
56
|
+
outline: 2px solid var(--mm-accent);
|
|
57
|
+
outline-offset: 1px;
|
|
58
|
+
}
|
|
59
|
+
.icon-button:disabled {
|
|
60
|
+
cursor: default;
|
|
61
|
+
opacity: 0.38;
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
@@ -7,8 +7,10 @@ const props = withDefaults(
|
|
|
7
7
|
url: string
|
|
8
8
|
position: { left: number; top: number }
|
|
9
9
|
startEditing?: boolean
|
|
10
|
+
/** 见 `CanvasContextMenu`:默认就地渲染,整页宿主传 `'body'`。 */
|
|
11
|
+
teleportTo?: string
|
|
10
12
|
}>(),
|
|
11
|
-
{ startEditing: false }
|
|
13
|
+
{ startEditing: false, teleportTo: undefined }
|
|
12
14
|
)
|
|
13
15
|
const emit = defineEmits<{ save: [url: string]; remove: []; keep: []; leave: []; close: [] }>()
|
|
14
16
|
const draft = ref(props.url)
|
|
@@ -50,52 +52,54 @@ defineExpose({ focusInput })
|
|
|
50
52
|
</script>
|
|
51
53
|
|
|
52
54
|
<template>
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
<input
|
|
60
|
-
ref="input"
|
|
61
|
-
v-model="draft"
|
|
62
|
-
class="link-input"
|
|
63
|
-
:class="{ readonly: !editing }"
|
|
64
|
-
:readonly="!editing"
|
|
65
|
-
aria-label="链接地址"
|
|
66
|
-
@dblclick="beginEdit"
|
|
67
|
-
@keydown="onKeydown"
|
|
68
|
-
/>
|
|
69
|
-
<button
|
|
70
|
-
v-if="editing"
|
|
71
|
-
type="button"
|
|
72
|
-
class="link-action primary"
|
|
73
|
-
title="更新链接 (Enter)"
|
|
74
|
-
aria-label="更新链接"
|
|
75
|
-
@click="submit"
|
|
55
|
+
<Teleport :to="teleportTo" :disabled="!teleportTo">
|
|
56
|
+
<div
|
|
57
|
+
class="link-popover"
|
|
58
|
+
:style="{ left: `${position.left}px`, top: `${position.top}px` }"
|
|
59
|
+
@mouseenter="emit('keep')"
|
|
60
|
+
@mouseleave="emit('leave')"
|
|
76
61
|
>
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
62
|
+
<input
|
|
63
|
+
ref="input"
|
|
64
|
+
v-model="draft"
|
|
65
|
+
class="link-input"
|
|
66
|
+
:class="{ readonly: !editing }"
|
|
67
|
+
:readonly="!editing"
|
|
68
|
+
aria-label="链接地址"
|
|
69
|
+
@dblclick="beginEdit"
|
|
70
|
+
@keydown="onKeydown"
|
|
71
|
+
/>
|
|
72
|
+
<button
|
|
73
|
+
v-if="editing"
|
|
74
|
+
type="button"
|
|
75
|
+
class="link-action primary"
|
|
76
|
+
title="更新链接 (Enter)"
|
|
77
|
+
aria-label="更新链接"
|
|
78
|
+
@click="submit"
|
|
79
|
+
>
|
|
80
|
+
更新
|
|
81
|
+
</button>
|
|
82
|
+
<button
|
|
83
|
+
v-else
|
|
84
|
+
type="button"
|
|
85
|
+
class="icon-action"
|
|
86
|
+
title="编辑链接地址"
|
|
87
|
+
aria-label="编辑链接地址"
|
|
88
|
+
@click="beginEdit"
|
|
89
|
+
>
|
|
90
|
+
<AppIcon name="edit" :size="18" />
|
|
91
|
+
</button>
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
class="icon-action"
|
|
95
|
+
title="移除链接,保留文案"
|
|
96
|
+
aria-label="移除链接"
|
|
97
|
+
@click="emit('remove')"
|
|
98
|
+
>
|
|
99
|
+
<AppIcon name="removeLink" :size="18" />
|
|
100
|
+
</button>
|
|
101
|
+
</div>
|
|
102
|
+
</Teleport>
|
|
99
103
|
</template>
|
|
100
104
|
|
|
101
105
|
<style scoped>
|
|
@@ -37,12 +37,14 @@ function onInput(e: Event) {
|
|
|
37
37
|
}, 300)
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
/** 有未提交的防抖输入时立即回流,返回被 flush 的草稿;没有则返回 `null`。 */
|
|
41
|
+
function flushDraft(): string | null {
|
|
42
|
+
if (!typing) return null
|
|
42
43
|
if (timer) clearTimeout(timer)
|
|
43
44
|
timer = null
|
|
44
45
|
typing = false
|
|
45
46
|
emit('update:modelValue', draft.value)
|
|
47
|
+
return draft.value
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
function goToDiagnostic(item: MarkdownDiagnostic) {
|
|
@@ -63,7 +65,7 @@ function selectAllFromHost(): void {
|
|
|
63
65
|
input.setSelectionRange(0, input.value.length)
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
defineExpose({ selectAllFromHost })
|
|
68
|
+
defineExpose({ selectAllFromHost, flushDraft })
|
|
67
69
|
|
|
68
70
|
function onPaste(e: ClipboardEvent) {
|
|
69
71
|
const image = [...(e.clipboardData?.items ?? [])]
|
|
@@ -13,7 +13,15 @@ import LinkPopover from './LinkPopover.vue'
|
|
|
13
13
|
import SelectionToolbar from './SelectionToolbar.vue'
|
|
14
14
|
import { pasteCanvasOutline, readMindmapClipboard, writeMindmapClipboard } from './mindmapClipboard'
|
|
15
15
|
|
|
16
|
-
const props = defineProps<{
|
|
16
|
+
const props = defineProps<{
|
|
17
|
+
session: MindmapSession
|
|
18
|
+
resolveImageSrc?: (src: string) => string
|
|
19
|
+
/**
|
|
20
|
+
* 浮层挂载点,透传给 `CanvasContextMenu` / `LinkPopover`。
|
|
21
|
+
* 默认 `undefined` = 就地渲染(嵌在笔记里的 Desk / SSG);整页宿主传 `'body'`。
|
|
22
|
+
*/
|
|
23
|
+
teleportTo?: string
|
|
24
|
+
}>()
|
|
17
25
|
|
|
18
26
|
const emit = defineEmits<{
|
|
19
27
|
ready: [editor: CanvasEditor]
|
|
@@ -209,6 +217,7 @@ onBeforeUnmount(() => {
|
|
|
209
217
|
v-if="linkEditor"
|
|
210
218
|
:url="linkEditor.url"
|
|
211
219
|
:position="linkEditor.position"
|
|
220
|
+
:teleport-to="teleportTo"
|
|
212
221
|
@save="saveLink"
|
|
213
222
|
@remove="removeLink"
|
|
214
223
|
@keep="keepLinkPopover"
|
|
@@ -227,6 +236,7 @@ onBeforeUnmount(() => {
|
|
|
227
236
|
:can-delete-tree="contextMenu.canDeleteTree"
|
|
228
237
|
:can-toggle-siblings="contextMenu.canToggleSiblings"
|
|
229
238
|
:can-focus="contextMenu.canFocus"
|
|
239
|
+
:teleport-to="teleportTo"
|
|
230
240
|
@insert-sibling="insertSibling"
|
|
231
241
|
@insert-child="insertChild"
|
|
232
242
|
@insert-parent="insertParent"
|