Skip to content

Commit a3c25c7

Browse files
author
sky.sun
committed
feat: 编辑器头部固定、增加快捷键、增加Tooltip
1 parent 0ded156 commit a3c25c7

File tree

5 files changed

+443
-51
lines changed

5 files changed

+443
-51
lines changed

docs/.vitepress/components/InteractiveCode.vue

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,41 @@
11
<template>
2-
<div class="interactive-code border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden my-4">
3-
<div class="border-b border-gray-200 dark:border-gray-700">
4-
<div class="px-4 py-2 flex justify-between items-center text-xs font-semibold bg-transparent dark:bg-[#1e1e1e]"
2+
<div class="interactive-code border-gray-200 dark:border-gray-700 my-4">
3+
<div class="border border-gray-200 dark:border-gray-700 rounded-t-lg overflow-hidden sticky top-[64px] z-10">
4+
<div class="px-4 py-2 flex justify-between items-center text-xs font-semibold bg-gray-50 dark:bg-[#1e1e1e]"
55
:class="[colorTheme.text]">
66
<span class="text-sm">{{ computedTitle }}</span>
77
<div class="flex items-center gap-1">
8-
<button @click="copyCode"
9-
class="text-current px-2 py-1.5 rounded-md text-xs cursor-pointer transition-all duration-200 flex items-center gap-1"
10-
:class="[colorTheme.hover]" title="复制代码">
11-
<CopyIcon v-if="!copySuccess" :size="14" />
12-
<CheckIcon v-else :size="14" />
13-
<span class="text-xs">{{ copySuccess ? '已复制' : '复制' }}</span>
14-
</button>
15-
<button v-if="runnable" @click="resetCode"
16-
class="text-current px-2 py-1.5 rounded-md text-xs cursor-pointer transition-all duration-200 flex items-center gap-1"
17-
:class="[colorTheme.hover]" title="重置代码">
18-
<RotateCcwIcon :size="14" />
19-
<span class="text-xs">重置</span>
20-
</button>
21-
<button v-if="runnable" @click="() => customRunCode(lang)"
22-
class="text-current px-2 py-1.5 rounded-md text-xs cursor-pointer transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-1"
23-
:class="[colorTheme.hover]" :disabled="isRunning || (lang === 'py' && !pyodideReady)"
24-
:title="getButtonText(lang)">
25-
<LoaderIcon v-if="isRunning || (lang === 'py' && !pyodideReady)" :size="14" class="animate-spin" />
26-
<PlayIcon v-else :size="14" />
27-
<span class="text-xs">{{ getSimpleButtonText(lang) }}</span>
28-
</button>
8+
<SimpleTooltip content="复制代码">
9+
<button @click="copyCode"
10+
class="text-current px-2 py-1.5 rounded-md text-xs cursor-pointer transition-all duration-200 flex items-center gap-1"
11+
:class="[colorTheme.hover]">
12+
<CopyIcon v-if="!copySuccess" :size="14" />
13+
<CheckIcon v-else :size="14" />
14+
<span class="text-xs">{{ copySuccess ? '已复制' : '复制' }}</span>
15+
</button>
16+
</SimpleTooltip>
17+
<SimpleTooltip v-if="runnable" content="重置代码">
18+
<button @click="resetCode"
19+
class="text-current px-2 py-1.5 rounded-md text-xs cursor-pointer transition-all duration-200 flex items-center gap-1"
20+
:class="[colorTheme.hover]">
21+
<RotateCcwIcon :size="14" />
22+
<span class="text-xs">重置</span>
23+
</button>
24+
</SimpleTooltip>
25+
<SimpleTooltip v-if="runnable" :content="`${getButtonText(lang)} (Ctrl/⌘+Enter)`">
26+
<button @click="handleRunCode"
27+
class="text-current px-2 py-1.5 rounded-md text-xs cursor-pointer transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-1"
28+
:class="[colorTheme.hover]" :disabled="isRunning || (lang === 'py' && !pyodideReady)">
29+
<LoaderIcon v-if="isRunning || (lang === 'py' && !pyodideReady)" :size="14" class="animate-spin" />
30+
<PlayIcon v-else :size="14" />
31+
<span class="text-xs">{{ getSimpleButtonText(lang) }}</span>
32+
</button>
33+
</SimpleTooltip>
2934
</div>
3035
</div>
3136
</div>
3237

