astro-atmosphere-comments 0.6.1 → 0.7.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0 (8 November 2026)
4
+
5
+ **Comments from people who show their posts only to signed-in readers.** Those accounts use Bluesky's "Discourage apps from showing my account to logged-out users".
6
+
7
+ - **Signed-in readers see them:** the reader's browser fetches those comments through the reader's own sign-in (`app.bsky.feed.getPosts`, already part of the approval, so nobody has to sign in again). They then show as normal comments, with Reply, Like and Repost. Signing out puts the placeholders back. The server still never sends their names, pictures or words; the page holds only each comment's address (its AT URI).
8
+ - **Signed-out readers get a clearer placeholder:** a blurred stand-in made of grey bars (never the real comment), with "Comment hidden" and a line saying to sign in to see it. Clicking it opens the sign-in overlay, and the page scrolls to the comment once they're back. The replies under it stay, as before. Styles: `.atmo-comment--withheld`, `.atmo-withheld__ghost`, `.atmo-withheld__cover`; `.atmo-comment__withheld` is gone.
9
+ - **Like and repost counts match the faces:** those accounts are no longer left out of the faces. Signed out, they show as a faceless silhouette (`.atmo-avatar-anonymous`) with no name or link; signed in, their real face, looked up through the site's `profile` route. The thread data gives only their DID (`withheld: true`).
10
+ - A broken avatar picture now falls back to the person's initial.
11
+ - New wording keys `comment.withheldTitle`, `comment.withheldSignedIn`, `signInFirst.view`, `reactions.hiddenPerson`; `comment.withheld` changed. Draft translations in all eight languages.
12
+
3
13
  ## 0.6.1 (7 November 2026)
4
14
 
5
15
  - Uses the current AT Protocol library (`@atproto/api` 0.30, was 0.25), the same version as Atmosphere Publishing for Astro, so a site using both installs it once.
package/README.md CHANGED
@@ -223,7 +223,7 @@ The component fires (bubbling):
223
223
  - **What's checked before anything is sent** (by the browser code, `lib/client/account.ts`): only your announcements and the comments under them can be replied to, liked, reposted or quoted; only a reader's own replies under your announcements can be deleted; only your `standardSite` publication can be subscribed to, and only documents in its account recommended; only your `allowedAuthors` can be followed; only an announcement's author can hide comments under it.
224
224
  - **What your server does:** it shows the threads, looks up accounts and suggestions, finds the links and mentions in a comment before it's posted, and passes on avatars and link pictures it has just shown. None of it needs to know who a reader is. Each route that calls Bluesky is rate-limited per address, with an overall cap; requests that aren't simple reads must come from your own pages (checked by `Origin`). Readers' addresses are used only for those limits, never stored.
225
225
  - **Trade-off:** account hosts trust browser sign-ins less than ones kept on a server, so they last less long (Bluesky's currently last about two weeks), and there's no way for a site to sign everyone out at once. For a reader who'd rather not trust your server, that's the point.
226
- - **People who'd rather not be shown publicly:** Bluesky's setting "Discourage apps from showing my account to logged-out users" is respected, as your page is public. A comment from such an account shows only as "Not shown here…" (no name, picture or words reach the page), the replies under it stay, and the account is left out of the faces of who liked and reposted.
226
+ - **People who'd rather not be shown publicly:** Bluesky's setting "Discourage apps from showing my account to logged-out users" is respected, as your page is public. For readers who aren't signed in, a comment from such an account shows as a blurred "Comment hidden" placeholder (made-up grey bars: no name, picture or words reach the page, only the comment's address), the replies under it stay, and the account appears in the faces of who liked and reposted as a faceless silhouette, so the counts still add up. Clicking the placeholder offers sign-in. Signed-in readers see these comments and faces as normal: their own browser fetches them with their own sign-in.
227
227
  - **Moderation:** comments appear automatically. Hide one with Hide under it (signed in as the account that posted the announcement) or in the Bluesky app; it disappears here too. Show it again in the Bluesky app. You can also block the account, or add it to `hidden`.
228
228
 
229
229
  ## Limitations (for now)
@@ -195,7 +195,9 @@ const returnTo = `${Astro.url.pathname}#atmosphere-comments`;
195
195
  import { translate } from "../lib/translate";
196
196
  import { enhanceHandleBox } from "../lib/client/handle-suggestions";
197
197
  import { MAX, quotePreview, setUpComposer, type ComposerBehaviour, type Posted } from "../lib/client/composer";
