adds public lists

This commit is contained in:
Christian Beutel
2024-12-08 21:30:55 +01:00
parent 254e053bd9
commit 673eb78dd0
15 changed files with 200 additions and 59 deletions

View File

@@ -0,0 +1,60 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/daos"
m "github.com/pocketbase/pocketbase/migrations"
"github.com/pocketbase/pocketbase/models/schema"
"github.com/pocketbase/pocketbase/tools/types"
)
func init() {
m.Register(func(db dbx.Builder) error {
dao := daos.New(db);
collection, err := dao.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
collection.ListRule = types.Pointer("author = @request.auth.id || public = true || (@request.auth.id != \"\" && list_share_via_list.user ?= @request.auth.id)")
collection.ViewRule = types.Pointer("author = @request.auth.id || public = true || (@request.auth.id != \"\" && list_share_via_list.user ?= @request.auth.id)")
// add
new_public := &schema.SchemaField{}
if err := json.Unmarshal([]byte(`{
"system": false,
"id": "rolk3q3j",
"name": "public",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
}`), new_public); err != nil {
return err
}
collection.Schema.AddField(new_public)
return dao.SaveCollection(collection)
}, func(db dbx.Builder) error {
dao := daos.New(db);
collection, err := dao.FindCollectionByNameOrId("r6gu2ajyidy1x69")
if err != nil {
return err
}
collection.ListRule = types.Pointer("author = @request.auth.id || (@request.auth.id != \"\" && list_share_via_list.user ?= @request.auth.id)")
collection.ViewRule = types.Pointer("author = @request.auth.id || (@request.auth.id != \"\" && list_share_via_list.user ?= @request.auth.id)")
// remove
collection.Schema.RemoveField("rolk3q3j")
return dao.SaveCollection(collection)
})
}

View File

@@ -9,6 +9,7 @@
export let title: string = $_('confirm-deletion');
export let text: string;
export let action: string = "delete";
export let id: string = "confirm-modal";
const dispatch = createEventDispatcher();
@@ -18,13 +19,13 @@
}
</script>
<Modal id="confirm-modal" {title} bind:openModal bind:closeModal>
<Modal {id} {title} bind:openModal bind:closeModal>
<p slot="content">{text}</p>
<div slot="footer" class="flex items-center gap-4">
<button class="btn-secondary" on:click={closeModal}
>{$_("cancel")}</button
>
<button class="btn-danger" type="button" on:click={confirm} name="delete"
<button class={action === "delete" ? "btn-delete" : "btn-primary"} type="button" on:click={confirm} name="delete"
>{$_(action)}</button
>
</div></Modal

View File

