diff --git a/db.js b/db.js index 1875ff1..c84600d 100644 --- a/db.js +++ b/db.js @@ -59,6 +59,15 @@ try { db.exec(`ALTER TABLE fiddles ADD COLUMN screenshot TEXT`); } catch (_) { /* column already exists */ } +// Migration: add publishing columns +try { + db.exec(`ALTER TABLE fiddles ADD COLUMN published_slug TEXT`); +} catch (_) { /* column already exists */ } +try { + db.exec(`ALTER TABLE fiddles ADD COLUMN published_html TEXT`); +} catch (_) { /* column already exists */ } +db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_fiddles_published_slug ON fiddles(published_slug) WHERE published_slug IS NOT NULL`); + // Version history table db.exec(` CREATE TABLE IF NOT EXISTS fiddle_versions ( @@ -95,6 +104,21 @@ db.exec(` ) `); +// Presentation slides table +db.exec(` + CREATE TABLE IF NOT EXISTS fiddle_slides ( + id TEXT PRIMARY KEY, + fiddle_id TEXT NOT NULL, + slide_order INTEGER NOT NULL, + html TEXT DEFAULT '', + css TEXT DEFAULT '', + js TEXT DEFAULT '', + notes TEXT DEFAULT '', + FOREIGN KEY (fiddle_id) REFERENCES fiddles(id) ON DELETE CASCADE + ) +`); +db.exec(`CREATE INDEX IF NOT EXISTS idx_slides_fiddle ON fiddle_slides(fiddle_id, slide_order)`); + export const stmts = { insert: db.prepare(` INSERT INTO fiddles (id, title, html, css, css_type, js, js_type, listed, options) @@ -145,6 +169,12 @@ export const stmts = { // Screenshot updateScreenshot: db.prepare('UPDATE fiddles SET screenshot = ? WHERE id = ?'), + // Publishing + publishFiddle: db.prepare('UPDATE fiddles SET published_slug = @slug, published_html = @html WHERE id = @id'), + getPublishedFiddle: db.prepare('SELECT id, title, published_html FROM fiddles WHERE published_slug = ?'), + unpublishFiddle: db.prepare('UPDATE fiddles SET published_slug = NULL, published_html = NULL WHERE id = ?'), + getPublishStatus: db.prepare('SELECT published_slug FROM fiddles WHERE id = ?'), + // Collections insertCollection: db.prepare(` INSERT INTO collections (id, name, description) VALUES (@id, @name, @description) @@ -179,6 +209,19 @@ export const stmts = { JOIN collection_fiddles cf ON cf.collection_id = c.id WHERE cf.fiddle_id = ? `), + + // Slides + insertSlide: db.prepare(` + INSERT INTO fiddle_slides (id, fiddle_id, slide_order, html, css, js, notes) + VALUES (@id, @fiddle_id, @slide_order, @html, @css, @js, @notes) + `), + listSlides: db.prepare('SELECT * FROM fiddle_slides WHERE fiddle_id = ? ORDER BY slide_order'), + getSlide: db.prepare('SELECT * FROM fiddle_slides WHERE id = ?'), + updateSlide: db.prepare(` + UPDATE fiddle_slides SET html = @html, css = @css, js = @js, notes = @notes, slide_order = @slide_order WHERE id = @id + `), + deleteSlide: db.prepare('DELETE FROM fiddle_slides WHERE id = ?'), + getMaxSlideOrder: db.prepare('SELECT COALESCE(MAX(slide_order), 0) as max_order FROM fiddle_slides WHERE fiddle_id = ?'), }; /** diff --git a/public/css/style.css b/public/css/style.css index 1c945e7..8f71920 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -766,3 +766,50 @@ body.resizing iframe { pointer-events: none; } .npm-result-version { font-size: 10px; color: var(--text-dim); font-weight: 400; } .npm-result-desc { font-size: 11px; color: var(--text-dim); margin-top: 2px; } .npm-no-results { padding: 12px; text-align: center; color: var(--text-dim); font-size: 12px; } + +/* Slide list (presentation manager) */ +.slide-list { max-height: 300px; overflow-y: auto; } +.slide-item { + display: flex; align-items: center; gap: 8px; padding: 8px 16px; + border-bottom: 1px solid var(--border); font-size: 12px; +} +.slide-item:hover { background: rgba(255,255,255,0.03); } +.slide-number { + width: 24px; height: 24px; border-radius: 50%; background: var(--accent); + color: #fff; display: flex; align-items: center; justify-content: center; + font-size: 11px; font-weight: 600; flex-shrink: 0; +} +.slide-preview-text { flex: 1; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.slide-delete-btn { flex-shrink: 0; } + +/* Presentation overlay (fullscreen) */ +.presentation-overlay { + position: fixed; inset: 0; z-index: 10000; + background: #111; display: flex; flex-direction: column; +} +.presentation-overlay.hidden { display: none; } +.pres-header { + display: flex; align-items: center; justify-content: space-between; + padding: 8px 16px; background: #1a1a1a; border-bottom: 1px solid #333; +} +.pres-counter { color: #aaa; font-size: 13px; font-weight: 500; } +.pres-exit-btn { + background: transparent; color: #aaa; border: 1px solid #444; + padding: 4px 12px; font-size: 12px; border-radius: 4px; +} +.pres-exit-btn:hover { background: #333; color: #fff; } +.pres-content { flex: 1; display: flex; overflow: hidden; } +.pres-iframe { width: 100%; height: 100%; border: none; background: #fff; } +.pres-footer { + display: flex; align-items: center; gap: 12px; + padding: 8px 16px; background: #1a1a1a; border-top: 1px solid #333; +} +.pres-nav-btn { + background: transparent; color: #aaa; border: 1px solid #444; + padding: 6px 16px; font-size: 12px; border-radius: 4px; flex-shrink: 0; +} +.pres-nav-btn:hover { background: #333; color: #fff; } +.pres-notes { + flex: 1; color: #888; font-size: 12px; font-style: italic; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: center; +} diff --git a/public/index.html b/public/index.html index 52d540f..50b7d9b 100644 --- a/public/index.html +++ b/public/index.html @@ -19,6 +19,7 @@ +
@@ -54,6 +55,9 @@ + @@ -63,12 +67,18 @@ + + @@ -306,6 +316,38 @@
+ + + +