- Multiple named notes with sidebar (persisted in localStorage + D1) - Dark mode toggle, persisted - Markdown preview toggle (Ctrl+M) with styled rendering - Drawing: eraser tool, undo/redo (40-step stack) - Auto-save indicator, word/char count status bar - Export note as .txt download - Keyboard shortcuts: Ctrl+S save, Ctrl+D draw, Ctrl+M markdown, Ctrl+Shift+N new note - D1 backend (noted-db) with Pages Functions API for cross-device sync - Share note: generates read-only link at /share/:token - Updated service worker to v2, skip API routes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
81 lines
3.7 KiB
JavaScript
81 lines
3.7 KiB
JavaScript
const CORS = {
|
|
'Content-Type': 'application/json',
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
|
|
'Access-Control-Allow-Headers': 'Content-Type',
|
|
};
|
|
|
|
function json(data, status = 200) {
|
|
return new Response(JSON.stringify(data), { status, headers: CORS });
|
|
}
|
|
|
|
export async function onRequest({ request, env }) {
|
|
if (request.method === 'OPTIONS') return new Response(null, { headers: CORS });
|
|
|
|
const url = new URL(request.url);
|
|
const path = url.pathname.replace(/^\/api/, '');
|
|
const method = request.method;
|
|
|
|
try {
|
|
// GET /notes?user_id=X
|
|
if (method === 'GET' && path === '/notes') {
|
|
const userId = url.searchParams.get('user_id');
|
|
if (!userId) return json({ error: 'missing user_id' }, 400);
|
|
const { results } = await env.DB.prepare(
|
|
'SELECT id, user_id, title, content, drawing, font_size, font_family, text_color, created_at as createdAt, updated_at as updatedAt FROM notes WHERE user_id = ? ORDER BY updated_at DESC'
|
|
).bind(userId).all();
|
|
return json({ notes: results });
|
|
}
|
|
|
|
// PUT /notes/:id
|
|
if (method === 'PUT' && /^\/notes\/[^/]+$/.test(path)) {
|
|
const id = path.split('/')[2];
|
|
const { user_id, title, content, drawing, fontSize, fontFamily, textColor, createdAt, updatedAt } = await request.json();
|
|
await env.DB.prepare(`
|
|
INSERT INTO notes (id, user_id, title, content, drawing, font_size, font_family, text_color, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON CONFLICT(id) DO UPDATE SET
|
|
title = excluded.title, content = excluded.content, drawing = excluded.drawing,
|
|
font_size = excluded.font_size, font_family = excluded.font_family, text_color = excluded.text_color,
|
|
updated_at = excluded.updated_at
|
|
WHERE notes.user_id = excluded.user_id
|
|
`).bind(id, user_id, title || '', content || '', drawing || null, fontSize || null, fontFamily || null, textColor || null, createdAt || Date.now(), updatedAt || Date.now()).run();
|
|
return json({ ok: true });
|
|
}
|
|
|
|
// DELETE /notes/:id
|
|
if (method === 'DELETE' && /^\/notes\/[^/]+$/.test(path)) {
|
|
const id = path.split('/')[2];
|
|
const { user_id } = await request.json();
|
|
await env.DB.prepare('DELETE FROM notes WHERE id = ? AND user_id = ?').bind(id, user_id).run();
|
|
return json({ ok: true });
|
|
}
|
|
|
|
// POST /notes/:id/share
|
|
if (method === 'POST' && /^\/notes\/[^/]+\/share$/.test(path)) {
|
|
const noteId = path.split('/')[2];
|
|
const { user_id } = await request.json();
|
|
const note = await env.DB.prepare('SELECT id FROM notes WHERE id = ? AND user_id = ?').bind(noteId, user_id).first();
|
|
if (!note) return json({ error: 'not found' }, 404);
|
|
const existing = await env.DB.prepare('SELECT token FROM shares WHERE note_id = ?').bind(noteId).first();
|
|
if (existing) return json({ token: existing.token });
|
|
const token = crypto.randomUUID().replace(/-/g, '');
|
|
await env.DB.prepare('INSERT INTO shares (token, note_id, user_id, created_at) VALUES (?, ?, ?, ?)').bind(token, noteId, user_id, Date.now()).run();
|
|
return json({ token });
|
|
}
|
|
|
|
// GET /share/:token
|
|
if (method === 'GET' && /^\/share\/[^/]+$/.test(path)) {
|
|
const token = path.split('/')[2];
|
|
const share = await env.DB.prepare('SELECT note_id FROM shares WHERE token = ?').bind(token).first();
|
|
if (!share) return json({ error: 'not found' }, 404);
|
|
const note = await env.DB.prepare('SELECT title, content FROM notes WHERE id = ?').bind(share.note_id).first();
|
|
if (!note) return json({ error: 'note not found' }, 404);
|
|
return json({ note });
|
|
}
|
|
|
|
return json({ error: 'not found' }, 404);
|
|
} catch (e) {
|
|
return json({ error: e.message }, 500);
|
|
}
|
|
}
|