diff --git a/assets/reader.css b/assets/reader.css index 5b0372a..0a67157 100644 --- a/assets/reader.css +++ b/assets/reader.css @@ -793,9 +793,34 @@ box-shadow: 0 0 8px 0 hsl(var(--tint-yellow) 50% / 0.3); } -.ap-notification__icon { +.ap-notification__avatar-wrap { flex-shrink: 0; - font-size: 1.5em; + position: relative; +} + +.ap-notification__avatar { + border: var(--border-width-thin) solid var(--color-outline); + border-radius: 50%; + height: 40px; + object-fit: cover; + width: 40px; +} + +.ap-notification__avatar--default { + align-items: center; + background: var(--color-offset-variant); + color: var(--color-on-offset); + display: inline-flex; + font-size: 1.1em; + font-weight: 600; + justify-content: center; +} + +.ap-notification__type-badge { + bottom: -2px; + font-size: 0.75em; + position: absolute; + right: -4px; } .ap-notification__body { diff --git a/lib/storage/notifications.js b/lib/storage/notifications.js index 0b2a11d..2b471e2 100644 --- a/lib/storage/notifications.js +++ b/lib/storage/notifications.js @@ -66,9 +66,10 @@ export async function getNotifications(collections, options = {}) { query.read = false; } - // Cursor pagination + // Cursor pagination — published is stored as ISO string, so compare + // as strings (lexicographic ISO 8601 comparison is correct for dates) if (options.before) { - query.published = { $lt: new Date(options.before) }; + query.published = { $lt: options.before }; } const rawItems = await ap_notifications @@ -85,9 +86,9 @@ export async function getNotifications(collections, options = {}) { : item.published, })); - // Generate cursor for next page + // Generate cursor for next page (only if full page returned = more may exist) const before = - items.length > 0 + items.length === limit ? items[items.length - 1].published : null; diff --git a/lib/storage/timeline.js b/lib/storage/timeline.js index 8d4e7a9..f495e92 100644 --- a/lib/storage/timeline.js +++ b/lib/storage/timeline.js @@ -94,23 +94,20 @@ export async function getTimelineItems(collections, options = {}) { query["author.url"] = options.authorUrl; } - // Cursor pagination — validate dates + // Cursor pagination — published is stored as ISO string, so compare + // as strings (lexicographic ISO 8601 comparison is correct for dates) if (options.before) { - const beforeDate = new Date(options.before); - - if (Number.isNaN(beforeDate.getTime())) { + if (Number.isNaN(new Date(options.before).getTime())) { throw new Error("Invalid before cursor"); } - query.published = { $lt: beforeDate }; + query.published = { $lt: options.before }; } else if (options.after) { - const afterDate = new Date(options.after); - - if (Number.isNaN(afterDate.getTime())) { + if (Number.isNaN(new Date(options.after).getTime())) { throw new Error("Invalid after cursor"); } - query.published = { $gt: afterDate }; + query.published = { $gt: options.after }; } const rawItems = await ap_timeline @@ -128,13 +125,16 @@ export async function getTimelineItems(collections, options = {}) { })); // Generate cursors for pagination + // Items are sorted newest-first, so: + // - "before" cursor (for "Older" link) = oldest item's date (last in array) + // - "after" cursor (for "Newer" link) = newest item's date (first in array) const before = - items.length > 0 - ? items[0].published + items.length === limit + ? items[items.length - 1].published : null; const after = - items.length > 0 - ? items[items.length - 1].published + items.length > 0 && (options.before || options.after) + ? items[0].published : null; return { @@ -190,7 +190,9 @@ export async function updateTimelineItem(collections, uid, updates) { */ export async function deleteOldTimelineItems(collections, cutoffDate) { const { ap_timeline } = collections; - const result = await ap_timeline.deleteMany({ published: { $lt: cutoffDate } }); + // published is stored as ISO string — convert cutoff to string for comparison + const cutoff = cutoffDate instanceof Date ? cutoffDate.toISOString() : cutoffDate; + const result = await ap_timeline.deleteMany({ published: { $lt: cutoff } }); return result.deletedCount; } diff --git a/views/activitypub-activities.njk b/views/activitypub-activities.njk index f916c7d..70e67d7 100644 --- a/views/activitypub-activities.njk +++ b/views/activitypub-activities.njk @@ -7,7 +7,7 @@ {% from "pagination/macro.njk" import pagination with context %} {% block content %} - {{ heading({ text: __("activitypub.activities"), level: 1, parent: { text: __("activitypub.title"), href: mountPath } }) }} + {{ heading({ text: __("activitypub.activities"), level: 1 }) }} {% if activities.length > 0 %} {% for activity in activities %} diff --git a/views/activitypub-compose.njk b/views/activitypub-compose.njk index 786db1d..529c2a9 100644 --- a/views/activitypub-compose.njk +++ b/views/activitypub-compose.njk @@ -3,11 +3,7 @@ {% from "heading/macro.njk" import heading with context %} {% block readercontent %} - {{ heading({ - text: title, - level: 1, - parent: { text: __("activitypub.reader.title"), href: mountPath + "/admin/reader" } - }) }} + {{ heading({ text: title, level: 1 }) }} {# Reply context — show the post being replied to #} {% if replyContext %} diff --git a/views/activitypub-featured-tags.njk b/views/activitypub-featured-tags.njk index 2867c98..a1b8fb6 100644 --- a/views/activitypub-featured-tags.njk +++ b/views/activitypub-featured-tags.njk @@ -4,7 +4,7 @@ {% from "prose/macro.njk" import prose with context %} {% block content %} - {{ heading({ text: title, level: 1, parent: { text: __("activitypub.title"), href: mountPath } }) }} + {{ heading({ text: title, level: 1 }) }} {% if tags.length > 0 %}