Skip to content

Commit 28eaf21

Browse files
committed
feat: 支持清除快捷键
1 parent 6d02afd commit 28eaf21

7 files changed

Lines changed: 143 additions & 41 deletions

File tree

lang/index.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3878,5 +3878,21 @@
38783878
"ru": "",
38793879
"en": "",
38803880
"fr": ""
3881+
},
3882+
"ei74fc4": {
3883+
"zh-cn": "清除绑定",
3884+
"ja": "",
3885+
"ko": "",
3886+
"ru": "",
3887+
"en": "",
3888+
"fr": ""
3889+
},
3890+
"fq1zn3": {
3891+
"zh-cn": "未绑定",
3892+
"ja": "",
3893+
"ko": "",
3894+
"ru": "",
3895+
"en": "",
3896+
"fr": ""
38813897
}
38823898
}

src/core/editor.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1140,7 +1140,9 @@ export class MilkupEditor implements IMilkupEditor {
11401140
const customMap = this.getCustomKeyMap();
11411141
const def = DEFAULT_SHORTCUTS.find((s) => s.id === actionId);
11421142
if (!def) return "";
1143-
const key = customMap[actionId] || def.defaultKey;
1143+
const customKey = customMap[actionId];
1144+
const key = customKey === undefined ? def.defaultKey : (customKey ?? "");
1145+
if (!key) return "";
11441146
return formatShortcutDisplay(key);
11451147
}
11461148

