This commit is contained in:
Stefan ATZLESBERGER
2026-04-16 16:05:22 +02:00
parent 14ed445328
commit c2b20a9f10
11 changed files with 326 additions and 14 deletions
+35 -9
View File
@@ -18,6 +18,7 @@ export type SnippetRow = {
sort_order: number;
created_at: string;
updated_at: string;
deadline_at: string | null;
tags: string[];
/** Anzeige-Label pro Tag (Prefix + Name), gleiche Reihenfolge wie `tags`. */
tag_display: string[];
@@ -25,6 +26,17 @@ export type SnippetRow = {
tag_colors: Record<string, string | null>;
};
/** PATCH/POST: `undefined` = nicht ändern, `null` = entfernen. */
export function normalizeDeadlineAtInput(raw: unknown): string | null | undefined {
if (raw === undefined) return undefined;
if (raw === null) return null;
if (typeof raw !== "string") throw new Error("deadline_at muss ein String (YYYY-MM-DD) sein");
const t = raw.trim();
if (t === "") return null;
if (!/^\d{4}-\d{2}-\d{2}$/.test(t)) throw new Error("deadline_at muss YYYY-MM-DD sein");
return t;
}
/** `tag:x`, `title:y`, `content:z` (case-insensitive); remainder = default (title OR tag names, LIKE). All parts AND. */
function parseSearchQuery(raw: string): {
defaultText: string | null;
@@ -69,6 +81,10 @@ function orderClause(sort: string): string {
return "ORDER BY s.title COLLATE NOCASE ASC NULLS LAST, s.updated_at DESC";
case "title_desc":
return "ORDER BY s.title COLLATE NOCASE DESC NULLS LAST, s.updated_at DESC";
case "deadline_asc":
return "ORDER BY s.deadline_at ASC NULLS LAST, s.updated_at DESC";
case "deadline_desc":
return "ORDER BY s.deadline_at DESC NULLS LAST, s.updated_at DESC";
case "manual":
return "ORDER BY s.sort_order ASC, s.updated_at DESC";
case "updated_desc":
@@ -107,6 +123,7 @@ function rowToPublic(
sort_order: number;
created_at: string;
updated_at: string;
deadline_at: string | null;
},
tagsRaw: string[],
): SnippetRow {
@@ -124,6 +141,7 @@ function rowToPublic(
sort_order: row.sort_order,
created_at: row.created_at,
updated_at: row.updated_at,
deadline_at: row.deadline_at,
tags,
tag_display,
tag_colors,
@@ -142,12 +160,13 @@ export function listSnippets(q: string | null, sort: string | null): SnippetRow[
sort_order: number;
created_at: string;
updated_at: string;
deadline_at: string | null;
}[];
if (trimmed === "") {
rows = database
.query(
`SELECT s.id, s.title, s.body, s.sort_order, s.created_at, s.updated_at
`SELECT s.id, s.title, s.body, s.sort_order, s.created_at, s.updated_at, s.deadline_at
FROM snippets s
${order}`,
)
@@ -229,7 +248,7 @@ export function listSnippets(q: string | null, sort: string | null): SnippetRow[
if (conditions.length === 0) {
rows = database
.query(
`SELECT s.id, s.title, s.body, s.sort_order, s.created_at, s.updated_at
`SELECT s.id, s.title, s.body, s.sort_order, s.created_at, s.updated_at, s.deadline_at
FROM snippets s
${order}`,
)
@@ -238,7 +257,7 @@ export function listSnippets(q: string | null, sort: string | null): SnippetRow[
const where = conditions.join(" AND ");
rows = database
.query(
`SELECT s.id, s.title, s.body, s.sort_order, s.created_at, s.updated_at
`SELECT s.id, s.title, s.body, s.sort_order, s.created_at, s.updated_at, s.deadline_at
FROM snippets s
WHERE ${where}
${order}`,
@@ -256,7 +275,7 @@ export function getSnippetById(id: number): SnippetRow | null {
const database = getDb();
const row = database
.query(
`SELECT id, title, body, sort_order, created_at, updated_at FROM snippets WHERE id = ?`,
`SELECT id, title, body, sort_order, created_at, updated_at, deadline_at FROM snippets WHERE id = ?`,
)
.get(id) as
| {
@@ -266,6 +285,7 @@ export function getSnippetById(id: number): SnippetRow | null {
sort_order: number;
created_at: string;
updated_at: string;
deadline_at: string | null;
}
| undefined;
if (!row) return null;
@@ -300,6 +320,7 @@ export function createSnippet(input: {
body?: string;
tags?: string[];
sort_order?: number;
deadline_at?: string | null;
}): SnippetRow {
const database = getDb();
const t = nowIso();
@@ -311,10 +332,12 @@ export function createSnippet(input: {
: String(input.title).trim();
const body = input.body ?? "";
const sortOrder = typeof input.sort_order === "number" ? input.sort_order : Date.now();
const deadlineAt =
input.deadline_at === undefined ? null : input.deadline_at === null ? null : String(input.deadline_at).trim() || null;
database.run(
`INSERT INTO snippets (title, body, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?)`,
[title, body, sortOrder, t, t],
`INSERT INTO snippets (title, body, sort_order, created_at, updated_at, deadline_at) VALUES (?, ?, ?, ?, ?, ?)`,
[title, body, sortOrder, t, t, deadlineAt],
);
const idRow = database.query(`SELECT last_insert_rowid() AS id`).get() as { id: number };
const newId = Number(idRow.id);
@@ -329,6 +352,7 @@ export function updateSnippet(
body?: string;
tags?: string[];
sort_order?: number;
deadline_at?: string | null;
},
): SnippetRow | null {
const database = getDb();
@@ -336,11 +360,12 @@ export function updateSnippet(
if (!existing) return null;
const current = database
.query(`SELECT title, body, sort_order FROM snippets WHERE id = ?`)
.query(`SELECT title, body, sort_order, deadline_at FROM snippets WHERE id = ?`)
.get(id) as {
title: string | null;
body: string;
sort_order: number;
deadline_at: string | null;
};
const title =
@@ -351,11 +376,12 @@ export function updateSnippet(
: String(input.title).trim();
const body = input.body === undefined ? current.body : String(input.body);
const sort_order = input.sort_order === undefined ? current.sort_order : input.sort_order;
const deadline_at = input.deadline_at === undefined ? current.deadline_at : input.deadline_at;
const t = nowIso();
database.run(
`UPDATE snippets SET title = ?, body = ?, sort_order = ?, updated_at = ? WHERE id = ?`,
[title, body, sort_order, t, id],
`UPDATE snippets SET title = ?, body = ?, sort_order = ?, deadline_at = ?, updated_at = ? WHERE id = ?`,
[title, body, sort_order, deadline_at, t, id],
);
if (input.tags !== undefined) {