adds trail link sharing
This commit is contained in:
116
db/migrations/1755695750_created_trail_link_share.go
Normal file
116
db/migrations/1755695750_created_trail_link_share.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(app core.App) error {
|
||||
jsonData := `{
|
||||
"createRule": null,
|
||||
"deleteRule": null,
|
||||
"fields": [
|
||||
{
|
||||
"autogeneratePattern": "[a-z0-9]{15}",
|
||||
"hidden": false,
|
||||
"id": "text3208210256",
|
||||
"max": 15,
|
||||
"min": 15,
|
||||
"name": "id",
|
||||
"pattern": "^[a-z0-9]+$",
|
||||
"presentable": false,
|
||||
"primaryKey": true,
|
||||
"required": true,
|
||||
"system": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"cascadeDelete": true,
|
||||
"collectionId": "e864strfxo14pm4",
|
||||
"hidden": false,
|
||||
"id": "relation2993194383",
|
||||
"maxSelect": 1,
|
||||
"minSelect": 0,
|
||||
"name": "trail",
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "relation"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "[a-z0-9]{32}",
|
||||
"hidden": false,
|
||||
"id": "text1597481275",
|
||||
"max": 32,
|
||||
"min": 32,
|
||||
"name": "token",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"hidden": false,
|
||||
"id": "select3762918058",
|
||||
"maxSelect": 1,
|
||||
"name": "permission",
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "select",
|
||||
"values": [
|
||||
"view",
|
||||
"edit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"hidden": false,
|
||||
"id": "autodate2990389176",
|
||||
"name": "created",
|
||||
"onCreate": true,
|
||||
"onUpdate": false,
|
||||
"presentable": false,
|
||||
"system": false,
|
||||
"type": "autodate"
|
||||
},
|
||||
{
|
||||
"hidden": false,
|
||||
"id": "autodate3332085495",
|
||||
"name": "updated",
|
||||
"onCreate": true,
|
||||
"onUpdate": true,
|
||||
"presentable": false,
|
||||
"system": false,
|
||||
"type": "autodate"
|
||||
}
|
||||
],
|
||||
"id": "pbc_2865004035",
|
||||
"indexes": [],
|
||||
"listRule": null,
|
||||
"name": "trail_link_share",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": null,
|
||||
"viewRule": null
|
||||
}`
|
||||
|
||||
collection := &core.Collection{}
|
||||
if err := json.Unmarshal([]byte(jsonData), &collection); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Save(collection)
|
||||
}, func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId("pbc_2865004035")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Delete(collection)
|
||||
})
|
||||
}
|
||||
42
db/migrations/1755696870_updated_trail_link_share.go
Normal file
42
db/migrations/1755696870_updated_trail_link_share.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId("pbc_2865004035")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update collection data
|
||||
if err := json.Unmarshal([]byte(`{
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX ` + "`" + `idx_YbuWvsh5la` + "`" + ` ON ` + "`" + `trail_link_share` + "`" + ` (` + "`" + `trail` + "`" + `)"
|
||||
]
|
||||
}`), &collection); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Save(collection)
|
||||
}, func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId("pbc_2865004035")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update collection data
|
||||
if err := json.Unmarshal([]byte(`{
|
||||
"indexes": []
|
||||
}`), &collection); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Save(collection)
|
||||
})
|
||||
}
|
||||
48
db/migrations/1755697071_updated_trail_link_share.go
Normal file
48
db/migrations/1755697071_updated_trail_link_share.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId("pbc_2865004035")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update collection data
|
||||
if err := json.Unmarshal([]byte(`{
|
||||
"createRule": "trail.author.user = @request.auth.id",
|
||||
"deleteRule": "trail.author.user = @request.auth.id",
|
||||
"listRule": "trail.author.user = @request.auth.id",
|
||||
"updateRule": "trail.author.user = @request.auth.id",
|
||||
"viewRule": "trail.author.user = @request.auth.id"
|
||||
}`), &collection); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Save(collection)
|
||||
}, func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId("pbc_2865004035")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update collection data
|
||||
if err := json.Unmarshal([]byte(`{
|
||||
"createRule": null,
|
||||
"deleteRule": null,
|
||||
"listRule": null,
|
||||
"updateRule": null,
|
||||
"viewRule": null
|
||||
}`), &collection); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Save(collection)
|
||||
})
|
||||
}
|
||||
42
db/migrations/1755697879_updated_trails.go
Normal file
42
db/migrations/1755697879_updated_trails.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update collection data
|
||||
if err := json.Unmarshal([]byte(`{
|
||||
"listRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && trail_share_via_trail.actor.user ?= @request.auth.id) || trail_link_share_via_trail.token = @request.query.share",
|
||||
"viewRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && trail_share_via_trail.actor.user ?= @request.auth.id) || trail_link_share_via_trail.token = @request.query.share "
|
||||
}`), &collection); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Save(collection)
|
||||
}, func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update collection data
|
||||
if err := json.Unmarshal([]byte(`{
|
||||
"listRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && trail_share_via_trail.actor.user ?= @request.auth.id)",
|
||||
"viewRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && trail_share_via_trail.actor.user ?= @request.auth.id)"
|
||||
}`), &collection); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Save(collection)
|
||||
})
|
||||
}
|
||||
42
db/migrations/1755700187_updated_waypoints.go
Normal file
42
db/migrations/1755700187_updated_waypoints.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId("goeo2ubp103rzp9")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update collection data
|
||||
if err := json.Unmarshal([]byte(`{
|
||||
"listRule": "author = @request.auth.id || trails_via_waypoints.author.user ?= @request.auth.id || trails_via_waypoints.public ?= true || \n(@collection.trail_share.trail.id ?= trails_via_waypoints.id && @collection.trail_share.actor.user ?= @request.auth.id)\n|| \n(@collection.trail_link_share.trail.id ?= trails_via_waypoints.id && @collection.trail_link_share.token = @request.query.share)",
|
||||
"viewRule": "author = @request.auth.id || trails_via_waypoints.author.user ?= @request.auth.id || trails_via_waypoints.public ?= true || \n(@collection.trail_share.trail.id ?= trails_via_waypoints.id && @collection.trail_share.actor.user ?= @request.auth.id)\n|| \n(@collection.trail_link_share.trail.id ?= trails_via_waypoints.id && @collection.trail_link_share.token = @request.query.share)"
|
||||
}`), &collection); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Save(collection)
|
||||
}, func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId("goeo2ubp103rzp9")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update collection data
|
||||
if err := json.Unmarshal([]byte(`{
|
||||
"listRule": "author = @request.auth.id || trails_via_waypoints.author.user ?= @request.auth.id || trails_via_waypoints.public ?= true || \n(@collection.trail_share.trail.id ?= trails_via_waypoints.id && @collection.trail_share.actor.user ?= @request.auth.id)",
|
||||
"viewRule": "author = @request.auth.id || trails_via_waypoints.author.user ?= @request.auth.id || trails_via_waypoints.public ?= true || \n(@collection.trail_share.trail.id ?= trails_via_waypoints.id && @collection.trail_share.actor.user ?= @request.auth.id)"
|
||||
}`), &collection); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Save(collection)
|
||||
})
|
||||
}
|
||||
@@ -22,6 +22,7 @@
|
||||
import TrailShareModal from "./trail_share_modal.svelte";
|
||||
import { handleFromRecordWithIRI } from "$lib/util/activitypub_util";
|
||||
import type { Snippet } from "svelte";
|
||||
import { page } from "$app/state";
|
||||
|
||||
interface Props {
|
||||
trails?: Set<Trail> | undefined;
|
||||
@@ -44,6 +45,7 @@
|
||||
return (
|
||||
hasTrail() &&
|
||||
!isMultiselectMode() &&
|
||||
Boolean($currentUser) &&
|
||||
(trail()!.expand?.author?.id === $currentUser?.actor ||
|
||||
trail()!.expand?.trail_share_via_trail?.some(
|
||||
(s) => s.permission == "edit",
|
||||
@@ -143,6 +145,9 @@
|
||||
}
|
||||
|
||||
function isFromCurrentUser(uTrail?: Trail): boolean {
|
||||
if (!$currentUser) {
|
||||
return false;
|
||||
}
|
||||
if (uTrail !== undefined) {
|
||||
return uTrail.expand?.author?.id === $currentUser?.actor;
|
||||
} else if (trails !== undefined && trails.size > 0) {
|
||||
@@ -169,14 +174,16 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const handle = handleFromRecordWithIRI(trail());
|
||||
const handle = page.params.handle
|
||||
|
||||
if (item.value == "show") {
|
||||
if (hasTrail()) {
|
||||
goto(
|
||||
mode == "overview" || mode == "multi-select"
|
||||
const url = mode == "overview" || mode == "multi-select"
|
||||
? `/map/trail/${handle}/${trailId()}`
|
||||
: `/trail/view/${handle}/${trailId()}`,
|
||||
: `/trail/view/${handle}/${trailId()}`
|
||||
|
||||
goto(
|
||||
url + '?' + page.url.searchParams
|
||||
);
|
||||
}
|
||||
} else if (item.value == "list") {
|
||||
@@ -199,7 +206,7 @@
|
||||
}
|
||||
} else if (item.value == "print") {
|
||||
if (hasTrail()) {
|
||||
goto(`/map/trail/${handle}/${trailId()}/print`);
|
||||
goto(`/map/trail/${handle}/${trailId()}/print?${page.url.searchParams}`);
|
||||
}
|
||||
} else if (item.value == "share") {
|
||||
trailShareModal.openModal();
|
||||
|
||||
@@ -398,19 +398,17 @@
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
{#if ($currentUser && $currentUser.actor == trail.author) || trail.expand?.trail_share_via_trail?.length || trail.public}
|
||||
<div class="flex flex-col items-center gap-y-2">
|
||||
<div class="flex flex-col items-center gap-y-2">
|
||||
{#if ($currentUser && $currentUser.actor == trail.author) || trail.expand?.trail_share_via_trail?.length || trail.public}
|
||||
<LikeButton {trail}></LikeButton>
|
||||
<TrailDropdown
|
||||
trails={new Set<Trail>([trail])}
|
||||
onDelete={() =>
|
||||
history.length
|
||||
? history.back()
|
||||
: goto("/trails")}
|
||||
{mode}
|
||||
></TrailDropdown>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
<TrailDropdown
|
||||
trails={new Set<Trail>([trail])}
|
||||
onDelete={() =>
|
||||
history.length ? history.back() : goto("/trails")}
|
||||
{mode}
|
||||
></TrailDropdown>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section
|
||||
@@ -520,7 +518,7 @@
|
||||
|
||||
<div class="mb-6 mt-12 flex justify-between flex-wrap gap-y-4">
|
||||
<Tabs {tabs} bind:activeTab></Tabs>
|
||||
{#if activeTab == 0 && mode != "list"}
|
||||
{#if $currentUser && activeTab == 0 && mode != "list"}
|
||||
<Button
|
||||
secondary
|
||||
type="button"
|
||||
|
||||
@@ -14,6 +14,15 @@
|
||||
import Button from "../base/button.svelte";
|
||||
import type { SelectItem } from "../base/select.svelte";
|
||||
import Select from "../base/select.svelte";
|
||||
import type { RadioItem } from "../base/radio_group.svelte";
|
||||
import RadioGroup from "../base/radio_group.svelte";
|
||||
import { TrailLinkShare } from "$lib/models/trail_link_share";
|
||||
import {
|
||||
linkShares,
|
||||
trail_link_share_create,
|
||||
trail_link_share_delete,
|
||||
trail_link_share_index,
|
||||
} from "$lib/stores/trail_link_share_store";
|
||||
|
||||
interface Props {
|
||||
trail?: Trail;
|
||||
@@ -30,8 +39,8 @@
|
||||
openShareModalLocal();
|
||||
}
|
||||
async function openShareModalLocal() {
|
||||
modal.openModal();
|
||||
await fetchShares();
|
||||
modal.openModal();
|
||||
}
|
||||
|
||||
let copyButtonText = $state($_("copy-link"));
|
||||
@@ -43,8 +52,25 @@
|
||||
{ text: $_("edit"), value: "edit" },
|
||||
];
|
||||
|
||||
const publicShareItems: RadioItem[] = [
|
||||
{
|
||||
text: $_("limited"),
|
||||
value: "limited",
|
||||
description: $_("public-share-limited"),
|
||||
},
|
||||
{
|
||||
text: $_("everyone-with-the-link"),
|
||||
value: "everyone",
|
||||
description: $_("public-share-everyone"),
|
||||
},
|
||||
];
|
||||
|
||||
function copyURLToClipboard() {
|
||||
navigator.clipboard.writeText(window.location.href);
|
||||
let link = window.location.href;
|
||||
if ($linkShares.length > 0) {
|
||||
link = link + "?share=" + $linkShares[0].token;
|
||||
}
|
||||
navigator.clipboard.writeText(link);
|
||||
|
||||
copyButtonText = $_("link-copied");
|
||||
setTimeout(() => (copyButtonText = $_("copy-link")), 3000);
|
||||
@@ -88,8 +114,24 @@
|
||||
|
||||
sharesLoading = true;
|
||||
await trail_share_index({ trail: trail.id! });
|
||||
await trail_link_share_index(trail.id);
|
||||
sharesLoading = false;
|
||||
}
|
||||
|
||||
async function handlePublicAccessChange(e: RadioItem) {
|
||||
if (trail === undefined) {
|
||||
return;
|
||||
}
|
||||
if (e.value === "everyone") {
|
||||
const share = new TrailLinkShare(trail.id!, "view");
|
||||
await trail_link_share_create(share);
|
||||
} else if (e.value === "limited" && $linkShares.length > 0) {
|
||||
const share = $linkShares[0];
|
||||
await trail_link_share_delete(share);
|
||||
}
|
||||
|
||||
await trail_link_share_index(trail.id);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
@@ -159,6 +201,14 @@
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<h4 class="font-semibold mt-4 mb-2">{$_("public-access")}</h4>
|
||||
<RadioGroup
|
||||
name="account"
|
||||
items={publicShareItems}
|
||||
selected={$linkShares.length == 0 ? 0 : 1}
|
||||
onchange={handlePublicAccessChange}
|
||||
></RadioGroup>
|
||||
</div>
|
||||
{/snippet}
|
||||
{#snippet footer()}
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
"error-updating-password": "Fehler beim Aktualisieren des Passworts",
|
||||
"error-updating-strava-integration": "Fehler bei Aktualisierung der Komoot-Integration",
|
||||
"est-duration": "Gesch. Dauer",
|
||||
"everyone-with-the-link": "",
|
||||
"explore": "Erkunden",
|
||||
"explore-some-trails": "Erkunde einige Routen",
|
||||
"export": "Exportieren",
|
||||
@@ -215,6 +216,7 @@
|
||||
"like-status": "\"Gefällt mir\" Status",
|
||||
"liked": "Gefällt mir",
|
||||
"likes": "Likes",
|
||||
"limited": "",
|
||||
"link-copied": "Link kopiert!",
|
||||
"list": "{n, plural, =1 {Liste} other {Listen}}",
|
||||
"list-not-shared": "Mit niemandem geteilt",
|
||||
@@ -311,6 +313,9 @@
|
||||
"private": "Privat",
|
||||
"profile": "Profil",
|
||||
"public": "Öffentlich",
|
||||
"public-access": "",
|
||||
"public-share-everyone": "",
|
||||
"public-share-limited": "",
|
||||
"public-transport": "",
|
||||
"radius": "Radius",
|
||||
"railway-station": "",
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
"error-updating-password": "Error updating password",
|
||||
"error-updating-strava-integration": "Error updating komoot integration",
|
||||
"est-duration": "Est. duration",
|
||||
"everyone-with-the-link": "Everyone with the link",
|
||||
"explore": "Explore",
|
||||
"explore-some-trails": "Explore some trails",
|
||||
"export": "Export",
|
||||
@@ -215,6 +216,7 @@
|
||||
"like-status": "Like Status",
|
||||
"liked": "Liked",
|
||||
"likes": "Likes",
|
||||
"limited": "Limited",
|
||||
"link-copied": "Link copied!",
|
||||
"list": "{n, plural, =1 {List} other {Lists}}",
|
||||
"list-not-shared": "Not shared with anyone",
|
||||
@@ -311,6 +313,9 @@
|
||||
"private": "Private",
|
||||
"profile": "Profile",
|
||||
"public": "Public",
|
||||
"public-access": "Public access",
|
||||
"public-share-everyone": "Everyone on the internet with the link can see this trail",
|
||||
"public-share-limited": "Only people with access can open the link",
|
||||
"public-transport": "Public transport",
|
||||
"radius": "Radius",
|
||||
"railway-station": "Railway station",
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
"error-updating-password": "Error actualizando la contraseña",
|
||||
"error-updating-strava-integration": "Error al actualizar la integración con komoot",
|
||||
"est-duration": "Duración estimada",
|
||||
"everyone-with-the-link": "",
|
||||
"explore": "Explora",
|
||||
"explore-some-trails": "Explora alguna ruta",
|
||||
"export": "Exportar",
|
||||
@@ -215,6 +216,7 @@
|
||||
"like-status": "Like Status",
|
||||
"liked": "Liked",
|
||||
"likes": "Likes",
|
||||
"limited": "",
|
||||
"link-copied": "¡Enlace copiado!",
|
||||
"list": "{n, plural, one {}=1 {Lista} other {Listas}}",
|
||||
"list-not-shared": "No compartido con ninguno",
|
||||
@@ -311,6 +313,9 @@
|
||||
"private": "Privado",
|
||||
"profile": "Perfil",
|
||||
"public": "Público",
|
||||
"public-access": "",
|
||||
"public-share-everyone": "",
|
||||
"public-share-limited": "",
|
||||
"public-transport": "",
|
||||
"radius": "Radio",
|
||||
"railway-station": "",
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
"error-updating-password": "Error updating password",
|
||||
"error-updating-strava-integration": "Error updating komoot integration",
|
||||
"est-duration": "Est. duration",
|
||||
"everyone-with-the-link": "",
|
||||
"explore": "Explore",
|
||||
"explore-some-trails": "Explore some trails",
|
||||
"export": "Export",
|
||||
@@ -215,6 +216,7 @@
|
||||
"like-status": "Like Status",
|
||||
"liked": "Liked",
|
||||
"likes": "Likes",
|
||||
"limited": "",
|
||||
"link-copied": "Link copied!",
|
||||
"list": "{n, plural, =1 {List} other {Lists}}",
|
||||
"list-not-shared": "Not shared with anyone",
|
||||
@@ -311,6 +313,9 @@
|
||||
"private": "Private",
|
||||
"profile": "Profile",
|
||||
"public": "Public",
|
||||
"public-access": "",
|
||||
"public-share-everyone": "",
|
||||
"public-share-limited": "",
|
||||
"public-transport": "",
|
||||
"radius": "Radius",
|
||||
"railway-station": "",
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
"error-updating-password": "Erreur lors de la mise à jour du mot de passe",
|
||||
"error-updating-strava-integration": "Erreur lors de la mise à jour de l'intégration Komoot",
|
||||
"est-duration": "Temps estimé",
|
||||
"everyone-with-the-link": "",
|
||||
"explore": "Explorer",
|
||||
"explore-some-trails": "Explorer les itinéraires",
|
||||
"export": "Exporter",
|
||||
@@ -215,6 +216,7 @@
|
||||
"like-status": "Like Status",
|
||||
"liked": "Aimé",
|
||||
"likes": "Likes",
|
||||
"limited": "",
|
||||
"link-copied": "Lien copié !",
|
||||
"list": "{n, plural, =1 {Liste} other {Listes}}",
|
||||
"list-not-shared": "Non partagé avec quiconque",
|
||||
@@ -311,6 +313,9 @@
|
||||
"private": "Privé",
|
||||
"profile": "Profile",
|
||||
"public": "Publique",
|
||||
"public-access": "",
|
||||
"public-share-everyone": "",
|
||||
"public-share-limited": "",
|
||||
"public-transport": "",
|
||||
"radius": "Rayon",
|
||||
"railway-station": "",
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
"error-updating-password": "Error updating password",
|
||||
"error-updating-strava-integration": "Error updating komoot integration",
|
||||
"est-duration": "Becsült időtartam",
|
||||
"everyone-with-the-link": "",
|
||||
"explore": "Felfedezés",
|
||||
"explore-some-trails": "Fedezzen fel néhány ösvényt",
|
||||
"export": "Export",
|
||||
@@ -215,6 +216,7 @@
|
||||
"like-status": "Like Status",
|
||||
"liked": "Liked",
|
||||
"likes": "Likes",
|
||||
"limited": "",
|
||||
"link-copied": "Link copied!",
|
||||
"list": "{n, plural, =1 {Lista} other {Listák}}",
|
||||
"list-not-shared": "Not shared with anyone",
|
||||
@@ -311,6 +313,9 @@
|
||||
"private": "Private",
|
||||
"profile": "Profil",
|
||||
"public": "Publikus",
|
||||
"public-access": "",
|
||||
"public-share-everyone": "",
|
||||
"public-share-limited": "",
|
||||
"public-transport": "",
|
||||
"radius": "Átmérő",
|
||||
"railway-station": "",
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
"error-updating-password": "Errore nell'aggiornamento della password",
|
||||
"error-updating-strava-integration": "Error updating komoot integration",
|
||||
"est-duration": "Durata stimata",
|
||||
"everyone-with-the-link": "",
|
||||
"explore": "Esplora",
|
||||
"explore-some-trails": "Esplora alcuni percorsi",
|
||||
"export": "Esporta",
|
||||
@@ -215,6 +216,7 @@
|
||||
"like-status": "Like Status",
|
||||
"liked": "Liked",
|
||||
"likes": "Likes",
|
||||
"limited": "",
|
||||
"link-copied": "Link copiato",
|
||||
"list": "{n, plural, =1 {Lista} other {Liste}}",
|
||||
"list-not-shared": "Non condivisa",
|
||||
@@ -311,6 +313,9 @@
|
||||
"private": "Privato",
|
||||
"profile": "Profilo",
|
||||
"public": "Pubblico",
|
||||
"public-access": "",
|
||||
"public-share-everyone": "",
|
||||
"public-share-limited": "",
|
||||
"public-transport": "",
|
||||
"radius": "Raggio",
|
||||
"railway-station": "",
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
"error-updating-password": "Fout bij bijwerken van wachtwoord",
|
||||
"error-updating-strava-integration": "Fout bij bijwerken van Komoot integratie",
|
||||
"est-duration": "Geschatte duur",
|
||||
"everyone-with-the-link": "",
|
||||
"explore": "Verkennen",
|
||||
"explore-some-trails": "Verken enkele routes",
|
||||
"export": "Exporteer",
|
||||
@@ -215,6 +216,7 @@
|
||||
"like-status": "",
|
||||
"liked": "Leuk gevonden",
|
||||
"likes": "Vind-ik-leuks",
|
||||
"limited": "",
|
||||
"link-copied": "Link gekopieerd",
|
||||
"list": "{n, plural, =1 {Lijst} other {Lijsten}}",
|
||||
"list-not-shared": "Niet gedeeld met iemand",
|
||||
@@ -311,6 +313,9 @@
|
||||
"private": "Privaat",
|
||||
"profile": "Profiel",
|
||||
"public": "Openbaar",
|
||||
"public-access": "",
|
||||
"public-share-everyone": "",
|
||||
"public-share-limited": "",
|
||||
"public-transport": "",
|
||||
"radius": "Straal",
|
||||
"railway-station": "",
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
"error-updating-password": "Błąd podczas aktualizacji hasła",
|
||||
"error-updating-strava-integration": "Błąd aktualizacji integracji kamoot",
|
||||
"est-duration": "Szacowany czas",
|
||||
"everyone-with-the-link": "",
|
||||
"explore": "Eksploruj",
|
||||
"explore-some-trails": "Eksploruj różne szlaki",
|
||||
"export": "Eksportuj",
|
||||
@@ -215,6 +216,7 @@
|
||||
"like-status": "Status polubień",
|
||||
"liked": "Polubiony",
|
||||
"likes": "Polubienia",
|
||||
"limited": "",
|
||||
"link-copied": "Link skopiowany!",
|
||||
"list": "{n, plural, =1 {Lista} other {Listy}}",
|
||||
"list-not-shared": "Nikomu nie udostępniony",
|
||||
@@ -311,6 +313,9 @@
|
||||
"private": "Prywatne",
|
||||
"profile": "Profil",
|
||||
"public": "Publiczny",
|
||||
"public-access": "",
|
||||
"public-share-everyone": "",
|
||||
"public-share-limited": "",
|
||||
"public-transport": "",
|
||||
"radius": "Promień",
|
||||
"railway-station": "",
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
"error-updating-password": "Erro ao atualizar password",
|
||||
"error-updating-strava-integration": "Error updating komoot integration",
|
||||
"est-duration": "Duração prevista",
|
||||
"everyone-with-the-link": "",
|
||||
"explore": "Explorar",
|
||||
"explore-some-trails": "Explore algumas trilhas",
|
||||
"export": "Exportar",
|
||||
@@ -215,6 +216,7 @@
|
||||
"like-status": "Like Status",
|
||||
"liked": "Liked",
|
||||
"likes": "Likes",
|
||||
"limited": "",
|
||||
"link-copied": "Link copiado!",
|
||||
"list": "{n, plural, =1 {Lista} other {Listas}}",
|
||||
"list-not-shared": "Não partilhado com ninguém",
|
||||
@@ -311,6 +313,9 @@
|
||||
"private": "Private",
|
||||
"profile": "Perfil",
|
||||
"public": "Público",
|
||||
"public-access": "",
|
||||
"public-share-everyone": "",
|
||||
"public-share-limited": "",
|
||||
"public-transport": "",
|
||||
"radius": "Raio",
|
||||
"railway-station": "",
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
"error-updating-password": "Ошибка изменения пароля",
|
||||
"error-updating-strava-integration": "Ошибка обновления Strava",
|
||||
"est-duration": "Продолжительность",
|
||||
"everyone-with-the-link": "",
|
||||
"explore": "Изучить",
|
||||
"explore-some-trails": "Изучите треки",
|
||||
"export": "Экспорт",
|
||||
@@ -215,6 +216,7 @@
|
||||
"like-status": "Статус лайка",
|
||||
"liked": "Лайкнуто",
|
||||
"likes": "Лайков",
|
||||
"limited": "",
|
||||
"link-copied": "Ссылка скопирована!",
|
||||
"list": "{n, plural, =1 {Список} other {Списки}}",
|
||||
"list-not-shared": "Нет общего доступа",
|
||||
@@ -311,6 +313,9 @@
|
||||
"private": "Зкрытый",
|
||||
"profile": "Профиль",
|
||||
"public": "Открытый",
|
||||
"public-access": "",
|
||||
"public-share-everyone": "",
|
||||
"public-share-limited": "",
|
||||
"public-transport": "",
|
||||
"radius": "Радиус",
|
||||
"railway-station": "",
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
"error-updating-password": "更新密码失败",
|
||||
"error-updating-strava-integration": "Error updating komoot integration",
|
||||
"est-duration": "预计时长",
|
||||
"everyone-with-the-link": "",
|
||||
"explore": "探索",
|
||||
"explore-some-trails": "探索行程",
|
||||
"export": "导出",
|
||||
@@ -215,6 +216,7 @@
|
||||
"like-status": "Like Status",
|
||||
"liked": "Liked",
|
||||
"likes": "Likes",
|
||||
"limited": "",
|
||||
"link-copied": "链接已复制",
|
||||
"list": "{n, plural, =1 {列表} other {列表}}",
|
||||
"list-not-shared": "未与任何人分享",
|
||||
@@ -311,6 +313,9 @@
|
||||
"private": "Private",
|
||||
"profile": "个人资料",
|
||||
"public": "公开",
|
||||
"public-access": "",
|
||||
"public-share-everyone": "",
|
||||
"public-share-limited": "",
|
||||
"public-transport": "",
|
||||
"radius": "半径",
|
||||
"railway-station": "",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { z } from "zod";
|
||||
|
||||
const RecordOptionsSchema = z.object({
|
||||
expand: z.string().optional(),
|
||||
query: z.record(z.string()).optional(),
|
||||
requestKey: z.string().optional()
|
||||
})
|
||||
|
||||
|
||||
16
web/src/lib/models/api/trail_link_share_schema.ts
Normal file
16
web/src/lib/models/api/trail_link_share_schema.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { z, ZodType } from "zod";
|
||||
import type { TrailShare } from "../trail_share";
|
||||
import type { TrailLinkShare } from "../trail_link_share";
|
||||
|
||||
const TrailLinkShareCreateSchema = z.object({
|
||||
trail: z.string().length(15),
|
||||
permission: z.enum(["view", "edit"])
|
||||
|
||||
}) satisfies ZodType<TrailLinkShare>
|
||||
|
||||
const TrailLinkShareUpdateSchema = z.object({
|
||||
permission: z.enum(["view", "edit"])
|
||||
}) satisfies ZodType<Partial<TrailShare>>
|
||||
|
||||
|
||||
export { TrailLinkShareCreateSchema, TrailLinkShareUpdateSchema };
|
||||
13
web/src/lib/models/trail_link_share.ts
Normal file
13
web/src/lib/models/trail_link_share.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
export class TrailLinkShare {
|
||||
id?: string;
|
||||
trail: string;
|
||||
token?: string;
|
||||
permission: "view" | "edit"
|
||||
|
||||
|
||||
constructor(trail: string, permission: "view" | "edit") {
|
||||
this.trail = trail;
|
||||
this.permission = permission
|
||||
}
|
||||
}
|
||||
66
web/src/lib/stores/trail_link_share_store.ts
Normal file
66
web/src/lib/stores/trail_link_share_store.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import type { TrailLinkShare } from "$lib/models/trail_link_share";
|
||||
import { APIError } from "$lib/util/api_util";
|
||||
import { type ListResult } from "pocketbase";
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
|
||||
export const linkShares: Writable<TrailLinkShare[]> = writable([])
|
||||
|
||||
|
||||
export async function trail_link_share_index(trail?: string, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||
let filter = ""
|
||||
filter += `trail='${trail}'`
|
||||
|
||||
const r = await f('/api/v1/trail-link-share?' + new URLSearchParams({
|
||||
filter: filter,
|
||||
expand: "actor"
|
||||
}), {
|
||||
method: 'GET',
|
||||
})
|
||||
|
||||
if (!r.ok) {
|
||||
const response = await r.json();
|
||||
throw new APIError(r.status, response.message, response.detail)
|
||||
}
|
||||
|
||||
const response: ListResult<TrailLinkShare> = await r.json();
|
||||
|
||||
linkShares.set(response.items);
|
||||
|
||||
return response.items;
|
||||
|
||||
}
|
||||
|
||||
export async function trail_link_share_create(share: TrailLinkShare) {
|
||||
let r = await fetch('/api/v1/trail-link-share', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(share),
|
||||
})
|
||||
|
||||
if (!r.ok) {
|
||||
const response = await r.json();
|
||||
throw new APIError(r.status, response.message, response.detail)
|
||||
}
|
||||
}
|
||||
|
||||
export async function trail_link_share_update(share: TrailLinkShare) {
|
||||
let r = await fetch('/api/v1/trail-link-share/' + share.id, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(share),
|
||||
})
|
||||
|
||||
if (!r.ok) {
|
||||
const response = await r.json();
|
||||
throw new APIError(r.status, response.message, response.detail)
|
||||
}
|
||||
}
|
||||
|
||||
export async function trail_link_share_delete(share: TrailLinkShare) {
|
||||
const r = await fetch('/api/v1/trail-link-share/' + share.id, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
if (!r.ok) {
|
||||
const response = await r.json();
|
||||
throw new APIError(r.status, response.message, response.detail)
|
||||
}
|
||||
}
|
||||
@@ -133,12 +133,12 @@ export async function trails_search_bounding_box(northEast: M.LngLat, southWest:
|
||||
|
||||
}
|
||||
|
||||
export async function trails_show(id: string, handle?: string, loadGPX?: boolean, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||
export async function trails_show(id: string, handle?: string, share?: 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_via_trail,summit_logs_via_trail.author,trail_share_via_trail.actor,trail_like_via_trail,tags,author",
|
||||
...(handle ? { handle } : {})
|
||||
|
||||
...(handle ? { handle } : {}),
|
||||
...(share ? { share } : {})
|
||||
}), {
|
||||
method: 'GET',
|
||||
})
|
||||
|
||||
@@ -34,6 +34,7 @@ export enum Collection {
|
||||
summit_logs = "summit_logs",
|
||||
trail_like = "trail_like",
|
||||
trail_share = "trail_share",
|
||||
trail_link_share = "trail_link_share",
|
||||
trails = "trails",
|
||||
tags = "tags",
|
||||
waypoints = "waypoints",
|
||||
|
||||
@@ -81,7 +81,7 @@ export async function trail2gpx(trail: Trail, user?: AuthRecord) {
|
||||
if (!trail.expand?.gpx_data) {
|
||||
// no gpx_data -> empty trail?
|
||||
// or just not expanded? -> expand now
|
||||
const response = await trails_show(trail.id!, handleFromRecordWithIRI(trail), true);
|
||||
const response = await trails_show(trail.id!, handleFromRecordWithIRI(trail), undefined, true);
|
||||
|
||||
if (!response.expand?.gpx_data) {
|
||||
throw Error("Trail has no GPX data")
|
||||
|
||||
26
web/src/routes/api/v1/trail-link-share/+server.ts
Normal file
26
web/src/routes/api/v1/trail-link-share/+server.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { TrailLinkShareCreateSchema } from '$lib/models/api/trail_link_share_schema';
|
||||
import type { TrailLinkShare } from '$lib/models/trail_link_share';
|
||||
import { Collection, handleError, list } from '$lib/util/api_util';
|
||||
import { json, type RequestEvent } from '@sveltejs/kit';
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
try {
|
||||
const r = await list<TrailLinkShare>(event, Collection.trail_link_share);
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(event: RequestEvent) {
|
||||
try {
|
||||
const data = await event.request.json();
|
||||
const safeData = TrailLinkShareCreateSchema.parse(data);
|
||||
|
||||
const r = await event.locals.pb.collection(Collection.trail_link_share).create<TrailLinkShare>(safeData)
|
||||
|
||||
return json(r);
|
||||
} catch (e) {
|
||||
return handleError(e)
|
||||
}
|
||||
}
|
||||
31
web/src/routes/api/v1/trail-link-share/[id]/+server.ts
Normal file
31
web/src/routes/api/v1/trail-link-share/[id]/+server.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { TrailLinkShareUpdateSchema } from "$lib/models/api/trail_link_share_schema";
|
||||
import type { TrailLinkShare } from "$lib/models/trail_link_share";
|
||||
import { Collection, handleError, remove, show, update } from "$lib/util/api_util";
|
||||
import { json, type RequestEvent } from "@sveltejs/kit";
|
||||
|
||||
export async function GET(event: RequestEvent) {
|
||||
try {
|
||||
const r = await show<TrailLinkShare>(event, Collection.trail_link_share)
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
return handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(event: RequestEvent) {
|
||||
try {
|
||||
const r = await update<TrailLinkShare>(event, TrailLinkShareUpdateSchema, Collection.trail_link_share)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
return handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(event: RequestEvent) {
|
||||
try {
|
||||
const r = await remove(event, Collection.trail_link_share)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
return handleError(e)
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,9 @@ export async function GET(event: RequestEvent) {
|
||||
event.url.searchParams.delete("handle")
|
||||
|
||||
const safeSearchParams = RecordOptionsSchema.parse(Object.fromEntries(event.url.searchParams));
|
||||
|
||||
if (event.url.searchParams.has("share")) {
|
||||
safeSearchParams.query = { share: event.url.searchParams.get("share")! }
|
||||
}
|
||||
let origin = new URL(actor.iri).origin
|
||||
let iri = `${origin}/api/v1/trail/${event.params.id}`
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@
|
||||
const fullTrail = await trails_show(
|
||||
trail.iri ? trail.iri.substring(trail.iri.length - 15) : trail.id!,
|
||||
handleFromRecordWithIRI(trail),
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
selectedTrail = fullTrail;
|
||||
@@ -251,7 +252,8 @@
|
||||
disabled={!selectedList}
|
||||
onclick={back}><i class="fa fa-arrow-left"></i></button
|
||||
>
|
||||
<Search bind:value={filter.q} onupdate={() => updateFilter()}></Search>
|
||||
<Search bind:value={filter.q} onupdate={() => updateFilter()}
|
||||
></Search>
|
||||
<button
|
||||
aria-label="Toggle filter"
|
||||
class="btn-icon"
|
||||
|
||||
@@ -187,7 +187,7 @@
|
||||
}
|
||||
|
||||
async function handleSearchClick(item: SearchItem) {
|
||||
const trail = await trails_show(item.value, undefined, true);
|
||||
const trail = await trails_show(item.value, undefined, undefined, true);
|
||||
$formData.trails?.push(trail.id!);
|
||||
$formData.expand = {
|
||||
trails: [...($formData.expand?.trails ?? []), trail],
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { trails_show } from "$lib/stores/trail_store";
|
||||
import { splitUsername } from "$lib/util/activitypub_util";
|
||||
import { APIError } from "$lib/util/api_util";
|
||||
import { error, type NumericRange, type ServerLoad } from "@sveltejs/kit";
|
||||
|
||||
export const load: ServerLoad = async ({ params, locals, fetch }) => {
|
||||
export const load: ServerLoad = async ({ params, url, fetch }) => {
|
||||
try {
|
||||
const trail = await trails_show(params.id!, params.handle, true, fetch)
|
||||
const trail = await trails_show(params.id!, params.handle, url.searchParams.get("share") ?? undefined, true, fetch)
|
||||
return { trail };
|
||||
} catch (e) {
|
||||
if (e instanceof APIError) {
|
||||
|
||||
@@ -2,9 +2,9 @@ import { trails_show } from "$lib/stores/trail_store";
|
||||
import { APIError } from "$lib/util/api_util";
|
||||
import { error, type Load, type NumericRange } from "@sveltejs/kit";
|
||||
|
||||
export const load: Load = async ({ params, fetch }) => {
|
||||
export const load: Load = async ({ params, fetch, url }) => {
|
||||
try {
|
||||
const trail = await trails_show(params.id!, params.handle, true, fetch)
|
||||
const trail = await trails_show(params.id!, params.handle, url.searchParams.get("share") ?? undefined, true, fetch)
|
||||
return { trail };
|
||||
} catch (e) {
|
||||
if (e instanceof APIError) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { currentUser } from "$lib/stores/user_store";
|
||||
import { error, type Load } from "@sveltejs/kit";
|
||||
import { get } from "svelte/store";
|
||||
|
||||
export const load: Load = async ({ params, fetch }) => {
|
||||
export const load: Load = async ({ params, fetch, url }) => {
|
||||
const user = get(currentUser)
|
||||
|
||||
if (!params.id) {
|
||||
@@ -19,7 +19,7 @@ export const load: Load = async ({ params, fetch }) => {
|
||||
if (params.id === "new") {
|
||||
trail = new Trail("", { category: categories[0] });
|
||||
} else {
|
||||
trail = await trails_show(params.id, undefined, true, fetch);
|
||||
trail = await trails_show(params.id, undefined, url.searchParams.get("share") ?? undefined, true, fetch);
|
||||
}
|
||||
|
||||
return { trail: trail, lists: lists }
|
||||
|
||||
@@ -2,9 +2,9 @@ import { trails_show } from "$lib/stores/trail_store";
|
||||
import { APIError } from "$lib/util/api_util";
|
||||
import { error, type Load, type NumericRange } from "@sveltejs/kit";
|
||||
|
||||
export const load: Load = async ({ params, fetch }) => {
|
||||
export const load: Load = async ({ params, fetch, url }) => {
|
||||
try {
|
||||
const trail = await trails_show(params.id!, params.handle, true, fetch)
|
||||
const trail = await trails_show(params.id!, params.handle, url.searchParams.get("share") ?? undefined, true, fetch)
|
||||
|
||||
return { trail }
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user