BlueskyCommentArticle.astro
1<script>
2 type Attributes = 'display-name' | 'handle' | 'likes' | 'replies' | 'reposts';
3
4 export class BlueskyCommentArticle extends HTMLElement {
5 static get observedAttributes() {
6 return [
7 'display-name',
8 'handle',
9 'likes',
10 'replies',
11 'reposts',
12 ] as Array<Attributes>;
13 }
14
15 constructor() {
16 super();
17
18 const template = document.querySelector<HTMLTemplateElement>(
19 'template#bluesky-comment'
20 )?.content;
21
22 if (!template) throw new Error("Couldn't find bluesky comment template!");
23
24 this.appendChild(template.cloneNode(true));
25 }
26
27 attributeChangedCallback(
28 name: Attributes,
29 _oldValue: string,
30 newValue: string
31 ) {
32 if (name === 'display-name') {
33 this.displayName = newValue;
34 return;
35 }
36
37 if (name === 'handle') {
38 this.handle = newValue;
39 return;
40 }
41
42 if (name === 'likes') {
43 this.likes = newValue;
44 return;
45 }
46
47 if (name === 'replies') {
48 this.replies = newValue;
49 return;
50 }
51
52 if (name === 'reposts') {
53 this.reposts = newValue;
54 return;
55 }
56 }
57
58 get displayName() {
59 return this.getAttribute('display-name') ?? 'Unknown';
60 }
61
62 set displayName(value: string) {
63 this?.querySelector('span.display-name')?.setHTMLUnsafe(value);
64 }
65
66 get handle() {
67 return this.getAttribute('handle') ?? 'unknown-handle';
68 }
69
70 set handle(value: string) {
71 this?.querySelector('span.handle')?.setHTMLUnsafe(`@(${value})`);
72 }
73
74 get likes() {
75 return String(this.getAttribute('likes') ?? 0);
76 }
77
78 set likes(value: string) {
79 this?.querySelector('span.likes')?.setHTMLUnsafe(`${value} Likes`);
80 }
81
82 get replies() {
83 return String(this.getAttribute('replies') ?? 0);
84 }
85
86 set replies(value: string) {
87 this?.querySelector('span.replies')?.setHTMLUnsafe(`${value} Replies`);
88 }
89
90 get reposts() {
91 return String(this.getAttribute('reposts') ?? 0);
92 }
93
94 set reposts(value: string) {
95 this?.querySelector('span.reposts')?.setHTMLUnsafe(`${value} Reposts`);
96 }
97 }
98
99 customElements.define('bluesky-comment', BlueskyCommentArticle, {
100 extends: 'article',
101 });
102</script>
103
104<template id={'bluesky-comment'}>
105 <style>
106 /* :host {
107 display: flex;
108 flex-direction: column;
109 gap: 1rem;
110 background-color: var(--surface0);
111 padding: 1rem;
112 border-radius: 0.5rem;
113 } */
114
115 .author {
116 display: flex;
117 flex-direction: row;
118 font-size: medium;
119 margin: 0;
120 }
121
122 .author > a {
123 flex-grow: 1;
124 text-decoration: none;
125 color: var(--text);
126 }
127
128 .handle {
129 color: var(--subtext0);
130 }
131
132 .time {
133 color: var(--subtext0);
134 font-size: x-small;
135 }
136
137 footer > a {
138 display: flex;
139 flex-direction: row;
140 gap: 2rem;
141 text-decoration: none;
142 color: var(--text);
143 }
144
145 footer > a > p {
146 display: flex;
147 flex-direction: row;
148 align-items: center;
149 gap: 0.5rem;
150 margin-bottom: 0;
151 }
152 </style>
153
154 <header>
155 <h2 class="author">
156 <a
157 rel="noopener noreferrer"
158 target="_blank"
159 aria-label="{comment.author.displayName}'s profile on Bluesky"
160 >
161 <span class="display-name"></span>
162 <span class="handle"></span>
163 </a>
164 <time class="timestamp"></time>
165 </h2>
166 </header>
167 <p>
168 <slot name="default">a</slot>
169 </p>
170 <footer>
171 <a rel="noopener noreferrer" target="_blank">
172 <p>
173 <iconify-icon icon="fa6-solid:heart" height="1rem"></iconify-icon>
174 <span class="likes"></span>
175 </p>
176 <p>
177 <iconify-icon icon="fa6-solid:comment" height="1rem"></iconify-icon>
178 <span class="replies"></span>
179 </p>
180 <p>
181 <iconify-icon icon="fa6-solid:repeat" height="1rem"></iconify-icon>
182 <span class="reposts"></span>
183 </p>
184 </a>
185 </footer>
186</template>