Write on the margins of the internet. Powered by the AT Protocol. margin.at
extension web atproto comments

prettier, image updates, and bug fixes

+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)}`}