@tobq/boardclip 2.1.112 → 2.1.114
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 +5 -2
- package/editor-preload.js +2 -0
- package/editor.html +12 -0
- package/index.html +94 -48
- package/lib/clipboard-model.js +588 -23
- package/lib/mcp-core.js +240 -69
- package/lib/sync-delta.js +5 -2
- package/lib/windows-hook-policy.js +127 -0
- package/lib/windows-hook-worker.js +18 -77
- package/lib/windows-hook.js +20 -9
- package/main.js +830 -192
- package/mcp/boardclip-mcp.js +68 -39
- package/mcp-approval.html +4 -2
- package/package.json +1 -1
- package/preload.js +4 -2
- package/site/index.html +108 -37
- package/site/shared/clip-search.js +291 -134
- package/site/shared/clipboard-popup.css +8 -20
- package/site/shared/clipboard-ui-core.js +405 -329
- package/viewer.html +1 -1
package/README.md
CHANGED
|
@@ -93,10 +93,13 @@ when you intentionally want Git to autostash local code changes.
|
|
|
93
93
|
| --- | --- |
|
|
94
94
|
| Cmd+Shift+V / Win+V | Toggle popup |
|
|
95
95
|
| Cmd+Numpad 1-9 / Win+Numpad 1-9 | Quick-paste slot |
|
|
96
|
-
| 1-9
|
|
96
|
+
| 1-9 right after the popup opens | Quick-paste slot |
|
|
97
|
+
| N right after the popup opens | New note |
|
|
98
|
+
| Win+N (Windows; record one on macOS in Settings) | New note |
|
|
97
99
|
| Arrow keys | Navigate items |
|
|
98
100
|
| Enter | Paste selected item |
|
|
99
101
|
| Escape | Close popup |
|
|
102
|
+
| Cmd+N / Ctrl+N in the note editor | New note |
|
|
100
103
|
|
|
101
104
|
## Development
|
|
102
105
|
|
|
@@ -148,7 +151,7 @@ kill.bat
|
|
|
148
151
|
- `preload.js`: safe renderer API bridge.
|
|
149
152
|
- `lib/clipboard-model.js`: shared pure history/pin/group merge logic.
|
|
150
153
|
- `lib/cloud-accounts.js`: Google Drive, OneDrive, and iCloud folder discovery.
|
|
151
|
-
- `lib/windows-hook*`: Windows low-level keyboard hook for Win+V / Win+Numpad.
|
|
154
|
+
- `lib/windows-hook*`: Windows low-level keyboard hook for Win+V / Win+N / Numpad.
|
|
152
155
|
- `site/`: static landing page and remote install/update scripts.
|
|
153
156
|
|
|
154
157
|
## Data
|
package/editor-preload.js
CHANGED
|
@@ -31,6 +31,8 @@ contextBridge.exposeInMainWorld('editorApi', {
|
|
|
31
31
|
resolveConflict: (payload) => ipcRenderer.invoke('resolve-conflict', payload),
|
|
32
32
|
unifyStep: (payload) => ipcRenderer.invoke('unify-step', sessionId, payload),
|
|
33
33
|
close: () => ipcRenderer.send('editor-close', sessionId),
|
|
34
|
+
// Ctrl/Cmd+N here: the popup's New note, through the same IPC.
|
|
35
|
+
newNote: (options) => ipcRenderer.invoke('new-note', options || {}),
|
|
34
36
|
// The find bar's mode + match case, remembered per device.
|
|
35
37
|
saveFindPrefs: (prefs) => ipcRenderer.invoke('save-settings', { find_regex: !!(prefs && prefs.regex), find_case: !!(prefs && prefs.caseSensitive) }),
|
|
36
38
|
// A drag on the idle title moves this window (main's window-drag, sender only).
|
package/editor.html
CHANGED
|
@@ -174,6 +174,18 @@ document.addEventListener('contextmenu', (e) => {
|
|
|
174
174
|
openClipMenu(e.clientX, e.clientY);
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
+
// Ctrl+N (Cmd+N on a Mac) while this window has the focus: a new note, through
|
|
178
|
+
// the popup's own New note action (the 'new-note' IPC -> openEditor, one path).
|
|
179
|
+
// This note is saved first. A held chord opens one note, not one per key
|
|
180
|
+
// repeat; the merge view has no note, so it opens none.
|
|
181
|
+
document.addEventListener('keydown', (e) => {
|
|
182
|
+
if (!editor || !Core.newNoteKey(e)) return;
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
if (e.repeat) return;
|
|
185
|
+
editor.commit();
|
|
186
|
+
window.editorApi.newNote();
|
|
187
|
+
});
|
|
188
|
+
|
|
177
189
|
if (window.editorApi.onFind) {
|
|
178
190
|
window.editorApi.onFind((find) => {
|
|
179
191
|
if (!editor || !find) return;
|
package/index.html
CHANGED
|
@@ -38,7 +38,7 @@ let groups = [];
|
|
|
38
38
|
// as tokens inside `query`; the chip bar's active/excluded state is DERIVED from it. No
|
|
39
39
|
// separate filter Sets (that dual-model was the drift Forge killed).
|
|
40
40
|
function parsedQuery() { return Core.search.parseQuery(query); }
|
|
41
|
-
function facetState() { return Core.search.facetState(parsedQuery()); }
|
|
41
|
+
function facetState() { return Core.search.facetState(parsedQuery(), groups); }
|
|
42
42
|
function activeFilterSet() { return facetState().active; }
|
|
43
43
|
// Ranking override for the Best/Recent toggle: null = default (relevance while searching,
|
|
44
44
|
// history order when idle), 'best' | 'new' when the user has flipped it for this query.
|
|
@@ -51,9 +51,11 @@ let diagnosticsEnabled = false;
|
|
|
51
51
|
let searchDocs = [], searchTextLower = [];
|
|
52
52
|
let itemLookup = new Map(), groupCounts = new Map(), builtinCounts = {}, numpadIdMap = {};
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
// New note: the header button and the popup's N quick key.
|
|
55
|
+
function newNote() {
|
|
55
56
|
window.api.newNote({ sourceGroups: Core.sourceGroupsFromFilters(activeFilterSet()) });
|
|
56
|
-
}
|
|
57
|
+
}
|
|
58
|
+
document.getElementById('newNoteBtn').addEventListener('click', newNote);
|
|
57
59
|
|
|
58
60
|
// Renderer exceptions used to vanish (a tray popup has no DevTools open): a thrown
|
|
59
61
|
// handler left the UI half-updated with nothing in the log. Route window errors +
|
|
@@ -384,7 +386,10 @@ const revGuard = (run, fallback) => Core.guardRevision(run, { toast: showToast,
|
|
|
384
386
|
const appAdapter = {
|
|
385
387
|
itemById: (id) => itemById(id),
|
|
386
388
|
numpadMap: () => numpadMap(),
|
|
387
|
-
|
|
389
|
+
// The popup's quick keys (controller.armQuickKeys): 1-9 and N right after it opens.
|
|
390
|
+
quickPaste: (slot) => window.api.numpadPasteAndHide(slot),
|
|
391
|
+
newNote: () => newNote(),
|
|
392
|
+
protectedGroups:() => new Set([aiGroupName]),
|
|
388
393
|
// The query TEXT is the source of truth: a chip click rewrites it through the
|
|
389
394
|
// controller (Search.applyFacet with the remembered or / and).
|
|
390
395
|
getQuery: () => query,
|
|
@@ -404,17 +409,20 @@ const appAdapter = {
|
|
|
404
409
|
numpadAssign: (id, slot) => revGuard(() => window.api.numpadAssign(id, slot, clipRevOf(id)), false),
|
|
405
410
|
numpadUnassign: (slot) => window.api.numpadUnassign(slot),
|
|
406
411
|
toggleGroup: (id, group) => revGuard(() => window.api.groupAssign(id, group, clipRevOf(id)), false), // applyGroupAssign toggles
|
|
407
|
-
|
|
412
|
+
// "todo, forge" creates (or reuses) both: main answers with the tag names.
|
|
413
|
+
createGroup: async (text) => { const names = await window.api.groupCreate(text); await refreshGroups({ render: false }); return names; },
|
|
408
414
|
deleteGroup: async (group) => {
|
|
409
415
|
// Drop every group: / -group: token of the deleted group from the query.
|
|
410
416
|
query = Core.search.stripFacet(query, { kind: 'group', value: group });
|
|
411
417
|
if (typeof searchBox !== 'undefined' && searchBox) searchBox.setQuery(query);
|
|
412
|
-
await window.api.groupDelete(group);
|
|
418
|
+
const result = await window.api.groupDelete(group); // { ok, name, ids, shared }: what its Undo puts back
|
|
413
419
|
await refreshGroups({ render: false });
|
|
420
|
+
return result;
|
|
414
421
|
},
|
|
422
|
+
restoreGroup: async (result) => { await window.api.groupRestore(result); await refreshGroups({ render: false }); },
|
|
415
423
|
deleteClip: (id) => revGuard(() => window.api.deleteItem(id, clipRevOf(id)), false),
|
|
416
424
|
clearUnpinned: () => window.api.deleteAll(),
|
|
417
|
-
setGroupSharedAi: (group) => window.api.setGroupSharedAi(group,
|
|
425
|
+
setGroupSharedAi: (group, shared) => window.api.setGroupSharedAi(group, shared), // shared: the switch's new state
|
|
418
426
|
// Always the id-based path: window.api.copy() only takes a string, so it
|
|
419
427
|
// would silently drop the clip's html/rtf. paste(id) puts every captured
|
|
420
428
|
// format on the clipboard for both text and image clips.
|
|
@@ -447,11 +455,7 @@ const appAdapter = {
|
|
|
447
455
|
groupAssignMany: (ids, group, shouldHave) => revGuard(() => window.api.groupAssignMany(Core.revisionTargets(ids, clipRevOf), group, shouldHave), false),
|
|
448
456
|
pasteMany: (ids) => window.api.pasteMany(ids),
|
|
449
457
|
startUnify: (ids) => window.api.startUnify(ids),
|
|
450
|
-
offerUndo: ({
|
|
451
|
-
message: `Deleted ${count} clip${count === 1 ? '' : 's'}`,
|
|
452
|
-
actionLabel: 'Undo',
|
|
453
|
-
onAction: undo,
|
|
454
|
-
}),
|
|
458
|
+
offerUndo: ({ message, undo }) => Core.showActionToast(toast, { message, actionLabel: 'Undo', onAction: undo }),
|
|
455
459
|
toast: (msg) => showToast(msg),
|
|
456
460
|
render: () => rerenderList(), // view-only re-render (expand/filter): no IPC
|
|
457
461
|
refresh: async () => { await refresh({ force: true }); if (document.getElementById('settingsView').classList.contains('show')) openSettings(); }, // after a mutation: re-fetch history THEN re-render settings if open (await so the settings panel reads rebuilt indexes, not stale ones)
|
|
@@ -486,9 +490,12 @@ document.addEventListener('wheel', (e) => controller.onWheel(e), { passive: fals
|
|
|
486
490
|
document.addEventListener('dragstart', (e) => controller.onDragstart(e));
|
|
487
491
|
document.addEventListener('mouseover', (e) => controller.onMouseover(e));
|
|
488
492
|
document.addEventListener('mouseout', (e) => controller.onMouseout(e));
|
|
493
|
+
// Any press in the popup, the search field included, ends the quick keys.
|
|
494
|
+
document.addEventListener('pointerdown', (e) => controller.onPointerdown(e), true);
|
|
489
495
|
|
|
490
|
-
// Popup reset (called by the main process when the window is shown).
|
|
496
|
+
// Popup reset (called by the main process when the window is shown and hidden).
|
|
491
497
|
window.resetPopupState = function() {
|
|
498
|
+
controller.armQuickKeys(); // 1-9 and N act until the first click or other key
|
|
492
499
|
appDialogs.dismiss();
|
|
493
500
|
controller.closeMenu(); // a popover left open at blur-hide would eat keys on re-show
|
|
494
501
|
controller.clearSelection({ paint: false });
|
|
@@ -498,6 +505,7 @@ window.resetPopupState = function() {
|
|
|
498
505
|
document.getElementById('mainView')?.classList.remove('hidden');
|
|
499
506
|
document.body.style.pointerEvents = '';
|
|
500
507
|
searchBox.closeOptions({ instant: true }); // every open starts with the options panel shut
|
|
508
|
+
cancelShortcutRecording(); // Settings closed above: a recording never outlives its row
|
|
501
509
|
window.focusSearch();
|
|
502
510
|
};
|
|
503
511
|
|
|
@@ -524,20 +532,6 @@ document.getElementById('copyDiagnostics').addEventListener('click', async () =>
|
|
|
524
532
|
});
|
|
525
533
|
|
|
526
534
|
// --- Search ---
|
|
527
|
-
searchEl.addEventListener('keydown', e => {
|
|
528
|
-
// A digit typed while a menu or popover is open belongs to it, never a
|
|
529
|
-
// quick-paste (typing "1ndex" with the + picker open pasted slot 1).
|
|
530
|
-
if (controller.isMenuOpen()) return;
|
|
531
|
-
if (query === '' && !e.altKey && !e.ctrlKey && !e.metaKey && e.key >= '1' && e.key <= '9') {
|
|
532
|
-
const n = +e.key;
|
|
533
|
-
const nmap = numpadMap();
|
|
534
|
-
if (n in nmap) {
|
|
535
|
-
e.preventDefault();
|
|
536
|
-
window.api.numpadPasteAndHide(n);
|
|
537
|
-
return;
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
});
|
|
541
535
|
const searchClear = document.getElementById('searchClear');
|
|
542
536
|
// Shared query-state change path (typed input AND autocomplete-applied tokens).
|
|
543
537
|
function onQueryChanged(v) {
|
|
@@ -748,44 +742,89 @@ function shortcutLabel(accelerator) {
|
|
|
748
742
|
.replace(/\+/g, ' + ');
|
|
749
743
|
}
|
|
750
744
|
|
|
745
|
+
// The recordable shortcuts: their Settings rows (the shared settings body)
|
|
746
|
+
// and the main-process setter each recording is saved through. canTurnOff:
|
|
747
|
+
// the shortcut may be none even though its default is a chord (the setter
|
|
748
|
+
// takes SHORTCUT_OFF; Windows' Win+N then stays Windows').
|
|
749
|
+
const SHORTCUT_OFF = 'none';
|
|
750
|
+
const SHORTCUT_KINDS = {
|
|
751
|
+
show: { status: 'shortcutStatus', record: 'shortcutRecord', reset: 'shortcutReset', set: (s) => window.api.setShowShortcut(s) },
|
|
752
|
+
quickPaste: { status: 'quickPasteStatus', record: 'quickPasteRecord', reset: 'quickPasteReset', set: (s) => window.api.setQuickPasteShortcut(s) },
|
|
753
|
+
newNote: { status: 'newNoteShortcutStatus', record: 'newNoteShortcutRecord', reset: 'newNoteShortcutReset', set: (s) => window.api.setNewNoteShortcut(s),
|
|
754
|
+
canTurnOff: (info) => !!info.newNoteDefault },
|
|
755
|
+
};
|
|
756
|
+
|
|
751
757
|
function shortcutStatusEl(kind) {
|
|
752
|
-
return document.getElementById(kind
|
|
758
|
+
return document.getElementById(SHORTCUT_KINDS[kind].status);
|
|
753
759
|
}
|
|
754
760
|
|
|
755
761
|
function shortcutRecordEl(kind) {
|
|
756
|
-
return document.getElementById(kind
|
|
762
|
+
return document.getElementById(SHORTCUT_KINDS[kind].record);
|
|
757
763
|
}
|
|
758
764
|
|
|
759
765
|
function shortcutResetEl(kind) {
|
|
760
|
-
return document.getElementById(kind
|
|
766
|
+
return document.getElementById(SHORTCUT_KINDS[kind].reset);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// The row's reset slot holds the one action its state has: back to the
|
|
770
|
+
// default once changed (restart_alt), Turn off at a default chord that can be
|
|
771
|
+
// off (close), else nothing, the slot kept so the row never shifts.
|
|
772
|
+
function renderShortcutReset(kind, info, custom) {
|
|
773
|
+
const button = shortcutResetEl(kind);
|
|
774
|
+
if (!button.dataset.resetTitle) button.dataset.resetTitle = button.title;
|
|
775
|
+
const canTurnOff = SHORTCUT_KINDS[kind].canTurnOff;
|
|
776
|
+
const off = !custom && !!(canTurnOff && canTurnOff(info));
|
|
777
|
+
const title = off ? 'Turn off' : button.dataset.resetTitle;
|
|
778
|
+
button.dataset.apply = off ? SHORTCUT_OFF : '';
|
|
779
|
+
button.title = title;
|
|
780
|
+
button.setAttribute('aria-label', title);
|
|
781
|
+
button.querySelector('.mi').textContent = off ? 'close' : 'restart_alt';
|
|
782
|
+
button.style.visibility = custom || off ? 'visible' : 'hidden';
|
|
761
783
|
}
|
|
762
784
|
|
|
763
785
|
function setShortcutStatus(kind, message, isError = false) {
|
|
764
786
|
Core.setSettingHelp(shortcutStatusEl(kind), message, isError ? 'error' : '');
|
|
765
787
|
}
|
|
766
788
|
|
|
789
|
+
// A start waits for main to suspend the shortcuts; a stop in that wait ends
|
|
790
|
+
// the start there (the generation moved) and still resumes them.
|
|
791
|
+
let shortcutRecordStarting = false;
|
|
792
|
+
let shortcutRecordGen = 0;
|
|
767
793
|
function stopShortcutRecording({ resume = true } = {}) {
|
|
768
794
|
const kind = shortcutRecording;
|
|
795
|
+
const starting = shortcutRecordStarting;
|
|
769
796
|
shortcutRecording = null;
|
|
770
|
-
|
|
797
|
+
shortcutRecordStarting = false;
|
|
798
|
+
shortcutRecordGen += 1;
|
|
799
|
+
for (const button of Object.keys(SHORTCUT_KINDS).map(shortcutRecordEl)) {
|
|
771
800
|
if (button) button.classList.remove('recording');
|
|
772
801
|
}
|
|
773
|
-
if (resume && kind && window.api.resumeShortcuts) {
|
|
802
|
+
if (resume && (kind || starting) && window.api.resumeShortcuts) {
|
|
774
803
|
window.api.resumeShortcuts().catch(() => {});
|
|
775
804
|
}
|
|
776
805
|
return kind;
|
|
777
806
|
}
|
|
778
807
|
|
|
808
|
+
// A recording lives only while its row is on screen: Esc, the popup hiding or
|
|
809
|
+
// showing (resetPopupState) and Settings closing all cancel it the same way,
|
|
810
|
+
// which resumes the shortcuts main suspended for it and puts the row's label
|
|
811
|
+
// back. (Left running, the recorder ate every key the search got and saved the
|
|
812
|
+
// next chord as a global shortcut.)
|
|
813
|
+
async function cancelShortcutRecording() {
|
|
814
|
+
if (!shortcutRecording && !shortcutRecordStarting) return;
|
|
815
|
+
stopShortcutRecording();
|
|
816
|
+
const settings = await window.api.getSettings().catch(() => null);
|
|
817
|
+
if (settings) renderShortcutSettings(settings);
|
|
818
|
+
}
|
|
819
|
+
|
|
779
820
|
function renderShortcutSettings(settings, { clearStatus = true } = {}) {
|
|
780
821
|
const info = settings.shortcut_info || { show: 'CommandOrControl+Shift+V', custom: false };
|
|
781
822
|
const record = shortcutRecordEl('show');
|
|
782
|
-
const reset = shortcutResetEl('show');
|
|
783
823
|
record.textContent = shortcutLabel(info.show);
|
|
784
824
|
record.title = info.show;
|
|
785
825
|
record.classList.remove('recording');
|
|
786
|
-
|
|
826
|
+
renderShortcutReset('show', info, info.custom);
|
|
787
827
|
const quickRecord = shortcutRecordEl('quickPaste');
|
|
788
|
-
const quickReset = shortcutResetEl('quickPaste');
|
|
789
828
|
const quickShortcut = info.quickPaste || '';
|
|
790
829
|
if (quickShortcut) {
|
|
791
830
|
quickRecord.textContent = `${shortcutLabel(quickShortcut).replace(/\s\+\s[1-9]$/, '')} + 1-9`;
|
|
@@ -796,10 +835,16 @@ function renderShortcutSettings(settings, { clearStatus = true } = {}) {
|
|
|
796
835
|
}
|
|
797
836
|
quickRecord.classList.toggle('unset', !quickShortcut);
|
|
798
837
|
quickRecord.classList.remove('recording');
|
|
799
|
-
|
|
838
|
+
renderShortcutReset('quickPaste', info, info.quickPasteCustom);
|
|
839
|
+
const noteRecord = shortcutRecordEl('newNote');
|
|
840
|
+
const noteShortcut = info.newNote || '';
|
|
841
|
+
noteRecord.textContent = noteShortcut ? shortcutLabel(noteShortcut) : 'Not set';
|
|
842
|
+
noteRecord.title = noteShortcut || 'Record new note shortcut';
|
|
843
|
+
noteRecord.classList.toggle('unset', !noteShortcut);
|
|
844
|
+
noteRecord.classList.remove('recording');
|
|
845
|
+
renderShortcutReset('newNote', info, info.newNoteCustom);
|
|
800
846
|
if (clearStatus) {
|
|
801
|
-
setShortcutStatus(
|
|
802
|
-
setShortcutStatus('quickPaste', '');
|
|
847
|
+
for (const kind of Object.keys(SHORTCUT_KINDS)) setShortcutStatus(kind, '');
|
|
803
848
|
}
|
|
804
849
|
}
|
|
805
850
|
|
|
@@ -868,9 +913,7 @@ function shortcutFromEvent(e) {
|
|
|
868
913
|
|
|
869
914
|
async function applyShortcut(kind, shortcut) {
|
|
870
915
|
stopShortcutRecording({ resume: false });
|
|
871
|
-
const result = kind
|
|
872
|
-
? await window.api.setQuickPasteShortcut(shortcut)
|
|
873
|
-
: await window.api.setShowShortcut(shortcut);
|
|
916
|
+
const result = await SHORTCUT_KINDS[kind].set(shortcut);
|
|
874
917
|
if (!result || !result.ok) {
|
|
875
918
|
const settings = await window.api.getSettings().catch(() => null);
|
|
876
919
|
if (settings) renderShortcutSettings(settings, { clearStatus: false });
|
|
@@ -883,9 +926,13 @@ async function applyShortcut(kind, shortcut) {
|
|
|
883
926
|
|
|
884
927
|
async function startShortcutRecording(kind) {
|
|
885
928
|
stopShortcutRecording();
|
|
929
|
+
const gen = shortcutRecordGen;
|
|
930
|
+
shortcutRecordStarting = true;
|
|
886
931
|
if (window.api.suspendShortcuts) {
|
|
887
932
|
await window.api.suspendShortcuts().catch(() => {});
|
|
888
933
|
}
|
|
934
|
+
if (gen !== shortcutRecordGen) return; // cancelled while main suspended them (that stop resumed them)
|
|
935
|
+
shortcutRecordStarting = false;
|
|
889
936
|
shortcutRecording = kind;
|
|
890
937
|
const record = shortcutRecordEl(kind);
|
|
891
938
|
record.classList.add('recording');
|
|
@@ -893,10 +940,10 @@ async function startShortcutRecording(kind) {
|
|
|
893
940
|
setShortcutStatus(kind, '');
|
|
894
941
|
}
|
|
895
942
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
943
|
+
for (const kind of Object.keys(SHORTCUT_KINDS)) {
|
|
944
|
+
shortcutRecordEl(kind).addEventListener('click', () => startShortcutRecording(kind));
|
|
945
|
+
shortcutResetEl(kind).addEventListener('click', (e) => applyShortcut(kind, e.currentTarget.dataset.apply || ''));
|
|
946
|
+
}
|
|
900
947
|
|
|
901
948
|
document.addEventListener('keydown', async (e) => {
|
|
902
949
|
if (!shortcutRecording) return;
|
|
@@ -904,9 +951,7 @@ document.addEventListener('keydown', async (e) => {
|
|
|
904
951
|
e.preventDefault();
|
|
905
952
|
e.stopPropagation();
|
|
906
953
|
if (e.key === 'Escape') {
|
|
907
|
-
|
|
908
|
-
const settings = await window.api.getSettings().catch(() => null);
|
|
909
|
-
if (settings) renderShortcutSettings(settings);
|
|
954
|
+
await cancelShortcutRecording();
|
|
910
955
|
return;
|
|
911
956
|
}
|
|
912
957
|
const shortcut = shortcutFromEvent(e);
|
|
@@ -1186,6 +1231,7 @@ async function renderConflicts() {
|
|
|
1186
1231
|
}
|
|
1187
1232
|
|
|
1188
1233
|
function closeSettings() {
|
|
1234
|
+
cancelShortcutRecording();
|
|
1189
1235
|
document.getElementById('settingsView').classList.remove('show');
|
|
1190
1236
|
document.getElementById('mainView').classList.remove('hidden');
|
|
1191
1237
|
searchEl.focus();
|