adds feed

This commit is contained in:
Christian Beutel
2025-07-11 16:02:18 +02:00
parent 25bf00a38b
commit 237d12bfec
33 changed files with 967 additions and 119 deletions

View File

@@ -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 { func processCreateOrUpdateTrailActivity(activity pub.Activity, app core.App, actor *core.Record) error {
trail, err := util.TrailFromActivity(activity, app, actor)
// no need to do anything if the actor is local if err != nil {
if actor.GetBool("isLocal") { return err
return nil
} }
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) 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 { func processCreateOrUpdateListActivity(activity pub.Activity, app core.App, actor *core.Record) error {
list, err := util.ListFromActivity(activity, app, actor)
// no need to do anything if the actor is local if err != nil {
if actor.GetBool("isLocal") { return err
return nil
} }
_, 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 return err
} }

View File

@@ -120,6 +120,8 @@ func setupEventHandlers(app *pocketbase.PocketBase, client meilisearch.ServiceMa
app.OnRecordUpdate("integrations").BindFunc(updateIntegrationHandler()) app.OnRecordUpdate("integrations").BindFunc(updateIntegrationHandler())
app.OnRecordAfterUpdateSuccess("integrations").BindFunc(createUpdateIntegrationSuccessHandler()) app.OnRecordAfterUpdateSuccess("integrations").BindFunc(createUpdateIntegrationSuccessHandler())
app.OnRecordsListRequest("feed").BindFunc(listFeedHandler())
app.OnRecordCreateRequest().BindFunc(sanitizeHTML()) app.OnRecordCreateRequest().BindFunc(sanitizeHTML())
app.OnRecordUpdateRequest().BindFunc(sanitizeHTML()) app.OnRecordUpdateRequest().BindFunc(sanitizeHTML())
@@ -242,6 +244,11 @@ func createTrailHandler(client meilisearch.ServiceManager) func(e *core.RecordEv
return err return err
} }
_, err = util.InsertIntoFeed(e.App, author.Id, author.Id, record.Id, util.TrailFeed)
if err != nil {
return err
}
return nil return nil
} }
} }
@@ -616,6 +623,11 @@ func createListHandler(client meilisearch.ServiceManager) func(e *core.RecordEve
return err return err
} }
_, err = util.InsertIntoFeed(e.App, author.Id, author.Id, record.Id, util.ListFeed)
if err != nil {
return err
}
return nil 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 { func onBeforeServeHandler(client meilisearch.ServiceManager) func(se *core.ServeEvent) error {
return func(se *core.ServeEvent) error { return func(se *core.ServeEvent) error {
registerRoutes(se, client) registerRoutes(se, client)

View 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)
})
}

View 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)
})
}

View 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)
})
}

View 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
})
}

View File

@@ -176,6 +176,11 @@ func TrailFromActivity(activity pub.Activity, app core.App, actor *core.Record)
} else { } else {
return nil, err return nil, err
} }
} else {
// this trail exists already
// nothing more to do
return record, nil
} }
var distance, duration, elevation_gain, elevation_loss float64 var distance, duration, elevation_gain, elevation_loss float64
@@ -442,6 +447,11 @@ func ListFromActivity(activity pub.Activity, app core.App, actor *core.Record) (
} else { } else {
return nil, err return nil, err
} }
} else {
// this list exists already
// nothing more to do
return record, nil
} }
record.Set("name", l.Name.First().Value) record.Set("name", l.Name.First().Value)

27
db/util/feed.go Normal file
View 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)
}

View File

@@ -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

View File

@@ -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

View 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>

View File

