@nuniapp/widget 0.1.10 → 0.1.12

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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as isProjectId, a as initials, c as IMAGE_TYPES, d as CAPTURE_MARK, f as MASK_ATTRIBUTE, g as generateSecret, h as REACTIONS, i as icon, m as LIMITS, n as copyText, o as parseColor, p as MASK_SELECTOR, r as h, s as readableOn, t as colorFor, u as imageFromFile } from "./dom-BWcT-q7S.js";
1
+ import { _ as isProjectId, a as initials, c as IMAGE_TYPES, d as CAPTURE_MARK, f as MASK_ATTRIBUTE, g as generateSecret, h as REACTIONS, i as icon, m as LIMITS, n as copyText, o as parseColor, p as MASK_SELECTOR, r as h, s as readableOn, t as colorFor, u as imageFromFile } from "./dom-YEIbiA4X.js";
2
2
  //#region ../shared/src/path.ts
3
3
  /**
4
4
  * Normalize a URL to the key comments are matched by. Query and hash are
@@ -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.12";
180
180
  function resolveConfig(options) {
181
181
  return {
182
182
  project: options.project,
@@ -5285,12 +5285,16 @@ 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
+ /** Comments on every page, newest first, live. */
5295
+ onSite(cb) {
5296
+ return this.client.onUpdate(api.comments.listForSite, { publicId: this.config.project }, (comments) => cb(comments), () => {});
5297
+ }
5294
5298
  onPages(cb) {
5295
5299
  return this.client.onUpdate(api.comments.pagesWithComments, { publicId: this.config.project }, cb, () => {});
5296
5300
  }
@@ -5300,6 +5304,24 @@ var NuniApi = class {
5300
5304
  sessionToken
5301
5305
  }, cb, () => {});
5302
5306
  }
5307
+ /** Who a stored commenter sign-in belongs to, live (invalid once revoked). */
5308
+ onCommenter(commenterToken, cb) {
5309
+ return this.client.onUpdate(api.commenters.validate, {
5310
+ publicId: this.config.project,
5311
+ commenterToken
5312
+ }, cb, () => {});
5313
+ }
5314
+ /** Right after signing in: this browser's guest comments become the account's. */
5315
+ linkBrowser(authorSecret, commenterToken) {
5316
+ return this.run(this.client.mutation(api.commenters.linkBrowser, {
5317
+ publicId: this.config.project,
5318
+ authorSecret,
5319
+ commenterToken
5320
+ }));
5321
+ }
5322
+ signOutCommenter(commenterToken) {
5323
+ return this.client.mutation(api.commenters.signOut, { commenterToken }).catch(() => null);
5324
+ }
5303
5325
  /** A comment with the owner-only context (screenshot, console, DOM). */
