refactor(frontend): modularise App.svelte — Phase 1-4
• Create docs/frontend-architecture.md and docs/frontend-change-map.md • Extract API layer: lib/services/ (wails, notes, files, search, inbox, trash, sync, journal, actions, links, activity, nodes, suggestions, today, browserEvents) • Extract ErrorBanner.svelte component • Extract CaptureDropOverlay.svelte component • Extract OverviewTab.svelte component • Extract NotesTab.svelte component • Wire all extracted components into App.svelte • Build passes (npm run build ✓)
This commit is contained in:
parent
bfe57ac0ac
commit
490a3dd624
|
|
@ -0,0 +1,143 @@
|
||||||
|
# Frontend Architecture
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Verstak frontend is a Svelte 3 application running inside Wails v2 (Go bridge).
|
||||||
|
The app manages a hierarchical vault of nodes (folders/cases, notes, files, links, actions)
|
||||||
|
with sync capabilities, worklog/journal, and activity tracking.
|
||||||
|
|
||||||
|
## Technology Stack
|
||||||
|
|
||||||
|
- **UI Framework:** Svelte 3 (plain JS, no TypeScript in components)
|
||||||
|
- **Desktop Bridge:** Wails v2 (`window.go.main.App.*`)
|
||||||
|
- **Bundler:** Vite (via Wails)
|
||||||
|
- **Markdown:** Custom renderer in `lib/markdown/`
|
||||||
|
- **i18n:** Custom lightweight system in `lib/i18n/`
|
||||||
|
- **Styling:** Scoped CSS in Svelte components, dark theme
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
frontend/src/
|
||||||
|
├── App.svelte # Root component (being modularised)
|
||||||
|
├── TreeNode.svelte # Tree node for sidebar (inline)
|
||||||
|
├── FileTreeRow.svelte # File row in file tab (inline)
|
||||||
|
├── wailsjs/go/main/App.js # Auto-generated Wails bindings
|
||||||
|
├── lib/
|
||||||
|
│ ├── components/ # Reusable UI components
|
||||||
|
│ │ └── notes/
|
||||||
|
│ │ ├── NoteEditorPanel.svelte
|
||||||
|
│ │ ├── MarkdownEditor.svelte
|
||||||
|
│ │ ├── MarkdownPreview.svelte
|
||||||
|
│ │ ├── InternalLinkPicker.svelte
|
||||||
|
│ │ └── ObjectPickerModal.svelte
|
||||||
|
│ ├── services/ # API/Data access layer
|
||||||
|
│ │ ├── wails.js # Base Wails call helper
|
||||||
|
│ │ ├── notes.js # Notes API
|
||||||
|
│ │ ├── files.js # Files API
|
||||||
|
│ │ ├── search.js # Search API
|
||||||
|
│ │ ├── inbox.js # Inbox API
|
||||||
|
│ │ ├── trash.js # Trash API
|
||||||
|
│ │ ├── sync.js # Sync API
|
||||||
|
│ │ ├── journal.js # Journal/Worklog API
|
||||||
|
│ │ ├── actions.js # Actions API
|
||||||
|
│ │ ├── links.js # Links API
|
||||||
|
│ │ └── activity.js # Activity API
|
||||||
|
│ ├── state/ # State management (planned)
|
||||||
|
│ │ ├── navigation.js # Navigation state
|
||||||
|
│ │ └── uiState.js # UI state
|
||||||
|
│ ├── markdown/ # Markdown processing
|
||||||
|
│ │ ├── markdown.ts
|
||||||
|
│ │ └── internalLinks.ts
|
||||||
|
│ ├── i18n/ # Internationalisation
|
||||||
|
│ │ ├── index.js
|
||||||
|
│ │ └── locales/
|
||||||
|
│ │ ├── en.js
|
||||||
|
│ │ └── ru.js
|
||||||
|
│ ├── util/ # Utilities
|
||||||
|
│ │ ├── keyboardLayout.ts
|
||||||
|
│ │ └── markdown.test.js
|
||||||
|
│ ├── AppHeader.svelte
|
||||||
|
│ ├── GlobalSearch.svelte
|
||||||
|
│ ├── FileBreadcrumbs.svelte
|
||||||
|
│ ├── FileIcon.svelte
|
||||||
|
│ ├── FilePreviewModal.svelte
|
||||||
|
│ ├── ConfirmModal.svelte
|
||||||
|
│ ├── TodayScreen.svelte
|
||||||
|
│ ├── BrowserEvents.svelte
|
||||||
|
│ ├── FirstRun.svelte
|
||||||
|
│ ├── VaultRecovery.svelte
|
||||||
|
│ ├── SyncStatus.svelte
|
||||||
|
│ ├── TemplateIcon.svelte
|
||||||
|
│ ├── CalendarPluginPage.svelte
|
||||||
|
│ ├── SettingsWindow.svelte
|
||||||
|
│ ├── SettingsSidebar.svelte
|
||||||
|
│ ├── SettingsGeneral.svelte
|
||||||
|
│ ├── SettingsSync.svelte
|
||||||
|
│ ├── SettingsPlugins.svelte
|
||||||
|
│ ├── SettingsBrowserBridge.svelte
|
||||||
|
│ ├── SettingsWorkspace.svelte
|
||||||
|
│ ├── SettingsTemplates.svelte
|
||||||
|
│ ├── SettingsFiles.svelte
|
||||||
|
│ ├── SettingsBackup.svelte
|
||||||
|
│ ├── SettingsActivity.svelte
|
||||||
|
│ ├── actionIcons.js
|
||||||
|
│ └── fileUtils.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Wails Bridge
|
||||||
|
|
||||||
|
All backend calls go through `window.go.main.App[method](...)`.
|
||||||
|
The `wailsCall()` helper in `lib/services/wails.js` provides error handling.
|
||||||
|
|
||||||
|
## Planned Components (to extract from App.svelte)
|
||||||
|
|
||||||
|
### Layout
|
||||||
|
- `AppShell.svelte` — root layout wrapper
|
||||||
|
- `Sidebar.svelte` — navigation sidebar
|
||||||
|
- `MainWorkspace.svelte` — main content area
|
||||||
|
|
||||||
|
### Pages/Tab Content
|
||||||
|
- `OverviewTab.svelte` — node overview with meta and quick actions
|
||||||
|
- `NotesTab.svelte` — notes list and creation
|
||||||
|
- `FilesTab.svelte` — file browser with breadcrumbs
|
||||||
|
- `InboxContent.svelte` + `InboxFullScreen.svelte`
|
||||||
|
- `LinksTab.svelte`
|
||||||
|
- `ActionsTab.svelte`
|
||||||
|
- `WorklogTab.svelte`
|
||||||
|
- `ActivityTabContent.svelte`
|
||||||
|
- `TrashContent.svelte`
|
||||||
|
- `JournalScreen.svelte`
|
||||||
|
- `ActivityFeedScreen.svelte`
|
||||||
|
- `WelcomeScreen.svelte`
|
||||||
|
|
||||||
|
### Modals
|
||||||
|
- `CreateNodeModal.svelte`
|
||||||
|
- `WorklogModal.svelte`
|
||||||
|
- `CreateActionModal.svelte`
|
||||||
|
- `ImportModal.svelte`
|
||||||
|
- `RenameModal.svelte`
|
||||||
|
- `AssignInboxModal.svelte`
|
||||||
|
- `EditLinkModal.svelte`
|
||||||
|
- `LinkInsertModal.svelte`
|
||||||
|
- `NoteRenameModal.svelte`
|
||||||
|
- `ContextMenu.svelte`
|
||||||
|
|
||||||
|
## Data Flow
|
||||||
|
|
||||||
|
1. User interacts with UI component
|
||||||
|
2. Component calls a service function (e.g., `notesApi.createNote(...)`)
|
||||||
|
3. Service calls `wailsCall('CreateNote', ...)`
|
||||||
|
4. Wails bridge forwards to Go backend
|
||||||
|
5. Go backend returns result → Wails → service → component updates state
|
||||||
|
|
||||||
|
## State Management
|
||||||
|
|
||||||
|
Currently all state lives in App.svelte as local variables.
|
||||||
|
Target: extract into `lib/state/navigation.js` and `lib/state/uiState.js`.
|
||||||
|
|
||||||
|
## Build & Verification
|
||||||
|
|
||||||
|
- `npm run build` in `frontend/` directory
|
||||||
|
- `go test ./...` from project root
|
||||||
|
- Manual smoke testing via Wails dev server
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
# Frontend Change Map
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This document tracks the refactoring of `App.svelte` from a 4794-line monolith
|
||||||
|
into a modular frontend architecture. Each step preserves behaviour exactly.
|
||||||
|
|
||||||
|
## Phase 1: Documentation & Foundation
|
||||||
|
|
||||||
|
- [x] Audit App.svelte (all 4794 lines read and mapped)
|
||||||
|
- [x] Create `docs/frontend-architecture.md`
|
||||||
|
- [x] Create `docs/frontend-change-map.md`
|
||||||
|
|
||||||
|
## Phase 2: API Layer
|
||||||
|
|
||||||
|
Extract all Wails calls into service modules.
|
||||||
|
|
||||||
|
- [ ] Create `lib/services/wails.js` — base `wailsCall` helper
|
||||||
|
- [ ] Create `lib/services/notes.js` — `listNotes`, `createNote`, `readNote`, `saveNote`, `renameNote`, `deleteNote`
|
||||||
|
- [ ] Create `lib/services/files.js` — `loadFolder`, `addFile`, `deleteFile`, etc.
|
||||||
|
- [ ] Create `lib/services/search.js` — `searchNodes`
|
||||||
|
- [ ] Create `lib/services/inbox.js` — `listInbox`, `captureClipboard`, etc.
|
||||||
|
- [ ] Create `lib/services/trash.js` — `loadTrash`, `restore`, `purge`
|
||||||
|
- [ ] Create `lib/services/sync.js` — `loadSyncStatus`, `runSync`
|
||||||
|
- [ ] Create `lib/services/journal.js` — `loadJournal`, `worklog CRUD`
|
||||||
|
- [ ] Create `lib/services/actions.js` — `listActions`, `createAction`, `deleteAction`
|
||||||
|
- [ ] Create `lib/services/links.js` — `listLinks`, `updateLink`, `deleteLink`
|
||||||
|
- [ ] Create `lib/services/activity.js` — `loadActivityFeed`, `loadCaseActivity`
|
||||||
|
|
||||||
|
## Phase 3: State Extraction
|
||||||
|
|
||||||
|
- [ ] Create `lib/state/navigation.js` — `selectedSection`, `selectedNode`, `activeTab`, `navHistory`
|
||||||
|
- [ ] Create `lib/state/uiState.js` — modals state, confirm state, rename state, drag state
|
||||||
|
|
||||||
|
## Phase 4: Component Extraction — Layout
|
||||||
|
|
||||||
|
- [ ] Extract `Sidebar.svelte` — brand, nav items, workspace tree, footer
|
||||||
|
- [ ] Extract `MainWorkspace.svelte` — content routing
|
||||||
|
- [ ] Create `AppShell.svelte` — root layout wrapper
|
||||||
|
|
||||||
|
## Phase 5: Component Extraction — Tab Content
|
||||||
|
|
||||||
|
- [ ] Extract `OverviewTab.svelte`
|
||||||
|
- [ ] Extract `NotesTab.svelte`
|
||||||
|
- [ ] Extract `FilesTab.svelte`
|
||||||
|
- [ ] Extract `LinksTab.svelte`
|
||||||
|
- [ ] Extract `ActionsTab.svelte`
|
||||||
|
- [ ] Extract `WorklogTab.svelte`
|
||||||
|
- [ ] Extract `ActivityTabContent.svelte`
|
||||||
|
- [ ] Extract `InboxContent.svelte`
|
||||||
|
- [ ] Extract `InboxFullScreen.svelte`
|
||||||
|
- [ ] Extract `TrashContent.svelte`
|
||||||
|
- [ ] Extract `JournalScreen.svelte`
|
||||||
|
- [ ] Extract `ActivityFeedScreen.svelte`
|
||||||
|
- [ ] Extract `WelcomeScreen.svelte`
|
||||||
|
|
||||||
|
## Phase 6: Component Extraction — Modals
|
||||||
|
|
||||||
|
- [ ] Extract `CreateNodeModal.svelte`
|
||||||
|
- [ ] Extract `WorklogModal.svelte`
|
||||||
|
- [ ] Extract `CreateActionModal.svelte`
|
||||||
|
- [ ] Extract `ImportModal.svelte`
|
||||||
|
- [ ] Extract `RenameModal.svelte`
|
||||||
|
- [ ] Extract `AssignInboxModal.svelte`
|
||||||
|
- [ ] Extract `EditLinkModal.svelte`
|
||||||
|
- [ ] Extract `ContextMenu.svelte`
|
||||||
|
|
||||||
|
## Phase 7: Extract Inline Components
|
||||||
|
|
||||||
|
- [ ] Extract `NoteEditorHeader.svelte` (note editor header with rename)
|
||||||
|
- [ ] Extract `ErrorBanner.svelte`
|
||||||
|
- [ ] Extract `CaptureDropOverlay.svelte`
|
||||||
|
- [ ] Extract `SidebarFooter.svelte`
|
||||||
|
|
||||||
|
## Phase 8: Verification
|
||||||
|
|
||||||
|
- [ ] `npm run build` passes
|
||||||
|
- [ ] `go test ./...` passes
|
||||||
|
- [ ] Smoke checklist:
|
||||||
|
1. Sidebar renders with system views
|
||||||
|
2. Workspace tree loads and is expandable
|
||||||
|
3. Selecting a node shows tabs
|
||||||
|
4. Overview tab shows metadata and quick actions
|
||||||
|
5. Notes tab — create, rename, delete notes
|
||||||
|
6. Note editor — edit, preview, save, internal links, external links
|
||||||
|
7. Files tab — browse, add file/folder, navigate breadcrumbs
|
||||||
|
8. File preview — open, close
|
||||||
|
9. Inbox — list, sort, group, assign, delete
|
||||||
|
10. Trash — browse, restore, purge
|
||||||
|
11. Journal — filter, export, worklog CRUD
|
||||||
|
12. Activity feed — load and open events
|
||||||
|
13. Today screen — dashboard, suggestions, browser events
|
||||||
|
14. Settings — open/close, sections
|
||||||
|
15. Context menu on workspace tree
|
||||||
|
16. Create node modal — templates
|
||||||
|
|
@ -18,6 +18,10 @@
|
||||||
import { t } from './lib/i18n'
|
import { t } from './lib/i18n'
|
||||||
import NoteEditorPanel from './lib/components/notes/NoteEditorPanel.svelte'
|
import NoteEditorPanel from './lib/components/notes/NoteEditorPanel.svelte'
|
||||||
import InternalLinkPicker from './lib/components/notes/InternalLinkPicker.svelte'
|
import InternalLinkPicker from './lib/components/notes/InternalLinkPicker.svelte'
|
||||||
|
import ErrorBanner from './lib/components/ErrorBanner.svelte'
|
||||||
|
import CaptureDropOverlay from './lib/components/CaptureDropOverlay.svelte'
|
||||||
|
import OverviewTab from './lib/components/OverviewTab.svelte'
|
||||||
|
import NotesTab from './lib/components/notes/NotesTab.svelte'
|
||||||
|
|
||||||
// ===== Wails v2 API call helper =====
|
// ===== Wails v2 API call helper =====
|
||||||
function wailsCall(method, ...args) {
|
function wailsCall(method, ...args) {
|
||||||
|
|
@ -2845,11 +2849,7 @@
|
||||||
<VaultRecovery vaultPath={startupStatus?.vaultPath || ''} onComplete={onRecoveryComplete} />
|
<VaultRecovery vaultPath={startupStatus?.vaultPath || ''} onComplete={onRecoveryComplete} />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="app">
|
<div class="app">
|
||||||
{#if captureDropActive}
|
<CaptureDropOverlay show={captureDropActive} label={captureDropLabel} />
|
||||||
<div class="capture-drop-overlay">
|
|
||||||
<div class="capture-drop-box">{captureDropLabel}</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
<div class="sidebar-brand">
|
<div class="sidebar-brand">
|
||||||
|
|
@ -2940,16 +2940,7 @@
|
||||||
</div>
|
</div>
|
||||||
</AppHeader>
|
</AppHeader>
|
||||||
|
|
||||||
{#if error}
|
<ErrorBanner {error} onDismiss={() => error = ''} />
|
||||||
<div class="error-banner" role="button" tabindex="0" on:click={() => error = ''} on:keydown={onKeyActivate(() => error = '')}>
|
|
||||||
{translateError(error)}
|
|
||||||
<button class="dismiss-btn" on:click|stopPropagation={() => error = ''} aria-label="Dismiss">
|
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if noteEditor}
|
{#if noteEditor}
|
||||||
<!-- Note editor with markdown preview -->
|
<!-- Note editor with markdown preview -->
|
||||||
|
|
@ -3004,89 +2995,31 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
{#if activeTab === 'overview'}
|
{#if activeTab === 'overview'}
|
||||||
<div class="overview">
|
<OverviewTab
|
||||||
<h2>{selectedNode.title}</h2>
|
node={selectedNode}
|
||||||
<div class="meta-grid">
|
{notes}
|
||||||
<div class="meta-item"><span class="meta-label">{t('overview.type')}</span><span>{nodeKindLabel(selectedNode.type)}</span></div>
|
{worklog}
|
||||||
<div class="meta-item"><span class="meta-label">{t('overview.section')}</span><span>{selectedNode.section || '—'}</span></div>
|
{nodeKindLabel}
|
||||||
<div class="meta-item"><span class="meta-label">{t('overview.created')}</span><span>{formatDate(selectedNode.createdAt)}</span></div>
|
{formatDate}
|
||||||
</div>
|
on:goTab={(e) => setActiveTab(e.detail)}
|
||||||
<div class="quick-actions">
|
on:openNote={(e) => openNote(e.detail.note)}
|
||||||
<button class="qa-btn" on:click={() => { setActiveTab('notes'); openCreateNote() }}>
|
on:createNote={() => { setActiveTab('notes'); openCreateNote() }}
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
|
on:addFile={() => { setActiveTab('files'); addFile() }}
|
||||||
{t('overview.newNote')}
|
on:createAction={openCreateAction}
|
||||||
</button>
|
/>
|
||||||
<button class="qa-btn" on:click={() => { setActiveTab('files'); addFile() }}>
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
|
|
||||||
{t('overview.addFile')}
|
|
||||||
</button>
|
|
||||||
<button class="qa-btn" on:click={openCreateAction}>
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
|
||||||
{t('overview.addAction')}
|
|
||||||
</button>
|
|
||||||
<button class="qa-btn" on:click={() => setActiveTab('worklog')}>
|
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
|
||||||
{t('overview.logTime')}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{#if notes.length > 0}
|
|
||||||
<div class="recent-section">
|
|
||||||
<h3>{t('overview.recentNotes')}</h3>
|
|
||||||
{#each notes.slice(0, 5) as note}
|
|
||||||
<div class="recent-note" role="button" tabindex="0" on:click={() => openNote(note)} on:keydown={onKeyActivate(() => openNote(note))}>
|
|
||||||
<span>{note.title}</span><span class="recent-date">{formatDate(note.createdAt)}</span>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if worklog.length > 0}
|
|
||||||
<div class="recent-section">
|
|
||||||
<h3>{t('overview.recentEntries')}</h3>
|
|
||||||
{#each worklog.slice(0, 3) as e}
|
|
||||||
<div class="recent-entry">{e.summary} ({e.minutes} {t('worklog.min')})</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{:else if activeTab === 'notes'}
|
{:else if activeTab === 'notes'}
|
||||||
<div class="notes-tab">
|
<NotesTab
|
||||||
<div class="tab-toolbar">
|
{notes}
|
||||||
<button class="btn btn-primary" on:click={openCreateNote}>{t('note.add')}</button>
|
showCreateNote={showCreateNote}
|
||||||
</div>
|
{formatDate}
|
||||||
{#if showCreateNote}
|
on:createNote={openCreateNote}
|
||||||
<div class="create-form">
|
on:submitCreateNote={(e) => { newNoteTitle = e.detail.title; submitCreateNote() }}
|
||||||
<input type="text" placeholder={t('note.title')} bind:value={newNoteTitle}
|
on:cancelCreateNote={cancelCreateNote}
|
||||||
on:keydown={(e) => e.key === 'Enter' && submitCreateNote()} />
|
on:openNote={(e) => openNote(e.detail.note)}
|
||||||
<div class="form-actions">
|
on:startRename={(e) => startRenameNote(e.detail.note.id, e.detail.note.title)}
|
||||||
<button class="btn btn-primary" on:click={submitCreateNote}>{t('common.create')}</button>
|
on:delete={(e) => deleteNote(e.detail.note)}
|
||||||
<button class="btn" on:click={cancelCreateNote}>{t('common.cancel')}</button>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{#if notes.length === 0 && !showCreateNote}
|
|
||||||
<div class="empty-state"><p>{t('note.noNotes')}</p><p class="hint">{t('note.createFirst')}</p></div>
|
|
||||||
{:else}
|
|
||||||
<div class="notes-list">
|
|
||||||
{#each notes as note}
|
|
||||||
<div class="note-card" role="button" tabindex="0" on:click={() => openNote(note)} on:keydown={onKeyActivate(() => openNote(note))}>
|
|
||||||
<div class="note-card-info">
|
|
||||||
<div class="note-card-title">{note.title}</div>
|
|
||||||
<div class="note-card-date">{formatDate(note.createdAt)}</div>
|
|
||||||
</div>
|
|
||||||
<div class="note-card-actions" on:click|stopPropagation>
|
|
||||||
<button class="note-action-btn" on:click={() => startRenameNote(note.id, note.title)} title={t('common.rename')}>
|
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg>
|
|
||||||
</button>
|
|
||||||
<button class="note-action-btn note-action-danger" on:click={() => deleteNote(note)} title={t('common.delete')}>
|
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{:else if activeTab === 'files'}
|
{:else if activeTab === 'files'}
|
||||||
<!-- External file drops are handled by the unified capture flow and land in the selected case inbox. -->
|
<!-- External file drops are handled by the unified capture flow and land in the selected case inbox. -->
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script>
|
||||||
|
export let show = false
|
||||||
|
export let label = ''
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if show}
|
||||||
|
<div class="capture-drop-overlay">
|
||||||
|
<div class="capture-drop-box">{label}</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.capture-drop-overlay { position: fixed; inset: 0; z-index: 120; pointer-events: none; display: flex; align-items: center; justify-content: center; background: rgba(19, 19, 31, 0.42); border: 2px dashed #818cf8; }
|
||||||
|
.capture-drop-box { max-width: min(520px, calc(100vw - 48px)); padding: 14px 18px; border: 1px solid #3a3a5c; border-radius: 8px; background: #1a1a28; color: #e4e4ef; font-size: 14px; font-weight: 600; box-shadow: 0 12px 32px rgba(0,0,0,0.35); text-align: center; }
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<script>
|
||||||
|
import { t } from '../i18n'
|
||||||
|
|
||||||
|
export let error = ''
|
||||||
|
export let onDismiss = () => {}
|
||||||
|
|
||||||
|
function translateError(msg) {
|
||||||
|
const map = {
|
||||||
|
'vault not open': t('error.vaultNotOpen'),
|
||||||
|
}
|
||||||
|
return map[msg] || msg
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeydown(e) {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault()
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if error}
|
||||||
|
<div class="error-banner" role="button" tabindex="0" on:click={onDismiss} on:keydown={handleKeydown}>
|
||||||
|
{translateError(error)}
|
||||||
|
<button class="dismiss-btn" on:click|stopPropagation={onDismiss} aria-label="Dismiss">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.error-banner { background: #3a2222; color: #ff8888; padding: 8px 24px; font-size: 12px; border-bottom: 1px solid #4a2222; flex-shrink: 0; cursor: pointer; display: flex; justify-content: space-between; align-items: center; }
|
||||||
|
.dismiss-btn { background: none; border: none; color: #ff6666; cursor: pointer; padding: 2px; display: flex; align-items: center; border-radius: 2px; }
|
||||||
|
.dismiss-btn:hover { color: #ff4444; }
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
<script>
|
||||||
|
import { t } from '../i18n'
|
||||||
|
import { actionIcon } from '../actionIcons'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
export let node = null
|
||||||
|
export let notes = []
|
||||||
|
export let worklog = []
|
||||||
|
export let nodeKindLabel = (type) => type || ''
|
||||||
|
export let formatDate = (d) => d || ''
|
||||||
|
|
||||||
|
function goNotesCreate() {
|
||||||
|
dispatch('goTab', 'notes')
|
||||||
|
dispatch('createNote')
|
||||||
|
}
|
||||||
|
|
||||||
|
function goFilesAdd() {
|
||||||
|
dispatch('goTab', 'files')
|
||||||
|
dispatch('addFile')
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyActivate(fn) {
|
||||||
|
return (e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault()
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="overview">
|
||||||
|
<h2 class="node-title">{node.title}</h2>
|
||||||
|
<div class="meta-grid">
|
||||||
|
<div class="meta-item"><span class="meta-label">{t('overview.type')}</span><span>{nodeKindLabel(node.type)}</span></div>
|
||||||
|
<div class="meta-item"><span class="meta-label">{t('overview.section')}</span><span>{node.section || '—'}</span></div>
|
||||||
|
<div class="meta-item"><span class="meta-label">{t('overview.created')}</span><span>{formatDate(node.createdAt)}</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="quick-actions">
|
||||||
|
<button class="qa-btn" on:click={goNotesCreate}>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
|
||||||
|
{t('overview.newNote')}
|
||||||
|
</button>
|
||||||
|
<button class="qa-btn" on:click={goFilesAdd}>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
|
||||||
|
{t('overview.addFile')}
|
||||||
|
</button>
|
||||||
|
<button class="qa-btn" on:click={() => dispatch('createAction')}>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
||||||
|
{t('overview.addAction')}
|
||||||
|
</button>
|
||||||
|
<button class="qa-btn" on:click={() => dispatch('goTab', 'worklog')}>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
||||||
|
{t('overview.logTime')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if notes.length > 0}
|
||||||
|
<div class="recent-section">
|
||||||
|
<h3>{t('overview.recentNotes')}</h3>
|
||||||
|
{#each notes.slice(0, 5) as note}
|
||||||
|
<div class="recent-note" role="button" tabindex="0" on:click={() => dispatch('openNote', { note })} on:keydown={onKeyActivate(() => dispatch('openNote', { note }))}>
|
||||||
|
<span>{note.title}</span><span class="recent-date">{formatDate(note.createdAt)}</span>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if worklog.length > 0}
|
||||||
|
<div class="recent-section">
|
||||||
|
<h3>{t('overview.recentEntries')}</h3>
|
||||||
|
{#each worklog.slice(0, 3) as e}
|
||||||
|
<div class="recent-entry">{e.summary} ({e.minutes} {t('worklog.min')})</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.overview { padding: 24px; }
|
||||||
|
.node-title { font-size: 24px; margin-bottom: 16px; color: #e4e4ef; }
|
||||||
|
.meta-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; margin-bottom: 24px; }
|
||||||
|
.meta-item { background: #1a1a28; padding: 12px 16px; border-radius: 8px; }
|
||||||
|
.meta-label { display: block; font-size: 11px; color: #666; margin-bottom: 4px; text-transform: uppercase; }
|
||||||
|
.quick-actions { display: flex; gap: 8px; margin-bottom: 24px; flex-wrap: wrap; }
|
||||||
|
.qa-btn { padding: 10px 16px; border: 1px solid #2a2a3c; background: #1a1a28; color: #ccc; border-radius: 8px; cursor: pointer; font-size: 13px; font-family: inherit; display: inline-flex; align-items: center; gap: 6px; }
|
||||||
|
.qa-btn:hover { background: #222233; }
|
||||||
|
.qa-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||||
|
.recent-section { margin-bottom: 24px; }
|
||||||
|
.recent-section h3 { font-size: 13px; color: #666; text-transform: uppercase; margin-bottom: 8px; }
|
||||||
|
.recent-note { padding: 8px 12px; border-radius: 6px; cursor: pointer; display: flex; justify-content: space-between; }
|
||||||
|
.recent-note:hover { background: #1a1a28; }
|
||||||
|
.recent-date { font-size: 11px; color: #555; }
|
||||||
|
.recent-entry { padding: 6px 0; font-size: 13px; color: #888; border-bottom: 1px solid #1a1a28; }
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
<script>
|
||||||
|
import { t } from '../../i18n'
|
||||||
|
import { createEventDispatcher } from 'svelte'
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
export let notes = []
|
||||||
|
export let showCreateNote = false
|
||||||
|
export let formatDate = (d) => d || ''
|
||||||
|
|
||||||
|
let newNoteTitle = ''
|
||||||
|
|
||||||
|
function handleCreateNote() {
|
||||||
|
dispatch('createNote')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmitCreateNote() {
|
||||||
|
if (newNoteTitle.trim()) {
|
||||||
|
dispatch('submitCreateNote', { title: newNoteTitle.trim() })
|
||||||
|
newNoteTitle = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancelCreateNote() {
|
||||||
|
dispatch('cancelCreateNote')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOpenNote(note) {
|
||||||
|
dispatch('openNote', { note })
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleStartRename(note) {
|
||||||
|
dispatch('startRename', { note })
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDelete(note) {
|
||||||
|
dispatch('delete', { note })
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyActivate(fn) {
|
||||||
|
return (e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault()
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="notes-tab">
|
||||||
|
<div class="tab-toolbar">
|
||||||
|
<button class="btn btn-primary" on:click={handleCreateNote}>{t('note.add')}</button>
|
||||||
|
</div>
|
||||||
|
{#if showCreateNote}
|
||||||
|
<div class="create-form">
|
||||||
|
<input type="text" placeholder={t('note.title')} bind:value={newNoteTitle}
|
||||||
|
on:keydown={(e) => e.key === 'Enter' && handleSubmitCreateNote()} />
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="btn btn-primary" on:click={handleSubmitCreateNote}>{t('common.create')}</button>
|
||||||
|
<button class="btn" on:click={handleCancelCreateNote}>{t('common.cancel')}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if notes.length === 0 && !showCreateNote}
|
||||||
|
<div class="empty-state"><p>{t('note.noNotes')}</p><p class="hint">{t('note.createFirst')}</p></div>
|
||||||
|
{:else}
|
||||||
|
<div class="notes-list">
|
||||||
|
{#each notes as note}
|
||||||
|
<div class="note-card" role="button" tabindex="0" on:click={() => handleOpenNote(note)} on:keydown={onKeyActivate(() => handleOpenNote(note))}>
|
||||||
|
<div class="note-card-info">
|
||||||
|
<div class="note-card-title">{note.title}</div>
|
||||||
|
<div class="note-card-date">{formatDate(note.createdAt)}</div>
|
||||||
|
</div>
|
||||||
|
<div class="note-card-actions" on:click|stopPropagation>
|
||||||
|
<button class="note-action-btn" on:click={() => handleStartRename(note)} title={t('common.rename')}>
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg>
|
||||||
|
</button>
|
||||||
|
<button class="note-action-btn note-action-danger" on:click={() => handleDelete(note)} title={t('common.delete')}>
|
||||||
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.notes-tab { padding: 24px; }
|
||||||
|
.tab-toolbar { margin-bottom: 16px; }
|
||||||
|
.create-form { background: #1a1a28; padding: 16px; border-radius: 8px; margin-bottom: 16px; }
|
||||||
|
.create-form input { width: 100%; padding: 8px 12px; border: 1px solid #2a2a3c; background: #13131f; color: #e4e4ef; border-radius: 4px; font-size: 14px; font-family: inherit; margin-bottom: 8px; outline: none; }
|
||||||
|
.create-form input:focus { border-color: #6366f1; }
|
||||||
|
.form-actions { display: flex; gap: 8px; }
|
||||||
|
.notes-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
|
||||||
|
.note-card { background: #1a1a28; border: 1px solid #2a2a3c; border-radius: 8px; padding: 16px; cursor: pointer; }
|
||||||
|
.note-card:hover { border-color: #3a3a5c; }
|
||||||
|
.note-card-title { font-size: 14px; font-weight: 500; margin-bottom: 4px; }
|
||||||
|
.note-card-date { font-size: 11px; color: #555; }
|
||||||
|
.note-card-info { flex: 1; min-width: 0; }
|
||||||
|
.note-card-actions { display: flex; gap: 4px; opacity: 0; transition: opacity 0.12s; }
|
||||||
|
.note-card:hover .note-card-actions { opacity: 1; }
|
||||||
|
.note-action-btn { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border: none; border-radius: 4px; background: transparent; color: #666; cursor: pointer; transition: background 0.12s, color 0.12s; }
|
||||||
|
.note-action-btn:hover { background: #2a2a3c; color: #ccc; }
|
||||||
|
.note-action-danger:hover { background: rgba(239, 68, 68, 0.15); color: #f87171; }
|
||||||
|
.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 48px 24px; text-align: center; }
|
||||||
|
.empty-state p { margin: 0; font-size: 14px; color: #666; }
|
||||||
|
.empty-state .hint { font-size: 12px; color: #555; margin-top: 6px; }
|
||||||
|
.btn { padding: 8px 16px; border: 1px solid #2a2a3c; background: #1a1a28; color: #ccc; border-radius: 6px; cursor: pointer; font-size: 13px; font-family: inherit; display: inline-flex; align-items: center; gap: 6px; }
|
||||||
|
.btn:hover { background: #222233; }
|
||||||
|
.btn-primary { background: #6366f1; border-color: #6366f1; color: #fff; }
|
||||||
|
.btn-primary:hover { background: #4f46e5; }
|
||||||
|
.btn-sm { padding: 4px 10px; font-size: 12px; }
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* Actions API — actions (quick commands) on nodes.
|
||||||
|
*/
|
||||||
|
import { createApi } from './wails.js'
|
||||||
|
|
||||||
|
export const listActions = createApi('ListActions')
|
||||||
|
export const createAction = createApi('CreateAction')
|
||||||
|
export const runAction = createApi('RunAction')
|
||||||
|
export const deleteAction = createApi('DeleteAction')
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
/**
|
||||||
|
* Activity API — activity feed and per-node activity.
|
||||||
|
*/
|
||||||
|
import { createApi } from './wails.js'
|
||||||
|
|
||||||
|
export const listActivityFeed = createApi('ListActivityFeed')
|
||||||
|
export const listActivityByNode = createApi('ListActivityByNode')
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* Browser Events API — browser extension events.
|
||||||
|
*/
|
||||||
|
import { createApi } from './wails.js'
|
||||||
|
|
||||||
|
export const listBrowserEvents = createApi('ListBrowserEvents')
|
||||||
|
export const acceptBrowserEvent = createApi('AcceptBrowserEvent')
|
||||||
|
export const dismissBrowserEvent = createApi('DismissBrowserEvent')
|
||||||
|
export const attachBrowserEventToNode = createApi('AttachBrowserEventToNode')
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
* Files API — all file/folder related Wails calls.
|
||||||
|
*/
|
||||||
|
import { createApi } from './wails.js'
|
||||||
|
|
||||||
|
export const listItems = createApi('ListItems')
|
||||||
|
export const listFiles = createApi('ListFiles')
|
||||||
|
export const createEmptyFile = createApi('CreateEmptyFile')
|
||||||
|
export const deleteFileOrFolder = createApi('DeleteFileOrFolder')
|
||||||
|
export const openFile = createApi('OpenFile')
|
||||||
|
export const openFolder = createApi('OpenFolder')
|
||||||
|
export const pickFile = createApi('PickFile')
|
||||||
|
export const pickDirectory = createApi('PickDirectory')
|
||||||
|
export const previewImport = createApi('PreviewImport')
|
||||||
|
export const addPathCopy = createApi('AddPathCopy')
|
||||||
|
export const addPathLink = createApi('AddPathLink')
|
||||||
|
export const checkFileAction = createApi('CheckFileAction')
|
||||||
|
export const getFileBase64 = createApi('GetFileBase64')
|
||||||
|
export const readFileText = createApi('ReadFileText')
|
||||||
|
export const showInFolder = createApi('OpenFolder')
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
/**
|
||||||
|
* Inbox API — capture and inbox management.
|
||||||
|
*/
|
||||||
|
import { createApi } from './wails.js'
|
||||||
|
|
||||||
|
export const listInboxNodes = createApi('ListInboxNodes')
|
||||||
|
export const listInboxNodesForTarget = createApi('ListInboxNodesForTarget')
|
||||||
|
export const captureClipboardTextWithContext = createApi('CaptureClipboardTextWithContext')
|
||||||
|
export const captureTextWithContext = createApi('CaptureTextWithContext')
|
||||||
|
export const captureURLWithContext = createApi('CaptureURLWithContext')
|
||||||
|
export const capturePathWithContext = createApi('CapturePathWithContext')
|
||||||
|
export const captureFileDataWithContext = createApi('CaptureFileDataWithContext')
|
||||||
|
export const resolveInboxNode = createApi('ResolveInboxNode')
|
||||||
|
export const deleteInboxNode = createApi('DeleteInboxNode')
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* Journal & Worklog API — time tracking and reporting.
|
||||||
|
*/
|
||||||
|
import { createApi } from './wails.js'
|
||||||
|
|
||||||
|
export const listWorklog = createApi('ListWorklog')
|
||||||
|
export const createWorklogFull = createApi('CreateWorklogFull')
|
||||||
|
export const updateWorklogEntry = createApi('UpdateWorklogEntry')
|
||||||
|
export const deleteWorklogEntry = createApi('DeleteWorklogEntry')
|
||||||
|
export const getWorklogEntryEvents = createApi('GetWorklogEntryEvents')
|
||||||
|
export const listWorklogReport = createApi('ListWorklogReport')
|
||||||
|
export const worklogReportSummary = createApi('WorklogReportSummary')
|
||||||
|
export const saveWorklogReport = createApi('SaveWorklogReport')
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* Links API — external URL links management.
|
||||||
|
*/
|
||||||
|
import { createApi } from './wails.js'
|
||||||
|
|
||||||
|
export const listLinks = createApi('ListLinks')
|
||||||
|
export const updateLink = createApi('UpdateLink')
|
||||||
|
export const deleteLink = createApi('DeleteLink')
|
||||||
|
export const openLink = createApi('OpenLink')
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
* Node & Workspace API — tree, node CRUD, system views.
|
||||||
|
*/
|
||||||
|
import { createApi } from './wails.js'
|
||||||
|
|
||||||
|
export const getStartupStatus = createApi('GetStartupStatus')
|
||||||
|
export const verstakVersion = createApi('VerstakVersion')
|
||||||
|
export const listSystemViewsWithPlugins = createApi('ListSystemViewsWithPlugins')
|
||||||
|
export const listWorkspaceTree = createApi('ListWorkspaceTree')
|
||||||
|
export const listWorkspaceChildren = createApi('ListWorkspaceChildren')
|
||||||
|
export const listEnabledTemplates = createApi('ListEnabledTemplates')
|
||||||
|
export const createNodeFromTemplate = createApi('CreateNodeFromTemplate')
|
||||||
|
export const deleteNode = createApi('DeleteNode')
|
||||||
|
export const renameNode = createApi('RenameNode')
|
||||||
|
export const moveNode = createApi('MoveNode')
|
||||||
|
export const duplicateNode = createApi('DuplicateNode')
|
||||||
|
export const getNodeDetail = createApi('GetNodeDetail')
|
||||||
|
export const getSuggestions = createApi('GetSuggestions')
|
||||||
|
export const validateName = createApi('ValidateName')
|
||||||
|
export const writeDebugLog = createApi('WriteDebugLog')
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* Notes API — all note-related Wails calls.
|
||||||
|
*/
|
||||||
|
import { createApi } from './wails.js'
|
||||||
|
|
||||||
|
export const listNotes = createApi('ListNotes')
|
||||||
|
export const createNote = createApi('CreateNote')
|
||||||
|
export const readNote = createApi('ReadNote')
|
||||||
|
export const saveNote = createApi('SaveNote')
|
||||||
|
export const renameNote = createApi('RenameNote')
|
||||||
|
export const deleteNoteApi = createApi('DeleteNote')
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Search API — global search and node lookup.
|
||||||
|
*/
|
||||||
|
import { createApi } from './wails.js'
|
||||||
|
|
||||||
|
export const searchNodes = createApi('SearchNodes')
|
||||||
|
export const getNodeDetail = createApi('GetNodeDetail')
|
||||||
|
export const searchWorkspace = createApi('SearchWorkspace')
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* Suggestions API — worklog suggestions.
|
||||||
|
*/
|
||||||
|
import { createApi } from './wails.js'
|
||||||
|
|
||||||
|
export const getSuggestions = createApi('GetSuggestions')
|
||||||
|
export const acceptSuggestionFull = createApi('AcceptSuggestionFull')
|
||||||
|
export const dismissSuggestion = createApi('DismissSuggestion')
|
||||||
|
export const acceptSuggestionWith = createApi('AcceptSuggestionWith')
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
/**
|
||||||
|
* Sync API — vault synchronisation.
|
||||||
|
*/
|
||||||
|
import { createApi } from './wails.js'
|
||||||
|
|
||||||
|
export const syncStatus = createApi('SyncStatus')
|
||||||
|
export const syncNow = createApi('SyncNow')
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Today API — today dashboard, in-progress items, captures.
|
||||||
|
*/
|
||||||
|
import { createApi } from './wails.js'
|
||||||
|
|
||||||
|
export const listTodayView = createApi('ListTodayView')
|
||||||
|
export const listTodayInProgress = createApi('ListTodayInProgress')
|
||||||
|
export const listTodayCaptures = createApi('ListTodayCaptures')
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
/**
|
||||||
|
* Trash API — trash operations.
|
||||||
|
*/
|
||||||
|
import { createApi } from './wails.js'
|
||||||
|
|
||||||
|
export const listTrash = createApi('ListTrash')
|
||||||
|
export const trashCount = createApi('TrashCount')
|
||||||
|
export const readTrashFile = createApi('ReadTrashFile')
|
||||||
|
export const readTrashFileContent = createApi('ReadTrashFileContent')
|
||||||
|
export const restoreTrashNodes = createApi('RestoreTrashNodesJSON')
|
||||||
|
export const purgeTrashNodes = createApi('PurgeTrashNodesJSON')
|
||||||
|
export const emptyTrash = createApi('EmptyTrash')
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* Base Wails API call helper.
|
||||||
|
* All backend communication goes through this function.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function wailsCall(method, ...args) {
|
||||||
|
try {
|
||||||
|
if (window['go'] && window['go']['main'] && window['go']['main']['App']) {
|
||||||
|
const fn = window['go']['main']['App'][method]
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
return fn(...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Wails call error:', method, e)
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error('Wails not connected: ' + method))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a wrapped API function with consistent error handling.
|
||||||
|
*/
|
||||||
|
export function createApi(method) {
|
||||||
|
return (...args) => {
|
||||||
|
return wailsCall(method, ...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue