Skip to content

Commit 70ae85f

Browse files
authored
Merge pull request #114 from qianmoQ/dev-26.3.0
Refactor global shortcuts and add new editor commands
2 parents 113235a + 0be12cd commit 70ae85f

8 files changed

Lines changed: 130 additions & 18 deletions

File tree

.github/workflows/docs.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ jobs:
4747
- name: Upload artifact
4848
uses: actions/upload-pages-artifact@v3
4949
with:
50-
path: docs/dist
50+
# 用绝对路径:job 设了 working-directory: docs,upload-pages-artifact 内部
51+
# 的 tar 会继承该目录,相对路径 docs/dist 会被当成 docs/docs/dist 而找不到。
52+
path: ${{ github.workspace }}/docs/dist
5153

5254
deploy:
5355
needs: build

docs/.gitignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ dist
33
.vite
44
*.local
55

6-
# 根 .gitignore 忽略了所有 pnpm-lock.yaml,这里为文档站重新纳入
7-
# 供 CI 的 --frozen-lockfile 与依赖缓存使用
6+
# 根 .gitignore 用 *.json / pnpm-lock.yaml 忽略了这些文件
7+
# 这里为文档站重新纳入版本控制(构建/类型检查/依赖锁定需要)
88
!pnpm-lock.yaml
9+
!tsconfig.json
10+
!package.json

docs/package.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "codeforge-website",
3+
"type": "module",
4+
"version": "1.0.0",
5+
"private": true,
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "vite-ssg build",
9+
"preview": "vite preview"
10+
},
11+
"dependencies": {
12+
"vue": "^3.5.13",
13+
"vue-i18n": "^11.4.6",
14+
"vue-router": "^4.5.0"
15+
},
16+
"devDependencies": {
17+
"@unhead/vue": "^1.11.14",
18+
"@vitejs/plugin-vue": "^5.2.1",
19+
"@vue/tsconfig": "^0.7.0",
20+
"autoprefixer": "^10.4.20",
21+
"markdown-it-anchor": "^9.2.0",
22+
"postcss": "^8.4.49",
23+
"tailwindcss": "^3.4.17",
24+
"typescript": "^5.7.2",
25+
"unplugin-vue-markdown": "^28.3.1",
26+
"vite": "^6.0.7",
27+
"vite-ssg": "^0.24.1",
28+
"vue-tsc": "^2.2.0"
29+
}
30+
}

docs/tsconfig.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "@vue/tsconfig/tsconfig.dom.json",
3+
"compilerOptions": {
4+
"baseUrl": ".",
5+
"paths": {"@/*": ["./src/*"]},
6+
"types": ["vite/client"]
7+
},
8+
"include": ["src/**/*.ts", "src/**/*.vue", "vite.config.ts", "env.d.ts"]
9+
}

src/App.vue

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,14 @@
367367
:file-name="currentFileName"
368368
@close="showDiff = false"/>
369369