@@ -185,7 +185,7 @@
</div> </div>
</Drawer> </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="/"> <a href="/">
{#if $theme == "light"} {#if $theme == "light"}
<LogoText></LogoText> <LogoText></LogoText>

View 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>

View File

@@ -31,6 +31,17 @@
"before": "Vor", "before": "Vor",
"bike-type": "Fahrradtyp", "bike-type": "Fahrradtyp",
"by": "von", "by": "von",
"calendar": {
"weekdays": {
"0": "Mo",
"1": "Di",
"2": "Mi",
"3": "Do",
"4": "Fr",
"5": "Sa",
"6": "So"
}
},
"can": "kann", "can": "kann",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"car": "Auto", "car": "Auto",
@@ -102,6 +113,8 @@
"email-verified": "Email verifiziert", "email-verified": "Email verifiziert",
"empty-activities": "{username} hat noch keine Aktivitäten", "empty-activities": "{username} hat noch keine Aktivitäten",
"empty-bio": "{username} hat noch keine Biografie hinzugefügt", "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", "empty-lists": "{username} hat keine öffentlichen Listen",
"enable-auto-routing": "Enable auto-routing", "enable-auto-routing": "Enable auto-routing",
"english": "Englisch", "english": "Englisch",
@@ -361,16 +374,5 @@
"welcome_to": "Willkommen bei", "welcome_to": "Willkommen bei",
"width": "Breite", "width": "Breite",
"wrong-username-or-password": "Falscher Nutzername oder falsches Passwort", "wrong-username-or-password": "Falscher Nutzername oder falsches Passwort",
"you-can": "Du kannst", "you-can": "Du kannst"
"calendar": {
"weekdays": {
"0": "Mo",
"1": "Di",
"2": "Mi",
"3": "Do",
"4": "Fr",
"5": "Sa",
"6": "So"
}
}
} }

View File

@@ -31,6 +31,17 @@
"before": "Before", "before": "Before",
"bike-type": "Bike Type", "bike-type": "Bike Type",
"by": "by", "by": "by",
"calendar": {
"weekdays": {
"0": "Mo",
"1": "Tu",
"2": "We",
"3": "Th",
"4": "Fr",
"5": "Sa",
"6": "Su"
}
},
"can": "can", "can": "can",
"cancel": "Cancel", "cancel": "Cancel",
"car": "Car", "car": "Car",
@@ -102,6 +113,8 @@
"email-verified": "Email verified", "email-verified": "Email verified",
"empty-activities": "{username} has no activity yet", "empty-activities": "{username} has no activity yet",
"empty-bio": "{username} has not added a bio 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", "empty-lists": "{username} has no public lists",
"enable-auto-routing": "Enable auto-routing", "enable-auto-routing": "Enable auto-routing",
"english": "English", "english": "English",
@@ -361,16 +374,5 @@
"welcome_to": "Welcome to", "welcome_to": "Welcome to",
"width": "Width", "width": "Width",
"wrong-username-or-password": "Wrong username or password", "wrong-username-or-password": "Wrong username or password",
"you-can": "You can", "you-can": "You can"
"calendar": {
"weekdays": {
"0": "Mo",
"1": "Tu",
"2": "We",
"3": "Th",
"4": "Fr",
"5": "Sa",
"6": "Su"
}
}
} }

View File

@@ -31,6 +31,17 @@
"before": "Antes", "before": "Antes",
"bike-type": "Tipo de bici", "bike-type": "Tipo de bici",
"by": "de", "by": "de",
"calendar": {
"weekdays": {
"0": "",
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"6": ""
}
},
"can": "puede", "can": "puede",
"cancel": "Borrar", "cancel": "Borrar",
"car": "Coche", "car": "Coche",
@@ -102,6 +113,8 @@
"email-verified": "Correo verificado", "email-verified": "Correo verificado",
"empty-activities": "{username} todavía no tiene actividad", "empty-activities": "{username} todavía no tiene actividad",
"empty-bio": "{username} no ha añadido ninguna Bio todavía", "empty-bio": "{username} no ha añadido ninguna Bio todavía",
"empty-feed": "",
"empty-feed-explanation": "",
"empty-lists": "{username} no tiene listas públicas", "empty-lists": "{username} no tiene listas públicas",
"enable-auto-routing": "Habilitar auto-ruta", "enable-auto-routing": "Habilitar auto-ruta",
"english": "Inglés", "english": "Inglés",