5304
5326
  onOwnerComment(id, sessionToken, cb) {
5305
5327
  return this.client.onUpdate(api.comments.getForOwner, {
@@ -5360,21 +5382,22 @@ var NuniApi = class {
5360
5382
  return data.id;
5361
5383
  }
5362
5384
  /** 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);
5385
+ uploadScreenshot(id, author, image) {
5386
+ return this.upload("screenshot", id, author, image);
5365
5387
  }
5366
5388
  /** An image for everyone: picked, pasted or a marked-up screenshot. */
5367
- uploadImage(id, authorSecret, image) {
5368
- return this.upload("image", id, authorSecret, image);
5389
+ uploadImage(id, author, image) {
5390
+ return this.upload("image", id, author, image);
5369
5391
  }
5370
- async upload(kind, id, authorSecret, image) {
5392
+ async upload(kind, id, author, image) {
5371
5393
  const response = await fetch(`${this.config.convexSiteUrl}/widget/${kind}`, {
5372
5394
  method: "POST",
5373
5395
  headers: {
5374
5396
  "Content-Type": image.type,
5375
5397
  "X-Nuni-Project": this.config.project,
5376
5398
  "X-Nuni-Comment": id,
5377
- "X-Nuni-Author": authorSecret
5399
+ "X-Nuni-Author": author.authorSecret,
5400
+ ...author.commenterToken ? { "X-Nuni-Commenter": author.commenterToken } : {}
5378
5401
  },
5379
5402
  body: image
5380
5403
  });
@@ -5390,18 +5413,18 @@ var NuniApi = class {
5390
5413
  throw new NuniApiError(errorMessage(error), "error");
5391
5414
  }
5392
5415
  }
5393
- editOwn(id, authorSecret, body, suggestion) {
5416
+ editOwn(id, author, body, suggestion) {
5394
5417
  return this.run(this.client.mutation(api.comments.editOwn, {
5395
5418
  id,
5396
- authorSecret,
5419
+ ...author,
5397
5420
  body,
5398
5421
  suggestion
5399
5422
  }));
5400
5423
  }
5401
- deleteOwn(id, authorSecret) {
5424
+ deleteOwn(id, author) {
5402
5425
  return this.run(this.client.mutation(api.comments.deleteOwn, {
5403
5426
  id,
5404
- authorSecret
5427
+ ...author
5405
5428
  }));
5406
5429
  }
5407
5430
  resolve(id, sessionToken, resolution) {
@@ -5444,17 +5467,17 @@ var NuniApi = class {
5444
5467
  sessionToken
5445
5468
  }));
5446
5469
  }
5447
- editReply(id, authorSecret, body) {
5470
+ editReply(id, author, body) {
5448
5471
  return this.run(this.client.mutation(api.replies.editOwn, {
5449
5472
  id,
5450
- authorSecret,
5473
+ ...author,
5451
5474
  body
5452
5475
  }));
5453
5476
  }
5454
- deleteReply(id, authorSecret) {
5477
+ deleteReply(id, author) {
5455
5478
  return this.run(this.client.mutation(api.replies.deleteOwn, {
5456
5479
  id,
5457
- authorSecret
5480
+ ...author
5458
5481
  }));
5459
5482
  }
5460
5483
  removeReply(id, sessionToken) {
@@ -5463,13 +5486,13 @@ var NuniApi = class {
5463
5486
  sessionToken
5464
5487
  }));
5465
5488
  }
5466
- toggleReaction(commentId, targetId, emoji, authorSecret) {
5489
+ toggleReaction(commentId, targetId, emoji, author) {
5467
5490
  return this.run(this.client.mutation(api.reactions.toggle, {
5468
5491
  publicId: this.config.project,
5469
5492
  commentId,
5470
5493
  targetId,
5471
5494
  emoji,
5472
- authorSecret
5495
+ ...author
5473
5496
  }));
5474
5497
  }
5475
5498
  remove(id, sessionToken) {
@@ -5497,6 +5520,8 @@ const EN = {
5497
5520
  pickElement: "Pick an element",
5498
5521
  allComments: "All comments",
5499
5522
  openCount: "Comments on this page: {count} open",
5523
+ hideComments: "Hide comments",
5524
+ hideCommentsKey: "Hide comments (Shift+{key})",
5500
5525
  commentSelection: "Comment on the selected text",
5501
5526
  commentSelectionKey: "Comment on the selected text ({key})",
5502
5527
  pickHint: "Click anything to comment, or drag to mark an area",
@@ -5531,6 +5556,11 @@ const EN = {
5531
5556
  yourName: "Your name",
5532
5557
  leaveComment: "Leave a comment",
5533
5558
  notYou: "Not you?",
5559
+ signIn: "Sign in",
5560
+ signInGithub: "Sign in with GitHub to comment as yourself",
5561
+ signOut: "Sign out",
5562
+ signedInAs: "Signed in as {name}",
5563
+ signedInGithub: "Signed in with GitHub",
5534
5564
  cancel: "Cancel",
5535
5565
  post: "Post",
5536
5566
  posting: "Posting…",
@@ -5604,7 +5634,12 @@ const EN = {
5604
5634
  copied: "Copied for your coding agent",
5605
5635
  copyFailed: "Couldn't copy",
5606
5636
  signedIn: "You're signed in as the owner",
5637
+ commenterSignedIn: "You're signed in",
5638
+ signedOut: "Signed out",
5607
5639
  linkGone: "That comment was deleted or is on another page",
5640
+ commentsHidden: "Comments hidden",
5641
+ commentsHiddenKey: "Comments hidden. Press Shift+{key} to show them",
5642
+ commentsShown: "Comments shown",
5608
5643
  enterName: "Enter your name",
5609
5644
  writeFirst: "Write a comment first",
5610
5645
  changeTextFirst: "Change the text to suggest an edit",
@@ -5678,6 +5713,8 @@ const s = (d) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" wi
5678
5713
  const ICONS = {
5679
5714
  comment: s("<path d=\"M21 12a8 8 0 0 1-11.6 7.1L4 20l1-4.6A8 8 0 1 1 21 12z\"/>"),
5680
5715
  list: s("<path d=\"M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01\"/>"),
5716
+ eye: s("<path d=\"M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>"),
5717
+ eyeOff: s("<path d=\"M10.7 5.1A10 10 0 0 1 12 5c6.5 0 10 7 10 7a17 17 0 0 1-2.2 3.2M6.6 6.6A17 17 0 0 0 2 12s3.5 7 10 7a9.7 9.7 0 0 0 5.4-1.6M9.9 9.9a3 3 0 0 0 4.2 4.2M2 2l20 20\"/>"),
5681
5718
  close: s("<path d=\"M18 6 6 18M6 6l12 12\"/>"),
5682
5719
  check: s("<path d=\"M20 6 9 17l-5-5\"/>"),
5683
5720
  undo: s("<path d=\"M3 7v6h6\"/><path d=\"M21 17a9 9 0 0 0-15-6.7L3 13\"/>"),
@@ -5947,10 +5984,14 @@ const KEYS = {
5947
5984
  name: "nuni:name",
5948
5985
  secret: "nuni:author-secret",
5949
5986
  session: (project) => `nuni:session:${project}`,
5987
+ /** A visitor's sign-in to comment under their account. */
5988
+ commenter: (project) => `nuni:commenter:${project}`,
5950
5989
  /** Where this visitor dragged the toolbar. */
5951
5990
  position: (project) => `nuni:position:${project}`,
5952
5991
  /** The comment this visitor opened last, marked in the panel. */
5953
- lastViewed: (project) => `nuni:last-viewed:${project}`
5992
+ lastViewed: (project) => `nuni:last-viewed:${project}`,
5993
+ /** This visitor hid the pins (Shift+C). */
5994
+ hidden: (project) => `nuni:hidden:${project}`
5954
5995
  };
5955
5996
  //#endregion
5956
5997
  //#region src/styles.ts
@@ -6066,6 +6107,9 @@ button { cursor: pointer; }
6066
6107
  justify-content: center;
6067
6108
  }
6068
6109
  .tb-btn[aria-pressed="true"] .tb-count { background: rgba(255,255,255,0.25); color: inherit; }
6110
+ .tb-icon { width: 36px; padding: 0; justify-content: center; }
6111
+ /* Pins hidden: the count stays, dimmed, so the comments aren't lost. */
6112
+ .toolbar[data-pins-hidden] .tb-count { opacity: 0.55; }
6069
6113
 
6070
6114
  /* Picking */
6071
6115
  .pick-hint {
@@ -6281,6 +6325,10 @@ button { cursor: pointer; }
6281
6325
  justify-content: center;
6282
6326
  flex: none;
6283
6327
  }
6328
+ .avatar { position: relative; overflow: hidden; }
6329
+ .avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
6330
+ .signed-in { display: inline-flex; flex: none; color: var(--n-fg-muted); }
6331
+ .signed-in .icon { width: 13px; height: 13px; }
6284
6332
  .author { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
6285
6333
  .meta { color: var(--n-fg-muted); font-size: 12px; white-space: nowrap; }
6286
6334
  .badge {
@@ -6509,6 +6557,10 @@ textarea.field { resize: vertical; min-height: 72px; max-height: 240px; }
6509
6557
  white-space: nowrap;
6510
6558
  }
6511
6559
  .cmp-link:hover { color: var(--n-fg); }
6560
+ .cmp-head-guest { padding-right: 116px; }
6561
+ .cmp-sign-in { position: absolute; top: 13px; right: 44px; height: 32px; display: inline-flex; align-items: center; gap: 4px; }
6562
+ .cmp-sign-in .icon { width: 13px; height: 13px; }
6563
+ .signed-in-as { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
6512
6564
  .composer .quote, .composer .suggest { margin: 6px 14px 0; }
6513
6565
  .suggest {
6514
6566
  display: grid;
@@ -6691,6 +6743,8 @@ textarea.field { resize: vertical; min-height: 72px; max-height: 240px; }
6691
6743
  .panel-search .field { padding-left: 32px; }
6692
6744
  .item-text { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; word-break: break-word; }
6693
6745
  .section-label { padding: 12px 10px 4px; font-size: 11px; font-weight: 650; letter-spacing: 0.04em; text-transform: uppercase; color: var(--n-fg-muted); }
6746
+ /* A page's path, as written: paths are case-sensitive. */
6747
+ .section-path { text-transform: none; letter-spacing: 0; font-size: 12px; overflow-wrap: anywhere; }
6694
6748
  .empty { padding: 32px 16px; text-align: center; color: var(--n-fg-muted); }
6695
6749
  .panel-foot { padding: 12px; border-top: 1px solid var(--n-border); display: grid; gap: 8px; font-size: 13px; }
6696
6750
  .link { border: 0; background: none; padding: 0; color: var(--n-accent); font-weight: 600; text-decoration: none; }
@@ -6830,11 +6884,23 @@ function isTypingTarget(target) {
6830
6884
  const el = target;
6831
6885
  return el.isContentEditable || el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT";
6832
6886
  }
6833
- function avatar(name) {
6834
- return h("span", {
6887
+ /** Initials, under the account photo of an author who signed in. */
6888
+ function avatar(name, photo) {
6889
+ const el = h("span", {
6835
6890
  class: "avatar",
6836
6891
  style: `background:${colorFor(name)}`
6837
6892
  }, initials(name));
6893
+ if (photo?.startsWith("https://")) {
6894
+ const img = h("img", {
6895
+ src: photo,
6896
+ alt: "",
6897
+ loading: "lazy",
6898
+ referrerpolicy: "no-referrer",
6899
+ onerror: () => img.remove()
6900
+ });
6901
+ el.append(img);
6902
+ }
6903
+ return el;
6838
6904
  }
6839
6905
  function describeElement(el) {
6840
6906
  const tag = el.tagName.toLowerCase();
@@ -6894,10 +6960,13 @@ var NuniWidget = class {
6894
6960
  _defineProperty(this, "status", null);
6895
6961
  _defineProperty(this, "pages", []);
6896
6962
  _defineProperty(this, "picking", false);
6963
+ _defineProperty(this, "pinsHidden", void 0);
6897
6964
  _defineProperty(this, "panelOpen", false);
6898
6965
  _defineProperty(this, "tab", "open");
6899
6966
  _defineProperty(this, "mine", null);
6900
6967
  _defineProperty(this, "mineUnsub", null);
6968
+ _defineProperty(this, "site", null);
6969
+ _defineProperty(this, "siteUnsub", null);
6901
6970
  _defineProperty(this, "repinId", null);
6902
6971
  _defineProperty(this, "pinReported", /* @__PURE__ */ new Map());
6903
6972
  _defineProperty(this, "pinReportTimer", 0);
@@ -6913,6 +6982,10 @@ var NuniWidget = class {
6913
6982
  _defineProperty(this, "secretHash", "");
6914
6983
  _defineProperty(this, "ownerToken", void 0);
6915
6984
  _defineProperty(this, "ownerName", null);
6985
+ _defineProperty(this, "commenterToken", void 0);
6986
+ _defineProperty(this, "commenter", null);
6987
+ _defineProperty(this, "commenterUnsub", null);
6988
+ _defineProperty(this, "mineToken", null);
6916
6989
  _defineProperty(this, "deepLinkId", null);
6917
6990
  _defineProperty(this, "extraComment", null);
6918
6991
  _defineProperty(this, "ownerDetail", null);
@@ -7023,7 +7096,9 @@ var NuniWidget = class {
7023
7096
  }
7024
7097
  this.secret = secret;
7025
7098
  this.ownerToken = read(KEYS.session(config.project));
7099
+ this.commenterToken = read(KEYS.commenter(config.project));
7026
7100
  this.lastViewedId = read(KEYS.lastViewed(config.project));
7101
+ this.pinsHidden = read(KEYS.hidden(config.project)) === "1";
7027
7102
  this.position = oneOf(read(KEYS.position(config.project)) ?? void 0, POSITIONS, config.position);
7028
7103
  this.host = document.createElement("div");
7029
7104
  this.host.id = "nuni-root";
@@ -7100,6 +7175,7 @@ var NuniWidget = class {
7100
7175
  this.render();
7101
7176
  }));
7102
7177
  if (this.ownerToken) this.watchSession(this.ownerToken);
7178
+ if (this.commenterToken) this.watchCommenter(this.commenterToken);
7103
7179
  this.readDeepLink();
7104
7180
  this.subscribePage();
7105
7181
  this.cleanups.push(onLocationChange(() => {
@@ -7172,9 +7248,11 @@ var NuniWidget = class {
7172
7248
  this.endToolbarDrag?.();
7173
7249
  this.pageUnsub?.();
7174
7250
  this.sessionUnsub?.();
7251
+ this.commenterUnsub?.();
7175
7252
  this.ownerDetailUnsub?.();
7176
7253
  this.threadUnsub?.();
7177
7254
  this.mineUnsub?.();
7255
+ this.siteUnsub?.();
7178
7256
  clearTimeout(this.pinReportTimer);
7179
7257
  this.resolveRun++;
7180
7258
  for (const stop of this.scopeWatchers.values()) stop();
@@ -7228,6 +7306,62 @@ var NuniWidget = class {
7228
7306
  this.render();
7229
7307
  });
7230
7308
  }
7309
+ watchCommenter(token) {
7310
+ this.commenterUnsub?.();
7311
+ this.commenterUnsub = this.api.onCommenter(token, (result) => {
7312
+ if (this.commenterToken !== token) return;
7313
+ if (result.valid) {
7314
+ const { name, avatarUrl, authorKey } = result;
7315
+ this.commenter = {
7316
+ name,
7317
+ avatarUrl,
7318
+ authorKey
7319
+ };
7320
+ } else this.forgetCommenter();
7321
+ this.render();
7322
+ });
7323
+ }
7324
+ /** Back to commenting as a guest in this browser. */
7325
+ forgetCommenter() {
7326
+ this.commenterUnsub?.();
7327
+ this.commenterUnsub = null;
7328
+ this.commenterToken = null;
7329
+ this.commenter = null;
7330
+ write(KEYS.commenter(this.config.project), null);
7331
+ this.syncMine();
7332
+ }
7333
+ /** Who is writing, for every comment, reply, reaction and upload. */
7334
+ get author() {
7335
+ return {
7336
+ authorSecret: this.secret,
7337
+ commenterToken: this.commenterToken ?? void 0
7338
+ };
7339
+ }
7340
+ /** The name new comments go out under. */
7341
+ get displayName() {
7342
+ return this.commenter?.name ?? this.name;
7343
+ }
7344
+ /** The key on what this visitor writes now. */
7345
+ get authorKey() {
7346
+ return this.commenter?.authorKey ?? this.secretHash;
7347
+ }
7348
+ /** Signed in or not, this browser's own guest comments stay editable. */
7349
+ ownsKey(key) {
7350
+ return Boolean(key) && (key === this.secretHash || key === this.commenter?.authorKey);
7351
+ }
7352
+ /** An author's photo and name, marked when they wrote it signed in. */
7353
+ authorLine(row) {
7354
+ return [
7355
+ avatar(row.authorName, row.signedIn ? row.avatarUrl : void 0),
7356
+ h("span", { class: "author" }, row.authorName),
7357
+ row.signedIn ? h("span", {
7358
+ class: "signed-in",
7359
+ role: "img",
7360
+ "aria-label": this.t("signedInGithub"),
7361
+ title: this.t("signedInGithub")
7362
+ }, icon(ICONS.github)) : null
7363
+ ];
7364
+ }
7231
7365
  /** Keep the owner-only details of the open thread subscribed. */
7232
7366
  syncOwnerDetail() {
7233
7367
  const token = this.ownerToken;
@@ -7265,10 +7399,19 @@ var NuniWidget = class {
7265
7399
  return Boolean(this.ownerToken && this.ownerName);
7266
7400
  }
7267
7401
  isMine(comment) {
7268
- return Boolean(this.secretHash) && comment.authorKeyHash === this.secretHash;
7402
+ return this.ownsKey(comment.authorKeyHash);
7269
7403
  }
7404
+ /** The open thread keeps its pin while the rest are hidden. */
7270
7405
  visibleComments() {
7271
- return this.comments.filter((c) => c.status === (this.panelOpen && this.tab === "resolved" ? "resolved" : "open") || this.card?.kind === "thread" && this.card.id === c._id);
7406
+ return this.comments.filter((c) => !this.pinsHidden && c.status === (this.panelOpen && this.tab === "resolved" ? "resolved" : "open") || this.card?.kind === "thread" && this.card.id === c._id);
7407
+ }
7408
+ /** Hide or show the pins, like Figma's Shift+C. */
7409
+ setPinsHidden(hidden, quiet = false) {
7410
+ if (this.pinsHidden === hidden) return;
7411
+ this.pinsHidden = hidden;
7412
+ write(KEYS.hidden(this.config.project), hidden ? "1" : null);
7413
+ if (quiet) return;
7414
+ this.showToast(hidden ? this.withKey("commentsHidden", "commentsHiddenKey") : this.t("commentsShown"));
7272
7415
  }
7273
7416
  scheduleResolve() {
7274
7417
  clearTimeout(this.resolveTimer);
@@ -7515,6 +7658,7 @@ var NuniWidget = class {
7515
7658
  this.dragBox = null;
7516
7659
  if (on) {
7517
7660
  this.card = null;
7661
+ this.setPinsHidden(false, true);
7518
7662
  if (window.matchMedia(MOBILE_QUERY).matches) this.panelOpen = false;
7519
7663
  this.pickDocs = [document, ...frameDocuments(document)];
7520
7664
  for (const doc of this.pickDocs) {
@@ -7635,6 +7779,7 @@ var NuniWidget = class {
7635
7779
  this.movePin(repinId, draft);
7636
7780
  return;
7637
7781
  }
7782
+ this.setPinsHidden(false, true);
7638
7783
  if (this.config.capture.dom) try {
7639
7784
  draft.dom = domContext(element);
7640
7785
  } catch {}
@@ -7692,7 +7837,7 @@ var NuniWidget = class {
7692
7837
  async attachScreenshot(id, shot) {
7693
7838
  try {
7694
7839
  const image = await shot;
7695
- if (image) await this.api.uploadScreenshot(id, this.secret, image);
7840
+ if (image) await this.api.uploadScreenshot(id, this.author, image);
7696
7841
  } catch {}
7697
7842
  }
7698
7843
  /** One after another, so they keep their order. */
@@ -7700,7 +7845,7 @@ var NuniWidget = class {
7700
7845
  const images = draft.images ?? [];
7701
7846
  revokeImages(draft);
7702
7847
  try {
7703
- for (const image of images) await this.api.uploadImage(id, this.secret, image.blob);
7848
+ for (const image of images) await this.api.uploadImage(id, this.author, image.blob);
7704
7849
  } catch (error) {
7705
7850
  this.showToast(error instanceof NuniApiError ? error.message : this.t("imageFailed"));
7706
7851
  }
@@ -7827,7 +7972,8 @@ var NuniWidget = class {
7827
7972
  const hotkey = this.config.hotkey;
7828
7973
  if (hotkey && e.key.toLowerCase() === hotkey) {
7829
7974
  e.preventDefault();
7830
- if (!this.picking && this.pageSelection()) this.commentOnSelection();
7975
+ if (e.shiftKey && hotkey !== hotkey.toUpperCase()) this.setPinsHidden(!this.pinsHidden);
7976
+ else if (!this.picking && this.pageSelection()) this.commentOnSelection();
7831
7977
  else this.setPicking(!this.picking);
7832
7978
  }
7833
7979
  }
@@ -7983,6 +8129,29 @@ var NuniWidget = class {
7983
8129
  url.searchParams.set("origin", location.origin);
7984
8130
  if (!window.open(url.toString(), "nuni-claim", "popup,width=480,height=680")) location.href = url.toString();
7985
8131
  }
8132
+ /** Sign in to comment under an account, in a popup on the dashboard. */
8133
+ openCommenterSignIn() {
8134
+ const url = new URL(`${this.config.appUrl}/dashboard/comment-sign-in`);
8135
+ url.searchParams.set("project", this.config.project);
8136
+ url.searchParams.set("origin", location.origin);
8137
+ if (!window.open(url.toString(), "nuni-comment-sign-in", "popup,width=480,height=680")) location.href = url.toString();
8138
+ }
8139
+ async onCommenterSignedIn(token) {
8140
+ this.commenterToken = token;
8141
+ write(KEYS.commenter(this.config.project), token);
8142
+ await this.api.linkBrowser(this.secret, token).catch(() => null);
8143
+ if (this.commenterToken !== token) return;
8144
+ this.watchCommenter(token);
8145
+ this.syncMine();
8146
+ this.showToast(this.t("commenterSignedIn"));
8147
+ }
8148
+ signOutCommenter() {
8149
+ const token = this.commenterToken;
8150
+ if (!token) return;
8151
+ this.forgetCommenter();
8152
+ this.api.signOutCommenter(token);
8153
+ this.showToast(this.t("signedOut"));
8154
+ }
7986
8155
  onMessage(e) {
7987
8156
  let appOrigin;
7988
8157
  try {
@@ -7992,7 +8161,12 @@ var NuniWidget = class {
7992
8161
  }
7993
8162
  if (e.origin !== appOrigin) return;
7994
8163
  const data = e.data;
7995
- if (!data || data.type !== "nuni:session" || data.project !== this.config.project) return;
8164
+ if (!data || data.project !== this.config.project) return;
8165
+ if (data.type === "nuni:commenter" && typeof data.token === "string" && data.token.startsWith("nuni_c_")) {
8166
+ this.onCommenterSignedIn(data.token);
8167
+ return;
8168
+ }
8169
+ if (data.type !== "nuni:session") return;
7996
8170
  if (typeof data.token !== "string" || !data.token.startsWith("nuni_s_")) return;
7997
8171
  this.ownerToken = data.token;
7998
8172
  write(KEYS.session(this.config.project), data.token);
@@ -8040,7 +8214,8 @@ var NuniWidget = class {
8040
8214
  setTimeout(() => this.scheduleLayout(), 400);
8041
8215
  }
8042
8216
  async submitDraft(draft, body, name) {
8043
- const cleanName = name.trim().slice(0, LIMITS.nameMaxLength);
8217
+ const signedIn = Boolean(this.commenter);
8218
+ const cleanName = (this.commenter?.name ?? name).trim().slice(0, LIMITS.nameMaxLength);
8044
8219
  const cleanBody = body.trim();
8045
8220
  if (!cleanName) return this.setDraftError(draft, this.t("enterName"));
8046
8221
  const before = draft.suggesting ? draft.suggestFrom : null;
@@ -8053,8 +8228,10 @@ var NuniWidget = class {
8053
8228
  } : void 0;
8054
8229
  if (!cleanBody && !suggestion) return this.setDraftError(draft, this.t("writeFirst"));
8055
8230
  if (cleanBody.length > LIMITS.bodyMaxLength) return this.setDraftError(draft, this.t("commentTooLong", { count: LIMITS.bodyMaxLength }));
8056
- this.name = cleanName;
8057
- write(KEYS.name, cleanName);
8231
+ if (!signedIn) {
8232
+ this.name = cleanName;
8233
+ write(KEYS.name, cleanName);
8234
+ }
8058
8235
  draft.sending = true;
8059
8236
  draft.error = void 0;
8060
8237
  this.render();
@@ -8065,7 +8242,7 @@ var NuniWidget = class {
8065
8242
  body: cleanBody,
8066
8243
  suggestion,
8067
8244
  authorName: cleanName,
8068
- authorSecret: this.secret,
8245
+ ...this.author,
8069
8246
  page: {
8070
8247
  ...loc,
8071
8248
  path: this.pageKey,
@@ -8174,7 +8351,7 @@ var NuniWidget = class {
8174
8351
  async deleteComment(c) {
8175
8352
  if (!await this.ask(this.t("confirmDeleteComment"), this.t("delete"))) return;
8176
8353
  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;
8354
+ const action = this.isMine(c) ? () => this.api.deleteOwn(c._id, this.author) : token ? () => this.api.remove(c._id, token) : null;
8178
8355
  if (!action) return;
8179
8356
  this.act(c._id, action, this.t("deleted")).then((ok) => {
8180
8357
  if (!ok) return;
@@ -8185,12 +8362,12 @@ var NuniWidget = class {
8185
8362
  });
8186
8363
  }
8187
8364
  isMyReply(reply) {
8188
- return Boolean(this.secretHash) && reply.authorKeyHash === this.secretHash;
8365
+ return this.ownsKey(reply.authorKeyHash);
8189
8366
  }
8190
8367
  async sendReply(comment, body, name) {
8191
8368
  const card = this.card;
8192
8369
  if (card?.kind !== "thread" || card.id !== comment._id) return;
8193
- const authorName = (this.isOwner ? this.ownerName : name)?.trim().slice(0, LIMITS.nameMaxLength);
8370
+ const authorName = (this.isOwner ? this.ownerName : this.commenter?.name ?? name)?.trim().slice(0, LIMITS.nameMaxLength);
8194
8371
  const clean = body.trim();
8195
8372
  if (!clean) return;
8196
8373
  if (!authorName) {
@@ -8203,7 +8380,7 @@ var NuniWidget = class {
8203
8380
  this.render();
8204
8381
  return;
8205
8382
  }
8206
- if (!this.isOwner) {
8383
+ if (!this.isOwner && !this.commenter) {
8207
8384
  this.name = authorName;
8208
8385
  write(KEYS.name, authorName);
8209
8386
  }
@@ -8215,7 +8392,7 @@ var NuniWidget = class {
8215
8392
  commentId: comment._id,
8216
8393
  body: clean,
8217
8394
  authorName,
8218
- authorSecret: this.secret,
8395
+ ...this.author,
8219
8396
  sessionToken: this.ownerToken ?? void 0
8220
8397
  });
8221
8398
  const box = this.uiLayer.querySelector("[data-focus-key=\"reply\"]");
@@ -8227,7 +8404,7 @@ var NuniWidget = class {
8227
8404
  this.render();
8228
8405
  }
8229
8406
  toggleReaction(comment, targetId, emoji) {
8230
- this.api.toggleReaction(comment._id, targetId, emoji, this.secret).catch((error) => {
8407
+ this.api.toggleReaction(comment._id, targetId, emoji, this.author).catch((error) => {
8231
8408
  if (this.card?.kind === "thread" && this.card.id === comment._id) {
8232
8409
  this.card.error = error instanceof Error ? error.message : this.t("reactFailed");
8233
8410
  this.render();
@@ -8237,11 +8414,11 @@ var NuniWidget = class {
8237
8414
  async deleteReply(comment, reply) {
8238
8415
  if (!await this.ask(this.t("confirmDeleteReply"), this.t("delete"))) return;
8239
8416
  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;
8417
+ const action = this.isMyReply(reply) ? () => this.api.deleteReply(reply._id, this.author) : token ? () => this.api.removeReply(reply._id, token) : null;
8241
8418
  if (action) this.act(comment._id, action, this.t("replyDeleted"));
8242
8419
  }
8243
8420
  saveReplyEdit(comment, reply, body) {
8244
- this.act(comment._id, () => this.api.editReply(reply._id, this.secret, body), this.t("saved")).then((ok) => {
8421
+ this.act(comment._id, () => this.api.editReply(reply._id, this.author, body), this.t("saved")).then((ok) => {
8245
8422
  if (ok && this.card?.kind === "thread") {
8246
8423
  this.card.replyEditing = void 0;
8247
8424
  this.render();
@@ -8249,7 +8426,7 @@ var NuniWidget = class {
8249
8426
  });
8250
8427
  }
8251
8428
  saveEdit(c, body, suggestion) {
8252
- this.act(c._id, () => this.api.editOwn(c._id, this.secret, body, suggestion), this.t("saved"));
8429
+ this.act(c._id, () => this.api.editOwn(c._id, this.author, body, suggestion), this.t("saved"));
8253
8430
  }
8254
8431
  /** For the owner: the suggested words, ready to paste into the code. */
8255
8432
  copyNewTextButton(text) {
@@ -8268,11 +8445,24 @@ var NuniWidget = class {
8268
8445
  }, 2400);
8269
8446
  this.render();
8270
8447
  }
8448
+ /** Keep Yours subscribed with the current sign-in, once the panel opened. */
8449
+ syncMine() {
8450
+ if (!this.mineUnsub && !this.panelOpen) return;
8451
+ if (this.mineUnsub && this.mineToken === this.commenterToken) return;
8452
+ this.mineUnsub?.();
8453
+ this.mine = null;
8454
+ this.mineToken = this.commenterToken;
8455
+ this.mineUnsub = this.api.onMine(this.author, (mine) => {
8456
+ this.mine = mine;
8457
+ this.render();
8458
+ });
8459
+ }
8271
8460
  togglePanel(open = !this.panelOpen) {
8272
8461
  if (open === this.panelOpen) return;
8273
8462
  this.panelOpen = open;
8274
- if (open && !this.mineUnsub) this.mineUnsub = this.api.onMine(this.secret, (mine) => {
8275
- this.mine = mine;
8463
+ if (open) this.syncMine();
8464
+ if (open && !this.siteUnsub) this.siteUnsub = this.api.onSite((site) => {
8465
+ this.site = site;
8276
8466
  this.render();
8277
8467
  });
8278
8468
  if (open) {
@@ -8370,8 +8560,8 @@ var NuniWidget = class {
8370
8560
  const pin = h("div", {
8371
8561
  class: "pin pin-draft pin-pending",
8372
8562
  "aria-hidden": "true"
8373
- }, this.name ? initials(this.name) : "+");
8374
- pin.style.background = this.name ? colorFor(this.name) : "var(--n-accent)";
8563
+ }, this.displayName ? initials(this.displayName) : "+");
8564
+ pin.style.background = this.displayName ? colorFor(this.displayName) : "var(--n-accent)";
8375
8565
  pin.style.transform = `translate(${Math.round(x)}px, ${Math.round(y)}px)`;
8376
8566
  this.pinLayer.append(pin);
8377
8567
  }
@@ -8414,6 +8604,7 @@ var NuniWidget = class {
8414
8604
  role: "toolbar",
8415
8605
  "aria-label": this.t("toolbar"),
8416
8606
  "data-dragging": !!drag,
8607
+ "data-pins-hidden": this.pinsHidden,
8417
8608
  style: drag && `transform: translate(${drag.dx}px, ${drag.dy}px)`,
8418
8609
  onpointerdown: (e) => this.startToolbarDrag(e)
8419
8610
  }, h("button", {
@@ -8432,7 +8623,15 @@ var NuniWidget = class {
8432
8623
  "aria-label": this.t("openCount", { count: open }),
8433
8624
  title: this.t("allComments"),
8434
8625
  onclick: () => this.togglePanel()
8435
- }, icon(ICONS.list), h("span", { class: "tb-count" }, String(open))));
8626
+ }, icon(ICONS.list), h("span", { class: "tb-count" }, String(open))), h("button", {
8627
+ class: "tb-btn tb-icon",
8628
+ type: "button",
8629
+ "aria-pressed": String(this.pinsHidden),
8630
+ "data-focus-key": "tb-hide",
8631
+ "aria-label": this.t("hideComments"),
8632
+ title: this.withKey("hideComments", "hideCommentsKey"),
8633
+ onclick: () => this.setPinsHidden(!this.pinsHidden)
8634
+ }, icon(this.pinsHidden ? ICONS.eyeOff : ICONS.eye)));
8436
8635
  }
8437
8636
  /**
8438
8637
  * Drag the toolbar anywhere; on release it glides to the nearest corner or
@@ -8515,10 +8714,18 @@ var NuniWidget = class {
8515
8714
  const query = this.search.trim().toLocaleLowerCase();
8516
8715
  const matches = (...texts) => !query || texts.some((t) => t?.toLocaleLowerCase().includes(query));
8517
8716
  const mine = this.mine ?? [];
8518
- const list = (this.tab === "open" ? open : this.tab === "resolved" ? resolved : mine).filter((c) => matches(c.body, c.authorName, c.anchor.quote?.exact, c.suggestion?.after, this.tab === "mine" ? c.page.path : void 0));
8717
+ const list = (this.tab === "open" ? open : this.tab === "resolved" ? resolved : mine).filter((c) => matches(c.body, c.authorName, c.anchor.quote?.exact, c.suggestion?.after, c.page.path));
8519
8718
  const placed = list.filter((c) => this.placements.get(c._id)?.element);
8520
8719
  const lost = list.filter((c) => !this.placements.get(c._id)?.element);
8521
- const otherPages = this.pages.filter((p) => p.path !== this.pageKey && matches(p.path));
8720
+ const elsewhere = (this.site ?? []).filter((c) => c.page.path !== this.pageKey);
8721
+ const elsewhereOf = (status) => elsewhere.filter((c) => c.status === status);
8722
+ const groups = /* @__PURE__ */ new Map();
8723
+ for (const c of elsewhereOf(this.tab === "resolved" ? "resolved" : "open")) {
8724
+ if (!matches(c.body, c.authorName, c.anchor.quote?.exact, c.suggestion?.after, c.page.path)) continue;
8725
+ groups.set(c.page.path, [...groups.get(c.page.path) ?? [], c]);
8726
+ }
8727
+ const listed = new Set(elsewhereOf(this.tab === "resolved" ? "resolved" : "open").map((c) => c.page.path));
8728
+ const otherPages = this.pages.filter((p) => p.path !== this.pageKey && !listed.has(p.path) && matches(p.path));
8522
8729
  const content = (c) => [c.suggestion ? renderSuggestionLine(c.suggestion) : c.anchor.quote ? h("div", { class: "item-quote" }, c.anchor.quote.exact) : null, c.body ? h("div", { class: "item-text" }, c.body) : null];
8523
8730
  const mineItem = (c) => {
8524
8731
  const here = c.page.path === this.pageKey;
@@ -8537,16 +8744,15 @@ var NuniWidget = class {
8537
8744
  href
8538
8745
  }, h("div", { class: "row" }, h("span", { class: "author" }, here ? this.t("thisPage") : c.page.path), h("span", { class: "meta" }, this.i18n.timeAgo(c.createdAt)), h("span", { class: "spacer" }), h("span", { class: resolvedBadge ? "badge badge-ok" : "badge" }, resolvedBadge ? icon(ICONS.check) : null, this.t(!resolvedBadge ? "statusOpen" : c.resolution === "outdated" ? "outdated" : "resolved"))), ...content(c));
8539
8746
  };
8540
- const item = (c) => h("button", {
8747
+ const item = (c, href) => h(href === void 0 ? "button" : href === null ? "div" : "a", {
8541
8748
  class: "item",
8542
- type: "button",
8543
8749
  "aria-current": c._id === this.lastViewedId ? "true" : null,
8544
8750
  "data-focus-key": `item-${c._id}`,
8545
- 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", {
8751
+ ...href === void 0 ? {
8752
+ type: "button",
8753
+ onclick: () => this.focusComment(c._id)
8754
+ } : { href }
8755
+ }, 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
8756
  class: "badge",
8551
8757
  title: c.page.origin
8552
8758
  }, new URL(c.page.origin).host) : null, c.resolution === "outdated" ? h("span", { class: "badge" }, this.t("outdated")) : null), ...content(c));
@@ -8556,14 +8762,20 @@ var NuniWidget = class {
8556
8762
  else if (!list.length) children.push(h("div", { class: "empty" }, query ? this.t("noMatches", { query: this.search.trim() }) : this.t("noYours")));
8557
8763
  else children.push(...list.map(mineItem));
8558
8764
  } else if (!this.loaded) children.push(h("div", { class: "empty" }, this.t("loading")));
8559
- else if (!list.length && query) children.push(h("div", { class: "empty" }, this.t("noMatches", { query: this.search.trim() })));
8560
- else if (!list.length) children.push(h("div", { class: "empty" }, this.tab === "open" ? this.withKey("noOpen", "noOpenKey") : this.t("noResolved")));
8561
8765
  else {
8562
- children.push(...placed.map(item));
8563
- if (lost.length) {
8564
- children.push(h("div", { class: "section-label" }, this.t("notFoundHere")));
8565
- children.push(...lost.map(item));
8766
+ if ((groups.size > 0 || otherPages.length > 0) && (list.length || !query)) children.push(h("div", { class: "section-label" }, this.t("thisPage")));
8767
+ if (list.length) {
8768
+ children.push(...placed.map((c) => item(c)));
8769
+ if (lost.length) {
8770
+ children.push(h("div", { class: "section-label" }, this.t("notFoundHere")));
8771
+ children.push(...lost.map((c) => item(c)));
8772
+ }
8773
+ } else if (!query) children.push(h("div", { class: "empty" }, this.tab === "open" ? this.withKey("noOpen", "noOpenKey") : this.t("noResolved")));
8774
+ for (const [path, comments] of groups) {
8775
+ children.push(h("div", { class: "section-label section-path" }, path));
8776
+ children.push(...comments.map((c) => item(c, commentHref(c, location))));
8566
8777
  }
8778
+ if (!children.length && !otherPages.length) children.push(h("div", { class: "empty" }, this.t("noMatches", { query: this.search.trim() })));
8567
8779
  }
8568
8780
  if (otherPages.length && this.tab !== "mine") {
8569
8781
  children.push(h("div", { class: "section-label" }, this.t("otherPages")));
@@ -8584,7 +8796,7 @@ var NuniWidget = class {
8584
8796
  type: "button",
8585
8797
  "aria-label": this.t("close"),
8586
8798
  onclick: () => this.togglePanel(false)
8587
- }, icon(ICONS.close))), this.comments.length || this.mine?.length ? h("div", { class: "panel-search" }, icon(ICONS.search), h("input", {
8799
+ }, icon(ICONS.close))), this.comments.length || this.mine?.length || this.site?.length ? h("div", { class: "panel-search" }, icon(ICONS.search), h("input", {
8588
8800
  class: "field",
8589
8801
  type: "search",
8590
8802
  value: this.search,
@@ -8628,22 +8840,30 @@ var NuniWidget = class {
8628
8840
  this.tab = tab;
8629
8841
  this.render();
8630
8842
  }
8631
- }, tab === "open" ? this.t("tabOpen", { count: open.length }) : tab === "resolved" ? this.t("tabResolved", { count: resolved.length }) : this.mine ? this.t("tabYours", { count: this.mine.length }) : this.t("yours")))), h("div", {
8843
+ }, tab === "open" ? this.t("tabOpen", { count: open.length + elsewhereOf("open").length }) : tab === "resolved" ? this.t("tabResolved", { count: resolved.length + elsewhereOf("resolved").length }) : this.mine ? this.t("tabYours", { count: this.mine.length }) : this.t("yours")))), h("div", {
8632
8844
  class: "panel-list",
8633
8845
  id: "nuni-panel-list",
8634
8846
  role: "tabpanel",
8635
8847
  "aria-labelledby": `nuni-tab-${this.tab}`
8636
- }, ...children), this.status && !this.status.claimed && !this.isOwner ? this.renderPanelFooter() : null);
8848
+ }, ...children), this.renderPanelFooter());
8637
8849
  }
8638
8850
  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", {
8851
+ const claimable = this.status && !this.status.claimed && !this.isOwner;
8852
+ const commenter = this.commenter;
8853
+ if (!claimable && !commenter) return null;
8854
+ 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", {
8855
+ class: "cmp-link",
8856
+ type: "button",
8857
+ onclick: () => this.signOutCommenter()
8858
+ }, this.t("signOut"))) : null, claimable ? h("div", { class: "row" }, h("span", { class: "meta" }, this.t("ownSite")), h("span", { class: "spacer" }), h("button", {
8640
8859
  class: "btn",
8641
8860
  type: "button",
8642
8861
  onclick: () => this.openClaim()
8643
- }, icon(ICONS.github), this.t("claim"))));
8862
+ }, icon(ICONS.github), this.t("claim"))) : null);
8644
8863
  }
8645
8864
  renderComposer(draft, cls) {
8646
- const needsName = !this.name;
8865
+ const commenter = this.commenter;
8866
+ const needsName = !commenter && !this.name;
8647
8867
  const avatar = h("span", {
8648
8868
  class: "avatar cmp-avatar",
8649
8869
  "aria-hidden": "true"
@@ -8769,7 +8989,15 @@ var NuniWidget = class {
8769
8989
  e.preventDefault();
8770
8990
  submit();
8771
8991
  }
8772
- }, h("div", { class: "cmp-head" }, avatar, needsName ? nameInput : h("span", { class: "author" }, this.name), h("span", { class: "spacer" }), needsName ? null : h("button", {
8992
+ }, commenter ? h("div", { class: "cmp-head" }, ...this.authorLine({
8993
+ authorName: commenter.name,
8994
+ avatarUrl: commenter.avatarUrl,
8995
+ signedIn: true
8996
+ }), h("span", { class: "spacer" }), h("button", {
8997
+ class: "cmp-link",
8998
+ type: "button",
8999
+ onclick: () => this.signOutCommenter()
9000
+ }, 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
9001
  class: "cmp-link",
8774
9002
  type: "button",
8775
9003
  onclick: () => {
@@ -8818,7 +9046,12 @@ var NuniWidget = class {
8818
9046
  title: this.t(draft.preparingMarkup ? "preparingScreenshot" : "markUpScreenshot"),
8819
9047
  disabled: Boolean(draft.preparingMarkup),
8820
9048
  onclick: () => void this.markUpScreenshot(draft)
8821
- }, icon(ICONS.pen)) : null, fileInput, counter, h("span", { class: "spacer" }), post), h("button", {
9049
+ }, icon(ICONS.pen)) : null, fileInput, counter, h("span", { class: "spacer" }), post), commenter ? null : h("button", {
9050
+ class: "cmp-link cmp-sign-in",
9051
+ type: "button",
9052
+ title: this.t("signInGithub"),
9053
+ onclick: () => this.openCommenterSignIn()
9054
+ }, icon(ICONS.github), this.t("signIn")), h("button", {
8822
9055
  class: "icon-btn cmp-close",
8823
9056
  type: "button",
8824
9057
  "aria-label": this.t("cancel"),
@@ -8889,7 +9122,7 @@ var NuniWidget = class {
8889
9122
  ].filter((badge) => badge !== null);
8890
9123
  const contextPending = owner && !(this.ownerDetailLoaded && this.ownerDetailId === comment._id);
8891
9124
  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", {
9125
+ 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
9126
  class: `icon-btn${resolved ? " is-resolved" : ""}`,
8894
9127
  type: "button",
8895
9128
  "aria-label": this.t(resolved ? "reopen" : "resolve"),
@@ -8982,7 +9215,7 @@ var NuniWidget = class {
8982
9215
  const open = card.picker === targetId;
8983
9216
  if (!withAdd && !summaries.length && !open) return null;
8984
9217
  return h("div", { class: "reactions" }, ...summaries.map((r) => {
8985
- const mine = r.authorKeyHashes.includes(this.secretHash);
9218
+ const mine = r.authorKeyHashes.includes(this.authorKey);
8986
9219
  return h("button", {
8987
9220
  class: "reaction",
8988
9221
  type: "button",
@@ -9055,7 +9288,7 @@ var NuniWidget = class {
9055
9288
  return h("div", {
9056
9289
  class: "reply msg",
9057
9290
  "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", {
9291
+ }, 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
9292
  class: "icon-btn",
9060
9293
  type: "button",
9061
9294
  "aria-label": this.t("editReply"),
@@ -9074,7 +9307,7 @@ var NuniWidget = class {
9074
9307
  onclick: () => void this.deleteReply(comment, reply)
9075
9308
  }, icon(ICONS.trash)) : null)), h("div", { class: "msg-main" }, h("div", { class: "comment-body" }, reply.body), this.renderReactions(comment, reply._id, false)));
9076
9309
  });
9077
- const needsName = !owner && !this.name;
9310
+ const needsName = !owner && !this.displayName;
9078
9311
  const nameInput = h("input", {
9079
9312
  class: "reply-name",
9080
9313
  placeholder: this.t("yourName"),
@@ -9167,7 +9400,7 @@ var NuniWidget = class {
9167
9400
  }
9168
9401
  focusComposer() {
9169
9402
  requestAnimationFrame(() => {
9170
- this.uiLayer.querySelector(this.name ? "[data-focus-key=\"body\"]" : "[data-focus-key=\"name\"]")?.focus();
9403
+ this.uiLayer.querySelector(this.displayName ? "[data-focus-key=\"body\"]" : "[data-focus-key=\"name\"]")?.focus();
9171
9404
  });
9172
9405
  }
9173
9406
  };
@@ -9269,7 +9502,7 @@ function unmount(state) {
9269
9502
  * down the widget it was created for.
9270
9503
  */
9271
9504
  function init(options) {
9272
- return mount(options, { loadScreenshot: () => import("./screenshot-tools-Dk-xNV0c.js").then((m) => m.screenshotTools) });
9505
+ return mount(options, { loadScreenshot: () => import("./screenshot-tools-BFj-93o4.js").then((m) => m.screenshotTools) });
9273
9506
  }
9274
9507
  //#endregion
9275
9508
  export { VERSION, init, EN as messages };