feat(cards): add 'note' card type for readable markdown content
New card type renders body as styled markdown with no copy/fill/run affordance. Glyph: ¶, color: --note. Migration uses transaction to safely rebuild table constraint. Checks both 'note' presence and modified_at column to catch partial migration state.
This commit is contained in:
+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
|
||||
|
||||
Reference in New Issue
Block a user