@@ -58,6 +58,14 @@
>
{list.name}
</h5>
{#if list.public}
<span
class="tooltip"
data-title={$_("public")}
>
<i class="fa fa-globe"></i>
</span>
{/if}
{#if listIsShared}
<ShareInfo type="list" subject={list}></ShareInfo>
{/if}

View File

@@ -72,14 +72,26 @@
</script>
<div class="relative">
{#if listIsShared}
{#if (list.public || listIsShared) && $currentUser}
<div
class="absolute top-8 right-8 bg-white text-primary rounded-full w-8 py-1 text-center"
class="flex absolute top-4 right-6 {list.public && listIsShared
? 'w-16'
: 'w-8'} h-8 rounded-full items-center justify-center bg-white text-primary"
>
{#if list.public}
<span
class="tooltip"
class:mr-3={list.public && listIsShared}
data-title={$_("public")}
>
<i class="fa fa-globe"></i>
</span>
{/if}
{#if listIsShared}
<ShareInfo type="list" subject={list}></ShareInfo>
{/if}
</div>
{/if}
{#if dropdownItems.length}
<div class="absolute bottom-8 right-8">
<Dropdown

View File

@@ -42,6 +42,7 @@
"completion-status": "Abschlussstatus",
"confirm": "Bestätigen",
"confirm-deletion": "Löschen bestätigen",
"confirm-publish": "",
"confirm-share": "Teilen bestätigen",
"contribute": "Mitwirken",
"copy-link": "Link kopieren",
@@ -129,6 +130,7 @@
"link-copied": "Link kopiert",
"list": "{n, plural, =1 {Liste} other {Listen}}",
"list-not-shared": "Mit niemandem geteilt",
"list-public-warning": "",
"list-saved-successfully": "Liste gespeichert",
"list-share-warning": "Durch das Teilen einer Liste werden automatisch alle darin enthaltenen Routen freigegeben.",
"list-share-warning-update": "Hinzugefügte Routen werden mit allen geteilt, die Zugriff auf diese Liste haben.",

View File

@@ -42,6 +42,7 @@
"completion-status": "Completion Status",
"confirm": "Confirm",
"confirm-deletion": "Confirm Deletion",
"confirm-publish": "Confirm publishing",
"confirm-share": "Confirm share",
"contribute": "Contribute",
"copy-link": "Copy Link",
@@ -129,6 +130,7 @@
"link-copied": "Link copied!",
"list": "{n, plural, =1 {List} other {Lists}}",
"list-not-shared": "Not shared with anyone",
"list-public-warning": "All trails in this list will become public.",
"list-saved-successfully": "List saved successfully",
"list-share-warning": "Sharing a list automatically shares all trails contained in it.",
"list-share-warning-update": "Added trails will be shared with everyone that has access to this list.",

View File

@@ -42,6 +42,7 @@
"completion-status": "L'état d'achèvement",
"confirm": "",
"confirm-deletion": "",
"confirm-publish": "",
"confirm-share": "",
"contribute": "Contribuer",
"copy-link": "",
@@ -129,6 +130,7 @@
"link-copied": "",
"list": "{n, plural, =1 {Liste} other {Listes}}",
"list-not-shared": "",
"list-public-warning": "",
"list-saved-successfully": "",
"list-share-warning": "",
"list-share-warning-update": "",

View File

@@ -42,6 +42,7 @@
"completion-status": "Befejezés állapota",
"confirm": "",
"confirm-deletion": "",
"confirm-publish": "",
"confirm-share": "",
"contribute": "Hozzájárulás",
"copy-link": "",
@@ -129,6 +130,7 @@
"link-copied": "",
"list": "{n, plural, =1 {Lista} other {Listák}}",
"list-not-shared": "",
"list-public-warning": "",
"list-saved-successfully": "",
"list-share-warning": "",
"list-share-warning-update": "",

View File

@@ -42,6 +42,7 @@
"completion-status": "Stato di completamento",
"confirm": "",
"confirm-deletion": "",
"confirm-publish": "",
"confirm-share": "",
"contribute": "Contribuisci",
"copy-link": "Copia link",
@@ -129,6 +130,7 @@
"link-copied": "Link copiato",
"list": "{n, plural, =1 {Lista} other {Liste}}",
"list-not-shared": "",
"list-public-warning": "",
"list-saved-successfully": "",
"list-share-warning": "",
"list-share-warning-update": "",

View File

@@ -42,6 +42,7 @@
"completion-status": "Voltooiingsstatus",
"confirm": "",
"confirm-deletion": "",
"confirm-publish": "",
"confirm-share": "",
"contribute": "Bijdragen",
"copy-link": "",
@@ -129,6 +130,7 @@
"link-copied": "",
"list": "{n, plural, =1 {Lijst} other {Lijsten}}",
"list-not-shared": "",
"list-public-warning": "",
"list-saved-successfully": "",
"list-share-warning": "",
"list-share-warning-update": "",

View File

@@ -42,6 +42,7 @@
"completion-status": "Stan ukończenia",
"confirm": "",
"confirm-deletion": "",
"confirm-publish": "",
"confirm-share": "",
"contribute": "Kontrybuuj",
"copy-link": "",
@@ -129,6 +130,7 @@
"link-copied": "",
"list": "{n, plural, =1 {Lista} other {Listy}}",
"list-not-shared": "",
"list-public-warning": "",
"list-saved-successfully": "",
"list-share-warning": "",
"list-share-warning-update": "",

View File

@@ -42,6 +42,7 @@
"completion-status": "Status de conclusão",
"confirm": "",
"confirm-deletion": "",
"confirm-publish": "",
"confirm-share": "",
"contribute": "Contribuir",
"copy-link": "",
@@ -129,6 +130,7 @@
"link-copied": "",
"list": "{n, plural, =1 {Lista} other {Listas}}",
"list-not-shared": "",
"list-public-warning": "",
"list-saved-successfully": "",
"list-share-warning": "",
"list-share-warning-update": "",

View File

@@ -42,6 +42,7 @@
"completion-status": "完成状态",
"confirm": "",
"confirm-deletion": "",
"confirm-publish": "",
"confirm-share": "",
"contribute": "贡献",
"copy-link": "",
@@ -129,6 +130,7 @@
"link-copied": "",
"list": "{n, plural, =1 {列表} other {列表}}",
"list-not-shared": "",
"list-public-warning": "",
"list-saved-successfully": "",
"list-share-warning": "",
"list-share-warning-update": "",

View File

@@ -5,6 +5,7 @@ import type { ListShare } from "./list_share";
export class List {
id?: string;
name: string;
public: boolean;
description?: string;
avatar?: string;
trails?: string[];
@@ -15,8 +16,9 @@ export class List {
}
author?: string;
constructor(name: string, trails: Trail[], params?: { description?: string, avatar?: string, author?: string }) {
constructor(name: string, trails: Trail[], params?: { description?: string, public?: boolean, avatar?: string, author?: string }) {
this.name = name;
this.public = params?.public ?? false
this.expand = { trails: trails };
this.trails = trails.map(t => t.id!);
this.description = params?.description;

View File

@@ -20,13 +20,15 @@
trail_share_create,
trail_share_index,
} from "$lib/stores/trail_share_store.js";
import { trails_show } from "$lib/stores/trail_store";
import { trails_show, trails_update } from "$lib/stores/trail_store";
import { getFileURL } from "$lib/util/file_util.js";
import {
formatDistance,
formatElevation,
formatTimeHHMM,
} from "$lib/util/format_util";
import Toggle from "$lib/components/base/toggle.svelte";
import { currentUser } from "$lib/stores/user_store.js";
export let data;
@@ -42,11 +44,36 @@
let newShares: TrailShare[] = [];
let openConfirmModal: () => void;
let openPublishConfirmModal: () => void;
const { form, errors, handleChange, handleSubmit } = createForm<List>({
initialValues: data.list!,
validationSchema: listSchema,
onSubmit: async (submittedList) => {
if (await checkPrerequisites()) {
await saveList();
}
},
});
async function checkPrerequisites() {
if (
(data.list?.public === false && $form.public === true) ||
($form.public === true &&
data.list?.expand?.trails?.length !==
$form.expand?.trails?.length)
) {
openPublishConfirmModal();
return false;
} else if (await findNewTrailShares()) {
openConfirmModal();
return false;
}
return true;
}
async function saveList() {
const avatarFile = (
document.getElementById("avatar") as HTMLInputElement
).files![0];
@@ -54,22 +81,14 @@
try {
if ($form.id) {
await lists_update($form, avatarFile);
await findNewTrailShares();
if (!newShares.length) {
show_toast({
type: "success",
icon: "check",
text: $_("list-saved-successfully"),
});
}
} else {
await lists_create($form, avatarFile);
}
show_toast({
type: "success",
icon: "check",
text: $_("list-saved-successfully"),
});
}
} catch (e) {
show_toast({
type: "error",
@@ -79,8 +98,7 @@
} finally {
loading = false;
}
},
});
}
function openAvatarBrowser() {
document.getElementById("avatar")!.click();
@@ -98,22 +116,21 @@
}
async function search(q: string) {
const r = await fetch("/api/v1/search/multi", {
const r = await fetch("/api/v1/search/trails", {
method: "POST",
body: JSON.stringify({
queries: [
{
indexUid: "trails",
q: q,
q,
options: {
filter: `author = ${$currentUser?.id} OR public = true`,
sort: ["name:desc"],
limit: 3,
},
],
}),
});
const response = await r.json();
searchDropdownItems = response.results[0].hits
searchDropdownItems = response.hits
.filter((h: List) => !$form.trails?.includes(h.id!))
.map((t: Record<string, any>) => ({
text: t.name,
@@ -149,7 +166,10 @@
user: userId,
});
for (const trail of $form.expand?.trails ?? []) {
if (trail.author == userId) {
if (
trail.author == userId ||
trail.author != $currentUser?.id
) {
continue;
}
const trailShare = existingTrailShares.find(
@@ -160,10 +180,7 @@
}
}
}
if (newShares.length) {
openConfirmModal();
}
return newShares.length > 0;
}
async function updateTrailShares() {
@@ -171,11 +188,16 @@
await trail_share_create(newShare);
}
newShares = [];
show_toast({
type: "success",
icon: "check",
text: $_("list-saved-successfully"),
});
}
async function publishTrails() {
for (const trail of $form.expand?.trails ?? []) {
if (trail.author !== $currentUser?.id) {
continue;
}
const updatedTrail: Trail = { ...trail, public: true };
await trails_update(trail, updatedTrail);
}
}
function moveTrail(trail: Trail, index: number, direction: 1 | -1) {
@@ -261,6 +283,8 @@
error={$errors.description}
on:change={handleChange}
></Textarea>
<Toggle name="public" label={$_("public")} bind:value={$form.public}
></Toggle>
<h3 class="text-xl font-semibold">
{$_("trail", { values: { n: 2 } })}
</h3>
@@ -379,11 +403,27 @@
</main>
<ConfirmModal
id="share-confirm-modal"
text={$_("list-share-warning-update")}
title={$_("confirm-share")}
action="confirm"
bind:openModal={openConfirmModal}
on:confirm={updateTrailShares}
on:confirm={async () => {
await updateTrailShares();
await saveList();
}}
></ConfirmModal>
<ConfirmModal
id="publish-confirm-modal"
text={$_("list-public-warning")}
title={$_("confirm-publish")}
action="confirm"
bind:openModal={openPublishConfirmModal}
on:confirm={async () => {
await publishTrails();
await saveList();
}}
></ConfirmModal>
<style>