370+
<!-- 与剪贴板比较 -->
371+
<DiffView v-if="clipboardDiff"
372+
:original="clipboardDiff.original"
373+
:modified="code"
374+
:file-name="currentFileName"
375+
:title="t('diff.clipboardTitle')"
376+
@close="clipboardDiff = null"/>
377+
370378
<!-- 应用 AI 代码前的差异预览 -->
371379
<DiffView v-if="applyPreview"
372380
:original="code"
@@ -540,6 +548,7 @@ import {useGitStatus} from './composables/useGitStatus'
540548
import {useSessionTabs} from './composables/useSessionTabs'
541549
import {useEditorContextMenu} from './composables/useEditorContextMenu'
542550
import {useRunConfig} from './composables/useRunConfig'
551+
import {useGlobalShortcuts} from './composables/useGlobalShortcuts'
543552
import EditorTabs from './components/EditorTabs.vue'
544553
import IndentControl from './components/IndentControl.vue'
545554
import Sidebar from './components/Sidebar.vue'
@@ -1108,6 +1117,25 @@ const editorView = shallowRef<any>(null)
11081117
// ===== 文本变换命令(排序行/大小写/去重/去行尾空白)=====
11091118
const {transformSelectionOrLine, sortLines, removeDuplicateLines, trimTrailingWhitespace} = useTextCommands(editorView)
11101119
1120+
// 复制为 Markdown 代码块(选区或全文,带语言围栏)
1121+
const copyAsMarkdown = async () => {
1122+
const view = editorView.value
1123+
if (!view) {
1124+
return
1125+
}
1126+
const sel = view.state.selection.main
1127+
const text = sel.empty ? view.state.doc.toString() : view.state.doc.sliceString(sel.from, sel.to)
1128+
const lang = (currentLanguage.value || '').toLowerCase().replace(/\d+$/, '')
1129+
const fence = '```' + lang + '\n' + text.replace(/\n$/, '') + '\n```'
1130+
try {
1131+
await navigator.clipboard.writeText(fence)
1132+
toast.success(t('app.copiedMarkdown'))
1133+
}
1134+
catch (error) {
1135+
toast.error(t('app.copyFailed') + error)
1136+
}
1137+
}
1138+
11111139
// AI 自然语言生成 / 选区改写
11121140
const showGenerate = ref(false)
11131141
const generateSelection = ref('')
@@ -1542,6 +1570,21 @@ const openDiff = () => {
15421570
}
15431571
showDiff.value = true
15441572
}
1573+
// 与剪贴板内容比较(剪贴板为原始,当前编辑内容为修改)
1574+
const clipboardDiff = ref<{ original: string } | null>(null)
1575+
const compareWithClipboard = async () => {
1576+
try {
1577+
const text = await navigator.clipboard.readText()
1578+
if (!text) {
1579+
toast.info(t('app.clipboardEmpty'))
1580+
return
1581+
}
1582+
clipboardDiff.value = {original: text}
1583+
}
1584+
catch (error) {
1585+
toast.error(t('app.clipboardReadFailed') + error)
1586+
}
1587+
}
15451588
const togglePreview = () => {
15461589
showPreview.value = !showPreview.value
15471590
}
@@ -2096,7 +2139,7 @@ const isOverlayOpen = () =>
20962139
|| showHistory.value || showViewer.value || showRunPrompt.value
20972140
|| showQuickOpen.value || showGenerate.value || showSearch.value
20982141
|| showCommandPalette.value || showDiff.value || showGoToLine.value || showOutline.value || showSnippets.value
2099-
|| applyPreview.value != null
2142+
|| applyPreview.value != null || clipboardDiff.value != null
21002143
21012144
// 全局快捷键(绑定可在设置中自定义)
21022145
const {matchAction: matchShortcut, reload: reloadShortcuts, getBinding, formatCombo} = useShortcuts()
@@ -2171,6 +2214,7 @@ const paletteCommands = computed<PaletteCommand[]>(() => [
21712214
{id: 'formatWithAi', label: t('command.formatWithAi'), icon: Sparkles, run: () => formatWithAi()},
21722215
{id: 'history', label: t('command.history'), icon: History, run: () => { showHistory.value = true }},
21732216
{id: 'diff', label: t('command.diff'), icon: GitCompare, run: () => openDiff()},
2217+
{id: 'compareClipboard', label: t('command.compareClipboard'), icon: GitCompare, run: () => compareWithClipboard()},
21742218
{id: 'preview', label: t('command.preview'), icon: Eye, run: () => togglePreview()},
21752219
{id: 'git', label: t('command.git'), icon: GitBranch, run: () => openGit()},
21762220
{id: 'tasks', label: t('command.tasks'), icon: ListChecks, run: () => openTasks()},
@@ -2187,6 +2231,7 @@ const paletteCommands = computed<PaletteCommand[]>(() => [
21872231
{id: 'toLowerCase', label: t('command.toLowerCase'), group: t('command.groupText'), icon: CaseLower, run: () => transformSelectionOrLine(s => s.toLowerCase())},
21882232
{id: 'removeDuplicateLines', label: t('command.removeDuplicateLines'), group: t('command.groupText'), icon: ListChecks, run: () => removeDuplicateLines()},
21892233
{id: 'trimTrailingWhitespace', label: t('command.trimTrailingWhitespace'), group: t('command.groupText'), icon: Eraser, run: () => trimTrailingWhitespace()},
2234+
{id: 'copyAsMarkdown', label: t('command.copyAsMarkdown'), group: t('command.groupText'), icon: Code2, run: () => copyAsMarkdown()},
21902235
{id: 'toggleAutoReveal', label: t('command.toggleAutoReveal'), icon: FolderOpen, run: () => toggleAutoReveal()},
21912236
{id: 'toggleSidebar', label: t('command.toggleSidebar'), icon: PanelLeft, hint: hintOf('toggleSidebar'), run: () => toggleSidebar()},
21922237
{id: 'toggleZen', label: t('command.toggleZen'), icon: Minimize2, run: () => toggleZen()},
@@ -2200,18 +2245,8 @@ const paletteCommands = computed<PaletteCommand[]>(() => [
22002245
{id: 'settings', label: t('command.settings'), icon: SettingsIcon, run: () => { showSettings.value = true }}
22012246
])
22022247
2203-
const onGlobalKeydown = (e: KeyboardEvent) => {
2204-
if (isOverlayOpen()) {
2205-
return
2206-
}
2207-
const action = matchShortcut(e)
2208-
if (action && shortcutDispatch[action]) {
2209-
// 捕获阶段拦截:阻止事件到达编辑器(避免 Cmd+Enter 等被插入换行)
2210-
e.preventDefault()
2211-
e.stopPropagation()
2212-
shortcutDispatch[action]()
2213-
}
2214-
}
2248+
// 全局快捷键(捕获拦截 + 派发)抽离到 useGlobalShortcuts
2249+
useGlobalShortcuts(matchShortcut, shortcutDispatch, isOverlayOpen)
22152250
22162251
const {init: initTheme, setTheme: setAppTheme} = useTheme()
22172252
@@ -2236,7 +2271,6 @@ onMounted(async () => {
22362271
// 恢复上次打开的文件标签
22372272
await restoreSession()
22382273
2239-
window.addEventListener('keydown', onGlobalKeydown, true)
22402274
window.addEventListener('lsp:open-location', onLspOpenLocation)
22412275
window.addEventListener('lsp:code-actions', onLspCodeActions)
22422276
@@ -2246,7 +2280,6 @@ onMounted(async () => {
22462280
22472281
onUnmounted(() => {
22482282
cleanupEventListeners()
2249-
window.removeEventListener('keydown', onGlobalKeydown, true)
22502283
window.removeEventListener('lsp:open-location', onLspOpenLocation)
22512284
window.removeEventListener('lsp:code-actions', onLspCodeActions)
22522285
})
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// 全局快捷键:捕获阶段拦截 keydown,匹配到动作则阻止默认并派发。
2+
import {onMounted, onUnmounted} from 'vue'
3+
4+
export function useGlobalShortcuts(
5+
matchShortcut: (e: KeyboardEvent) => string | null,
6+
dispatch: Record<string, () => void>,
7+
isOverlayOpen: () => boolean
8+
) {
9+
const onGlobalKeydown = (e: KeyboardEvent) => {
10+
if (isOverlayOpen()) {
11+
return
12+
}
13+
const action = matchShortcut(e)
14+
if (action && dispatch[action]) {
15+
// 捕获阶段拦截:阻止事件到达编辑器(避免 Cmd+Enter 等被插入换行)
16+
e.preventDefault()
17+
e.stopPropagation()
18+
dispatch[action]()
19+
}
20+
}
21+
22+
onMounted(() => window.addEventListener('keydown', onGlobalKeydown, true))
23+
onUnmounted(() => window.removeEventListener('keydown', onGlobalKeydown, true))
24+
}

src/i18n/locales/en.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,8 @@
702702
"toLowerCase": "Transform to lowercase",
703703
"removeDuplicateLines": "Remove duplicate lines",
704704
"trimTrailingWhitespace": "Trim trailing whitespace",
705+
"copyAsMarkdown": "Copy as Markdown code block",
706+
"compareClipboard": "Compare with clipboard",
705707
"groupText": "Text",
706708
"toggleAutoReveal": "Toggle: auto-reveal active file",
707709
"toggleSidebar": "Toggle sidebar",
@@ -1057,6 +1059,7 @@
10571059
},
10581060
"diff": {
10591061
"title": "Diff",
1062+
"clipboardTitle": "Compare with clipboard",
10601063
"close": "Close",
10611064
"subtitle": "Saved (red) → Current (green)",
10621065
"noDiff": "No differences, content is identical",
@@ -1320,6 +1323,9 @@
13201323
"untitled": "Untitled",
13211324
"multiEngine": "This type has multiple run engines; selected \"{name}\", switch manually in the dropdown",
13221325
"pathCopied": "Path copied",
1326+
"copiedMarkdown": "Copied as Markdown code block",
1327+
"clipboardEmpty": "Clipboard is empty",
1328+
"clipboardReadFailed": "Failed to read clipboard: ",
13231329
"copyFailed": "Copy failed: ",
13241330
"notTextFile": "Not a text file, cannot open",
13251331
"openFailed": "Open failed: ",

src/i18n/locales/zh-CN.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,8 @@
702702
"toLowerCase": "转为小写",
703703
"removeDuplicateLines": "删除重复行",
704704
"trimTrailingWhitespace": "去除行尾空白",
705+
"copyAsMarkdown": "复制为 Markdown 代码块",
706+
"compareClipboard": "与剪贴板比较",
705707
"groupText": "文本",
706708
"toggleAutoReveal": "切换:自动定位当前文件",
707709
"toggleSidebar": "切换侧栏",
@@ -1057,6 +1059,7 @@
10571059
},
10581060
"diff": {
10591061
"title": "差异对比",
1062+
"clipboardTitle": "与剪贴板比较",
10601063
"close": "关闭",
10611064
"subtitle": "已保存(红) → 当前(绿)",
10621065
"noDiff": "没有差异,内容一致",
@@ -1320,6 +1323,9 @@
13201323
"untitled": "未命名",
13211324
"multiEngine": "该类型可用多个运行引擎,已选「{name}」,可在下拉手动切换",
13221325
"pathCopied": "已复制路径",
1326+
"copiedMarkdown": "已复制为 Markdown 代码块",
1327+
"clipboardEmpty": "剪贴板为空",
1328+
"clipboardReadFailed": "读取剪贴板失败: ",
13231329
"copyFailed": "复制失败: ",
13241330
"notTextFile": "不是文本文件,无法打开",
13251331
"openFailed": "打开失败: ",

0 commit comments

Comments
 (0)