- Tailwind CSS: toolbar checkbox injects Play CDN into preview, persisted per-fiddle via new options JSON column - Markdown mode: uses marked.js CDN, renders markdown to HTML preview with CSS tab for custom styling - WASM mode: starter template with inline WebAssembly add function, supports top-level await via module detection - npm imports: auto-detect bare import specifiers in module code and inject importmap pointing to esm.sh CDN - Module auto-detection for html-css-js mode (import/export statements) - DB migration adds options column, server passes through all API endpoints - All features work across preview, export, and embed
106 lines
3.4 KiB
JavaScript
106 lines
3.4 KiB
JavaScript
import Database from 'better-sqlite3';
|
|
import { mkdirSync } from 'fs';
|
|
|
|
mkdirSync('data', { recursive: true });
|
|
|
|
const db = new Database('data/fiddles.db');
|
|
db.pragma('journal_mode = WAL');
|
|
db.pragma('foreign_keys = ON');
|
|
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS fiddles (
|
|
id TEXT PRIMARY KEY,
|
|
title TEXT NOT NULL DEFAULT 'Untitled',
|
|
html TEXT NOT NULL DEFAULT '',
|
|
css TEXT NOT NULL DEFAULT '',
|
|
css_type TEXT NOT NULL DEFAULT 'css',
|
|
js TEXT NOT NULL DEFAULT '',
|
|
js_type TEXT NOT NULL DEFAULT 'javascript',
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
)
|
|
`);
|
|
|
|
// Migration: add js_type column for existing databases
|
|
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 */ }
|
|
|
|
// Migration: add options column (JSON string for per-fiddle settings like tailwind)
|
|
try {
|
|
db.exec(`ALTER TABLE fiddles ADD COLUMN options TEXT NOT NULL DEFAULT '{}'`);
|
|
} 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, listed, options)
|
|
VALUES (@id, @title, @html, @css, @css_type, @js, @js_type, @listed, @options)
|
|
`),
|
|
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, listed = @listed,
|
|
options = @options, 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;
|