adds privacy settings

This commit is contained in:
Christian Beutel
2024-12-17 00:53:27 +01:00
parent 43b6be8fb2
commit 92f0b033fa
25 changed files with 2025 additions and 28 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@
text: string;
value: string;
icon?: string;
description?: string;
};
</script>
@@ -32,6 +33,13 @@
class="w-4 h-4 accent-primary border-input-border focus:ring-input-ring focus:ring-2"
on:change={() => handleRadioChange(i)}
/>
<label for="{name}-radio-{i}" class="ms-2 text-sm">{item.text}</label>
<div class="ms-2 text-sm">
<label for="{name}-radio-{i}"
>{item.text}
{#if item.description}
<p class="text-gray-500">{item.description}</p>
{/if}
</label>
</div>
</div>
{/each}

View File

@@ -134,9 +134,13 @@
`https://api.dicebear.com/7.x/initials/svg?seed=${list.expand.author.username}&backgroundType=gradientLinear`}
alt="avatar"
/>
<a class="underline" href="/profile/{list.expand.author.id}"
>{list.expand.author.username}</a
>
{#if !list.expand.author.private}
<a class="underline" href="/profile/{list.expand.author.id}"
>{list.expand.author.username}</a
>
{:else}
<span>{list.expand.author.username}</span>
{/if}
</p>
{/if}
<hr />

View File

@@ -23,8 +23,16 @@
alt="avatar"
/>
<div>
<a class="underline" href="/profile/{user.id}"> {user.username} </a>
{activity.type === "trail" ? $_("planned-a-trail") : $_("completed-a-trail")}
{#if !user.private}
<a class="underline" href="/profile/{user.id}"
>{user.username}</a
>
{:else}
<span>{user.username}</span>
{/if}
{activity.type === "trail"
? $_("planned-a-trail")
: $_("completed-a-trail")}
<p class="text-xs text-gray-500 mb-3">
{new Date(activity.date).toLocaleDateString(undefined, {
month: "long",
@@ -83,7 +91,13 @@
? activity.description?.substring(0, 100)
: activity.description}
{#if (activity.description?.length ?? 0) > 100 && !fullDescription}
<button on:click={() => (fullDescription = true)}>
<button
on:click={(e) => {
e.stopPropagation();
e.preventDefault();
fullDescription = true;
}}
>
... <span class="underline">{$_("read-more")}</span></button
>
{/if}

View File

@@ -136,7 +136,19 @@
class="tooltip flex justify-center"
data-title={log.expand.author.username}
>
<a href="/profile/{log.expand.author.id}">
{#if !log.expand.author.private}
<a href="/profile/{log.expand.author.id}">
<img
class="rounded-full w-7 aspect-square"
src={getFileURL(
log.expand?.author,
log.expand?.author.avatar,
) ||
`https://api.dicebear.com/7.x/initials/svg?seed=${log.expand?.author.username}&backgroundType=gradientLinear`}
alt="avatar"
/>
</a>
{:else}
<img
class="rounded-full w-7 aspect-square"
src={getFileURL(
@@ -146,7 +158,7 @@
`https://api.dicebear.com/7.x/initials/svg?seed=${log.expand?.author.username}&backgroundType=gradientLinear`}
alt="avatar"
/>
</a>
{/if}
</p>
</td>
{/if}
@@ -166,4 +178,4 @@
td {
padding: 0.3rem 0.5rem;
}
</style>
</style>

View File

@@ -50,7 +50,6 @@
const trailIsShared =
(trail.expand?.trail_share_via_trail?.length ?? 0) > 0;
let thumbnail = trail.photos.length
? getFileURL(trail, trail.photos[trail.thumbnail])
: "/imgs/default_thumbnail.webp";
@@ -68,14 +67,14 @@
onMount(async () => {});
function openMarkerPopup(i: number) {
if((markers[i] as M.Marker).getPopup().isOpen()) {
if ((markers[i] as M.Marker).getPopup().isOpen()) {
return;
}
(markers[i] as M.Marker).togglePopup();
}
function closeMarkerPopup(i: number) {
if(!(markers[i] as M.Marker).getPopup().isOpen()) {
if (!(markers[i] as M.Marker).getPopup().isOpen()) {
return;
}
(markers[i] as M.Marker).togglePopup();
@@ -195,11 +194,15 @@
`https://api.dicebear.com/7.x/initials/svg?seed=${trail.expand.author.username}&backgroundType=gradientLinear`}
alt="avatar"
/>
<a
class="underline"
href="/profile/{trail.expand.author.id}"
>{trail.expand.author.username}</a
>
{#if !trail.expand.author.private}
<a
class="underline"
href="/profile/{trail.expand.author.id}"
>{trail.expand.author.username}</a
>
{:else}
<span>{trail.expand.author.username}</span>
{/if}
</p>
{/if}
<div class="flex flex-wrap gap-x-8 gap-y-2 mt-4 mr-8">

View File

@@ -7,6 +7,7 @@
"Walking": "Laufen",
"about": "Über",
"account-delete-confirm": "Du bist dabei, dein Konto zu löschen. Alle deine Routen werden ebenfalls gelöscht. Möchtest du fortfahren?",
"account-privacy": "",
"activity": "{n, plural, =1 {Aktivität} other {Aktivitäten}}",
"add-bio": "Bio hinzufügen",
"add-entry": "Eintrag hinzufügen",
@@ -109,8 +110,8 @@
"filter-difficulty": "Schwierigkeit filtern",
"focus-map-on": "Karte fokussieren auf",
"follow": "Folgen",
"followers": "Folgen dir",
"following": "Du folgst",
"followers": "Follower",
"following": "Folgt",
"forgot-your-password": "Passwort vergessen?",
"french": "Französisch",
"german": "Deutsch",
@@ -177,6 +178,7 @@
"not-a-valid-email-address": "Keine gültige Email-Adresse",
"not-completed": "Nicht abgeschlossen",
"off": "Aus",
"only-me": "",
"or": "oder",
"orientation": "Ausrichtung",
"paper-size": "Papierformat",
@@ -192,6 +194,8 @@
"polish": "Polnisch",
"portuguese": "Portugiesisch",
"print": "Drucken",
"privacy": "",
"private": "",
"profile": "Profil",
"public": "Öffentlich",
"radius": "Radius",
@@ -209,6 +213,12 @@
"search-trails": "Route suchen",
"select-list": "Liste auswählen",
"settings": "Einstellungen",
"settings-privacy-account-private": "",
"settings-privacy-account-public": "",
"settings-privacy-lists-private": "",
"settings-privacy-lists-public": "",
"settings-privacy-trails-private": "",
"settings-privacy-trails-public": "",
"share": "Teilen",
"share-this-list": "Diese Liste teilen",
"share-this-trail": "Diese Route teilen",

View File

@@ -7,6 +7,7 @@
"Walking": "Walking",
"about": "About",
"account-delete-confirm": "You are about to delete your account. All your trails will also be deleted. Do you want to proceed?",
"account-privacy": "Account privacy",
"activity": "{n, plural, =1 {Activity} other {Activities}}",
"add-bio": "Add Bio",
"add-entry": "Add Entry",
@@ -177,6 +178,7 @@
"not-a-valid-email-address": "Not a valid email address",
"not-completed": "Not completed",
"off": "Off",
"only-me": "Only me",
"or": "or",
"orientation": "Orientation",
"paper-size": "Paper size",
@@ -192,6 +194,8 @@
"polish": "Polish",
"portuguese": "Portuguese",
"print": "Print",
"privacy": "Privacy",
"private": "Private",
"profile": "Profile",
"public": "Public",
"radius": "Radius",
@@ -209,6 +213,12 @@
"search-trails": "Search trails",
"select-list": "Select List",
"settings": "Settings",
"settings-privacy-account-private": "Only you can see your profile. You will not appear in search results. Other users cannot follow you or share trails with you. You can still publish trails or lists.",
"settings-privacy-account-public": "Everyone can see your profile. You appear in search results. Other users can follow you and share trails with you.",
"settings-privacy-lists-private": "Your lists are private by default. No one except you will be able to see them. You can change this setting at any point for individual lists.",
"settings-privacy-lists-public": "Your lists are public by default. Everyone will be able to see them. You can change this setting at any point for individual lists.",
"settings-privacy-trails-private": "Your trails are private by default. No one except you will be able to see them. You can change this setting at any point for individual trails.",
"settings-privacy-trails-public": "Your trails are public by default. Everyone will be able to see them. You can change this setting at any point for individual trails.",
"share": "Share",
"share-this-list": "Share this list",
"share-this-trail": "Share this trail",

View File

@@ -7,6 +7,7 @@
"Walking": "",
"about": "Informations",
"account-delete-confirm": "Vous êtes sur le point de supprimer votre compte. Toutes vos trails seront également supprimées. Voulez-vous continuer ?",
"account-privacy": "",
"activity": "",
"add-bio": "",
"add-entry": "Ajouter une entrée",
@@ -177,6 +178,7 @@
"not-a-valid-email-address": "Adresse email invalide",
"not-completed": "Pas terminé",
"off": "",
"only-me": "",
"or": "ou",
"orientation": "Orientation",
"paper-size": "Taille de papier",
@@ -192,6 +194,8 @@
"polish": "polonais",
"portuguese": "Portugais",
"print": "Imprimer",
"privacy": "",
"private": "",
"profile": "Profile",
"public": "Publique",
"radius": "Rayon",
@@ -209,6 +213,12 @@
"search-trails": "Chercher un itinéraire",
"select-list": "Liste de choix",
"settings": "Paramètres",
"settings-privacy-account-private": "",
"settings-privacy-account-public": "",
"settings-privacy-lists-private": "",
"settings-privacy-lists-public": "",
"settings-privacy-trails-private": "",
"settings-privacy-trails-public": "",
"share": "",
"share-this-list": "",
"share-this-trail": "",

View File

@@ -7,6 +7,7 @@
"Walking": "",
"about": "A programról",
"account-delete-confirm": "Ön most a profilját készül törölni. Minden nyomvonala törlődik. Szeretné folytatni?",
"account-privacy": "",
"activity": "",
"add-bio": "",
"add-entry": "Bejegyzés hozzáadása",
@@ -177,6 +178,7 @@
"not-a-valid-email-address": "Érvénytelen e-mail cím",
"not-completed": "Nem teljesített",
"off": "",
"only-me": "",
"or": "vagy",
"orientation": "",
"paper-size": "",
@@ -192,6 +194,8 @@
"polish": "Lengyel",
"portuguese": "Portugál",
"print": "",
"privacy": "",
"private": "",
"profile": "Profil",
"public": "Publikus",
"radius": "Átmérő",
@@ -209,6 +213,12 @@
"search-trails": "Nyomvonalak keresése",
"select-list": "Lista kiválasztása",
"settings": "Beállítások",
"settings-privacy-account-private": "",
"settings-privacy-account-public": "",
"settings-privacy-lists-private": "",
"settings-privacy-lists-public": "",
"settings-privacy-trails-private": "",
"settings-privacy-trails-public": "",
"share": "",
"share-this-list": "",
"share-this-trail": "",

View File

@@ -7,6 +7,7 @@
"Walking": "Camminare",
"about": "Su di noi",
"account-delete-confirm": "Stai per eliminare il tuo account. Tutti i tuoi percorsi saranno cancellati. Vuoi procedere?",
"account-privacy": "",
"activity": "",
"add-bio": "",
"add-entry": "Aggiungi voce",
@@ -177,6 +178,7 @@
"not-a-valid-email-address": "Indirizzo email non valido",
"not-completed": "Non completato",
"off": "Spento",
"only-me": "",
"or": "o",
"orientation": "Orientamento",
"paper-size": "Formato carta",
@@ -192,6 +194,8 @@
"polish": "Polacco",
"portuguese": "Portoghese",
"print": "Stampa",
"privacy": "",
"private": "",
"profile": "Profilo",
"public": "Pubblico",
"radius": "Raggio",
@@ -209,6 +213,12 @@
"search-trails": "Cerca percorsi",
"select-list": "Seleziona lista",
"settings": "Impostazioni",
"settings-privacy-account-private": "",
"settings-privacy-account-public": "",
"settings-privacy-lists-private": "",
"settings-privacy-lists-public": "",
"settings-privacy-trails-private": "",
"settings-privacy-trails-public": "",
"share": "Condividi",
"share-this-list": "",
"share-this-trail": "Condividi questo percorso",

View File

@@ -7,6 +7,7 @@
"Walking": "",
"about": "Over",
"account-delete-confirm": "Je staat op het punt je account te verwijderen. Al je wandelroutes worden hierdoor eveneens verwijderd. Wil je doorgaan?",
"account-privacy": "",
"activity": "",
"add-bio": "",
"add-entry": "Item toevoegen",
@@ -177,6 +178,7 @@
"not-a-valid-email-address": "Het e-mailadres is ongeldig",
"not-completed": "Niet voltooid",
"off": "",
"only-me": "",
"or": "of",
"orientation": "Oriëntatie",
"paper-size": "Papierformaat",
@@ -192,6 +194,8 @@
"polish": "Pools",
"portuguese": "Portugees",
"print": "Afdrukken",
"privacy": "",
"private": "",
"profile": "Profiel",
"public": "Openbaar",
"radius": "Straal",
@@ -209,6 +213,12 @@
"search-trails": "Zoeken naar wandelroutes",
"select-list": "Kies een lijst",
"settings": "Instellingen",
"settings-privacy-account-private": "",
"settings-privacy-account-public": "",
"settings-privacy-lists-private": "",
"settings-privacy-lists-public": "",
"settings-privacy-trails-private": "",
"settings-privacy-trails-public": "",
"share": "",
"share-this-list": "",
"share-this-trail": "",

View File

@@ -7,6 +7,7 @@
"Walking": "",
"about": "Na temat",
"account-delete-confirm": "Usuniesz swoje konto. Wszystkie twoje ścieżki zostaną usunięte. Czy chcesz kontynuować?",
"account-privacy": "",
"activity": "",
"add-bio": "",
"add-entry": "Dodaj Pozycję",
@@ -177,6 +178,7 @@
"not-a-valid-email-address": "Nieprawidłowy adres email",
"not-completed": "Nie dokończono",
"off": "",
"only-me": "",
"or": "lub",
"orientation": "",
"paper-size": "",
@@ -192,6 +194,8 @@
"polish": "Polski",
"portuguese": "Portugalski",
"print": "",
"privacy": "",
"private": "",
"profile": "Profil",
"public": "Publiczny",
"radius": "Promień",
@@ -209,6 +213,12 @@
"search-trails": "Szukaj ścieżek",
"select-list": "Wybierz Listę",
"settings": "Ustawienia",
"settings-privacy-account-private": "",
"settings-privacy-account-public": "",
"settings-privacy-lists-private": "",
"settings-privacy-lists-public": "",
"settings-privacy-trails-private": "",
"settings-privacy-trails-public": "",
"share": "",
"share-this-list": "",
"share-this-trail": "",

View File

@@ -7,6 +7,7 @@
"Walking": "",
"about": "Sobre",
"account-delete-confirm": "Você está prestes a excluir sua conta. Todas as suas trilhas também serão excluídas. Queres prosseguir?",
"account-privacy": "",
"activity": "",
"add-bio": "",
"add-entry": "Adicionar entrada",
@@ -177,6 +178,7 @@
"not-a-valid-email-address": "Não um endereço de e-mail válido",
"not-completed": "Não preenchido",
"off": "",
"only-me": "",
"or": "ou",
"orientation": "",
"paper-size": "",
@@ -192,6 +194,8 @@
"polish": "Polonês",
"portuguese": "Português",
"print": "",
"privacy": "",
"private": "",
"profile": "Perfil",
"public": "Público",
"radius": "Raio",
@@ -209,6 +213,12 @@
"search-trails": "Procurar trilhos",
"select-list": "Selecionar lista",
"settings": "Definições",
"settings-privacy-account-private": "",
"settings-privacy-account-public": "",
"settings-privacy-lists-private": "",
"settings-privacy-lists-public": "",
"settings-privacy-trails-private": "",
"settings-privacy-trails-public": "",
"share": "",
"share-this-list": "",
"share-this-trail": "",

View File

@@ -7,6 +7,7 @@
"Walking": "",
"about": "关于",
"account-delete-confirm": "您现在要删除当前账户,所有的路线都会删除无法恢复,确认继续操作吗?",
"account-privacy": "",
"activity": "",
"add-bio": "",
"add-entry": "添加日程",
@@ -177,6 +178,7 @@
"not-a-valid-email-address": "无效电子邮箱地址",
"not-completed": "未完成",
"off": "",
"only-me": "",
"or": "或",
"orientation": "",
"paper-size": "",
@@ -192,6 +194,8 @@
"polish": "波兰语",
"portuguese": "葡萄牙语",
"print": "",
"privacy": "",
"private": "",
"profile": "个人资料",
"public": "公开",
"radius": "半径",
@@ -209,6 +213,12 @@
"search-trails": "搜索路线",
"select-list": "选择列表",
"settings": "设置",
"settings-privacy-account-private": "",
"settings-privacy-account-public": "",
"settings-privacy-lists-private": "",
"settings-privacy-lists-public": "",
"settings-privacy-trails-private": "",
"settings-privacy-trails-public": "",
"share": "",
"share-this-list": "",
"share-this-trail": "",

View File

@@ -9,6 +9,7 @@ class Settings {
tilesets?: {name: string, url: string}[]
terrain?: { terrain: string, hillshading: string};
user?: string;
privacy?: {account: "public" | "private", trails: "public" | "private", lists: "public" | "private"}
constructor(
unit: "metric" | "imperial",

View File

@@ -17,4 +17,5 @@ export type UserAnonymous = {
avatar?: string;
bio?: string;
created?: string;
private: boolean;
}

View File

@@ -5,5 +5,5 @@ import { env } from "$env/dynamic/private";
import type { Settings } from '$lib/models/settings';
export const load: LayoutServerLoad = async ({ locals, url }) => {
return { settings: locals.settings, origin: env.ORIGIN }
return { settings: locals.settings as Settings, origin: env.ORIGIN }
}

View File

@@ -47,7 +47,10 @@
let openPublishConfirmModal: () => void;
const { form, errors, handleChange, handleSubmit } = createForm<List>({
initialValues: data.list!,
initialValues: {
...data.list!,
public: $page.data.settings?.privacy.lists === "public",
},
validationSchema: listSchema,
onSubmit: async (submittedList) => {
if (await checkPrerequisites()) {

View File

@@ -1,4 +1,8 @@
import type { Follow } from "$lib/models/follow";
import type { Settings } from "$lib/models/settings";
import type { UserAnonymous } from "$lib/models/user";
import { follows_a_b, follows_counts, follows_index } from "$lib/stores/follow_store";
import { settings_show } from "$lib/stores/settings_store";
import { users_show } from "$lib/stores/user_store";
import { error, type ServerLoad } from "@sveltejs/kit";
import { ClientResponseError } from "pocketbase";
@@ -11,20 +15,24 @@ export const load: ServerLoad = async ({ params, locals, fetch }) => {
try {
const user = await users_show(params.id, fetch);
if(user.private && locals.user.id != params.id) {
error(401, {
message: "Profile is private"
})
}
const isOwnProfile = params.id === locals.user?.id;
const follow = await follows_a_b(locals.user?.id, params.id, fetch) ?? null
const followCounts = await follows_counts(params.id, fetch)
return { user, isOwnProfile, ...followCounts, follow: follow }
} catch (e) {
if (e instanceof ClientResponseError && e.status == 404) {
error(404, {
message: 'Not found'
});
} else {
throw e
}
throw e
}
};

View File

@@ -39,7 +39,26 @@
<h1 class="text-3xl font-semibold mb-4">{$_($page.params.type)}</h1>
<ul class="space-y-4">
{#each data.follows.items as follow}
<a href="/profile/{follow.expand?.follower.id}">
{#if !follow.expand?.follower.private}
<a href="/profile/{follow.expand?.follower.id}">
<li
class="flex items-center gap-x-4 hover:bg-menu-item-background-hover p-4"
>
<img
class="rounded-full w-10 aspect-square overflow-hidden"
src={getFileURL(
follow.expand?.follower ?? {},
follow.expand?.follower.avatar,
) ||
`https://api.dicebear.com/7.x/initials/svg?seed=${data.user.username}&backgroundType=gradientLinear`}
alt="avatar"
/>
<p class="text-lg font-medium">
{follow.expand?.follower.username}
</p>
</li>
</a>
{:else}
<li
class="flex items-center gap-x-4 hover:bg-menu-item-background-hover p-4"
>
@@ -56,7 +75,7 @@
{follow.expand?.follower.username}
</p>
</li>
</a>
{/if}
{/each}
</ul>
</div>

View File

@@ -7,6 +7,8 @@
const settingsLinks: SelectItem[] = [
{ text: $_("profile"), value: "/settings/profile" },
{ text: $_("my-account"), value: "/settings/account" },
{ text: $_("privacy"), value: "/settings/privacy" },
{
text: $_("language") + " & " + $_("units"),
value: "/settings/language",

View File

@@ -0,0 +1,117 @@
<script lang="ts">
import { page } from "$app/stores";
import RadioGroup, {
type RadioItem,
} from "$lib/components/base/radio_group.svelte";
import type { Settings } from "$lib/models/settings";
import { settings_update } from "$lib/stores/settings_store";
import { _ } from "svelte-i18n";
$: settings = $page.data.settings as Settings;
const accoutPrivacyItems: RadioItem[] = [
{
text: $_("public"),
value: "public",
description: $_("settings-privacy-account-public"),
},
{
text: $_("private"),
value: "private",
description: $_("settings-privacy-account-private"),
},
];
const trailPrivacyItems: RadioItem[] = [
{
text: $_("public"),
value: "public",
description: $_("settings-privacy-trails-public"),
},
{
text: $_("only-me"),
value: "private",
description: $_("settings-privacy-trails-private"),
},
];
const listPrivacyItems: RadioItem[] = [
{
text: $_("public"),
value: "public",
description: $_("settings-privacy-lists-public"),
},
{
text: $_("only-me"),
value: "private",
description: $_("settings-privacy-lists-private"),
},
];
async function handleAccountSelection(e: RadioItem) {
await settings_update({
id: settings!.id,
privacy: {
trails: settings.privacy?.trails ?? "private",
lists: settings.privacy?.lists ?? "private",
account: e.value as "public" | "private",
},
});
}
async function handleTrailsSelection(e: RadioItem) {
await settings_update({
id: settings!.id,
privacy: {
trails: e.value as "public" | "private",
lists: settings.privacy?.lists ?? "private",
account: settings.privacy?.account ?? "public",
},
});
}
async function handleListsSelection(e: RadioItem) {
await settings_update({
id: settings!.id,
privacy: {
trails: settings.privacy?.trails ?? "private",
lists: e.value as "public" | "private",
account: settings.privacy?.account ?? "private",
},
});
}
</script>
<svelte:head>
<title>{$_("settings")} | wanderer</title>
</svelte:head>
<h2 class="text-2xl font-semibold">{$_("privacy")}</h2>
<hr class="mt-4 mb-6 border-input-border" />
<h4 class="text-xl font-medium mb-4">{$_("account-privacy")}</h4>
<RadioGroup
name="account"
items={accoutPrivacyItems}
selected={settings?.privacy?.account == "private" ? 1 : 0}
on:change={(e) => handleAccountSelection(e.detail)}
></RadioGroup>
<h4 class="text-xl font-medium mt-12 mb-4">
{$_("trail", { values: { n: 2 } })}
</h4>
<RadioGroup
name="trails"
items={trailPrivacyItems}
selected={settings?.privacy?.trails == "public" ? 0 : 1}
on:change={(e) => handleTrailsSelection(e.detail)}
></RadioGroup>
<h4 class="text-xl font-medium mt-12 mb-4">
{$_("list", { values: { n: 2 } })}
</h4>
<RadioGroup
name="lists"
items={listPrivacyItems}
selected={settings?.privacy?.lists == "public" ? 0 : 1}
on:change={(e) => handleListsSelection(e.detail)}
></RadioGroup>

View File

@@ -119,6 +119,7 @@
const { form, errors, handleChange, handleSubmit } = createForm<Trail>({
initialValues: {
...data.trail,
public: $page.data.settings?.privacy.trails === "public",
category:
data.trail.category ||
$page.data.settings?.category ||

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 KiB

After

Width:  |  Height:  |  Size: 64 KiB