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:
@@ -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>
|
||||
Reference in New Issue
Block a user