src/core/keymap/dynamic-keymap.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ export function createDynamicKeymapPlugin(
3838
// 构建 ProseMirror 格式的 key → command 绑定
3939
const bindings: Record<string, any> = {};
4040
for (const shortcut of DEFAULT_SHORTCUTS) {
41-
const boundKey = customMap[shortcut.id] || shortcut.defaultKey;
41+
const customKey = customMap[shortcut.id];
42+
const boundKey = customKey === undefined ? shortcut.defaultKey : customKey;
4243
const command = commandMap[shortcut.id];
43-
if (command) {
44+
if (command && boundKey) {
4445
bindings[boundKey] = command;
4546
}
4647
}

src/core/keymap/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@ export interface ShortcutDefinition {
4444
defaultKey: string;
4545
}
4646

47-
/** 用户自定义快捷键映射(仅存储与默认值不同的部分) */
48-
export type ShortcutKeyMap = Partial<Record<ShortcutActionId, string>>;
47+
/** 用户自定义快捷键映射(仅存储与默认值不同的部分;null 表示显式清除绑定) */
48+
export type ShortcutKeyMap = Partial<Record<ShortcutActionId, string | null>>;

src/renderer/components/settings/ShortcutPage.vue

Lines changed: 102 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const {
1313
hasConflict,
1414
getConflictLabels,
1515
updateShortcut,
16+
clearShortcut,
1617
resetShortcut,
1718
resetAll,
1819
CATEGORY_LABELS,
@@ -157,6 +158,12 @@ function toggleCategory(cat: ShortcutCategory) {
157158
recordingKey = '';
158159
return;
159160
}
161+
if (e.key === 'Backspace' || e.key === 'Delete') {
162+
clearShortcut(s.id);
163+
recordingId = null;
164+
recordingKey = '';
165+
return;
166+
}
160167
const k = keyEventToProseMirrorKey(e);
161168
if (k) {
162169
updateShortcut(s.id, k);
@@ -172,14 +179,25 @@ function toggleCategory(cat: ShortcutCategory) {
172179
>
173180
{{ recordingId === s.id ? "请按下新快捷键..." : formatKeyForDisplay(s.key) }}
174181
</div>
175-
<button
176-
v-if="s.key !== s.defaultKey"
177-
class="reset-btn"
178-
title="重置为默认值"
179-
@click="resetShortcut(s.id)"
180-
>
181-
182-
</button>
182+
<div class="shortcut-actions">
183+
<button
184+
v-if="s.key"
185+
class="action-btn icon-btn"
186+
title="清除绑定"
187+
@click="clearShortcut(s.id)"
188+
>
189+
<AppIcon name="trash" />
190+
</button>
191+
<span v-else class="action-placeholder" aria-hidden="true"></span>
192+
<button
193+
v-if="s.key !== s.defaultKey"
194+
class="action-btn reset-btn"
195+
title="重置为默认值"
196+
@click="resetShortcut(s.id)"
197+
>
198+
199+
</button>
200+
</div>
183201
</div>
184202
</div>
185203
</div>
@@ -198,15 +216,18 @@ function toggleCategory(cat: ShortcutCategory) {
198216
.shortcut-page {
199217
display: flex;
200218
flex-direction: column;
201-
gap: 16px;
219+
gap: 20px;
202220
height: 100%;
203-
overflow-y: auto;
221+
padding: 8px 20px 24px 8px;
222+
box-sizing: border-box;
223+
overflow: hidden;
204224
}
205225
206226
.search-area {
207227
display: flex;
208-
gap: 8px;
228+
gap: 12px;
209229
flex-shrink: 0;
230+
padding: 4px 0 2px;
210231
211232
.search-box {
212233
flex: 1;
@@ -238,9 +259,10 @@ function toggleCategory(cat: ShortcutCategory) {
238259
239260
.search-input {
240261
width: 100%;
241-
padding: 8px 12px;
262+
min-height: 40px;
263+
padding: 10px 14px;
242264
border: 1px solid var(--border-color-1);
243-
border-radius: 6px;
265+
border-radius: 10px;
244266
background: var(--background-color);
245267
color: var(--text-color);
246268
font-size: 13px;
@@ -254,9 +276,10 @@ function toggleCategory(cat: ShortcutCategory) {
254276
255277
.key-search-input {
256278
width: 100%;
257-
padding: 8px 12px;
279+
min-height: 40px;
280+
padding: 10px 14px;
258281
border: 1px solid var(--border-color-1);
259-
border-radius: 6px;
282+
border-radius: 10px;
260283
background: var(--background-color);
261284
color: var(--text-color-2);
262285
font-size: 13px;
@@ -279,21 +302,25 @@ function toggleCategory(cat: ShortcutCategory) {
279302
.shortcut-list {
280303
display: flex;
281304
flex-direction: column;
282-
gap: 8px;
283-
flex-shrink: 0;
305+
gap: 12px;
306+
flex: 1;
307+
min-height: 0;
308+
overflow-y: auto;
309+
padding-right: 4px;
284310
}
285311
286312
.category-section {
287313
border: 1px solid var(--border-color-1);
288-
border-radius: 8px;
289-
overflow: hidden;
314+
border-radius: 12px;
315+
overflow: visible;
316+
background: var(--background-color);
290317
}
291318
292319
.category-header {
293320
display: flex;
294321
align-items: center;
295322
gap: 8px;
296-
padding: 10px 16px;
323+
padding: 14px 18px;
297324
cursor: pointer;
298325
background: var(--background-color);
299326
user-select: none;
@@ -339,7 +366,9 @@ function toggleCategory(cat: ShortcutCategory) {
339366
display: flex;
340367
align-items: center;
341368
justify-content: space-between;
342-
padding: 8px 16px 8px 36px;
369+
flex-wrap: wrap;
370+
gap: 20px;
371+
padding: 14px 18px 14px 42px;
343372
border-top: 1px solid var(--border-color-1);
344373
345374
&:hover {
@@ -349,12 +378,18 @@ function toggleCategory(cat: ShortcutCategory) {
349378
.shortcut-label {
350379
font-size: 13px;
351380
color: var(--text-color);
381+
flex: 1;
382+
min-width: 0;
352383
}
353384
354385
.shortcut-key-area {
355386
display: flex;
356387
align-items: center;
357-
gap: 6px;
388+
justify-content: flex-end;
389+
flex-wrap: wrap;
390+
gap: 8px;
391+
flex-shrink: 0;
392+
margin-left: auto;
358393
}
359394
360395
.conflict-icon {
@@ -364,15 +399,15 @@ function toggleCategory(cat: ShortcutCategory) {
364399
}
365400
366401
.key-badge {
367-
padding: 4px 10px;
402+
padding: 7px 12px;
368403
border: 1px solid var(--border-color-1);
369-
border-radius: 4px;
404+
border-radius: 8px;
370405
background: var(--background-color);
371406
color: var(--text-color);
372407
font-size: 12px;
373408
font-family: monospace;
374409
cursor: pointer;
375-
min-width: 80px;
410+
min-width: 104px;
376411
text-align: center;
377412
outline: none;
378413
user-select: none;
@@ -404,35 +439,69 @@ function toggleCategory(cat: ShortcutCategory) {
404439
}
405440
}
406441
407-
.reset-btn {
408-
padding: 2px 6px;
442+
.shortcut-actions {
443+
display: flex;
444+
align-items: center;
445+
gap: 6px;
446+
min-width: 70px;
447+
justify-content: flex-end;
448+
}
449+
450+
.action-btn {
451+
width: 32px;
452+
min-width: 32px;
453+
height: 32px;
454+
padding: 0;
409455
border: 1px solid var(--border-color-1);
410-
border-radius: 4px;
456+
border-radius: 8px;
411457
background: transparent;
412458
color: var(--text-color-2);
413-
font-size: 14px;
459+
font-size: 12px;
414460
cursor: pointer;
415461
line-height: 1;
462+
display: inline-flex;
463+
align-items: center;
464+
justify-content: center;
416465
417466
&:hover {
418467
background: var(--hover-color);
419468
color: var(--text-color);
420469
}
470+
471+
&:disabled {
472+
opacity: 0.45;
473+
cursor: not-allowed;
474+
}
475+
}
476+
477+
.icon-btn {
478+
font-size: 14px;
479+
}
480+
481+
.reset-btn {
482+
font-size: 14px;
483+
}
484+
485+
.action-placeholder {
486+
width: 32px;
487+
min-width: 32px;
488+
height: 32px;
489+
flex-shrink: 0;
421490
}
422491
}
423492
424493
.footer-actions {
425494
display: flex;
426495
justify-content: flex-end;
427-
padding-top: 8px;
428-
padding-bottom: 16px;
496+
margin-top: auto;
497+
padding: 4px 0 8px;
429498
border-top: 1px solid var(--border-color-1);
430499
flex-shrink: 0;
431500
432501
.reset-all-btn {
433-
padding: 6px 16px;
502+
padding: 8px 16px;
434503
border: 1px solid var(--border-color-1);
435-
border-radius: 6px;
504+
border-radius: 8px;
436505
background: transparent;
437506
color: var(--text-color);
438507
font-size: 13px;

src/renderer/components/ui/AppIcon.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
SquarePen,
3131
SquareSquare,
3232
Type,
33+
Trash2,
3334
WandSparkles,
3435
X,
3536
Plus,
@@ -69,6 +70,7 @@ const iconMap = {
6970
"shortcut-key": Keyboard,
7071
"magic-wand": WandSparkles,
7172
"image-config": FileOutput,
73+
trash: Trash2,
7274
} satisfies Record<string, FunctionalComponent<LucideProps>>;
7375
7476
type AppIconName = keyof typeof iconMap;

src/renderer/hooks/useShortcutConfig.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,24 @@ import type { ShortcutActionId, ShortcutDefinition } from "@/core";
1010
export function useShortcutConfig() {
1111
const { config } = useConfig();
1212

13+
function getResolvedKey(id: ShortcutActionId, defaultKey: string): string {
14+
const customKey = config.value.shortcuts?.[id];
15+
return customKey === undefined ? defaultKey : (customKey ?? "");
16+
}
17+
1318
/** 合并默认值和用户自定义值后的完整快捷键列表 */
1419
const shortcuts = computed<ShortcutDefinition[]>(() => {
15-
const customMap = config.value.shortcuts || {};
1620
return DEFAULT_SHORTCUTS.map((def) => ({
1721
...def,
18-
key: customMap[def.id] || def.defaultKey,
22+
key: getResolvedKey(def.id, def.defaultKey),
1923
}));
2024
});
2125

2226
/** 冲突检测:同一快捷键绑定了多个动作 */
2327
const conflicts = computed<Map<string, ShortcutActionId[]>>(() => {
2428
const keyToActions = new Map<string, ShortcutActionId[]>();
2529
for (const s of shortcuts.value) {
30+
if (!s.key) continue;
2631
const existing = keyToActions.get(s.key);
2732
if (existing) {
2833
existing.push(s.id);
@@ -63,7 +68,7 @@ export function useShortcutConfig() {
6368
}
6469

6570
/** 更新单个快捷键 */
66-
function updateShortcut(id: ShortcutActionId, newKey: string) {
71+
function updateShortcut(id: ShortcutActionId, newKey: string | null) {
6772
const current = { ...config.value.shortcuts };
6873
const def = DEFAULT_SHORTCUTS.find((d) => d.id === id);
6974
// 如果和默认值相同,删除自定义项
@@ -75,6 +80,11 @@ export function useShortcutConfig() {
7580
config.value = { ...config.value, shortcuts: current };
7681
}
7782

83+
/** 清除单个快捷键绑定 */
84+
function clearShortcut(id: ShortcutActionId) {
85+
updateShortcut(id, null);
86+
}
87+
7888
/** 重置单个快捷键 */
7989
function resetShortcut(id: ShortcutActionId) {
8090
const current = { ...config.value.shortcuts };
@@ -93,6 +103,7 @@ export function useShortcutConfig() {
93103
hasConflict,
94104
getConflictLabels,
95105
updateShortcut,
106+
clearShortcut,
96107
resetShortcut,
97108
resetAll,
98109
CATEGORY_LABELS,
@@ -104,6 +115,7 @@ export function useShortcutConfig() {
104115
* 例如:Mod-b → Ctrl+B (Windows) / Cmd+B (Mac)
105116
*/
106117
export function formatKeyForDisplay(key: string): string {
118+
if (!key) return "未绑定";
107119
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
108120
return key
109121
.split("-")

0 commit comments

Comments
 (0)