198
- import { handleSignInForm, me as whoIsSignedIn, send, signIn, signOut, state } from "../lib/client/account";
198
+ import { handleSignInForm, me as whoIsSignedIn, send, signIn, signOut, state, withheldPosts } from "../lib/client/account";
199
+ import publicSettings from "../lib/public-settings";
200
+ import { segments } from "../lib/richtext";
199
201
  import { addSubscribeButtons, finishPendingSubscribe, forgetSubscribed, loadSubscribed } from "../lib/client/subscribe";
200
202
  import type { Permission } from "../lib/permissions";
201
203
  import { authorOf } from "../lib/uris";
@@ -268,7 +270,7 @@ const returnTo = `${Astro.url.pathname}#atmosphere-comments`;
268
270
 
269
271
  // The thread: likes and reposts above the comment box, and the comments
270
272
  // below it, read through this site (routes/thread.ts).
271
- type Person = { did: string; handle: string; displayName?: string; avatar?: string; url: string };
273
+ type Person = { did: string; handle: string; displayName?: string; avatar?: string; url: string; withheld?: true };
272
274
  type Reply = {
273
275
  uri: string;
274
276
  url: string;
@@ -283,6 +285,9 @@ const returnTo = `${Astro.url.pathname}#atmosphere-comments`;
283
285
  };
284
286
  type Thread = { uri: string; url: string; likeCount: number; repostCount: number; likes: Person[]; reposts: Person[]; replies: Reply[] };
285
287
  let thread: Thread | null = null;
288
+ // Withheld comments and faces, as a signed-in reader sees them (by AT URI and DID).
289
+ const revealedReplies = new Map<string, Reply>();
290
+ const revealedPeople = new Map<string, Person>();
286
291
  const reactionsBox = $<HTMLElement>("[data-reactions]");
287
292
  const repliesList = $<HTMLOListElement>("[data-replies]");
288
293
  const authorLabel = t("comment.author");
@@ -298,12 +303,24 @@ const returnTo = `${Astro.url.pathname}#atmosphere-comments`;
298
303
  if (text !== undefined) element.textContent = text;
299
304
  return element;
300
305
  };
