From dad87a66029e699979e05375db49d80187c9346e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jok=C5=ABbas=20Banaitis?= Date: Thu, 7 May 2026 18:33:56 +0300 Subject: [PATCH 1/6] Display a badge for crew member comments --- messages/en.json | 3 +- messages/fi.json | 3 +- messages/lt.json | 3 +- .../components/common/AddCommentForm.svelte | 42 ++++++++++++++++-- src/lib/components/common/Comment.svelte | 12 ++++- src/lib/server/actions/comment.actions.ts | 44 +++++++++++++------ src/lib/server/db/queries/comments.ts | 1 + src/lib/server/db/schema.ts | 1 + src/lib/server/db/validations.ts | 1 + 9 files changed, 89 insertions(+), 21 deletions(-) diff --git a/messages/en.json b/messages/en.json index 81a014c..213407b 100644 --- a/messages/en.json +++ b/messages/en.json @@ -57,7 +57,8 @@ "select_image": "Select an image", "upload": "Upload", "uploading": "Uploading...", - "clear_image": "Clear image" + "clear_image": "Clear image", + "post_as_crew": "Comment as a crew member" }, "draft": "DRAFT / NOT VISIBLE", "submit": "post", diff --git a/messages/fi.json b/messages/fi.json index 52211a5..439d120 100644 --- a/messages/fi.json +++ b/messages/fi.json @@ -57,7 +57,8 @@ "select_image": "Valitse kuva", "upload": "Lataa", "uploading": "Ladataan...", - "clear_image": "Tyhjennä kuva" + "clear_image": "Tyhjennä kuva", + "post_as_crew": "Julkaise miehistön jäsenenä" }, "draft": "LUONNOS / EI NÄKYVISSÄ", "submit": "lähetä", diff --git a/messages/lt.json b/messages/lt.json index 0b53dda..b62d0d4 100644 --- a/messages/lt.json +++ b/messages/lt.json @@ -57,7 +57,8 @@ "select_image": "Pasirinkti nuotrauką", "upload": "Įkelti", "uploading": "Įkeliama...", - "clear_image": "Panaikinti" + "clear_image": "Panaikinti", + "post_as_crew": "Rodyti chebrytės ženklelį" }, "draft": "nepaskelbta / viešai nematoma", "submit": "būpt", diff --git a/src/lib/components/common/AddCommentForm.svelte b/src/lib/components/common/AddCommentForm.svelte index abeecae..9204f2b 100644 --- a/src/lib/components/common/AddCommentForm.svelte +++ b/src/lib/components/common/AddCommentForm.svelte @@ -4,8 +4,12 @@ import FieldError from "./FieldError.svelte"; import { faCommentDots } from "@fortawesome/free-solid-svg-icons"; import { m } from "$lib/paraglide/messages.js"; + import { page } from "$app/state"; let { form } = $props(); + + let isPostingAsCrew = $derived(page.data.user); + let author = $derived(isPostingAsCrew ? page.data.user?.username : null);

{m.add_comment()}

