fix: UI issues #23-25 + note card type + promote modal #26
+55
-1
@@ -3,8 +3,10 @@ package db
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
@@ -63,7 +65,7 @@ func (s *Store) migrate() error {
|
||||
pinned INTEGER NOT NULL DEFAULT 0,
|
||||
deleted_at TEXT,
|
||||
card_type TEXT
|
||||
CHECK (card_type IN ('snippet', 'template', 'checklist', 'decision', 'link')
|
||||
CHECK (card_type IN ('snippet', 'template', 'checklist', 'decision', 'link', 'note')
|
||||
OR card_type IS NULL),
|
||||
card_data TEXT,
|
||||
use_count INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -91,6 +93,58 @@ func (s *Store) migrate() error {
|
||||
s.db.Exec(`ALTER TABLE entities ADD COLUMN title TEXT`)
|
||||
s.db.Exec(`ALTER TABLE entities ADD COLUMN description TEXT`)
|
||||
|
||||
// Migrate CHECK constraint to include 'note' card type
|
||||
var needsMigrate bool
|
||||
row := s.db.QueryRow(`SELECT sql FROM sqlite_master WHERE type='table' AND name='entities'`)
|
||||
var ddl string
|
||||
if row.Scan(&ddl) == nil {
|
||||
hasNote := strings.Contains(ddl, "'link', 'note'")
|
||||
hasModified := strings.Contains(ddl, "modified_at")
|
||||
needsMigrate = !hasNote || !hasModified
|
||||
}
|
||||
if needsMigrate {
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if _, err := tx.Exec(`ALTER TABLE entities RENAME TO _entities_migrate`); err != nil {
|
||||
return fmt.Errorf("migrate rename: %w", err)
|
||||
}
|
||||
if _, err := tx.Exec(`CREATE TABLE entities (
|
||||
id TEXT PRIMARY KEY,
|
||||
created_at TEXT NOT NULL,
|
||||
modified_at TEXT NOT NULL,
|
||||
body TEXT NOT NULL,
|
||||
glyph TEXT NOT NULL
|
||||
CHECK (glyph IN ('todo', 'event', 'note')),
|
||||
time_anchor TEXT,
|
||||
completed_at TEXT,
|
||||
pinned INTEGER NOT NULL DEFAULT 0,
|
||||
deleted_at TEXT,
|
||||
card_type TEXT
|
||||
CHECK (card_type IN ('snippet', 'template', 'checklist', 'decision', 'link', 'note')
|
||||
OR card_type IS NULL),
|
||||
card_data TEXT,
|
||||
use_count INTEGER NOT NULL DEFAULT 0,
|
||||
last_used_at TEXT,
|
||||
title TEXT,
|
||||
description TEXT
|
||||
)`); err != nil {
|
||||
return fmt.Errorf("migrate create: %w", err)
|
||||
}
|
||||
if _, err := tx.Exec(`INSERT INTO entities SELECT * FROM _entities_migrate`); err != nil {
|
||||
return fmt.Errorf("migrate copy: %w", err)
|
||||
}
|
||||
if _, err := tx.Exec(`DROP TABLE _entities_migrate`); err != nil {
|
||||
return fmt.Errorf("migrate drop: %w", err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return fmt.Errorf("migrate commit: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ const (
|
||||
CardChecklist CardType = "checklist"
|
||||
CardDecision CardType = "decision"
|
||||
CardLink CardType = "link"
|
||||
CardNote CardType = "note"
|
||||
)
|
||||
|
||||
func ValidGlyph(s string) bool {
|
||||
@@ -39,7 +40,7 @@ func ValidGlyph(s string) bool {
|
||||
|
||||
func ValidCardType(s string) bool {
|
||||
switch CardType(s) {
|
||||
case CardSnippet, CardTemplate, CardChecklist, CardDecision, CardLink:
|
||||
case CardSnippet, CardTemplate, CardChecklist, CardDecision, CardLink, CardNote:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -15,6 +15,7 @@ var cardGlyphMap = map[db.CardType]string{
|
||||
db.CardChecklist: "☐",
|
||||
db.CardDecision: "⚖",
|
||||
db.CardLink: "↗",
|
||||
db.CardNote: "¶",
|
||||
}
|
||||
|
||||
func DisplayGlyph(glyph db.Glyph, cardType *db.CardType) string {
|
||||
|
||||
@@ -27,6 +27,8 @@ var validCardTypes = map[string]string{
|
||||
"checklist": "checklist",
|
||||
"decision": "decision",
|
||||
"link": "link",
|
||||
"note": "note",
|
||||
"n": "note",
|
||||
}
|
||||
|
||||
func Parse(input string) (*Result, error) {
|
||||
|
||||
+3
-3
@@ -4,14 +4,14 @@
|
||||
const GLYPHS = {
|
||||
note: '—', todo: '○', event: '◇', reminder: '△',
|
||||
snippet: '◆', template: '◈', checklist: '☐',
|
||||
decision: '⚖', link: '↗',
|
||||
decision: '⚖', link: '↗', note: '¶',
|
||||
};
|
||||
|
||||
const GLYPH_CLASSES = {
|
||||
note: 'glyph-note', todo: 'glyph-todo', event: 'glyph-event', reminder: 'glyph-reminder',
|
||||
snippet: 'glyph-snippet', template: 'glyph-template',
|
||||
checklist: 'glyph-checklist', decision: 'glyph-decision',
|
||||
link: 'glyph-link',
|
||||
link: 'glyph-link', note: 'glyph-note',
|
||||
};
|
||||
|
||||
const PAGE_SIZE = 50;
|
||||
@@ -121,7 +121,7 @@
|
||||
|
||||
// ========== Grammar parser (mirrors Go parser) ==========
|
||||
|
||||
const VALID_CARDS = { card: 'snippet', c: 'snippet', snippet: 'snippet', template: 'template', checklist: 'checklist', decision: 'decision', link: 'link' };
|
||||
const VALID_CARDS = { card: 'snippet', c: 'snippet', snippet: 'snippet', template: 'template', checklist: 'checklist', decision: 'decision', link: 'link', note: 'note', n: 'note' };
|
||||
|
||||
function validateTime(s) {
|
||||
const parts = s.split(':');
|
||||
|
||||
@@ -64,6 +64,11 @@
|
||||
<span class="type-name">decision</span>
|
||||
<span class="type-hint">record a choice + rationale</span>
|
||||
</button>
|
||||
<button data-type="note" class="type-btn">
|
||||
<span class="type-glyph glyph-note">¶</span>
|
||||
<span class="type-name">note</span>
|
||||
<span class="type-hint">readable markdown content</span>
|
||||
</button>
|
||||
<button data-type="link" class="type-btn">
|
||||
<span class="type-glyph glyph-link">↗</span>
|
||||
<span class="type-name">link</span>
|
||||
|
||||
@@ -462,6 +462,7 @@ main.focus-peek .resize-handle { visibility: hidden; }
|
||||
.glyph-checklist { color: var(--remind); }
|
||||
.glyph-decision { color: var(--note); }
|
||||
.glyph-link { color: var(--event); }
|
||||
.glyph-note { color: var(--note); }
|
||||
|
||||
.entity-content {
|
||||
flex: 1;
|
||||
|
||||
Reference in New Issue
Block a user