chore: updated docs
08e41d32
1 file(s) · +116 −56
| 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 | } |
|