finishes feed
This commit is contained in:
26
db/main.go
26
db/main.go
@@ -120,7 +120,7 @@ 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.OnRecordsListRequest("feed", "profile_feed").BindFunc(listFeedHandler())
|
||||||
|
|
||||||
app.OnRecordCreateRequest().BindFunc(sanitizeHTML())
|
app.OnRecordCreateRequest().BindFunc(sanitizeHTML())
|
||||||
app.OnRecordUpdateRequest().BindFunc(sanitizeHTML())
|
app.OnRecordUpdateRequest().BindFunc(sanitizeHTML())
|
||||||
@@ -905,19 +905,35 @@ func listFeedHandler() func(e *core.RecordsListRequestEvent) error {
|
|||||||
for _, r := range e.Records {
|
for _, r := range e.Records {
|
||||||
var item *core.Record
|
var item *core.Record
|
||||||
var err error
|
var err error
|
||||||
switch r.GetString("type") {
|
|
||||||
|
typ := r.GetString("type")
|
||||||
|
typ = strings.Trim(typ, "\"")
|
||||||
|
|
||||||
|
itemId := r.GetString("item")
|
||||||
|
itemId = strings.Trim(itemId, "\"")
|
||||||
|
|
||||||
|
switch typ {
|
||||||
case string(util.TrailFeed):
|
case string(util.TrailFeed):
|
||||||
item, err = e.App.FindRecordById("trails", r.GetString("item"))
|
item, err = e.App.FindRecordById("trails", itemId)
|
||||||
case string(util.ListFeed):
|
case string(util.ListFeed):
|
||||||
item, err = e.App.FindRecordById("lists", r.GetString("item"))
|
item, err = e.App.FindRecordById("lists", itemId)
|
||||||
case string(util.SummitLogFeed):
|
case string(util.SummitLogFeed):
|
||||||
item, err = e.App.FindRecordById("summit_logs", r.GetString("item"))
|
item, err = e.App.FindRecordById("summit_logs", itemId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if item == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := e.App.ExpandRecord(item, []string{"author"}, nil)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return fmt.Errorf("failed to expand: %v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
r.MergeExpand(map[string]any{"item": item})
|
r.MergeExpand(map[string]any{"item": item})
|
||||||
}
|
}
|
||||||
return e.Next()
|
return e.Next()
|
||||||
|
|||||||
106
db/migrations/1752321031_created_profile_feed.go
Normal file
106
db/migrations/1752321031_created_profile_feed.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
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": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text3208210256",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "id",
|
||||||
|
"pattern": "^[a-z0-9]+$",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": true,
|
||||||
|
"required": true,
|
||||||
|
"system": true,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json1148540665",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "actor",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json3182418120",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "author",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json521872670",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "item",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json2363381545",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "type",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json2990389176",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "created",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "pbc_1973704172",
|
||||||
|
"indexes": [],
|
||||||
|
"listRule": "",
|
||||||
|
"name": "profile_feed",
|
||||||
|
"system": false,
|
||||||
|
"type": "view",
|
||||||
|
"updateRule": null,
|
||||||
|
"viewQuery": "SELECT\n (ROW_NUMBER() OVER ()) as id,\n actor,\n author,\n item,\n type,\n created\nFROM\n (\n SELECT\n author as actor,\n author,\n id as item,\n \"list\" as type,\n created\n FROM\n lists\n UNION\n SELECT\n author as actor,\n author,\n id as item,\n \"trail\" as type,\n created\n FROM trails\n )\nORDER BY created desc;",
|
||||||
|
"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_1973704172")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Delete(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
57
db/migrations/1752324952_updated_profile_feed.go
Normal file
57
db/migrations/1752324952_updated_profile_feed.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
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_1973704172")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update collection data
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"viewQuery": "SELECT\n (ROW_NUMBER() OVER ()) as id,\n actor,\n item,\n type,\n created\nFROM\n (\n SELECT\n author as actor,\n id as item,\n 'list' as type,\n created\n FROM\n lists\n UNION\n SELECT\n author as actor,\n id as item,\n 'trail' as type,\n created\n FROM trails\n )\nORDER BY created desc;"
|
||||||
|
}`), &collection); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove field
|
||||||
|
collection.Fields.RemoveById("json3182418120")
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
}, func(app core.App) error {
|
||||||
|
collection, err := app.FindCollectionByNameOrId("pbc_1973704172")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update collection data
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"viewQuery": "SELECT\n (ROW_NUMBER() OVER ()) as id,\n actor,\n author,\n item,\n type,\n created\nFROM\n (\n SELECT\n author as actor,\n author,\n id as item,\n \"list\" as type,\n created\n FROM\n lists\n UNION\n SELECT\n author as actor,\n author,\n id as item,\n \"trail\" as type,\n created\n FROM trails\n )\nORDER BY created desc;"
|
||||||
|
}`), &collection); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json3182418120",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "author",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
44
db/migrations/1752324970_updated_feed.go
Normal file
44
db/migrations/1752324970_updated_feed.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove field
|
||||||
|
collection.Fields.RemoveById("relation3182418120")
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
}, func(app core.App) error {
|
||||||
|
collection, err := app.FindCollectionByNameOrId("pbc_72164123")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add field
|
||||||
|
if err := collection.Fields.AddMarshaledJSONAt(2, []byte(`{
|
||||||
|
"cascadeDelete": true,
|
||||||
|
"collectionId": "pbc_1295301207",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "relation3182418120",
|
||||||
|
"maxSelect": 1,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "author",
|
||||||
|
"presentable": false,
|
||||||
|
"required": true,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
}`)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
216
db/migrations/1752330359_deleted_timeline.go
Normal file
216
db/migrations/1752330359_deleted_timeline.go
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
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_468398817")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Delete(collection)
|
||||||
|
}, func(app core.App) error {
|
||||||
|
jsonData := `{
|
||||||
|
"createRule": null,
|
||||||
|
"deleteRule": null,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text3208210256",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "id",
|
||||||
|
"pattern": "^[a-z0-9]+$",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": true,
|
||||||
|
"required": true,
|
||||||
|
"system": true,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json2310347867",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "trail_id",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json3184124860",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "trail_author_username",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json2887874732",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "trail_author_domain",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json113557190",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "trail_iri",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json2862495610",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "date",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json1579384326",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "name",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json1843675174",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "description",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json3275261007",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "gpx",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json3182418120",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "author",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json142008537",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "photos",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json479369857",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "distance",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json2254405824",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "duration",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json3015100073",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "elevation_gain",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json3171089056",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "elevation_loss",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json2990389176",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "created",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "json2363381545",
|
||||||
|
"maxSize": 1,
|
||||||
|
"name": "type",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "pbc_468398817",
|
||||||
|
"indexes": [],
|
||||||
|
"listRule": "@collection.trails.id ?= trail_id && @collection.trails.public ?= true",
|
||||||
|
"name": "timeline",
|
||||||
|
"system": false,
|
||||||
|
"type": "view",
|
||||||
|
"updateRule": null,
|
||||||
|
"viewQuery": "SELECT\n id,\n trail_id,\n trail_author_username,\n trail_author_domain,\n trail_iri,\n date,\n name,\n description,\n gpx,\n author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n created,\n type\nFROM\n (\n SELECT\n summit_logs.id,\n summit_logs.trail as trail_id,\n tapa.preferred_username as trail_author_username,\n tapa.domain as trail_author_domain,\n trails.iri as trail_iri,\n summit_logs.date,\n trails.name,\n text as description,\n summit_logs.gpx,\n sapa.iri as author,\n summit_logs.photos,\n summit_logs.distance,\n summit_logs.duration,\n summit_logs.elevation_gain,\n summit_logs.elevation_loss,\n summit_logs.created,\n \"summit_log\" as type\n FROM\n summit_logs\n JOIN trails ON summit_logs.trail = trails.id\n JOIN activitypub_actors sapa ON sapa.id = summit_logs.author\n JOIN activitypub_actors tapa ON tapa.id = trails.author\n UNION\n SELECT\n trails.id,\n trails.id as trail_id,\n activitypub_actors.preferred_username as trail_author_username,\n activitypub_actors.domain as trail_author_domain,\n trails.iri as trail_iri,\n date,\n trails.name,\n description,\n gpx,\n activitypub_actors.iri as author,\n photos,\n distance,\n duration,\n elevation_gain,\n elevation_loss,\n trails.created,\n \"trail\" as type\n FROM\n trails\n JOIN activitypub_actors ON activitypub_actors.id = trails.author\n )\nORDER BY\n created DESC;\n",
|
||||||
|
"viewRule": "@collection.trails.id ?= trail_id && @collection.trails.public ?= true"
|
||||||
|
}`
|
||||||
|
|
||||||
|
collection := &core.Collection{}
|
||||||
|
if err := json.Unmarshal([]byte(jsonData), &collection); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
40
db/migrations/1752330401_updated_profile_feed.go
Normal file
40
db/migrations/1752330401_updated_profile_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_1973704172")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update collection data
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"listRule": "actor.user.settings_via_user.privacy.account != 'private' || actor.user = @request.auth.id"
|
||||||
|
}`), &collection); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
}, func(app core.App) error {
|
||||||
|
collection, err := app.FindCollectionByNameOrId("pbc_1973704172")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update collection data
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"listRule": ""
|
||||||
|
}`), &collection); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.Save(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
let isOpen = $state(false);
|
let isOpen = $state(false);
|
||||||
|
|
||||||
let dropdownElement: HTMLUListElement;
|
let dropdownElement: HTMLUListElement | undefined = $state();
|
||||||
let dropdownToggleElement: HTMLDivElement;
|
let dropdownToggleElement: HTMLDivElement;
|
||||||
|
|
||||||
export async function toggleMenu(e: MouseEvent) {
|
export async function toggleMenu(e: MouseEvent) {
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
isOpen = !isOpen;
|
isOpen = !isOpen;
|
||||||
|
|
||||||
if (isOpen) {
|
if (isOpen && dropdownElement) {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
const toggleRect = dropdownToggleElement.getBoundingClientRect();
|
const toggleRect = dropdownToggleElement.getBoundingClientRect();
|
||||||
@@ -81,7 +81,8 @@
|
|||||||
isOpen = false;
|
isOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleItemClick(e: Event, item: { text: string; value: any }) {
|
function handleItemClick(e: MouseEvent, item: { text: string; value: any }) {
|
||||||
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onchange?.(item);
|
onchange?.(item);
|
||||||
closeMenu();
|
closeMenu();
|
||||||
@@ -129,7 +130,7 @@
|
|||||||
<li
|
<li
|
||||||
class="menu-item flex items-center px-4 py-3 cursor-pointer hover:bg-menu-item-background-hover focus:bg-menu-item-background-focus transition-colors"
|
class="menu-item flex items-center px-4 py-3 cursor-pointer hover:bg-menu-item-background-hover focus:bg-menu-item-background-focus transition-colors"
|
||||||
role="presentation"
|
role="presentation"
|
||||||
onmousedown={(e) => handleItemClick(e, item)}
|
onclick={(e) => handleItemClick(e, item)}
|
||||||
>
|
>
|
||||||
{#if item.icon}
|
{#if item.icon}
|
||||||
<i class="fa fa-{item.icon} mr-3"></i>
|
<i class="fa fa-{item.icon} mr-3"></i>
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
|
|
||||||
const dropdownItems = [
|
const dropdownItems = [
|
||||||
{ text: $_("profile"), value: "profile", icon: "user" },
|
{ text: $_("profile"), value: "profile", icon: "user" },
|
||||||
|
{ text: $_("my-trails"), value: "trails", icon: "route" },
|
||||||
|
|
||||||
{ text: $_("settings"), value: "settings", icon: "cog" },
|
{ text: $_("settings"), value: "settings", icon: "cog" },
|
||||||
{ text: $_("logout"), value: "logout", icon: "right-from-bracket" },
|
{ text: $_("logout"), value: "logout", icon: "right-from-bracket" },
|
||||||
];
|
];
|
||||||
@@ -97,6 +99,8 @@
|
|||||||
function handleDropdownClick(item: { text: string; value: any }) {
|
function handleDropdownClick(item: { text: string; value: any }) {
|
||||||
if (item.value == "profile") {
|
if (item.value == "profile") {
|
||||||
goto(`/profile/@${$currentUser?.username?.toLowerCase()}`);
|
goto(`/profile/@${$currentUser?.username?.toLowerCase()}`);
|
||||||
|
} else if (item.value == "trails") {
|
||||||
|
goto(`/profile/@${$currentUser?.username?.toLowerCase()}/trails`);
|
||||||
} else if (item.value == "logout") {
|
} else if (item.value == "logout") {
|
||||||
logout();
|
logout();
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
@@ -185,7 +189,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
<nav class="flex justify-between items-center p-6 {page.url.pathname === '/' ? 'sticky top-0 z-10 bg-background' : ''}">
|
<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>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { FeedItem } from "$lib/models/feed";
|
import type { FeedItem } from "$lib/models/feed";
|
||||||
|
import type { List } from "$lib/models/list";
|
||||||
import type { Trail } from "$lib/models/trail";
|
import type { Trail } from "$lib/models/trail";
|
||||||
import { getFileURL, isVideoURL } from "$lib/util/file_util";
|
import { getFileURL, isVideoURL } from "$lib/util/file_util";
|
||||||
import {
|
import {
|
||||||
@@ -10,34 +11,48 @@
|
|||||||
formatTimeSince,
|
formatTimeSince,
|
||||||
} from "$lib/util/format_util";
|
} from "$lib/util/format_util";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
|
import TrailDropdown from "../trail/trail_dropdown.svelte";
|
||||||
interface Props {
|
interface Props {
|
||||||
feedItem: FeedItem;
|
feedItem: FeedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { feedItem }: Props = $props();
|
let { feedItem }: Props = $props();
|
||||||
|
|
||||||
let fullDescription = $state(false)
|
let fullDescription = $state(false);
|
||||||
|
|
||||||
const timeSince = formatTimeSince(new Date(feedItem.created ?? ""));
|
const timeSince = $derived(formatTimeSince(new Date(feedItem.created ?? "")));
|
||||||
|
|
||||||
const photos = (feedItem.expand.item as Trail).photos
|
const photos = $derived((feedItem.expand.item as Trail).photos);
|
||||||
|
const location = $derived((feedItem.expand.item as Trail).location);
|
||||||
|
|
||||||
|
const trails = $derived((feedItem.expand.item as List).trails);
|
||||||
|
|
||||||
|
const author = $derived(feedItem.expand.item.expand?.author);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="feed-card p-6 rounded-xl border border-input-border">
|
<div class="feed-card px-6 py-4 rounded-xl border border-input-border">
|
||||||
|
<p class="mb-2 text-gray-500 text-sm">
|
||||||
|
{#if feedItem.type === "trail"}
|
||||||
|
<i class="fa fa-route mr-2"></i>{$_("trail", { values: { n: 1 } })}
|
||||||
|
{:else if feedItem.type === "list"}
|
||||||
|
<i class="fa fa-layer-group mr-2"></i>{$_("list", {
|
||||||
|
values: { n: 1 },
|
||||||
|
})}
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a href="/profile/{author?.preferred_username}@{author?.domain}">
|
||||||
<div class="feed-card-header flex gap-x-4 items-start">
|
<div class="feed-card-header flex gap-x-4 items-start">
|
||||||
<img
|
<img
|
||||||
class="rounded-full w-10 aspect-square overflow-hidden shrink-0"
|
class="rounded-full w-10 aspect-square overflow-hidden shrink-0"
|
||||||
src={feedItem.expand.author?.icon ||
|
src={author?.icon ||
|
||||||
`https://api.dicebear.com/7.x/initials/svg?seed=${feedItem.expand.author?.preferred_username}&backgroundType=gradientLinear`}
|
`https://api.dicebear.com/7.x/initials/svg?seed=${author?.preferred_username}&backgroundType=gradientLinear`}
|
||||||
alt="avatar"
|
alt="avatar"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<span class="font-semibold"
|
<span class="font-semibold">{author?.preferred_username}</span>
|
||||||
>{feedItem.expand.author?.preferred_username}</span
|
|
||||||
>
|
|
||||||
<p class="text-sm text-gray-500 mb-3">
|
<p class="text-sm text-gray-500 mb-3">
|
||||||
{feedItem.expand.author?.preferred_username}@{feedItem.expand
|
{author?.preferred_username}@{author?.domain}
|
||||||
.author?.domain}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="basis-full"></div>
|
<div class="basis-full"></div>
|
||||||
@@ -47,11 +62,24 @@
|
|||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class="block"
|
||||||
|
href={(feedItem.type === "trail" ? "/trail/view/" : "/lists/") +
|
||||||
|
`@${author?.preferred_username}@${author?.domain}/${feedItem.item}`}
|
||||||
|
>
|
||||||
<div class="feed-card-body">
|
<div class="feed-card-body">
|
||||||
<h3 class="text-2xl font-semibold !mt-2">
|
<h3 class="text-2xl font-semibold !mt-2">
|
||||||
{feedItem.expand.item.name}
|
{feedItem.expand.item.name}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="flex flex-wrap mt-1 gap-x-4 gap-y-2 text-sm text-gray-500">
|
{#if location}
|
||||||
|
<h5 class="my-1">
|
||||||
|
<i class="fa fa-location-dot mr-3"></i>{location}
|
||||||
|
</h5>
|
||||||
|
{/if}
|
||||||
|
<div
|
||||||
|
class="flex flex-wrap mt-1 gap-x-4 gap-y-2 text-sm text-gray-500 mb-2"
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
><i class="fa fa-left-right mr-2"></i>{formatDistance(
|
><i class="fa fa-left-right mr-2"></i>{formatDistance(
|
||||||
feedItem.expand.item.distance,
|
feedItem.expand.item.distance,
|
||||||
@@ -68,16 +96,25 @@
|
|||||||
)}</span
|
)}</span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
><i class="fa fa-arrow-trend-down mr-2"></i>{formatElevation(
|
><i class="fa fa-arrow-trend-down mr-2"
|
||||||
|
></i>{formatElevation(
|
||||||
feedItem.expand.item.elevation_loss,
|
feedItem.expand.item.elevation_loss,
|
||||||
)}</span
|
)}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
{#if trails}
|
||||||
|
<p class="text-sm text-gray-500">
|
||||||
|
{trails.length}
|
||||||
|
{$_("trail", {
|
||||||
|
values: { n: trails.length },
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
{#if photos?.length}
|
{#if photos?.length}
|
||||||
<div
|
<div
|
||||||
class="grid gap-[1px] {photos.length > 1
|
class="grid gap-[1px] {photos.length > 1
|
||||||
? 'grid-cols-[8fr_5fr]'
|
? 'grid-cols-[8fr_5fr]'
|
||||||
: 'grid-cols-1'} mt-6"
|
: 'grid-cols-1'} mt-4"
|
||||||
>
|
>
|
||||||
{#each photos.slice(0, 3) as photo, i}
|
{#each photos.slice(0, 3) as photo, i}
|
||||||
{#if isVideoURL(photo)}
|
{#if isVideoURL(photo)}
|
||||||
@@ -97,8 +134,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<img
|
<img
|
||||||
class="object-cover h-full max-h-80 w-full"
|
class="object-cover h-full max-h-80 w-full"
|
||||||
class:row-span-2={i == 0 &&
|
class:row-span-2={i == 0 && photos.length > 2}
|
||||||
photos.length > 2}
|
|
||||||
src={getFileURL(
|
src={getFileURL(
|
||||||
{
|
{
|
||||||
collectionId: "trails",
|
collectionId: "trails",
|
||||||
@@ -116,7 +152,10 @@
|
|||||||
<p class="text-sm whitespace-pre-wrap mt-6">
|
<p class="text-sm whitespace-pre-wrap mt-6">
|
||||||
{formatHTMLAsText(
|
{formatHTMLAsText(
|
||||||
!fullDescription
|
!fullDescription
|
||||||
? feedItem.expand.item.description?.substring(0, 100)
|
? feedItem.expand.item.description?.substring(
|
||||||
|
0,
|
||||||
|
100,
|
||||||
|
)
|
||||||
: feedItem.expand.item.description,
|
: feedItem.expand.item.description,
|
||||||
)}
|
)}
|
||||||
{#if (feedItem.expand.item.description?.length ?? 0) > 100 && !fullDescription}
|
{#if (feedItem.expand.item.description?.length ?? 0) > 100 && !fullDescription}
|
||||||
@@ -134,4 +173,23 @@
|
|||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
|
{#if feedItem.type == "trail"}
|
||||||
|
<div class="feed-card-actions flex items-center justify-end mt-4">
|
||||||
|
<TrailDropdown
|
||||||
|
trails={new Set<Trail>([feedItem.expand.item as Trail])}
|
||||||
|
mode="overview"
|
||||||
|
>
|
||||||
|
{#snippet toggle({ toggleMenu: openDropdown })}
|
||||||
|
<button
|
||||||
|
class="btn-icon"
|
||||||
|
onclick={openDropdown}
|
||||||
|
aria-label="Trail actions"
|
||||||
|
type="button"
|
||||||
|
><i class="fa fa-ellipsis-vertical"></i></button
|
||||||
|
>
|
||||||
|
{/snippet}</TrailDropdown
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,14 +21,17 @@
|
|||||||
import TrailExportModal from "./trail_export_modal.svelte";
|
import TrailExportModal from "./trail_export_modal.svelte";
|
||||||
import TrailShareModal from "./trail_share_modal.svelte";
|
import TrailShareModal from "./trail_share_modal.svelte";
|
||||||
import { handleFromRecordWithIRI } from "$lib/util/activitypub_util";
|
import { handleFromRecordWithIRI } from "$lib/util/activitypub_util";
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
trails?: Set<Trail> | undefined;
|
trails?: Set<Trail> | undefined;
|
||||||
mode: "overview" | "map" | "list" | "multi-select";
|
mode: "overview" | "map" | "list" | "multi-select";
|
||||||
onconfirm?: (resetSelection?: boolean) => void;
|
toggle?: Snippet<[any]>;
|
||||||
|
onDelete?: () => void;
|
||||||
|
onShare?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { trails, mode, onconfirm }: Props = $props();
|
let { trails, mode, toggle, onDelete, onShare }: Props = $props();
|
||||||
|
|
||||||
let confirmModal: ConfirmModal;
|
let confirmModal: ConfirmModal;
|
||||||
let listSelectModal: ListSelectModal;
|
let listSelectModal: ListSelectModal;
|
||||||
@@ -302,7 +305,7 @@
|
|||||||
await doDeleteTrail(dTrail);
|
await doDeleteTrail(dTrail);
|
||||||
}
|
}
|
||||||
|
|
||||||
onconfirm?.(true);
|
onDelete?.();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,7 +318,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleShareUpdate() {
|
async function handleShareUpdate() {
|
||||||
onconfirm?.();
|
onShare?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleListSelection(list: List) {
|
async function handleListSelection(list: List) {
|
||||||
@@ -390,9 +393,14 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Dropdown items={dropdownItems()} onchange={(item) => handleDropdownClick(item)}
|
<Dropdown
|
||||||
>{#snippet children({ toggleMenu: openDropdown })}
|
items={dropdownItems()}
|
||||||
{#if mode == "multi-select"}
|
onchange={(item) => handleDropdownClick(item)}
|
||||||
|
>
|
||||||
|
{#snippet children({ toggleMenu: openDropdown })}
|
||||||
|
{#if toggle}{@render toggle({
|
||||||
|
toggleMenu: openDropdown,
|
||||||
|
})}{:else if mode == "multi-select"}
|
||||||
<button
|
<button
|
||||||
aria-label="Open dropdown"
|
aria-label="Open dropdown"
|
||||||
class="btn-primary flex-shrink-0 !font-medium"
|
class="btn-primary flex-shrink-0 !font-medium"
|
||||||
@@ -400,7 +408,8 @@
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
>{trails?.size}
|
>{trails?.size}
|
||||||
{$_("selected")} <i class="fa fa-caret-down ml-1"></i></span
|
{$_("selected")}
|
||||||
|
<i class="fa fa-caret-down ml-1"></i></span
|
||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -403,6 +403,10 @@
|
|||||||
<LikeButton {trail}></LikeButton>
|
<LikeButton {trail}></LikeButton>
|
||||||
<TrailDropdown
|
<TrailDropdown
|
||||||
trails={new Set<Trail>([trail])}
|
trails={new Set<Trail>([trail])}
|
||||||
|
onDelete={() =>
|
||||||
|
history.length
|
||||||
|
? history.back()
|
||||||
|
: goto("/trails")}
|
||||||
{mode}
|
{mode}
|
||||||
></TrailDropdown>
|
></TrailDropdown>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import TrailTable from "./trail_table.svelte";
|
import TrailTable from "./trail_table.svelte";
|
||||||
import SkeletonCard from "../base/skeleton_card.svelte";
|
import SkeletonCard from "../base/skeleton_card.svelte";
|
||||||
import SkeletonListItem from "../base/skeleton_list_item.svelte";
|
import SkeletonListItem from "../base/skeleton_list_item.svelte";
|
||||||
import { onMount } from "svelte";
|
import { onMount, tick } from "svelte";
|
||||||
import TrailDropdown from "$lib/components/trail/trail_dropdown.svelte";
|
import TrailDropdown from "$lib/components/trail/trail_dropdown.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -191,15 +191,13 @@
|
|||||||
else hoveredTrail = undefined;
|
else hoveredTrail = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTrailsEditDone(resetSelection: boolean = false) {
|
async function handleTrailsEditDone(resetSelection: boolean = false) {
|
||||||
if (resetSelection) {
|
if (resetSelection) {
|
||||||
selection?.clear();
|
selection?.clear();
|
||||||
hoveredTrail = undefined;
|
hoveredTrail = undefined;
|
||||||
}
|
}
|
||||||
|
await tick();
|
||||||
setTimeout(() => {
|
|
||||||
onupdate?.(filter, selection);
|
onupdate?.(filter, selection);
|
||||||
}, 500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMouseEnter(trail: Trail) {
|
function handleMouseEnter(trail: Trail) {
|
||||||
@@ -224,7 +222,8 @@
|
|||||||
<TrailDropdown
|
<TrailDropdown
|
||||||
trails={selection}
|
trails={selection}
|
||||||
mode={"multi-select"}
|
mode={"multi-select"}
|
||||||
onconfirm={handleTrailsEditDone}
|
onDelete={() => handleTrailsEditDone(true)}
|
||||||
|
onShare={() => handleTrailsEditDone(false)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -218,6 +218,7 @@
|
|||||||
"must-be-at-least-n-characters-long": "Muss mindestens {n} Zeichen lang sein",
|
"must-be-at-least-n-characters-long": "Muss mindestens {n} Zeichen lang sein",
|
||||||
"must-be-at-most-n-characters-long": "Darf höchstens {n} Zeichen lang sein",
|
"must-be-at-most-n-characters-long": "Darf höchstens {n} Zeichen lang sein",
|
||||||
"my-account": "Mein Konto",
|
"my-account": "Mein Konto",
|
||||||
|
"my-trails": "",
|
||||||
"n-days-ago": "vor {n} Tagen",
|
"n-days-ago": "vor {n} Tagen",
|
||||||
"n-hours-ago": "vor {n} Stunden",
|
"n-hours-ago": "vor {n} Stunden",
|
||||||
"n-minutes-ago": "vor {n} Minuten",
|
"n-minutes-ago": "vor {n} Minuten",
|
||||||
|
|||||||
@@ -218,6 +218,7 @@
|
|||||||
"must-be-at-least-n-characters-long": "Must be at least {n} characters long",
|
"must-be-at-least-n-characters-long": "Must be at least {n} characters long",
|
||||||
"must-be-at-most-n-characters-long": "Must be at most {n} characters long",
|
"must-be-at-most-n-characters-long": "Must be at most {n} characters long",
|
||||||
"my-account": "My Account",
|
"my-account": "My Account",
|
||||||
|
"my-trails": "My trails",
|
||||||
"n-days-ago": "{n} days ago",
|
"n-days-ago": "{n} days ago",
|
||||||
"n-hours-ago": "{n} hours ago",
|
"n-hours-ago": "{n} hours ago",
|
||||||
"n-minutes-ago": "{n} minutes ago",
|
"n-minutes-ago": "{n} minutes ago",
|
||||||
|
|||||||
@@ -218,6 +218,7 @@
|
|||||||
"must-be-at-least-n-characters-long": "Tiene que tener por lo menos {n} caracteres",
|
"must-be-at-least-n-characters-long": "Tiene que tener por lo menos {n} caracteres",
|
||||||
"must-be-at-most-n-characters-long": "Must be at most {n} characters long",
|
"must-be-at-most-n-characters-long": "Must be at most {n} characters long",
|
||||||
"my-account": "Mi cuenta",
|
"my-account": "Mi cuenta",
|
||||||
|
"my-trails": "",
|
||||||
"n-days-ago": "hace {n} días",
|
"n-days-ago": "hace {n} días",
|
||||||
"n-hours-ago": "hace {n} horas",
|
"n-hours-ago": "hace {n} horas",
|
||||||
"n-minutes-ago": "hace {n} minutos",
|
"n-minutes-ago": "hace {n} minutos",
|
||||||
|
|||||||
@@ -218,6 +218,7 @@
|
|||||||
"must-be-at-least-n-characters-long": "Must be at least {n} characters long",
|
"must-be-at-least-n-characters-long": "Must be at least {n} characters long",
|
||||||
"must-be-at-most-n-characters-long": "Must be at most {n} characters long",
|
"must-be-at-most-n-characters-long": "Must be at most {n} characters long",
|
||||||
"my-account": "My Account",
|
"my-account": "My Account",
|
||||||
|
"my-trails": "",
|
||||||
"n-days-ago": "{n} days ago",
|
"n-days-ago": "{n} days ago",
|
||||||
"n-hours-ago": "{n} hours ago",
|
"n-hours-ago": "{n} hours ago",
|
||||||
"n-minutes-ago": "{n} minutes ago",
|
"n-minutes-ago": "{n} minutes ago",
|
||||||
|
|||||||
@@ -218,6 +218,7 @@
|
|||||||
"must-be-at-least-n-characters-long": "Doit être composé d'au moins {n} caractères",
|
"must-be-at-least-n-characters-long": "Doit être composé d'au moins {n} caractères",
|
||||||
"must-be-at-most-n-characters-long": "Doit être au maximum de {n} caractères",
|
"must-be-at-most-n-characters-long": "Doit être au maximum de {n} caractères",
|
||||||
"my-account": "Mon profil",
|
"my-account": "Mon profil",
|
||||||
|
"my-trails": "",
|
||||||
"n-days-ago": "il y a {n} jours",
|
"n-days-ago": "il y a {n} jours",
|
||||||
"n-hours-ago": "il y a {n} heures",
|
"n-hours-ago": "il y a {n} heures",
|
||||||
"n-minutes-ago": "il y a {n} minutes",
|
"n-minutes-ago": "il y a {n} minutes",
|
||||||
|
|||||||
@@ -218,6 +218,7 @@
|
|||||||
"must-be-at-least-n-characters-long": "Legalább {n} karakter hosszúnak kell lennie",
|
"must-be-at-least-n-characters-long": "Legalább {n} karakter hosszúnak kell lennie",
|
||||||
"must-be-at-most-n-characters-long": "Must be at most {n} characters long",
|
"must-be-at-most-n-characters-long": "Must be at most {n} characters long",
|
||||||
"my-account": "My Account",
|
"my-account": "My Account",
|
||||||
|
"my-trails": "",
|
||||||
"n-days-ago": "{n} days ago",
|
"n-days-ago": "{n} days ago",
|
||||||
"n-hours-ago": "{n} hours ago",
|
"n-hours-ago": "{n} hours ago",
|
||||||
"n-minutes-ago": "{n} minutes ago",
|
"n-minutes-ago": "{n} minutes ago",
|
||||||
|
|||||||
@@ -218,6 +218,7 @@
|
|||||||
"must-be-at-least-n-characters-long": "Deve essere lungo almeno {n} caratteri",
|
"must-be-at-least-n-characters-long": "Deve essere lungo almeno {n} caratteri",
|
||||||
"must-be-at-most-n-characters-long": "Must be at most {n} characters long",
|
"must-be-at-most-n-characters-long": "Must be at most {n} characters long",
|
||||||
"my-account": "Il mio account",
|
"my-account": "Il mio account",
|
||||||
|
"my-trails": "",
|
||||||
"n-days-ago": "{n} giorni fa",
|
"n-days-ago": "{n} giorni fa",
|
||||||
"n-hours-ago": "{n} ore fa",
|
"n-hours-ago": "{n} ore fa",
|
||||||
"n-minutes-ago": "{n} minuti fa",
|
"n-minutes-ago": "{n} minuti fa",
|
||||||
|
|||||||
@@ -218,6 +218,7 @@
|
|||||||
"must-be-at-least-n-characters-long": "Minimaal {n} tekens",
|
"must-be-at-least-n-characters-long": "Minimaal {n} tekens",
|
||||||
"must-be-at-most-n-characters-long": "Mag maximaal {n} tekens lang zijn.",
|
"must-be-at-most-n-characters-long": "Mag maximaal {n} tekens lang zijn.",
|
||||||
"my-account": "Mijn Account",
|
"my-account": "Mijn Account",
|
||||||
|
"my-trails": "",
|
||||||
"n-days-ago": "{n} dagen geleden",
|
"n-days-ago": "{n} dagen geleden",
|
||||||
"n-hours-ago": "{n} uuren geleden",
|
"n-hours-ago": "{n} uuren geleden",
|
||||||
"n-minutes-ago": "{n} minuten geleden",
|
"n-minutes-ago": "{n} minuten geleden",
|
||||||
|
|||||||
@@ -218,6 +218,7 @@
|
|||||||
"must-be-at-least-n-characters-long": "Długość musi wynosić przynajmniej {n} znaków",
|
"must-be-at-least-n-characters-long": "Długość musi wynosić przynajmniej {n} znaków",
|
||||||
"must-be-at-most-n-characters-long": "Musi mieć długość co najwyżej {n} znaków",
|
"must-be-at-most-n-characters-long": "Musi mieć długość co najwyżej {n} znaków",
|
||||||
"my-account": "Moje konto",
|
"my-account": "Moje konto",
|
||||||
|
"my-trails": "",
|
||||||
"n-days-ago": "{n} dni temu",
|
"n-days-ago": "{n} dni temu",
|
||||||
"n-hours-ago": "{n} godzin temu",
|
"n-hours-ago": "{n} godzin temu",
|
||||||
"n-minutes-ago": "{n} minut temu",
|
"n-minutes-ago": "{n} minut temu",
|
||||||
|
|||||||
@@ -218,6 +218,7 @@
|
|||||||
"must-be-at-least-n-characters-long": "Deve ter pelo menos {n} caracteres",
|
"must-be-at-least-n-characters-long": "Deve ter pelo menos {n} caracteres",
|
||||||
"must-be-at-most-n-characters-long": "Must be at most {n} characters long",
|
"must-be-at-most-n-characters-long": "Must be at most {n} characters long",
|
||||||
"my-account": "A minha conta",
|
"my-account": "A minha conta",
|
||||||
|
"my-trails": "",
|
||||||
"n-days-ago": "{n} dias atrás",
|
"n-days-ago": "{n} dias atrás",
|
||||||
"n-hours-ago": "{n} horas atrás",
|
"n-hours-ago": "{n} horas atrás",
|
||||||
"n-minutes-ago": "{n} minutos atrás",
|
"n-minutes-ago": "{n} minutos atrás",
|
||||||
|
|||||||
@@ -218,6 +218,7 @@
|
|||||||
"must-be-at-least-n-characters-long": "Минимум {n} символов",
|
"must-be-at-least-n-characters-long": "Минимум {n} символов",
|
||||||
"must-be-at-most-n-characters-long": "Максимум {n} символов",
|
"must-be-at-most-n-characters-long": "Максимум {n} символов",
|
||||||
"my-account": "Мой аккаунт",
|
"my-account": "Мой аккаунт",
|
||||||
|
"my-trails": "",
|
||||||
"n-days-ago": "{n} дней назад",
|
"n-days-ago": "{n} дней назад",
|
||||||
"n-hours-ago": "{n} часов назад",
|
"n-hours-ago": "{n} часов назад",
|
||||||
"n-minutes-ago": "{n} минут назад",
|
"n-minutes-ago": "{n} минут назад",
|
||||||
|
|||||||
@@ -218,6 +218,7 @@
|
|||||||
"must-be-at-least-n-characters-long": "长度至少 {n} 字符",
|
"must-be-at-least-n-characters-long": "长度至少 {n} 字符",
|
||||||
"must-be-at-most-n-characters-long": "Must be at most {n} characters long",
|
"must-be-at-most-n-characters-long": "Must be at most {n} characters long",
|
||||||
"my-account": "我的账户",
|
"my-account": "我的账户",
|
||||||
|
"my-trails": "",
|
||||||
"n-days-ago": "{n} 天前",
|
"n-days-ago": "{n} 天前",
|
||||||
"n-hours-ago": "{n} 小时前",
|
"n-hours-ago": "{n} 小时前",
|
||||||
"n-minutes-ago": "{n} 分钟前",
|
"n-minutes-ago": "{n} 分钟前",
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
import type { Actor } from "./activitypub/actor";
|
import type { Actor } from "./activitypub/actor";
|
||||||
import type { List } from "./list";
|
import type { List } from "./list";
|
||||||
import type { SummitLog } from "./summit_log";
|
|
||||||
import type { Trail } from "./trail";
|
import type { Trail } from "./trail";
|
||||||
|
|
||||||
interface FeedItem {
|
interface FeedItem {
|
||||||
id: string;
|
id: string;
|
||||||
actor: string;
|
actor: string;
|
||||||
author: string;
|
|
||||||
item: string;
|
item: string;
|
||||||
type: "trail" | "list" | "summit_log"
|
type: "trail" | "list" | "summit_log"
|
||||||
expand: {
|
expand: {
|
||||||
actor?: Actor,
|
actor?: Actor,
|
||||||
author?: Actor,
|
|
||||||
item: Trail | List
|
item: Trail | List
|
||||||
}
|
}
|
||||||
created: string;
|
created: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { type FeedItem }
|
export { type FeedItem };
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ let feed: FeedItem[] = []
|
|||||||
|
|
||||||
export async function feed_index(page: number, perPage: number = 10, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
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({
|
let r = await f(`/api/v1/feed?` + new URLSearchParams({
|
||||||
expand: "author",
|
|
||||||
page: page.toString(),
|
page: page.toString(),
|
||||||
perPage: perPage.toString(),
|
perPage: perPage.toString(),
|
||||||
sort: '-created'
|
sort: '-created'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import type { FeedItem } from "$lib/models/feed";
|
||||||
import type { ListFilter } from "$lib/models/list";
|
import type { ListFilter } from "$lib/models/list";
|
||||||
import type { SummitLog, SummitLogFilter } from "$lib/models/summit_log";
|
import type { SummitLog, SummitLogFilter } from "$lib/models/summit_log";
|
||||||
import type { TimelineItem } from "$lib/models/timeline";
|
|
||||||
import { defaultTrailSearchAttributes, Trail, type TrailFilter, type TrailSearchResult } from "$lib/models/trail";
|
import { defaultTrailSearchAttributes, Trail, type TrailFilter, type TrailSearchResult } from "$lib/models/trail";
|
||||||
import { APIError } from "$lib/util/api_util";
|
import { APIError } from "$lib/util/api_util";
|
||||||
import type { Hits } from "meilisearch";
|
import type { Hits } from "meilisearch";
|
||||||
@@ -10,7 +10,7 @@ import type { ListSearchResult } from "./search_store";
|
|||||||
import { buildFilterText } from "./summit_log_store";
|
import { buildFilterText } from "./summit_log_store";
|
||||||
import { searchResultToTrailList } from "./trail_store";
|
import { searchResultToTrailList } from "./trail_store";
|
||||||
|
|
||||||
let timeline: TimelineItem[] = []
|
let feed: FeedItem[] = []
|
||||||
|
|
||||||
|
|
||||||
export async function profile_show(handle: string, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
export async function profile_show(handle: string, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||||
@@ -58,8 +58,8 @@ export async function profile_lists_index(handle: string, filter: ListFilter, pa
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function profile_timeline_index(handle: string, page: number, perPage: number = 10, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
export async function profile_feed_index(handle: string, page: number, perPage: number = 10, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
|
||||||
let r = await f(`/api/v1/profile/${handle}/timeline?` + new URLSearchParams({
|
let r = await f(`/api/v1/profile/${handle}/feed?` + new URLSearchParams({
|
||||||
page: page.toString(),
|
page: page.toString(),
|
||||||
perPage: perPage.toString(),
|
perPage: perPage.toString(),
|
||||||
sort: '-created'
|
sort: '-created'
|
||||||
@@ -71,13 +71,13 @@ export async function profile_timeline_index(handle: string, page: number, perPa
|
|||||||
throw new APIError(r.status, response.message, response.detail)
|
throw new APIError(r.status, response.message, response.detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchedTimeline: ListResult<TimelineItem> = await r.json()
|
const fetchedFeed: ListResult<FeedItem> = await r.json()
|
||||||
|
|
||||||
const result = page > 1 ? [...timeline, ...fetchedTimeline.items] : fetchedTimeline.items
|
const result = page > 1 ? [...feed, ...fetchedFeed.items] : fetchedFeed.items
|
||||||
|
|
||||||
timeline = result;
|
feed = result;
|
||||||
|
|
||||||
return { ...fetchedTimeline, items: result };
|
return { ...fetchedFeed, items: result };
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export enum Collection {
|
|||||||
list_share = "list_share",
|
list_share = "list_share",
|
||||||
lists = "lists",
|
lists = "lists",
|
||||||
notifications = "notifications",
|
notifications = "notifications",
|
||||||
|
profile_feed = "profile_feed",
|
||||||
settings = "settings",
|
settings = "settings",
|
||||||
summit_logs = "summit_logs",
|
summit_logs = "summit_logs",
|
||||||
trail_like = "trail_like",
|
trail_like = "trail_like",
|
||||||
@@ -36,7 +37,6 @@ export enum Collection {
|
|||||||
trails = "trails",
|
trails = "trails",
|
||||||
tags = "tags",
|
tags = "tags",
|
||||||
waypoints = "waypoints",
|
waypoints = "waypoints",
|
||||||
timeline = "timeline",
|
|
||||||
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",
|
||||||
|
|||||||
@@ -139,11 +139,12 @@
|
|||||||
<svelte:window onscroll={onScroll} />
|
<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 gap-4 md:gap-8"
|
||||||
style="min-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 max-h-screen md:sticky top-0"
|
class="flex flex-col justify-center gap-8 max-w-md mx-8 sm:mx-auto mt-0 lg:sticky"
|
||||||
|
style="max-height: calc(100vh - 112px); top: 112px;"
|
||||||
>
|
>
|
||||||
<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>
|
||||||
@@ -172,16 +173,8 @@
|
|||||||
<EmptyStateFeed></EmptyStateFeed>
|
<EmptyStateFeed></EmptyStateFeed>
|
||||||
{/if}
|
{/if}
|
||||||
{#each feed.items as f}
|
{#each feed.items as f}
|
||||||
{#if f.expand.item}
|
{#if f.expand?.item}
|
||||||
<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>
|
<FeedCard feedItem={f}></FeedCard>
|
||||||
</a>
|
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
52
web/src/routes/api/v1/profile/[handle]/feed/+server.ts
Normal file
52
web/src/routes/api/v1/profile/[handle]/feed/+server.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { RecordListOptionsSchema } from '$lib/models/api/base_schema';
|
||||||
|
import { type FeedItem } from '$lib/models/feed';
|
||||||
|
import type { Trail } from '$lib/models/trail';
|
||||||
|
import { Collection, handleError } from '$lib/util/api_util';
|
||||||
|
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||||
|
import { ClientResponseError, type ListResult } from 'pocketbase';
|
||||||
|
|
||||||
|
export async function GET(event: RequestEvent) {
|
||||||
|
const handle = event.params.handle;
|
||||||
|
if (!handle) {
|
||||||
|
return error(400, { message: "Bad request" })
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { actor, error } = await event.locals.pb.send(`/activitypub/actor?resource=acct:${handle}`, { method: "GET", fetch: event.fetch, });
|
||||||
|
|
||||||
|
const searchParams = Object.fromEntries(event.url.searchParams);
|
||||||
|
const safeSearchParams = RecordListOptionsSchema.parse(searchParams);
|
||||||
|
|
||||||
|
let feed: ListResult<FeedItem>;
|
||||||
|
if (actor.isLocal) {
|
||||||
|
feed = await event.locals.pb.collection(Collection.profile_feed)
|
||||||
|
.getList<FeedItem>(safeSearchParams.page, safeSearchParams.perPage, { ...safeSearchParams, filter: `actor='${actor.id}'` })
|
||||||
|
} else {
|
||||||
|
const origin = new URL(actor.iri).origin
|
||||||
|
const feedURL = `${origin}/api/v1/profile/${actor.preferred_username}/feed?` + event.url.searchParams
|
||||||
|
|
||||||
|
const response = await event.fetch(feedURL, { method: 'GET' })
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorResponse = await response.json()
|
||||||
|
throw new ClientResponseError({ status: response.status, response: errorResponse });
|
||||||
|
}
|
||||||
|
feed = await response.json()
|
||||||
|
|
||||||
|
feed.items.forEach(f => {
|
||||||
|
if (f.type == "trail") {
|
||||||
|
const trail = f.expand.item as Trail
|
||||||
|
trail.photos = trail.photos.map(p =>
|
||||||
|
`${origin}/api/v1/files/trails/${f.item}/${p}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return json(feed)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return handleError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import type { Actor } from '$lib/models/activitypub/actor';
|
|
||||||
import { RecordListOptionsSchema } from '$lib/models/api/base_schema';
|
|
||||||
import { type TimelineItem } from '$lib/models/timeline';
|
|
||||||
import { Collection, handleError } from '$lib/util/api_util';
|
|
||||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
|
||||||
import { ClientResponseError, type ListResult } from 'pocketbase';
|
|
||||||
|
|
||||||
export async function GET(event: RequestEvent) {
|
|
||||||
const handle = event.params.handle;
|
|
||||||
if (!handle) {
|
|
||||||
return error(400, { message: "Bad request" })
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const {actor, error} = await event.locals.pb.send(`/activitypub/actor?resource=acct:${handle}`, { method: "GET", fetch: event.fetch, });
|
|
||||||
|
|
||||||
const searchParams = Object.fromEntries(event.url.searchParams);
|
|
||||||
const safeSearchParams = RecordListOptionsSchema.parse(searchParams);
|
|
||||||
|
|
||||||
let timeline: ListResult<TimelineItem>;
|
|
||||||
if (actor.isLocal) {
|
|
||||||
timeline = await event.locals.pb.collection(Collection.timeline)
|
|
||||||
.getList<TimelineItem>(safeSearchParams.page, safeSearchParams.perPage, { ...safeSearchParams, filter: `author='${actor.iri}'` })
|
|
||||||
} else {
|
|
||||||
const origin = new URL(actor.iri).origin
|
|
||||||
const timelineURL = `${origin}/api/v1/profile/${actor.preferred_username}/timeline?` + event.url.searchParams
|
|
||||||
|
|
||||||
const response = await event.fetch(timelineURL, { method: 'GET' })
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorResponse = await response.json()
|
|
||||||
throw new ClientResponseError({ status: response.status, response: errorResponse });
|
|
||||||
}
|
|
||||||
timeline = await response.json()
|
|
||||||
|
|
||||||
timeline.items.forEach(i => {
|
|
||||||
i.photos = i. photos.map(p =>
|
|
||||||
`${origin}/api/v1/files/${i.type}s/${i.id}/${p}`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return json(timeline)
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
return handleError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,23 +3,28 @@
|
|||||||
import { page } from "$app/state";
|
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 ActivityCard from "$lib/components/profile/activity_card.svelte";
|
import FeedCard from "$lib/components/profile/feed_card.svelte";
|
||||||
|
import type { FeedItem } from "$lib/models/feed.js";
|
||||||
|
|
||||||
import type { TimelineItem } from "$lib/models/timeline.js";
|
import { profile_feed_index } from "$lib/stores/profile_store.js";
|
||||||
import { profile_timeline_index } from "$lib/stores/profile_store.js";
|
|
||||||
import { theme } from "$lib/stores/theme_store.js";
|
import { theme } from "$lib/stores/theme_store.js";
|
||||||
import { getFileURL } from "$lib/util/file_util.js";
|
import { getFileURL } from "$lib/util/file_util.js";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
let timeline = $state(data.timeline);
|
$effect(() => {
|
||||||
|
data.feed;
|
||||||
|
feed = data.feed
|
||||||
|
})
|
||||||
|
|
||||||
|
let feed = $state(data.feed);
|
||||||
|
|
||||||
let loading: boolean = false;
|
let loading: boolean = false;
|
||||||
|
|
||||||
let pagination = $derived({
|
let pagination = $derived({
|
||||||
page: timeline.page,
|
page: feed.page,
|
||||||
totalPages: timeline.totalItems,
|
totalPages: feed.totalItems,
|
||||||
});
|
});
|
||||||
|
|
||||||
async function onListScroll(e: Event) {
|
async function onListScroll(e: Event) {
|
||||||
@@ -37,19 +42,19 @@
|
|||||||
|
|
||||||
async function loadNextPage() {
|
async function loadNextPage() {
|
||||||
pagination.page += 1;
|
pagination.page += 1;
|
||||||
timeline = await profile_timeline_index(
|
feed = await profile_feed_index(page.params.handle, pagination.page);
|
||||||
page.params.handle,
|
|
||||||
pagination.page,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTimeLineItemClick(item: TimelineItem) {
|
function handleFeedItemClick(f: FeedItem) {
|
||||||
if (item.trail_iri.length) {
|
const author = f.expand.item.expand?.author;
|
||||||
|
if (f.type == "trail") {
|
||||||
goto(
|
goto(
|
||||||
`/trail/view/@${item.trail_author_username}@${item.trail_author_domain}/${item.trail_id}`,
|
`/trail/view/@${author?.preferred_username}@${author?.domain}/${f.item}`,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
goto(`/trail/view/${page.params.handle}/${item.trail_id}`);
|
goto(
|
||||||
|
`/lists/@${author?.preferred_username}@${author?.domain}/${f.item}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -130,24 +135,24 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<h4 class="text-xl font-semibold">Timeline</h4>
|
<h4 class="text-xl font-semibold">Timeline</h4>
|
||||||
{#if !timeline.items?.length && data.isOwnProfile}
|
{#if !feed.items?.length && data.isOwnProfile}
|
||||||
<a class="btn-primary inline-block" href="/trails/edit/new"
|
<a class="btn-primary inline-block" href="/trails/edit/new"
|
||||||
>+ {$_("new-trail")}</a
|
>+ {$_("new-trail")}</a
|
||||||
>
|
>
|
||||||
{:else if !timeline.items?.length}
|
{:else if !feed.items?.length}
|
||||||
<p class="w-full text-center text-gray-500 text-sm">
|
<p class="w-full text-center text-gray-500 text-sm">
|
||||||
{$_("empty-activities", {
|
{$_("empty-activities", {
|
||||||
values: { username: data.profile.preferredUsername },
|
values: { username: data.profile.preferredUsername },
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
{#each timeline.items as item}
|
{#each feed.items as item}
|
||||||
<div
|
<div
|
||||||
class="py-1 cursor-pointer"
|
class="py-1 cursor-pointer"
|
||||||
role="presentation"
|
role="presentation"
|
||||||
onclick={() => handleTimeLineItemClick(item)}
|
onclick={() => handleFeedItemClick(item)}
|
||||||
>
|
>
|
||||||
<ActivityCard activity={item} actor={data.actor}></ActivityCard>
|
<FeedCard feedItem={item}></FeedCard>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { ListFilter } from "$lib/models/list";
|
import type { ListFilter } from "$lib/models/list";
|
||||||
import { profile_lists_index, profile_timeline_index } from "$lib/stores/profile_store";
|
import { profile_lists_index, profile_feed_index } from "$lib/stores/profile_store";
|
||||||
import { error, type Load } from "@sveltejs/kit";
|
import { error, type Load } from "@sveltejs/kit";
|
||||||
|
|
||||||
export const load: Load = async ({ params, fetch, parent }) => {
|
export const load: Load = async ({ params, fetch, parent }) => {
|
||||||
@@ -19,10 +19,10 @@ export const load: Load = async ({ params, fetch, parent }) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const lists = await profile_lists_index(params.handle, filter, 1, 6, fetch)
|
const lists = await profile_lists_index(params.handle, filter, 1, 6, fetch)
|
||||||
const timeline = await profile_timeline_index(params.handle, 1, 10, fetch);
|
const feed = await profile_feed_index(params.handle, 1, 10, fetch);
|
||||||
return { lists: lists.items, timeline }
|
return { lists: lists.items, feed }
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return {lists: [], timeline: {items: [], page: 1, perPage: 1, totalItems: 0, totalPages: 0}}
|
return {lists: [], feed: {items: [], page: 1, perPage: 1, totalItems: 0, totalPages: 0}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user