Merge branch 'feat/hide-comments-when-disabled'

This commit is contained in:
MechaCat02
2026-07-19 18:27:05 +02:00
11 changed files with 133 additions and 78 deletions

View File

@@ -302,6 +302,7 @@ pub async fn rebuild_export(
r.event_id,
r.event_name,
r.epoch,
state.config.comments_enabled,
std::time::Duration::ZERO,
state.pool.clone(),
state.config.media_path.clone(),
@@ -549,6 +550,7 @@ pub fn start_regen(state: &AppState, regen: crate::services::export::PendingRege
regen.event_id,
regen.event_name,
regen.epoch,
state.config.comments_enabled,
// Debounced: a takedown pass is a burst, and each request retires the last generation. The
// delay lets superseded workers fail their claim and do zero work instead of each building
// a full archive. See export::REGEN_DEBOUNCE.
@@ -753,6 +755,7 @@ pub async fn release_gallery(
event_id,
event_name,
epoch,
state.config.comments_enabled,
std::time::Duration::ZERO,
state.pool.clone(),
state.config.media_path.clone(),

View File

@@ -58,6 +58,7 @@ async fn main() -> Result<()> {
pool.clone(),
config.media_path.clone(),
config.export_path.clone(),
config.comments_enabled,
state.sse_tx.clone(),
)
.await;

View File

@@ -53,6 +53,9 @@ struct ViewerData {
struct ViewerEvent {
name: String,
exported_at: String,
// Mirrors the live COMMENTS_ENABLED flag so the offline keepsake hides all comment
// UI (buttons, counts, sections) when the feature was off for the event.
comments_enabled: bool,
}
#[derive(Serialize)]
@@ -238,6 +241,7 @@ pub async fn recover_exports(
pool: PgPool,
media_path: PathBuf,
export_path: PathBuf,
comments_enabled: bool,
sse_tx: broadcast::Sender<SseEvent>,
) {
let rows = match sqlx::query_as::<_, (Uuid, String, i64)>(
@@ -297,6 +301,7 @@ pub async fn recover_exports(
event_id,
event_name,
epoch,
comments_enabled,
Duration::ZERO,
pool.clone(),
media_path.clone(),
@@ -403,6 +408,7 @@ pub fn spawn_export_jobs(
event_id: Uuid,
event_name: String,
epoch: i64,
comments_enabled: bool,
delay: Duration,
pool: PgPool,
media_path: PathBuf,
@@ -439,6 +445,7 @@ pub fn spawn_export_jobs(
event_id,
epoch,
&event_name2,
comments_enabled,
&pool2,
&media_path2,
&export_path2,
@@ -635,10 +642,12 @@ impl MediaSource {
}
}
#[allow(clippy::too_many_arguments)]
async fn run_html_export(
event_id: Uuid,
epoch: i64,
event_name: &str,
comments_enabled: bool,
pool: &PgPool,
media_path: &Path,
export_path: &Path,
@@ -653,6 +662,7 @@ async fn run_html_export(
epoch,
event_id,
event_name,
comments_enabled,
pool,
media_path,
export_path,
@@ -672,10 +682,12 @@ async fn run_html_export(
abandon_if_superseded("HTML", event_id, epoch, res)
}
#[allow(clippy::too_many_arguments)]
async fn run_html_export_inner(
epoch: i64,
event_id: Uuid,
event_name: &str,
comments_enabled: bool,
pool: &PgPool,
media_path: &Path,
export_path: &Path,
@@ -882,6 +894,7 @@ async fn run_html_export_inner(
event: ViewerEvent {
name: event_name.to_string(),
exported_at: Utc::now().to_rfc3339(),
comments_enabled,
},
posts: viewer_posts,
};

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,9 @@ export interface ViewerData {
event: {
name: string;
exported_at: string;
// Mirrors the live COMMENTS_ENABLED flag. Older exports predate this field, so
// treat a missing value as enabled (`?? true`) to preserve their comment UI.
comments_enabled?: boolean;
};
posts: ViewerPost[];
}

View File

@@ -31,6 +31,9 @@
let posts = $derived(data?.posts ?? []);
// Mirror the live COMMENTS_ENABLED flag. Missing on older exports → treat as enabled.
let commentsEnabled = $derived(data?.event.comments_enabled ?? true);
let allTags = $derived.by(() => {
// eslint-disable-next-line svelte/prefer-svelte-reactivity -- local throwaway counter inside a $derived.by; never stored in $state.
const freq = new Map<string, number>();
@@ -550,22 +553,24 @@
</svg>
{post.likes}
</span>
<span class="flex items-center gap-1.5 text-sm font-medium text-gray-500">
<svg
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
/>
</svg>
{post.comments.length}
</span>
{#if commentsEnabled}
<span class="flex items-center gap-1.5 text-sm font-medium text-gray-500">
<svg
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
/>
</svg>
{post.comments.length}
</span>
{/if}
</div>
<!-- Caption -->
@@ -641,17 +646,24 @@
</svg>
{post.likes}
</span>
<span class="flex items-center gap-0.5">
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
/>
</svg>
{post.comments.length}
</span>
{#if commentsEnabled}
<span class="flex items-center gap-0.5">
<svg
class="h-3.5 w-3.5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
/>
</svg>
{post.comments.length}
</span>
{/if}
</div>
</div>
</div>
@@ -784,23 +796,25 @@
</div>
<!-- Comments list -->
<div class="flex-1 overflow-y-auto p-3">
{#if selectedPost.comments.length === 0}
<p class="text-center text-sm text-gray-400">Keine Kommentare.</p>
{:else}
<div class="space-y-3">
{#each selectedPost.comments as comment, i (i)}
<div>
<span class="text-sm font-medium text-gray-900">{comment.author}</span>
<span class="ml-1 text-sm text-gray-700">{comment.text}</span>
<div class="mt-0.5 text-xs text-gray-400">
{formatShortDate(comment.timestamp)}
{#if commentsEnabled}
<div class="flex-1 overflow-y-auto p-3">
{#if selectedPost.comments.length === 0}
<p class="text-center text-sm text-gray-400">Keine Kommentare.</p>
{:else}
<div class="space-y-3">
{#each selectedPost.comments as comment, i (i)}
<div>
<span class="text-sm font-medium text-gray-900">{comment.author}</span>
<span class="ml-1 text-sm text-gray-700">{comment.text}</span>
<div class="mt-0.5 text-xs text-gray-400">
{formatShortDate(comment.timestamp)}
</div>
</div>
</div>
{/each}
</div>
{/if}
</div>
{/each}
</div>
{/if}
</div>
{/if}
</div>
</div>
</div>

View File

@@ -6,6 +6,7 @@
import CameraCapture from '$lib/components/CameraCapture.svelte';
import type { PendingFile } from '$lib/pending-upload-store';
import { eventState, uploadsClosed } from '$lib/event-state-store';
import { commentsEnabled } from '$lib/event-config-store';
// Uploads closed (event locked or gallery released) — show a lock notice instead of
// the capture options, so a guest can't stage a photo that would just be rejected.
@@ -163,8 +164,9 @@
<div class="rounded-xl bg-amber-50 px-5 py-4 text-center dark:bg-amber-950/30">
<p class="font-semibold text-amber-800 dark:text-amber-300">Uploads geschlossen</p>
<p class="mt-1 text-sm text-amber-700 dark:text-amber-400">
Der Host hat die Uploads für dieses Event beendet. Du kannst weiterhin Fotos ansehen,
liken und kommentieren.
Der Host hat die Uploads für dieses Event beendet. Du kannst weiterhin Fotos ansehen{$commentsEnabled
? ', liken und kommentieren'
: ' und liken'}.
</p>
</div>
<button onclick={close} class="btn btn-secondary btn-block"> Schließen </button>

View File

@@ -22,6 +22,7 @@
import { browser } from '$app/environment';
import type { FeedUpload } from '$lib/types';
import { dataMode } from '$lib/data-mode-store';
import { commentsEnabled } from '$lib/event-config-store';
import { longpress } from '$lib/actions/longpress';
import FeedListCard from './FeedListCard.svelte';
@@ -277,24 +278,31 @@
</svg>
{upload.like_count}
</button>
<button
class="pointer-events-auto -m-1 flex items-center gap-0.5 p-1"
onclick={(e) => {
e.stopPropagation();
oncomment(upload.id);
}}
aria-label="Kommentare anzeigen"
>
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
/>
</svg>
{upload.comment_count}
</button>
{#if $commentsEnabled}
<button
class="pointer-events-auto -m-1 flex items-center gap-0.5 p-1"
onclick={(e) => {
e.stopPropagation();
oncomment(upload.id);
}}
aria-label="Kommentare anzeigen"
>
<svg
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
/>
</svg>
{upload.comment_count}
</button>
{/if}
</div>
</div>
</div>

View File

@@ -8,7 +8,12 @@
import Modal from '$lib/components/Modal.svelte';
import IconButton from '$lib/components/IconButton.svelte';
import { PRESETS, DEFAULT_SEED, buildPaletteCss, type ThemeConfig } from '$lib/theme/palette';
import { previewTheme, PALETTE_CACHE_KEY, loadEventConfig } from '$lib/event-config-store';
import {
previewTheme,
PALETTE_CACHE_KEY,
loadEventConfig,
commentsEnabled
} from '$lib/event-config-store';
import { onDestroy } from 'svelte';
interface StatsDto {
@@ -644,12 +649,14 @@
</p>
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Uploads</p>
</div>
<div class="card p-4 text-center">
<p class="text-3xl font-bold text-gray-900 dark:text-gray-100">
{stats.comment_count}
</p>
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Kommentare</p>
</div>
{#if $commentsEnabled}
<div class="card p-4 text-center">
<p class="text-3xl font-bold text-gray-900 dark:text-gray-100">
{stats.comment_count}
</p>
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Kommentare</p>
</div>
{/if}
<div class="card p-4 text-center">
<p class="text-3xl font-bold text-gray-900 dark:text-gray-100">
{diskPct(stats)} %
@@ -1053,7 +1060,7 @@
onclick={() =>
(confirmAction = {
title: 'Sperre aufheben?',
message: `${user.display_name} kann danach wieder hochladen, liken und kommentieren.`,
message: `${user.display_name} kann danach wieder hochladen${$commentsEnabled ? ', liken und kommentieren' : ' und liken'}.`,
confirmLabel: 'Entsperren',
tone: 'default',
run: () => unban(user)

View File

@@ -7,6 +7,7 @@
import { toastError } from '$lib/toast-store';
import { focusTrap } from '$lib/actions/focus-trap';
import IconButton from '$lib/components/IconButton.svelte';
import { commentsEnabled } from '$lib/event-config-store';
interface JobStatus {
status: 'locked' | 'pending' | 'running' | 'done' | 'failed';
@@ -266,7 +267,8 @@
<div class="min-w-0">
<h2 class="section-title">HTML-Viewer</h2>
<p class="mt-0.5 text-sm text-gray-500 dark:text-gray-400">
Schöne Offline-Galerie mit Filterung, Kommentaren und Likes — kein Internet nötig.
Schöne Offline-Galerie mit Filterung{$commentsEnabled ? ', Kommentaren' : ''} und Likes
— kein Internet nötig.
</p>
<p
class="mt-1 text-xs {status.html.status === 'done'

View File

@@ -9,6 +9,7 @@
import ConfirmSheet from '$lib/components/ConfirmSheet.svelte';
import Modal from '$lib/components/Modal.svelte';
import IconButton from '$lib/components/IconButton.svelte';
import { commentsEnabled } from '$lib/event-config-store';
interface UserSummary {
id: string;
@@ -448,9 +449,10 @@
Benutzer sperren
</h2>
<p class="mb-4 text-sm text-gray-600 dark:text-gray-400">
<strong>{banTarget.display_name}</strong> wird gesperrt: alle Uploads verschwinden aus Galerie,
Diashow und Export, und Hochladen, Liken und Kommentieren werden blockiert. Der Lesezugriff (Feed
ansehen, Keepsake herunterladen) bleibt bestehen. Rückgängig machbar über „Entsperren“.
<strong>{banTarget.display_name}</strong> wird gesperrt: alle Uploads verschwinden aus
Galerie, Diashow und Export, und Hochladen, Liken{$commentsEnabled ? ' und Kommentieren' : ''} werden
blockiert. Der Lesezugriff (Feed ansehen, Keepsake herunterladen) bleibt bestehen. Rückgängig machbar
über „Entsperren“.
</p>
<div class="flex gap-2">
<button onclick={() => (banTarget = null)} class="btn btn-secondary flex-1">
@@ -823,7 +825,7 @@
onclick={() =>
(confirmAction = {
title: 'Sperre aufheben?',
message: `${user.display_name} kann danach wieder hochladen, liken und kommentieren.`,
message: `${user.display_name} kann danach wieder hochladen${$commentsEnabled ? ', liken und kommentieren' : ' und liken'}.`,
confirmLabel: 'Entsperren',
tone: 'default',
run: () => unban(user)