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',