adds comments

This commit is contained in:
Christian Beutel
2024-04-03 22:28:34 +02:00
parent 8af1efaf20
commit 76522c808a
23 changed files with 714 additions and 46 deletions

View File

@@ -0,0 +1,64 @@
<script lang="ts">
import type { Comment } from "$lib/models/comment";
import { getFileURL } from "$lib/util/file_util";
import { createEventDispatcher } from "svelte";
import TextField from "../base/text_field.svelte";
import { fade } from "svelte/transition";
export let comment: Comment;
export let mode: "show" | "edit" = "show";
const dispatch = createEventDispatcher();
let editing: boolean = false;
let editedComment = comment.text;
const avatarSrc =
comment.expand && comment.expand.author
? getFileURL(comment.expand?.author, comment.expand?.author.avatar)
: "https://api.dicebear.com/7.x/initials/svg?seed=comment&backgroundType=gradientLinear";
function deleteComment() {
dispatch("delete", comment);
}
function toggleEdit() {
if (editing) {
dispatch("edit", { comment: comment, text: editedComment });
}
editing = !editing;
}
</script>
<div class="flex gap-4 items-center" in:fade={{duration: 150}} out:fade={{duration: 150}}>
<img class="rounded-full w-10 aspect-square" src={avatarSrc} alt="avatar" />
<div>
<div class="flex items-center">
<p class="text-sm font-semibold">
{comment.expand?.author.username}
</p>
{#if mode == "edit"}
<button
type="button"
class="btn-icon ml-2"
style="font-size: 0.75rem;"
on:click={toggleEdit}
><i class="fa fa-{editing ? 'check' : 'pen'}"></i></button
>
<button
type="button"
class="btn-icon text-xs"
style="font-size: 0.75rem;"
on:click={deleteComment}><i class="fa fa-trash"></i></button
>
{/if}
</div>
{#if editing}
<TextField extraClasses="mt-2" bind:value={editedComment}
></TextField>
{:else}
<p>{comment.text}</p>
{/if}
</div>
</div>

View File

@@ -1,10 +1,17 @@
<script lang="ts">
import { goto } from "$app/navigation";
import SummitLogCard from "$lib/components/summit_log/summit_log_card.svelte";
import Tabs from "$lib/components/base/tabs.svelte";
import SummitLogCard from "$lib/components/summit_log/summit_log_card.svelte";
import TrailDropdown from "$lib/components/trail/trail_dropdown.svelte";
import WaypointCard from "$lib/components/waypoint/waypoint_card.svelte";
import { Comment } from "$lib/models/comment";
import type { Trail } from "$lib/models/trail";
import {
comments_create,
comments_delete,
comments_update,
} from "$lib/stores/comment_store";
import { currentUser } from "$lib/stores/user_store";
import { getFileURL } from "$lib/util/file_util";
import {
@@ -20,6 +27,9 @@
import "photoswipe/style.css";
import { onMount } from "svelte";
import { _ } from "svelte-i18n";
import Button from "../base/button.svelte";
import Textarea from "../base/textarea.svelte";
import CommentCard from "../comment/comment_card.svelte";
import PhotoGallery from "../photo_gallery.svelte";
export let trail: Trail;
@@ -31,6 +41,7 @@
$_("waypoints"),
$_("photos"),
$_("summit-book"),
$_("comments"),
];
let map: Map;
@@ -43,6 +54,10 @@
let openGallery: (idx?: number) => void;
let newComment: Comment = new Comment("", 0, "", trail.id ?? "");
let commentCreateLoading: boolean = false;
let commentDeleteLoading: boolean = false;
onMount(async () => {
if (mode == "overview") {
const L = (await import("leaflet")).default;
@@ -98,6 +113,42 @@
async function toggleMapFullScreen() {
goto(`/map/trail/${trail.id!}`);
}
async function createComment() {
if (!$currentUser || !trail.id) {
return;
}
commentCreateLoading = true;
newComment.author = $currentUser.id;
newComment.trail = trail.id;
comments_create(newComment).then((c) => {
newComment.text = "";
c.expand = {
author: $currentUser!,
};
trail.expand.comments_via_trail = [
c,
...(trail.expand.comments_via_trail ?? []),
];
});
commentCreateLoading = false;
}
async function editComment(data: { comment: Comment; text: string }) {
data.comment.text = data.text;
await comments_update(data.comment);
}
async function deleteComment(comment: Comment) {
commentDeleteLoading = true;
await comments_delete(comment);
trail.expand.comments_via_trail =
trail.expand.comments_via_trail.filter((c) => c.id !== comment.id);
commentDeleteLoading = false;
}
</script>
<div
@@ -228,6 +279,53 @@
{/each}
</ul>
{/if}
{#if activeTab == 4}
<div>
{#if $currentUser}
<div class="flex items-center gap-4">
<img
class="rounded-full w-10 aspect-square"
src={getFileURL(
$currentUser,
$currentUser.avatar,
) ||
`https://api.dicebear.com/7.x/initials/svg?seed=${$currentUser.username}&backgroundType=gradientLinear`}
alt="avatar"
/>
<div class="basis-full">
<Textarea
bind:value={newComment.text}
rows={2}
placeholder="Add comment..."
></Textarea>
</div>
</div>
<div class="flex justify-end mt-3">
<Button
on:click={createComment}
loading={commentCreateLoading}
secondary={true}
disabled={newComment.text.length == 0}
>Comment</Button
>
</div>
{/if}
<ul>
{#each trail.expand.comments_via_trail ?? [] as comment}
<li>
<CommentCard
{comment}
mode={comment.author == $currentUser?.id
? "edit"
: "show"}
on:delete={(e) => deleteComment(e.detail)}
on:edit={(e) => editComment(e.detail)}
></CommentCard>
</li>
{/each}
</ul>
</div>
{/if}
{#if mode == "overview"}
<div class="relative">
<div class="rounded-xl h-72" id="map">

View File

@@ -18,6 +18,7 @@
"change": "Ändern",
"changelog": "Changelog",
"chinese": "Chinesisch",
"comments": "",
"completed": "Abgeschlossen",
"completion-status": "Abschlussstatus",
"contribute": "Mitwirken",

View File

@@ -18,6 +18,7 @@
"change": "Change",
"changelog": "Changelog",
"chinese": "Chinese",
"comments": "Comments",
"completed": "Completed",
"completion-status": "Completion Status",
"contribute": "Contribute",

View File

@@ -18,6 +18,7 @@
"change": "Changement",
"changelog": "Journal des modifications",
"chinese": "Chinois",
"comments": "",
"completed": "Compléter",
"completion-status": "L'état d'achèvement",
"contribute": "Contribuer",

View File

@@ -18,6 +18,7 @@
"change": "Változás",
"changelog": "Változás napló",
"chinese": "kínai",
"comments": "",
"completed": "Teljesítve",
"completion-status": "Befejezés állapota",
"contribute": "Hozzájárulás",

View File

@@ -18,6 +18,7 @@
"change": "Aanpassen",
"changelog": "Changelog",
"chinese": "Chinese",
"comments": "",
"completed": "Voltooid",
"completion-status": "Voltooiingsstatus",
"contribute": "Bijdragen",

View File

@@ -18,6 +18,7 @@
"change": "Zmień",
"changelog": "Dziennik zmian",
"chinese": "chiński",
"comments": "",
"completed": "Zakończono",
"completion-status": "Stan ukończenia",
"contribute": "Kontrybuuj",

View File

@@ -18,6 +18,7 @@
"change": "Alterar",
"changelog": "Registo de alterações",
"chinese": "chinês",
"comments": "",
"completed": "Completada",
"completion-status": "Status de conclusão",
"contribute": "Contribuir",

View File

@@ -18,6 +18,7 @@
"change": "更换",
"changelog": "变更日志",
"chinese": "华人",
"comments": "",
"completed": "已完成",
"completion-status": "完成状态",
"contribute": "贡献",

View File

@@ -0,0 +1,19 @@
import type { User } from "$lib/stores/user_store";
export class Comment {
id?: string;
text: string;
rating: number;
author: string;
trail: string;
expand?: {
author: User
}
constructor(text: string, rating: number, author: string, trail: string) {
this.text = text;
this.rating = rating;
this.author = author;
this.trail = trail;
}
}

View File

@@ -1,4 +1,5 @@
import type { Category } from "./category";
import type { Comment } from "./comment";
import type { SummitLog } from "./summit_log";
import type { Waypoint } from "./waypoint";
@@ -24,6 +25,7 @@ class Trail {
category?: Category;
waypoints: Waypoint[]
summit_logs: SummitLog[]
comments_via_trail: Comment[]
gpx_data?: string
}
tags?: string[];
@@ -47,6 +49,7 @@ class Trail {
category?: Category,
waypoints?: Waypoint[],
summit_logs?: SummitLog[],
comments?: Comment[],
tags?: string[],
description?: string
created?: string
@@ -71,7 +74,8 @@ class Trail {
this.expand = {
category: params?.category,
waypoints: params?.waypoints ?? [],
summit_logs: params?.summit_logs ?? []
summit_logs: params?.summit_logs ?? [],
comments_via_trail: params?.comments ?? []
}
this.tags = params?.tags ?? []
this.description = params?.description ?? "";

View File

@@ -0,0 +1,53 @@
import { Comment } from "$lib/models/comment";
import { pb } from "$lib/pocketbase";
import { ClientResponseError } from "pocketbase";
import { writable, type Writable } from "svelte/store";
export const comments: Writable<Comment[]> = writable([])
export async function comments_create(comment: Comment) {
if (!pb.authStore.model) {
throw new Error("Unauthenticated");
}
comment.author = pb.authStore.model!.id;
let r = await fetch('/api/v1/comment', {
method: 'PUT',
body: JSON.stringify(comment),
})
if (!r.ok) {
throw new ClientResponseError(await r.json())
}
const model: Comment = await r.json();
return model;
}
export async function comments_update(comment: Comment) {
let r = await fetch('/api/v1/comment/' + comment.id, {
method: 'POST',
body: JSON.stringify(comment),
})
if (!r.ok) {
throw new ClientResponseError(await r.json())
}
const model: Comment = await r.json();
return model;
}
export async function comments_delete(comment: Comment) {
const r = await fetch('/api/v1/comment/' + comment.id, {
method: 'DELETE',
})
if (!r.ok) {
throw new ClientResponseError(await r.json())
}
}

View File

@@ -167,7 +167,7 @@ export async function trails_search_bounding_box(northEast: LatLng, southWest: L
export async function trails_show(id: string, loadGPX?: boolean, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
const r = await f(`/api/v1/trail/${id}?` + new URLSearchParams({
expand: "category,waypoints,summit_logs",
expand: "category,waypoints,summit_logs,comments_via_trail.author",
}), {
method: 'GET',
})
@@ -245,7 +245,7 @@ export async function trails_create(trail: Trail, photos: File[], gpx: File | Bl
}
}
export async function trails_update(oldTrail: Trail, newTrail: Trail, photos: File[], gpx: File | Blob | null) {
export async function trails_update(oldTrail: Trail, newTrail: Trail, photos?: File[], gpx?: File | Blob | null) {
const waypointUpdates = compareObjectArrays<Waypoint>(oldTrail.expand.waypoints ?? [], newTrail.expand.waypoints ?? []);
@@ -300,10 +300,13 @@ export async function trails_update(oldTrail: Trail, newTrail: Trail, photos: Fi
formData.append("gpx", gpx);
}
for (const photo of photos) {
formData.append("photos", photo)
if (photos) {
for (const photo of photos) {
formData.append("photos", photo)
}
}
const deletedPhotos = oldTrail.photos.filter(oldPhoto => !newTrail.photos.find(newPhoto => newPhoto === oldPhoto));
for (const deletedPhoto of deletedPhotos) {