View File

@@ -31,6 +31,17 @@
"before": "Before", "before": "Before",
"bike-type": "Bike Type", "bike-type": "Bike Type",
"by": "by", "by": "by",
"calendar": {
"weekdays": {
"0": "",
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"6": ""
}
},
"can": "can", "can": "can",
"cancel": "Cancel", "cancel": "Cancel",
"car": "Car", "car": "Car",
@@ -102,6 +113,8 @@
"email-verified": "Email verified", "email-verified": "Email verified",
"empty-activities": "{username} has no activity yet", "empty-activities": "{username} has no activity yet",
"empty-bio": "{username} has not added a bio yet", "empty-bio": "{username} has not added a bio yet",
"empty-feed": "",
"empty-feed-explanation": "",
"empty-lists": "{username} has no public lists", "empty-lists": "{username} has no public lists",
"enable-auto-routing": "Enable auto-routing", "enable-auto-routing": "Enable auto-routing",
"english": "English", "english": "English",

View File

@@ -31,6 +31,17 @@
"before": "Avant le", "before": "Avant le",
"bike-type": "Bike Type", "bike-type": "Bike Type",
"by": "par", "by": "par",
"calendar": {
"weekdays": {
"0": "",
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"6": ""
}
},
"can": "peut", "can": "peut",
"cancel": "Annuler", "cancel": "Annuler",
"car": "Car", "car": "Car",
@@ -102,6 +113,8 @@
"email-verified": "Email vérifié", "email-verified": "Email vérifié",
"empty-activities": "{username} n'a encore aucune activité", "empty-activities": "{username} n'a encore aucune activité",
"empty-bio": "{username} n'a pas encore de description", "empty-bio": "{username} n'a pas encore de description",
"empty-feed": "",
"empty-feed-explanation": "",
"empty-lists": "{username} n'a pas de liste publique", "empty-lists": "{username} n'a pas de liste publique",
"enable-auto-routing": "Enable auto-routing", "enable-auto-routing": "Enable auto-routing",
"english": "Anglais", "english": "Anglais",

View File

@@ -31,6 +31,17 @@
"before": "Before", "before": "Before",
"bike-type": "Bike Type", "bike-type": "Bike Type",
"by": "by", "by": "by",
"calendar": {
"weekdays": {
"0": "",
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"6": ""
}
},
"can": "can", "can": "can",
"cancel": "Mégsem", "cancel": "Mégsem",
"car": "Car", "car": "Car",
@@ -102,6 +113,8 @@
"email-verified": "Email verified", "email-verified": "Email verified",
"empty-activities": "{username} has no activity yet", "empty-activities": "{username} has no activity yet",
"empty-bio": "{username} has not added a bio yet", "empty-bio": "{username} has not added a bio yet",
"empty-feed": "",
"empty-feed-explanation": "",
"empty-lists": "{username} has no public lists", "empty-lists": "{username} has no public lists",
"enable-auto-routing": "Enable auto-routing", "enable-auto-routing": "Enable auto-routing",
"english": "Angol", "english": "Angol",

View File

@@ -31,6 +31,17 @@
"before": "Prima", "before": "Prima",
"bike-type": "Bike Type", "bike-type": "Bike Type",
"by": "di", "by": "di",
"calendar": {
"weekdays": {
"0": "",
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"6": ""
}
},
"can": "può", "can": "può",
"cancel": "Annulla", "cancel": "Annulla",
"car": "Car", "car": "Car",
@@ -102,6 +113,8 @@
"email-verified": "Email verified", "email-verified": "Email verified",
"empty-activities": "{username} non ha ancora attività", "empty-activities": "{username} non ha ancora attività",
"empty-bio": "{username} non ha ancora aggiunto una biografia", "empty-bio": "{username} non ha ancora aggiunto una biografia",
"empty-feed": "",
"empty-feed-explanation": "",
"empty-lists": "{username} non ha liste pubbliche", "empty-lists": "{username} non ha liste pubbliche",
"enable-auto-routing": "Enable auto-routing", "enable-auto-routing": "Enable auto-routing",
"english": "Inglese", "english": "Inglese",

