tangled
alpha
login
or
join now
apoena.dev
/
remanso-cli
0
fork
atom
this repo has no description
0
fork
atom
overview
issues
pulls
pipelines
chore: updated docs
stevedylan.dev
1 month ago
08e41d32
43c6edbb
+116
-56
1 changed file
expand all
collapse all
unified
split
docs
docs
public
sequoia-comments.js
+116
-56
docs/docs/public/sequoia-comments.js
···
99
99
align-items: center;
100
100
margin-bottom: 1rem;
101
101
padding-bottom: 0.75rem;
102
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
139
-
gap: 0;
138
138
+
}
139
139
+
140
140
+
.sequoia-thread {
141
141
+
border-top: 1px solid var(--sequoia-border-color, #e5e7eb);
142
142
+
padding-bottom: 1rem;
143
143
+
}
144
144
+
145
145
+
.sequoia-thread + .sequoia-thread {
146
146
+
margin-top: 0.5rem;
147
147
+
}
148
148
+
149
149
+
.sequoia-thread:last-child {
150
150
+
border-bottom: 1px solid var(--sequoia-border-color, #e5e7eb);
140
151
}
141
152
142
153
.sequoia-comment {
143
143
-
padding: 1rem;
144
144
-
background: var(--sequoia-bg-color, #ffffff);
145
145
-
border: 1px solid var(--sequoia-border-color, #e5e7eb);
146
146
-
border-radius: var(--sequoia-border-radius, 8px);
147
147
-
margin-bottom: 0.75rem;
154
154
+
display: flex;
155
155
+
gap: 0.75rem;
156
156
+
padding-top: 1rem;
148
157
}
149
158
150
150
-
.sequoia-comment-header {
159
159
+
.sequoia-comment-avatar-column {
151
160
display: flex;
161
161
+
flex-direction: column;
152
162
align-items: center;
153
153
-
gap: 0.75rem;
154
154
-
margin-bottom: 0.5rem;
163
163
+
flex-shrink: 0;
164
164
+
width: 2.5rem;
165
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
175
+
position: relative;
176
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
191
+
position: relative;
192
192
+
z-index: 1;
178
193
}
179
194
180
180
-
.sequoia-comment-meta {
181
181
-
display: flex;
182
182
-
flex-direction: column;
195
195
+
.sequoia-thread-line {
196
196
+
position: absolute;
197
197
+
top: 2.5rem;
198
198
+
bottom: calc(-1rem - 0.5rem);
199
199
+
left: 50%;
200
200
+
transform: translateX(-50%);
201
201
+
width: 2px;
202
202
+
background: var(--sequoia-border-color, #e5e7eb);
203
203
+
}
204
204
+
205
205
+
.sequoia-comment-content {
206
206
+
flex: 1;
183
207
min-width: 0;
184
208
}
185
209
210
210
+
.sequoia-comment-header {
211
211
+
display: flex;
212
212
+
align-items: baseline;
213
213
+
gap: 0.5rem;
214
214
+
margin-bottom: 0.25rem;
215
215
+
flex-wrap: wrap;
216
216
+
}
217
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
208
-
font-size: 0.75rem;
240
240
+
font-size: 0.875rem;
209
241
color: var(--sequoia-secondary-color, #6b7280);
210
210
-
margin-left: auto;
211
242
flex-shrink: 0;
212
243
}
213
244
245
245
+
.sequoia-comment-time::before {
246
246
+
content: "·";
247
247
+
margin-right: 0.5rem;
248
248
+
}
249
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
229
-
.sequoia-comment-replies {
230
230
-
margin-top: 0.75rem;
231
231
-
margin-left: 1.5rem;
232
232
-
padding-left: 1rem;
233
233
-
border-left: 2px solid var(--sequoia-border-color, #e5e7eb);
234
234
-
}
235
235
-
236
236
-
.sequoia-comment-replies .sequoia-comment {
237
237
-
margin-bottom: 0.5rem;
238
238
-
}
239
239
-
240
240
-
.sequoia-comment-replies .sequoia-comment:last-child {
241
241
-
margin-bottom: 0;
242
242
-
}
243
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
321
-
(a, b) => a.index.byteStart - b.index.byteStart
342
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
421
-
(s) => s.id === "#atproto_pds" || s.type === "AtprotoPersonalDataServer"
442
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
435
-
(s) => s.id === "#atproto_pds" || s.type === "AtprotoPersonalDataServer"
456
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
495
-
"https://public.api.bsky.app/xrpc/app.bsky.feed.getPostThread"
516
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
550
-
const BaseElement =
551
551
-
typeof HTMLElement !== "undefined"
552
552
-
? HTMLElement
553
553
-
: class {};
571
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
591
-
'link[rel="site.standard.document"]'
609
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
718
-
const replies = this.state.thread.replies?.filter(isThreadViewPost) ?? [];
719
719
-
const commentsHtml = replies.map((reply) => this.renderComment(reply)).join("");
736
736
+
const replies =
737
737
+
this.state.thread.replies?.filter(isThreadViewPost) ?? [];
738
738
+
const threadsHtml = replies
739
739
+
.map((reply) => this.renderThread(reply))
740
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
733
-
${commentsHtml}
754
754
+
${threadsHtml}
734
755
</div>
735
756
</div>
736
757
`;
···
739
760
}
740
761
}
741
762
742
742
-
renderComment(thread) {
743
743
-
const { post } = thread;
763
763
+
/**
764
764
+
* Flatten a thread into a linear list of comments
765
765
+
* @param {ThreadViewPost} thread - Thread to flatten
766
766
+
* @returns {Array<{post: any, hasMoreReplies: boolean}>} Flattened comments
767
767
+
*/
768
768
+
flattenThread(thread) {
769
769
+
const result = [];
770
770
+
const nestedReplies = thread.replies?.filter(isThreadViewPost) ?? [];
771
771
+
772
772
+
result.push({
773
773
+
post: thread.post,
774
774
+
hasMoreReplies: nestedReplies.length > 0,
775
775
+
});
776
776
+
777
777
+
// Recursively flatten nested replies
778
778
+
for (const reply of nestedReplies) {
779
779
+
result.push(...this.flattenThread(reply));
780
780
+
}
781
781
+
782
782
+
return result;
783
783
+
}
784
784
+
785
785
+
/**
786
786
+
* Render a complete thread (top-level comment + all nested replies)
787
787
+
*/
788
788
+
renderThread(thread) {
789
789
+
const flatComments = this.flattenThread(thread);
790
790
+
const commentsHtml = flatComments
791
791
+
.map((item, index) =>
792
792
+
this.renderComment(item.post, item.hasMoreReplies, index),
793
793
+
)
794
794
+
.join("");
795
795
+
796
796
+
return `<div class="sequoia-thread">${commentsHtml}</div>`;
797
797
+
}
798
798
+
799
799
+
/**
800
800
+
* Render a single comment
801
801
+
* @param {any} post - Post data
802
802
+
* @param {boolean} showThreadLine - Whether to show the connecting thread line
803
803
+
* @param {number} _index - Index in the flattened thread (0 = top-level)
804
804
+
*/
805
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
753
-
754
754
-
// Render nested replies
755
755
-
const nestedReplies = thread.replies?.filter(isThreadViewPost) ?? [];
756
756
-
const repliesHtml =
757
757
-
nestedReplies.length > 0
758
758
-
? `<div class="sequoia-comment-replies">${nestedReplies.map((r) => this.renderComment(r)).join("")}</div>`
759
759
-
: "";
815
815
+
const threadLineHtml = showThreadLine
816
816
+
? '<div class="sequoia-thread-line"></div>'
817
817
+
: "";
760
818
761
819
return `
762
820
<div class="sequoia-comment">
763
763
-
<div class="sequoia-comment-header">
821
821
+
<div class="sequoia-comment-avatar-column">
764
822
${avatarHtml}
765
765
-
<div class="sequoia-comment-meta">
823
823
+
${threadLineHtml}
824
824
+
</div>
825
825
+
<div class="sequoia-comment-content">
826
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
831
+
<span class="sequoia-comment-time">${timeAgo}</span>
770
832
</div>
771
771
-
<span class="sequoia-comment-time">${timeAgo}</span>
833
833
+
<p class="sequoia-comment-text">${textHtml}</p>
772
834
</div>
773
773
-
<p class="sequoia-comment-text">${textHtml}</p>
774
774
-
${repliesHtml}
775
835
</div>
776
836
`;
777
837
}