33-
<div class="flex flex-col">
38+
<div class="flex flex-col border border-gray-200 dark:border-gray-700 rounded-b-lg overflow-hidden border-t-0">
3439
<div class="relative" :class="{ 'readonly-code': !runnable }">
3540
<!-- 懒加载占位符 -->
3641
<div v-if="!isInitialized"
@@ -69,6 +74,7 @@
6974
import { ref, computed, nextTick, onMounted, onUnmounted, type Ref } from 'vue'
7075
import { PlayIcon, LoaderIcon, RotateCcwIcon, CopyIcon, CheckIcon, XIcon } from 'lucide-vue-next'
7176
import { useCodeEditor } from '../composables/useCodeEditor'
77+
import SimpleTooltip from './SimpleTooltip.vue'
7278
7379
interface Props {
7480
lang: string
@@ -114,8 +120,23 @@ const {
114120
copyCode,
115121
getButtonText,
116122
getSimpleButtonText,
123+
enableKeyboardShortcuts,
117124
destroy
118-
} = useCodeEditor({ runnable: props.runnable, onCodeChange })
125+
} = useCodeEditor({
126+
runnable: props.runnable,
127+
onCodeChange,
128+
lang: props.lang,
129+
customRunFunction: async (lang: string) => {
130+
await runCode(lang)
131+
showOutput.value = true
132+
}
133+
})
134+
135+
// 本地运行函数(用于按钮点击)
136+
const handleRunCode = async () => {
137+
await runCode(props.lang)
138+
showOutput.value = true
139+
}
119140
120141
// 计算属性
121142
const computedTitle = computed((): string => {
@@ -202,6 +223,9 @@ async function initializeLazyEditor(): Promise<void> {
202223
}
203224
204225
setupThemeObserver()
226+
227+
// 设置键盘快捷键
228+
enableKeyboardShortcuts()
205229
}
206230
207231
// 设置主题观察器
@@ -266,14 +290,17 @@ function setupIntersectionObserver(): void {
266290
function resetCode(): void {
267291
const processedCode = processCode(props.code)
268292
269-
// 先销毁现有编辑器
293+
// 先销毁现有编辑器(会自动清理快捷键)
270294
destroy()
271295
272296
// 重新初始化编辑器
273297
initializeEditor(props.lang, processedCode, {
274298
maxHeight: '600px'
275299
})
276300
301+
// 重新设置键盘快捷键
302+
enableKeyboardShortcuts()
303+
277304
output.value = ''
278305
hasError.value = false
279306
showOutput.value = false
@@ -284,12 +311,7 @@ function closeOutput(): void {
284311
showOutput.value = false
285312
}
286313
287-
// 覆盖运行函数以显示输出
288-
const originalRunCode = runCode
289-
const customRunCode = async (lang: string) => {
290-
await originalRunCode(lang)
291-
showOutput.value = true
292-
}
314+
293315
294316
onMounted(async (): Promise<void> => {
295317
await nextTick()
@@ -302,7 +324,7 @@ onMounted(async (): Promise<void> => {
302324
})
303325
304326
onUnmounted((): void => {
305-
destroy()
327+
destroy() // destroy 函数会自动清理快捷键
306328
if (intersectionObserver) {
307329
intersectionObserver.disconnect()
308330
intersectionObserver = null

docs/.vitepress/components/PlaygroundEditor.vue

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,23 @@
1717
</div>
1818
</div>
1919
<div class="flex items-center gap-2">
20-
<button @click="copyCode"
21-
class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 px-3 py-1.5 rounded-md text-sm cursor-pointer transition-all duration-200 hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-1"
22-
title="复制代码">
23-
<CopyIcon v-if="!copySuccess" :size="16" />
24-
<CheckIcon v-else :size="16" />
25-
<span>{{ copySuccess ? '已复制' : '复制' }}</span>
26-
</button>
27-
<button @click="() => runCode(selectedLang)"
28-
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-1.5 rounded-md text-sm cursor-pointer transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
29-
:disabled="isRunning || (selectedLang === 'py' && !pyodideReady)"
30-
:title="getButtonText(selectedLang)">
31-
<LoaderIcon v-if="isRunning || (selectedLang === 'py' && !pyodideReady)" :size="16" class="animate-spin" />
32-
<PlayIcon v-else :size="16" />
33-
<span>{{ getSimpleButtonText(selectedLang) }}</span>
34-
</button>
20+
<SimpleTooltip content="复制代码" placement="bottom">
21+
<button @click="copyCode"
22+
class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 px-3 py-1.5 rounded-md text-sm cursor-pointer transition-all duration-200 hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-1">
23+
<CopyIcon v-if="!copySuccess" :size="16" />
24+
<CheckIcon v-else :size="16" />
25+
<span>{{ copySuccess ? '已复制' : '复制' }}</span>
26+
</button>
27+
</SimpleTooltip>
28+
<SimpleTooltip :content="`${getButtonText(selectedLang)} (Ctrl/⌘+Enter)`" placement="bottom-end">
29+
<button @click="() => runCode(selectedLang)"
30+
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-1.5 rounded-md text-sm cursor-pointer transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
31+
:disabled="isRunning || (selectedLang === 'py' && !pyodideReady)">
32+
<LoaderIcon v-if="isRunning || (selectedLang === 'py' && !pyodideReady)" :size="16" class="animate-spin" />
33+
<PlayIcon v-else :size="16" />
34+
<span>{{ getSimpleButtonText(selectedLang) }}</span>
35+
</button>
36+
</SimpleTooltip>
3537
</div>
3638
</div>
3739
</div>
@@ -82,6 +84,7 @@
8284
import { ref, onMounted, computed, nextTick, onUnmounted, watch, type Ref } from 'vue'
8385
import { PlayIcon, LoaderIcon, CopyIcon, CheckIcon, XIcon } from 'lucide-vue-next'
8486
import { useCodeEditor } from '../composables/useCodeEditor'
87+
import SimpleTooltip from './SimpleTooltip.vue'
8588
8689
const placeholderRef: Ref<HTMLElement | null> = ref(null)
8790
const selectedLang: Ref<string> = ref('py')
@@ -110,6 +113,7 @@ const {
110113
copyCode,
111114
getButtonText,
112115
getSimpleButtonText,
116+
enableKeyboardShortcuts,
113117
destroy
114118
} = useCodeEditor({ runnable: true, onCodeChange })
115119
@@ -263,9 +267,12 @@ const playgroundTheme = {
263267
}
264268
265269
// 监听语言变化
266-
watch(selectedLang, (newLang) => {
270+
watch(selectedLang, async (newLang) => {
267271
updateEditorLanguage(newLang, defaultCode.value, playgroundTheme)
268272
273+
// 重新设置快捷键以适配新语言
274+
enableKeyboardShortcuts(selectedLang)
275+
269276
// 切换语言时清空输出
270277
output.value = ''
271278
hasError.value = false
@@ -280,6 +287,9 @@ onMounted(async (): Promise<void> => {
280287
// 初始化 Pyodide
281288
await initializePyodide()
282289
290+
// 设置键盘快捷键
291+
enableKeyboardShortcuts(selectedLang)
292+
283293
// 监听主题变化
284294
const observer = new MutationObserver(() => {
285295
handleThemeChange(selectedLang.value, playgroundTheme)
@@ -292,7 +302,7 @@ onMounted(async (): Promise<void> => {
292302
})
293303
294304
onUnmounted((): void => {
295-
destroy()
305+
destroy() // destroy 函数会自动清理快捷键
296306
})
297307
</script>
298308
<style scoped>

0 commit comments

Comments
 (0)