View File

@@ -31,6 +31,17 @@
"before": "Voor", "before": "Voor",
"bike-type": "Fiets Type", "bike-type": "Fiets Type",
"by": "door", "by": "door",
"calendar": {
"weekdays": {
"0": "",
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"6": ""
}
},
"can": "kan", "can": "kan",
"cancel": "Annuleren", "cancel": "Annuleren",
"car": "Wagen", "car": "Wagen",
@@ -102,6 +113,8 @@
"email-verified": "Email geverifieerd", "email-verified": "Email geverifieerd",
"empty-activities": "{username} heeft nog geen activiteit", "empty-activities": "{username} heeft nog geen activiteit",
"empty-bio": "{username} heeft nog geen bio toegevoegd", "empty-bio": "{username} heeft nog geen bio toegevoegd",
"empty-feed": "",
"empty-feed-explanation": "",
"empty-lists": "{username} heeft geen publieke lijsten", "empty-lists": "{username} heeft geen publieke lijsten",
"enable-auto-routing": "Automatische routering inschakelen", "enable-auto-routing": "Automatische routering inschakelen",
"english": "Engels", "english": "Engels",

View File

@@ -31,6 +31,17 @@
"before": "Przed", "before": "Przed",
"bike-type": "Typ roweru", "bike-type": "Typ roweru",
"by": "przez", "by": "przez",
"calendar": {
"weekdays": {
"0": "",
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"6": ""
}
},
"can": "może", "can": "może",
"cancel": "Anuluj", "cancel": "Anuluj",
"car": "Samochód", "car": "Samochód",
@@ -102,6 +113,8 @@
"email-verified": "Adres e-mail został zweryfikowany", "email-verified": "Adres e-mail został zweryfikowany",
"empty-activities": "{username} nie ma jeszcze aktywności", "empty-activities": "{username} nie ma jeszcze aktywności",
"empty-bio": "{username} jeszcze nie dodał biogramu", "empty-bio": "{username} jeszcze nie dodał biogramu",
"empty-feed": "",
"empty-feed-explanation": "",
"empty-lists": "{username} nie ma publicznych list", "empty-lists": "{username} nie ma publicznych list",
"enable-auto-routing": "Włącz auto-trasowanie", "enable-auto-routing": "Włącz auto-trasowanie",
"english": "Angielski", "english": "Angielski",

View File

@@ -31,6 +31,17 @@
"before": "Antes", "before": "Antes",
"bike-type": "Bike Type", "bike-type": "Bike Type",
"by": "by", "by": "by",
"calendar": {
"weekdays": {
"0": "",
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"6": ""
}
},
"can": "pode", "can": "pode",
"cancel": "Cancelar", "cancel": "Cancelar",
"car": "Car", "car": "Car",
@@ -102,6 +113,8 @@
"email-verified": "Email verified", "email-verified": "Email verified",
"empty-activities": "{username} has no activity yet", "empty-activities": "{username} has no activity yet",
"empty-bio": "{username} has not added a bio yet", "empty-bio": "{username} has not added a bio yet",
"empty-feed": "",
"empty-feed-explanation": "",
"empty-lists": "{username} has no public lists", "empty-lists": "{username} has no public lists",
"enable-auto-routing": "Enable auto-routing", "enable-auto-routing": "Enable auto-routing",
"english": "Inglês", "english": "Inglês",

View File

@@ -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 подтверждён", "email-verified": "Email подтверждён",
"empty-activities": "у {username} ещё нет активностей", "empty-activities": "у {username} ещё нет активностей",
"empty-bio": "{username} ещё не рассказал(а) о себе", "empty-bio": "{username} ещё не рассказал(а) о себе",
"empty-feed": "",
"empty-feed-explanation": "",
"empty-lists": "у {username} нет публичных списков", "empty-lists": "у {username} нет публичных списков",
"enable-auto-routing": "Авто-маршрутизация", "enable-auto-routing": "Авто-маршрутизация",
"english": "Английский", "english": "Английский",

