atproto-embed 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,542 @@
1
+ (function () {
2
+ "use strict";
3
+
4
+ var API_BASE = "https://public.api.bsky.app/xrpc/";
5
+ var PROFILE_CACHE = new Map();
6
+ var INFLIGHT = new Map();
7
+ var CONTAINER_ABORTS = new WeakMap();
8
+ var SIGNAL_IDS = new WeakMap();
9
+ var NEXT_SIGNAL_ID = 1;
10
+
11
+ function getSignalId(signal) {
12
+ if (!signal) return 0;
13
+ var id = SIGNAL_IDS.get(signal);
14
+ if (!id) {
15
+ id = NEXT_SIGNAL_ID++;
16
+ SIGNAL_IDS.set(signal, id);
17
+ }
18
+ return id;
19
+ }
20
+
21
+ function fetchJsonOrThrow(url, errMsg, signal) {
22
+ return fetch(url, signal ? { signal: signal } : undefined).then(function (res) {
23
+ if (!res.ok) throw new Error(errMsg);
24
+ return res.json();
25
+ });
26
+ }
27
+
28
+ function fetchJsonDedup(key, url, errMsg, signal) {
29
+ var sigId = getSignalId(signal);
30
+ var inflightKey = key + "|s" + sigId;
31
+ if (INFLIGHT.has(inflightKey)) return INFLIGHT.get(inflightKey);
32
+ var p = fetchJsonOrThrow(url, errMsg, signal).finally(function () {
33
+ INFLIGHT.delete(inflightKey);
34
+ });
35
+ INFLIGHT.set(inflightKey, p);
36
+ return p;
37
+ }
38
+
39
+ function fetchProfile(actor, signal) {
40
+ if (!actor) return Promise.resolve(null);
41
+ if (PROFILE_CACHE.has(actor)) {
42
+ return Promise.resolve(PROFILE_CACHE.get(actor));
43
+ }
44
+ var url =
45
+ API_BASE +
46
+ "app.bsky.actor.getProfile?actor=" +
47
+ encodeURIComponent(actor);
48
+ return fetchJsonDedup("profile:" + actor, url, "Failed to fetch profile", signal)
49
+ .then(function (data) {
50
+ PROFILE_CACHE.set(actor, data);
51
+ return data;
52
+ })
53
+ .catch(function (err) {
54
+ if (err && err.name === "AbortError") throw err;
55
+ return null;
56
+ });
57
+ }
58
+
59
+ function el(tag, className, attrs) {
60
+ var e = document.createElement(tag);
61
+ if (className) e.className = className;
62
+ if (attrs) {
63
+ for (var k in attrs) {
64
+ if (k === "textContent") e.textContent = attrs[k];
65
+ else if (k === "innerHTML") e.innerHTML = attrs[k];
66
+ else e.setAttribute(k, attrs[k]);
67
+ }
68
+ }
69
+ return e;
70
+ }
71
+
72
+ function formatCount(n) {
73
+ if (n >= 1000000)
74
+ return (n / 1000000).toFixed(1).replace(/\.0$/, "") + "M";
75
+ if (n >= 1000) return (n / 1000).toFixed(1).replace(/\.0$/, "") + "K";
76
+ return String(n);
77
+ }
78
+
79
+ function escapeHtml(text) {
80
+ return String(text)
81
+ .replace(/&/g, "&")
82
+ .replace(/</g, "&lt;")
83
+ .replace(/>/g, "&gt;")
84
+ .replace(/"/g, "&quot;")
85
+ .replace(/'/g, "&#39;");
86
+ }
87
+
88
+ function linkifyInline(text, config) {
89
+ if (!text) return "";
90
+ var input = String(text);
91
+ var out = "";
92
+ var lastIndex = 0;
93
+ var re = /https?:\/\/[^\s<]+|[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}|(?:^|[^@\w.])([A-Z0-9-]+(?:\.[A-Z0-9-]+)+)(?=\/|[^\w.-]|$)|@[A-Z0-9._-]+|#[A-Z0-9_]+/gi;
94
+ var m;
95
+ while ((m = re.exec(input)) !== null) {
96
+ var start = m.index;
97
+ var match = m[1] || m[0];
98
+ var prefix = m[1] ? m[0].slice(0, m[0].length - m[1].length) : "";
99
+ if (start > lastIndex) {
100
+ out += escapeHtml(input.slice(lastIndex, start));
101
+ }
102
+ if (prefix) {
103
+ out += escapeHtml(prefix);
104
+ }
105
+ if (match[0] === "@") {
106
+ var handle = match.slice(1);
107
+ var url = profileUrl(config, handle);
108
+ out +=
109
+ '<a class="atproto-profile__link" href="' +
110
+ escapeHtml(url) +
111
+ '" target="_blank" rel="noopener noreferrer">@' +
112
+ escapeHtml(handle) +
113
+ "</a>";
114
+ } else if (match[0] === "#") {
115
+ var tag = match.slice(1);
116
+ var tagUrl = clientBase(config) + "/hashtag/" + encodeURIComponent(tag);
117
+ out +=
118
+ '<a class="atproto-profile__link" href="' +
119
+ escapeHtml(tagUrl) +
120
+ '" target="_blank" rel="noopener noreferrer">#' +
121
+ escapeHtml(tag) +
122
+ "</a>";
123
+ } else if (match.indexOf("://") !== -1) {
124
+ out +=
125
+ '<a class="atproto-profile__link" href="' +
126
+ escapeHtml(match) +
127
+ '" target="_blank" rel="noopener noreferrer">' +
128
+ escapeHtml(match) +
129
+ "</a>";
130
+ } else if (match.indexOf("@") !== -1) {
131
+ out +=
132
+ '<a class="atproto-profile__link" href="mailto:' +
133
+ escapeHtml(match) +
134
+ '">' +
135
+ escapeHtml(match) +
136
+ "</a>";
137
+ } else if (match.indexOf(".") !== -1) {
138
+ var urlGuess = "https://" + match;
139
+ out +=
140
+ '<a class="atproto-profile__link" href="' +
141
+ escapeHtml(urlGuess) +
142
+ '" target="_blank" rel="noopener noreferrer">' +
143
+ escapeHtml(match) +
144
+ "</a>";
145
+ } else {
146
+ out += escapeHtml(match);
147
+ }
148
+ lastIndex = start + (m[1] ? m[0].length : match.length);
149
+ }
150
+ if (lastIndex < input.length) {
151
+ out += escapeHtml(input.slice(lastIndex));
152
+ }
153
+ return out;
154
+ }
155
+
156
+ function linkifyDescription(text, config) {
157
+ if (!text) return "";
158
+ var paragraphs = String(text).split(/\n\s*\n/);
159
+ var parts = paragraphs.map(function (para) {
160
+ var html = linkifyInline(para, config).replace(/\n/g, "<br>");
161
+ return '<div class="atproto-profile__paragraph">' + html + "</div>";
162
+ });
163
+ return parts.join("");
164
+ }
165
+
166
+ var ICONS = {
167
+ "avatar-fallback": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2248%22%20height%3D%2248%22%20fill%3D%22none%22%20viewBox%3D%220%200%2048%2048%22%3E%3Crect%20width%3D%2248%22%20height%3D%2248%22%20fill%3D%22%23e0e0e0%22%20rx%3D%2224%22%2F%3E%3Cpath%20fill%3D%22%23000%22%20d%3D%22M32%2020a8%208%200%201%201-16%200%208%208%200%200%201%2016%200%22%20opacity%3D%22.2%22%2F%3E%3Cpath%20fill%3D%22%23000%22%20d%3D%22M36.865%2034.5c-1.904-3.291-4.838-5.651-8.261-6.77a9%209%200%201%200-9.208%200c-3.424%201.117-6.357%203.477-8.261%206.77a.997.997%200%200%200%20.352%201.389%201%201%200%200%200%201.38-.389C15.22%2031.43%2019.383%2029%2024%2029s8.779%202.43%2011.134%206.5a1.001%201.001%200%201%200%201.73-1M17%2020a7%207%200%201%201%207%207%207.007%207.007%200%200%201-7-7%22%2F%3E%3C%2Fsvg%3E",
168
+ "check-circle": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22none%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22url(%23a)%22%20d%3D%22M16%203a13%2013%200%201%200%2013%2013A13.013%2013.013%200%200%200%2016%203m5.708%2010.708-7%207a1%201%200%200%201-1.415%200l-3-3a1%201%200%200%201%201.415-1.415L14%2018.586l6.293-6.293a1%201%200%200%201%201.415%201.415%22%2F%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%223%22%20x2%3D%2229%22%20y1%3D%223%22%20y2%3D%2229%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20stop-color%3D%22%23006aff%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23004099%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3C%2Fsvg%3E",
169
+ "original-seal": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22none%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22url(%23a)%22%20d%3D%22M28.233%2012.853c-.472-.493-.96-1-1.143-1.447-.17-.409-.18-1.086-.19-1.742-.019-1.22-.039-2.603-1-3.564s-2.344-.981-3.564-1c-.656-.01-1.333-.02-1.742-.19-.445-.184-.954-.671-1.447-1.142C18.286%202.938%2017.306%202%2016%202s-2.284.939-3.148%201.768c-.492.47-1%20.958-1.446%201.142-.406.17-1.086.18-1.742.19-1.22.019-2.603.039-3.564%201s-.975%202.344-1%203.564c-.01.656-.02%201.334-.19%201.742-.184.445-.671.954-1.142%201.446C2.938%2013.715%202%2014.696%202%2016s.939%202.284%201.768%203.148c.47.492.958%201%201.142%201.446.17.409.18%201.086.19%201.742.019%201.22.039%202.603%201%203.564s2.344.981%203.564%201c.656.01%201.334.02%201.742.19.445.184.954.671%201.446%201.143C13.715%2029.06%2014.696%2030%2016%2030s2.284-.939%203.148-1.767c.492-.472%201-.96%201.446-1.143.409-.17%201.086-.18%201.742-.19%201.22-.019%202.603-.039%203.564-1s.981-2.344%201-3.564c.01-.656.02-1.333.19-1.742.184-.445.671-.954%201.143-1.447C29.06%2018.286%2030%2017.306%2030%2016s-.939-2.284-1.767-3.148m-6.526.854-7%207a1%201%200%200%201-1.415%200l-3-3a1%201%200%200%201%201.415-1.415L14%2018.587l6.293-6.293a1%201%200%200%201%201.415%201.415%22%2F%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%222%22%20x2%3D%2230%22%20y1%3D%222%22%20y2%3D%2230%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20stop-color%3D%22%23ff6200%22%2F%3E%3Cstop%20offset%3D%22.615%22%20stop-color%3D%22%23f80%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23ff5900%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3C%2Fsvg%3E",
170
+ "trusted-seal": "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20fill%3D%22none%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20fill%3D%22url(%23a)%22%20d%3D%22M28.233%2012.853c-.472-.493-.96-1-1.143-1.447-.17-.409-.18-1.086-.19-1.742-.019-1.22-.039-2.603-1-3.564s-2.344-.981-3.564-1c-.656-.01-1.333-.02-1.742-.19-.445-.184-.954-.671-1.447-1.142C18.286%202.938%2017.306%202%2016%202s-2.284.939-3.148%201.768c-.492.47-1%20.958-1.446%201.142-.406.17-1.086.18-1.742.19-1.22.019-2.603.039-3.564%201s-.975%202.344-1%203.564c-.01.656-.02%201.334-.19%201.742-.184.445-.671.954-1.142%201.446C2.938%2013.715%202%2014.696%202%2016s.939%202.284%201.768%203.148c.47.492.958%201%201.142%201.446.17.409.18%201.086.19%201.742.019%201.22.039%202.603%201%203.564s2.344.981%203.564%201c.656.01%201.334.02%201.742.19.445.184.954.671%201.446%201.143C13.715%2029.06%2014.696%2030%2016%2030s2.284-.939%203.148-1.767c.492-.472%201-.96%201.446-1.143.409-.17%201.086-.18%201.742-.19%201.22-.019%202.603-.039%203.564-1s.981-2.344%201-3.564c.01-.656.02-1.333.19-1.742.184-.445.671-.954%201.143-1.447C29.06%2018.286%2030%2017.306%2030%2016s-.939-2.284-1.767-3.148m-6.526.854-7%207a1%201%200%200%201-1.415%200l-3-3a1%201%200%200%201%201.415-1.415L14%2018.587l6.293-6.293a1%201%200%200%201%201.415%201.415%22%2F%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22a%22%20x1%3D%222%22%20x2%3D%2230%22%20y1%3D%222%22%20y2%3D%2230%22%20gradientUnits%3D%22userSpaceOnUse%22%3E%3Cstop%20stop-color%3D%22%2329de6b%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%230f9f44%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3C%2Fsvg%3E"
171
+ };
172
+
173
+ function getIconUrl(name) {
174
+ return ICONS[name] || "";
175
+ }
176
+
177
+ var BADGE_ICONS = {
178
+ crown: "original-seal",
179
+ seal: "trusted-seal",
180
+ check: "check-circle",
181
+ };
182
+
183
+ function detectBadge(author) {
184
+ var v = (author && author.verification) || {};
185
+ if (v.trustedVerifierStatus === "valid") {
186
+ return author && author.handle === "bsky.app" ? "crown" : "seal";
187
+ }
188
+ if (v.verifiedStatus === "valid") return "check";
189
+ return null;
190
+ }
191
+
192
+ function renderBadge(author) {
193
+ var badge = detectBadge(author);
194
+ if (!badge || !BADGE_ICONS[badge]) return null;
195
+ var img = el("img", "atproto-profile__badge", {
196
+ src: getIconUrl(BADGE_ICONS[badge]),
197
+ alt: badge,
198
+ width: "16",
199
+ height: "16",
200
+ });
201
+ return img;
202
+ }
203
+
204
+ /* ───── Config ───── */
205
+
206
+ function parseActor(container) {
207
+ var direct = container.getAttribute("data-profile");
208
+ var handle = container.getAttribute("data-handle");
209
+ var did = container.getAttribute("data-did");
210
+ var raw = did || handle || direct;
211
+ if (!raw) return null;
212
+ raw = raw.trim();
213
+ if (raw.indexOf("://") !== -1) {
214
+ var m = raw.match(/profile\/([^/?#]+)/);
215
+ if (m && m[1]) return m[1];
216
+ }
217
+ return raw;
218
+ }
219
+
220
+ function parseConfig(container) {
221
+ function parseAttr(c, name, defaultVal) {
222
+ var val = c.getAttribute("data-" + name);
223
+ if (val === null) return defaultVal;
224
+ return val === "true" || val === "1" || val === "";
225
+ }
226
+
227
+ function parseStringAttr(c, name, defaultVal) {
228
+ var val = c.getAttribute("data-" + name);
229
+ return val !== null ? val : defaultVal;
230
+ }
231
+
232
+ var config = {
233
+ showAvatar: parseAttr(container, "show-avatar", true),
234
+ showDisplayName: parseAttr(container, "show-display-name", true),
235
+ showHandle: parseAttr(container, "show-handle", true),
236
+ showVerification: parseAttr(container, "show-verification", true),
237
+ showDescription: parseAttr(container, "show-description", true),
238
+ showCover: parseAttr(container, "show-cover", true),
239
+ showMetrics: parseAttr(container, "show-metrics", true),
240
+ showFollowers: parseAttr(container, "show-followers", true),
241
+ showFollowing: parseAttr(container, "show-following", true),
242
+ showPosts: parseAttr(container, "show-posts", true),
243
+ showFollow: parseAttr(container, "show-follow", false),
244
+ showConnect: parseAttr(container, "show-connect", false),
245
+ labelFollow: parseStringAttr(container, "label-follow", null),
246
+ labelConnect: parseStringAttr(container, "label-connect", null),
247
+ size: parseStringAttr(container, "size", "full"),
248
+ clientBase: parseStringAttr(container, "client-base", null),
249
+ clientDomain: parseStringAttr(container, "client-domain", null),
250
+ clientName: parseStringAttr(container, "client-name", "Bluesky"),
251
+ width: parseStringAttr(container, "width", null),
252
+ maxWidth: parseStringAttr(container, "max-width", null),
253
+ };
254
+
255
+ return config;
256
+ }
257
+
258
+ function clientBase(config) {
259
+ if (config.clientBase) {
260
+ if (config.clientBase.indexOf("http") === 0) return config.clientBase;
261
+ return "https://" + config.clientBase;
262
+ }
263
+ if (config.clientDomain) return "https://" + config.clientDomain;
264
+ return "https://bsky.app";
265
+ }
266
+
267
+ function profileUrl(config, handle) {
268
+ return clientBase(config) + "/profile/" + handle;
269
+ }
270
+
271
+ function renderProfileCard(profile, config) {
272
+ if (!profile) return null;
273
+
274
+ var avatar = profile.avatar;
275
+ var displayName = profile.displayName || "";
276
+ var handle = profile.handle || "";
277
+ var description = profile.description || "";
278
+ var cover = profile.banner || "";
279
+ var followersCount = profile.followersCount || 0;
280
+ var followingCount = profile.followsCount || 0;
281
+ var postsCount = profile.postsCount || 0;
282
+
283
+ var card = el("div", "atproto-profile-card");
284
+ if (config.size) card.classList.add("atproto-profile-card--size-" + config.size);
285
+
286
+ if (config.showCover !== false && cover) {
287
+ var coverWrap = el("div", "atproto-profile__cover");
288
+ coverWrap.appendChild(
289
+ el("img", null, {
290
+ src: cover,
291
+ alt: displayName || handle || "Profile cover",
292
+ loading: "lazy",
293
+ })
294
+ );
295
+ card.appendChild(coverWrap);
296
+ } else {
297
+ card.classList.add("atproto-profile-card--no-cover");
298
+ }
299
+
300
+ var body = el("div", "atproto-profile__body");
301
+ var header = el("div", "atproto-profile__header");
302
+
303
+ if (config.showAvatar !== false) {
304
+ var avatarWrap = el("div", "atproto-profile__avatar-wrap");
305
+ avatarWrap.appendChild(
306
+ el("img", "atproto-profile__avatar", {
307
+ src: avatar || getIconUrl("avatar-fallback"),
308
+ alt: displayName || handle || "Avatar",
309
+ loading: "lazy",
310
+ })
311
+ );
312
+ header.appendChild(avatarWrap);
313
+ }
314
+
315
+ var identity = el("div", "atproto-profile__identity");
316
+ if (config.showDisplayName !== false) {
317
+ var nameRow = el("div", "atproto-profile__name-row");
318
+ var nameText = el("div", "atproto-profile__name", {
319
+ textContent: displayName || handle || "Profile",
320
+ });
321
+ nameRow.appendChild(nameText);
322
+ if (config.showVerification !== false) {
323
+ var badge = renderBadge(profile);
324
+ if (badge) nameRow.appendChild(badge);
325
+ }
326
+ identity.appendChild(nameRow);
327
+ }
328
+
329
+ if (config.showHandle !== false && handle) {
330
+ var handleText = handle.charAt(0) === "@" ? handle : "@" + handle;
331
+ identity.appendChild(
332
+ el("div", "atproto-profile__handle", { textContent: handleText })
333
+ );
334
+ }
335
+
336
+ header.appendChild(identity);
337
+ body.appendChild(header);
338
+
339
+ if (config.showDescription !== false && description) {
340
+ var desc = el("div", "atproto-profile__description");
341
+ desc.innerHTML = linkifyDescription(description, config);
342
+ body.appendChild(desc);
343
+ }
344
+
345
+ if (config.showMetrics !== false) {
346
+ var metrics = el("div", "atproto-profile__metrics");
347
+
348
+ if (config.showFollowers !== false) {
349
+ var m1 = el("div", "atproto-profile__metric");
350
+ m1.appendChild(
351
+ el("div", "atproto-profile__metric-count", {
352
+ textContent: formatCount(followersCount),
353
+ })
354
+ );
355
+ m1.appendChild(
356
+ el("div", "atproto-profile__metric-label", {
357
+ textContent: "Followers",
358
+ })
359
+ );
360
+ metrics.appendChild(m1);
361
+ }
362
+
363
+ if (config.showFollowing !== false) {
364
+ var m2 = el("div", "atproto-profile__metric");
365
+ m2.appendChild(
366
+ el("div", "atproto-profile__metric-count", {
367
+ textContent: formatCount(followingCount),
368
+ })
369
+ );
370
+ m2.appendChild(
371
+ el("div", "atproto-profile__metric-label", {
372
+ textContent: "Following",
373
+ })
374
+ );
375
+ metrics.appendChild(m2);
376
+ }
377
+
378
+ if (config.showPosts !== false) {
379
+ var m3 = el("div", "atproto-profile__metric");
380
+ m3.appendChild(
381
+ el("div", "atproto-profile__metric-count", {
382
+ textContent: formatCount(postsCount),
383
+ })
384
+ );
385
+ m3.appendChild(
386
+ el("div", "atproto-profile__metric-label", {
387
+ textContent: "Posts",
388
+ })
389
+ );
390
+ metrics.appendChild(m3);
391
+ }
392
+
393
+ if (metrics.children.length) {
394
+ body.appendChild(metrics);
395
+ }
396
+ }
397
+
398
+ // Actions
399
+ if (config.showFollow || config.showConnect) {
400
+ var actions = el("div", "atproto-profile__actions");
401
+ if (config.showFollow) {
402
+ var label = config.labelFollow || ("Follow on " + config.clientName);
403
+ actions.appendChild(
404
+ el("a", "atproto-profile__btn atproto-profile__btn--primary", {
405
+ href: profileUrl(config, handle),
406
+ target: "_blank",
407
+ rel: "noopener noreferrer",
408
+ textContent: label,
409
+ })
410
+ );
411
+ }
412
+ if (config.showConnect) {
413
+ var label = config.labelConnect || ("Connect on " + config.clientName);
414
+ actions.appendChild(
415
+ el("a", "atproto-profile__btn atproto-profile__btn--secondary", {
416
+ href: profileUrl(config, handle),
417
+ target: "_blank",
418
+ rel: "noopener noreferrer",
419
+ textContent: label,
420
+ })
421
+ );
422
+ }
423
+ body.appendChild(actions);
424
+ }
425
+
426
+ card.appendChild(body);
427
+ return card;
428
+ }
429
+
430
+ /* ───── CSS injection ───── */
431
+
432
+ function injectStyles(root) {
433
+ var style = document.createElement("style");
434
+ style.textContent = "@import url('https://fonts.googleapis.com/css2?family=Google+Sans+Flex:opsz,wght@6..144,1..1000&display=swap');:host{--neutral-0:#ffffff;--neutral-1:#f8f9fa;--neutral-2:#f1f3f5;--neutral-3:#e9ecef;--neutral-4:#dee2e6;--neutral-5:#ced4da;--neutral-6:#adb5bd;--neutral-7:#6a7178;--neutral-8:#4f575e;--neutral-9:#272b30;--neutral-10:#101213;--neutral-11:#000000;--primary-light:#f8f9ff;--primary-base:#0a66f4;--primary-hover:#20439b;--primary-dark:#1c2855;--font-displayLarge:45px;--font-displaymedium:40px;--font-displaySmall:36px;--font-heading1:32px;--font-heading2:28px;--font-heading3:25px;--font-heading4:22px;--font-heading5:20px;--font-heading6:18px;--font-subtitle:16px;--font-body:14px;--font-caption:12px;--font-label:11px;--font-tagline:10px;--font-sans:\"Google Sans Flex\",-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif;--transition:all 0.32s ease-in-out;--atproto-bg:var(--neutral-0,#ffffff);--atproto-border-color:var(--neutral-3,#e9ecef);--atproto-text-color:var(--neutral-10,#101213);--atproto-muted-color:var(--neutral-7,#6a7178);--atproto-accent-color:var(--primary-base,#0a66f4);--atproto-radius:12px;--atproto-font-family:var(--font-sans,-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif)}:host(.atproto-profile-host){display:block;width:var(--atproto-width,100%);max-width:var(--atproto-max-width,none);margin:32px auto;box-sizing:border-box;font-family:var(--atproto-font-family);color:var(--atproto-text-color)}.atproto-profile-inner{margin:0 auto;box-sizing:border-box;width:100%}.atproto-profile-card--size-full{width:100%}.atproto-profile-card--size-md{width:100%;max-width:480px}.atproto-profile-card--size-fit{width:fit-content;min-width:0;max-width:100%}.atproto-profile--loading,.atproto-profile--error{padding:18px;border:1px solid var(--atproto-border-color);border-radius:var(--atproto-radius);background:var(--atproto-bg);color:var(--atproto-muted-color);text-align:center;font-size:var(--font-body,14px)}.atproto-profile-card{margin:0 auto;overflow:hidden;background:var(--atproto-bg);border:1px solid var(--neutral-3);border-radius:20px;box-shadow:0 0 0 3px var(--neutral-1)}.atproto-profile__cover{height:140px;background:#f1f3f5;position:relative;border-radius:inherit;border-bottom-left-radius:0;border-bottom-right-radius:0;overflow:hidden}.atproto-profile__cover img{width:100%;height:100%;object-fit:cover;display:block}.atproto-profile__cover--empty{background:linear-gradient(135deg,#f8f9fa,#e9ecef)}.atproto-profile__body{padding:16px 18px 18px;position:relative}.atproto-profile__header{display:flex;gap:10px;align-items:center;min-width:0}.atproto-profile-card--no-cover .atproto-profile__header{margin-top:0}.atproto-profile__avatar-wrap{width:40px;height:40px;border-radius:50%;background:var(--atproto-profile-bg);border:1px solid var(--atproto-profile-bg);flex-shrink:0;overflow:hidden}.atproto-profile-card--no-cover .atproto-profile__avatar-wrap{border:none;box-shadow:none}.atproto-profile__avatar{width:100%;height:100%;object-fit:cover;display:block}.atproto-profile__identity{display:flex;flex-direction:column;gap:2px;min-width:0}.atproto-profile__name-row{display:flex;align-items:center;gap:6px;min-width:0}.atproto-profile__name{font-size:var(--font-subtitle);font-weight:600;color:var(--neutral-11);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:0 1 auto;max-width:100%;display:block}.atproto-profile__badge{width:16px;height:16px;flex:0 0 auto}.atproto-profile__handle{font-size:var(--font-body);color:var(--atproto-muted-color);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:100%;display:block}.atproto-profile__description{margin-top:12px;font-size:var(--font-body);line-height:1.5;color:var(--atproto-text-color);white-space:pre-wrap;word-break:break-all}.atproto-profile__paragraph:not(:last-child){margin-bottom:8px}.atproto-profile__link{color:var(--primary-base);font-weight:500;text-decoration:none;text-underline-offset:2px}.atproto-profile__link:hover{text-decoration:underline;text-decoration-thickness:2px}.atproto-profile__metrics{display:flex;flex-wrap:wrap;gap:12px 24px;margin-top:16px}.atproto-profile__metric{display:flex;gap:4px;min-width:80px}.atproto-profile__metric-count{font-size:var(--font-caption);font-weight:600;color:var(--atproto-text-color)}.atproto-profile__metric-label{font-size:var(--font-caption);color:var(--neutral-8)}.atproto-profile__actions{display:flex;flex-wrap:wrap;gap:10px;margin-top:16px}.atproto-profile__btn{flex:1;display:inline-flex;align-items:center;justify-content:center;padding:10px 16px;border-radius:100px;font-size:var(--font-body,14px);font-weight:600;text-decoration:none;transition:transform 0.2s ease,box-shadow 0.2s ease,background 0.2s ease;white-space:nowrap}.atproto-profile__btn:hover{}.atproto-profile__btn--primary{background:var(--primary-base);color:#ffffff;border:1px solid var(--primary-base)}.atproto-profile__btn--primary:hover{background:var(--primary-hover);border-color:var(--primary-hover)}.atproto-profile__btn--secondary{background:transparent;color:var(--neutral-11);border:1px solid var(--neutral-2)}.atproto-profile__btn--secondary:hover{background:var(--neutral-2);border-color:var(--neutral-3)}@media (max-width:540px){.atproto-profile__actions{flex-direction:column}.atproto-profile__header{align-items:flex-start}.atproto-profile__body{padding:14px 14px 16px}}";
435
+ root.appendChild(style);
436
+ }
437
+
438
+ function ensureFontLoaded() {
439
+ if (typeof document === "undefined") return;
440
+ if (document.getElementById("atproto-font-google-sans")) return;
441
+ var link = document.createElement("link");
442
+ link.id = "atproto-font-google-sans";
443
+ link.setAttribute("rel", "stylesheet");
444
+ link.href =
445
+ "https://fonts.googleapis.com/css2?family=Google+Sans+Flex:opsz,wght@6..144,1..1000&display=swap";
446
+ document.head.appendChild(link);
447
+ }
448
+
449
+ /* ───── Init ───── */
450
+
451
+ async function initContainer(container) {
452
+ var actor = parseActor(container);
453
+ if (!actor) return;
454
+
455
+ var previous = CONTAINER_ABORTS.get(container);
456
+ if (previous) previous.abort();
457
+ var controller = new AbortController();
458
+ CONTAINER_ABORTS.set(container, controller);
459
+
460
+ var config = parseConfig(container);
461
+ container.classList.add("atproto-profile-host");
462
+
463
+ if (config.width) container.style.setProperty("--atproto-width", config.width);
464
+ if (config.maxWidth) container.style.setProperty("--atproto-max-width", config.maxWidth);
465
+
466
+ var shadow = container.shadowRoot;
467
+ if (!shadow) {
468
+ shadow = container.attachShadow({ mode: "open" });
469
+ } else {
470
+ shadow.innerHTML = "";
471
+ }
472
+
473
+ ensureFontLoaded();
474
+ injectStyles(shadow);
475
+
476
+ var wrapper = el("div", "atproto-profile-inner");
477
+ shadow.appendChild(wrapper);
478
+
479
+ wrapper.appendChild(
480
+ el("div", "atproto-profile--loading", { textContent: "Loading profile…" })
481
+ );
482
+
483
+ try {
484
+ var profile = await fetchProfile(actor, controller.signal);
485
+ if (!profile) throw new Error("Profile not found");
486
+ var card = renderProfileCard(profile, config);
487
+ wrapper.innerHTML = "";
488
+ if (card) wrapper.appendChild(card);
489
+ } catch (err) {
490
+ if (err && err.name === "AbortError") return;
491
+ wrapper.innerHTML = "";
492
+ wrapper.appendChild(
493
+ el("div", "atproto-profile--error", {
494
+ textContent: "Failed to load profile",
495
+ })
496
+ );
497
+ console.error("[atproto-profile]", err);
498
+ }
499
+ }
500
+
501
+ function init(force) {
502
+ var containers = document.querySelectorAll(
503
+ ".atproto-profile:not([data-profile-child])"
504
+ );
505
+ containers.forEach(function (c) {
506
+ if (!force && c.getAttribute("data-loaded")) return;
507
+ var lazy = c.getAttribute("data-lazy");
508
+ if (lazy === "true" && typeof IntersectionObserver !== "undefined") {
509
+ if (c.getAttribute("data-loaded") === "pending") return;
510
+ c.setAttribute("data-loaded", "pending");
511
+ var observer = new IntersectionObserver(function (entries) {
512
+ entries.forEach(function (entry) {
513
+ if (!entry.isIntersecting) return;
514
+ observer.disconnect();
515
+ c.setAttribute("data-loaded", "true");
516
+ initContainer(c);
517
+ });
518
+ }, { rootMargin: "200px 0px" });
519
+ observer.observe(c);
520
+ } else {
521
+ c.setAttribute("data-loaded", "true");
522
+ initContainer(c);
523
+ }
524
+ });
525
+ }
526
+
527
+ if (document.readyState === "loading") {
528
+ document.addEventListener("DOMContentLoaded", init);
529
+ } else {
530
+ init();
531
+ }
532
+
533
+ if (typeof window !== "undefined") {
534
+ window.AtProtoProfile = window.AtProtoProfile || {};
535
+ window.AtProtoProfile.init = function (force) {
536
+ init(!!force);
537
+ };
538
+ window.AtProtoProfile.refresh = function () {
539
+ init(true);
540
+ };
541
+ }
542
+ })();
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "atproto-embed",
3
+ "version": "2.0.0",
4
+ "description": "Embed Bluesky posts, profiles, or a comment section with a single script tag. Supports up to 100 profiles from a list or starter pack.",
5
+ "main": "dist/embed.js",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "scripts": {
10
+ "build": "node scripts/build.js",
11
+ "release": "node scripts/release.js"
12
+ },
13
+ "author": "romiojoseph",
14
+ "license": "Apache-2.0"
15
+ }