Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

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

auto-link @handles in news titles, body text, and comments

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+17 -4
+17 -4
system/netlify/functions/news.mjs
··· 369 369 .join("\n"); 370 370 return s; 371 371 }); 372 - return rendered.join("\n"); 372 + return autoLinkHandles(rendered.join("\n")); 373 373 } 374 374 375 375 function formatDate(date) { ··· 403 403 return `<a href="${profileUrl}" class="news-modal-link news-handle-link" data-modal-url="${profileUrl}">${safeHandle}</a>`; 404 404 } 405 405 406 + // Auto-link @handles in already-escaped HTML text. 407 + // Matches @word patterns that aren't already inside an href or tag attribute. 408 + function autoLinkHandles(html) { 409 + if (!html) return html; 410 + // Split on existing HTML tags to avoid linking inside attributes. 411 + return html.replace(/(<[^>]*>)|(@([a-zA-Z0-9_-]+))/g, (match, tag, mention, username) => { 412 + if (tag) return tag; // Pass through HTML tags unchanged. 413 + if (username === "anon") return match; 414 + const profileUrl = `https://aesthetic.computer/${username}`; 415 + return `<a href="${profileUrl}" class="news-modal-link news-handle-link" data-modal-url="${profileUrl}">@${username}</a>`; 416 + }); 417 + } 418 + 406 419 function parseRoute(event) { 407 420 // Extract route from path, stripping the function prefix 408 421 let path = event.path || ""; ··· 500 513 } 501 514 502 515 function renderPostRow(post, idx, basePath) { 503 - const title = escapeHtml(post.title || "(untitled)"); 516 + const title = autoLinkHandles(escapeHtml(post.title || "(untitled)")); 504 517 const url = post.url ? escapeHtml(post.url) : ""; 505 518 // Show URL with reasonable truncation for homepage list 506 519 const displayUrl = post.url ? (() => { ··· 543 556 <button type="submit" class="news-delete-btn" title="Delete comment">delete</button> 544 557 </form> 545 558 </div> 546 - <div class="news-comment-body">${escapeHtml(comment.text || "")}</div> 559 + <div class="news-comment-body">${autoLinkHandles(escapeHtml(comment.text || ""))}</div> 547 560 </div>`; 548 561 } 549 562 ··· 624 637 const hydratedComments = await hydrateHandles(database, commentDocs); 625 638 const commentsHtml = hydratedComments.map((c) => renderComment(c)).join("\n"); 626 639 627 - const postTitle = escapeHtml(hydratedPost.title || "(untitled)"); 640 + const postTitle = autoLinkHandles(escapeHtml(hydratedPost.title || "(untitled)")); 628 641 const pageTitle = `${hydratedPost.title || "(untitled)"} | Aesthetic News`; 629 642 const url = hydratedPost.url ? escapeHtml(hydratedPost.url) : ""; 630 643 // Show full URL