build(app): migrate to electron-vite (main/preload/renderer), keep electron-builder (Phase 5d-3a)

Restructure app into src/{main,preload,renderer}; electron.vite.config.ts builds
all three (each main module its own input so CJS requires + the indexer worker
resolve; better-sqlite3 externalized; Vue plugin for renderer). main/index.js
paths updated for the out/ layout + ELECTRON_RENDERER_URL. Still JS/CJS — TS+ESM
and core consumption are the next stages. Verified: npm run dev launches clean;
electron-vite build succeeds; root suite 119/119.
This commit is contained in:
tommy0103
2026-07-09 10:20:18 +08:00
parent e04c07de05
commit 80c125a572
69 changed files with 572 additions and 78 deletions
+129
View File
@@ -0,0 +1,129 @@
// Keyboard shortcut handling -- ported from the HTML mock.
// Registers a single document-level keydown listener that dispatches
// all navigation, mutation, and route-switching shortcuts.
import { state, IS_MAC } from './state.js';
import {
renderAll,
renderMemoryList,
renderSessionList,
renderStatus,
enterDetail,
exitDetail,
setRoute,
setView,
toggleSort,
moveCursor,
archive,
restore,
doUndo,
navigateToSession
} from './render.js';
export function initKeyboard() {
document.addEventListener('keydown', e => {
const inInput =
document.activeElement.tagName === 'INPUT' ||
document.activeElement.tagName === 'TEXTAREA';
const mod = IS_MAC ? e.metaKey : e.ctrlKey;
// -- Route switching: Cmd+1/2/3/4 --
if (mod && e.key === '1') { e.preventDefault(); setRoute('sessions'); return; }
if (mod && e.key === '2') { e.preventDefault(); setView('active'); return; }
if (mod && e.key === '3') { e.preventDefault(); setView('archived'); return; }
if (mod && e.key === '4') { e.preventDefault(); setView('broken'); return; }
// -- Undo: Cmd+Z --
if (mod && e.key.toLowerCase() === 'z' && !e.shiftKey) {
if (state.lastArchiveSnapshot) { e.preventDefault(); doUndo(); }
return;
}
// -- When inside an input, only Escape is handled (to blur) --
if (inInput) {
if (e.key === 'Escape') e.target.blur();
return;
}
// -- Search focus: / --
if (e.key === '/') {
e.preventDefault();
const searchInput = document.getElementById('search');
if (searchInput) { searchInput.focus(); searchInput.select(); }
return;
}
// -- Detail mode shortcuts --
if (state.mode === 'detail') {
if (e.key === 'Escape') { e.preventDefault(); exitDetail(); return; }
if (state.route === 'memory' && (e.key === 'd' || e.key === 'D')) {
e.preventDefault();
const m = state.memories.find(x => x.id === state.detailId);
if (m && m.archived) restore([m.id]);
else if (m) archive([m.id]);
return;
}
return;
}
// -- List mode shortcuts --
// Navigation: j/k/arrows
if (e.key === 'j' || e.key === 'ArrowDown') {
e.preventDefault();
if (state.route === 'memory') moveCursor(1, e.shiftKey);
return;
}
if (e.key === 'k' || e.key === 'ArrowUp') {
e.preventDefault();
if (state.route === 'memory') moveCursor(-1, e.shiftKey);
return;
}
// Open detail: Enter
if (e.key === 'Enter') {
e.preventDefault();
if (state.route === 'memory' && state.cursorId) enterDetail(state.cursorId);
return;
}
// Archive / Restore: d/D
if (state.route === 'memory' && (e.key === 'd' || e.key === 'D')) {
e.preventDefault();
const ids = state.selection.size > 0
? Array.from(state.selection)
: state.cursorId ? [state.cursorId] : [];
if (!ids.length) return;
const m = state.memories.find(x => x.id === ids[0]);
if (state.view === 'archived' || (m && m.archived)) restore(ids);
else archive(ids);
return;
}
// Undo: u
if (e.key === 'u') {
e.preventDefault();
if (state.lastArchiveSnapshot) doUndo();
return;
}
// Sort toggle: s
if (e.key === 's') { e.preventDefault(); toggleSort(); return; }
// Escape: clear selection or search
if (e.key === 'Escape') {
if (state.selection.size) {
state.selection.clear();
renderMemoryList();
renderStatus();
} else if (state.query) {
state.query = '';
const searchInput = document.getElementById('search');
if (searchInput) searchInput.value = '';
if (state.route === 'sessions') renderSessionList();
else renderMemoryList();
}
return;
}
});
}