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