@@ -17,7 +21,16 @@ use:enhance > - +
+ +
- - - + {#if !page.data.user} + + + + {:else} + + {/if}
diff --git a/src/lib/components/common/Comment.svelte b/src/lib/components/common/Comment.svelte index 439c0a5..4cd6995 100644 --- a/src/lib/components/common/Comment.svelte +++ b/src/lib/components/common/Comment.svelte @@ -2,17 +2,24 @@ import { enhance } from "$app/forms"; import { page } from "$app/state"; import type { CommentComponent } from "$lib/types"; - import { faBan, faGavel, faTrash } from "@fortawesome/free-solid-svg-icons"; + import { + faBan, + faGavel, + faTrash, + faUserShield, + } from "@fortawesome/free-solid-svg-icons"; import Fa from "svelte-fa"; import { slide } from "svelte/transition"; import { m } from "$lib/paraglide/messages.js"; import { SvelteDate } from "svelte/reactivity"; let { ...comment }: CommentComponent = $props(); + let date: Date = $derived(new SvelteDate(comment.date)); let confirmDelete: boolean = $state(false); let confirmBlock: boolean = $state(false); let isIpBanned: boolean | undefined = $derived(comment.isIpBanned); + let authorIsCrew: boolean | undefined = $derived(comment.authorIsCrew); function banUser() { return async ({ update, result }) => { @@ -30,6 +37,9 @@ {#if page.data.user && isIpBanned} {/if} + {#if authorIsCrew} + + {/if} => { }; export const commentActions = { - add_comment: async ({ request, params, route, getClientAddress }) => { + add_comment: async ({ request, locals, params, route, getClientAddress }) => { const parentRoute = route.id.split("/")[1]; + const formData: FormData = await request.formData(); + + // Resolve the parent entity (blog post or event) and append its ID to the form data let postId: number | undefined; let eventId: number | undefined; - const formData: FormData = await request.formData(); + switch (parentRoute) { case "blog": { postId = await queryPostId(params.slug); @@ -69,30 +72,45 @@ export const commentActions = { break; } } - const ipAddress: string = getClientAddress(); + if (!locals.session && formData.get("authorIsCrew") === "on") { + return fail(401); + } + + // Append acab flag to the form data if user is logged in + if (locals.session) formData.append("acab", "1312"); + + // Append the client's IP address and parse the form data into a flat object + const ipAddress = getClientAddress(); formData.append("ipAddress", ipAddress); - const data: object = Object.fromEntries(formData.entries()); + const data = Object.fromEntries(formData.entries()); + + if (!locals.session && data.authorIsCrew === "on") { + return fail(401); + } + try { const comment = commentInsertSchema.parse(data); - const lookupResult = await db + + // Reject the comment if the IP is banned + const [bannedEntry] = await db .select() .from(table.bannedIp) - .where(eq(table.bannedIp.ipAddress, ipAddress as string)); - const isIpBanned = lookupResult.at(0); - if (isIpBanned) { + .where(eq(table.bannedIp.ipAddress, ipAddress)); + + if (bannedEntry) { return fail(403, { errors: { submit: ["Something went wrong"] } }); - } else { - await db.insert(table.comment).values(comment); } + + // Insert the comment into the database + await db.insert(table.comment).values(comment); } catch (err) { if (err instanceof z.ZodError) { const { fieldErrors: errors } = z.flattenError(err); return fail(400, { errors }); - } else { - console.error(err); - return error(500, "Something went wrong"); } + console.error(err); + return error(500, "Something went wrong"); } }, diff --git a/src/lib/server/db/queries/comments.ts b/src/lib/server/db/queries/comments.ts index 493fcf4..d95a470 100644 --- a/src/lib/server/db/queries/comments.ts +++ b/src/lib/server/db/queries/comments.ts @@ -19,6 +19,7 @@ const loadComments = async ( ...(locals.user && { isIpBanned: isNotNull(table.bannedIp.ipAddress), }), + authorIsCrew: table.comment.authorIsCrew, }) .from(table.comment) .innerJoin(parentTable, eq(parentId, parentTable.id)) diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index 60f0196..a849c96 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -63,6 +63,7 @@ export const comment = pgTable("comment", { date: timestamp("date", { withTimezone: true }).notNull().defaultNow(), content: text("content").notNull(), ipAddress: text("ip_address"), + authorIsCrew: boolean("is_crew").default(false).notNull(), }); export const bannedIp = pgTable("banned_ip", { diff --git a/src/lib/server/db/validations.ts b/src/lib/server/db/validations.ts index 9cc988d..579e23d 100644 --- a/src/lib/server/db/validations.ts +++ b/src/lib/server/db/validations.ts @@ -29,6 +29,7 @@ export const commentInsertSchema = createInsertSchema(comment, { }), eventId: z.coerce.number().optional(), postId: z.coerce.number().optional(), + authorIsCrew: z.coerce.boolean(), }) .extend({ acab: z.literal("1312", { From 180b4de66dc0687d03d80a2c5a65a319bba4ff5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jok=C5=ABbas=20Banaitis?= Date: Thu, 7 May 2026 20:19:09 +0300 Subject: [PATCH 2/6] Switch to Lucide icons --- package-lock.json | 92 ++++++++----------- package.json | 2 +- src/app.css | 7 ++ src/app.d.ts | 4 - src/lib/components/blog/Post.svelte | 5 +- src/lib/components/blog/PostEntryForm.svelte | 9 +- .../components/common/AddCommentForm.svelte | 5 +- src/lib/components/common/Comment.svelte | 20 ++-- .../components/common/CopyTextButton.svelte | 6 +- .../components/common/RemoveItemForm.svelte | 5 +- src/lib/components/common/ThemeToggle.svelte | 7 +- src/lib/components/crew/BannedAddress.svelte | 5 +- src/lib/components/crew/CrewMenu.svelte | 15 +-- src/lib/components/events/Event.svelte | 13 +-- .../components/events/EventEntryForm.svelte | 9 +- src/routes/+layout.svelte | 2 +- src/routes/+page.svelte | 6 +- src/routes/blog/+page.svelte | 7 +- src/routes/events/+page.svelte | 22 ++--- 19 files changed, 100 insertions(+), 141 deletions(-) diff --git a/package-lock.json b/package-lock.json index b8fc807..3bbd0af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,9 @@ "version": "1.0.0", "dependencies": { "@fontsource/courier-prime": "^5.2.8", - "@fortawesome/free-solid-svg-icons": "^7.2.0", "@friendofsvelte/toggle": "^0.0.4-svelte.5", "@inlang/paraglide-js": "^2.15.3", + "@lucide/svelte": "^1.14.0", "@node-rs/argon2": "^2.0.2", "@oslojs/crypto": "^1.0.1", "@oslojs/encoding": "^1.1.0", @@ -742,27 +742,6 @@ "url": "https://github.com/sponsors/ayuhito" } }, - "node_modules/@fortawesome/fontawesome-common-types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.2.0.tgz", - "integrity": "sha512-IpR0bER9FY25p+e7BmFH25MZKEwFHTfRAfhOyJubgiDnoJNsSvJ7nigLraHtp4VOG/cy8D7uiV0dLkHOne5Fhw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-7.2.0.tgz", - "integrity": "sha512-YTVITFGN0/24PxzXrwqCgnyd7njDuzp5ZvaCx5nq/jg55kUYd94Nj8UTchBdBofi/L0nwRfjGOg0E41d2u9T1w==", - "license": "(CC-BY-4.0 AND MIT)", - "dependencies": { - "@fortawesome/fontawesome-common-types": "7.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@friendofsvelte/toggle": { "version": "0.0.4-svelte.5", "resolved": "https://registry.npmjs.org/@friendofsvelte/toggle/-/toggle-0.0.4-svelte.5.tgz", @@ -960,6 +939,15 @@ "integrity": "sha512-jBeALB6prAbtr5q4vTuxnRZZv1M2rKe8iNqRQhFJ4Tv7150unEa0vKyz0hs8Gl3fUGsWaNJBh3J8++fpbrpRBQ==", "license": "Apache-2.0" }, + "node_modules/@lucide/svelte": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-1.14.0.tgz", + "integrity": "sha512-MVuP5VRCBQa2OaIpaRbuEV4k5OV2dy9MyxA6Tf4Sz/IN0v3zzUU72ObHc9r2Zn/wSMXDg6lLrHrczqI7w7gCzQ==", + "license": "ISC", + "peerDependencies": { + "svelte": "^5" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.5.tgz", @@ -2160,9 +2148,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.57.1", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.57.1.tgz", - "integrity": "sha512-VRdSbB96cI1EnRh09CqmnQqP/YJvET5buj8S6k7CxaJqBJD4bw4fRKDjcarAj/eX9k2eHifQfDH8NtOh+ZxxPw==", + "version": "2.59.1", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.59.1.tgz", + "integrity": "sha512-d8OON70AphLdDesuTIl//M2O6fRTIicX8aYv8vhCiYEhTTI2OboKqey0Hu1A4VFhqwgqtq0vKDmPFGkw8kKmgw==", "dev": true, "license": "MIT", "dependencies": { @@ -2638,9 +2626,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -2719,9 +2707,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -4096,9 +4084,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -4480,9 +4468,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -5072,9 +5060,9 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -5711,9 +5699,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -5955,9 +5943,9 @@ } }, "node_modules/postcss": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", - "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", "dev": true, "funding": [ { @@ -6014,9 +6002,9 @@ } }, "node_modules/postcss-load-config/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", "dev": true, "license": "ISC", "engines": { @@ -7446,9 +7434,9 @@ "license": "MIT" }, "node_modules/uuid": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", - "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.2.tgz", + "integrity": "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" diff --git a/package.json b/package.json index 5da17a0..5d128c5 100644 --- a/package.json +++ b/package.json @@ -46,9 +46,9 @@ }, "dependencies": { "@fontsource/courier-prime": "^5.2.8", - "@fortawesome/free-solid-svg-icons": "^7.2.0", "@friendofsvelte/toggle": "^0.0.4-svelte.5", "@inlang/paraglide-js": "^2.15.3", + "@lucide/svelte": "^1.14.0", "@node-rs/argon2": "^2.0.2", "@oslojs/crypto": "^1.0.1", "@oslojs/encoding": "^1.1.0", diff --git a/src/app.css b/src/app.css index c21e87a..ea1966e 100644 --- a/src/app.css +++ b/src/app.css @@ -286,3 +286,10 @@ div.text-block { p.align-right { text-align: right; } + +.lucide { + vertical-align: text-bottom; + max-width: 1.25em; + max-height: 1.25em; + align-self: center; +} diff --git a/src/app.d.ts b/src/app.d.ts index e5a227d..f303d8d 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -20,8 +20,4 @@ declare global { } } -declare module "@fortawesome/pro-solid-svg-icons/index.es" { - export * from "@fortawesome/pro-solid-svg-icons"; -} - export {}; diff --git a/src/lib/components/blog/Post.svelte b/src/lib/components/blog/Post.svelte index 40ea965..1cd3aa0 100644 --- a/src/lib/components/blog/Post.svelte +++ b/src/lib/components/blog/Post.svelte @@ -3,15 +3,14 @@ import { page } from "$app/state"; import { RemoveItemForm, CommentCount, PostEntryForm } from "$lib/components"; import type { PostComponent } from "$lib/types"; - import { faPenToSquare } from "@fortawesome/free-solid-svg-icons"; import remarkYoutubePlugin from "remark-youtube"; import Markdown from "svelte-exmarkdown"; - import Fa from "svelte-fa"; import type { Plugin } from "svelte-exmarkdown"; import rehypeRaw from "rehype-raw"; import { m } from "$lib/paraglide/messages.js"; import { SvelteDate } from "svelte/reactivity"; import { resolve } from "$app/paths"; + import { SquarePen } from "@lucide/svelte"; let { preview = false, form, ...post }: PostComponent = $props(); @@ -69,7 +68,7 @@ {#if page.url.pathname !== "/" && page.data.user}
{m.edit()}
diff --git a/src/lib/components/blog/PostEntryForm.svelte b/src/lib/components/blog/PostEntryForm.svelte index 6cf6301..02d86ee 100644 --- a/src/lib/components/blog/PostEntryForm.svelte +++ b/src/lib/components/blog/PostEntryForm.svelte @@ -1,11 +1,10 @@ @@ -14,9 +13,9 @@ onclick={() => (appearance.dark = !appearance.dark)} > {#if appearance.dark} - + {:else} - + {/if} {/if} diff --git a/src/lib/components/crew/BannedAddress.svelte b/src/lib/components/crew/BannedAddress.svelte index 086ea47..de7c467 100644 --- a/src/lib/components/crew/BannedAddress.svelte +++ b/src/lib/components/crew/BannedAddress.svelte @@ -1,10 +1,9 @@