301
- const avatarOf = (person: Pick<Person, "handle" | "displayName" | "avatar">, className: string) => {
302
- if (person.avatar) return Object.assign(make("img", className), { src: person.avatar, alt: "", loading: "lazy" as const });
306
+ const initialOf = (person: Pick<Person, "handle" | "displayName">, className: string) => {
303
307
  const initial = make("span", `${className} atmo-avatar-initial`, nameOf(person).replace("@", "").charAt(0).toUpperCase());
304
308
  initial.setAttribute("aria-hidden", "true");
305
309
  return initial;
306
310
  };
311
+ // A faceless avatar, for someone who asks not to be shown to people who aren't signed in.
312
+ const anonymousAvatar = (className: string) => {
313
+ const face = make("span", `${className} atmo-avatar-anonymous`);
314
+ face.setAttribute("aria-hidden", "true");
315
+ return face;
316
+ };
317
+ const avatarOf = (person: Pick<Person, "handle" | "displayName" | "avatar"> & { withheld?: true }, className: string) => {
318
+ if (person.withheld) return anonymousAvatar(className);
319
+ if (!person.avatar) return initialOf(person, className);
320
+ const img = Object.assign(make("img", className), { src: person.avatar, alt: "", loading: "lazy" as const });
321
+ img.addEventListener("error", () => img.replaceWith(initialOf(person, className)), { once: true });
322
+ return img;
323
+ };
307
324
 
308
325
  const renderReactions = () => {
309
326
  if (!thread) return;
@@ -318,7 +335,16 @@ const returnTo = `${Astro.url.pathname}#atmosphere-comments`;
318
335
  group.dataset.reaction = kind;
319
336
  group.append(make("p", "atmo-reactions__count", t(kind === "like" ? "reactions.likes" : "reactions.reposts", { count })));
320
337
  const faces = make("ul", "atmo-facepile");
321
- for (const person of people) {
338
+ for (const shown of people) {
339
+ const person = (shown.withheld && revealedPeople.get(shown.did)) || shown;
340
+ if (person.withheld) {
341
+ // Counted, but not identified: signed out, or not yet looked up.
342
+ const item = make("li");
343
+ item.title = t("reactions.hiddenPerson");
344
+ item.append(anonymousAvatar("atmo-facepile__avatar"));
345
+ faces.append(item);
346
+ continue;
347
+ }
322
348
  const link = Object.assign(make("a"), { href: person.url, title: nameOf(person), rel: "ugc nofollow noopener", target: "_blank" });
323
349
  link.setAttribute("aria-label", nameOf(person));
324
350
  link.append(avatarOf(person, "atmo-facepile__avatar"));
@@ -339,9 +365,30 @@ const returnTo = `${Astro.url.pathname}#atmosphere-comments`;
339
365
  // Its author asks not to be shown to logged-out people: a placeholder,
340
366
  // keeping the replies under it in place. (No AT URI, so no actions.)
341
367
  if (reply.withheld) {
368
+ const revealed = revealedReplies.get(reply.uri);
369
+ if (revealed) return replyElement({ ...revealed, depth: reply.depth });
342
370
  item.classList.add("atmo-comment--withheld");
371
+ if (reply.uri) item.dataset.withheldUri = reply.uri;
372
+ // A blurred, made-up comment (never the real one) under the explanation.
343
373
  const article = make("article");
344
- article.append(make("p", "atmo-comment__withheld", t("comment.withheld")));
374
+ const ghost = make("div", "atmo-withheld__ghost");
375
+ ghost.setAttribute("aria-hidden", "true");
376
+ const ghostHeader = make("div", "atmo-comment__header");
377
+ const bar = (width: string) => Object.assign(make("span", "atmo-withheld__bar"), { style: `width: ${width}` });
378
+ const ghostNames = make("div", "atmo-withheld__names");
379
+ ghostNames.append(bar("9em"), bar("13em"));
380
+ ghostHeader.append(anonymousAvatar("atmo-comment__avatar"), ghostNames);
381
+ const ghostText = make("div", "atmo-withheld__text");
382
+ ghostText.append(bar("100%"), bar("92%"), bar("60%"));
383
+ ghost.append(ghostHeader, ghostText);
384
+ const cover = make("button", "atmo-withheld__cover");
385
+ cover.type = "button";
386
+ cover.append(make("strong", "", t("comment.withheldTitle")), make("span", "", t(isSignedIn ? "comment.withheldSignedIn" : "comment.withheld")));
387
+ cover.disabled = isSignedIn;
388
+ cover.addEventListener("click", () => {
389
+ if (!isSignedIn) askToSignIn("view", reply.uri || undefined);
390
+ });
391
+ article.append(ghost, cover);
345
392
  item.append(article);
346
393
  return item;
347
394
  }
@@ -487,6 +534,7 @@ const returnTo = `${Astro.url.pathname}#atmosphere-comments`;
487
534
  reply: "signInFirst.reply",
488
535
  "comment-like": "signInFirst.like",
489
536
  "comment-repost": "signInFirst.repost",
537
+ view: "signInFirst.view",
490
538
  };
491
539
  const askToSignIn = (kind: string, uri?: string, problem?: string) =>
492
540
  openSignIn(t(SIGN_IN_HEADINGS[kind]), `#atmo-do=${kind}${uri ? `:${encodeURIComponent(uri)}` : ""}`, problem);
@@ -907,6 +955,53 @@ const returnTo = `${Astro.url.pathname}#atmosphere-comments`;
907
955
  else if (kind === "comment-repost" && comment) press(comment.querySelector<HTMLElement>(".atmo-comment-action[data-kind='repost']"));
908
956
  };
909
957
 
958
+ // Draws the comments and faces again (after revealing, or signing out).
959
+ const redrawThread = () => {
960
+ if (!thread) return;
961
+ renderReactions();
962
+ repliesList.replaceChildren(...thread.replies.map(replyElement));
963
+ };
964
+ // Signed in: the comments and faces of people who ask not to be shown to
965
+ // people who aren't signed in, fetched as the reader and shown.
966
+ const revealWithheld = async () => {
967
+ if (!thread) return;
968
+ const uris = thread.replies.filter((reply) => reply.withheld && reply.uri && !revealedReplies.has(reply.uri)).map((reply) => reply.uri);
969
+ const dids = [...new Set([...thread.likes, ...thread.reposts].filter((person) => person.withheld && person.did && !revealedPeople.has(person.did)).map((person) => person.did))].slice(0, 20);
970
+ if (!uris.length && !dids.length) return;
971
+ const [posts, people] = await Promise.all([
972
+ withheldPosts(uris),
973
+ Promise.all(
974
+ dids.map((did) =>
975
+ fetch(`${base}/profile?did=${encodeURIComponent(did)}`)
976
+ .then((response) => (response.ok ? (response.json() as Promise<Pick<Person, "did" | "handle" | "displayName" | "avatar">>) : null))
977
+ .catch(() => null),
978
+ ),
979
+ ),
980
+ ]);
981
+ if (!isSignedIn) return;
982
+ for (const found of posts) {
983
+ const handle = found.author.handle;
984
+ const profileUrl = `https://bsky.app/profile/${encodeURIComponent(handle)}`;
985
+ revealedReplies.set(found.uri, {
986
+ uri: found.uri,
987
+ url: `${profileUrl}/post/${found.uri.split("/").pop()}`,
988
+ author: { did: found.author.did, handle, displayName: found.author.displayName, avatar: `${base}/media?avatar=${encodeURIComponent(found.author.did)}`, url: profileUrl },
989
+ byAuthor: publicSettings.allowedAuthors.includes(found.author.did),
990
+ createdAt: found.record.createdAt ?? "",
991
+ text: segments(found.record.text ?? "", found.record.facets),
992
+ depth: 0,
993
+ likeCount: found.likeCount,
994
+ repostCount: found.repostCount,
995
+ });
996
+ }
997
+ for (const found of people) {
998
+ if (found?.did && found.handle && found.handle !== found.did) {
999
+ revealedPeople.set(found.did, { ...found, url: `https://bsky.app/profile/${encodeURIComponent(found.handle)}` });
1000
+ }
1001
+ }
1002
+ redrawThread();
1003
+ };
1004
+
910
1005
  const show = async (signed: boolean, handle?: string, displayName?: string, avatar?: string) => {
911
1006
  if (handle) {
912
1007
  $<HTMLElement>("[data-preview-name]").textContent = displayName || handle;
@@ -931,6 +1026,11 @@ const returnTo = `${Astro.url.pathname}#atmosphere-comments`;
931
1026
  permissionPanel.hidden = true;
932
1027
  setPressed("like", false);
933
1028
  setPressed("repost", false);
1029
+ if (revealedReplies.size || revealedPeople.size) {
1030
+ revealedReplies.clear();
1031
+ revealedPeople.clear();
1032
+ redrawThread();
1033
+ }
934
1034
  resumePending(false);
935
1035
  return closeReplies();
936
1036
  }
@@ -940,6 +1040,7 @@ const returnTo = `${Astro.url.pathname}#atmosphere-comments`;
940
1040
  permissionPanel.hidden = can("post");
941
1041
  // Which account, as they may have more than one.
942
1042
  $<HTMLElement>("[data-permission-text]").textContent = t("composer.needsPermission", { handle: handle ? `@${handle}` : "" });
1043
+ await revealWithheld().catch(() => undefined);
943
1044
  addCommentActions();
944
1045
  addDeleteButtons();
945
1046
  await loadState();
@@ -868,10 +868,79 @@ dialog.atmo.atmo-signin:has(> .atmo__login) {
868
868
  margin: 0;
869
869
  }
870
870
 
871
- /* In place of a comment whose author asks not to be shown to logged-out people. */
872
- :where(.atmo-comment__withheld) {
873
- margin: 0;
871
+ /* In place of a comment whose author asks not to be shown to logged-out people:
872
+ a blurred, made-up comment (grey bars, never the real one) under an explanation. */
873
+ :where(.atmo-comment--withheld article) {
874
+ position: relative;
875
+ overflow: hidden;
876
+ }
877
+
878
+ .atmo-withheld__ghost {
879
+ filter: blur(5px);
880
+ opacity: 0.7;
881
+ pointer-events: none;
882
+ user-select: none;
883
+ }
884
+
885
+ .atmo-withheld__names,
886
+ .atmo-withheld__text {
887
+ display: grid;
888
+ gap: 8px;
889
+ align-content: center;
890
+ }
891
+
892
+ .atmo-withheld__text {
893
+ margin-top: 14px;
894
+ }
895
+
896
+ .atmo-withheld__bar {
897
+ display: block;
898
+ max-width: 100%;
899
+ height: 0.75em;
900
+ border-radius: 999px;
901
+ background: color-mix(in srgb, var(--atmo-muted, currentColor) 45%, transparent);
902
+ }
903
+
904
+ .atmo-withheld__cover {
905
+ position: absolute;
906
+ inset: 0;
907
+ display: grid;
908
+ place-content: center;
909
+ gap: 4px;
910
+ padding: 12px 20px;
911
+ border: 0;
912
+ color: var(--atmo-text, inherit);
913
+ font: inherit;
914
+ text-align: center;
915
+ background: color-mix(in srgb, var(--atmo-card-bg, Canvas) 35%, transparent);
916
+ cursor: pointer;
917
+ }
918
+
919
+ .atmo-withheld__cover:disabled {
920
+ cursor: default;
921
+ }
922
+
923
+ .atmo-withheld__cover span {
924
+ max-width: 42ch;
874
925
  color: var(--atmo-muted, inherit);
875
926
  font-size: 0.9em;
876
- font-style: italic;
927
+ }
928
+
929
+ .atmo-withheld__cover:not(:disabled):hover strong,
930
+ .atmo-withheld__cover:not(:disabled):focus-visible strong {
931
+ text-decoration: underline;
932
+ }
933
+
934
+ .atmo-withheld__cover:focus-visible {
935
+ outline: 2px solid var(--atmo-accent, #1185fe);
936
+ outline-offset: -2px;
937
+ }
938
+
939
+ /* A faceless avatar: a head and shoulders. */
940
+ :where(.atmo-avatar-anonymous) {
941
+ display: inline-block;
942
+ background:
943
+ radial-gradient(circle at 50% 38%, color-mix(in srgb, var(--atmo-muted, #888) 70%, transparent) 0 18%, transparent 19%),
944
+ radial-gradient(ellipse 34% 26% at 50% 88%, color-mix(in srgb, var(--atmo-muted, #888) 70%, transparent) 0 99%, transparent 100%),
945
+ var(--atmo-field-bg, #8882);
877
946
  }
@@ -35,6 +35,7 @@ import {
35
35
  } from "../permissions";
36
36
  import settings from "../public-settings";
37
37
  import { authorOf, inSiteThread, isPostUri, isSiteDocument, isSitePost, rkeyOf, type PostView } from "../uris";
38
+ import type { Facet } from "../richtext";
38
39
 
39
40
  type IdentityResolver = NonNullable<ConstructorParameters<typeof BrowserOAuthClient>[0]["identityResolver"]>;
40
41
 
@@ -538,6 +539,44 @@ export async function state(uris: string[]) {
538
539
  }
539
540
  }
540
541
 
542
+ export type WithheldPost = {
543
+ uri: string;
544
+ author: { did: string; handle: string; displayName?: string };
545
+ record: { text?: string; createdAt?: string; facets?: Facet[] };
546
+ likeCount: number;
547
+ repostCount: number;
548
+ };
549
+
550
+ /**
551
+ * Comments whose authors ask not to be shown to people who aren't signed in,
552
+ * read as the signed-in reader (the site only gives their AT URIs). Only posts
553
+ * in the site's threads; nothing if no one is signed in.
554
+ */
555
+ export async function withheldPosts(uris: string[]): Promise<WithheldPost[]> {
556
+ const signedIn = await reader();
557
+ if (signedIn instanceof Response || !uris.length) return [];
558
+ try {
559
+ const views = (await postsAsReader(signedIn, [...new Set(uris)].filter(isPostUri).slice(0, 100))) as (PostView & {
560
+ author?: { did: string; handle?: string; displayName?: string };
561
+ record?: { text?: string; createdAt?: string; facets?: Facet[] };
562
+ likeCount?: number;
563
+ repostCount?: number;
564
+ })[];
565
+ return views
566
+ .filter((view) => inSiteThread(view) && view.author?.did)
567
+ .map((view) => ({
568
+ uri: view.uri,
569
+ author: { did: view.author!.did, handle: view.author!.handle || view.author!.did, displayName: view.author!.displayName?.trim() || undefined },
570
+ record: { text: view.record?.text, createdAt: view.record?.createdAt, facets: view.record?.facets },
571
+ likeCount: view.likeCount ?? 0,
572
+ repostCount: view.repostCount ?? 0,
573
+ }));
574
+ } catch (error) {
575
+ failed(error, "withheld comments");
576
+ return [];
577
+ }
578
+ }
579
+
541
580
  /** Signs out: cancels the sign-in at the reader's account host, and deletes it from this browser. */
542
581
  export async function signOut() {
543
582
  const current = await session().catch(() => null);
package/lib/thread.ts CHANGED
@@ -9,9 +9,11 @@
9
9
  // Accounts that ask apps not to show them to logged-out people (Bluesky's
10
10
  // "Discourage apps from showing my account to logged-out users", the
11
11
  // !no-unauthenticated label) are respected too, as the page is public:
12
- // their comments become a bare placeholder (no name, picture, address or
13
- // words), keeping the replies under them, which are other people's, and
14
- // they're left out of the faces of who liked and reposted.
12
+ // their comments become a bare placeholder (no name, picture or words, only
13
+ // the comment's AT URI), keeping the replies under them, which are other
14
+ // people's, and their faces among who liked and reposted become anonymous (only
15
+ // their DID), so the counts match. A signed-in reader's browser then fetches
16
+ // them with the reader's own sign-in and shows them (AtmosphereComments.astro).
15
17
  // Cached for two minutes per announcement; if Bluesky can't be reached the
16
18
  // last good copy is kept.
17
19
  import { rememberAvatar } from "./avatars";
@@ -27,7 +29,15 @@ const MAX_FACES = 48;
27
29
  const HIDING_LABELS = new Set(["!hide", "!takedown", "!suspend", "spam", "porn", "sexual", "nudity", "graphic-media", "gore"]);
28
30
  const LOGGED_IN_ONLY = "!no-unauthenticated";
29
31
 
30
- export type Person = { did: string; handle: string; displayName?: string; avatar?: string; url: string };
32
+ export type Person = {
33
+ did: string;
34
+ handle: string;
35
+ displayName?: string;
36
+ avatar?: string;
37
+ url: string;
38
+ /** Asks not to be shown to logged-out people: only the DID is given (an anonymous face). */
39
+ withheld?: true;
40
+ };
31
41
 
32
42
  export type Reply = {
33
43
  uri: string;
@@ -42,8 +52,9 @@ export type Reply = {
42
52
  likeCount: number;
43
53
  repostCount: number;
44
54
  /**
45
- * Its author asks not to be shown to logged-out people: everything else
46
- * is left empty, and the page shows a placeholder in its place.
55
+ * Its author asks not to be shown to logged-out people: everything but
56
+ * `uri` and `depth` is left empty, and the page shows a placeholder in its
57
+ * place (and the real comment to signed-in readers).
47
58
  */
48
59
  withheld?: true;
49
60
  };
@@ -97,6 +108,13 @@ function person(author: Author): Person | null {
97
108
  /** Whether an account asks apps not to show it to people who aren't signed in. */
98
109
  const loggedInOnly = (author?: Author) => !!author?.labels?.some((label) => label.val === LOGGED_IN_ONLY);
99
110
 
111
+ /** Someone who liked or reposted: anonymous (their DID only) if they ask not to be shown to logged-out people. */
112
+ function face(actor: Author | undefined): Person[] {
113
+ const found = actor && person(actor);
114
+ if (!found) return [];
115
+ return [loggedInOnly(actor) ? { did: found.did, handle: "", url: "", withheld: true } : found];
116
+ }
117
+
100
118
  function isHidden(post: ThreadPost, author: Person, url: string): boolean {
101
119
  const labels = [...(post.labels ?? []), ...(post.author?.labels ?? [])];
102
120
  if (labels.some((label) => label.val && HIDING_LABELS.has(label.val))) return true;
@@ -116,7 +134,7 @@ function collect(nodes: ThreadNode[] = [], depth: number, hiddenByAuthor: Set<st
116
134
  if (isHidden(post, author, url)) continue;
117
135
  if (loggedInOnly(post.author)) {
118
136
  out.push({
119
- uri: "",
137
+ uri: post.uri,
120
138
  url: "",
121
139
  author: { did: "", handle: "", url: "" },
122
140
  byAuthor: false,
@@ -161,8 +179,8 @@ async function fetchThread(uri: string): Promise<Thread> {
161
179
  url: rootAuthor ? `${rootAuthor.url}/post/${uri.split("/").pop()}` : "",
162
180
  likeCount: root.likeCount ?? likes.length,
163
181
  repostCount: root.repostCount ?? repostedBy.length,
164
- likes: likes.flatMap(({ actor }) => (actor && !loggedInOnly(actor) ? [person(actor)].filter((p): p is Person => !!p) : [])),
165
- reposts: repostedBy.filter((actor) => !loggedInOnly(actor)).map(person).filter((p): p is Person => !!p),
182
+ likes: likes.flatMap(({ actor }) => face(actor)),
183
+ reposts: repostedBy.flatMap(face),
166
184
  replies,
167
185
  };
168
186
  }
package/locales/de.json CHANGED
@@ -107,5 +107,9 @@
107
107
  "permission.post": "Um von dieser Seite aus zu antworten und zu zitieren, braucht sie eine weitere Erlaubnis von deinem Atmosphere-Konto: Beiträge erstellen.",
108
108
  "permission.react": "Um von dieser Seite aus zu liken und zu reposten, braucht sie eine weitere Erlaubnis von deinem Atmosphere-Konto: liken und reposten und das zurücknehmen.",
109
109
  "permission.approve": "Die Seite deines Kontos (für die meisten die von Bluesky) bittet dich, das zu bestätigen, und vielleicht vorher, dich dort anzumelden. Danach kommst du direkt hierher zurück.",
110
- "comment.withheld": "Hier nicht angezeigt: Die Person bittet Apps, ihre Beiträge nicht Leuten zu zeigen, die nicht angemeldet sind."
110
+ "comment.withheld": "Die Person zeigt ihre Beiträge nur Leuten, die angemeldet sind. Melde dich an, um ihn zu sehen.",
111
+ "comment.withheldTitle": "Kommentar ausgeblendet",
112
+ "comment.withheldSignedIn": "Er konnte gerade nicht geladen werden. Lade die Seite neu.",
113
+ "signInFirst.view": "Melde dich an, um ausgeblendete Kommentare zu sehen",
114
+ "reactions.hiddenPerson": "Jemand, der sein Konto nur angemeldeten Leuten zeigt"
111
115
  }
package/locales/en.json CHANGED
@@ -126,5 +126,9 @@
126
126
  "permission.post": "To reply and quote from this site, it needs one more permission from your Atmosphere account: to create posts.",
127
127
  "permission.react": "To like and repost from this site, it needs one more permission from your Atmosphere account: to like and repost, and to undo that.",
128
128
  "permission.approve": "Your account's own site (Bluesky's, for most people) will ask you to approve it, and may ask you to sign in there first. Then you'll come straight back here.",
129
- "comment.withheld": "Not shown here: its author asks apps not to show their posts to people who aren't signed in."
129
+ "comment.withheld": "Its author shows their posts only to people who are signed in. Sign in to see it.",
130
+ "comment.withheldTitle": "Comment hidden",
131
+ "comment.withheldSignedIn": "It couldn't be loaded just now. Try reloading the page.",
132
+ "signInFirst.view": "Sign in to see hidden comments",
133
+ "reactions.hiddenPerson": "Someone who shows their account only to people who are signed in"
130
134
  }
package/locales/es.json CHANGED
@@ -110,5 +110,9 @@
110
110
  "permission.post": "Para responder y citar desde este sitio, necesita un permiso más de tu cuenta de Atmosphere: crear publicaciones.",
111
111
  "permission.react": "Para dar me gusta y republicar desde este sitio, necesita un permiso más de tu cuenta de Atmosphere: dar me gusta y republicar, y deshacerlo.",
112
112
  "permission.approve": "El sitio de tu cuenta (el de Bluesky, para la mayoría) te pedirá que lo apruebes, y quizá que inicies sesión allí primero. Después volverás directamente aquí.",
113
- "comment.withheld": "No se muestra aquí: su autor pide a las aplicaciones que no muestren sus publicaciones a quien no haya iniciado sesión."
113
+ "comment.withheld": "Su autor solo muestra sus publicaciones a quien haya iniciado sesión. Inicia sesión para verlo.",
114
+ "comment.withheldTitle": "Comentario oculto",
115
+ "comment.withheldSignedIn": "No se ha podido cargar. Prueba a recargar la página.",
116
+ "signInFirst.view": "Inicia sesión para ver los comentarios ocultos",
117
+ "reactions.hiddenPerson": "Alguien que solo muestra su cuenta a quien haya iniciado sesión"
114
118
  }
package/locales/fr.json CHANGED
@@ -110,5 +110,9 @@
110
110
  "permission.post": "Pour répondre et citer depuis ce site, il a besoin d'une autorisation de plus de ton compte Atmosphere : créer des publications.",
111
111
  "permission.react": "Pour aimer et republier depuis ce site, il a besoin d'une autorisation de plus de ton compte Atmosphere : aimer et republier, et l'annuler.",
112
112
  "permission.approve": "Le site de ton compte (celui de Bluesky, pour la plupart des gens) te demandera de l'approuver, et peut-être de t'y connecter d'abord. Ensuite, tu reviendras directement ici.",
113
- "comment.withheld": "Non affiché ici : son auteur demande aux applications de ne pas montrer ses publications aux personnes non connectées."
113
+ "comment.withheld": "Son auteur ne montre ses publications qu'aux personnes connectées. Connectez-vous pour le voir.",
114
+ "comment.withheldTitle": "Commentaire masqué",
115
+ "comment.withheldSignedIn": "Il n'a pas pu être chargé. Essayez de recharger la page.",
116
+ "signInFirst.view": "Connectez-vous pour voir les commentaires masqués",
117
+ "reactions.hiddenPerson": "Une personne qui ne montre son compte qu'aux personnes connectées"
114
118
  }
package/locales/it.json CHANGED
@@ -110,5 +110,9 @@
110
110
  "permission.post": "Per rispondere e citare da questo sito, serve un'altra autorizzazione dal tuo account Atmosphere: creare post.",
111
111
  "permission.react": "Per mettere mi piace e ripubblicare da questo sito, serve un'altra autorizzazione dal tuo account Atmosphere: mettere mi piace e ripubblicare, e annullarlo.",
112
112
  "permission.approve": "Il sito del tuo account (quello di Bluesky, per la maggior parte delle persone) ti chiederà di approvare, e forse prima di accedere. Poi tornerai subito qui.",
113
- "comment.withheld": "Non mostrato qui: l'autore chiede alle app di non mostrare i suoi post a chi non ha effettuato l'accesso."
113
+ "comment.withheld": "L'autore mostra i suoi post solo a chi ha effettuato l'accesso. Accedi per vederlo.",
114
+ "comment.withheldTitle": "Commento nascosto",
115
+ "comment.withheldSignedIn": "Non è stato possibile caricarlo. Prova a ricaricare la pagina.",
116
+ "signInFirst.view": "Accedi per vedere i commenti nascosti",
117
+ "reactions.hiddenPerson": "Qualcuno che mostra il proprio account solo a chi ha effettuato l'accesso"
114
118
  }
package/locales/ja.json CHANGED
@@ -104,5 +104,9 @@
104
104
  "permission.post": "このサイトから返信や引用をするには、Atmosphereアカウントからの許可がもうひとつ必要です(投稿の作成)。",
105
105
  "permission.react": "このサイトからいいねやリポストをするには、Atmosphereアカウントからの許可がもうひとつ必要です(いいねとリポスト、その取り消し)。",
106
106
  "permission.approve": "アカウントのサイト(ほとんどの方はBluesky)で承認を求められます。先にそこでログインを求められることもあります。その後、すぐにここへ戻ります。",
107
- "comment.withheld": "ここには表示されません:投稿者は、ログインしていない人に投稿を表示しないようアプリに求めています。"
107
+ "comment.withheld": "投稿者は、ログインしている人にだけ投稿を表示しています。表示するにはログインしてください。",
108
+ "comment.withheldTitle": "非表示のコメント",
109
+ "comment.withheldSignedIn": "読み込めませんでした。ページを再読み込みしてください。",
110
+ "signInFirst.view": "ログインすると非表示のコメントを表示できます",
111
+ "reactions.hiddenPerson": "ログインしている人にだけアカウントを表示している人"
108
112
  }
package/locales/nl.json CHANGED
@@ -107,5 +107,9 @@
107
107
  "permission.post": "Om vanaf deze site te reageren en te citeren, is nog één toestemming van je Atmosphere-account nodig: berichten plaatsen.",
108
108
  "permission.react": "Om vanaf deze site te liken en te reposten, is nog één toestemming van je Atmosphere-account nodig: liken en reposten, en dat ongedaan maken.",
109
109
  "permission.approve": "De site van je account (voor de meeste mensen die van Bluesky) vraagt je dit goed te keuren, en misschien eerst daar in te loggen. Daarna kom je meteen hierheen terug.",
110
- "comment.withheld": "Hier niet getoond: de schrijver vraagt apps om zijn of haar berichten niet te tonen aan mensen die niet zijn ingelogd."
110
+ "comment.withheld": "De schrijver toont berichten alleen aan mensen die zijn ingelogd. Log in om de reactie te zien.",
111
+ "comment.withheldTitle": "Reactie verborgen",
112
+ "comment.withheldSignedIn": "De reactie kon nu niet worden geladen. Probeer de pagina opnieuw te laden.",
113
+ "signInFirst.view": "Log in om verborgen reacties te zien",
114
+ "reactions.hiddenPerson": "Iemand die zijn of haar account alleen toont aan mensen die zijn ingelogd"
111
115
  }
package/locales/pt.json CHANGED
@@ -110,5 +110,9 @@
110
110
  "permission.post": "Para responder e citar por este site, ele precisa de mais uma permissão da sua conta Atmosphere: criar publicações.",
111
111
  "permission.react": "Para curtir e repostar por este site, ele precisa de mais uma permissão da sua conta Atmosphere: curtir e repostar, e desfazer isso.",
112
112
  "permission.approve": "O site da sua conta (o do Bluesky, para a maioria das pessoas) vai pedir que você aprove, e talvez que entre lá primeiro. Depois você volta direto para cá.",
113
- "comment.withheld": "Não exibido aqui: o autor pede aos apps que não mostrem suas publicações a quem não está conectado."
113
+ "comment.withheld": "O autor mostra suas publicações apenas a quem está conectado. Entre para ver.",
114
+ "comment.withheldTitle": "Comentário oculto",
115
+ "comment.withheldSignedIn": "Não foi possível carregá-lo agora. Tente recarregar a página.",
116
+ "signInFirst.view": "Entre para ver os comentários ocultos",
117
+ "reactions.hiddenPerson": "Alguém que mostra sua conta apenas a quem está conectado"
114
118
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-atmosphere-comments",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Atmosphere Comments for Astro: Bluesky comments, likes, reposts and quote posts for Astro sites; readers sign in with their Atmosphere (AT Protocol) account and respond without leaving the page.",
5
5
  "type": "module",
6
6
  "license": "MIT",