@nuniapp/widget 0.1.10 → 0.1.11

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/dist/index.d.ts CHANGED
@@ -49,6 +49,11 @@ declare const EN: {
49
49
  yourName: string;
50
50
  leaveComment: string;
51
51
  notYou: string;
52
+ signIn: string;
53
+ signInGithub: string;
54
+ signOut: string;
55
+ signedInAs: string;
56
+ signedInGithub: string;
52
57
  cancel: string;
53
58
  post: string;
54
59
  posting: string;
@@ -122,6 +127,8 @@ declare const EN: {
122
127
  copied: string;
123
128
  copyFailed: string;
124
129
  signedIn: string;
130
+ commenterSignedIn: string;
131
+ signedOut: string;
125
132
  linkGone: string;
126
133
  enterName: string;
127
134
  writeFirst: string;
package/dist/index.js CHANGED
@@ -176,7 +176,7 @@ function buildCommentPrompt(comment, options = {}) {
176
176
  }
177
177
  //#endregion
178
178
  //#region src/config.ts
179
- const VERSION = "0.1.10";
179
+ const VERSION = "0.1.11";
180
180
  function resolveConfig(options) {
181
181
  return {
182
182
  project: options.project,
@@ -5285,10 +5285,10 @@ var NuniApi = class {
5285
5285
  }, (comments) => cb(comments), onError);
5286
5286
  }
5287
5287
  /** The visitor's own comments on every page, live. */
5288
- onMine(authorSecret, cb) {
5288
+ onMine(author, cb) {
5289
5289
  return this.client.onUpdate(api.comments.listMine, {
5290
5290
  publicId: this.config.project,
5291
- authorSecret
5291
+ ...author
5292
5292
  }, (comments) => cb(comments), () => {});
5293
5293
  }
5294
5294
  onPages(cb) {
@@ -5300,6 +5300,24 @@ var NuniApi = class {
5300
5300
  sessionToken
5301
5301
  }, cb, () => {});
5302
5302
  }
5303
+ /** Who a stored commenter sign-in belongs to, live (invalid once revoked). */
5304
+ onCommenter(commenterToken, cb) {
5305
+ return this.client.onUpdate(api.commenters.validate, {
5306
+ publicId: this.config.project,
5307
+ commenterToken
5308
+ }, cb, () => {});
5309
+ }
5310
+ /** Right after signing in: this browser's guest comments become the account's. */
5311
+ linkBrowser(authorSecret, commenterToken) {
5312
+ return this.run(this.client.mutation(api.commenters.linkBrowser, {
5313
+ publicId: this.config.project,
5314
+ authorSecret,
5315
+ commenterToken
5316
+ }));
5317
+ }
5318
+ signOutCommenter(commenterToken) {
5319
+ return this.client.mutation(api.commenters.signOut, { commenterToken }).catch(() => null);
5320
+ }
5303
5321
  /** A comment with the owner-only context (screenshot, console, DOM). */
5304
5322
  onOwnerComment(id, sessionToken, cb) {
5305
5323
  return this.client.onUpdate(api.comments.getForOwner, {
@@ -5360,21 +5378,22 @@ var NuniApi = class {
5360
5378
  return data.id;
5361
5379
  }
5362
5380
  /** Attach the element's screenshot (for the owner) to a just-posted comment. */
5363
- uploadScreenshot(id, authorSecret, image) {
5364
- return this.upload("screenshot", id, authorSecret, image);
5381
+ uploadScreenshot(id, author, image) {
5382
+ return this.upload("screenshot", id, author, image);
5365
5383
  }
5366
5384
  /** An image for everyone: picked, pasted or a marked-up screenshot. */
5367
- uploadImage(id, authorSecret, image) {
5368
- return this.upload("image", id, authorSecret, image);
5385
+ uploadImage(id, author, image) {
5386
+ return this.upload("image", id, author, image);
5369
5387
  }
5370
- async upload(kind, id, authorSecret, image) {
5388
+ async upload(kind, id, author, image) {
5371
5389
  const response = await fetch(`${this.config.convexSiteUrl}/widget/${kind}`, {
5372
5390
  method: "POST",
5373
5391
  headers: {
5374
5392
  "Content-Type": image.type,
5375
5393
  "X-Nuni-Project": this.config.project,
5376
5394
  "X-Nuni-Comment": id,
5377
- "X-Nuni-Author": authorSecret
5395
+ "X-Nuni-Author": author.authorSecret,
5396
+ ...author.commenterToken ? { "X-Nuni-Commenter": author.commenterToken } : {}
5378
5397
  },
5379
5398
  body: image
5380
5399
  });
@@ -5390,18 +5409,18 @@ var NuniApi = class {
5390
5409
  throw new NuniApiError(errorMessage(error), "error");
5391
5410
  }
5392
5411
  }
5393
- editOwn(id, authorSecret, body, suggestion) {
5412
+ editOwn(id, author, body, suggestion) {
5394
5413
  return this.run(this.client.mutation(api.comments.editOwn, {
5395
5414
  id,
5396
- authorSecret,
5415
+ ...author,
5397
5416
  body,
5398
5417
  suggestion
5399
5418
  }));
5400
5419
  }
5401
- deleteOwn(id, authorSecret) {
5420
+ deleteOwn(id, author) {
5402
5421
  return this.run(this.client.mutation(api.comments.deleteOwn, {
5403
5422
  id,
5404
- authorSecret
5423
+ ...author
5405
5424
  }));
5406
5425
  }
5407
5426
  resolve(id, sessionToken, resolution) {
@@ -5444,17 +5463,17 @@ var NuniApi = class {
5444
5463
  sessionToken
5445
5464
  }));
5446
5465
  }
5447
- editReply(id, authorSecret, body) {
5466
+ editReply(id, author, body) {
5448
5467
  return this.run(this.client.mutation(api.replies.editOwn, {
5449
5468
  id,
5450
- authorSecret,
5469
+ ...author,
5451
5470
  body
5452
5471
  }));
5453
5472
  }
5454
- deleteReply(id, authorSecret) {
5473
+ deleteReply(id, author) {
5455
5474
  return this.run(this.client.mutation(api.replies.deleteOwn, {
5456
5475
  id,
5457
- authorSecret
5476
+ ...author
5458
5477
  }));
5459
5478
  }
5460
5479
  removeReply(id, sessionToken) {
@@ -5463,13 +5482,13 @@ var NuniApi = class {
5463
5482
  sessionToken
5464
5483
  }));
5465
5484
  }
5466
- toggleReaction(commentId, targetId, emoji, authorSecret) {
5485
+ toggleReaction(commentId, targetId, emoji, author) {
5467
5486
  return this.run(this.client.mutation(api.reactions.toggle, {
5468
5487
  publicId: this.config.project,
5469
5488
  commentId,
5470
5489
  targetId,
5471
5490
  emoji,
5472
- authorSecret
5491
+ ...author
5473
5492
  }));
5474
5493
  }
5475
5494
  remove(id, sessionToken) {
@@ -5531,6 +5550,11 @@ const EN = {
5531
5550
  yourName: "Your name",
5532
5551
  leaveComment: "Leave a comment",
5533
5552
  notYou: "Not you?",
5553
+ signIn: "Sign in",
5554
+ signInGithub: "Sign in with GitHub to comment as yourself",
5555
+ signOut: "Sign out",
5556
+ signedInAs: "Signed in as {name}",
5557
+ signedInGithub: "Signed in with GitHub",
5534
5558
  cancel: "Cancel",
5535
5559
  post: "Post",
5536
5560
  posting: "Posting…",
@@ -5604,6 +5628,8 @@ const EN = {
5604
5628
  copied: "Copied for your coding agent",
5605
5629
  copyFailed: "Couldn't copy",
5606
5630
  signedIn: "You're signed in as the owner",
5631
+ commenterSignedIn: "You're signed in",
5632
+ signedOut: "Signed out",
5607
5633
  linkGone: "That comment was deleted or is on another page",
5608
5634
  enterName: "Enter your name",
5609
5635
  writeFirst: "Write a comment first",
@@ -5947,6 +5973,8 @@ const KEYS = {
5947
5973
  name: "nuni:name",
5948
5974
  secret: "nuni:author-secret",
5949
5975
  session: (project) => `nuni:session:${project}`,
5976
+ /** A visitor's sign-in to comment under their account. */
5977
+ commenter: (project) => `nuni:commenter:${project}`,
5950
5978
  /** Where this visitor dragged the toolbar. */
5951
5979
  position: (project) => `nuni:position:${project}`,
5952
5980
  /** The comment this visitor opened last, marked in the panel. */
@@ -6281,6 +6309,10 @@ button { cursor: pointer; }
6281
6309
  justify-content: center;
6282
6310
  flex: none;
6283
6311
  }
6312
+ .avatar { position: relative; overflow: hidden; }
6313
+ .avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
6314
+ .signed-in { display: inline-flex; flex: none; color: var(--n-fg-muted); }
6315
+ .signed-in .icon { width: 13px; height: 13px; }
6284
6316
  .author { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
6285
6317
  .meta { color: var(--n-fg-muted); font-size: 12px; white-space: nowrap; }
6286
6318
  .badge {
@@ -6509,6 +6541,10 @@ textarea.field { resize: vertical; min-height: 72px; max-height: 240px; }
6509
6541
  white-space: nowrap;
6510
6542
  }
6511
6543
  .cmp-link:hover { color: var(--n-fg); }
6544
+ .cmp-head-guest { padding-right: 116px; }
6545
+ .cmp-sign-in { position: absolute; top: 13px; right: 44px; height: 32px; display: inline-flex; align-items: center; gap: 4px; }
6546
+ .cmp-sign-in .icon { width: 13px; height: 13px; }
6547
+ .signed-in-as { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
6512
6548
  .composer .quote, .composer .suggest { margin: 6px 14px 0; }
6513
6549
  .suggest {
6514
6550
  display: grid;
@@ -6830,11 +6866,23 @@ function isTypingTarget(target) {
6830
6866
  const el = target;
6831
6867
  return el.isContentEditable || el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT";
6832
6868
  }
6833
- function avatar(name) {
6834
- return h("span", {
6869
+ /** Initials, under the account photo of an author who signed in. */
6870
+ function avatar(name, photo) {
6871
+ const el = h("span", {
6835
6872
  class: "avatar",
6836
6873
  style: `background:${colorFor(name)}`
6837
6874
  }, initials(name));
6875
+ if (photo?.startsWith("https://")) {
6876
+ const img = h("img", {
6877
+ src: photo,
6878
+ alt: "",
6879
+ loading: "lazy",
6880
+ referrerpolicy: "no-referrer",
6881
+ onerror: () => img.remove()
6882
+ });
6883
+ el.append(img);
6884
+ }
6885
+ return el;
6838
6886
  }
6839
6887
  function describeElement(el) {
6840
6888
  const tag = el.tagName.toLowerCase();
@@ -6913,6 +6961,10 @@ var NuniWidget = class {
6913
6961
  _defineProperty(this, "secretHash", "");
6914
6962
  _defineProperty(this, "ownerToken", void 0);
6915
6963
  _defineProperty(this, "ownerName", null);
6964
+ _defineProperty(this, "commenterToken", void 0);
6965
+ _defineProperty(this, "commenter", null);
6966
+ _defineProperty(this, "commenterUnsub", null);
6967
+ _defineProperty(this, "mineToken", null);
6916
6968
  _defineProperty(this, "deepLinkId", null);
6917
6969
  _defineProperty(this, "extraComment", null);
6918
6970
  _defineProperty(this, "ownerDetail", null);
@@ -7023,6 +7075,7 @@ var NuniWidget = class {
7023
7075
  }
7024
7076
  this.secret = secret;
7025
7077
  this.ownerToken = read(KEYS.session(config.project));
7078
+ this.commenterToken = read(KEYS.commenter(config.project));
7026
7079
  this.lastViewedId = read(KEYS.lastViewed(config.project));
7027
7080
  this.position = oneOf(read(KEYS.position(config.project)) ?? void 0, POSITIONS, config.position);
7028
7081
  this.host = document.createElement("div");
@@ -7100,6 +7153,7 @@ var NuniWidget = class {
7100
7153
  this.render();
7101
7154
  }));
7102
7155
  if (this.ownerToken) this.watchSession(this.ownerToken);
7156
+ if (this.commenterToken) this.watchCommenter(this.commenterToken);
7103
7157
  this.readDeepLink();
7104
7158
  this.subscribePage();
7105
7159
  this.cleanups.push(onLocationChange(() => {
@@ -7172,6 +7226,7 @@ var NuniWidget = class {
7172
7226
  this.endToolbarDrag?.();
7173
7227
  this.pageUnsub?.();
7174
7228
  this.sessionUnsub?.();
7229
+ this.commenterUnsub?.();
7175
7230
  this.ownerDetailUnsub?.();
7176
7231
  this.threadUnsub?.();
7177
7232
  this.mineUnsub?.();
@@ -7228,6 +7283,62 @@ var NuniWidget = class {
7228
7283
  this.render();
7229
7284
  });
7230
7285
  }
7286
+ watchCommenter(token) {
7287
+ this.commenterUnsub?.();
7288
+ this.commenterUnsub = this.api.onCommenter(token, (result) => {
7289
+ if (this.commenterToken !== token) return;
7290
+ if (result.valid) {
7291
+ const { name, avatarUrl, authorKey } = result;
7292
+ this.commenter = {
7293
+ name,
7294
+ avatarUrl,
7295
+ authorKey
7296
+ };
7297
+ } else this.forgetCommenter();
7298
+ this.render();
7299
+ });
7300
+ }
7301
+ /** Back to commenting as a guest in this browser. */
7302
+ forgetCommenter() {
7303
+ this.commenterUnsub?.();
7304
+ this.commenterUnsub = null;
7305
+ this.commenterToken = null;
7306
+ this.commenter = null;
7307
+ write(KEYS.commenter(this.config.project), null);
7308
+ this.syncMine();
7309
+ }
7310
+ /** Who is writing, for every comment, reply, reaction and upload. */
7311
+ get author() {
7312
+ return {
7313
+ authorSecret: this.secret,
7314
+ commenterToken: this.commenterToken ?? void 0
7315
+ };
7316
+ }
7317
+ /** The name new comments go out under. */
7318
+ get displayName() {
7319
+ return this.commenter?.name ?? this.name;
7320
+ }
7321
+ /** The key on what this visitor writes now. */
7322
+ get authorKey() {
7323
+ return this.commenter?.authorKey ?? this.secretHash;
7324
+ }
7325
+ /** Signed in or not, this browser's own guest comments stay editable. */
7326
+ ownsKey(key) {
7327
+ return Boolean(key) && (key === this.secretHash || key === this.commenter?.authorKey);
7328
+ }
7329
+ /** An author's photo and name, marked when they wrote it signed in. */
7330
+ authorLine(row) {
7331
+ return [
7332
+ avatar(row.authorName, row.signedIn ? row.avatarUrl : void 0),
7333
+ h("span", { class: "author" }, row.authorName),
7334
+ row.signedIn ? h("span", {
7335
+ class: "signed-in",
7336
+ role: "img",
7337
+ "aria-label": this.t("signedInGithub"),
7338
+ title: this.t("signedInGithub")
7339
+ }, icon(ICONS.github)) : null
7340
+ ];
7341
+ }
7231
7342
  /** Keep the owner-only details of the open thread subscribed. */
7232
7343
  syncOwnerDetail() {
7233
7344
  const token = this.ownerToken;
@@ -7265,7 +7376,7 @@ var NuniWidget = class {
7265
7376
  return Boolean(this.ownerToken && this.ownerName);
7266
7377
  }
7267
7378
  isMine(comment) {
7268
- return Boolean(this.secretHash) && comment.authorKeyHash === this.secretHash;
7379
+ return this.ownsKey(comment.authorKeyHash);
7269
7380
  }
7270
7381
  visibleComments() {
7271
7382
  return this.comments.filter((c) => c.status === (this.panelOpen && this.tab === "resolved" ? "resolved" : "open") || this.card?.kind === "thread" && this.card.id === c._id);
@@ -7692,7 +7803,7 @@ var NuniWidget = class {
7692
7803
  async attachScreenshot(id, shot) {
7693
7804
  try {
7694
7805
  const image = await shot;
7695
- if (image) await this.api.uploadScreenshot(id, this.secret, image);
7806
+ if (image) await this.api.uploadScreenshot(id, this.author, image);
7696
7807
  } catch {}
7697
7808
  }
7698
7809
  /** One after another, so they keep their order. */
@@ -7700,7 +7811,7 @@ var NuniWidget = class {
7700
7811
  const images = draft.images ?? [];
7701
7812
  revokeImages(draft);
7702
7813
  try {
7703
- for (const image of images) await this.api.uploadImage(id, this.secret, image.blob);
7814
+ for (const image of images) await this.api.uploadImage(id, this.author, image.blob);
7704
7815
  } catch (error) {
7705
7816
  this.showToast(error instanceof NuniApiError ? error.message : this.t("imageFailed"));
7706
7817
  }
@@ -7983,6 +8094,29 @@ var NuniWidget = class {
7983
8094
  url.searchParams.set("origin", location.origin);
7984
8095
  if (!window.open(url.toString(), "nuni-claim", "popup,width=480,height=680")) location.href = url.toString();
7985
8096
  }
8097
+ /** Sign in to comment under an account, in a popup on the dashboard. */
8098
+ openCommenterSignIn() {
8099
+ const url = new URL(`${this.config.appUrl}/dashboard/comment-sign-in`);
8100
+ url.searchParams.set("project", this.config.project);
8101
+ url.searchParams.set("origin", location.origin);
8102
+ if (!window.open(url.toString(), "nuni-comment-sign-in", "popup,width=480,height=680")) location.href = url.toString();
8103
+ }
8104
+ async onCommenterSignedIn(token) {
8105
+ this.commenterToken = token;
8106
+ write(KEYS.commenter(this.config.project), token);
8107
+ await this.api.linkBrowser(this.secret, token).catch(() => null);
8108
+ if (this.commenterToken !== token) return;
8109
+ this.watchCommenter(token);
8110
+ this.syncMine();
8111
+ this.showToast(this.t("commenterSignedIn"));
8112
+ }
8113
+ signOutCommenter() {
8114
+ const token = this.commenterToken;
8115
+ if (!token) return;
8116
+ this.forgetCommenter();
8117
+ this.api.signOutCommenter(token);
8118
+ this.showToast(this.t("signedOut"));
8119
+ }
7986
8120
  onMessage(e) {
7987
8121
  let appOrigin;
7988
8122
  try {
@@ -7992,7 +8126,12 @@ var NuniWidget = class {
7992
8126
  }
7993
8127
  if (e.origin !== appOrigin) return;
7994
8128
  const data = e.data;
7995
- if (!data || data.type !== "nuni:session" || data.project !== this.config.project) return;
8129
+ if (!data || data.project !== this.config.project) return;
8130
+ if (data.type === "nuni:commenter" && typeof data.token === "string" && data.token.startsWith("nuni_c_")) {
8131
+ this.onCommenterSignedIn(data.token);
8132
+ return;
8133
+ }
8134
+ if (data.type !== "nuni:session") return;
7996
8135
  if (typeof data.token !== "string" || !data.token.startsWith("nuni_s_")) return;
7997
8136
  this.ownerToken = data.token;
7998
8137
  write(KEYS.session(this.config.project), data.token);
@@ -8040,7 +8179,8 @@ var NuniWidget = class {
8040
8179
  setTimeout(() => this.scheduleLayout(), 400);
8041
8180
  }
8042
8181
  async submitDraft(draft, body, name) {
8043
- const cleanName = name.trim().slice(0, LIMITS.nameMaxLength);
8182
+ const signedIn = Boolean(this.commenter);
8183
+ const cleanName = (this.commenter?.name ?? name).trim().slice(0, LIMITS.nameMaxLength);
8044
8184
  const cleanBody = body.trim();
8045
8185
  if (!cleanName) return this.setDraftError(draft, this.t("enterName"));
8046
8186
  const before = draft.suggesting ? draft.suggestFrom : null;
@@ -8053,8 +8193,10 @@ var NuniWidget = class {
8053
8193
  } : void 0;
8054
8194
  if (!cleanBody && !suggestion) return this.setDraftError(draft, this.t("writeFirst"));
8055
8195
  if (cleanBody.length > LIMITS.bodyMaxLength) return this.setDraftError(draft, this.t("commentTooLong", { count: LIMITS.bodyMaxLength }));
8056
- this.name = cleanName;
8057
- write(KEYS.name, cleanName);
8196
+ if (!signedIn) {
8197
+ this.name = cleanName;
8198
+ write(KEYS.name, cleanName);
8199
+ }
8058
8200
  draft.sending = true;
8059
8201
  draft.error = void 0;
8060
8202
  this.render();
@@ -8065,7 +8207,7 @@ var NuniWidget = class {
8065
8207
  body: cleanBody,
8066
8208
  suggestion,
8067
8209
  authorName: cleanName,
8068
- authorSecret: this.secret,
8210
+ ...this.author,
8069
8211
  page: {
8070
8212
  ...loc,
8071
8213
  path: this.pageKey,
@@ -8174,7 +8316,7 @@ var NuniWidget = class {
8174
8316
  async deleteComment(c) {
8175
8317
  if (!await this.ask(this.t("confirmDeleteComment"), this.t("delete"))) return;
8176
8318
  const token = this.ownerToken;
8177
- const action = this.isMine(c) ? () => this.api.deleteOwn(c._id, this.secret) : token ? () => this.api.remove(c._id, token) : null;
8319
+ const action = this.isMine(c) ? () => this.api.deleteOwn(c._id, this.author) : token ? () => this.api.remove(c._id, token) : null;
8178
8320
  if (!action) return;
8179
8321
  this.act(c._id, action, this.t("deleted")).then((ok) => {
8180
8322
  if (!ok) return;
@@ -8185,12 +8327,12 @@ var NuniWidget = class {
8185
8327
  });
8186
8328
  }
8187
8329
  isMyReply(reply) {
8188
- return Boolean(this.secretHash) && reply.authorKeyHash === this.secretHash;
8330
+ return this.ownsKey(reply.authorKeyHash);
8189
8331
  }
8190
8332
  async sendReply(comment, body, name) {
8191
8333
  const card = this.card;
8192
8334
  if (card?.kind !== "thread" || card.id !== comment._id) return;
8193
- const authorName = (this.isOwner ? this.ownerName : name)?.trim().slice(0, LIMITS.nameMaxLength);
8335
+ const authorName = (this.isOwner ? this.ownerName : this.commenter?.name ?? name)?.trim().slice(0, LIMITS.nameMaxLength);
8194
8336
  const clean = body.trim();
8195
8337
  if (!clean) return;
8196
8338
  if (!authorName) {
@@ -8203,7 +8345,7 @@ var NuniWidget = class {
8203
8345
  this.render();
8204
8346
  return;
8205
8347
  }
8206
- if (!this.isOwner) {
8348
+ if (!this.isOwner && !this.commenter) {
8207
8349
  this.name = authorName;
8208
8350
  write(KEYS.name, authorName);
8209
8351
  }
@@ -8215,7 +8357,7 @@ var NuniWidget = class {
8215
8357
  commentId: comment._id,
8216
8358
  body: clean,
8217
8359
  authorName,
8218
- authorSecret: this.secret,
8360
+ ...this.author,
8219
8361
  sessionToken: this.ownerToken ?? void 0
8220
8362
  });
8221
8363
  const box = this.uiLayer.querySelector("[data-focus-key=\"reply\"]");
@@ -8227,7 +8369,7 @@ var NuniWidget = class {
8227
8369
  this.render();
8228
8370
  }
8229
8371
  toggleReaction(comment, targetId, emoji) {
8230
- this.api.toggleReaction(comment._id, targetId, emoji, this.secret).catch((error) => {
8372
+ this.api.toggleReaction(comment._id, targetId, emoji, this.author).catch((error) => {
8231
8373
  if (this.card?.kind === "thread" && this.card.id === comment._id) {
8232
8374
  this.card.error = error instanceof Error ? error.message : this.t("reactFailed");
8233
8375
  this.render();
@@ -8237,11 +8379,11 @@ var NuniWidget = class {
8237
8379
  async deleteReply(comment, reply) {
8238
8380
  if (!await this.ask(this.t("confirmDeleteReply"), this.t("delete"))) return;
8239
8381
  const token = this.ownerToken;
8240
- const action = this.isMyReply(reply) ? () => this.api.deleteReply(reply._id, this.secret) : token ? () => this.api.removeReply(reply._id, token) : null;
8382
+ const action = this.isMyReply(reply) ? () => this.api.deleteReply(reply._id, this.author) : token ? () => this.api.removeReply(reply._id, token) : null;
8241
8383
  if (action) this.act(comment._id, action, this.t("replyDeleted"));
8242
8384
  }
8243
8385
  saveReplyEdit(comment, reply, body) {
8244
- this.act(comment._id, () => this.api.editReply(reply._id, this.secret, body), this.t("saved")).then((ok) => {
8386
+ this.act(comment._id, () => this.api.editReply(reply._id, this.author, body), this.t("saved")).then((ok) => {
8245
8387
  if (ok && this.card?.kind === "thread") {
8246
8388
  this.card.replyEditing = void 0;
8247
8389
  this.render();
@@ -8249,7 +8391,7 @@ var NuniWidget = class {
8249
8391
  });
8250
8392
  }
8251
8393
  saveEdit(c, body, suggestion) {
8252
- this.act(c._id, () => this.api.editOwn(c._id, this.secret, body, suggestion), this.t("saved"));
8394
+ this.act(c._id, () => this.api.editOwn(c._id, this.author, body, suggestion), this.t("saved"));
8253
8395
  }
8254
8396
  /** For the owner: the suggested words, ready to paste into the code. */
8255
8397
  copyNewTextButton(text) {
@@ -8268,13 +8410,22 @@ var NuniWidget = class {
8268
8410
  }, 2400);
8269
8411
  this.render();
8270
8412
  }
8271
- togglePanel(open = !this.panelOpen) {
8272
- if (open === this.panelOpen) return;
8273
- this.panelOpen = open;
8274
- if (open && !this.mineUnsub) this.mineUnsub = this.api.onMine(this.secret, (mine) => {
8413
+ /** Keep Yours subscribed with the current sign-in, once the panel opened. */
8414
+ syncMine() {
8415
+ if (!this.mineUnsub && !this.panelOpen) return;
8416
+ if (this.mineUnsub && this.mineToken === this.commenterToken) return;
8417
+ this.mineUnsub?.();
8418
+ this.mine = null;
8419
+ this.mineToken = this.commenterToken;
8420
+ this.mineUnsub = this.api.onMine(this.author, (mine) => {
8275
8421
  this.mine = mine;
8276
8422
  this.render();
8277
8423
  });
8424
+ }
8425
+ togglePanel(open = !this.panelOpen) {
8426
+ if (open === this.panelOpen) return;
8427
+ this.panelOpen = open;
8428
+ if (open) this.syncMine();
8278
8429
  if (open) {
8279
8430
  this.keepFocus();
8280
8431
  this.focusNext = ".panel [role=\"tab\"][aria-selected=\"true\"]";
@@ -8370,8 +8521,8 @@ var NuniWidget = class {
8370
8521
  const pin = h("div", {
8371
8522
  class: "pin pin-draft pin-pending",
8372
8523
  "aria-hidden": "true"
8373
- }, this.name ? initials(this.name) : "+");
8374
- pin.style.background = this.name ? colorFor(this.name) : "var(--n-accent)";
8524
+ }, this.displayName ? initials(this.displayName) : "+");
8525
+ pin.style.background = this.displayName ? colorFor(this.displayName) : "var(--n-accent)";
8375
8526
  pin.style.transform = `translate(${Math.round(x)}px, ${Math.round(y)}px)`;
8376
8527
  this.pinLayer.append(pin);
8377
8528
  }
@@ -8543,10 +8694,7 @@ var NuniWidget = class {
8543
8694
  "aria-current": c._id === this.lastViewedId ? "true" : null,
8544
8695
  "data-focus-key": `item-${c._id}`,
8545
8696
  onclick: () => this.focusComment(c._id)
8546
- }, h("div", { class: "row" }, h("span", {
8547
- class: "avatar",
8548
- style: `background:${colorFor(c.authorName)}`
8549
- }, initials(c.authorName)), h("span", { class: "author" }, c.authorName), h("span", { class: "meta" }, this.i18n.timeAgo(c.createdAt) + (c.replyCount ? ` · ${this.t("replyCount", { count: c.replyCount })}` : "")), c.page.origin !== location.origin ? h("span", {
8697
+ }, h("div", { class: "row" }, ...this.authorLine(c), h("span", { class: "meta" }, this.i18n.timeAgo(c.createdAt) + (c.replyCount ? ` · ${this.t("replyCount", { count: c.replyCount })}` : "")), c.page.origin !== location.origin ? h("span", {
8550
8698
  class: "badge",
8551
8699
  title: c.page.origin
8552
8700
  }, new URL(c.page.origin).host) : null, c.resolution === "outdated" ? h("span", { class: "badge" }, this.t("outdated")) : null), ...content(c));
@@ -8633,17 +8781,25 @@ var NuniWidget = class {
8633
8781
  id: "nuni-panel-list",
8634
8782
  role: "tabpanel",
8635
8783
  "aria-labelledby": `nuni-tab-${this.tab}`
8636
- }, ...children), this.status && !this.status.claimed && !this.isOwner ? this.renderPanelFooter() : null);
8784
+ }, ...children), this.renderPanelFooter());
8637
8785
  }
8638
8786
  renderPanelFooter() {
8639
- return h("div", { class: "panel-foot" }, h("div", { class: "row" }, h("span", { class: "meta" }, this.t("ownSite")), h("span", { class: "spacer" }), h("button", {
8787
+ const claimable = this.status && !this.status.claimed && !this.isOwner;
8788
+ const commenter = this.commenter;
8789
+ if (!claimable && !commenter) return null;
8790
+ return h("div", { class: "panel-foot" }, commenter ? h("div", { class: "row" }, avatar(commenter.name, commenter.avatarUrl), h("span", { class: "meta signed-in-as" }, this.t("signedInAs", { name: commenter.name })), h("span", { class: "spacer" }), h("button", {
8791
+ class: "cmp-link",
8792
+ type: "button",
8793
+ onclick: () => this.signOutCommenter()
8794
+ }, this.t("signOut"))) : null, claimable ? h("div", { class: "row" }, h("span", { class: "meta" }, this.t("ownSite")), h("span", { class: "spacer" }), h("button", {
8640
8795
  class: "btn",
8641
8796
  type: "button",
8642
8797
  onclick: () => this.openClaim()
8643
- }, icon(ICONS.github), this.t("claim"))));
8798
+ }, icon(ICONS.github), this.t("claim"))) : null);
8644
8799
  }
8645
8800
  renderComposer(draft, cls) {
8646
- const needsName = !this.name;
8801
+ const commenter = this.commenter;
8802
+ const needsName = !commenter && !this.name;
8647
8803
  const avatar = h("span", {
8648
8804
  class: "avatar cmp-avatar",
8649
8805
  "aria-hidden": "true"
@@ -8769,7 +8925,15 @@ var NuniWidget = class {
8769
8925
  e.preventDefault();
8770
8926
  submit();
8771
8927
  }
8772
- }, h("div", { class: "cmp-head" }, avatar, needsName ? nameInput : h("span", { class: "author" }, this.name), h("span", { class: "spacer" }), needsName ? null : h("button", {
8928
+ }, commenter ? h("div", { class: "cmp-head" }, ...this.authorLine({
8929
+ authorName: commenter.name,
8930
+ avatarUrl: commenter.avatarUrl,
8931
+ signedIn: true
8932
+ }), h("span", { class: "spacer" }), h("button", {
8933
+ class: "cmp-link",
8934
+ type: "button",
8935
+ onclick: () => this.signOutCommenter()
8936
+ }, this.t("signOut"))) : h("div", { class: "cmp-head cmp-head-guest" }, avatar, needsName ? nameInput : h("span", { class: "author" }, this.name), h("span", { class: "spacer" }), needsName ? null : h("button", {
8773
8937
  class: "cmp-link",
8774
8938
  type: "button",
8775
8939
  onclick: () => {
@@ -8818,7 +8982,12 @@ var NuniWidget = class {
8818
8982
  title: this.t(draft.preparingMarkup ? "preparingScreenshot" : "markUpScreenshot"),
8819
8983
  disabled: Boolean(draft.preparingMarkup),
8820
8984
  onclick: () => void this.markUpScreenshot(draft)
8821
- }, icon(ICONS.pen)) : null, fileInput, counter, h("span", { class: "spacer" }), post), h("button", {
8985
+ }, icon(ICONS.pen)) : null, fileInput, counter, h("span", { class: "spacer" }), post), commenter ? null : h("button", {
8986
+ class: "cmp-link cmp-sign-in",
8987
+ type: "button",
8988
+ title: this.t("signInGithub"),
8989
+ onclick: () => this.openCommenterSignIn()
8990
+ }, icon(ICONS.github), this.t("signIn")), h("button", {
8822
8991
  class: "icon-btn cmp-close",
8823
8992
  type: "button",
8824
8993
  "aria-label": this.t("cancel"),
@@ -8889,7 +9058,7 @@ var NuniWidget = class {
8889
9058
  ].filter((badge) => badge !== null);
8890
9059
  const contextPending = owner && !(this.ownerDetailLoaded && this.ownerDetailId === comment._id);
8891
9060
  const resolved = comment.status === "resolved";
8892
- body = h("div", { class: "card-body msg" }, h("div", { class: "card-head" }, avatar(comment.authorName), h("span", { class: "author" }, comment.authorName), h("span", { class: "meta" }, this.i18n.timeAgo(comment.createdAt) + (comment.editedAt ? ` · ${this.t("edited")}` : "")), h("span", { class: "spacer" }), owner ? h("button", {
9061
+ body = h("div", { class: "card-body msg" }, h("div", { class: "card-head" }, ...this.authorLine(comment), h("span", { class: "meta" }, this.i18n.timeAgo(comment.createdAt) + (comment.editedAt ? ` · ${this.t("edited")}` : "")), h("span", { class: "spacer" }), owner ? h("button", {
8893
9062
  class: `icon-btn${resolved ? " is-resolved" : ""}`,
8894
9063
  type: "button",
8895
9064
  "aria-label": this.t(resolved ? "reopen" : "resolve"),
@@ -8982,7 +9151,7 @@ var NuniWidget = class {
8982
9151
  const open = card.picker === targetId;
8983
9152
  if (!withAdd && !summaries.length && !open) return null;
8984
9153
  return h("div", { class: "reactions" }, ...summaries.map((r) => {
8985
- const mine = r.authorKeyHashes.includes(this.secretHash);
9154
+ const mine = r.authorKeyHashes.includes(this.authorKey);
8986
9155
  return h("button", {
8987
9156
  class: "reaction",
8988
9157
  type: "button",
@@ -9055,7 +9224,7 @@ var NuniWidget = class {
9055
9224
  return h("div", {
9056
9225
  class: "reply msg",
9057
9226
  "data-reply": reply._id
9058
- }, h("div", { class: "card-head" }, avatar(reply.authorName), h("span", { class: "author" }, reply.authorName), reply.isOwner ? h("span", { class: "badge badge-owner" }, this.t("owner")) : null, h("span", { class: "meta" }, this.i18n.timeAgo(reply.createdAt) + (reply.editedAt ? ` · ${this.t("edited")}` : "")), h("span", { class: "spacer" }), h("span", { class: "msg-tools" }, this.reactionAdd(reply._id, "icon-btn"), mine ? h("button", {
9227
+ }, h("div", { class: "card-head" }, ...this.authorLine(reply), reply.isOwner ? h("span", { class: "badge badge-owner" }, this.t("owner")) : null, h("span", { class: "meta" }, this.i18n.timeAgo(reply.createdAt) + (reply.editedAt ? ` · ${this.t("edited")}` : "")), h("span", { class: "spacer" }), h("span", { class: "msg-tools" }, this.reactionAdd(reply._id, "icon-btn"), mine ? h("button", {
9059
9228
  class: "icon-btn",
9060
9229
  type: "button",
9061
9230
  "aria-label": this.t("editReply"),
@@ -9074,7 +9243,7 @@ var NuniWidget = class {
9074
9243
  onclick: () => void this.deleteReply(comment, reply)
9075
9244
  }, icon(ICONS.trash)) : null)), h("div", { class: "msg-main" }, h("div", { class: "comment-body" }, reply.body), this.renderReactions(comment, reply._id, false)));
9076
9245
  });
9077
- const needsName = !owner && !this.name;
9246
+ const needsName = !owner && !this.displayName;
9078
9247
  const nameInput = h("input", {
9079
9248
  class: "reply-name",
9080
9249
  placeholder: this.t("yourName"),
@@ -9167,7 +9336,7 @@ var NuniWidget = class {
9167
9336
  }
9168
9337
  focusComposer() {
9169
9338
  requestAnimationFrame(() => {
9170
- this.uiLayer.querySelector(this.name ? "[data-focus-key=\"body\"]" : "[data-focus-key=\"name\"]")?.focus();
9339
+ this.uiLayer.querySelector(this.displayName ? "[data-focus-key=\"body\"]" : "[data-focus-key=\"name\"]")?.focus();
9171
9340
  });
9172
9341
  }
9173
9342
  };