Add browse dashboard, tags, visibility control, export, QR sharing, and embed mode
- Browse dashboard at / with search, framework filter, tag pills, and pagination - Tags system with autocomplete datalist and per-fiddle tag management - Listed/unlisted toggle for visibility control (unlisted still accessible via direct URL) - Export standalone HTML with inlined CSS/JS and framework CDN tags - QR code modal for sharing fiddle URLs - Embed mode at /embed/:id for minimal preview-only rendering - Extract shared loadScript() utility from 4 files into utils.js - Database schema: listed column, tags and fiddle_tags tables with index
This commit is contained in:
63
db.js
63
db.js
@@ -26,18 +26,75 @@ try {
|
||||
db.exec(`ALTER TABLE fiddles ADD COLUMN js_type TEXT NOT NULL DEFAULT 'javascript'`);
|
||||
} catch (_) { /* column already exists */ }
|
||||
|
||||
// Migration: add listed column
|
||||
try {
|
||||
db.exec(`ALTER TABLE fiddles ADD COLUMN listed INTEGER NOT NULL DEFAULT 1`);
|
||||
} catch (_) { /* column already exists */ }
|
||||
|
||||
// Tags tables
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS tags (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE COLLATE NOCASE
|
||||
)
|
||||
`);
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS fiddle_tags (
|
||||
fiddle_id TEXT NOT NULL REFERENCES fiddles(id) ON DELETE CASCADE,
|
||||
tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (fiddle_id, tag_id)
|
||||
)
|
||||
`);
|
||||
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_fiddles_listed_updated ON fiddles(listed, updated_at DESC)`);
|
||||
|
||||
export const stmts = {
|
||||
insert: db.prepare(`
|
||||
INSERT INTO fiddles (id, title, html, css, css_type, js, js_type)
|
||||
VALUES (@id, @title, @html, @css, @css_type, @js, @js_type)
|
||||
INSERT INTO fiddles (id, title, html, css, css_type, js, js_type, listed)
|
||||
VALUES (@id, @title, @html, @css, @css_type, @js, @js_type, @listed)
|
||||
`),
|
||||
get: db.prepare('SELECT * FROM fiddles WHERE id = ?'),
|
||||
update: db.prepare(`
|
||||
UPDATE fiddles SET title = @title, html = @html, css = @css,
|
||||
css_type = @css_type, js = @js, js_type = @js_type, updated_at = datetime('now')
|
||||
css_type = @css_type, js = @js, js_type = @js_type, listed = @listed,
|
||||
updated_at = datetime('now')
|
||||
WHERE id = @id
|
||||
`),
|
||||
list: db.prepare('SELECT id, title, css_type, js_type, created_at, updated_at FROM fiddles ORDER BY updated_at DESC LIMIT 50'),
|
||||
|
||||
// Tags
|
||||
getTagsForFiddle: db.prepare(`
|
||||
SELECT t.id, t.name FROM tags t
|
||||
JOIN fiddle_tags ft ON ft.tag_id = t.id
|
||||
WHERE ft.fiddle_id = ?
|
||||
`),
|
||||
insertTag: db.prepare('INSERT OR IGNORE INTO tags (name) VALUES (?)'),
|
||||
getTagByName: db.prepare('SELECT id, name FROM tags WHERE name = ? COLLATE NOCASE'),
|
||||
insertFiddleTag: db.prepare('INSERT OR IGNORE INTO fiddle_tags (fiddle_id, tag_id) VALUES (?, ?)'),
|
||||
deleteFiddleTags: db.prepare('DELETE FROM fiddle_tags WHERE fiddle_id = ?'),
|
||||
listTags: db.prepare(`
|
||||
SELECT t.id, t.name, COUNT(ft.fiddle_id) as count
|
||||
FROM tags t
|
||||
LEFT JOIN fiddle_tags ft ON ft.tag_id = t.id
|
||||
GROUP BY t.id
|
||||
HAVING count > 0
|
||||
ORDER BY count DESC
|
||||
`),
|
||||
};
|
||||
|
||||
/**
|
||||
* Upsert tags for a fiddle. Accepts an array of tag name strings.
|
||||
*/
|
||||
export function setFiddleTags(fiddleId, tagNames) {
|
||||
stmts.deleteFiddleTags.run(fiddleId);
|
||||
for (const name of tagNames) {
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed) continue;
|
||||
stmts.insertTag.run(trimmed);
|
||||
const tag = stmts.getTagByName.get(trimmed);
|
||||
if (tag) stmts.insertFiddleTag.run(fiddleId, tag.id);
|
||||
}
|
||||
}
|
||||
|
||||
export default db;
|
||||
|
||||
Reference in New Issue
Block a user