(READ ONLY) Margin is an open annotation layer for the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
99
fork

Configure Feed

Select the types of activity you want to include in your feed.

prettier, image updates, and bug fixes

scanash00 4f62fb8a ebf63261

+45 -15
web/public/favicon.ico

This is a binary file and will not be displayed.

web/public/logo.png

This is a binary file and will not be displayed.

web/public/og.png

This is a binary file and will not be displayed.

+20 -1
web/src/middleware.ts
··· 1 1 import type { APIContext } from "astro"; 2 + import { readFile } from "node:fs/promises"; 3 + import { join } from "node:path"; 2 4 3 5 const API_PORT = process.env.API_PORT || 8081; 4 6 const API_URL = process.env.API_URL || `http://localhost:${API_PORT}`; ··· 7 9 8 10 const CORS_HEADERS = { 9 11 "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", 10 - "Access-Control-Allow-Headers": "Accept, Authorization, Content-Type, X-CSRF-Token, X-Session-Token", 12 + "Access-Control-Allow-Headers": 13 + "Accept, Authorization, Content-Type, X-CSRF-Token, X-Session-Token", 11 14 "Access-Control-Expose-Headers": "Link", 12 15 "Access-Control-Allow-Credentials": "true", 13 16 "Access-Control-Max-Age": "300", ··· 26 29 { request, url }: APIContext, 27 30 next: () => Promise<Response>, 28 31 ): Promise<Response> { 32 + if (url.pathname === "/favicon.ico") { 33 + try { 34 + const file = await readFile( 35 + join(process.cwd(), "dist", "client", "favicon.ico"), 36 + ); 37 + return new Response(file, { 38 + headers: { 39 + "Content-Type": "image/x-icon", 40 + "Cache-Control": "public, max-age=86400", 41 + }, 42 + }); 43 + } catch { 44 + /* ignore */ 45 + } 46 + } 47 + 29 48 const shouldProxy = PROXY_PATHS.some( 30 49 (p) => url.pathname.startsWith(p) || url.pathname === p.replace(/\/$/, ""), 31 50 );
+1
web/src/types.ts
··· 61 61 replyCount?: number; 62 62 repostCount?: number; 63 63 children?: AnnotationItem[]; 64 + inReplyTo?: string; 64 65 viewer?: { 65 66 like?: string; 66 67 };
+24 -14
web/src/views/core/Notifications.tsx
··· 46 46 return "liked your post"; 47 47 } 48 48 case "reply": { 49 - const parentUri = (subject as any)?.inReplyTo as string | undefined; 50 - const parentIsReply = parentUri ? getContentType(parentUri) === "reply" : false; 51 - return parentIsReply ? "replied to your reply" : "replied to your annotation"; 49 + const parentUri = subject?.inReplyTo; 50 + const parentIsReply = parentUri 51 + ? getContentType(parentUri) === "reply" 52 + : false; 53 + return parentIsReply 54 + ? "replied to your reply" 55 + : "replied to your annotation"; 52 56 } 53 57 case "mention": 54 58 return "mentioned you in an annotation"; ··· 135 139 </p> 136 140 )} 137 141 {body && ( 138 - <p className="text-surface-700 dark:text-surface-300 text-sm line-clamp-2">{body}</p> 142 + <p className="text-surface-700 dark:text-surface-300 text-sm line-clamp-2"> 143 + {body} 144 + </p> 139 145 )} 140 146 </> 141 147 ); ··· 172 178 ); 173 179 } else if (contentType === "reply") { 174 180 const text = item?.text; 175 - const parentUri = (item as any)?.inReplyTo as string | undefined; 176 - const parentIsReply = parentUri ? getContentType(parentUri) === "reply" : false; 181 + const parentUri = item?.inReplyTo; 182 + const parentIsReply = parentUri 183 + ? getContentType(parentUri) === "reply" 184 + : false; 177 185 preview = ( 178 186 <> 179 187 {text && ( 180 - <p className="text-surface-700 dark:text-surface-300 text-sm line-clamp-2">{text}</p> 188 + <p className="text-surface-700 dark:text-surface-300 text-sm line-clamp-2"> 189 + {text} 190 + </p> 181 191 )} 182 192 {parentUri && ( 183 193 <p className="text-surface-400 dark:text-surface-500 text-xs mt-1"> ··· 266 276 <div className="space-y-2"> 267 277 {notifications.map((n) => { 268 278 const contentType = getContentType(n.subjectUri || ""); 269 - const verb = getNotificationVerb(n.type, contentType, n.subject as AnnotationItem); 279 + const verb = getNotificationVerb( 280 + n.type, 281 + contentType, 282 + n.subject as AnnotationItem, 283 + ); 270 284 const timeAgo = formatDistanceToNow(new Date(n.createdAt), { 271 285 addSuffix: false, 272 286 }); ··· 286 300 </div> 287 301 <div className="flex-1 min-w-0"> 288 302 <div className="flex items-start gap-2 flex-wrap"> 289 - <Link 290 - to={`/profile/${n.actor.did}`} 291 - className="shrink-0" 292 - > 303 + <Link to={`/profile/${n.actor.did}`} className="shrink-0"> 293 304 <Avatar src={n.actor.avatar} size="xs" /> 294 305 </Link> 295 306 <div className="flex-1 min-w-0"> ··· 299 310 className="font-semibold text-surface-900 dark:text-white hover:underline" 300 311 > 301 312 {n.actor.displayName || `@${n.actor.handle}`} 302 - </Link> 303 - {" "} 313 + </Link>{" "} 304 314 {n.type !== "follow" && n.subjectUri ? ( 305 315 <Link 306 316 to={`/annotation/${encodeURIComponent(n.subjectUri)}`}