mirror of
https://github.com/bretzel-app/crumbs.git
synced 2026-10-12 20:06:57 +02:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22b97469e1 | ||
|
|
bae7934ceb | ||
|
|
214fdec341 | ||
|
|
03481fea8a | ||
|
|
92743cda5f | ||
|
|
e8c3aca5ec |
@@ -54,7 +54,7 @@ Note colors for cards are defined in `src/lib/utils/colors.ts`.
|
||||
- **Animations**: Crisp and fast (150ms `ease-out`). No spring physics, no bounce. The retro feel comes from snappy transitions.
|
||||
- **Background texture**: A subtle 4px pixel grid overlay at 3% opacity (defined in `body::before`).
|
||||
- **Checkboxes**: Use `accent-color: var(--primary)` globally — gold checkboxes match the theme.
|
||||
- **Icons**: Lucide icons throughout. Keep at 16-20px size.
|
||||
- **Icons**: Pixelarticons via `@iconify/svelte` — use `<Icon icon="pixelarticons:name" width={size} class="..." />`. Keep at 16-20px size. Exception: Bold, Italic, Strikethrough, Underline stay as Lucide (`lucide-svelte`). See full mapping in `docs/superpowers/specs/2026-03-20-pixelarticons-migration-design.md`.
|
||||
- **Hover actions**: Never use `hidden group-hover:block` for action buttons — they're invisible on mobile (no hover). Use the opacity pattern instead: `max-md:opacity-100 md:opacity-0 transition-opacity md:group-hover:opacity-100`. This keeps actions always visible on touch devices and hover-revealed on desktop.
|
||||
|
||||
### Layout groups
|
||||
|
||||
+5
-5
@@ -64,10 +64,10 @@
|
||||
- Fog, Storm, Dusk, Blossom, Clay, Chalk
|
||||
- Colors adapt to note theme
|
||||
|
||||
### Pin Notes
|
||||
- Pin important notes to always appear at top
|
||||
- Pinned section separated from unpinned notes
|
||||
- Toggle pin from note card hover actions
|
||||
### Favorite Notes
|
||||
- Favorite important notes to always appear at top
|
||||
- Favorites section separated from other notes
|
||||
- Toggle favorite from note card hover actions
|
||||
|
||||
### Archive
|
||||
- Archive notes to declutter main view
|
||||
@@ -138,7 +138,7 @@
|
||||
|
||||
### MCP Server (Model Context Protocol)
|
||||
- Built-in MCP endpoint at `POST /api/mcp` (Streamable HTTP transport)
|
||||
- 14 tools: list, get, create, update, trash, restore, archive, unarchive, delete, search notes; list tags; pin, reorder notes; upload images
|
||||
- 14 tools: list, get, create, update, trash, restore, archive, unarchive, delete, search notes; list tags; favorite, reorder notes; upload images
|
||||
- API key authentication (Bearer token)
|
||||
- Compatible with Claude Code, Claude Desktop, and other MCP clients
|
||||
- Stateful sessions with automatic cleanup
|
||||
|
||||
@@ -0,0 +1,672 @@
|
||||
# Pixelarticons Migration + Favorites Rename — Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Replace Lucide icons with pixelarticons via @iconify/svelte across the app (except 4 formatting letterforms), and rename user-facing "pinned" to "favorites" with a heart icon.
|
||||
|
||||
**Architecture:** Each component file gets its Lucide imports replaced with a single `Icon` import from `@iconify/svelte`. The `size` prop becomes `width`, and the icon name becomes a `pixelarticons:name` string. The 4 formatting letterforms (Bold, Italic, Strikethrough, Underline) stay as Lucide. The favorites rename touches stores, components, MCP server, and one e2e test.
|
||||
|
||||
**Tech Stack:** `@iconify/svelte` v5.2.1, `@iconify-json/pixelarticons` v1.2.4 (both already installed), `lucide-svelte` (kept for 4 icons)
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-03-20-pixelarticons-migration-design.md`
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
No new files created. All changes are modifications to existing files:
|
||||
|
||||
**Icon migration (16 files):** Replace Lucide imports with `@iconify/svelte` Icon component.
|
||||
**Favorites rename (4 source files + 1 test file):** Rename stores, functions, labels, test IDs.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Favorites Rename — Stores
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/stores/notes.ts:38-44,234-236`
|
||||
|
||||
- [ ] **Step 1: Rename store exports and function**
|
||||
|
||||
In `src/lib/stores/notes.ts`, make these changes:
|
||||
|
||||
```typescript
|
||||
// Line 38-44: Rename stores
|
||||
export const favoriteNotes = derived(filteredNotes, ($notes) =>
|
||||
sortNotes($notes.filter((n) => n.pinned))
|
||||
);
|
||||
|
||||
export const unfavoritedNotes = derived(filteredNotes, ($notes) =>
|
||||
sortNotes($notes.filter((n) => !n.pinned))
|
||||
);
|
||||
|
||||
// Line 234-236: Rename function
|
||||
export async function toggleFavorite(id: string, currentPinned: boolean): Promise<Note | null> {
|
||||
return updateNote(id, { pinned: !currentPinned });
|
||||
}
|
||||
```
|
||||
|
||||
Note: The `pinned` field in updateNote stays — it's the DB column name.
|
||||
|
||||
- [ ] **Step 2: Verify no type errors**
|
||||
|
||||
Run: `pnpm check 2>&1 | head -30`
|
||||
Expected: Errors in files that still reference old names (NoteCard, NotesView) — that's expected, we fix those next.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/lib/stores/notes.ts
|
||||
git commit -m "refactor: rename pinned stores to favorites"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Favorites Rename — NoteCard Component
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/components/NoteCard.svelte:4,110-118,170-178`
|
||||
|
||||
- [ ] **Step 1: Update import**
|
||||
|
||||
Change line 4:
|
||||
```typescript
|
||||
import { toggleFavorite, trashNote, archiveNote, unarchiveNote, restoreNote, deleteNote, leaveNote, currentFilter } from '$lib/stores/notes.js';
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update favorite indicator (lines 110-118)**
|
||||
|
||||
Replace the pinned indicator block:
|
||||
```svelte
|
||||
{#if note.pinned}
|
||||
<button
|
||||
onclick={stop(() => toggleFavorite(note.id, note.pinned))}
|
||||
class="rounded-sm p-1 text-[var(--primary)] hover:bg-[var(--border)]/10"
|
||||
title="Unfavorite"
|
||||
data-testid="favorite-indicator"
|
||||
>
|
||||
<Bookmark class="h-4 w-4 fill-[var(--primary)]" />
|
||||
</button>
|
||||
{/if}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Update favorite button (lines 170-178)**
|
||||
|
||||
Replace the pin button block:
|
||||
```svelte
|
||||
{#if !note.pinned}
|
||||
<button
|
||||
onclick={stop(() => toggleFavorite(note.id, note.pinned))}
|
||||
class="rounded-sm p-1.5 hover:bg-[var(--border)]/10"
|
||||
title="Favorite"
|
||||
data-testid="favorite-btn"
|
||||
>
|
||||
<Bookmark class="h-4 w-4" />
|
||||
</button>
|
||||
{/if}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add src/lib/components/NoteCard.svelte
|
||||
git commit -m "refactor: rename pin to favorite in NoteCard"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Favorites Rename — NotesView Component
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/components/NotesView.svelte:5,89-104`
|
||||
|
||||
- [ ] **Step 1: Update imports (line 5)**
|
||||
|
||||
```typescript
|
||||
import { favoriteNotes, unfavoritedNotes, selectedTag, currentFilter, notes, notesLoaded, loadNotes, updateSortOrders } from '$lib/stores/notes.js';
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update template references**
|
||||
|
||||
Replace all `$pinnedNotes` with `$favoriteNotes`, `$unpinnedNotes` with `$unfavoritedNotes`, and label `"Pinned"` with `"Favorites"`:
|
||||
|
||||
Line 89: `{#if $favoriteNotes.length > 0}`
|
||||
Line 91: `<NoteGrid notes={$favoriteNotes} label="Favorites" onEdit={openEditor} draggable dndType="pinned-notes" onReorder={handleReorder} />`
|
||||
Line 96: `notes={$unfavoritedNotes}`
|
||||
Line 97: `label={$favoriteNotes.length > 0 ? 'Others' : ''}`
|
||||
Line 104: `{#if $notesLoaded && $favoriteNotes.length === 0 && $unfavoritedNotes.length === 0}`
|
||||
|
||||
Note: Keep `dndType="pinned-notes"` unchanged — it's an internal DnD identifier, not user-facing.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/lib/components/NotesView.svelte
|
||||
git commit -m "refactor: rename pinned to favorites in NotesView"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Favorites Rename — MCP Server
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/server/mcp/server.ts:241-265`
|
||||
|
||||
- [ ] **Step 1: Rename MCP tool**
|
||||
|
||||
Replace the `pin_note` tool definition (lines 241-265):
|
||||
```typescript
|
||||
server.tool(
|
||||
'favorite_note',
|
||||
'Favorite or unfavorite a note',
|
||||
{
|
||||
id: z.string().describe('Note ID'),
|
||||
pinned: z.boolean().describe('Whether to favorite (true) or unfavorite (false)')
|
||||
},
|
||||
async ({ id, pinned }: { id: string; pinned: boolean }) => {
|
||||
const result = updateNote(db, userId, id, { pinned });
|
||||
if (!result) {
|
||||
return {
|
||||
content: [{ type: 'text' as const, text: 'Note not found' }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: `Note "${result.title}" ${pinned ? 'favorited' : 'unfavorited'}`
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Note: The `pinned` parameter name stays since it maps to the DB column.
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add src/lib/server/mcp/server.ts
|
||||
git commit -m "refactor: rename pin_note MCP tool to favorite_note"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Favorites Rename — E2E Test
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/e2e/organization.spec.ts:4-15`
|
||||
|
||||
- [ ] **Step 1: Update test scenario**
|
||||
|
||||
Replace lines 4-15:
|
||||
```typescript
|
||||
test('Scenario: Favorited note appears under the Favorites section', async ({ authenticatedPage: page }) => {
|
||||
// Given a note titled "Favorite Me" exists
|
||||
await createNote(page, 'Favorite Me');
|
||||
|
||||
// When the user favorites the note
|
||||
const favCard = noteCard(page, 'Favorite Me');
|
||||
await favCard.hover();
|
||||
await favCard.getByTestId('favorite-btn').first().click({ force: true });
|
||||
|
||||
// Then the "Favorites" section is visible
|
||||
await expect(page.getByText('Favorites')).toBeVisible();
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run check to verify favorites rename is complete**
|
||||
|
||||
Run: `pnpm check 2>&1 | head -20`
|
||||
Expected: PASS (no errors related to old pin names)
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add tests/e2e/organization.spec.ts
|
||||
git commit -m "test: update pin test to favorites"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Icon Migration — Header + SearchBar
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/components/Layout/Header.svelte:2-5`
|
||||
- Modify: `src/lib/components/SearchBar.svelte:4-6`
|
||||
|
||||
- [ ] **Step 1: Migrate Header.svelte**
|
||||
|
||||
Replace imports (lines 2-5):
|
||||
```typescript
|
||||
import SearchBar from '../SearchBar.svelte';
|
||||
import SyncIndicator from '../SyncIndicator.svelte';
|
||||
import Icon from '@iconify/svelte';
|
||||
```
|
||||
|
||||
Replace icon usage in template:
|
||||
- `<Menu class="h-6 w-6 text-[var(--text)]" />` → `<Icon icon="pixelarticons:menu" class="h-6 w-6 text-[var(--text)]" />`
|
||||
- `<Search class="h-5 w-5 text-[var(--text)]" />` → `<Icon icon="pixelarticons:search" class="h-5 w-5 text-[var(--text)]" />`
|
||||
|
||||
- [ ] **Step 2: Migrate SearchBar.svelte**
|
||||
|
||||
Replace imports (lines 4-6):
|
||||
```typescript
|
||||
import Icon from '@iconify/svelte';
|
||||
```
|
||||
|
||||
Replace icon usage:
|
||||
- `<Search .../>` → `<Icon icon="pixelarticons:search" .../>`
|
||||
- `<X .../>` → `<Icon icon="pixelarticons:close" .../>`
|
||||
- `<ArrowLeft .../>` → `<Icon icon="pixelarticons:arrow-left" .../>`
|
||||
|
||||
Match existing `class` attributes exactly — just change the component and add the `icon` prop.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/lib/components/Layout/Header.svelte src/lib/components/SearchBar.svelte
|
||||
git commit -m "feat: migrate Header and SearchBar to pixelarticons"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Icon Migration — Sidebar
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/components/Layout/Sidebar.svelte:2-4,19-23,47,65,81`
|
||||
|
||||
- [ ] **Step 1: Replace imports and nav items**
|
||||
|
||||
Replace the import (line 4):
|
||||
```typescript
|
||||
import Icon from '@iconify/svelte';
|
||||
```
|
||||
|
||||
Replace navItems array (lines 19-23):
|
||||
```typescript
|
||||
const navItems = [
|
||||
{ href: '/', label: 'Crumbs', icon: 'pixelarticons:sticky-note', match: (p: string) => p === '/' },
|
||||
{ href: '/archive', label: 'Archive', icon: 'pixelarticons:archive', match: (p: string) => p === '/archive' },
|
||||
{ href: '/trash', label: 'Trash', icon: 'pixelarticons:trash', match: (p: string) => p === '/trash' }
|
||||
];
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update template icon usage**
|
||||
|
||||
Line 47: `<item.icon size={20} />` → `<Icon icon={item.icon} width={20} />`
|
||||
Line 65: `<Tag size={16} />` → `<Icon icon="pixelarticons:label" width={16} />`
|
||||
Line 81: `<Settings size={20} />` → `<Icon icon="pixelarticons:settings-cog" width={20} />`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/lib/components/Layout/Sidebar.svelte
|
||||
git commit -m "feat: migrate Sidebar to pixelarticons"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Icon Migration — NoteCard
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/components/NoteCard.svelte:5-13,117,159,167,177,187,196,206,215`
|
||||
|
||||
- [ ] **Step 1: Replace imports**
|
||||
|
||||
Replace lines 5-13:
|
||||
```typescript
|
||||
import Icon from '@iconify/svelte';
|
||||
```
|
||||
|
||||
Remove all individual Lucide imports (Undo2, Trash2, Bookmark, Archive, ArchiveRestore, UserMinus).
|
||||
|
||||
- [ ] **Step 2: Replace all icon usages in template**
|
||||
|
||||
- `<Bookmark class="h-4 w-4 fill-[var(--primary)]" />` (favorite indicator) → `<Icon icon="pixelarticons:heart" class="h-4 w-4 fill-[var(--primary)]" />`
|
||||
- `<Bookmark class="h-4 w-4" />` (favorite button) → `<Icon icon="pixelarticons:heart" class="h-4 w-4" />`
|
||||
- `<Undo2 class="h-4 w-4" />` (restore) → `<Icon icon="pixelarticons:undo" class="h-4 w-4" />`
|
||||
- `<Trash2 class="h-4 w-4 text-[var(--destructive)]" />` → `<Icon icon="pixelarticons:trash" class="h-4 w-4 text-[var(--destructive)]" />`
|
||||
- `<Trash2 class="h-4 w-4" />` → `<Icon icon="pixelarticons:trash" class="h-4 w-4" />`
|
||||
- `<Archive class="h-4 w-4" />` → `<Icon icon="pixelarticons:archive" class="h-4 w-4" />`
|
||||
- `<ArchiveRestore class="h-4 w-4" />` → `<Icon icon="pixelarticons:archive" class="h-4 w-4" />`
|
||||
- `<UserMinus class="h-4 w-4" />` → `<Icon icon="pixelarticons:user-minus" class="h-4 w-4" />`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/lib/components/NoteCard.svelte
|
||||
git commit -m "feat: migrate NoteCard to pixelarticons"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 9: Icon Migration — NoteEditor
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/components/NoteEditor.svelte:16-24`
|
||||
|
||||
- [ ] **Step 1: Replace imports**
|
||||
|
||||
Replace lines 16-24:
|
||||
```typescript
|
||||
import Icon from '@iconify/svelte';
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Replace all icon usages**
|
||||
|
||||
Search the file for each Lucide component and replace:
|
||||
- `<Palette .../>` → `<Icon icon="pixelarticons:colors-swatch" .../>`
|
||||
- `<SquareCheck .../>` → `<Icon icon="pixelarticons:checkbox-on" .../>`
|
||||
- `<ImageIcon .../>` → `<Icon icon="pixelarticons:image" .../>`
|
||||
- `<Type .../>` → `<Icon icon="pixelarticons:art-text" .../>`
|
||||
- `<FileCode .../>` → `<Icon icon="pixelarticons:script" .../>`
|
||||
- `<FileText .../>` → `<Icon icon="pixelarticons:file-text" .../>`
|
||||
- `<UserPlus .../>` → `<Icon icon="pixelarticons:user-plus" .../>`
|
||||
- `<Users .../>` → `<Icon icon="pixelarticons:users" .../>`
|
||||
- `<History .../>` → `<Icon icon="pixelarticons:timeline" .../>`
|
||||
|
||||
For each, keep the existing `size` prop but rename to `width`. Keep `class` as-is.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/lib/components/NoteEditor.svelte
|
||||
git commit -m "feat: migrate NoteEditor to pixelarticons"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 10: Icon Migration — FormattingToolbar
|
||||
|
||||
This is the largest file. Bold, Italic, Strikethrough, Underline stay as Lucide. Everything else migrates.
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/components/FormattingToolbar.svelte:2-31,131-482`
|
||||
|
||||
- [ ] **Step 1: Replace imports**
|
||||
|
||||
Replace the import block (lines 3-31):
|
||||
```typescript
|
||||
import {
|
||||
Bold,
|
||||
Italic,
|
||||
Strikethrough,
|
||||
Underline
|
||||
} from 'lucide-svelte';
|
||||
import Icon from '@iconify/svelte';
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Replace icon usages in template**
|
||||
|
||||
Replace every non-B/I/S/U icon. The pattern: `<IconName size={iconSize} />` → `<Icon icon="pixelarticons:name" width={iconSize} />`. Full mapping:
|
||||
|
||||
- `<Undo2 size={iconSize} />` → `<Icon icon="pixelarticons:undo" width={iconSize} />`
|
||||
- `<Redo2 size={iconSize} />` → `<Icon icon="pixelarticons:redo" width={iconSize} />`
|
||||
- `<Heading size={iconSize} />` → `<Icon icon="pixelarticons:heading" width={iconSize} />`
|
||||
- `<List size={iconSize} />` (appears 3x) → `<Icon icon="pixelarticons:list" width={iconSize} />`
|
||||
- `<ListOrdered size={iconSize} />` → `<Icon icon="pixelarticons:bulletlist" width={iconSize} />`
|
||||
- `<ListChecks size={iconSize} />` → `<Icon icon="pixelarticons:checklist" width={iconSize} />`
|
||||
- `<Link size={iconSize} />` → `<Icon icon="pixelarticons:link" width={iconSize} />`
|
||||
- `<CornerDownLeft size={16} />` → `<Icon icon="pixelarticons:corner-down-left" width={16} />`
|
||||
- `<ExternalLink size={16} />` → `<Icon icon="pixelarticons:external-link" width={16} />`
|
||||
- `<Trash2 size={16} />` → `<Icon icon="pixelarticons:trash" width={16} />`
|
||||
- `<Table2 size={iconSize} />` (appears 2x) → `<Icon icon="pixelarticons:table" width={iconSize} />`
|
||||
- `<ChevronDown size={chevronSize} />` (appears 4x) → `<Icon icon="pixelarticons:chevron-down" width={chevronSize} />`
|
||||
- `<BetweenHorizontalEnd size={iconSize} />` → `<Icon icon="pixelarticons:add-row" width={iconSize} />`
|
||||
- `<BetweenVerticalEnd size={iconSize} />` → `<Icon icon="pixelarticons:add-col" width={iconSize} />`
|
||||
- `<Minus size={iconSize} />` (appears 3x) → `<Icon icon="pixelarticons:minus" width={iconSize} />`
|
||||
- `<RemoveFormatting size={iconSize} />` → `<Icon icon="pixelarticons:remove-box" width={iconSize} />`
|
||||
- `<Code size={iconSize} />` → `<Icon icon="pixelarticons:braces" width={iconSize} />`
|
||||
- `<TextQuote size={iconSize} />` → `<Icon icon="pixelarticons:quote-text-inline" width={iconSize} />`
|
||||
- `<CodeXml size={iconSize} />` → `<Icon icon="pixelarticons:braces" width={iconSize} />`
|
||||
- `<AlignLeft size={iconSize} />` (2x) → `<Icon icon="pixelarticons:text-align-left" width={iconSize} />`
|
||||
- `<AlignCenter size={iconSize} />` (2x) → `<Icon icon="pixelarticons:text-align-center" width={iconSize} />`
|
||||
- `<AlignRight size={iconSize} />` (2x) → `<Icon icon="pixelarticons:text-align-right" width={iconSize} />`
|
||||
- `<AlignJustify size={iconSize} />` (2x) → `<Icon icon="pixelarticons:text-align-justify" width={iconSize} />`
|
||||
|
||||
Leave these unchanged: `<Bold size={iconSize} />`, `<Italic size={iconSize} />`, `<Strikethrough size={iconSize} />`, `<Underline size={iconSize} />`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/lib/components/FormattingToolbar.svelte
|
||||
git commit -m "feat: migrate FormattingToolbar to pixelarticons (keep B/I/S/U as Lucide)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 11: Icon Migration — SyncIndicator
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/components/SyncIndicator.svelte:4-7,54-61`
|
||||
|
||||
- [ ] **Step 1: Replace imports**
|
||||
|
||||
Replace lines 4-7:
|
||||
```typescript
|
||||
import Icon from '@iconify/svelte';
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Replace icon usages**
|
||||
|
||||
Replace the status icon block (lines 53-61):
|
||||
```svelte
|
||||
{#if displayStatus === 'synced'}
|
||||
<Icon icon="pixelarticons:cloud-done" class="h-5 w-5 text-[var(--primary)]" />
|
||||
{:else if displayStatus === 'syncing'}
|
||||
<Icon icon="pixelarticons:sync" class="h-5 w-5 text-[var(--primary)] opacity-60" />
|
||||
{:else if displayStatus === 'offline'}
|
||||
<Icon icon="pixelarticons:cloud" class="h-5 w-5 text-[var(--text-muted)]" />
|
||||
{:else}
|
||||
<Icon icon="pixelarticons:cloud" class="h-5 w-5 text-[var(--destructive)]" />
|
||||
{/if}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/lib/components/SyncIndicator.svelte
|
||||
git commit -m "feat: migrate SyncIndicator to pixelarticons"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 12: Icon Migration — Remaining Small Components
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/components/NoteHistory.svelte:3-5`
|
||||
- Modify: `src/lib/components/ShareDialog.svelte:3-5`
|
||||
- Modify: `src/lib/components/Checklist.svelte:2-5`
|
||||
- Modify: `src/lib/components/CollaboratorPopover.svelte:3`
|
||||
- Modify: `src/lib/components/ImageLightbox.svelte:2`
|
||||
- Modify: `src/lib/components/ImageUpload.svelte:2-4`
|
||||
- Modify: `src/lib/components/NotesView.svelte:8`
|
||||
|
||||
- [ ] **Step 1: Migrate NoteHistory.svelte**
|
||||
|
||||
Replace imports:
|
||||
```typescript
|
||||
import Icon from '@iconify/svelte';
|
||||
```
|
||||
Replace usages:
|
||||
- `<X .../>` → `<Icon icon="pixelarticons:close" .../>`
|
||||
- `<RotateCcw .../>` → `<Icon icon="pixelarticons:undo" .../>`
|
||||
- `<Clock .../>` → `<Icon icon="pixelarticons:clock" .../>`
|
||||
|
||||
- [ ] **Step 2: Migrate ShareDialog.svelte**
|
||||
|
||||
Replace imports:
|
||||
```typescript
|
||||
import Icon from '@iconify/svelte';
|
||||
```
|
||||
Replace usages:
|
||||
- `<X .../>` → `<Icon icon="pixelarticons:close" .../>`
|
||||
- `<UserMinus .../>` → `<Icon icon="pixelarticons:user-minus" .../>`
|
||||
- `<Search .../>` → `<Icon icon="pixelarticons:search" .../>`
|
||||
|
||||
- [ ] **Step 3: Migrate Checklist.svelte**
|
||||
|
||||
Replace imports:
|
||||
```typescript
|
||||
import Icon from '@iconify/svelte';
|
||||
```
|
||||
Replace usages:
|
||||
- `<XIcon .../>` → `<Icon icon="pixelarticons:close" .../>`
|
||||
- `<Plus .../>` → `<Icon icon="pixelarticons:plus" .../>`
|
||||
- `<GripVertical .../>` → `<Icon icon="pixelarticons:drag-and-drop" .../>`
|
||||
- `<ChevronRight .../>` → `<Icon icon="pixelarticons:chevron-right" .../>`
|
||||
|
||||
- [ ] **Step 4: Migrate CollaboratorPopover.svelte**
|
||||
|
||||
Replace import:
|
||||
```typescript
|
||||
import Icon from '@iconify/svelte';
|
||||
```
|
||||
Replace: `<Users .../>` → `<Icon icon="pixelarticons:users" .../>`
|
||||
|
||||
- [ ] **Step 5: Migrate ImageLightbox.svelte**
|
||||
|
||||
Replace import:
|
||||
```typescript
|
||||
import Icon from '@iconify/svelte';
|
||||
```
|
||||
Replace: `<XIcon .../>` → `<Icon icon="pixelarticons:close" .../>`
|
||||
|
||||
- [ ] **Step 6: Migrate ImageUpload.svelte**
|
||||
|
||||
Replace imports:
|
||||
```typescript
|
||||
import Icon from '@iconify/svelte';
|
||||
```
|
||||
Replace usages:
|
||||
- `<XIcon .../>` → `<Icon icon="pixelarticons:close" .../>`
|
||||
- `<Star .../>` → `<Icon icon="pixelarticons:pin" .../>`
|
||||
- `<RefreshCw .../>` → `<Icon icon="pixelarticons:reload" .../>`
|
||||
|
||||
- [ ] **Step 7: Migrate NotesView.svelte (Plus icon)**
|
||||
|
||||
Replace import (line 8):
|
||||
```typescript
|
||||
import Icon from '@iconify/svelte';
|
||||
```
|
||||
Replace usages:
|
||||
- `<Plus class="h-4 w-4" />` → `<Icon icon="pixelarticons:plus" class="h-4 w-4" />`
|
||||
- `<Plus class="h-5 w-5" />` → `<Icon icon="pixelarticons:plus" class="h-5 w-5" />`
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add src/lib/components/NoteHistory.svelte src/lib/components/ShareDialog.svelte src/lib/components/Checklist.svelte src/lib/components/CollaboratorPopover.svelte src/lib/components/ImageLightbox.svelte src/lib/components/ImageUpload.svelte src/lib/components/NotesView.svelte
|
||||
git commit -m "feat: migrate remaining components to pixelarticons"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 13: Icon Migration — Settings Pages
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/routes/(app)/settings/+layout.svelte:3`
|
||||
- Modify: `src/routes/(app)/settings/mcp/+page.svelte:2`
|
||||
|
||||
- [ ] **Step 1: Migrate settings layout**
|
||||
|
||||
Replace import (line 3):
|
||||
```typescript
|
||||
import Icon from '@iconify/svelte';
|
||||
```
|
||||
|
||||
Replace usages:
|
||||
- `<SlidersHorizontal size={16} />` → `<Icon icon="pixelarticons:sliders" width={16} />`
|
||||
- `<User size={16} />` → `<Icon icon="pixelarticons:user" width={16} />`
|
||||
- `<Cpu size={16} />` → `<Icon icon="pixelarticons:cpu" width={16} />`
|
||||
- `<ShieldCheck size={16} />` → `<Icon icon="pixelarticons:shield" width={16} />`
|
||||
- `<Info size={16} />` → `<Icon icon="pixelarticons:info-box" width={16} />`
|
||||
|
||||
- [ ] **Step 2: Migrate MCP settings page**
|
||||
|
||||
Replace import (line 2):
|
||||
```typescript
|
||||
import Icon from '@iconify/svelte';
|
||||
```
|
||||
|
||||
Replace usages:
|
||||
- `<Key .../>` → `<Icon icon="pixelarticons:lock" .../>`
|
||||
- `<Copy .../>` → `<Icon icon="pixelarticons:copy" .../>`
|
||||
- `<Trash2 .../>` → `<Icon icon="pixelarticons:trash" .../>`
|
||||
- `<Plus .../>` → `<Icon icon="pixelarticons:plus" .../>`
|
||||
- `<Check .../>` → `<Icon icon="pixelarticons:check" .../>`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/routes/(app)/settings/+layout.svelte src/routes/(app)/settings/mcp/+page.svelte
|
||||
git commit -m "feat: migrate settings pages to pixelarticons"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 14: Update docs/FEATURES.md
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/FEATURES.md:67-70,141`
|
||||
|
||||
- [ ] **Step 1: Rename "Pin Notes" section to "Favorite Notes"**
|
||||
|
||||
Replace lines 67-70:
|
||||
```markdown
|
||||
### Favorite Notes
|
||||
- Favorite important notes to always appear at top
|
||||
- Favorites section separated from other notes
|
||||
- Toggle favorite from note card hover actions
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update MCP tools description**
|
||||
|
||||
Line 141: replace `pin` with `favorite` in the tools list:
|
||||
```markdown
|
||||
- 14 tools: list, get, create, update, trash, restore, archive, unarchive, delete, search notes; list tags; favorite, reorder notes; upload images
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/FEATURES.md
|
||||
git commit -m "docs: rename pin to favorites in FEATURES.md"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 15: Verification
|
||||
|
||||
- [ ] **Step 1: Run type check**
|
||||
|
||||
Run: `pnpm check`
|
||||
Expected: PASS with no errors
|
||||
|
||||
- [ ] **Step 2: Run unit tests**
|
||||
|
||||
Run: `pnpm test:unit`
|
||||
Expected: All tests pass (DB/CRDT tests use `pinned` field which is unchanged)
|
||||
|
||||
- [ ] **Step 3: Run e2e tests**
|
||||
|
||||
Run: `pnpm test:e2e`
|
||||
Expected: All tests pass including the updated organization spec
|
||||
|
||||
- [ ] **Step 4: Update CLAUDE.md icons section**
|
||||
|
||||
Add to the "Visual rules" section in CLAUDE.md after the "Icons" line:
|
||||
|
||||
```markdown
|
||||
- **Icons**: Pixelarticons via `@iconify/svelte` — use `<Icon icon="pixelarticons:name" width={size} class="..." />`. Exception: Bold, Italic, Strikethrough, Underline stay as Lucide (`lucide-svelte`). See full mapping in `docs/superpowers/specs/2026-03-20-pixelarticons-migration-design.md`.
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Commit docs update**
|
||||
|
||||
```bash
|
||||
git add CLAUDE.md
|
||||
git commit -m "docs: update CLAUDE.md with pixelarticons icon usage"
|
||||
```
|
||||
@@ -0,0 +1,182 @@
|
||||
# Icon Migration: Lucide → Pixelarticons + "Pinned" → "Favorites" Rename
|
||||
|
||||
**Date:** 2026-03-20
|
||||
**Status:** Approved
|
||||
|
||||
## Summary
|
||||
|
||||
Replace Lucide icons with pixelarticons (via `@iconify/svelte`) across the app to match the retro parchment aesthetic. Keep Lucide only for 4 text-formatting letterform icons (Bold, Italic, Strikethrough, Underline). Additionally, rename the user-facing "pinned" concept to "favorites" with a heart icon, while keeping the `pinned` database column unchanged.
|
||||
|
||||
## Motivation
|
||||
|
||||
The current Lucide icons have smooth, modern strokes that clash with the 8-bit retro parchment design system. Pixelarticons are drawn on a strict 24×24 pixel grid with no anti-aliasing — a natural fit for the `Press Start 2P` pixel font and hard-offset shadow aesthetic already in use.
|
||||
|
||||
## Technical Approach
|
||||
|
||||
### Icon Library
|
||||
|
||||
- **Package:** `@iconify/svelte` (v5.2.1, already installed) + `@iconify-json/pixelarticons` (v1.2.4, already installed)
|
||||
- **Usage:** `<Icon icon="pixelarticons:icon-name" width={size} class="..." />`
|
||||
- **Tree-shaking:** Iconify handles this automatically — only icons used in code are bundled
|
||||
|
||||
### Icon Mapping
|
||||
|
||||
56 icons migrated to pixelarticons, 4 kept as Lucide:
|
||||
|
||||
#### Navigation & UI
|
||||
| Lucide | Pixelarticons |
|
||||
|--------|--------------|
|
||||
| Menu | `menu` |
|
||||
| Search | `search` |
|
||||
| X / XIcon | `close` |
|
||||
| Plus | `plus` |
|
||||
| Check | `check` |
|
||||
| ArrowLeft | `arrow-left` |
|
||||
| ChevronRight | `chevron-right` |
|
||||
| ChevronDown | `chevron-down` |
|
||||
| Settings | `settings-cog` |
|
||||
| ExternalLink | `external-link` |
|
||||
|
||||
#### Notes & Content
|
||||
| Lucide | Pixelarticons | Notes |
|
||||
|--------|--------------|-------|
|
||||
| StickyNote | `sticky-note` | |
|
||||
| Archive | `archive` | |
|
||||
| ArchiveRestore | `archive` | Context makes it clear |
|
||||
| Trash2 | `trash` | |
|
||||
| Bookmark | `heart` | Renamed to "favorite" |
|
||||
| Clock | `clock` | |
|
||||
| History | `timeline` | |
|
||||
| FileText | `file-text` | |
|
||||
| FileCode | `script` | |
|
||||
| ImageIcon | `image` | |
|
||||
| Palette | `colors-swatch` | |
|
||||
| GripVertical | `drag-and-drop` | |
|
||||
| Star | `pin` | Used in ImageUpload for "featured image" toggle — not related to favorites |
|
||||
| SquareCheck | `checkbox-on` | |
|
||||
| Type | `art-text` | |
|
||||
|
||||
#### Text Formatting (toolbar)
|
||||
| Lucide | Pixelarticons | Notes |
|
||||
|--------|--------------|-------|
|
||||
| **Bold** | **Keep Lucide** | Letterform |
|
||||
| **Italic** | **Keep Lucide** | Letterform |
|
||||
| **Strikethrough** | **Keep Lucide** | Letterform |
|
||||
| **Underline** | **Keep Lucide** | Letterform |
|
||||
| Code | `braces` | |
|
||||
| CodeXml | `braces` | |
|
||||
| Heading | `heading` | |
|
||||
| Link | `link` | |
|
||||
| TextQuote | `quote-text-inline` | |
|
||||
| List | `list` | |
|
||||
| ListOrdered | `bulletlist` | |
|
||||
| ListChecks | `checklist` | |
|
||||
| AlignLeft | `text-align-left` | |
|
||||
| AlignCenter | `text-align-center` | |
|
||||
| AlignRight | `text-align-right` | |
|
||||
| AlignJustify | `text-align-justify` | |
|
||||
| Table2 | `table` | |
|
||||
| Minus | `minus` | |
|
||||
| RemoveFormatting | `remove-box` | |
|
||||
| CornerDownLeft | `corner-down-left` | |
|
||||
| BetweenHorizontalEnd | `add-row` | |
|
||||
| BetweenVerticalEnd | `add-col` | |
|
||||
|
||||
#### Undo/Redo
|
||||
| Lucide | Pixelarticons |
|
||||
|--------|--------------|
|
||||
| Undo2 | `undo` |
|
||||
| Redo2 | `redo` |
|
||||
| RotateCcw | `undo` |
|
||||
| RefreshCw | `reload` |
|
||||
|
||||
#### Sync Status
|
||||
| Lucide | Pixelarticons | Notes |
|
||||
|--------|--------------|-------|
|
||||
| CloudCheck | `cloud-done` | |
|
||||
| CloudSync | `sync` | |
|
||||
| CloudOff | `cloud` | Color conveys offline state |
|
||||
| CloudAlert | `cloud` | Color conveys error state |
|
||||
|
||||
#### Users & Admin
|
||||
| Lucide | Pixelarticons |
|
||||
|--------|--------------|
|
||||
| User | `user` |
|
||||
| Users | `users` |
|
||||
| UserPlus | `user-plus` |
|
||||
| UserMinus | `user-minus` |
|
||||
| Cpu | `cpu` |
|
||||
| ShieldCheck | `shield` |
|
||||
| SlidersHorizontal | `sliders` |
|
||||
| Info | `info-box` |
|
||||
| Key | `lock` |
|
||||
| Copy | `copy` |
|
||||
| Tag | `label` |
|
||||
|
||||
### "Pinned" → "Favorites" Rename
|
||||
|
||||
**Scope:** User-facing labels, function names, store names, MCP tool names, test IDs. The database column `pinned` stays unchanged to avoid a migration.
|
||||
|
||||
**Renames:**
|
||||
| Current | New |
|
||||
|---------|-----|
|
||||
| `pinnedNotes` (store) | `favoriteNotes` |
|
||||
| `unpinnedNotes` (store) | `unfavoritedNotes` |
|
||||
| `togglePin()` | `toggleFavorite()` |
|
||||
| `"Pinned"` label in NotesView | `"Favorites"` |
|
||||
| `"Pin"` / `"Unpin"` tooltips in NoteCard | `"Favorite"` / `"Unfavorite"` |
|
||||
| `pin_note` MCP tool | `favorite_note` |
|
||||
| `pin-btn` / `pin-indicator` test IDs | `favorite-btn` / `favorite-indicator` |
|
||||
|
||||
**Not renamed (internal/DB):**
|
||||
- `pinned` column in `notes` and `noteUserState` tables
|
||||
- `pinned` field in TypeScript types (`Note`, `NoteUpdate`, `NoteChange`)
|
||||
- `pinned` references in CRDT sync logic
|
||||
- `pinned` in server-side sync overlay
|
||||
|
||||
## Files to Modify
|
||||
|
||||
### Icon migration (16 files)
|
||||
1. `src/lib/components/Layout/Sidebar.svelte`
|
||||
2. `src/lib/components/Layout/Header.svelte`
|
||||
3. `src/lib/components/FormattingToolbar.svelte`
|
||||
4. `src/lib/components/NoteCard.svelte`
|
||||
5. `src/lib/components/NoteEditor.svelte`
|
||||
6. `src/lib/components/NoteHistory.svelte`
|
||||
7. `src/lib/components/SyncIndicator.svelte`
|
||||
8. `src/lib/components/SearchBar.svelte`
|
||||
9. `src/lib/components/ShareDialog.svelte`
|
||||
10. `src/lib/components/Checklist.svelte`
|
||||
11. `src/lib/components/CollaboratorPopover.svelte`
|
||||
12. `src/lib/components/ImageLightbox.svelte`
|
||||
13. `src/lib/components/ImageUpload.svelte`
|
||||
14. `src/lib/components/NotesView.svelte`
|
||||
15. `src/routes/(app)/settings/+layout.svelte`
|
||||
16. `src/routes/(app)/settings/mcp/+page.svelte`
|
||||
|
||||
### Favorites rename (7 files)
|
||||
1. `src/lib/stores/notes.ts` — store names + `toggleFavorite`
|
||||
2. `src/lib/components/NoteCard.svelte` — tooltips, test IDs, icon
|
||||
3. `src/lib/components/NotesView.svelte` — label, store imports
|
||||
4. `src/lib/server/mcp/server.ts` — tool name + description
|
||||
5. `docs/FEATURES.md` — update feature description
|
||||
6. `tests/e2e/organization.spec.ts` — `pin-btn` test ID + `getByText('Pinned')` → `getByText('Favorites')` + scenario names/comments
|
||||
7. Note: `tests/e2e/sharing.spec.ts` references `pinned` as a data field — does NOT need renaming (DB field stays `pinned`)
|
||||
|
||||
### Cleanup
|
||||
- Remove `lucide-svelte` from `package.json` once only 4 icons remain — or keep it as a dependency for those 4. Decision: keep it, the 4 formatting icons are deeply integrated.
|
||||
|
||||
### Documentation
|
||||
- Update `CLAUDE.md` "Icons" section to document pixelarticons usage
|
||||
- Update `docs/FEATURES.md` if it mentions pinning
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
- Run `pnpm check` after migration to catch type errors
|
||||
- Run `pnpm test:unit` to verify sync/CRDT tests still pass (they use `pinned` field which is unchanged)
|
||||
- Run `pnpm test:e2e` to verify UI still works — tests referencing `pin-btn`/`pin-indicator` will need updating
|
||||
- Visual verification: icons render correctly at all sizes (12-20px)
|
||||
|
||||
## Rendering Note
|
||||
|
||||
Pixelarticons are designed for the 24×24 grid and render sharpest at multiples of 24px. At the sizes used in this app (12-20px), they will still look good because `@iconify/svelte` renders them as SVG (vector), but the pixel-art crispness is maximized at 24px. Consider using `image-rendering: pixelated` on icon SVGs if sub-24px rendering looks blurry, though SVG scaling typically handles this well.
|
||||
@@ -33,6 +33,8 @@
|
||||
"docs:api": "tsx scripts/generate-api-docs.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify-json/pixelarticons": "^1.2.4",
|
||||
"@iconify/svelte": "^5.2.1",
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"@sveltejs/adapter-node": "^5.5.4",
|
||||
"@tiptap/core": "^3.20.1",
|
||||
|
||||
Generated
+28
@@ -14,6 +14,12 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@iconify-json/pixelarticons':
|
||||
specifier: ^1.2.4
|
||||
version: 1.2.4
|
||||
'@iconify/svelte':
|
||||
specifier: ^5.2.1
|
||||
version: 5.2.1(svelte@5.53.7)
|
||||
'@modelcontextprotocol/sdk':
|
||||
specifier: ^1.27.1
|
||||
version: 1.27.1(zod@4.3.6)
|
||||
@@ -1018,6 +1024,17 @@ packages:
|
||||
peerDependencies:
|
||||
hono: '>=4.12.7'
|
||||
|
||||
'@iconify-json/pixelarticons@1.2.4':
|
||||
resolution: {integrity: sha512-nADJKEI3mlxDSmJMLqGvNuvNl1TGa+VbFJIwrtj/8vUA4m15qPNiwxMyw8YxW3gVokuO9MNU462B8uK1hb4rqw==}
|
||||
|
||||
'@iconify/svelte@5.2.1':
|
||||
resolution: {integrity: sha512-zHmsIPmnIhGd5gc95bNN5FL+GifwMZv7M2rlZEpa7IXYGFJm/XGHdWf6PWQa6OBoC+R69WyiPO9NAj5wjfjbow==}
|
||||
peerDependencies:
|
||||
svelte: '>5.0.0'
|
||||
|
||||
'@iconify/types@2.0.0':
|
||||
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -4851,6 +4868,17 @@ snapshots:
|
||||
dependencies:
|
||||
hono: 4.12.7
|
||||
|
||||
'@iconify-json/pixelarticons@1.2.4':
|
||||
dependencies:
|
||||
'@iconify/types': 2.0.0
|
||||
|
||||
'@iconify/svelte@5.2.1(svelte@5.53.7)':
|
||||
dependencies:
|
||||
'@iconify/types': 2.0.0
|
||||
svelte: 5.53.7
|
||||
|
||||
'@iconify/types@2.0.0': {}
|
||||
|
||||
'@isaacs/cliui@8.0.2':
|
||||
dependencies:
|
||||
string-width: 5.1.2
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<script lang="ts">
|
||||
import XIcon from 'lucide-svelte/icons/x';
|
||||
import Plus from 'lucide-svelte/icons/plus';
|
||||
import GripVertical from 'lucide-svelte/icons/grip-vertical';
|
||||
import ChevronRight from 'lucide-svelte/icons/chevron-right';
|
||||
import Icon from '@iconify/svelte';
|
||||
import { dragHandleZone, dragHandle, type DndEvent } from 'svelte-dnd-action';
|
||||
import { flip } from 'svelte/animate';
|
||||
|
||||
@@ -123,7 +120,7 @@
|
||||
{#each activeItems as item (item.id)}
|
||||
<div class="group flex items-center gap-2 py-1.5" animate:flip={{ duration: flipDurationMs }}>
|
||||
<div use:dragHandle aria-label="drag handle" class="drag-handle cursor-grab max-md:opacity-100 md:opacity-0 md:group-hover:opacity-100 focus:opacity-100 transition-opacity duration-150" data-testid="checklist-drag-handle">
|
||||
<GripVertical class="h-4 w-4 text-[var(--text-muted)]" />
|
||||
<Icon icon="pixelarticons:drag-and-drop" class="h-4 w-4 text-[var(--text-muted)]" />
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -147,7 +144,7 @@
|
||||
aria-label="Remove item"
|
||||
data-testid="checklist-remove"
|
||||
>
|
||||
<XIcon class="h-4 w-4 text-[var(--text-muted)]" />
|
||||
<Icon icon="pixelarticons:close" class="h-4 w-4 text-[var(--text-muted)]" />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
@@ -158,7 +155,7 @@
|
||||
class="flex items-center gap-2 py-1.5 text-sm text-[var(--text-muted)] hover:text-[var(--text)]"
|
||||
data-testid="checklist-add"
|
||||
>
|
||||
<Plus class="h-4 w-4" />
|
||||
<Icon icon="pixelarticons:plus" class="h-4 w-4" />
|
||||
Add item
|
||||
</button>
|
||||
|
||||
@@ -169,7 +166,7 @@
|
||||
class="flex items-center gap-1 text-xs text-[var(--text-muted)] hover:text-[var(--text)]"
|
||||
data-testid="checklist-toggle-done"
|
||||
>
|
||||
<ChevronRight class="h-3 w-3 transition-transform duration-150 {doneExpanded ? 'rotate-90' : ''}" />
|
||||
<Icon icon="pixelarticons:chevron-right" class="h-3 w-3 transition-transform duration-150 {doneExpanded ? 'rotate-90' : ''}" />
|
||||
{doneCount} done
|
||||
</button>
|
||||
|
||||
@@ -191,7 +188,7 @@
|
||||
aria-label="Remove item"
|
||||
data-testid="checklist-done-remove"
|
||||
>
|
||||
<XIcon class="h-4 w-4 text-[var(--text-muted)]" />
|
||||
<Icon icon="pixelarticons:close" class="h-4 w-4 text-[var(--text-muted)]" />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { Collaborator } from '$lib/types/index.js';
|
||||
import Users from 'lucide-svelte/icons/users';
|
||||
import Icon from '@iconify/svelte';
|
||||
|
||||
interface Props {
|
||||
collaborators: Collaborator[];
|
||||
@@ -42,7 +42,7 @@
|
||||
onclick={handleClick}
|
||||
data-testid="collaborator-indicator"
|
||||
>
|
||||
<Users class="h-4 w-4" />
|
||||
<Icon icon="pixelarticons:users" class="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
{#if showPopover}
|
||||
|
||||
@@ -1,34 +1,12 @@
|
||||
<script lang="ts">
|
||||
import type { Editor } from '@tiptap/core';
|
||||
import {
|
||||
Undo2,
|
||||
Redo2,
|
||||
Heading,
|
||||
List,
|
||||
ListOrdered,
|
||||
ListChecks,
|
||||
TextQuote,
|
||||
CodeXml,
|
||||
Bold,
|
||||
Italic,
|
||||
Strikethrough,
|
||||
Code,
|
||||
Underline,
|
||||
Link,
|
||||
CornerDownLeft,
|
||||
ExternalLink,
|
||||
Trash2,
|
||||
AlignLeft,
|
||||
AlignCenter,
|
||||
AlignRight,
|
||||
AlignJustify,
|
||||
Minus,
|
||||
ChevronDown,
|
||||
Table2,
|
||||
BetweenHorizontalEnd,
|
||||
BetweenVerticalEnd,
|
||||
RemoveFormatting
|
||||
Underline
|
||||
} from 'lucide-svelte';
|
||||
import Icon from '@iconify/svelte';
|
||||
|
||||
interface Props {
|
||||
tick?: number;
|
||||
@@ -128,7 +106,7 @@
|
||||
title="Undo"
|
||||
data-testid="format-undo"
|
||||
>
|
||||
<Undo2 size={iconSize} />
|
||||
<Icon icon="pixelarticons:undo" width={iconSize} />
|
||||
</button>
|
||||
<button
|
||||
onclick={() => editor?.chain().focus().redo().run()}
|
||||
@@ -137,7 +115,7 @@
|
||||
title="Redo"
|
||||
data-testid="format-redo"
|
||||
>
|
||||
<Redo2 size={iconSize} />
|
||||
<Icon icon="pixelarticons:redo" width={iconSize} />
|
||||
</button>
|
||||
|
||||
<div class="mx-1 h-4 w-px shrink-0 bg-[var(--border-subtle)]"></div>
|
||||
@@ -186,8 +164,8 @@
|
||||
title="Heading"
|
||||
data-testid="format-heading"
|
||||
>
|
||||
<Heading size={iconSize} />
|
||||
<ChevronDown size={chevronSize} />
|
||||
<Icon icon="pixelarticons:heading" width={iconSize} />
|
||||
<Icon icon="pixelarticons:chevron-down" width={chevronSize} />
|
||||
</button>
|
||||
{#if openDropdown === 'heading'}
|
||||
<div class="absolute left-0 bottom-full z-50 mb-1 min-w-[150px] rounded-sm border border-[var(--border)] bg-[var(--bg-surface)] py-1">
|
||||
@@ -227,8 +205,8 @@
|
||||
title="Lists"
|
||||
data-testid="format-list"
|
||||
>
|
||||
<List size={iconSize} />
|
||||
<ChevronDown size={chevronSize} />
|
||||
<Icon icon="pixelarticons:list" width={iconSize} />
|
||||
<Icon icon="pixelarticons:chevron-down" width={chevronSize} />
|
||||
</button>
|
||||
{#if openDropdown === 'list'}
|
||||
<div class="absolute left-0 bottom-full z-50 mb-1 min-w-[170px] rounded-sm border border-[var(--border)] bg-[var(--bg-surface)] py-1">
|
||||
@@ -237,7 +215,7 @@
|
||||
class={dropdownItemClass(isActive('bulletList'))}
|
||||
data-testid="format-bullet-list"
|
||||
>
|
||||
<List size={iconSize} />
|
||||
<Icon icon="pixelarticons:list" width={iconSize} />
|
||||
<span>Bullet list</span>
|
||||
</button>
|
||||
<button
|
||||
@@ -245,7 +223,7 @@
|
||||
class={dropdownItemClass(isActive('orderedList'))}
|
||||
data-testid="format-ordered-list"
|
||||
>
|
||||
<ListOrdered size={iconSize} />
|
||||
<Icon icon="pixelarticons:bulletlist" width={iconSize} />
|
||||
<span>Ordered list</span>
|
||||
</button>
|
||||
<button
|
||||
@@ -253,7 +231,7 @@
|
||||
class={dropdownItemClass(isActive('taskList'))}
|
||||
data-testid="format-task-list"
|
||||
>
|
||||
<ListChecks size={iconSize} />
|
||||
<Icon icon="pixelarticons:checklist" width={iconSize} />
|
||||
<span>Task list</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -268,7 +246,7 @@
|
||||
title="Insert link (Ctrl+K)"
|
||||
data-testid="format-link"
|
||||
>
|
||||
<Link size={iconSize} />
|
||||
<Icon icon="pixelarticons:link" width={iconSize} />
|
||||
</button>
|
||||
{#if openDropdown === 'link'}
|
||||
<div class="absolute left-1/2 bottom-full z-50 mb-1 -translate-x-1/2 rounded-sm border border-[var(--border)] bg-[var(--bg-surface)] px-2 py-1.5">
|
||||
@@ -290,7 +268,7 @@
|
||||
title="Apply link"
|
||||
data-testid="format-link-apply"
|
||||
>
|
||||
<CornerDownLeft size={16} />
|
||||
<Icon icon="pixelarticons:corner-down-left" width={16} />
|
||||
</button>
|
||||
<div class="mx-0.5 h-4 w-px bg-[var(--border-subtle)]"></div>
|
||||
<button
|
||||
@@ -301,7 +279,7 @@
|
||||
title="Open link"
|
||||
data-testid="format-link-open"
|
||||
>
|
||||
<ExternalLink size={16} />
|
||||
<Icon icon="pixelarticons:external-link" width={16} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -311,7 +289,7 @@
|
||||
title="Remove link"
|
||||
data-testid="format-unlink"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
<Icon icon="pixelarticons:trash" width={16} />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -326,8 +304,8 @@
|
||||
title="Table"
|
||||
data-testid="format-table"
|
||||
>
|
||||
<Table2 size={iconSize} />
|
||||
<ChevronDown size={chevronSize} />
|
||||
<Icon icon="pixelarticons:table" width={iconSize} />
|
||||
<Icon icon="pixelarticons:chevron-down" width={chevronSize} />
|
||||
</button>
|
||||
{#if openDropdown === 'table'}
|
||||
<div class="absolute left-0 bottom-full z-50 mb-1 min-w-[190px] rounded-sm border border-[var(--border)] bg-[var(--bg-surface)] py-1">
|
||||
@@ -336,7 +314,7 @@
|
||||
class={dropdownItemClass()}
|
||||
data-testid="format-table-insert"
|
||||
>
|
||||
<Table2 size={iconSize} />
|
||||
<Icon icon="pixelarticons:table" width={iconSize} />
|
||||
<span>Insert table</span>
|
||||
</button>
|
||||
{#if isActive('table')}
|
||||
@@ -346,7 +324,7 @@
|
||||
class={dropdownItemClass()}
|
||||
data-testid="format-table-add-row"
|
||||
>
|
||||
<BetweenHorizontalEnd size={iconSize} />
|
||||
<Icon icon="pixelarticons:add-row" width={iconSize} />
|
||||
<span>Add row</span>
|
||||
</button>
|
||||
<button
|
||||
@@ -354,7 +332,7 @@
|
||||
class={dropdownItemClass()}
|
||||
data-testid="format-table-add-col"
|
||||
>
|
||||
<BetweenVerticalEnd size={iconSize} />
|
||||
<Icon icon="pixelarticons:add-col" width={iconSize} />
|
||||
<span>Add column</span>
|
||||
</button>
|
||||
<div class="my-1 h-px bg-[var(--border-subtle)]"></div>
|
||||
@@ -363,7 +341,7 @@
|
||||
class={dropdownItemClass()}
|
||||
data-testid="format-table-delete-row"
|
||||
>
|
||||
<Minus size={iconSize} />
|
||||
<Icon icon="pixelarticons:minus" width={iconSize} />
|
||||
<span>Delete row</span>
|
||||
</button>
|
||||
<button
|
||||
@@ -371,7 +349,7 @@
|
||||
class={dropdownItemClass()}
|
||||
data-testid="format-table-delete-col"
|
||||
>
|
||||
<Minus size={iconSize} />
|
||||
<Icon icon="pixelarticons:minus" width={iconSize} />
|
||||
<span>Delete column</span>
|
||||
</button>
|
||||
<button
|
||||
@@ -379,7 +357,7 @@
|
||||
class={`${dropdownItemClass()} text-[var(--destructive)]`}
|
||||
data-testid="format-table-delete"
|
||||
>
|
||||
<RemoveFormatting size={iconSize} />
|
||||
<Icon icon="pixelarticons:remove-box" width={iconSize} />
|
||||
<span>Delete table</span>
|
||||
</button>
|
||||
{/if}
|
||||
@@ -396,15 +374,15 @@
|
||||
data-testid="format-align"
|
||||
>
|
||||
{#if isActive({ textAlign: 'center' })}
|
||||
<AlignCenter size={iconSize} />
|
||||
<Icon icon="pixelarticons:text-align-center" width={iconSize} />
|
||||
{:else if isActive({ textAlign: 'right' })}
|
||||
<AlignRight size={iconSize} />
|
||||
<Icon icon="pixelarticons:text-align-right" width={iconSize} />
|
||||
{:else if isActive({ textAlign: 'justify' })}
|
||||
<AlignJustify size={iconSize} />
|
||||
<Icon icon="pixelarticons:text-align-justify" width={iconSize} />
|
||||
{:else}
|
||||
<AlignLeft size={iconSize} />
|
||||
<Icon icon="pixelarticons:text-align-left" width={iconSize} />
|
||||
{/if}
|
||||
<ChevronDown size={chevronSize} />
|
||||
<Icon icon="pixelarticons:chevron-down" width={chevronSize} />
|
||||
</button>
|
||||
{#if openDropdown === 'align'}
|
||||
<div class="absolute right-0 bottom-full z-50 mb-1 min-w-[160px] rounded-sm border border-[var(--border)] bg-[var(--bg-surface)] py-1">
|
||||
@@ -413,7 +391,7 @@
|
||||
class={dropdownItemClass(isActive({ textAlign: 'left' }))}
|
||||
data-testid="format-align-left"
|
||||
>
|
||||
<AlignLeft size={iconSize} />
|
||||
<Icon icon="pixelarticons:text-align-left" width={iconSize} />
|
||||
<span>Left</span>
|
||||
</button>
|
||||
<button
|
||||
@@ -421,7 +399,7 @@
|
||||
class={dropdownItemClass(isActive({ textAlign: 'center' }))}
|
||||
data-testid="format-align-center"
|
||||
>
|
||||
<AlignCenter size={iconSize} />
|
||||
<Icon icon="pixelarticons:text-align-center" width={iconSize} />
|
||||
<span>Center</span>
|
||||
</button>
|
||||
<button
|
||||
@@ -429,7 +407,7 @@
|
||||
class={dropdownItemClass(isActive({ textAlign: 'right' }))}
|
||||
data-testid="format-align-right"
|
||||
>
|
||||
<AlignRight size={iconSize} />
|
||||
<Icon icon="pixelarticons:text-align-right" width={iconSize} />
|
||||
<span>Right</span>
|
||||
</button>
|
||||
<button
|
||||
@@ -437,7 +415,7 @@
|
||||
class={dropdownItemClass(isActive({ textAlign: 'justify' }))}
|
||||
data-testid="format-align-justify"
|
||||
>
|
||||
<AlignJustify size={iconSize} />
|
||||
<Icon icon="pixelarticons:text-align-justify" width={iconSize} />
|
||||
<span>Justify</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -453,7 +431,7 @@
|
||||
title="Inline code"
|
||||
data-testid="format-code"
|
||||
>
|
||||
<Code size={iconSize} />
|
||||
<Icon icon="pixelarticons:braces" width={iconSize} />
|
||||
</button>
|
||||
<button
|
||||
onclick={() => editor?.chain().focus().toggleBlockquote().run()}
|
||||
@@ -461,7 +439,7 @@
|
||||
title="Blockquote"
|
||||
data-testid="format-blockquote"
|
||||
>
|
||||
<TextQuote size={iconSize} />
|
||||
<Icon icon="pixelarticons:quote-text-inline" width={iconSize} />
|
||||
</button>
|
||||
<button
|
||||
onclick={() => editor?.chain().focus().toggleCodeBlock().run()}
|
||||
@@ -469,7 +447,7 @@
|
||||
title="Code block"
|
||||
data-testid="format-code-block"
|
||||
>
|
||||
<CodeXml size={iconSize} />
|
||||
<Icon icon="pixelarticons:braces" width={iconSize} />
|
||||
</button>
|
||||
<button
|
||||
onclick={() => editor?.chain().focus().setHorizontalRule().run()}
|
||||
@@ -477,6 +455,6 @@
|
||||
title="Divider"
|
||||
data-testid="format-hr"
|
||||
>
|
||||
<Minus size={iconSize} />
|
||||
<Icon icon="pixelarticons:minus" width={iconSize} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import XIcon from 'lucide-svelte/icons/x';
|
||||
import Icon from '@iconify/svelte';
|
||||
|
||||
interface Props {
|
||||
src: string;
|
||||
@@ -46,7 +46,7 @@
|
||||
aria-label="Close lightbox"
|
||||
data-testid="lightbox-close"
|
||||
>
|
||||
<XIcon class="h-6 w-6" />
|
||||
<Icon icon="pixelarticons:close" class="h-6 w-6" />
|
||||
</button>
|
||||
|
||||
<img
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<script lang="ts">
|
||||
import XIcon from 'lucide-svelte/icons/x';
|
||||
import Star from 'lucide-svelte/icons/star';
|
||||
import RefreshCw from 'lucide-svelte/icons/refresh-cw';
|
||||
import Icon from '@iconify/svelte';
|
||||
import ImageLightbox from './ImageLightbox.svelte';
|
||||
import type { Attachment } from '$lib/types/index.js';
|
||||
import { optimizeImage } from '$lib/utils/image-optimize.js';
|
||||
@@ -133,7 +131,7 @@
|
||||
</button>
|
||||
{#if isPending}
|
||||
<div class="absolute bottom-0.5 left-0.5 rounded-sm bg-black/50 p-0.5" title="Pending sync">
|
||||
<RefreshCw class="h-3 w-3 text-white" />
|
||||
<Icon icon="pixelarticons:reload" class="h-3 w-3 text-white" />
|
||||
</div>
|
||||
{:else if onToggleFeatured}
|
||||
<button
|
||||
@@ -142,7 +140,7 @@
|
||||
aria-label={attachment.featured ? 'Unfeature image' : 'Feature image'}
|
||||
data-testid="toggle-featured"
|
||||
>
|
||||
<Star class="h-3 w-3 {attachment.featured ? 'fill-[var(--primary)] text-[var(--primary)]' : 'text-white'}" />
|
||||
<Icon icon="pixelarticons:pin" class="h-3 w-3 {attachment.featured ? 'fill-[var(--primary)] text-[var(--primary)]' : 'text-white'}" />
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
@@ -151,7 +149,7 @@
|
||||
aria-label="Remove attachment"
|
||||
data-testid="remove-attachment"
|
||||
>
|
||||
<XIcon class="h-3 w-3" />
|
||||
<Icon icon="pixelarticons:close" class="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import SearchBar from '../SearchBar.svelte';
|
||||
import SyncIndicator from '../SyncIndicator.svelte';
|
||||
import Menu from 'lucide-svelte/icons/menu';
|
||||
import Search from 'lucide-svelte/icons/search';
|
||||
import Icon from '@iconify/svelte';
|
||||
|
||||
interface Props {
|
||||
onMenuToggle: () => void;
|
||||
@@ -24,7 +23,7 @@
|
||||
class="rounded-sm p-2 hover:bg-[var(--border)]/10"
|
||||
aria-label="Toggle sidebar"
|
||||
>
|
||||
<Menu class="h-6 w-6 text-[var(--text)]" />
|
||||
<Icon icon="pixelarticons:menu" class="h-6 w-6 text-[var(--text)]" />
|
||||
</button>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -43,7 +42,7 @@
|
||||
class="ml-auto rounded-sm p-2 hover:bg-[var(--border)]/10 lg:hidden"
|
||||
aria-label="Search"
|
||||
>
|
||||
<Search class="h-5 w-5 text-[var(--text)]" />
|
||||
<Icon icon="pixelarticons:search" class="h-5 w-5 text-[var(--text)]" />
|
||||
</button>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { allTags } from '$lib/stores/notes.js';
|
||||
import { StickyNote, Archive, Trash2, Tag, Settings } from 'lucide-svelte';
|
||||
import Icon from '@iconify/svelte';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
@@ -17,9 +17,9 @@
|
||||
}
|
||||
|
||||
const navItems = [
|
||||
{ href: '/', label: 'Crumbs', icon: StickyNote, match: (p: string) => p === '/' },
|
||||
{ href: '/archive', label: 'Archive', icon: Archive, match: (p: string) => p === '/archive' },
|
||||
{ href: '/trash', label: 'Trash', icon: Trash2, match: (p: string) => p === '/trash' }
|
||||
{ href: '/', label: 'Crumbs', icon: 'pixelarticons:sticky-note', match: (p: string) => p === '/' },
|
||||
{ href: '/archive', label: 'Archive', icon: 'pixelarticons:archive', match: (p: string) => p === '/archive' },
|
||||
{ href: '/trash', label: 'Trash', icon: 'pixelarticons:trash', match: (p: string) => p === '/trash' }
|
||||
];
|
||||
</script>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
onclick={closeMobile}
|
||||
class="flex w-full items-center gap-3 rounded-sm px-6 py-3 text-left text-sm transition-colors {item.match($page.url.pathname) ? 'bg-[var(--primary)]/15 text-[var(--primary)]' : 'text-[var(--text)] hover:bg-[var(--bg-base)]'}"
|
||||
>
|
||||
<item.icon size={20} />
|
||||
<Icon icon={item.icon} width={20} />
|
||||
{item.label}
|
||||
</a>
|
||||
</li>
|
||||
@@ -62,7 +62,7 @@
|
||||
onclick={closeMobile}
|
||||
class="flex w-full items-center gap-3 rounded-sm px-6 py-2 text-left text-sm transition-colors {$page.url.pathname === `/tag/${tag}` ? 'bg-[var(--primary)]/15 text-[var(--primary)]' : 'text-[var(--text)] hover:bg-[var(--bg-base)]'}"
|
||||
>
|
||||
<Tag size={16} />
|
||||
<Icon icon="pixelarticons:label" width={16} />
|
||||
#{tag}
|
||||
</a>
|
||||
</li>
|
||||
@@ -78,7 +78,7 @@
|
||||
onclick={closeMobile}
|
||||
data-testid="settings-link"
|
||||
>
|
||||
<Settings size={20} />
|
||||
<Icon icon="pixelarticons:settings-cog" width={20} />
|
||||
Settings
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { NOTE_COLORS } from '$lib/utils/colors.js';
|
||||
import { renderMarkdown } from '$lib/utils/markdown.js';
|
||||
import { togglePin, trashNote, archiveNote, unarchiveNote, restoreNote, deleteNote, leaveNote, currentFilter } from '$lib/stores/notes.js';
|
||||
import { toggleFavorite, trashNote, archiveNote, unarchiveNote, restoreNote, deleteNote, leaveNote, currentFilter } from '$lib/stores/notes.js';
|
||||
import ImageLightbox from './ImageLightbox.svelte';
|
||||
import CollaboratorPopover from './CollaboratorPopover.svelte';
|
||||
import type { Note } from '$lib/types/index.js';
|
||||
import Undo2 from 'lucide-svelte/icons/undo-2';
|
||||
import Trash2 from 'lucide-svelte/icons/trash-2';
|
||||
import Bookmark from 'lucide-svelte/icons/bookmark';
|
||||
import Archive from 'lucide-svelte/icons/archive';
|
||||
import ArchiveRestore from 'lucide-svelte/icons/archive-restore';
|
||||
import UserMinus from 'lucide-svelte/icons/user-minus';
|
||||
import Icon from '@iconify/svelte';
|
||||
|
||||
interface ChecklistItem {
|
||||
text: string;
|
||||
@@ -109,12 +104,12 @@
|
||||
{/if}
|
||||
{#if note.pinned}
|
||||
<button
|
||||
onclick={stop(() => togglePin(note.id, note.pinned))}
|
||||
onclick={stop(() => toggleFavorite(note.id, note.pinned))}
|
||||
class="rounded-sm p-1 text-[var(--primary)] hover:bg-[var(--border)]/10"
|
||||
title="Unpin"
|
||||
data-testid="pin-indicator"
|
||||
title="Unfavorite"
|
||||
data-testid="favorite-indicator"
|
||||
>
|
||||
<Bookmark class="h-4 w-4 fill-[var(--primary)]" />
|
||||
<Icon icon="pixelarticons:heart" class="h-4 w-4 fill-[var(--primary)]" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -156,7 +151,7 @@
|
||||
title="Restore"
|
||||
data-testid="restore-btn"
|
||||
>
|
||||
<Undo2 class="h-4 w-4" />
|
||||
<Icon icon="pixelarticons:undo" class="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onclick={stop(() => deleteNote(note.id))}
|
||||
@@ -164,17 +159,17 @@
|
||||
title="Delete forever"
|
||||
data-testid="delete-forever-btn"
|
||||
>
|
||||
<Trash2 class="h-4 w-4 text-[var(--destructive)]" />
|
||||
<Icon icon="pixelarticons:trash" class="h-4 w-4 text-[var(--destructive)]" />
|
||||
</button>
|
||||
{:else}
|
||||
{#if !note.pinned}
|
||||
<button
|
||||
onclick={stop(() => togglePin(note.id, note.pinned))}
|
||||
onclick={stop(() => toggleFavorite(note.id, note.pinned))}
|
||||
class="rounded-sm p-1.5 hover:bg-[var(--border)]/10"
|
||||
title="Pin"
|
||||
data-testid="pin-btn"
|
||||
title="Favorite"
|
||||
data-testid="favorite-btn"
|
||||
>
|
||||
<Bookmark class="h-4 w-4" />
|
||||
<Icon icon="pixelarticons:heart" class="h-4 w-4" />
|
||||
</button>
|
||||
{/if}
|
||||
{#if $currentFilter === 'archived'}
|
||||
@@ -184,7 +179,7 @@
|
||||
title="Unarchive"
|
||||
data-testid="unarchive-btn"
|
||||
>
|
||||
<ArchiveRestore class="h-4 w-4" />
|
||||
<Icon icon="pixelarticons:archive" class="h-4 w-4" />
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
@@ -193,7 +188,7 @@
|
||||
title="Archive"
|
||||
data-testid="archive-btn"
|
||||
>
|
||||
<Archive class="h-4 w-4" />
|
||||
<Icon icon="pixelarticons:archive" class="h-4 w-4" />
|
||||
</button>
|
||||
{/if}
|
||||
{#if note.isShared && !note.isOwner}
|
||||
@@ -203,7 +198,7 @@
|
||||
title="Leave note"
|
||||
data-testid="leave-btn"
|
||||
>
|
||||
<UserMinus class="h-4 w-4" />
|
||||
<Icon icon="pixelarticons:user-minus" class="h-4 w-4" />
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
@@ -212,7 +207,7 @@
|
||||
title="Delete"
|
||||
data-testid="trash-btn"
|
||||
>
|
||||
<Trash2 class="h-4 w-4" />
|
||||
<Icon icon="pixelarticons:trash" class="h-4 w-4" />
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -13,15 +13,7 @@
|
||||
import { getPreferences } from '$lib/stores/preferences.svelte.js';
|
||||
import type { Editor } from '@tiptap/core';
|
||||
import type { Note, NoteColor, Attachment, Collaborator } from '$lib/types/index.js';
|
||||
import Palette from 'lucide-svelte/icons/palette';
|
||||
import SquareCheck from 'lucide-svelte/icons/square-check';
|
||||
import ImageIcon from 'lucide-svelte/icons/image';
|
||||
import Type from 'lucide-svelte/icons/type';
|
||||
import FileCode from 'lucide-svelte/icons/file-code';
|
||||
import FileText from 'lucide-svelte/icons/file-text';
|
||||
import UserPlus from 'lucide-svelte/icons/user-plus';
|
||||
import Users from 'lucide-svelte/icons/users';
|
||||
import History from 'lucide-svelte/icons/history';
|
||||
import Icon from '@iconify/svelte';
|
||||
|
||||
interface Props {
|
||||
note: Note | null;
|
||||
@@ -345,7 +337,7 @@
|
||||
title="Background color"
|
||||
data-testid="color-picker-toggle"
|
||||
>
|
||||
<Palette class="h-5 w-5 text-[var(--text-muted)]" />
|
||||
<Icon icon="pixelarticons:colors-swatch" class="h-5 w-5 text-[var(--text-muted)]" />
|
||||
</button>
|
||||
{#if showColorPicker}
|
||||
<div class="absolute left-0 bottom-full z-10 mb-2 w-[calc(100vw-4rem)] max-w-xs rounded-sm border border-[var(--border)] bg-[var(--bg-surface)] p-2">
|
||||
@@ -362,9 +354,9 @@
|
||||
data-testid="checklist-toggle"
|
||||
>
|
||||
{#if checklistMode}
|
||||
<Type class="h-5 w-5" />
|
||||
<Icon icon="pixelarticons:art-text" class="h-5 w-5" />
|
||||
{:else}
|
||||
<SquareCheck class="h-5 w-5" />
|
||||
<Icon icon="pixelarticons:checkbox-on" class="h-5 w-5" />
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
@@ -375,7 +367,7 @@
|
||||
title="Image attachments"
|
||||
data-testid="image-toggle"
|
||||
>
|
||||
<ImageIcon class="h-5 w-5 {showImageUpload ? 'text-[var(--primary)]' : ''}" />
|
||||
<Icon icon="pixelarticons:image" class="h-5 w-5 {showImageUpload ? 'text-[var(--primary)]' : ''}" />
|
||||
</button>
|
||||
|
||||
<!-- Raw markdown mode toggle -->
|
||||
@@ -387,9 +379,9 @@
|
||||
data-testid="markdown-toggle"
|
||||
>
|
||||
{#if rawMarkdownMode}
|
||||
<FileText class="h-5 w-5" />
|
||||
<Icon icon="pixelarticons:file-text" class="h-5 w-5" />
|
||||
{:else}
|
||||
<FileCode class="h-5 w-5" />
|
||||
<Icon icon="pixelarticons:script" class="h-5 w-5" />
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
@@ -402,14 +394,14 @@
|
||||
data-testid="share-toggle"
|
||||
>
|
||||
{#if isShared}
|
||||
<Users class="h-5 w-5 text-[var(--primary)]" />
|
||||
<Icon icon="pixelarticons:users" class="h-5 w-5 text-[var(--primary)]" />
|
||||
{:else}
|
||||
<UserPlus class="h-5 w-5" />
|
||||
<Icon icon="pixelarticons:user-plus" class="h-5 w-5" />
|
||||
{/if}
|
||||
</button>
|
||||
{:else if isShared}
|
||||
<span class="flex items-center gap-1 rounded-sm p-2 text-[var(--text-muted)]" title="Shared note">
|
||||
<Users class="h-5 w-5" />
|
||||
<Icon icon="pixelarticons:users" class="h-5 w-5" />
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
@@ -421,7 +413,7 @@
|
||||
title="Version history"
|
||||
data-testid="history-toggle"
|
||||
>
|
||||
<History class="h-5 w-5 {showHistory ? 'text-[var(--primary)]' : ''}" />
|
||||
<Icon icon="pixelarticons:timeline" class="h-5 w-5 {showHistory ? 'text-[var(--primary)]' : ''}" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { NoteVersionSummary, NoteVersion } from '$lib/types/index.js';
|
||||
import X from 'lucide-svelte/icons/x';
|
||||
import RotateCcw from 'lucide-svelte/icons/rotate-ccw';
|
||||
import Clock from 'lucide-svelte/icons/clock';
|
||||
import Icon from '@iconify/svelte';
|
||||
import { showToast } from '$lib/stores/toast.js';
|
||||
|
||||
interface Props {
|
||||
@@ -111,7 +109,7 @@
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between border-b border-[var(--border-subtle)] px-4 py-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<Clock class="h-4 w-4 text-[var(--text-muted)]" />
|
||||
<Icon icon="pixelarticons:clock" class="h-4 w-4 text-[var(--text-muted)]" />
|
||||
<h2 class="text-sm font-semibold text-[var(--text)]">Version History</h2>
|
||||
</div>
|
||||
<button
|
||||
@@ -120,7 +118,7 @@
|
||||
title="Close history"
|
||||
data-testid="close-history-btn"
|
||||
>
|
||||
<X class="h-4 w-4 text-[var(--text-muted)]" />
|
||||
<Icon icon="pixelarticons:close" class="h-4 w-4 text-[var(--text-muted)]" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -190,7 +188,7 @@
|
||||
class="flex items-center gap-1.5 rounded-sm border border-[var(--destructive)] bg-[var(--destructive)] px-3 py-1.5 text-xs font-medium text-white hover:opacity-90 disabled:opacity-50"
|
||||
data-testid="confirm-restore-btn"
|
||||
>
|
||||
<RotateCcw class="h-3.5 w-3.5" />
|
||||
<Icon icon="pixelarticons:undo" class="h-3.5 w-3.5" />
|
||||
{isRestoring ? 'Restoring…' : 'Confirm Restore'}
|
||||
</button>
|
||||
<button
|
||||
@@ -207,7 +205,7 @@
|
||||
class="flex items-center gap-1.5 rounded-sm border border-[var(--border-subtle)] px-3 py-1.5 text-xs font-medium text-[var(--text)] hover:border-[var(--primary)] hover:text-[var(--primary)] transition-colors"
|
||||
data-testid="restore-btn"
|
||||
>
|
||||
<RotateCcw class="h-3.5 w-3.5" />
|
||||
<Icon icon="pixelarticons:undo" class="h-3.5 w-3.5" />
|
||||
Restore this version
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
import NoteGrid from '$lib/components/NoteGrid.svelte';
|
||||
import NoteEditor from '$lib/components/NoteEditor.svelte';
|
||||
import TagFilter from '$lib/components/TagFilter.svelte';
|
||||
import { pinnedNotes, unpinnedNotes, selectedTag, currentFilter, notes, notesLoaded, loadNotes, updateSortOrders } from '$lib/stores/notes.js';
|
||||
import { favoriteNotes, unfavoritedNotes, selectedTag, currentFilter, notes, notesLoaded, loadNotes, updateSortOrders } from '$lib/stores/notes.js';
|
||||
import { onMount } from 'svelte';
|
||||
import type { Note, NoteFilter } from '$lib/types/index.js';
|
||||
import Plus from 'lucide-svelte/icons/plus';
|
||||
import Icon from '@iconify/svelte';
|
||||
|
||||
interface Props {
|
||||
filter: NoteFilter;
|
||||
@@ -60,7 +60,7 @@
|
||||
class="flex w-full items-center gap-3 rounded-sm border border-dashed border-[var(--border-subtle)] bg-[var(--bg-surface)] px-4 py-3 text-left text-sm text-[var(--text-muted)] transition-colors hover:border-[var(--primary)] hover:text-[var(--primary)]"
|
||||
data-testid="new-note-btn"
|
||||
>
|
||||
<Plus class="h-4 w-4" />
|
||||
<Icon icon="pixelarticons:plus" class="h-4 w-4" />
|
||||
Add a crumb...
|
||||
</button>
|
||||
</div>
|
||||
@@ -70,7 +70,7 @@
|
||||
aria-label="Add a crumb"
|
||||
data-testid="new-note-fab"
|
||||
>
|
||||
<Plus class="h-5 w-5" />
|
||||
<Icon icon="pixelarticons:plus" class="h-5 w-5" />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -86,22 +86,22 @@
|
||||
<h2 class="mb-4 text-lg font-medium text-[var(--text-muted)]">#{tag}</h2>
|
||||
{/if}
|
||||
|
||||
{#if $pinnedNotes.length > 0}
|
||||
{#if $favoriteNotes.length > 0}
|
||||
<div class="mb-6">
|
||||
<NoteGrid notes={$pinnedNotes} label="Pinned" onEdit={openEditor} draggable dndType="pinned-notes" onReorder={handleReorder} />
|
||||
<NoteGrid notes={$favoriteNotes} label="Favorites" onEdit={openEditor} draggable dndType="pinned-notes" onReorder={handleReorder} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<NoteGrid
|
||||
notes={$unpinnedNotes}
|
||||
label={$pinnedNotes.length > 0 ? 'Others' : ''}
|
||||
notes={$unfavoritedNotes}
|
||||
label={$favoriteNotes.length > 0 ? 'Others' : ''}
|
||||
onEdit={openEditor}
|
||||
draggable
|
||||
dndType="unpinned-notes"
|
||||
onReorder={handleReorder}
|
||||
/>
|
||||
|
||||
{#if $notesLoaded && $pinnedNotes.length === 0 && $unpinnedNotes.length === 0}
|
||||
{#if $notesLoaded && $favoriteNotes.length === 0 && $unfavoritedNotes.length === 0}
|
||||
<div class="flex flex-col items-center justify-center py-20 text-[var(--text-muted)]">
|
||||
<img src="/favicon.svg" alt="" class="mb-4 h-18 w-18" />
|
||||
<p class="font-['Press_Start_2P'] text-xs">
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { notes, loadNotes, currentFilter } from '$lib/stores/notes.js';
|
||||
import type { Note } from '$lib/types/index.js';
|
||||
import Search from 'lucide-svelte/icons/search';
|
||||
import X from 'lucide-svelte/icons/x';
|
||||
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
|
||||
import Icon from '@iconify/svelte';
|
||||
|
||||
interface Props {
|
||||
onClose?: () => void;
|
||||
@@ -59,10 +57,10 @@
|
||||
<div class="flex items-center rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-base)] px-4 py-2 focus-within:border-[var(--primary)]">
|
||||
{#if onClose}
|
||||
<button onclick={close} class="mr-2 text-[var(--text-muted)] hover:text-[var(--text)]" aria-label="Back">
|
||||
<ArrowLeft class="h-5 w-5" />
|
||||
<Icon icon="pixelarticons:arrow-left" class="h-5 w-5" />
|
||||
</button>
|
||||
{:else}
|
||||
<Search class="mr-3 h-5 w-5 text-[var(--text-muted)]" />
|
||||
<Icon icon="pixelarticons:search" class="mr-3 h-5 w-5 text-[var(--text-muted)]" />
|
||||
{/if}
|
||||
<input
|
||||
bind:this={inputEl}
|
||||
@@ -75,7 +73,7 @@
|
||||
/>
|
||||
{#if query}
|
||||
<button onclick={clearSearch} class="ml-2 text-[var(--text-muted)] hover:text-[var(--text)]" aria-label="Clear search">
|
||||
<X class="h-5 w-5" />
|
||||
<Icon icon="pixelarticons:close" class="h-5 w-5" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { Collaborator } from '$lib/types/index.js';
|
||||
import X from 'lucide-svelte/icons/x';
|
||||
import UserMinus from 'lucide-svelte/icons/user-minus';
|
||||
import Search from 'lucide-svelte/icons/search';
|
||||
import Icon from '@iconify/svelte';
|
||||
import { showToast } from '$lib/stores/toast.js';
|
||||
|
||||
interface UserResult {
|
||||
@@ -119,14 +117,14 @@
|
||||
class="rounded-sm p-1 hover:bg-[var(--border)]/10"
|
||||
title="Close"
|
||||
>
|
||||
<X class="h-4 w-4" />
|
||||
<Icon icon="pixelarticons:close" class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Search input -->
|
||||
<div class="relative px-4 pt-3">
|
||||
<div class="relative">
|
||||
<Search class="absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-[var(--text-muted)]" />
|
||||
<Icon icon="pixelarticons:search" class="absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-[var(--text-muted)]" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search users..."
|
||||
@@ -181,7 +179,7 @@
|
||||
title="Remove"
|
||||
data-testid="remove-collaborator-btn"
|
||||
>
|
||||
<UserMinus class="h-4 w-4" />
|
||||
<Icon icon="pixelarticons:user-minus" class="h-4 w-4" />
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { getSyncStatus, onSyncStatusChange, sync, type SyncStatus } from '$lib/sync/client.js';
|
||||
import CloudCheck from 'lucide-svelte/icons/cloud-check';
|
||||
import CloudSync from 'lucide-svelte/icons/cloud-sync';
|
||||
import CloudOff from 'lucide-svelte/icons/cloud-off';
|
||||
import CloudAlert from 'lucide-svelte/icons/cloud-alert';
|
||||
import Icon from '@iconify/svelte';
|
||||
|
||||
let status: SyncStatus = $state(getSyncStatus());
|
||||
let displayStatus: SyncStatus = $state(getSyncStatus());
|
||||
@@ -51,12 +48,12 @@
|
||||
data-testid="sync-indicator"
|
||||
>
|
||||
{#if displayStatus === 'synced'}
|
||||
<CloudCheck class="h-5 w-5 text-[var(--primary)]" />
|
||||
<Icon icon="pixelarticons:cloud-done" class="h-5 w-5 text-[var(--primary)]" />
|
||||
{:else if displayStatus === 'syncing'}
|
||||
<CloudSync class="h-5 w-5 text-[var(--primary)] opacity-60" />
|
||||
<Icon icon="pixelarticons:sync" class="h-5 w-5 text-[var(--primary)] opacity-60" />
|
||||
{:else if displayStatus === 'offline'}
|
||||
<CloudOff class="h-5 w-5 text-[var(--text-muted)]" />
|
||||
<Icon icon="pixelarticons:cloud" class="h-5 w-5 text-[var(--text-muted)]" />
|
||||
{:else}
|
||||
<CloudAlert class="h-5 w-5 text-[var(--destructive)]" />
|
||||
<Icon icon="pixelarticons:cloud" class="h-5 w-5 text-[var(--destructive)]" />
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
@@ -239,8 +239,8 @@ export function createMcpServer(userId: number): McpServer {
|
||||
});
|
||||
|
||||
server.tool(
|
||||
'pin_note',
|
||||
'Pin or unpin a note',
|
||||
'favorite_note',
|
||||
'Favorite or unfavorite a note',
|
||||
{
|
||||
id: z.string().describe('Note ID'),
|
||||
pinned: z.boolean().describe('Whether to pin (true) or unpin (false)')
|
||||
@@ -257,7 +257,7 @@ export function createMcpServer(userId: number): McpServer {
|
||||
content: [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: `Note "${result.title}" ${pinned ? 'pinned' : 'unpinned'}`
|
||||
text: `Note "${result.title}" ${pinned ? 'favorited' : 'unfavorited'}`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -35,11 +35,11 @@ function sortNotes(list: Note[]): Note[] {
|
||||
});
|
||||
}
|
||||
|
||||
export const pinnedNotes = derived(filteredNotes, ($notes) =>
|
||||
export const favoriteNotes = derived(filteredNotes, ($notes) =>
|
||||
sortNotes($notes.filter((n) => n.pinned))
|
||||
);
|
||||
|
||||
export const unpinnedNotes = derived(filteredNotes, ($notes) =>
|
||||
export const unfavoritedNotes = derived(filteredNotes, ($notes) =>
|
||||
sortNotes($notes.filter((n) => !n.pinned))
|
||||
);
|
||||
|
||||
@@ -231,7 +231,7 @@ export async function unarchiveNote(id: string): Promise<Note | null> {
|
||||
return updateNote(id, { archived: false });
|
||||
}
|
||||
|
||||
export async function togglePin(id: string, currentPinned: boolean): Promise<Note | null> {
|
||||
export async function toggleFavorite(id: string, currentPinned: boolean): Promise<Note | null> {
|
||||
return updateNote(id, { pinned: !currentPinned });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { User, Cpu, ShieldCheck, SlidersHorizontal, Info } from 'lucide-svelte';
|
||||
import Icon from '@iconify/svelte';
|
||||
|
||||
let { data, children } = $props();
|
||||
let pathname = $derived(page.url.pathname);
|
||||
@@ -23,7 +23,7 @@
|
||||
class="flex items-center gap-2 rounded-sm px-3 py-2 text-sm whitespace-nowrap transition-colors {pathname === '/settings/preferences' ? 'bg-[var(--primary)]/15 text-[var(--primary)] font-medium' : 'text-[var(--text)] hover:bg-[var(--bg-base)]'}"
|
||||
data-testid="settings-nav-preferences"
|
||||
>
|
||||
<SlidersHorizontal size={16} />
|
||||
<Icon icon="pixelarticons:sliders" width={16} />
|
||||
Preferences
|
||||
</a>
|
||||
</li>
|
||||
@@ -32,7 +32,7 @@
|
||||
href="/settings/profile"
|
||||
class="flex items-center gap-2 rounded-sm px-3 py-2 text-sm whitespace-nowrap transition-colors {pathname === '/settings/profile' ? 'bg-[var(--primary)]/15 text-[var(--primary)] font-medium' : 'text-[var(--text)] hover:bg-[var(--bg-base)]'}"
|
||||
>
|
||||
<User size={16} />
|
||||
<Icon icon="pixelarticons:user" width={16} />
|
||||
Profile
|
||||
</a>
|
||||
</li>
|
||||
@@ -41,7 +41,7 @@
|
||||
href="/settings/mcp"
|
||||
class="flex items-center gap-2 rounded-sm px-3 py-2 text-sm whitespace-nowrap transition-colors {pathname === '/settings/mcp' ? 'bg-[var(--primary)]/15 text-[var(--primary)] font-medium' : 'text-[var(--text)] hover:bg-[var(--bg-base)]'}"
|
||||
>
|
||||
<Cpu size={16} />
|
||||
<Icon icon="pixelarticons:cpu" width={16} />
|
||||
API
|
||||
</a>
|
||||
</li>
|
||||
@@ -51,7 +51,7 @@
|
||||
href="/settings/users"
|
||||
class="flex items-center gap-2 rounded-sm px-3 py-2 text-sm whitespace-nowrap transition-colors {pathname === '/settings/users' ? 'bg-[var(--primary)]/15 text-[var(--primary)] font-medium' : 'text-[var(--text)] hover:bg-[var(--bg-base)]'}"
|
||||
>
|
||||
<ShieldCheck size={16} />
|
||||
<Icon icon="pixelarticons:shield" width={16} />
|
||||
Users
|
||||
</a>
|
||||
</li>
|
||||
@@ -61,7 +61,7 @@
|
||||
href="/settings/about"
|
||||
class="flex items-center gap-2 rounded-sm px-3 py-2 text-sm whitespace-nowrap transition-colors {pathname === '/settings/about' ? 'bg-[var(--primary)]/15 text-[var(--primary)] font-medium' : 'text-[var(--text)] hover:bg-[var(--bg-base)]'}"
|
||||
>
|
||||
<Info size={16} />
|
||||
<Icon icon="pixelarticons:info-box" width={16} />
|
||||
About
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Key, Copy, Trash2, Plus, Check } from 'lucide-svelte';
|
||||
import Icon from '@iconify/svelte';
|
||||
|
||||
interface ApiKey {
|
||||
id: string;
|
||||
@@ -118,7 +118,7 @@ ${mcpJsonConfig}`);
|
||||
<!-- API Keys -->
|
||||
<section class="mb-6 rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-surface)] p-6 shadow-[var(--card-shadow)]">
|
||||
<div class="mb-4 flex items-center gap-2">
|
||||
<Key size={16} class="text-[var(--primary)]" />
|
||||
<Icon icon="pixelarticons:lock" width={16} class="text-[var(--primary)]" />
|
||||
<h2 class="text-lg font-semibold text-[var(--text)]">API Keys</h2>
|
||||
</div>
|
||||
<p class="mb-4 text-xs text-[var(--text-muted)]">
|
||||
@@ -140,7 +140,7 @@ ${mcpJsonConfig}`);
|
||||
class="flex items-center gap-1 rounded-sm bg-[var(--primary)] px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-[var(--primary-hover)] disabled:opacity-50"
|
||||
data-testid="create-api-key-btn"
|
||||
>
|
||||
<Plus size={14} />
|
||||
<Icon icon="pixelarticons:plus" width={14} />
|
||||
Create
|
||||
</button>
|
||||
</form>
|
||||
@@ -167,9 +167,9 @@ ${mcpJsonConfig}`);
|
||||
data-testid="copy-key-btn"
|
||||
>
|
||||
{#if copiedKey}
|
||||
<Check size={14} class="text-[var(--success-text,#3a5a40)]" />
|
||||
<Icon icon="pixelarticons:check" width={14} class="text-[var(--success-text,#3a5a40)]" />
|
||||
{:else}
|
||||
<Copy size={14} />
|
||||
<Icon icon="pixelarticons:copy" width={14} />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
@@ -219,7 +219,7 @@ ${mcpJsonConfig}`);
|
||||
data-testid="delete-api-key-btn"
|
||||
title="Revoke key"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
<Icon icon="pixelarticons:trash" width={14} />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -253,9 +253,9 @@ ${mcpJsonConfig}`);
|
||||
title="Copy JSON config"
|
||||
>
|
||||
{#if copiedJson}
|
||||
<Check size={12} class="text-[var(--success-text,#3a5a40)]" />
|
||||
<Icon icon="pixelarticons:check" width={12} class="text-[var(--success-text,#3a5a40)]" />
|
||||
{:else}
|
||||
<Copy size={12} />
|
||||
<Icon icon="pixelarticons:copy" width={12} />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
@@ -279,9 +279,9 @@ ${mcpJsonConfig}`);
|
||||
title="Copy LLM prompt"
|
||||
>
|
||||
{#if copiedPrompt}
|
||||
<Check size={12} class="text-[var(--success-text,#3a5a40)]" />
|
||||
<Icon icon="pixelarticons:check" width={12} class="text-[var(--success-text,#3a5a40)]" />
|
||||
{:else}
|
||||
<Copy size={12} />
|
||||
<Icon icon="pixelarticons:copy" width={12} />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"info": {
|
||||
"title": "Crumbs API",
|
||||
"description": "Auto-generated API spec for crumbs",
|
||||
"version": "0.21.9",
|
||||
"version": "0.22.3",
|
||||
"contact": {
|
||||
"name": "Bretzel",
|
||||
"url": "https://bretzel.app"
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { test, expect, noteCard, createNote } from './helpers/fixtures.js';
|
||||
|
||||
test.describe('Organization Features', () => {
|
||||
test('Scenario: Pinned note appears under the Pinned section', async ({ authenticatedPage: page }) => {
|
||||
// Given a note titled "Pin Me" exists
|
||||
await createNote(page, 'Pin Me');
|
||||
test('Scenario: Favorited note appears under the Favorites section', async ({ authenticatedPage: page }) => {
|
||||
// Given a note titled "Favorite Me" exists
|
||||
await createNote(page, 'Favorite Me');
|
||||
|
||||
// When the user pins the note
|
||||
const pinCard = noteCard(page, 'Pin Me');
|
||||
await pinCard.hover();
|
||||
await pinCard.getByTestId('pin-btn').first().click({ force: true });
|
||||
// When the user favorites the note
|
||||
const favCard = noteCard(page, 'Favorite Me');
|
||||
await favCard.hover();
|
||||
await favCard.getByTestId('favorite-btn').first().click({ force: true });
|
||||
|
||||
// Then the "Pinned" section is visible
|
||||
await expect(page.getByText('Pinned')).toBeVisible();
|
||||
// Then the "Favorites" section is visible
|
||||
await expect(page.getByText('Favorites')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Scenario: Archived note is removed from the main view', async ({ authenticatedPage: page }) => {
|
||||
|
||||
Reference in New Issue
Block a user