adds feed
This commit is contained in:
@@ -466,13 +466,20 @@ func ProcessCreateOrUpdateActivity(app core.App, actor *core.Record, activity pu
|
||||
}
|
||||
|
||||
func processCreateOrUpdateTrailActivity(activity pub.Activity, app core.App, actor *core.Record) error {
|
||||
|
||||
// no need to do anything if the actor is local
|
||||
if actor.GetBool("isLocal") {
|
||||
return nil
|
||||
trail, err := util.TrailFromActivity(activity, app, actor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
trail, err := util.TrailFromActivity(activity, app, actor)
|
||||
trailAuthor, err := app.FindRecordById("activitypub_actors", trail.GetString("author"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = util.InsertIntoFeed(app, actor.Id, trailAuthor.Id, trail.Id, util.TrailFeed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
trailObject, _ := pub.ToObject(activity.Object)
|
||||
|
||||
@@ -792,13 +799,20 @@ func processCreateOrUpdateSummitLogActivity(activity pub.Activity, app core.App,
|
||||
}
|
||||
|
||||
func processCreateOrUpdateListActivity(activity pub.Activity, app core.App, actor *core.Record) error {
|
||||
|
||||
// no need to do anything if the actor is local
|
||||
if actor.GetBool("isLocal") {
|
||||
return nil
|
||||
list, err := util.ListFromActivity(activity, app, actor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := util.ListFromActivity(activity, app, actor)
|
||||
listAuthor, err := app.FindRecordById("activitypub_actors", list.GetString("author"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = util.InsertIntoFeed(app, actor.Id, listAuthor.Id, list.Id, util.ListFeed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
37
db/main.go
37
db/main.go
@@ -120,6 +120,8 @@ func setupEventHandlers(app *pocketbase.PocketBase, client meilisearch.ServiceMa
|
||||
app.OnRecordUpdate("integrations").BindFunc(updateIntegrationHandler())
|
||||
app.OnRecordAfterUpdateSuccess("integrations").BindFunc(createUpdateIntegrationSuccessHandler())
|
||||
|
||||
app.OnRecordsListRequest("feed").BindFunc(listFeedHandler())
|
||||
|
||||
app.OnRecordCreateRequest().BindFunc(sanitizeHTML())
|
||||
app.OnRecordUpdateRequest().BindFunc(sanitizeHTML())
|
||||
|
||||
@@ -242,6 +244,11 @@ func createTrailHandler(client meilisearch.ServiceManager) func(e *core.RecordEv
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = util.InsertIntoFeed(e.App, author.Id, author.Id, record.Id, util.TrailFeed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -616,6 +623,11 @@ func createListHandler(client meilisearch.ServiceManager) func(e *core.RecordEve
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = util.InsertIntoFeed(e.App, author.Id, author.Id, record.Id, util.ListFeed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -877,6 +889,31 @@ func changeUserEmailHandler() func(e *core.RecordRequestEmailChangeRequestEvent)
|
||||
}
|
||||
}
|
||||
|
||||
func listFeedHandler() func(e *core.RecordsListRequestEvent) error {
|
||||
return func(e *core.RecordsListRequestEvent) error {
|
||||
|
||||
for _, r := range e.Records {
|
||||
var item *core.Record
|
||||
var err error
|
||||
switch r.GetString("type") {
|
||||
case string(util.TrailFeed):
|
||||
item, err = e.App.FindRecordById("trails", r.GetString("item"))
|
||||
case string(util.ListFeed):
|
||||
item, err = e.App.FindRecordById("lists", r.GetString("item"))
|
||||
case string(util.SummitLogFeed):
|
||||
item, err = e.App.FindRecordById("summit_logs", r.GetString("item"))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.MergeExpand(map[string]any{"item": item})
|
||||
}
|
||||
return e.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func onBeforeServeHandler(client meilisearch.ServiceManager) func(se *core.ServeEvent) error {
|
||||
return func(se *core.ServeEvent) error {
|
||||
registerRoutes(se, client)
|
||||
|
||||
115
db/migrations/1752231390_created_feed.go
Normal file
115
db/migrations/1752231390_created_feed.go
Normal file
@@ -0,0 +1,115 @@
|
||||
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": "pbc_1295301207",
|
||||
"hidden": false,
|
||||
"id": "relation1148540665",
|
||||
"maxSelect": 1,
|
||||
"minSelect": 0,
|
||||
"name": "actor",
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "relation"
|
||||
},
|
||||
{
|
||||
"cascadeDelete": true,
|
||||
"collectionId": "pbc_1295301207",
|
||||
"hidden": false,
|
||||
"id": "relation3182418120",
|
||||
"maxSelect": 1,
|
||||
"minSelect": 0,
|
||||
"name": "author",
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "relation"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"hidden": false,
|
||||
"id": "text521872670",
|
||||
"max": 15,
|
||||
"min": 15,
|
||||
"name": "item",
|
||||
"pattern": "^[a-z0-9]+$",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"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_72164123",
|
||||
"indexes": [],
|
||||
"listRule": null,
|
||||
"name": "feed",
|
||||
"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_72164123")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Delete(collection)
|
||||
})
|
||||
}
|
||||
46
db/migrations/1752232212_updated_feed.go
Normal file
46
db/migrations/1752232212_updated_feed.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"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_72164123")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add field
|
||||
if err := collection.Fields.AddMarshaledJSONAt(4, []byte(`{
|
||||
"hidden": false,
|
||||
"id": "select2363381545",
|
||||
"maxSelect": 1,
|
||||
"name": "type",
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "select",
|
||||
"values": [
|
||||
"trail",
|
||||
"list",
|
||||
"summit_log"
|
||||
]
|
||||
}`)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Save(collection)
|
||||
}, func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId("pbc_72164123")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// remove field
|
||||
collection.Fields.RemoveById("select2363381545")
|
||||
|
||||
return app.Save(collection)
|
||||
})
|
||||
}
|
||||
40
db/migrations/1752233467_updated_feed.go
Normal file
40
db/migrations/1752233467_updated_feed.go
Normal file
@@ -0,0 +1,40 @@
|
||||
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_72164123")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update collection data
|
||||
if err := json.Unmarshal([]byte(`{
|
||||
"listRule": "actor.user = @request.auth.id"
|
||||
}`), &collection); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Save(collection)
|
||||
}, func(app core.App) error {
|
||||
collection, err := app.FindCollectionByNameOrId("pbc_72164123")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update collection data
|
||||
if err := json.Unmarshal([]byte(`{
|
||||
"listRule": null
|
||||
}`), &collection); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return app.Save(collection)
|
||||
})
|
||||
}
|
||||
100
db/migrations/1752239359_create_initial_feed.go
Normal file
100
db/migrations/1752239359_create_initial_feed.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"pocketbase/util"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(app core.App) error {
|
||||
trails, err := app.FindAllRecords("trails")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, t := range trails {
|
||||
trailAuthor, err := app.FindRecordById("activitypub_actors", t.GetString("author"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
feed, err := util.InsertIntoFeed(app, trailAuthor.Id, trailAuthor.Id, t.Id, util.TrailFeed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
feed.SetRaw("created", t.GetDateTime("created"))
|
||||
err = app.Save(feed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
followers, err := app.FindRecordsByFilter("follows", "followee={:author}", "", -1, 0, dbx.Params{"author": trailAuthor.Id})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range followers {
|
||||
feed, err = util.InsertIntoFeed(app, f.GetString("follower"), trailAuthor.Id, t.Id, util.TrailFeed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
feed.SetRaw("created", t.GetDateTime("created"))
|
||||
err = app.Save(feed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lists, err := app.FindAllRecords("lists")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, t := range lists {
|
||||
listAuthor, err := app.FindRecordById("activitypub_actors", t.GetString("author"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
feed, err := util.InsertIntoFeed(app, listAuthor.Id, listAuthor.Id, t.Id, util.ListFeed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
feed.SetRaw("created", t.GetDateTime("created"))
|
||||
err = app.Save(feed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
followers, err := app.FindRecordsByFilter("follows", "followee={:author}", "", -1, 0, dbx.Params{"author": listAuthor.Id})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range followers {
|
||||
feed, err = util.InsertIntoFeed(app, f.GetString("follower"), listAuthor.Id, t.Id, util.ListFeed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
feed.SetRaw("created", t.GetDateTime("created"))
|
||||
err = app.Save(feed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, func(app core.App) error {
|
||||
_, err := app.DB().
|
||||
NewQuery("DELETE FROM feed;").
|
||||
Execute()
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
@@ -176,6 +176,11 @@ func TrailFromActivity(activity pub.Activity, app core.App, actor *core.Record)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// this trail exists already
|
||||
// nothing more to do
|
||||
|
||||
return record, nil
|
||||
}
|
||||
|
||||
var distance, duration, elevation_gain, elevation_loss float64
|
||||
@@ -442,6 +447,11 @@ func ListFromActivity(activity pub.Activity, app core.App, actor *core.Record) (
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// this list exists already
|
||||
// nothing more to do
|
||||
|
||||
return record, nil
|
||||
}
|
||||
|
||||
record.Set("name", l.Name.First().Value)
|
||||
|
||||
27
db/util/feed.go
Normal file
27
db/util/feed.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package util
|
||||
|
||||
import "github.com/pocketbase/pocketbase/core"
|
||||
|
||||
type FeedType string
|
||||
|
||||
const (
|
||||
ListFeed FeedType = "list"
|
||||
TrailFeed FeedType = "trail"
|
||||
SummitLogFeed FeedType = "summit_log"
|
||||
)
|
||||
|
||||
func InsertIntoFeed(app core.App, actorId string, authorId string, itemId string, feedType FeedType) (*core.Record, error) {
|
||||
collection, err := app.FindCollectionByNameOrId("feed")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
record := core.NewRecord(collection)
|
||||
|
||||
record.Set("actor", actorId)
|
||||
record.Set("author", authorId)
|
||||
record.Set("item", itemId)
|
||||
record.Set("type", string(feedType))
|
||||
|
||||
return record, app.Save(record)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<svg width="118" height="110" viewBox="0 0 118 110" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M102.734 26.66V30.97" stroke="#4A5059" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M100.585 28.82H104.884" stroke="#4A5059" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14.5745 10.03V14.34" stroke="#4A5059" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.4145 12.18H16.7245" stroke="#4A5059" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M48.0345 1.96C48.5757 1.96 49.0145 1.52124 49.0145 0.98C49.0145 0.43876 48.5757 0 48.0345 0C47.4933 0 47.0545 0.43876 47.0545 0.98C47.0545 1.52124 47.4933 1.96 48.0345 1.96Z" fill="#4A5059"/>
|
||||
<path d="M79.4545 40.92C71.0745 40.92 62.5645 40.59 54.5945 38.37C46.7745 36.17 39.6345 31.99 33.1945 27.17C28.9945 23.99 25.1945 21.47 19.7445 21.85C14.4314 22.1238 9.3459 24.0935 5.2345 27.47C-1.6955 33.53 -0.645498 44.74 2.1245 52.62C6.2845 64.49 18.9845 72.74 29.7045 78.11C42.1345 84.32 55.7845 87.93 69.4845 90C81.4845 91.83 96.8945 93.15 107.295 85.31C116.854 78.11 119.474 61.67 117.134 50.57C116.566 47.2888 114.816 44.3292 112.215 42.25C105.505 37.34 95.5045 40.62 87.9645 40.78C85.1645 40.84 82.3145 40.91 79.4545 40.92Z" fill="#272933"/>
|
||||
<path d="M58.9645 109.46C79.2389 109.46 95.6745 108.435 95.6745 107.17C95.6745 105.905 79.2389 104.88 58.9645 104.88C38.6901 104.88 22.2545 105.905 22.2545 107.17C22.2545 108.435 38.6901 109.46 58.9645 109.46Z" fill="#272933"/>
|
||||
<path d="M74.5592 3.32263L25.6839 9.93083C24.1186 10.1425 23.0212 11.5829 23.2329 13.1482L32.7111 83.2504C32.9227 84.8157 34.3632 85.913 35.9285 85.7014L84.8038 79.0932C86.369 78.8816 87.4664 77.4411 87.2548 75.8758L77.7766 5.77364C77.5649 4.20834 76.1245 3.11099 74.5592 3.32263Z" fill="#4A5059"/>
|
||||
<path d="M90.8045 13.62H41.4845C39.905 13.62 38.6245 14.9005 38.6245 16.48V87.22C38.6245 88.7995 39.905 90.08 41.4845 90.08H90.8045C92.384 90.08 93.6645 88.7995 93.6645 87.22V16.48C93.6645 14.9005 92.384 13.62 90.8045 13.62Z" fill="#2D333E" stroke="#4A5059" stroke-miterlimit="10"/>
|
||||
<path d="M77.9945 68.53H54.2845C53.3953 68.53 52.6745 69.2508 52.6745 70.14V70.15C52.6745 71.0391 53.3953 71.76 54.2845 71.76H77.9945C78.8837 71.76 79.6045 71.0391 79.6045 70.15V70.14C79.6045 69.2508 78.8837 68.53 77.9945 68.53Z" fill="#4A5059"/>
|
||||
<path d="M77.1445 81.73H54.3945C54.1239 81.73 53.9045 81.9494 53.9045 82.22V82.37C53.9045 82.6406 54.1239 82.86 54.3945 82.86H77.1445C77.4151 82.86 77.6345 82.6406 77.6345 82.37V82.22C77.6345 81.9494 77.4151 81.73 77.1445 81.73Z" fill="#4A5059"/>
|
||||
<path d="M82.9445 76.44H49.4345C49.0755 76.44 48.7845 76.731 48.7845 77.09C48.7845 77.449 49.0755 77.74 49.4345 77.74H82.9445C83.3035 77.74 83.5945 77.449 83.5945 77.09C83.5945 76.731 83.3035 76.44 82.9445 76.44Z" fill="#4A5059"/>
|
||||
<path d="M89.0745 15.94H43.2145C42.0768 15.94 41.1545 16.8623 41.1545 18V57.77C41.1545 58.9077 42.0768 59.83 43.2145 59.83H89.0745C90.2122 59.83 91.1345 58.9077 91.1345 57.77V18C91.1345 16.8623 90.2122 15.94 89.0745 15.94Z" fill="#2D333E" stroke="#4A5059" stroke-miterlimit="10"/>
|
||||
<path d="M72.2645 47.51C72.5938 47.5077 72.9144 47.4038 73.1825 47.2126C73.4506 47.0213 73.6532 46.752 73.7626 46.4414C73.872 46.1308 73.8828 45.7939 73.7937 45.4769C73.7045 45.1599 73.5197 44.8781 73.2645 44.67C71.1761 42.9479 68.5476 42.017 65.8408 42.0407C63.1341 42.0645 60.5223 43.0415 58.4645 44.8C58.2148 45.0149 58.0371 45.3013 57.9554 45.6205C57.8736 45.9396 57.8918 46.2762 58.0073 46.5847C58.1229 46.8932 58.3303 47.1589 58.6016 47.3458C58.8729 47.5327 59.195 47.6319 59.5245 47.63L72.2645 47.51Z" stroke="#4A5059" stroke-width="0.71" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M80.4345 37.49L74.6045 34.13L80.4345 30.77" stroke="#4A5059" stroke-width="0.71" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M51.3745 37.49L57.1945 34.13L51.3745 30.77" stroke="#4A5059" stroke-width="0.71" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M19.4045 58.83V63.13" stroke="#4A5059" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M17.2545 60.98H21.5545" stroke="#4A5059" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M100.904 61.87C101.446 61.87 101.884 61.4312 101.884 60.89C101.884 60.3487 101.446 59.91 100.904 59.91C100.363 59.91 99.9245 60.3487 99.9245 60.89C99.9245 61.4312 100.363 61.87 100.904 61.87Z" fill="#4A5059"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,21 @@
|
||||
<svg width="118" height="110" viewBox="0 0 118 110" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M102.731 26.66V30.97" stroke="#BABABA" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M100.581 28.81H104.881" stroke="#BABABA" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14.5712 10.03V14.33" stroke="#BABABA" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.4112 12.18H16.7212" stroke="#BABABA" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M48.0312 1.96C48.5724 1.96 49.0112 1.52124 49.0112 0.98C49.0112 0.43876 48.5724 0 48.0312 0C47.49 0 47.0512 0.43876 47.0512 0.98C47.0512 1.52124 47.49 1.96 48.0312 1.96Z" fill="#CFCFCF"/>
|
||||
<path d="M79.4512 40.92C71.0812 40.92 62.5612 40.58 54.5912 38.36C46.7712 36.19 39.5912 31.98 33.1912 27.11C29.0012 23.94 25.1912 21.42 19.7512 21.8C14.4247 22.0916 9.3336 24.0902 5.23121 27.5C-1.68879 33.5 -0.648793 44.73 2.12121 52.62C6.28121 64.5 18.9412 72.73 29.7012 78.11C42.1312 84.32 55.7912 87.92 69.4812 90C81.4812 91.82 96.9012 93.15 107.301 85.31C116.851 78.11 119.471 61.67 117.131 50.57C116.564 47.2879 114.818 44.3258 112.221 42.24C105.511 37.34 95.5012 40.61 87.9612 40.78C85.1612 40.84 82.3112 40.91 79.4512 40.92Z" fill="#F2F2F2"/>
|
||||
<path d="M58.9712 109.45C79.2456 109.45 95.6812 108.425 95.6812 107.16C95.6812 105.895 79.2456 104.87 58.9712 104.87C38.6968 104.87 22.2612 105.895 22.2612 107.16C22.2612 108.425 38.6968 109.45 58.9712 109.45Z" fill="#F2F2F2"/>
|
||||
<path d="M74.5639 3.32116L25.6886 9.92936C24.1234 10.141 23.026 11.5815 23.2376 13.1468L32.7158 83.2489C32.9275 84.8142 34.3679 85.9116 35.9332 85.6999L84.8085 79.0917C86.3738 78.8801 87.4712 77.4396 87.2595 75.8743L77.7813 5.77217C77.5697 4.20688 76.1292 3.10953 74.5639 3.32116Z" fill="#D2D2D2"/>
|
||||
<path d="M90.8012 13.61H41.4812C39.9017 13.61 38.6212 14.8905 38.6212 16.47V87.21C38.6212 88.7895 39.9017 90.07 41.4812 90.07H90.8012C92.3807 90.07 93.6612 88.7895 93.6612 87.21V16.47C93.6612 14.8905 92.3807 13.61 90.8012 13.61Z" fill="white" stroke="#BABABA" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M77.9912 68.53H54.2812C53.392 68.53 52.6712 69.2508 52.6712 70.14V70.15C52.6712 71.0392 53.392 71.76 54.2812 71.76H77.9912C78.8804 71.76 79.6012 71.0392 79.6012 70.15V70.14C79.6012 69.2508 78.8804 68.53 77.9912 68.53Z" fill="#D2D2D2"/>
|
||||
<path d="M77.1412 81.73H54.3912C54.1206 81.73 53.9012 81.9494 53.9012 82.22V82.37C53.9012 82.6406 54.1206 82.86 54.3912 82.86H77.1412C77.4118 82.86 77.6312 82.6406 77.6312 82.37V82.22C77.6312 81.9494 77.4118 81.73 77.1412 81.73Z" fill="#D2D2D2"/>
|
||||
<path d="M82.9412 76.44H49.4312C49.0722 76.44 48.7812 76.731 48.7812 77.09C48.7812 77.449 49.0722 77.74 49.4312 77.74H82.9412C83.3002 77.74 83.5912 77.449 83.5912 77.09C83.5912 76.731 83.3002 76.44 82.9412 76.44Z" fill="#D2D2D2"/>
|
||||
<path d="M89.0712 15.94H43.2112C42.0735 15.94 41.1512 16.8623 41.1512 18V57.77C41.1512 58.9077 42.0735 59.83 43.2112 59.83H89.0712C90.2089 59.83 91.1312 58.9077 91.1312 57.77V18C91.1312 16.8623 90.2089 15.94 89.0712 15.94Z" fill="white" stroke="#BABABA" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M72.2612 47.5C72.5879 47.4922 72.9044 47.3852 73.1688 47.1933C73.4332 47.0013 73.633 46.7334 73.7415 46.4252C73.85 46.117 73.8622 45.783 73.7764 45.4677C73.6906 45.1525 73.5109 44.8707 73.2612 44.66C71.1753 42.9323 68.5458 41.9978 65.8375 42.0216C63.1291 42.0454 60.5164 43.026 58.4612 44.79C58.2135 45 58.0356 45.2803 57.9511 45.5938C57.8666 45.9073 57.8794 46.2391 57.9879 46.5451C58.0965 46.8511 58.2955 47.1169 58.5587 47.3071C58.8218 47.4973 59.1366 47.603 59.4612 47.61L72.2612 47.5Z" fill="white" stroke="#BABABA" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M80.4312 37.49L74.6012 34.13L80.4312 30.76" stroke="#BABABA" stroke-width="0.71" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M51.3712 37.49L57.2012 34.13L51.3712 30.76" stroke="#BABABA" stroke-width="0.71" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M19.4012 58.82V63.13" stroke="#BABABA" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M17.2512 60.97H21.5612" stroke="#BABABA" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M100.911 61.87C101.452 61.87 101.891 61.4312 101.891 60.89C101.891 60.3488 101.452 59.91 100.911 59.91C100.37 59.91 99.9312 60.3488 99.9312 60.89C99.9312 61.4312 100.37 61.87 100.911 61.87Z" fill="#CFCFCF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
20
web/src/lib/components/empty_states/empty_state_feed.svelte
Normal file
20
web/src/lib/components/empty_states/empty_state_feed.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import emptyStateFeedDark from "$lib/assets/svgs/empty_states/empty_state_feed_dark.svg";
|
||||
import emptyStateFeedLight from "$lib/assets/svgs/empty_states/empty_state_feed_light.svg";
|
||||
import { theme } from "$lib/stores/theme_store";
|
||||
|
||||
import { _ } from "svelte-i18n";
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col justify-center items-center h-full">
|
||||
<img
|
||||
class="aspect-square h-96 mb-4"
|
||||
src={$theme === "light" ? emptyStateFeedLight : emptyStateFeedDark}
|
||||
alt="Empty State showing a wanderer going into the distance"
|
||||
/>
|
||||
|
||||
<h3 class="text-2xl font-medium text-center text-gray-500">{$_("empty-feed")}...</h3>
|
||||
<p class="text-center text-gray-500">
|
||||
{$_('empty-feed-explanation')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -185,7 +185,7 @@
|
||||
</div>
|
||||
</Drawer>
|
||||
|
||||
<nav class="flex justify-between items-center p-6">
|
||||
<nav class="flex justify-between items-center p-6 {page.url.pathname === '/' ? 'sticky top-0 z-10 bg-background' : ''}">
|
||||
<a href="/">
|
||||
{#if $theme == "light"}
|
||||
<LogoText></LogoText>
|
||||
|
||||
137
web/src/lib/components/profile/feed_card.svelte
Normal file
137
web/src/lib/components/profile/feed_card.svelte
Normal file
@@ -0,0 +1,137 @@
|
||||
<script lang="ts">
|
||||
import type { FeedItem } from "$lib/models/feed";
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
import { getFileURL, isVideoURL } from "$lib/util/file_util";
|
||||
import {
|
||||
formatDistance,
|
||||
formatElevation,
|
||||
formatHTMLAsText,
|
||||
formatTimeHHMM,
|
||||
formatTimeSince,
|
||||
} from "$lib/util/format_util";
|
||||
import { _ } from "svelte-i18n";
|
||||
interface Props {
|
||||
feedItem: FeedItem;
|
||||
}
|
||||
|
||||
let { feedItem }: Props = $props();
|
||||
|
||||
let fullDescription = $state(false)
|
||||
|
||||
const timeSince = formatTimeSince(new Date(feedItem.created ?? ""));
|
||||
|
||||
const photos = (feedItem.expand.item as Trail).photos
|
||||
</script>
|
||||
|
||||
<div class="feed-card p-6 rounded-xl border border-input-border">
|
||||
<div class="feed-card-header flex gap-x-4 items-start">
|
||||
<img
|
||||
class="rounded-full w-10 aspect-square overflow-hidden shrink-0"
|
||||
src={feedItem.expand.author?.icon ||
|
||||
`https://api.dicebear.com/7.x/initials/svg?seed=${feedItem.expand.author?.preferred_username}&backgroundType=gradientLinear`}
|
||||
alt="avatar"
|
||||
/>
|
||||
<div>
|
||||
<span class="font-semibold"
|
||||
>{feedItem.expand.author?.preferred_username}</span
|
||||
>
|
||||
<p class="text-sm text-gray-500 mb-3">
|
||||
{feedItem.expand.author?.preferred_username}@{feedItem.expand
|
||||
.author?.domain}
|
||||
</p>
|
||||
</div>
|
||||
<div class="basis-full"></div>
|
||||
<p class="text-xs text-gray-500 shrink-0">
|
||||
{$_(`n-${timeSince.unit}-ago`, {
|
||||
values: { n: timeSince.value },
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
<div class="feed-card-body">
|
||||
<h3 class="text-2xl font-semibold !mt-2">
|
||||
{feedItem.expand.item.name}
|
||||
</h3>
|
||||
<div class="flex flex-wrap mt-1 gap-x-4 gap-y-2 text-sm text-gray-500">
|
||||
<span
|
||||
><i class="fa fa-left-right mr-2"></i>{formatDistance(
|
||||
feedItem.expand.item.distance,
|
||||
)}</span
|
||||
>
|
||||
<span
|
||||
><i class="fa fa-clock mr-2"></i>{formatTimeHHMM(
|
||||
feedItem.expand.item.duration,
|
||||
)}</span
|
||||
>
|
||||
<span
|
||||
><i class="fa fa-arrow-trend-up mr-2"></i>{formatElevation(
|
||||
feedItem.expand.item.elevation_gain,
|
||||
)}</span
|
||||
>
|
||||
<span
|
||||
><i class="fa fa-arrow-trend-down mr-2"></i>{formatElevation(
|
||||
feedItem.expand.item.elevation_loss,
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
{#if photos?.length}
|
||||
<div
|
||||
class="grid gap-[1px] {photos.length > 1
|
||||
? 'grid-cols-[8fr_5fr]'
|
||||
: 'grid-cols-1'} mt-6"
|
||||
>
|
||||
{#each photos.slice(0, 3) as photo, i}
|
||||
{#if isVideoURL(photo)}
|
||||
<!-- svelte-ignore a11y_media_has_caption -->
|
||||
<video
|
||||
class="object-cover h-full max-h-80 w-full"
|
||||
autoplay
|
||||
loop
|
||||
src={getFileURL(
|
||||
{
|
||||
collectionId: "trails",
|
||||
id: feedItem.item,
|
||||
},
|
||||
photo,
|
||||
)}
|
||||
></video>
|
||||
{:else}
|
||||
<img
|
||||
class="object-cover h-full max-h-80 w-full"
|
||||
class:row-span-2={i == 0 &&
|
||||
photos.length > 2}
|
||||
src={getFileURL(
|
||||
{
|
||||
collectionId: "trails",
|
||||
id: feedItem.item,
|
||||
},
|
||||
photo,
|
||||
)}
|
||||
alt=""
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#if feedItem.expand.item.description?.length}
|
||||
<p class="text-sm whitespace-pre-wrap mt-6">
|
||||
{formatHTMLAsText(
|
||||
!fullDescription
|
||||
? feedItem.expand.item.description?.substring(0, 100)
|
||||
: feedItem.expand.item.description,
|
||||
)}
|
||||
{#if (feedItem.expand.item.description?.length ?? 0) > 100 && !fullDescription}
|
||||
<button
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
fullDescription = true;
|
||||
}}
|
||||
>
|
||||
... <span class="underline">{$_("read-more")}</span
|
||||
></button
|
||||
>
|
||||
{/if}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -31,6 +31,17 @@
|
||||
"before": "Vor",
|
||||
"bike-type": "Fahrradtyp",
|
||||
"by": "von",
|
||||
"calendar": {
|
||||
"weekdays": {
|
||||
"0": "Mo",
|
||||
"1": "Di",
|
||||
"2": "Mi",
|
||||
"3": "Do",
|
||||
"4": "Fr",
|
||||
"5": "Sa",
|
||||
"6": "So"
|
||||
}
|
||||
},
|
||||
"can": "kann",
|
||||
"cancel": "Abbrechen",
|
||||
"car": "Auto",
|
||||
@@ -102,6 +113,8 @@
|
||||
"email-verified": "Email verifiziert",
|
||||
"empty-activities": "{username} hat noch keine Aktivitäten",
|
||||
"empty-bio": "{username} hat noch keine Biografie hinzugefügt",
|
||||
"empty-feed": "",
|
||||
"empty-feed-explanation": "Activities by you or people you follow will appear here",
|
||||
"empty-lists": "{username} hat keine öffentlichen Listen",
|
||||
"enable-auto-routing": "Enable auto-routing",
|
||||
"english": "Englisch",
|
||||
@@ -361,16 +374,5 @@
|
||||
"welcome_to": "Willkommen bei",
|
||||
"width": "Breite",
|
||||
"wrong-username-or-password": "Falscher Nutzername oder falsches Passwort",
|
||||
"you-can": "Du kannst",
|
||||
"calendar": {
|
||||
"weekdays": {
|
||||
"0": "Mo",
|
||||
"1": "Di",
|
||||
"2": "Mi",
|
||||
"3": "Do",
|
||||
"4": "Fr",
|
||||
"5": "Sa",
|
||||
"6": "So"
|
||||
}
|
||||
}
|
||||
"you-can": "Du kannst"
|
||||
}
|
||||
|
||||
@@ -31,6 +31,17 @@
|
||||
"before": "Before",
|
||||
"bike-type": "Bike Type",
|
||||
"by": "by",
|
||||
"calendar": {
|
||||
"weekdays": {
|
||||
"0": "Mo",
|
||||
"1": "Tu",
|
||||
"2": "We",
|
||||
"3": "Th",
|
||||
"4": "Fr",
|
||||
"5": "Sa",
|
||||
"6": "Su"
|
||||
}
|
||||
},
|
||||
"can": "can",
|
||||
"cancel": "Cancel",
|
||||
"car": "Car",
|
||||
@@ -102,6 +113,8 @@
|
||||
"email-verified": "Email verified",
|
||||
"empty-activities": "{username} has no activity yet",
|
||||
"empty-bio": "{username} has not added a bio yet",
|
||||
"empty-feed": "Your feed is empty",
|
||||
"empty-feed-explanation": "Activities by you or people you follow will appear here",
|
||||
"empty-lists": "{username} has no public lists",
|
||||
"enable-auto-routing": "Enable auto-routing",
|
||||
"english": "English",
|
||||
@@ -361,16 +374,5 @@
|
||||
"welcome_to": "Welcome to",
|
||||
"width": "Width",
|
||||
"wrong-username-or-password": "Wrong username or password",
|
||||
"you-can": "You can",
|
||||
"calendar": {
|
||||
"weekdays": {
|
||||
"0": "Mo",
|
||||
"1": "Tu",
|
||||
"2": "We",
|
||||
"3": "Th",
|
||||
"4": "Fr",
|
||||
"5": "Sa",
|
||||
"6": "Su"
|
||||
}
|
||||
}
|
||||
"you-can": "You can"
|
||||
}
|
||||
|
||||
@@ -31,6 +31,17 @@
|
||||
"before": "Antes",
|
||||
"bike-type": "Tipo de bici",
|
||||
"by": "de",
|
||||
"calendar": {
|
||||
"weekdays": {
|
||||
"0": "",
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"can": "puede",
|
||||
"cancel": "Borrar",
|
||||
"car": "Coche",
|
||||
@@ -102,6 +113,8 @@
|
||||
"email-verified": "Correo verificado",
|
||||
"empty-activities": "{username} todavía no tiene actividad",
|
||||
"empty-bio": "{username} no ha añadido ninguna Bio todavía",
|
||||
"empty-feed": "",
|
||||
"empty-feed-explanation": "",
|
||||
"empty-lists": "{username} no tiene listas públicas",
|
||||
"enable-auto-routing": "Habilitar auto-ruta",
|
||||
"english": "Inglés",
|
||||
|
||||
@@ -31,6 +31,17 @@
|
||||
"before": "Before",
|
||||
"bike-type": "Bike Type",
|
||||
"by": "by",
|
||||
"calendar": {
|
||||
"weekdays": {
|
||||
"0": "",
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"can": "can",
|
||||
"cancel": "Cancel",
|
||||
"car": "Car",
|
||||
@@ -102,6 +113,8 @@
|
||||
"email-verified": "Email verified",
|
||||
"empty-activities": "{username} has no activity yet",
|
||||
"empty-bio": "{username} has not added a bio yet",
|
||||
"empty-feed": "",
|
||||
"empty-feed-explanation": "",
|
||||
"empty-lists": "{username} has no public lists",
|
||||
"enable-auto-routing": "Enable auto-routing",
|
||||
"english": "English",
|
||||
|
||||
@@ -31,6 +31,17 @@
|
||||
"before": "Avant le",
|
||||
"bike-type": "Bike Type",
|
||||
"by": "par",
|
||||
"calendar": {
|
||||
"weekdays": {
|
||||
"0": "",
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"can": "peut",
|
||||
"cancel": "Annuler",
|
||||
"car": "Car",
|
||||
@@ -102,6 +113,8 @@
|
||||
"email-verified": "Email vérifié",
|
||||
"empty-activities": "{username} n'a encore aucune activité",
|
||||
"empty-bio": "{username} n'a pas encore de description",
|
||||
"empty-feed": "",
|
||||
"empty-feed-explanation": "",
|
||||
"empty-lists": "{username} n'a pas de liste publique",
|
||||
"enable-auto-routing": "Enable auto-routing",
|
||||
"english": "Anglais",
|
||||
|
||||
@@ -31,6 +31,17 @@
|
||||
"before": "Before",
|
||||
"bike-type": "Bike Type",
|
||||
"by": "by",
|
||||
"calendar": {
|
||||
"weekdays": {
|
||||
"0": "",
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"can": "can",
|
||||
"cancel": "Mégsem",
|
||||
"car": "Car",
|
||||
@@ -102,6 +113,8 @@
|
||||
"email-verified": "Email verified",
|
||||
"empty-activities": "{username} has no activity yet",
|
||||
"empty-bio": "{username} has not added a bio yet",
|
||||
"empty-feed": "",
|
||||
"empty-feed-explanation": "",
|
||||
"empty-lists": "{username} has no public lists",
|
||||
"enable-auto-routing": "Enable auto-routing",
|
||||
"english": "Angol",
|
||||
|
||||
@@ -31,6 +31,17 @@
|
||||
"before": "Prima",
|
||||
"bike-type": "Bike Type",
|
||||
"by": "di",
|
||||
"calendar": {
|
||||
"weekdays": {
|
||||
"0": "",
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"can": "può",
|
||||
"cancel": "Annulla",
|
||||
"car": "Car",
|
||||
@@ -102,6 +113,8 @@
|
||||
"email-verified": "Email verified",
|
||||
"empty-activities": "{username} non ha ancora attività",
|
||||
"empty-bio": "{username} non ha ancora aggiunto una biografia",
|
||||
"empty-feed": "",
|
||||
"empty-feed-explanation": "",
|
||||
"empty-lists": "{username} non ha liste pubbliche",
|
||||
"enable-auto-routing": "Enable auto-routing",
|
||||
"english": "Inglese",
|
||||
|
||||
@@ -31,6 +31,17 @@
|
||||
"before": "Voor",
|
||||
"bike-type": "Fiets Type",
|
||||
"by": "door",
|
||||
"calendar": {
|
||||
"weekdays": {
|
||||
"0": "",
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"can": "kan",
|
||||
"cancel": "Annuleren",
|
||||
"car": "Wagen",
|
||||
@@ -102,6 +113,8 @@
|
||||
"email-verified": "Email geverifieerd",
|
||||
"empty-activities": "{username} heeft nog geen activiteit",
|
||||
"empty-bio": "{username} heeft nog geen bio toegevoegd",
|
||||
"empty-feed": "",
|
||||
"empty-feed-explanation": "",
|
||||
"empty-lists": "{username} heeft geen publieke lijsten",
|
||||
"enable-auto-routing": "Automatische routering inschakelen",
|
||||
"english": "Engels",
|
||||
|
||||
@@ -31,6 +31,17 @@
|
||||
"before": "Przed",
|
||||
"bike-type": "Typ roweru",
|
||||
"by": "przez",
|
||||
"calendar": {
|
||||
"weekdays": {
|
||||
"0": "",
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"can": "może",
|
||||
"cancel": "Anuluj",
|
||||
"car": "Samochód",
|
||||
@@ -102,6 +113,8 @@
|
||||
"email-verified": "Adres e-mail został zweryfikowany",
|
||||
"empty-activities": "{username} nie ma jeszcze aktywności",
|
||||
"empty-bio": "{username} jeszcze nie dodał biogramu",
|
||||
"empty-feed": "",
|
||||
"empty-feed-explanation": "",
|
||||
"empty-lists": "{username} nie ma publicznych list",
|
||||
"enable-auto-routing": "Włącz auto-trasowanie",
|
||||
"english": "Angielski",
|
||||
|
||||
@@ -31,6 +31,17 @@
|
||||
"before": "Antes",
|
||||
"bike-type": "Bike Type",
|
||||
"by": "by",
|
||||
"calendar": {
|
||||
"weekdays": {
|
||||
"0": "",
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"can": "pode",
|
||||
"cancel": "Cancelar",
|
||||
"car": "Car",
|
||||
@@ -102,6 +113,8 @@
|
||||
"email-verified": "Email verified",
|
||||
"empty-activities": "{username} has no activity yet",
|
||||
"empty-bio": "{username} has not added a bio yet",
|
||||
"empty-feed": "",
|
||||
"empty-feed-explanation": "",
|
||||
"empty-lists": "{username} has no public lists",
|
||||
"enable-auto-routing": "Enable auto-routing",
|
||||
"english": "Inglês",
|
||||
|
||||
@@ -31,6 +31,17 @@
|
||||
"before": "До",
|
||||
"bike-type": "Тип велосипеда",
|
||||
"by": "От",
|
||||
"calendar": {
|
||||
"weekdays": {
|
||||
"0": "",
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"can": "можно",
|
||||
"cancel": "Отмена",
|
||||
"car": "Автомобиль:",
|
||||
@@ -102,6 +113,8 @@
|
||||
"email-verified": "Email подтверждён",
|
||||
"empty-activities": "у {username} ещё нет активностей",
|
||||
"empty-bio": "{username} ещё не рассказал(а) о себе",
|
||||
"empty-feed": "",
|
||||
"empty-feed-explanation": "",
|
||||
"empty-lists": "у {username} нет публичных списков",
|
||||
"enable-auto-routing": "Авто-маршрутизация",
|
||||
"english": "Английский",
|
||||
|
||||
@@ -31,6 +31,17 @@
|
||||
"before": "之前",
|
||||
"bike-type": "Bike Type",
|
||||
"by": "by",
|
||||
"calendar": {
|
||||
"weekdays": {
|
||||
"0": "",
|
||||
"1": "",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"4": "",
|
||||
"5": "",
|
||||
"6": ""
|
||||
}
|
||||
},
|
||||
"can": "可以",
|
||||
"cancel": "取消",
|
||||
"car": "Car",
|
||||
@@ -102,6 +113,8 @@
|
||||
"email-verified": "Email verified",
|
||||
"empty-activities": "{username} has no activity yet",
|
||||
"empty-bio": "{username} has not added a bio yet",
|
||||
"empty-feed": "",
|
||||
"empty-feed-explanation": "",
|
||||
"empty-lists": "{username} has no public lists",
|
||||
"enable-auto-routing": "Enable auto-routing",
|
||||
"english": "英语",
|
||||
|
||||
21
web/src/lib/models/feed.ts
Normal file
21
web/src/lib/models/feed.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { Actor } from "./activitypub/actor";
|
||||
import type { List } from "./list";
|
||||
import type { SummitLog } from "./summit_log";
|
||||
import type { Trail } from "./trail";
|
||||
|
||||
interface FeedItem {
|
||||
id: string;
|
||||
actor: string;
|
||||
author: string;
|
||||
item: string;
|
||||
type: "trail" | "list" | "summit_log"
|
||||
expand: {
|
||||
actor?: Actor,
|
||||
author?: Actor,
|
||||
item: Trail | List
|
||||
}
|
||||
created: string;
|
||||
|
||||
}
|
||||
|
||||
export { type FeedItem }
|
||||
@@ -21,6 +21,7 @@ export class List {
|
||||
author?: Actor;
|
||||
|
||||
}
|
||||
created?: string;
|
||||
author: string;
|
||||
|
||||
constructor(name: string, trails: Trail[], params?: { description?: string, public?: boolean, avatar?: string, author?: string }) {
|
||||
|
||||
30
web/src/lib/stores/feed_store.ts
Normal file
30
web/src/lib/stores/feed_store.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { FeedItem } from "$lib/models/feed";
|
||||
import { APIError } from "$lib/util/api_util";
|
||||
import type { ListResult } from "pocketbase";
|
||||
|
||||
let feed: FeedItem[] = []
|
||||
|
||||
|
||||
export async function feed_index(page: number, perPage: number = 10, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||
let r = await f(`/api/v1/feed?` + new URLSearchParams({
|
||||
expand: "author",
|
||||
page: page.toString(),
|
||||
perPage: perPage.toString(),
|
||||
sort: '-created'
|
||||
}), {
|
||||
method: 'GET',
|
||||
})
|
||||
if (!r.ok) {
|
||||
const response = await r.json();
|
||||
throw new APIError(r.status, response.message, response.detail)
|
||||
}
|
||||
|
||||
const fetchedFeed: ListResult<FeedItem> = await r.json()
|
||||
|
||||
const result = page > 1 ? [...feed, ...fetchedFeed.items] : fetchedFeed.items
|
||||
|
||||
feed = result;
|
||||
|
||||
return { ...fetchedFeed, items: result };
|
||||
|
||||
}
|
||||
@@ -23,6 +23,7 @@ export enum Collection {
|
||||
activitypub_actors = "activitypub_actors",
|
||||
categories = "categories",
|
||||
comments = "comments",
|
||||
feed = "feed",
|
||||
follows = "follows",
|
||||
integrations = "integrations",
|
||||
list_share = "list_share",
|
||||
@@ -39,6 +40,7 @@ export enum Collection {
|
||||
trails_bounding_box = "trails_bounding_box",
|
||||
trails_filter = "trails_filter",
|
||||
users_anonymous = "users_anonymous",
|
||||
|
||||
}
|
||||
|
||||
export async function list<T>(event: RequestEvent, collection: Collection) {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
import emptyStateTrailDark from "$lib/assets/svgs/empty_states/empty_state_trail_dark.svg";
|
||||
import emptyStateTrailLight from "$lib/assets/svgs/empty_states/empty_state_trail_light.svg";
|
||||
import Search, {
|
||||
type SearchItem,
|
||||
} from "$lib/components/base/search.svelte";
|
||||
import CategoryCard from "$lib/components/category_card.svelte";
|
||||
import EmptyStateFeed from "$lib/components/empty_states/empty_state_feed.svelte";
|
||||
import FeedCard from "$lib/components/profile/feed_card.svelte";
|
||||
import Scene from "$lib/components/scene.svelte";
|
||||
import TrailCard from "$lib/components/trail/trail_card.svelte";
|
||||
import {
|
||||
@@ -13,6 +16,7 @@
|
||||
type TrailSearchResult,
|
||||
} from "$lib/models/trail.js";
|
||||
import { categories } from "$lib/stores/category_store";
|
||||
import { feed_index } from "$lib/stores/feed_store.js";
|
||||
import {
|
||||
searchActors,
|
||||
searchMulti,
|
||||
@@ -20,7 +24,6 @@
|
||||
type LocationSearchResult,
|
||||
} from "$lib/stores/search_store.js";
|
||||
import { theme } from "$lib/stores/theme_store";
|
||||
import { currentUser } from "$lib/stores/user_store";
|
||||
import { getIconForLocation } from "$lib/util/icon_util.js";
|
||||
import { Canvas } from "@threlte/core";
|
||||
import { _ } from "svelte-i18n";
|
||||
@@ -29,6 +32,15 @@
|
||||
|
||||
let searchDropdownItems: SearchItem[] = $state([]);
|
||||
|
||||
let feed = $state(data.feed);
|
||||
|
||||
let pagination = $derived({
|
||||
page: feed.page,
|
||||
totalPages: feed.totalItems,
|
||||
});
|
||||
|
||||
let loading: boolean = false;
|
||||
|
||||
async function search(q: string) {
|
||||
if (q.startsWith("@")) {
|
||||
const actors = await searchActors(q);
|
||||
@@ -97,18 +109,41 @@
|
||||
goto(`/map/?lat=${item.value.lat}&lon=${item.value.lon}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function onScroll(e: Event) {
|
||||
if (!page.data.user) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
window.innerHeight + window.scrollY >=
|
||||
0.8 * document.body.offsetHeight &&
|
||||
pagination.page !== pagination.totalPages &&
|
||||
!loading
|
||||
) {
|
||||
loading = true;
|
||||
await loadNextPage();
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadNextPage() {
|
||||
pagination.page += 1;
|
||||
feed = await feed_index(pagination.page);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Home | wanderer</title>
|
||||
</svelte:head>
|
||||
|
||||
<svelte:window onscroll={onScroll} />
|
||||
|
||||
<section
|
||||
class="hero grid grid-cols-1 lg:grid-cols-2 md:px-8 md:gap-8"
|
||||
style="height: calc(100vh - 112px)"
|
||||
style="min-height: calc(100vh - 112px)"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col justify-center gap-8 max-w-md mx-8 sm:mx-auto mt-0 lg:-mt-24 md:mt-24"
|
||||
class="flex flex-col justify-center gap-8 max-w-md mx-8 sm:mx-auto mt-0 lg:-mt-24 md:mt-24 max-h-screen md:sticky top-0"
|
||||
>
|
||||
<h2 class="text-5xl sm:text-6xl md:text-7xl font-bold">
|
||||
{$_("welcome_to")} <span class="-tracking-[0.075em]">wanderer</span>
|
||||
@@ -125,12 +160,30 @@
|
||||
items={searchDropdownItems}
|
||||
></Search>
|
||||
</div>
|
||||
{#if !page.data.user}
|
||||
<div class="hidden md:block">
|
||||
<Canvas toneMapping={0}>
|
||||
<Scene></Scene>
|
||||
</Canvas>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="space-y-2">
|
||||
{#if feed.items.length === 0}
|
||||
<EmptyStateFeed></EmptyStateFeed>
|
||||
{/if}
|
||||
{#each feed.items as f}
|
||||
<a
|
||||
class="block"
|
||||
href={(f.type === "trail" ? "/trail/view/" : "/lists/") +
|
||||
`@${f.expand.author?.preferred_username}@${f.expand.author?.domain}/${f.item}`}
|
||||
>
|
||||
<FeedCard feedItem={f}></FeedCard>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
{#if !page.data.user}
|
||||
<section
|
||||
class="max-w-7xl mx-auto mt-8 px-8 xl:px-0 grid grid-cols-1 md:grid-cols-2 items-center gap-x-12"
|
||||
>
|
||||
@@ -155,7 +208,12 @@
|
||||
? '@' + trail.domain
|
||||
: ''}/{trail.id}"
|
||||
>
|
||||
<TrailCard {trail} fullWidth selected={false} hovered={false}></TrailCard></a
|
||||
<TrailCard
|
||||
{trail}
|
||||
fullWidth
|
||||
selected={false}
|
||||
hovered={false}
|
||||
></TrailCard></a
|
||||
>
|
||||
{/each}
|
||||
{/if}
|
||||
@@ -173,9 +231,7 @@
|
||||
>
|
||||
{:else}
|
||||
<h2 class="text-4xl md:text-5xl font-bold">
|
||||
{$currentUser
|
||||
? $_("trails-for-you")
|
||||
: $_("explore-some-trails")}
|
||||
{$_("explore-some-trails")}
|
||||
</h2>
|
||||
<h5>
|
||||
{$_("hero_section_1_text")}
|
||||
@@ -208,6 +264,7 @@
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
import { categories_index } from "$lib/stores/category_store";
|
||||
import { feed_index } from "$lib/stores/feed_store";
|
||||
import { trails_recommend } from "$lib/stores/trail_store";
|
||||
import type { ServerLoad } from "@sveltejs/kit";
|
||||
|
||||
@@ -7,11 +8,13 @@ export const load: ServerLoad = async ({ params, locals, fetch }) => {
|
||||
try {
|
||||
await categories_index(fetch)
|
||||
|
||||
const feed = await feed_index(1, 10, fetch);
|
||||
|
||||
const trails: Trail[] = await trails_recommend(4, fetch)
|
||||
return { trails: trails ?? [] }
|
||||
return { trails: trails ?? [], feed }
|
||||
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
return { trails: [] }
|
||||
return { trails: [], feed: {items: [], page: 1, perPage: 1, totalItems: 0, totalPages: 0} }
|
||||
};
|
||||
13
web/src/routes/api/v1/feed/+server.ts
Normal file
13
web/src/routes/api/v1/feed/+server.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { FeedItem } from "$lib/models/feed";
|
||||
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<FeedItem>(event, Collection.feed);
|
||||
return json(r)
|
||||
} catch (e) {
|
||||
return handleError(e)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -404,7 +404,6 @@
|
||||
><i class="fa fa-table mr-3"></i>{$_("all-activities")}</span
|
||||
>
|
||||
<div class=" overflow-x-auto">
|
||||
{data.logs.length}
|
||||
<SummitLogTable
|
||||
{summitLogs}
|
||||
handle={page.params.handle}
|
||||
|
||||
Reference in New Issue
Block a user