View File

@@ -31,6 +31,17 @@
"before": "之前", "before": "之前",
"bike-type": "Bike Type", "bike-type": "Bike Type",
"by": "by", "by": "by",
"calendar": {
"weekdays": {
"0": "",
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"6": ""
}
},
"can": "可以", "can": "可以",
"cancel": "取消", "cancel": "取消",
"car": "Car", "car": "Car",
@@ -102,6 +113,8 @@
"email-verified": "Email verified", "email-verified": "Email verified",
"empty-activities": "{username} has no activity yet", "empty-activities": "{username} has no activity yet",
"empty-bio": "{username} has not added a bio yet", "empty-bio": "{username} has not added a bio yet",
"empty-feed": "",
"empty-feed-explanation": "",
"empty-lists": "{username} has no public lists", "empty-lists": "{username} has no public lists",
"enable-auto-routing": "Enable auto-routing", "enable-auto-routing": "Enable auto-routing",
"english": "英语", "english": "英语",

View 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 }

View File

@@ -21,6 +21,7 @@ export class List {
author?: Actor; author?: Actor;
} }
created?: string;
author: string; author: string;
constructor(name: string, trails: Trail[], params?: { description?: string, public?: boolean, avatar?: string, author?: string }) { constructor(name: string, trails: Trail[], params?: { description?: string, public?: boolean, avatar?: string, author?: string }) {

View 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 };
}

View File

