this repo has no description

chore: updated thread style and added test.html

authored by stevedylan.dev and committed by tangled.org 2ea975e7 6f2a9dde

+159 -56
+116 -56
packages/cli/src/components/sequoia-comments.js
··· 99 99 align-items: center; 100 100 margin-bottom: 1rem; 101 101 padding-bottom: 0.75rem; 102 - border-bottom: 1px solid var(--sequoia-border-color, #e5e7eb); 103 102 } 104 103 105 104 .sequoia-comments-title { ··· 136 135 .sequoia-comments-list { 137 136 display: flex; 138 137 flex-direction: column; 139 - gap: 0; 138 + } 139 + 140 + .sequoia-thread { 141 + border-top: 1px solid var(--sequoia-border-color, #e5e7eb); 142 + padding-bottom: 1rem; 143 + } 144 + 145 + .sequoia-thread + .sequoia-thread { 146 + margin-top: 0.5rem; 147 + } 148 + 149 + .sequoia-thread:last-child { 150 + border-bottom: 1px solid var(--sequoia-border-color, #e5e7eb); 140 151 } 141 152 142 153 .sequoia-comment { 143 - padding: 1rem; 144 - background: var(--sequoia-bg-color, #ffffff); 145 - border: 1px solid var(--sequoia-border-color, #e5e7eb); 146 - border-radius: var(--sequoia-border-radius, 8px); 147 - margin-bottom: 0.75rem; 154 + display: flex; 155 + gap: 0.75rem; 156 + padding-top: 1rem; 148 157 } 149 158 150 - .sequoia-comment-header { 159 + .sequoia-comment-avatar-column { 151 160 display: flex; 161 + flex-direction: column; 152 162 align-items: center; 153 - gap: 0.75rem; 154 - margin-bottom: 0.5rem; 163 + flex-shrink: 0; 164 + width: 2.5rem; 165 + position: relative; 155 166 } 156 167 157 168 .sequoia-comment-avatar { ··· 161 172 background: var(--sequoia-border-color, #e5e7eb); 162 173 object-fit: cover; 163 174 flex-shrink: 0; 175 + position: relative; 176 + z-index: 1; 164 177 } 165 178 166 179 .sequoia-comment-avatar-placeholder { ··· 175 188 color: var(--sequoia-secondary-color, #6b7280); 176 189 font-weight: 600; 177 190 font-size: 1rem; 191 + position: relative; 192 + z-index: 1; 178 193 } 179 194 180 - .sequoia-comment-meta { 181 - display: flex; 182 - flex-direction: column; 195 + .sequoia-thread-line { 196 + position: absolute; 197 + top: 2.5rem; 198 + bottom: calc(-1rem - 0.5rem); 199 + left: 50%; 200 + transform: translateX(-50%); 201 + width: 2px; 202 + background: var(--sequoia-border-color, #e5e7eb); 203 + } 204 + 205 + .sequoia-comment-content { 206 + flex: 1; 183 207 min-width: 0; 184 208 } 185 209 210 + .sequoia-comment-header { 211 + display: flex; 212 + align-items: baseline; 213 + gap: 0.5rem; 214 + margin-bottom: 0.25rem; 215 + flex-wrap: wrap; 216 + } 217 + 186 218 .sequoia-comment-author { 187 219 font-weight: 600; 188 220 color: var(--sequoia-fg-color, #1f2937); ··· 205 237 } 206 238 207 239 .sequoia-comment-time { 208 - font-size: 0.75rem; 240 + font-size: 0.875rem; 209 241 color: var(--sequoia-secondary-color, #6b7280); 210 - margin-left: auto; 211 242 flex-shrink: 0; 212 243 } 213 244 245 + .sequoia-comment-time::before { 246 + content: "·"; 247 + margin-right: 0.5rem; 248 + } 249 + 214 250 .sequoia-comment-text { 215 251 margin: 0; 216 252 white-space: pre-wrap; ··· 226 262 text-decoration: underline; 227 263 } 228 264 229 - .sequoia-comment-replies { 230 - margin-top: 0.75rem; 231 - margin-left: 1.5rem; 232 - padding-left: 1rem; 233 - border-left: 2px solid var(--sequoia-border-color, #e5e7eb); 234 - } 235 - 236 - .sequoia-comment-replies .sequoia-comment { 237 - margin-bottom: 0.5rem; 238 - } 239 - 240 - .sequoia-comment-replies .sequoia-comment:last-child { 241 - margin-bottom: 0; 242 - } 243 - 244 265 .sequoia-bsky-logo { 245 266 width: 1rem; 246 267 height: 1rem; ··· 318 339 319 340 // Sort facets by start index 320 341 const sortedFacets = [...facets].sort( 321 - (a, b) => a.index.byteStart - b.index.byteStart 342 + (a, b) => a.index.byteStart - b.index.byteStart, 322 343 ); 323 344 324 345 let result = ""; ··· 418 439 419 440 // Find the PDS service endpoint 420 441 const pdsService = didDoc.service?.find( 421 - (s) => s.id === "#atproto_pds" || s.type === "AtprotoPersonalDataServer" 442 + (s) => s.id === "#atproto_pds" || s.type === "AtprotoPersonalDataServer", 422 443 ); 423 444 pdsUrl = pdsService?.serviceEndpoint; 424 445 } else if (did.startsWith("did:web:")) { ··· 432 453 const didDoc = await didDocResponse.json(); 433 454 434 455 const pdsService = didDoc.service?.find( 435 - (s) => s.id === "#atproto_pds" || s.type === "AtprotoPersonalDataServer" 456 + (s) => s.id === "#atproto_pds" || s.type === "AtprotoPersonalDataServer", 436 457 ); 437 458 pdsUrl = pdsService?.serviceEndpoint; 438 459 } else { ··· 492 513 */ 493 514 async function getPostThread(postUri, depth = 6) { 494 515 const url = new URL( 495 - "https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread" 516 + "https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread", 496 517 ); 497 518 url.searchParams.set("uri", postUri); 498 519 url.searchParams.set("depth", depth.toString()); ··· 547 568 // ============================================================================ 548 569 549 570 // SSR-safe base class - use HTMLElement in browser, empty class in Node.js 550 - const BaseElement = 551 - typeof HTMLElement !== "undefined" 552 - ? HTMLElement 553 - : class {}; 571 + const BaseElement = typeof HTMLElement !== "undefined" ? HTMLElement : class {}; 554 572 555 573 class SequoiaComments extends BaseElement { 556 574 constructor() { ··· 588 606 589 607 // Then scan for link tag in document head 590 608 const linkTag = document.querySelector( 591 - 'link[rel="site.standard.document"]' 609 + 'link[rel="site.standard.document"]', 592 610 ); 593 611 return linkTag?.href ?? null; 594 612 } ··· 715 733 break; 716 734 717 735 case "loaded": { 718 - const replies = this.state.thread.replies?.filter(isThreadViewPost) ?? []; 719 - const commentsHtml = replies.map((reply) => this.renderComment(reply)).join(""); 736 + const replies = 737 + this.state.thread.replies?.filter(isThreadViewPost) ?? []; 738 + const threadsHtml = replies 739 + .map((reply) => this.renderThread(reply)) 740 + .join(""); 720 741 const commentCount = this.countComments(replies); 721 742 722 743 this.shadow.innerHTML = ` ··· 730 751 </a> 731 752 </div> 732 753 <div class="sequoia-comments-list"> 733 - ${commentsHtml} 754 + ${threadsHtml} 734 755 </div> 735 756 </div> 736 757 `; ··· 739 760 } 740 761 } 741 762 742 - renderComment(thread) { 743 - const { post } = thread; 763 + /** 764 + * Flatten a thread into a linear list of comments 765 + * @param {ThreadViewPost} thread - Thread to flatten 766 + * @returns {Array<{post: any, hasMoreReplies: boolean}>} Flattened comments 767 + */ 768 + flattenThread(thread) { 769 + const result = []; 770 + const nestedReplies = thread.replies?.filter(isThreadViewPost) ?? []; 771 + 772 + result.push({ 773 + post: thread.post, 774 + hasMoreReplies: nestedReplies.length > 0, 775 + }); 776 + 777 + // Recursively flatten nested replies 778 + for (const reply of nestedReplies) { 779 + result.push(...this.flattenThread(reply)); 780 + } 781 + 782 + return result; 783 + } 784 + 785 + /** 786 + * Render a complete thread (top-level comment + all nested replies) 787 + */ 788 + renderThread(thread) { 789 + const flatComments = this.flattenThread(thread); 790 + const commentsHtml = flatComments 791 + .map((item, index) => 792 + this.renderComment(item.post, item.hasMoreReplies, index), 793 + ) 794 + .join(""); 795 + 796 + return `<div class="sequoia-thread">${commentsHtml}</div>`; 797 + } 798 + 799 + /** 800 + * Render a single comment 801 + * @param {any} post - Post data 802 + * @param {boolean} showThreadLine - Whether to show the connecting thread line 803 + * @param {number} index - Index in the flattened thread (0 = top-level) 804 + */ 805 + renderComment(post, showThreadLine = false, index = 0) { 744 806 const author = post.author; 745 807 const displayName = author.displayName || author.handle; 746 808 const avatarHtml = author.avatar ··· 750 812 const profileUrl = `https://bsky.app/profile/${author.did}`; 751 813 const textHtml = renderTextWithFacets(post.record.text, post.record.facets); 752 814 const timeAgo = formatRelativeTime(post.record.createdAt); 753 - 754 - // Render nested replies 755 - const nestedReplies = thread.replies?.filter(isThreadViewPost) ?? []; 756 - const repliesHtml = 757 - nestedReplies.length > 0 758 - ? `<div class="sequoia-comment-replies">${nestedReplies.map((r) => this.renderComment(r)).join("")}</div>` 759 - : ""; 815 + const threadLineHtml = showThreadLine 816 + ? '<div class="sequoia-thread-line"></div>' 817 + : ""; 760 818 761 819 return ` 762 820 <div class="sequoia-comment"> 763 - <div class="sequoia-comment-header"> 821 + <div class="sequoia-comment-avatar-column"> 764 822 ${avatarHtml} 765 - <div class="sequoia-comment-meta"> 823 + ${threadLineHtml} 824 + </div> 825 + <div class="sequoia-comment-content"> 826 + <div class="sequoia-comment-header"> 766 827 <a href="${profileUrl}" target="_blank" rel="noopener noreferrer" class="sequoia-comment-author"> 767 828 ${escapeHtml(displayName)} 768 829 </a> 769 830 <span class="sequoia-comment-handle">@${escapeHtml(author.handle)}</span> 831 + <span class="sequoia-comment-time">${timeAgo}</span> 770 832 </div> 771 - <span class="sequoia-comment-time">${timeAgo}</span> 833 + <p class="sequoia-comment-text">${textHtml}</p> 772 834 </div> 773 - <p class="sequoia-comment-text">${textHtml}</p> 774 - ${repliesHtml} 775 835 </div> 776 836 `; 777 837 }
+43
packages/cli/test.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>Sequoia Comments Test</title> 7 + <!-- Link to a published document - replace with your own AT URI --> 8 + <link rel="site.standard.document" href="at://did:plc:kq6bvkw4sxof3vdinuitehn5/site.standard.document/3mdnztyhoem2v"> 9 + <style> 10 + body { 11 + font-family: system-ui, -apple-system, sans-serif; 12 + max-width: 800px; 13 + margin: 2rem auto; 14 + padding: 0 1rem; 15 + line-height: 1.6; 16 + background-color: #1A1A1A; 17 + color: #F5F3EF; 18 + } 19 + h1 { 20 + margin-bottom: 2rem; 21 + } 22 + /* Custom styling example */ 23 + :root { 24 + --sequoia-accent-color: #3A5A40; 25 + --sequoia-border-radius: 12px; 26 + --sequoia-bg-color: #1a1a1a; 27 + --sequoia-fg-color: #F5F3EF; 28 + --sequoia-border-color: #333; 29 + --sequoia-secondary-color: #8B7355; 30 + } 31 + </style> 32 + </head> 33 + <body> 34 + <h1>Blog Post Title</h1> 35 + <p>This is a test page for the sequoia-comments web component.</p> 36 + <p>The component will look for a <code>&lt;link rel="site.standard.document"&gt;</code> tag in the document head to find the AT Protocol document, then fetch and display Bluesky replies as comments.</p> 37 + 38 + <h2>Comments</h2> 39 + <sequoia-comments></sequoia-comments> 40 + 41 + <script type="module" src="./src/components/sequoia-comments.js"></script> 42 + </body> 43 + </html>