From 7a97563448fa346adb6b29485902c6279b837926 Mon Sep 17 00:00:00 2001 From: phil Date: Wed, 23 Jul 2025 11:47:01 -0400 Subject: [PATCH] load records for notification context --- .../src/components/Notification.css | 13 +++++++ .../src/components/Notification.tsx | 39 +++++++++++++------ lexicons/bits.js | 32 +++++++++++++-- lexicons/defs.js | 3 ++ 4 files changed, 71 insertions(+), 16 deletions(-) diff --git a/atproto-notifications/src/components/Notification.css b/atproto-notifications/src/components/Notification.css index df75597..e0c7366 100644 --- a/atproto-notifications/src/components/Notification.css +++ b/atproto-notifications/src/components/Notification.css @@ -7,6 +7,7 @@ box-sizing: border-box; display: flex; justify-content: space-between; + gap: 0.5rem; } a.notification { font: inherit; @@ -30,10 +31,22 @@ a.notification:hover { margin: 0; } +.notification-info { + display: flex; + align-items: baseline; +} + .handle { color: skyblue; } +.notification-context { + font-size: 0.8rem; + opacity: 0.667; + margin: 0.25rem 0 0; + max-width: 48em; +} + .notification-when { font-size: 0.8rem; opacity: 0.667; diff --git a/atproto-notifications/src/components/Notification.tsx b/atproto-notifications/src/components/Notification.tsx index 0c15c9c..1bfaa18 100644 --- a/atproto-notifications/src/components/Notification.tsx +++ b/atproto-notifications/src/components/Notification.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react'; import ReactTimeAgo from 'react-time-ago'; import psl from 'psl'; -import { default as lexicons, getLink } from 'lexicons'; +import { default as lexicons, getLink, getContext } from 'lexicons'; import { resolveDid } from '../atproto/resolve'; import { Fetch } from './Fetch'; @@ -19,12 +19,17 @@ export function fallbackRender({ error, resetErrorBoundary }) { export function Notification({ app, group, source, source_record, source_did, subject, timestamp }) { const [resolvedLink, setResolvedLink] = useState(null); + const [resolvedContext, setResolvedContext] = useState([]); useEffect(() => { (async () => { const link = await getLink(source, source_record, subject); if (link) setResolvedLink(link); })(); + (async() => { + const context = await getContext(source, source_record, subject); + setResolvedContext(context); + })(); }, [source, source_record, subject]); // TODO: clean up / move this to lexicons package? @@ -77,23 +82,33 @@ export function Notification({ app, group, source, source_record, source_did, su } link = resolvedLink ?? directLink ?? link; + let contextClipped = resolvedContext.join(' '); + if (contextClipped.length > 240) { + contextClipped = contextClipped.slice(0, 239) + '…'; + } + const contents = ( <>
{icon && ( )} - {title} from - {' '} - {source_did ? ( - @{handle}} - /> - ) : ( - source_record - )} +
+ {title} from + {' '} + {source_did ? ( + @{handle}} + /> + ) : ( + source_record + )} + {contextClipped.length > 0 && ( +

{contextClipped}

+ )} +
{timestamp && (
diff --git a/lexicons/bits.js b/lexicons/bits.js index c4c4b90..1aabed4 100644 --- a/lexicons/bits.js +++ b/lexicons/bits.js @@ -19,9 +19,6 @@ function getAppDefs(source) { return [appSource, defs[appPrefix]]; } -export async function getContext(source, source_record, subject) { -} - const uriBits = async uri => { const bits = uri.slice('at://'.length).split('/'); // TODO: identifier might be a handle @@ -70,7 +67,7 @@ export async function getLink(source, source_record, subject) { const sub = JSONPath({ path: `$.${match.groups.path}`, json: subjectRecord, - })[0]; + })[0]; // TODO: array result? link = link.replaceAll(match[0], sub); } @@ -80,3 +77,30 @@ export async function getLink(source, source_record, subject) { // 2.b TODO: source record lookups if needed return link; } + +export async function getContext(source, source_record, subject) { + const [appSource, appDefs] = getAppDefs(source); + const contexts = appDefs?.known_sources?.[appSource]?.context ?? []; + const linkType = subject.startsWith('did:') ? 'did' : 'at_uri'; + + let loaded = []; + for (const ctx of contexts) { + const [o, ...pathstuff] = ctx.split(':'); + if (o !== '@subject') { + throw new Error('only @subject is implemented for context loading so far'); + } + if (linkType !== 'at_uri') { + throw new Error('only at_uris can be used for @subject loading so far'); + } + const path = pathstuff.join(':'); + const subjectRecord = await getAtUri(subject); + // using json path is temporary -- need recordpath convention defined + const found = JSONPath({ + path, + json: subjectRecord, + }); + loaded = loaded.concat(found); // TODO: think about array handling + } + + return loaded; +} diff --git a/lexicons/defs.js b/lexicons/defs.js index 087df7c..eb00062 100644 --- a/lexicons/defs.js +++ b/lexicons/defs.js @@ -55,6 +55,7 @@ export default { }, 'feed.like:subject.uri': { name: 'Like', + context: ['@subject:text'], }, 'feed.like:via.uri': { name: 'Repost like', @@ -118,12 +119,14 @@ export default { main: 'https://tangled.sh', direct_links: { 'at_uri:feed.star:subject': 'https://tangled.sh/{subject.did}/{@subject:name}', + 'did:graph.follow:subject': 'https://tangled.sh/{source_record.did}', }, } ], known_sources: { 'feed.star:subject': { name: 'Star', + context: ['@subject:name'], }, 'feed.reaction:subject': { name: 'Reaction', -- 2.51.2