@@ -23,6 +23,7 @@ export enum Collection {
activitypub_actors = "activitypub_actors", activitypub_actors = "activitypub_actors",
categories = "categories", categories = "categories",
comments = "comments", comments = "comments",
feed = "feed",
follows = "follows", follows = "follows",
integrations = "integrations", integrations = "integrations",
list_share = "list_share", list_share = "list_share",
@@ -39,6 +40,7 @@ export enum Collection {
trails_bounding_box = "trails_bounding_box", trails_bounding_box = "trails_bounding_box",
trails_filter = "trails_filter", trails_filter = "trails_filter",
users_anonymous = "users_anonymous", users_anonymous = "users_anonymous",
} }
export async function list<T>(event: RequestEvent, collection: Collection) { export async function list<T>(event: RequestEvent, collection: Collection) {

View File

@@ -1,11 +1,14 @@
<script lang="ts"> <script lang="ts">
import { goto } from "$app/navigation"; import { goto } from "$app/navigation";
import { page } from "$app/state";
import emptyStateTrailDark from "$lib/assets/svgs/empty_states/empty_state_trail_dark.svg"; 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 emptyStateTrailLight from "$lib/assets/svgs/empty_states/empty_state_trail_light.svg";
import Search, { import Search, {
type SearchItem, type SearchItem,
} from "$lib/components/base/search.svelte"; } from "$lib/components/base/search.svelte";
import CategoryCard from "$lib/components/category_card.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 Scene from "$lib/components/scene.svelte";
import TrailCard from "$lib/components/trail/trail_card.svelte"; import TrailCard from "$lib/components/trail/trail_card.svelte";
import { import {
@@ -13,6 +16,7 @@
type TrailSearchResult, type TrailSearchResult,
} from "$lib/models/trail.js"; } from "$lib/models/trail.js";
import { categories } from "$lib/stores/category_store"; import { categories } from "$lib/stores/category_store";
import { feed_index } from "$lib/stores/feed_store.js";
import { import {
searchActors, searchActors,
searchMulti, searchMulti,
@@ -20,7 +24,6 @@
type LocationSearchResult, type LocationSearchResult,
} from "$lib/stores/search_store.js"; } from "$lib/stores/search_store.js";
import { theme } from "$lib/stores/theme_store"; import { theme } from "$lib/stores/theme_store";
import { currentUser } from "$lib/stores/user_store";
import { getIconForLocation } from "$lib/util/icon_util.js"; import { getIconForLocation } from "$lib/util/icon_util.js";
import { Canvas } from "@threlte/core"; import { Canvas } from "@threlte/core";
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
@@ -29,6 +32,15 @@
let searchDropdownItems: SearchItem[] = $state([]); 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) { async function search(q: string) {
if (q.startsWith("@")) { if (q.startsWith("@")) {
const actors = await searchActors(q); const actors = await searchActors(q);
@@ -97,18 +109,41 @@
goto(`/map/?lat=${item.value.lat}&lon=${item.value.lon}`); 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> </script>
<svelte:head> <svelte:head>
<title>Home | wanderer</title> <title>Home | wanderer</title>
</svelte:head> </svelte:head>
<svelte:window onscroll={onScroll} />
<section <section
class="hero grid grid-cols-1 lg:grid-cols-2 md:px-8 md:gap-8" 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 <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"> <h2 class="text-5xl sm:text-6xl md:text-7xl font-bold">
{$_("welcome_to")} <span class="-tracking-[0.075em]">wanderer</span> {$_("welcome_to")} <span class="-tracking-[0.075em]">wanderer</span>
@@ -125,12 +160,30 @@
items={searchDropdownItems} items={searchDropdownItems}
></Search> ></Search>
</div> </div>
{#if !page.data.user}
<div class="hidden md:block"> <div class="hidden md:block">
<Canvas toneMapping={0}> <Canvas toneMapping={0}>
<Scene></Scene> <Scene></Scene>
</Canvas> </Canvas>
</div> </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> </section>
{#if !page.data.user}
<section <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" 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.domain
: ''}/{trail.id}" : ''}/{trail.id}"
> >
<TrailCard {trail} fullWidth selected={false} hovered={false}></TrailCard></a <TrailCard
{trail}
fullWidth
selected={false}
hovered={false}
></TrailCard></a
> >
{/each} {/each}
{/if} {/if}
@@ -173,9 +231,7 @@
> >
{:else} {:else}
<h2 class="text-4xl md:text-5xl font-bold"> <h2 class="text-4xl md:text-5xl font-bold">
{$currentUser {$_("explore-some-trails")}
? $_("trails-for-you")
: $_("explore-some-trails")}
</h2> </h2>
<h5> <h5>
{$_("hero_section_1_text")} {$_("hero_section_1_text")}
@@ -208,6 +264,7 @@
{/each} {/each}
</div> </div>
</section> </section>
{/if}
<style> <style>
</style> </style>

View File

@@ -1,5 +1,6 @@
import type { Trail } from "$lib/models/trail"; import type { Trail } from "$lib/models/trail";
import { categories_index } from "$lib/stores/category_store"; 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 { trails_recommend } from "$lib/stores/trail_store";
import type { ServerLoad } from "@sveltejs/kit"; import type { ServerLoad } from "@sveltejs/kit";
@@ -7,11 +8,13 @@ export const load: ServerLoad = async ({ params, locals, fetch }) => {
try { try {
await categories_index(fetch) await categories_index(fetch)
const feed = await feed_index(1, 10, fetch);
const trails: Trail[] = await trails_recommend(4, fetch) const trails: Trail[] = await trails_recommend(4, fetch)
return { trails: trails ?? [] } return { trails: trails ?? [], feed }
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} }
return { trails: [] } return { trails: [], feed: {items: [], page: 1, perPage: 1, totalItems: 0, totalPages: 0} }
}; };

View 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)
}
}

View File

@@ -404,7 +404,6 @@
><i class="fa fa-table mr-3"></i>{$_("all-activities")}</span ><i class="fa fa-table mr-3"></i>{$_("all-activities")}</span
> >
<div class=" overflow-x-auto"> <div class=" overflow-x-auto">
{data.logs.length}
<SummitLogTable <SummitLogTable
{summitLogs} {summitLogs}
handle={page.params.handle} handle={page.params.handle}