@nuniapp/widget 0.1.5 → 0.1.7
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 +4 -3
- package/dist/index.js +533 -246
- package/dist/nuni.global.js +233 -60
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -165,7 +165,7 @@ function buildCommentPrompt(comment, options = {}) {
|
|
|
165
165
|
}
|
|
166
166
|
//#endregion
|
|
167
167
|
//#region src/config.ts
|
|
168
|
-
const VERSION = "0.1.
|
|
168
|
+
const VERSION = "0.1.7";
|
|
169
169
|
function resolveConfig(options) {
|
|
170
170
|
return {
|
|
171
171
|
project: options.project,
|
|
@@ -5567,14 +5567,15 @@ const EN = {
|
|
|
5567
5567
|
yourName: "Your name",
|
|
5568
5568
|
leaveComment: "Leave a comment",
|
|
5569
5569
|
notYou: "Not you?",
|
|
5570
|
-
screenshotAlt: "Screenshot that will be attached",
|
|
5571
|
-
screenshotNote: "Screenshot attached. Only the site owner sees it.",
|
|
5572
|
-
remove: "Remove",
|
|
5573
5570
|
cancel: "Cancel",
|
|
5574
5571
|
post: "Post",
|
|
5575
5572
|
posting: "Posting…",
|
|
5576
5573
|
save: "Save",
|
|
5577
5574
|
send: "{key} + Enter",
|
|
5575
|
+
charsLeft: {
|
|
5576
|
+
one: "{count} character left",
|
|
5577
|
+
other: "{count} characters left"
|
|
5578
|
+
},
|
|
5578
5579
|
pinLabel: "Comment by {name}: {body}",
|
|
5579
5580
|
commentBy: "Comment by {name}",
|
|
5580
5581
|
openScreenshot: "Open the screenshot",
|
|
@@ -5687,6 +5688,8 @@ const ICONS = {
|
|
|
5687
5688
|
send: s("<path d=\"m22 2-7 20-4-9-9-4z\"/><path d=\"M22 2 11 13\"/>"),
|
|
5688
5689
|
bot: s("<path d=\"M12 8V4H8\"/><rect width=\"16\" height=\"12\" x=\"4\" y=\"8\" rx=\"2\"/><path d=\"M2 14h2M20 14h2M15 13v2M9 13v2\"/>"),
|
|
5689
5690
|
smile: s("<circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M8 14s1.5 2 4 2 4-2 4-2M9 9h.01M15 9h.01\"/>"),
|
|
5691
|
+
user: s("<circle cx=\"12\" cy=\"8\" r=\"4\"/><path d=\"M4 21a8 8 0 0 1 16 0\"/>"),
|
|
5692
|
+
arrowUp: s("<path d=\"M12 19V5M5 12l7-7 7 7\"/>"),
|
|
5690
5693
|
target: s("<circle cx=\"12\" cy=\"12\" r=\"9\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>"),
|
|
5691
5694
|
github: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M12 .5a11.5 11.5 0 0 0-3.6 22.4c.6.1.8-.3.8-.6v-2c-3.2.7-3.9-1.5-3.9-1.5-.5-1.3-1.3-1.7-1.3-1.7-1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.7-1.6-2.6-.3-5.3-1.3-5.3-5.7 0-1.3.5-2.3 1.2-3.1-.1-.3-.5-1.5.1-3.1 0 0 1-.3 3.2 1.2a11 11 0 0 1 5.8 0c2.2-1.5 3.2-1.2 3.2-1.2.6 1.6.2 2.8.1 3.1.8.8 1.2 1.9 1.2 3.1 0 4.4-2.7 5.4-5.3 5.7.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A11.5 11.5 0 0 0 12 .5z"/></svg>`,
|
|
5692
5695
|
link: s("<path d=\"M15 3h6v6M10 14 21 3M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"/>")
|
|
@@ -5869,6 +5872,57 @@ function sha256(data) {
|
|
|
5869
5872
|
return out;
|
|
5870
5873
|
}
|
|
5871
5874
|
//#endregion
|
|
5875
|
+
//#region src/scroll-to-pin.ts
|
|
5876
|
+
/** Reveal the pin itself, including scroll containers and same-origin frames. */
|
|
5877
|
+
function scrollToPin(element, point) {
|
|
5878
|
+
element.scrollIntoView({
|
|
5879
|
+
block: "center",
|
|
5880
|
+
inline: "center",
|
|
5881
|
+
behavior: "instant"
|
|
5882
|
+
});
|
|
5883
|
+
let node = element;
|
|
5884
|
+
while (node) {
|
|
5885
|
+
const parent = node.parentElement;
|
|
5886
|
+
if (parent) {
|
|
5887
|
+
const style = parent.ownerDocument.defaultView?.getComputedStyle(parent);
|
|
5888
|
+
const scrollX = style && /auto|scroll|hidden/.test(style.overflowX);
|
|
5889
|
+
const scrollY = style && /auto|scroll|hidden/.test(style.overflowY);
|
|
5890
|
+
if (parent !== parent.ownerDocument.scrollingElement && (scrollX || scrollY)) {
|
|
5891
|
+
const rect = viewportRect(parent, document);
|
|
5892
|
+
const pin = point();
|
|
5893
|
+
parent.scrollBy({
|
|
5894
|
+
left: scrollX ? pin.x - rect.left - parent.clientLeft - parent.clientWidth / 2 : 0,
|
|
5895
|
+
top: scrollY ? pin.y - rect.top - parent.clientTop - parent.clientHeight / 2 : 0,
|
|
5896
|
+
behavior: "instant"
|
|
5897
|
+
});
|
|
5898
|
+
}
|
|
5899
|
+
node = parent;
|
|
5900
|
+
continue;
|
|
5901
|
+
}
|
|
5902
|
+
const root = node.getRootNode();
|
|
5903
|
+
if (isShadowRoot(root)) {
|
|
5904
|
+
node = root.host;
|
|
5905
|
+
continue;
|
|
5906
|
+
}
|
|
5907
|
+
const doc = node.ownerDocument;
|
|
5908
|
+
const view = doc.defaultView;
|
|
5909
|
+
const frame = frameElementOf(doc);
|
|
5910
|
+
if (view) {
|
|
5911
|
+
const pin = point();
|
|
5912
|
+
const rect = frame ? viewportRect(frame, document) : {
|
|
5913
|
+
left: 0,
|
|
5914
|
+
top: 0
|
|
5915
|
+
};
|
|
5916
|
+
view.scrollBy({
|
|
5917
|
+
left: pin.x - rect.left - (frame?.clientLeft ?? 0) - view.innerWidth / 2,
|
|
5918
|
+
top: pin.y - rect.top - (frame?.clientTop ?? 0) - view.innerHeight / 2,
|
|
5919
|
+
behavior: "instant"
|
|
5920
|
+
});
|
|
5921
|
+
}
|
|
5922
|
+
node = frame;
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5925
|
+
//#endregion
|
|
5872
5926
|
//#region src/storage.ts
|
|
5873
5927
|
/** localStorage that never throws (private mode, blocked storage, sandboxed iframes). */
|
|
5874
5928
|
const memory = /* @__PURE__ */ new Map();
|
|
@@ -6175,59 +6229,34 @@ button { cursor: pointer; }
|
|
|
6175
6229
|
/* Cards (composer + thread) */
|
|
6176
6230
|
.card {
|
|
6177
6231
|
position: fixed;
|
|
6178
|
-
width:
|
|
6232
|
+
width: 340px;
|
|
6179
6233
|
max-width: calc(100vw - 24px);
|
|
6180
6234
|
background: var(--n-bg);
|
|
6181
6235
|
color: var(--n-fg);
|
|
6182
6236
|
border: 1px solid var(--n-border);
|
|
6183
|
-
border-radius:
|
|
6237
|
+
border-radius: 14px;
|
|
6184
6238
|
box-shadow: var(--n-shadow);
|
|
6185
6239
|
pointer-events: auto;
|
|
6186
6240
|
overflow: hidden;
|
|
6187
6241
|
}
|
|
6188
|
-
.card-body { padding: 12px; display: grid; gap: 10px; }
|
|
6242
|
+
.card-body { padding: 12px 14px; display: grid; gap: 10px; }
|
|
6189
6243
|
.card[data-card="thread"] { display: flex; flex-direction: column; max-height: calc(100vh - 24px); }
|
|
6190
|
-
.
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
.reply-form .field { flex: 1 1 100%; }
|
|
6197
|
-
/* A fixed size: growing on focus would move the Send button mid-click. */
|
|
6198
|
-
.reply-form .field-reply { flex: 1 1 0; min-height: 56px; height: 56px; resize: none; }
|
|
6199
|
-
.reactions { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; position: relative; }
|
|
6200
|
-
.reaction {
|
|
6201
|
-
display: inline-flex;
|
|
6202
|
-
align-items: center;
|
|
6203
|
-
gap: 4px;
|
|
6204
|
-
height: 24px;
|
|
6205
|
-
padding: 0 8px;
|
|
6206
|
-
border: 1px solid var(--n-border);
|
|
6207
|
-
border-radius: 999px;
|
|
6208
|
-
background: var(--n-bg);
|
|
6244
|
+
.card .quote {
|
|
6245
|
+
margin: 0;
|
|
6246
|
+
padding: 6px 10px;
|
|
6247
|
+
border-left: 0;
|
|
6248
|
+
border-radius: 8px;
|
|
6249
|
+
background: var(--n-accent-soft);
|
|
6209
6250
|
color: var(--n-fg);
|
|
6210
|
-
font-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
.reaction[aria-pressed="true"] { border-color: var(--n-accent); background: var(--n-accent-soft); }
|
|
6214
|
-
.reaction-add { color: var(--n-fg-muted); padding: 0 6px; }
|
|
6215
|
-
.reaction-picker {
|
|
6216
|
-
display: flex;
|
|
6217
|
-
gap: 2px;
|
|
6218
|
-
padding: 4px;
|
|
6219
|
-
border: 1px solid var(--n-border);
|
|
6220
|
-
border-radius: 999px;
|
|
6221
|
-
background: var(--n-bg);
|
|
6222
|
-
box-shadow: var(--n-shadow);
|
|
6251
|
+
font-style: normal;
|
|
6252
|
+
font-size: 13px;
|
|
6253
|
+
box-shadow: inset 2px 0 0 var(--n-accent);
|
|
6223
6254
|
}
|
|
6224
|
-
.
|
|
6225
|
-
.btn-xs { width: 24px; height: 24px; }
|
|
6226
|
-
.card-head { display: flex; align-items: center; gap: 8px; }
|
|
6255
|
+
.card-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
6227
6256
|
.card-head .spacer { flex: 1; }
|
|
6228
6257
|
.avatar {
|
|
6229
|
-
width:
|
|
6230
|
-
height:
|
|
6258
|
+
width: 26px;
|
|
6259
|
+
height: 26px;
|
|
6231
6260
|
border-radius: 999px;
|
|
6232
6261
|
color: #fff;
|
|
6233
6262
|
font-size: 10px;
|
|
@@ -6237,8 +6266,8 @@ button { cursor: pointer; }
|
|
|
6237
6266
|
justify-content: center;
|
|
6238
6267
|
flex: none;
|
|
6239
6268
|
}
|
|
6240
|
-
.author { font-weight: 600; }
|
|
6241
|
-
.meta { color: var(--n-fg-muted); font-size: 12px; }
|
|
6269
|
+
.author { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
6270
|
+
.meta { color: var(--n-fg-muted); font-size: 12px; white-space: nowrap; }
|
|
6242
6271
|
.badge {
|
|
6243
6272
|
display: inline-flex;
|
|
6244
6273
|
align-items: center;
|
|
@@ -6253,28 +6282,147 @@ button { cursor: pointer; }
|
|
|
6253
6282
|
text-overflow: ellipsis;
|
|
6254
6283
|
white-space: nowrap;
|
|
6255
6284
|
}
|
|
6285
|
+
.badge .icon { width: 12px; height: 12px; }
|
|
6256
6286
|
.badge-ok { background: rgba(31,157,85,0.14); color: var(--n-ok); }
|
|
6257
|
-
.
|
|
6287
|
+
.badge-owner { background: var(--n-accent-soft); color: var(--n-accent); }
|
|
6288
|
+
.comment-body { white-space: pre-wrap; word-break: break-word; line-height: 1.5; }
|
|
6258
6289
|
.shot {
|
|
6259
6290
|
display: block;
|
|
6260
6291
|
border: 1px solid var(--n-border);
|
|
6261
|
-
border-radius:
|
|
6292
|
+
border-radius: 10px;
|
|
6262
6293
|
overflow: hidden;
|
|
6263
6294
|
background: var(--n-bg-subtle);
|
|
6264
6295
|
}
|
|
6265
6296
|
.shot img { display: block; width: 100%; max-height: 160px; object-fit: cover; object-position: top; }
|
|
6266
|
-
.
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
width:
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
border: 1px solid var(--n-border);
|
|
6297
|
+
.icon-btn {
|
|
6298
|
+
display: inline-grid;
|
|
6299
|
+
place-items: center;
|
|
6300
|
+
width: 28px;
|
|
6301
|
+
height: 28px;
|
|
6302
|
+
padding: 0;
|
|
6303
|
+
border: 0;
|
|
6274
6304
|
border-radius: 8px;
|
|
6275
|
-
background:
|
|
6305
|
+
background: none;
|
|
6306
|
+
color: var(--n-fg-muted);
|
|
6307
|
+
flex: none;
|
|
6308
|
+
transition: background 120ms ease, color 120ms ease;
|
|
6309
|
+
}
|
|
6310
|
+
.icon-btn:hover { background: var(--n-bg-subtle); color: var(--n-fg); }
|
|
6311
|
+
.icon-btn:disabled { opacity: 0.4; cursor: default; background: none; }
|
|
6312
|
+
.icon-btn-danger:hover { background: color-mix(in srgb, var(--n-danger) 12%, transparent); color: var(--n-danger); }
|
|
6313
|
+
.icon-btn.is-resolved, .icon-btn.is-resolved:hover { background: rgba(31,157,85,0.14); color: var(--n-ok); }
|
|
6314
|
+
|
|
6315
|
+
/* A message (the comment or a reply): text lines up with the name. */
|
|
6316
|
+
.msg { gap: 2px; padding: 12px 8px 8px 14px; }
|
|
6317
|
+
.msg-main { display: grid; gap: 8px; padding: 0 6px 0 34px; min-width: 0; }
|
|
6318
|
+
.msg .card-head { min-height: 28px; }
|
|
6319
|
+
.badges { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
6320
|
+
.msg-foot { display: flex; align-items: center; gap: 2px; margin-right: -6px; }
|
|
6321
|
+
.msg-foot .spacer { flex: 1; }
|
|
6322
|
+
/* A reply's tools float over its header on hover, so names never truncate. */
|
|
6323
|
+
.msg-tools:empty { display: none; }
|
|
6324
|
+
.msg-tools {
|
|
6325
|
+
position: absolute;
|
|
6326
|
+
top: 4px;
|
|
6327
|
+
right: 8px;
|
|
6328
|
+
display: flex;
|
|
6329
|
+
gap: 2px;
|
|
6330
|
+
padding: 2px;
|
|
6331
|
+
border: 1px solid var(--n-border);
|
|
6332
|
+
border-radius: 10px;
|
|
6333
|
+
background: var(--n-bg);
|
|
6334
|
+
box-shadow: 0 1px 2px rgba(0,0,0,0.06), 0 4px 12px rgba(0,0,0,0.08);
|
|
6335
|
+
opacity: 0;
|
|
6336
|
+
pointer-events: none;
|
|
6337
|
+
transition: opacity 120ms ease;
|
|
6338
|
+
}
|
|
6339
|
+
.reply:hover .msg-tools, .reply:focus-within .msg-tools { opacity: 1; pointer-events: auto; }
|
|
6340
|
+
@media (hover: none) {
|
|
6341
|
+
.msg-tools { position: static; opacity: 1; pointer-events: auto; padding: 0; border: 0; box-shadow: none; background: none; }
|
|
6342
|
+
}
|
|
6343
|
+
|
|
6344
|
+
/* Replies */
|
|
6345
|
+
.thread { border-top: 1px solid var(--n-border); display: grid; min-height: 0; }
|
|
6346
|
+
.replies { display: grid; padding: 4px 0; overflow-y: auto; max-height: 45vh; }
|
|
6347
|
+
.reply.msg { position: relative; display: grid; padding: 8px 8px 8px 14px; }
|
|
6348
|
+
.reply-form {
|
|
6349
|
+
display: grid;
|
|
6350
|
+
margin: 4px 12px 12px;
|
|
6351
|
+
border: 1px solid var(--n-border);
|
|
6352
|
+
border-radius: 12px;
|
|
6353
|
+
background: var(--n-bg);
|
|
6354
|
+
transition: box-shadow 140ms ease, border-color 140ms ease;
|
|
6355
|
+
}
|
|
6356
|
+
.reply-form:focus-within {
|
|
6357
|
+
border-color: color-mix(in srgb, var(--n-accent) 45%, var(--n-border));
|
|
6358
|
+
box-shadow: 0 0 0 3px var(--n-accent-soft);
|
|
6359
|
+
}
|
|
6360
|
+
.reply-name {
|
|
6361
|
+
padding: 8px 12px 6px;
|
|
6362
|
+
border: 0;
|
|
6363
|
+
border-bottom: 1px solid var(--n-border);
|
|
6364
|
+
background: none;
|
|
6365
|
+
outline: none;
|
|
6366
|
+
font-weight: 600;
|
|
6367
|
+
}
|
|
6368
|
+
.reply-row { display: flex; align-items: flex-end; gap: 4px; padding: 4px; }
|
|
6369
|
+
.reply-text {
|
|
6370
|
+
flex: 1;
|
|
6371
|
+
min-width: 0;
|
|
6372
|
+
min-height: 32px;
|
|
6373
|
+
max-height: 160px;
|
|
6374
|
+
padding: 6px 8px;
|
|
6375
|
+
border: 0;
|
|
6376
|
+
background: none;
|
|
6377
|
+
outline: none;
|
|
6378
|
+
resize: none;
|
|
6379
|
+
line-height: 1.4;
|
|
6380
|
+
}
|
|
6381
|
+
.reply-name::placeholder, .reply-text::placeholder { color: var(--n-fg-muted); opacity: 1; font-weight: 400; }
|
|
6382
|
+
.reply-send {
|
|
6383
|
+
display: inline-grid;
|
|
6384
|
+
place-items: center;
|
|
6385
|
+
width: 32px;
|
|
6386
|
+
height: 32px;
|
|
6387
|
+
border: 0;
|
|
6388
|
+
border-radius: 999px;
|
|
6389
|
+
background: var(--n-accent);
|
|
6390
|
+
color: var(--n-accent-fg);
|
|
6391
|
+
flex: none;
|
|
6392
|
+
}
|
|
6393
|
+
.reply-send:hover { filter: brightness(1.08); }
|
|
6394
|
+
.reply-send:disabled { opacity: 0.4; cursor: default; filter: none; }
|
|
6395
|
+
|
|
6396
|
+
/* Reactions */
|
|
6397
|
+
.reactions { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; position: relative; }
|
|
6398
|
+
.reaction {
|
|
6399
|
+
display: inline-flex;
|
|
6400
|
+
align-items: center;
|
|
6401
|
+
gap: 4px;
|
|
6402
|
+
height: 26px;
|
|
6403
|
+
padding: 0 8px;
|
|
6404
|
+
border: 1px solid var(--n-border);
|
|
6405
|
+
border-radius: 999px;
|
|
6406
|
+
background: var(--n-bg);
|
|
6407
|
+
color: var(--n-fg);
|
|
6408
|
+
font-size: 12px;
|
|
6409
|
+
}
|
|
6410
|
+
.reaction:hover { background: var(--n-bg-subtle); }
|
|
6411
|
+
.reaction[aria-pressed="true"] { border-color: color-mix(in srgb, var(--n-accent) 50%, transparent); background: var(--n-accent-soft); }
|
|
6412
|
+
.reaction-add { color: var(--n-fg-muted); padding: 0 6px; border-color: transparent; }
|
|
6413
|
+
.reaction-add:hover, .reaction-add[aria-expanded="true"] { color: var(--n-fg); }
|
|
6414
|
+
.reaction-picker {
|
|
6415
|
+
display: flex;
|
|
6416
|
+
gap: 2px;
|
|
6417
|
+
padding: 4px;
|
|
6418
|
+
border: 1px solid var(--n-border);
|
|
6419
|
+
border-radius: 999px;
|
|
6420
|
+
background: var(--n-bg);
|
|
6421
|
+
box-shadow: var(--n-shadow);
|
|
6276
6422
|
}
|
|
6277
|
-
.
|
|
6423
|
+
.reaction-picker .reaction { border-color: transparent; font-size: 14px; }
|
|
6424
|
+
|
|
6425
|
+
/* Shared controls */
|
|
6278
6426
|
.field {
|
|
6279
6427
|
width: 100%;
|
|
6280
6428
|
padding: 8px 10px;
|
|
@@ -6306,11 +6454,90 @@ textarea.field { resize: vertical; min-height: 72px; max-height: 240px; }
|
|
|
6306
6454
|
.btn-ghost { border-color: transparent; background: transparent; }
|
|
6307
6455
|
.btn-icon { width: 32px; padding: 0; justify-content: center; }
|
|
6308
6456
|
.btn-danger { color: var(--n-danger); }
|
|
6309
|
-
.kbd { color: var(--n-fg-muted); font-size: 11px; }
|
|
6310
6457
|
.error { color: var(--n-danger); font-size: 12px; }
|
|
6311
6458
|
.divider { height: 1px; background: var(--n-border); }
|
|
6312
|
-
|
|
6313
|
-
|
|
6459
|
+
|
|
6460
|
+
/* Composer: one surface, the card itself is the input. */
|
|
6461
|
+
.composer {
|
|
6462
|
+
display: grid;
|
|
6463
|
+
transition: box-shadow 140ms ease, border-color 140ms ease;
|
|
6464
|
+
}
|
|
6465
|
+
.composer:focus-within {
|
|
6466
|
+
border-color: color-mix(in srgb, var(--n-accent) 45%, var(--n-border));
|
|
6467
|
+
box-shadow: 0 0 0 4px var(--n-accent-soft), var(--n-shadow);
|
|
6468
|
+
}
|
|
6469
|
+
.composer.sheet:focus-within { border-color: var(--n-border); box-shadow: var(--n-shadow); }
|
|
6470
|
+
.composer .spacer { flex: 1; }
|
|
6471
|
+
.cmp-head { display: flex; align-items: center; gap: 8px; min-height: 48px; padding: 10px 44px 4px 14px; }
|
|
6472
|
+
.avatar-empty { background: var(--n-bg-subtle); color: var(--n-fg-muted); }
|
|
6473
|
+
.avatar-empty .icon { width: 14px; height: 14px; }
|
|
6474
|
+
.cmp-name {
|
|
6475
|
+
flex: 1;
|
|
6476
|
+
min-width: 0;
|
|
6477
|
+
padding: 0;
|
|
6478
|
+
border: 0;
|
|
6479
|
+
background: none;
|
|
6480
|
+
outline: none;
|
|
6481
|
+
font-weight: 600;
|
|
6482
|
+
}
|
|
6483
|
+
.cmp-link {
|
|
6484
|
+
border: 0;
|
|
6485
|
+
background: none;
|
|
6486
|
+
padding: 2px 4px;
|
|
6487
|
+
border-radius: 6px;
|
|
6488
|
+
color: var(--n-fg-muted);
|
|
6489
|
+
font-size: 12px;
|
|
6490
|
+
white-space: nowrap;
|
|
6491
|
+
}
|
|
6492
|
+
.cmp-link:hover { color: var(--n-fg); }
|
|
6493
|
+
.composer .quote { margin: 6px 14px 0; }
|
|
6494
|
+
.cmp-text {
|
|
6495
|
+
display: block;
|
|
6496
|
+
width: 100%;
|
|
6497
|
+
min-height: 76px;
|
|
6498
|
+
max-height: 240px;
|
|
6499
|
+
padding: 8px 14px;
|
|
6500
|
+
border: 0;
|
|
6501
|
+
background: none;
|
|
6502
|
+
outline: none;
|
|
6503
|
+
resize: none;
|
|
6504
|
+
line-height: 1.5;
|
|
6505
|
+
}
|
|
6506
|
+
.cmp-name::placeholder, .cmp-text::placeholder { color: var(--n-fg-muted); opacity: 1; }
|
|
6507
|
+
.cmp-name::placeholder { font-weight: 500; }
|
|
6508
|
+
.cmp-error { padding: 0 14px 4px; }
|
|
6509
|
+
.cmp-foot { display: flex; align-items: center; gap: 8px; padding: 6px 8px 8px 14px; }
|
|
6510
|
+
.cmp-keys { display: inline-flex; gap: 3px; }
|
|
6511
|
+
.cmp-keys kbd {
|
|
6512
|
+
display: inline-grid;
|
|
6513
|
+
place-items: center;
|
|
6514
|
+
min-width: 20px;
|
|
6515
|
+
height: 20px;
|
|
6516
|
+
padding: 0 4px;
|
|
6517
|
+
border: 1px solid var(--n-border);
|
|
6518
|
+
border-bottom-width: 2px;
|
|
6519
|
+
border-radius: 5px;
|
|
6520
|
+
color: var(--n-fg-muted);
|
|
6521
|
+
font: 500 11px/1 var(--n-font);
|
|
6522
|
+
}
|
|
6523
|
+
.cmp-count { color: var(--n-fg-muted); font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
6524
|
+
.cmp-post {
|
|
6525
|
+
display: inline-flex;
|
|
6526
|
+
align-items: center;
|
|
6527
|
+
gap: 6px;
|
|
6528
|
+
height: 32px;
|
|
6529
|
+
padding: 0 10px 0 14px;
|
|
6530
|
+
border: 0;
|
|
6531
|
+
border-radius: 999px;
|
|
6532
|
+
background: var(--n-accent);
|
|
6533
|
+
color: var(--n-accent-fg);
|
|
6534
|
+
font-weight: 600;
|
|
6535
|
+
font-size: 13px;
|
|
6536
|
+
}
|
|
6537
|
+
.cmp-post:hover { filter: brightness(1.08); }
|
|
6538
|
+
.cmp-post:disabled { opacity: 0.4; cursor: default; filter: none; }
|
|
6539
|
+
.cmp-close { position: absolute; top: 13px; right: 8px; }
|
|
6540
|
+
@media (hover: none) { .cmp-keys { display: none; } }
|
|
6314
6541
|
|
|
6315
6542
|
/* Panel */
|
|
6316
6543
|
.panel {
|
|
@@ -6458,13 +6685,11 @@ function isTypingTarget(target) {
|
|
|
6458
6685
|
const el = target;
|
|
6459
6686
|
return el.isContentEditable || el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT";
|
|
6460
6687
|
}
|
|
6461
|
-
function
|
|
6462
|
-
return
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
reader.readAsDataURL(blob);
|
|
6467
|
-
});
|
|
6688
|
+
function avatar(name) {
|
|
6689
|
+
return h("span", {
|
|
6690
|
+
class: "avatar",
|
|
6691
|
+
style: `background:${colorFor(name)}`
|
|
6692
|
+
}, initials(name));
|
|
6468
6693
|
}
|
|
6469
6694
|
function describeElement(el) {
|
|
6470
6695
|
const tag = el.tagName.toLowerCase();
|
|
@@ -6473,6 +6698,21 @@ function describeElement(el) {
|
|
|
6473
6698
|
const text = elementText(el, 40);
|
|
6474
6699
|
return `${tag}${id}${cls}${text ? ` "${text}"` : ""}`;
|
|
6475
6700
|
}
|
|
6701
|
+
/**
|
|
6702
|
+
* Build an "Other pages" link target, or null when it would leave the
|
|
6703
|
+
* current origin. The stored path comes from anonymous comment submissions;
|
|
6704
|
+
* one that does not start with "/" (".evil.com/x", "@evil.com/") would
|
|
6705
|
+
* extend or replace the host once the concatenated string is parsed as a
|
|
6706
|
+
* URL, sending the visitor - and the page's query string - to another site.
|
|
6707
|
+
*/
|
|
6708
|
+
function otherPageHref(path, loc) {
|
|
6709
|
+
const candidate = /[?#]/.test(path) ? loc.origin + path : loc.origin + path + loc.search;
|
|
6710
|
+
try {
|
|
6711
|
+
return new URL(candidate).origin === loc.origin ? candidate : null;
|
|
6712
|
+
} catch {
|
|
6713
|
+
return null;
|
|
6714
|
+
}
|
|
6715
|
+
}
|
|
6476
6716
|
var NuniWidget = class {
|
|
6477
6717
|
constructor(config, runtime) {
|
|
6478
6718
|
this.config = config;
|
|
@@ -6533,6 +6773,7 @@ var NuniWidget = class {
|
|
|
6533
6773
|
_defineProperty(this, "layoutFrame", 0);
|
|
6534
6774
|
_defineProperty(this, "pinPositions", /* @__PURE__ */ new Map());
|
|
6535
6775
|
_defineProperty(this, "resolveTimer", 0);
|
|
6776
|
+
_defineProperty(this, "shownPlacements", "");
|
|
6536
6777
|
_defineProperty(this, "i18n", void 0);
|
|
6537
6778
|
_defineProperty(this, "isIgnored", (el) => el === this.host || this.host.contains(el));
|
|
6538
6779
|
_defineProperty(this, "onFrameKey", (e) => this.onKeyDown(e));
|
|
@@ -6894,10 +7135,18 @@ var NuniWidget = class {
|
|
|
6894
7135
|
this.renderPins();
|
|
6895
7136
|
this.layout();
|
|
6896
7137
|
if (queue.length) whenIdle(slice);
|
|
6897
|
-
else if ((onlyStale || run > 1) && this.panelOpen) this.render();
|
|
7138
|
+
else if ((onlyStale || run > 1) && this.panelOpen && this.placementSummary() !== this.shownPlacements) this.render();
|
|
6898
7139
|
};
|
|
6899
7140
|
slice();
|
|
6900
7141
|
}
|
|
7142
|
+
/** What the panel and cards show from the placements: found, approximate or lost. */
|
|
7143
|
+
placementSummary() {
|
|
7144
|
+
return this.comments.map((c) => {
|
|
7145
|
+
const placement = this.placements.get(c._id);
|
|
7146
|
+
if (!placement?.element) return "-";
|
|
7147
|
+
return placement.confidence === "low" ? "~" : "+";
|
|
7148
|
+
}).join("");
|
|
7149
|
+
}
|
|
6901
7150
|
/**
|
|
6902
7151
|
* Pins inside shadow roots and iframes: the page's observers don't see
|
|
6903
7152
|
* changes or scrolling in there, so watch each of those scopes too.
|
|
@@ -7179,12 +7428,12 @@ var NuniWidget = class {
|
|
|
7179
7428
|
this.activeId = null;
|
|
7180
7429
|
this.render();
|
|
7181
7430
|
this.focusComposer();
|
|
7182
|
-
this.takeScreenshot(draft);
|
|
7431
|
+
draft.screenshot = this.takeScreenshot(draft);
|
|
7183
7432
|
}
|
|
7184
7433
|
/** Capture while the person types, so posting stays instant. */
|
|
7185
7434
|
async takeScreenshot(draft) {
|
|
7186
7435
|
const load = this.runtime.loadScreenshot;
|
|
7187
|
-
if (!load || draft.element.ownerDocument !== document) return;
|
|
7436
|
+
if (!load || draft.element.ownerDocument !== document) return null;
|
|
7188
7437
|
const accent = getComputedStyle(this.host).getPropertyValue("--n-accent").trim() || "#d6246e";
|
|
7189
7438
|
try {
|
|
7190
7439
|
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
@@ -7199,23 +7448,14 @@ var NuniWidget = class {
|
|
|
7199
7448
|
bottom: box.top + box.height
|
|
7200
7449
|
};
|
|
7201
7450
|
} : void 0;
|
|
7202
|
-
|
|
7451
|
+
return capture ? await capture(element, {
|
|
7203
7452
|
exclude: this.host,
|
|
7204
7453
|
accent,
|
|
7205
7454
|
focus
|
|
7206
7455
|
}) : null;
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
draft.screenshot = {
|
|
7211
|
-
blob,
|
|
7212
|
-
preview
|
|
7213
|
-
};
|
|
7214
|
-
this.render();
|
|
7215
|
-
} catch {}
|
|
7216
|
-
}
|
|
7217
|
-
isComposing(draft) {
|
|
7218
|
-
return this.card?.kind === "composer" && this.card.draft === draft;
|
|
7456
|
+
} catch {
|
|
7457
|
+
return null;
|
|
7458
|
+
}
|
|
7219
7459
|
}
|
|
7220
7460
|
collectContext(draft) {
|
|
7221
7461
|
const { collectors } = this.runtime;
|
|
@@ -7227,9 +7467,10 @@ var NuniWidget = class {
|
|
|
7227
7467
|
if (draft.dom) context.dom = draft.dom;
|
|
7228
7468
|
return Object.keys(context).length ? context : void 0;
|
|
7229
7469
|
}
|
|
7230
|
-
async attachScreenshot(id,
|
|
7470
|
+
async attachScreenshot(id, shot) {
|
|
7231
7471
|
try {
|
|
7232
|
-
|
|
7472
|
+
const image = await shot;
|
|
7473
|
+
if (image) await this.api.uploadScreenshot(id, this.secret, image);
|
|
7233
7474
|
} catch {}
|
|
7234
7475
|
}
|
|
7235
7476
|
copyForAgent(comment) {
|
|
@@ -7490,10 +7731,9 @@ var NuniWidget = class {
|
|
|
7490
7731
|
this.focusComment(comment._id);
|
|
7491
7732
|
}
|
|
7492
7733
|
focusComment(id) {
|
|
7493
|
-
this.placements.get(id)
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
});
|
|
7734
|
+
const placement = this.placements.get(id);
|
|
7735
|
+
const comment = this.comments.find((c) => c._id === id);
|
|
7736
|
+
if (placement?.element && comment) scrollToPin(placement.element, () => this.anchorPoint(comment.anchor, placement.element, placement.range));
|
|
7497
7737
|
this.openThread(id);
|
|
7498
7738
|
if (window.matchMedia(MOBILE_QUERY).matches) this.panelOpen = false;
|
|
7499
7739
|
this.render();
|
|
@@ -7530,7 +7770,7 @@ var NuniWidget = class {
|
|
|
7530
7770
|
},
|
|
7531
7771
|
context: this.collectContext(draft)
|
|
7532
7772
|
});
|
|
7533
|
-
if (shot) this.attachScreenshot(id, shot
|
|
7773
|
+
if (shot) this.attachScreenshot(id, shot);
|
|
7534
7774
|
this.placements.set(id, {
|
|
7535
7775
|
element: draft.element,
|
|
7536
7776
|
confidence: "exact",
|
|
@@ -7701,7 +7941,7 @@ var NuniWidget = class {
|
|
|
7701
7941
|
kind: "thread",
|
|
7702
7942
|
id
|
|
7703
7943
|
};
|
|
7704
|
-
this.focusNext = "[data-card=\"thread\"] .card-
|
|
7944
|
+
this.focusNext = "[data-card=\"thread\"] .card-close";
|
|
7705
7945
|
}
|
|
7706
7946
|
render() {
|
|
7707
7947
|
this.syncOwnerDetail();
|
|
@@ -7764,6 +8004,7 @@ var NuniWidget = class {
|
|
|
7764
8004
|
}
|
|
7765
8005
|
}
|
|
7766
8006
|
renderUi() {
|
|
8007
|
+
this.shownPlacements = this.placementSummary();
|
|
7767
8008
|
const nodes = [this.renderToolbar()];
|
|
7768
8009
|
if (this.panelOpen) nodes.push(this.renderPanel());
|
|
7769
8010
|
const card = this.renderCard();
|
|
@@ -7848,8 +8089,8 @@ var NuniWidget = class {
|
|
|
7848
8089
|
if (otherPages.length) {
|
|
7849
8090
|
children.push(h("div", { class: "section-label" }, this.t("otherPages")));
|
|
7850
8091
|
for (const p of otherPages.slice(0, 20)) {
|
|
7851
|
-
const href =
|
|
7852
|
-
children.push(h("a", {
|
|
8092
|
+
const href = otherPageHref(p.path, location);
|
|
8093
|
+
children.push(h(href === null ? "div" : "a", href === null ? { class: "item" } : {
|
|
7853
8094
|
class: "item",
|
|
7854
8095
|
href
|
|
7855
8096
|
}, h("div", { class: "row" }, h("span", { class: "author" }, p.path), h("span", { class: "spacer" }), h("span", { class: "badge" }, this.t("openOnPage", { count: p.count })))));
|
|
@@ -7908,80 +8149,112 @@ var NuniWidget = class {
|
|
|
7908
8149
|
onclick: () => this.openClaim()
|
|
7909
8150
|
}, icon(ICONS.github), this.t("claim"))));
|
|
7910
8151
|
}
|
|
8152
|
+
renderComposer(draft, cls) {
|
|
8153
|
+
const needsName = !this.name;
|
|
8154
|
+
const avatar = h("span", {
|
|
8155
|
+
class: "avatar cmp-avatar",
|
|
8156
|
+
"aria-hidden": "true"
|
|
8157
|
+
});
|
|
8158
|
+
const paintAvatar = (name) => {
|
|
8159
|
+
const clean = name.trim();
|
|
8160
|
+
avatar.classList.toggle("avatar-empty", !clean);
|
|
8161
|
+
if (clean) {
|
|
8162
|
+
avatar.textContent = initials(clean);
|
|
8163
|
+
avatar.style.background = colorFor(clean);
|
|
8164
|
+
} else {
|
|
8165
|
+
avatar.replaceChildren(icon(ICONS.user));
|
|
8166
|
+
avatar.style.background = "";
|
|
8167
|
+
}
|
|
8168
|
+
};
|
|
8169
|
+
const nameInput = h("input", {
|
|
8170
|
+
class: "cmp-name",
|
|
8171
|
+
placeholder: this.t("yourName"),
|
|
8172
|
+
value: this.name,
|
|
8173
|
+
maxlength: LIMITS.nameMaxLength,
|
|
8174
|
+
autocomplete: "name",
|
|
8175
|
+
"data-focus-key": "name",
|
|
8176
|
+
"aria-label": this.t("yourName")
|
|
8177
|
+
});
|
|
8178
|
+
const textarea = h("textarea", {
|
|
8179
|
+
class: "cmp-text",
|
|
8180
|
+
placeholder: this.t("leaveComment"),
|
|
8181
|
+
maxlength: LIMITS.bodyMaxLength,
|
|
8182
|
+
rows: 3,
|
|
8183
|
+
"data-focus-key": "body",
|
|
8184
|
+
"aria-label": this.t("comment")
|
|
8185
|
+
});
|
|
8186
|
+
const prev = this.uiLayer.querySelector("[data-focus-key=\"body\"]");
|
|
8187
|
+
if (prev) textarea.value = prev.value;
|
|
8188
|
+
const prevName = this.uiLayer.querySelector("[data-focus-key=\"name\"]");
|
|
8189
|
+
if (prevName) nameInput.value = prevName.value;
|
|
8190
|
+
paintAvatar(needsName ? nameInput.value : this.name);
|
|
8191
|
+
nameInput.addEventListener("input", () => paintAvatar(nameInput.value));
|
|
8192
|
+
const counter = h("span", {
|
|
8193
|
+
class: "cmp-count",
|
|
8194
|
+
"aria-live": "polite"
|
|
8195
|
+
});
|
|
8196
|
+
const post = h("button", {
|
|
8197
|
+
class: "cmp-post",
|
|
8198
|
+
type: "submit",
|
|
8199
|
+
disabled: Boolean(draft.sending) || !textarea.value.trim()
|
|
8200
|
+
}, draft.sending ? this.t("posting") : this.t("post"), icon(ICONS.arrowUp));
|
|
8201
|
+
const update = () => {
|
|
8202
|
+
post.disabled = Boolean(draft.sending) || !textarea.value.trim();
|
|
8203
|
+
const left = LIMITS.bodyMaxLength - textarea.value.length;
|
|
8204
|
+
counter.textContent = left <= LIMITS.bodyMaxLength * .1 ? this.t("charsLeft", { count: left }) : "";
|
|
8205
|
+
textarea.style.height = "auto";
|
|
8206
|
+
textarea.style.height = `${textarea.scrollHeight + 2}px`;
|
|
8207
|
+
};
|
|
8208
|
+
textarea.addEventListener("input", () => {
|
|
8209
|
+
update();
|
|
8210
|
+
this.positionCard();
|
|
8211
|
+
});
|
|
8212
|
+
requestAnimationFrame(() => {
|
|
8213
|
+
if (!textarea.isConnected) return;
|
|
8214
|
+
update();
|
|
8215
|
+
this.positionCard();
|
|
8216
|
+
});
|
|
8217
|
+
const submit = () => void this.submitDraft(draft, textarea.value, needsName ? nameInput.value : this.name);
|
|
8218
|
+
textarea.addEventListener("keydown", (e) => {
|
|
8219
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
8220
|
+
e.preventDefault();
|
|
8221
|
+
submit();
|
|
8222
|
+
}
|
|
8223
|
+
});
|
|
8224
|
+
return h("form", {
|
|
8225
|
+
class: `${cls} composer`,
|
|
8226
|
+
"data-card": "composer",
|
|
8227
|
+
onsubmit: (e) => {
|
|
8228
|
+
e.preventDefault();
|
|
8229
|
+
submit();
|
|
8230
|
+
}
|
|
8231
|
+
}, h("div", { class: "cmp-head" }, avatar, needsName ? nameInput : h("span", { class: "author" }, this.name), h("span", { class: "spacer" }), needsName ? null : h("button", {
|
|
8232
|
+
class: "cmp-link",
|
|
8233
|
+
type: "button",
|
|
8234
|
+
onclick: () => {
|
|
8235
|
+
this.name = "";
|
|
8236
|
+
this.render();
|
|
8237
|
+
this.focusComposer();
|
|
8238
|
+
}
|
|
8239
|
+
}, this.t("notYou"))), draft.anchor.quote ? h("blockquote", { class: "quote" }, draft.anchor.quote.exact) : null, textarea, draft.error ? h("div", {
|
|
8240
|
+
class: "error cmp-error",
|
|
8241
|
+
role: "alert"
|
|
8242
|
+
}, draft.error) : null, h("div", { class: "cmp-foot" }, h("span", {
|
|
8243
|
+
class: "cmp-keys",
|
|
8244
|
+
title: this.t("send", { key: this.i18n.mod })
|
|
8245
|
+
}, h("kbd", {}, this.i18n.mod), h("kbd", {}, "↵")), counter, h("span", { class: "spacer" }), post), h("button", {
|
|
8246
|
+
class: "icon-btn cmp-close",
|
|
8247
|
+
type: "button",
|
|
8248
|
+
"aria-label": this.t("cancel"),
|
|
8249
|
+
title: this.t("cancel"),
|
|
8250
|
+
onclick: () => this.closeCard()
|
|
8251
|
+
}, icon(ICONS.close)));
|
|
8252
|
+
}
|
|
7911
8253
|
renderCard() {
|
|
7912
8254
|
const card = this.card;
|
|
7913
8255
|
if (!card) return null;
|
|
7914
8256
|
const cls = `card${window.matchMedia(MOBILE_QUERY).matches ? " sheet" : ""}`;
|
|
7915
|
-
if (card.kind === "composer")
|
|
7916
|
-
const { draft } = card;
|
|
7917
|
-
const needsName = !this.name;
|
|
7918
|
-
const nameInput = h("input", {
|
|
7919
|
-
class: "field",
|
|
7920
|
-
placeholder: this.t("yourName"),
|
|
7921
|
-
value: this.name,
|
|
7922
|
-
maxlength: LIMITS.nameMaxLength,
|
|
7923
|
-
autocomplete: "name",
|
|
7924
|
-
"data-focus-key": "name",
|
|
7925
|
-
"aria-label": this.t("yourName")
|
|
7926
|
-
});
|
|
7927
|
-
const textarea = h("textarea", {
|
|
7928
|
-
class: "field",
|
|
7929
|
-
placeholder: this.t("leaveComment"),
|
|
7930
|
-
maxlength: LIMITS.bodyMaxLength,
|
|
7931
|
-
"data-focus-key": "body",
|
|
7932
|
-
"aria-label": this.t("comment")
|
|
7933
|
-
});
|
|
7934
|
-
const prev = this.uiLayer.querySelector("[data-focus-key=\"body\"]");
|
|
7935
|
-
if (prev) textarea.value = prev.value;
|
|
7936
|
-
const prevName = this.uiLayer.querySelector("[data-focus-key=\"name\"]");
|
|
7937
|
-
if (prevName) nameInput.value = prevName.value;
|
|
7938
|
-
const submit = () => void this.submitDraft(draft, textarea.value, needsName ? nameInput.value : this.name);
|
|
7939
|
-
textarea.addEventListener("keydown", (e) => {
|
|
7940
|
-
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
7941
|
-
e.preventDefault();
|
|
7942
|
-
submit();
|
|
7943
|
-
}
|
|
7944
|
-
});
|
|
7945
|
-
return h("form", {
|
|
7946
|
-
class: cls,
|
|
7947
|
-
"data-card": "composer",
|
|
7948
|
-
onsubmit: (e) => {
|
|
7949
|
-
e.preventDefault();
|
|
7950
|
-
submit();
|
|
7951
|
-
}
|
|
7952
|
-
}, h("div", { class: "card-body" }, needsName ? nameInput : h("div", { class: "row" }, h("span", {
|
|
7953
|
-
class: "avatar",
|
|
7954
|
-
style: `background:${colorFor(this.name)}`
|
|
7955
|
-
}, initials(this.name)), h("span", { class: "author" }, this.name), h("span", { class: "spacer" }), h("button", {
|
|
7956
|
-
class: "link",
|
|
7957
|
-
type: "button",
|
|
7958
|
-
onclick: () => {
|
|
7959
|
-
this.name = "";
|
|
7960
|
-
this.render();
|
|
7961
|
-
}
|
|
7962
|
-
}, this.t("notYou"))), draft.anchor.quote ? h("blockquote", { class: "quote" }, draft.anchor.quote.exact) : null, textarea, draft.screenshot ? h("div", { class: "shot-preview" }, h("img", {
|
|
7963
|
-
src: draft.screenshot.preview,
|
|
7964
|
-
alt: this.t("screenshotAlt")
|
|
7965
|
-
}), h("div", { class: "shot-note" }, h("span", {}, this.t("screenshotNote")), h("span", { class: "spacer" }), h("button", {
|
|
7966
|
-
class: "link",
|
|
7967
|
-
type: "button",
|
|
7968
|
-
onclick: () => {
|
|
7969
|
-
draft.screenshot = void 0;
|
|
7970
|
-
this.render();
|
|
7971
|
-
}
|
|
7972
|
-
}, this.t("remove")))) : null, draft.error ? h("div", {
|
|
7973
|
-
class: "error",
|
|
7974
|
-
role: "alert"
|
|
7975
|
-
}, draft.error) : null, h("div", { class: "row" }, h("span", { class: "kbd" }, this.t("send", { key: this.i18n.mod })), h("span", { class: "spacer" }), h("button", {
|
|
7976
|
-
class: "btn btn-ghost",
|
|
7977
|
-
type: "button",
|
|
7978
|
-
onclick: () => this.closeCard()
|
|
7979
|
-
}, this.t("cancel")), h("button", {
|
|
7980
|
-
class: "btn btn-primary",
|
|
7981
|
-
type: "submit",
|
|
7982
|
-
disabled: Boolean(draft.sending)
|
|
7983
|
-
}, icon(ICONS.send), draft.sending ? this.t("posting") : this.t("post")))));
|
|
7984
|
-
}
|
|
8257
|
+
if (card.kind === "composer") return this.renderComposer(card.draft, cls);
|
|
7985
8258
|
const comment = this.comments.find((c) => c._id === card.id);
|
|
7986
8259
|
if (!comment) return null;
|
|
7987
8260
|
const mine = this.isMine(comment);
|
|
@@ -8009,61 +8282,55 @@ var NuniWidget = class {
|
|
|
8009
8282
|
disabled: Boolean(card.busy),
|
|
8010
8283
|
onclick: () => this.saveEdit(comment, textarea.value)
|
|
8011
8284
|
}, this.t("save"))));
|
|
8012
|
-
} else
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
class: "
|
|
8031
|
-
|
|
8032
|
-
}, new URL(comment.page.origin).host) : null, placement?.confidence === "low" ? h("span", {
|
|
8033
|
-
class: "badge",
|
|
8034
|
-
title: this.t("approximateHint")
|
|
8035
|
-
}, this.t("approximate")) : null, !placement?.element ? h("span", {
|
|
8036
|
-
class: "badge",
|
|
8037
|
-
title: this.t("originallyOn", { text: comment.anchor.text || comment.anchor.tag })
|
|
8038
|
-
}, this.t("notFound")) : null), this.renderReactions(comment, comment._id), card.error ? h("div", {
|
|
8039
|
-
class: "error",
|
|
8040
|
-
role: "alert"
|
|
8041
|
-
}, card.error) : null);
|
|
8042
|
-
const actions = [];
|
|
8043
|
-
if (!card.editing) {
|
|
8044
|
-
if (owner) actions.push(comment.status === "open" ? h("button", {
|
|
8045
|
-
class: "btn",
|
|
8285
|
+
} else {
|
|
8286
|
+
const badges = [
|
|
8287
|
+
comment.status === "resolved" ? h("span", { class: "badge badge-ok" }, icon(ICONS.check), this.t("resolved")) : null,
|
|
8288
|
+
comment.page.origin !== location.origin ? h("span", {
|
|
8289
|
+
class: "badge",
|
|
8290
|
+
title: this.t("leftOn", { origin: comment.page.origin })
|
|
8291
|
+
}, new URL(comment.page.origin).host) : null,
|
|
8292
|
+
placement?.confidence === "low" ? h("span", {
|
|
8293
|
+
class: "badge",
|
|
8294
|
+
title: this.t("approximateHint")
|
|
8295
|
+
}, this.t("approximate")) : null,
|
|
8296
|
+
!placement?.element ? h("span", {
|
|
8297
|
+
class: "badge",
|
|
8298
|
+
title: this.t("originallyOn", { text: comment.anchor.text || comment.anchor.tag })
|
|
8299
|
+
}, this.t("notFound")) : null
|
|
8300
|
+
].filter((badge) => badge !== null);
|
|
8301
|
+
const contextPending = owner && !(this.ownerDetailLoaded && this.ownerDetailId === comment._id);
|
|
8302
|
+
const resolved = comment.status === "resolved";
|
|
8303
|
+
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", {
|
|
8304
|
+
class: `icon-btn${resolved ? " is-resolved" : ""}`,
|
|
8046
8305
|
type: "button",
|
|
8306
|
+
"aria-label": this.t(resolved ? "reopen" : "resolve"),
|
|
8307
|
+
title: this.t(resolved ? "reopen" : "resolve"),
|
|
8047
8308
|
disabled: Boolean(card.busy),
|
|
8048
|
-
onclick: () => this.resolveComment(comment)
|
|
8049
|
-
}, icon(ICONS.check)
|
|
8050
|
-
class: "btn",
|
|
8309
|
+
onclick: () => resolved ? this.reopenComment(comment) : this.resolveComment(comment)
|
|
8310
|
+
}, icon(ICONS.check)) : null, h("button", {
|
|
8311
|
+
class: "icon-btn card-close",
|
|
8051
8312
|
type: "button",
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8313
|
+
"aria-label": this.t("close"),
|
|
8314
|
+
title: this.t("close"),
|
|
8315
|
+
onclick: () => this.closeCard()
|
|
8316
|
+
}, icon(ICONS.close))), h("div", { class: "msg-main" }, comment.anchor.quote ? h("blockquote", { class: "quote" }, comment.anchor.quote.exact) : null, h("div", { class: "comment-body" }, comment.body), this.ownerDetail?._id === comment._id && this.ownerDetail.screenshotUrl ? h("a", {
|
|
8317
|
+
class: "shot",
|
|
8318
|
+
href: this.ownerDetail.screenshotUrl,
|
|
8319
|
+
target: "_blank",
|
|
8320
|
+
rel: "noreferrer",
|
|
8321
|
+
title: this.t("openScreenshot")
|
|
8322
|
+
}, h("img", {
|
|
8323
|
+
src: this.ownerDetail.screenshotUrl,
|
|
8324
|
+
alt: this.t("screenshotTaken")
|
|
8325
|
+
})) : null, badges.length ? h("div", { class: "badges" }, ...badges) : null, h("div", { class: "msg-foot" }, this.renderReactions(comment, comment._id), h("span", { class: "spacer" }), h("button", {
|
|
8326
|
+
class: "icon-btn",
|
|
8059
8327
|
type: "button",
|
|
8060
8328
|
"aria-label": this.t("copyForAgent"),
|
|
8061
8329
|
title: contextPending ? this.t("loadingContext") : this.t("copyForAgentHint"),
|
|
8062
8330
|
disabled: contextPending,
|
|
8063
8331
|
onclick: () => this.copyForAgent(comment)
|
|
8064
|
-
}, icon(ICONS.bot))
|
|
8065
|
-
|
|
8066
|
-
class: "btn btn-ghost btn-icon",
|
|
8332
|
+
}, icon(ICONS.bot)), mine ? h("button", {
|
|
8333
|
+
class: "icon-btn",
|
|
8067
8334
|
type: "button",
|
|
8068
8335
|
"aria-label": this.t("edit"),
|
|
8069
8336
|
title: this.t("edit"),
|
|
@@ -8071,30 +8338,36 @@ var NuniWidget = class {
|
|
|
8071
8338
|
card.editing = true;
|
|
8072
8339
|
this.render();
|
|
8073
8340
|
}
|
|
8074
|
-
}, icon(ICONS.edit))
|
|
8075
|
-
|
|
8076
|
-
class: "btn btn-ghost btn-icon btn-danger",
|
|
8341
|
+
}, icon(ICONS.edit)) : null, mine || owner ? h("button", {
|
|
8342
|
+
class: "icon-btn icon-btn-danger",
|
|
8077
8343
|
type: "button",
|
|
8078
8344
|
"aria-label": this.t("delete"),
|
|
8079
8345
|
title: this.t("delete"),
|
|
8080
8346
|
"data-focus-key": "delete",
|
|
8081
8347
|
disabled: Boolean(card.busy),
|
|
8082
8348
|
onclick: () => void this.deleteComment(comment)
|
|
8083
|
-
}, icon(ICONS.trash)))
|
|
8349
|
+
}, icon(ICONS.trash)) : null), card.error ? h("div", {
|
|
8350
|
+
class: "error",
|
|
8351
|
+
role: "alert"
|
|
8352
|
+
}, card.error) : null));
|
|
8084
8353
|
}
|
|
8085
8354
|
return h("div", {
|
|
8086
8355
|
class: cls,
|
|
8087
8356
|
"data-card": "thread",
|
|
8088
8357
|
role: "dialog",
|
|
8089
8358
|
"aria-label": this.t("commentBy", { name: comment.authorName })
|
|
8090
|
-
}, body,
|
|
8359
|
+
}, body, card.editing ? null : this.renderThread(comment));
|
|
8091
8360
|
}
|
|
8092
|
-
/**
|
|
8093
|
-
|
|
8361
|
+
/**
|
|
8362
|
+
* Emoji chips for one message and the picker to add one. Replies keep
|
|
8363
|
+
* their add button with their other tools, so `withAdd` is off for them.
|
|
8364
|
+
*/
|
|
8365
|
+
renderReactions(comment, targetId, withAdd = true) {
|
|
8094
8366
|
const card = this.card;
|
|
8095
8367
|
if (card?.kind !== "thread") return null;
|
|
8096
8368
|
const summaries = this.threadId === comment._id && this.thread ? this.thread.reactions.filter((r) => r.targetId === targetId) : [];
|
|
8097
8369
|
const open = card.picker === targetId;
|
|
8370
|
+
if (!withAdd && !summaries.length && !open) return null;
|
|
8098
8371
|
return h("div", { class: "reactions" }, ...summaries.map((r) => {
|
|
8099
8372
|
const mine = r.authorKeyHashes.includes(this.secretHash);
|
|
8100
8373
|
return h("button", {
|
|
@@ -8104,17 +8377,7 @@ var NuniWidget = class {
|
|
|
8104
8377
|
"aria-label": `${r.emoji} ${r.count}${mine ? ", including you" : ""}`,
|
|
8105
8378
|
onclick: () => this.toggleReaction(comment, targetId, r.emoji)
|
|
8106
8379
|
}, `${r.emoji} ${r.count}`);
|
|
8107
|
-
}), h("
|
|
8108
|
-
class: "reaction reaction-add",
|
|
8109
|
-
type: "button",
|
|
8110
|
-
"aria-label": this.t("addReaction"),
|
|
8111
|
-
"aria-expanded": String(open),
|
|
8112
|
-
title: this.t("addReaction"),
|
|
8113
|
-
onclick: () => {
|
|
8114
|
-
card.picker = open ? null : targetId;
|
|
8115
|
-
this.render();
|
|
8116
|
-
}
|
|
8117
|
-
}, icon(ICONS.smile)), open ? h("div", {
|
|
8380
|
+
}), withAdd ? this.reactionAdd(targetId, "reaction reaction-add") : null, open ? h("div", {
|
|
8118
8381
|
class: "reaction-picker",
|
|
8119
8382
|
role: "group",
|
|
8120
8383
|
"aria-label": this.t("reactions")
|
|
@@ -8129,6 +8392,22 @@ var NuniWidget = class {
|
|
|
8129
8392
|
}
|
|
8130
8393
|
}, emoji))) : null);
|
|
8131
8394
|
}
|
|
8395
|
+
reactionAdd(targetId, cls) {
|
|
8396
|
+
const card = this.card;
|
|
8397
|
+
if (card?.kind !== "thread") return null;
|
|
8398
|
+
const open = card.picker === targetId;
|
|
8399
|
+
return h("button", {
|
|
8400
|
+
class: cls,
|
|
8401
|
+
type: "button",
|
|
8402
|
+
"aria-label": this.t("addReaction"),
|
|
8403
|
+
"aria-expanded": String(open),
|
|
8404
|
+
title: this.t("addReaction"),
|
|
8405
|
+
onclick: () => {
|
|
8406
|
+
card.picker = open ? null : targetId;
|
|
8407
|
+
this.render();
|
|
8408
|
+
}
|
|
8409
|
+
}, icon(ICONS.smile));
|
|
8410
|
+
}
|
|
8132
8411
|
/** The replies under a comment and the box to add one. */
|
|
8133
8412
|
renderThread(comment) {
|
|
8134
8413
|
const card = this.card;
|
|
@@ -8161,13 +8440,10 @@ var NuniWidget = class {
|
|
|
8161
8440
|
}, this.t("save"))));
|
|
8162
8441
|
}
|
|
8163
8442
|
return h("div", {
|
|
8164
|
-
class: "reply",
|
|
8443
|
+
class: "reply msg",
|
|
8165
8444
|
"data-reply": reply._id
|
|
8166
|
-
}, h("div", { class: "card-head" }, h("span", {
|
|
8167
|
-
class: "
|
|
8168
|
-
style: `background:${colorFor(reply.authorName)}`
|
|
8169
|
-
}, initials(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" }), mine ? h("button", {
|
|
8170
|
-
class: "btn btn-ghost btn-icon btn-xs",
|
|
8445
|
+
}, 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", {
|
|
8446
|
+
class: "icon-btn",
|
|
8171
8447
|
type: "button",
|
|
8172
8448
|
"aria-label": this.t("editReply"),
|
|
8173
8449
|
title: this.t("edit"),
|
|
@@ -8176,18 +8452,18 @@ var NuniWidget = class {
|
|
|
8176
8452
|
this.render();
|
|
8177
8453
|
}
|
|
8178
8454
|
}, icon(ICONS.edit)) : null, mine || owner ? h("button", {
|
|
8179
|
-
class: "btn
|
|
8455
|
+
class: "icon-btn icon-btn-danger",
|
|
8180
8456
|
type: "button",
|
|
8181
8457
|
"aria-label": this.t("deleteReply"),
|
|
8182
8458
|
title: this.t("delete"),
|
|
8183
8459
|
"data-focus-key": `delete-${reply._id}`,
|
|
8184
8460
|
disabled: Boolean(card.busy),
|
|
8185
8461
|
onclick: () => void this.deleteReply(comment, reply)
|
|
8186
|
-
}, icon(ICONS.trash)) : null), h("div", { class: "comment-body" }, reply.body), this.renderReactions(comment, reply._id));
|
|
8462
|
+
}, icon(ICONS.trash)) : null)), h("div", { class: "msg-main" }, h("div", { class: "comment-body" }, reply.body), this.renderReactions(comment, reply._id, false)));
|
|
8187
8463
|
});
|
|
8188
8464
|
const needsName = !owner && !this.name;
|
|
8189
8465
|
const nameInput = h("input", {
|
|
8190
|
-
class: "
|
|
8466
|
+
class: "reply-name",
|
|
8191
8467
|
placeholder: this.t("yourName"),
|
|
8192
8468
|
value: this.name,
|
|
8193
8469
|
maxlength: LIMITS.nameMaxLength,
|
|
@@ -8198,14 +8474,31 @@ var NuniWidget = class {
|
|
|
8198
8474
|
const prevName = this.uiLayer.querySelector("[data-focus-key=\"reply-name\"]");
|
|
8199
8475
|
if (prevName) nameInput.value = prevName.value;
|
|
8200
8476
|
const box = h("textarea", {
|
|
8201
|
-
class: "
|
|
8477
|
+
class: "reply-text",
|
|
8202
8478
|
placeholder: replies.length ? this.t("reply") : this.t("replyFirst"),
|
|
8203
8479
|
maxlength: LIMITS.bodyMaxLength,
|
|
8480
|
+
rows: 1,
|
|
8204
8481
|
"data-focus-key": "reply",
|
|
8205
8482
|
"aria-label": this.t("reply")
|
|
8206
8483
|
});
|
|
8207
8484
|
const prev = this.uiLayer.querySelector("[data-focus-key=\"reply\"]");
|
|
8208
8485
|
if (prev) box.value = prev.value;
|
|
8486
|
+
const sendButton = h("button", {
|
|
8487
|
+
class: "reply-send",
|
|
8488
|
+
type: "submit",
|
|
8489
|
+
"aria-label": this.t("sendReply"),
|
|
8490
|
+
title: this.t("sendReply"),
|
|
8491
|
+
disabled: Boolean(card.sendingReply) || !box.value.trim()
|
|
8492
|
+
}, icon(ICONS.arrowUp));
|
|
8493
|
+
const grow = () => {
|
|
8494
|
+
sendButton.disabled = Boolean(card.sendingReply) || !box.value.trim();
|
|
8495
|
+
box.style.height = "auto";
|
|
8496
|
+
box.style.height = `${box.scrollHeight}px`;
|
|
8497
|
+
};
|
|
8498
|
+
box.addEventListener("input", grow);
|
|
8499
|
+
requestAnimationFrame(() => {
|
|
8500
|
+
if (box.isConnected) grow();
|
|
8501
|
+
});
|
|
8209
8502
|
const send = () => void this.sendReply(comment, box.value, needsName ? nameInput.value : this.name);
|
|
8210
8503
|
box.addEventListener("keydown", (e) => {
|
|
8211
8504
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
@@ -8226,13 +8519,7 @@ var NuniWidget = class {
|
|
|
8226
8519
|
e.preventDefault();
|
|
8227
8520
|
send();
|
|
8228
8521
|
}
|
|
8229
|
-
}, needsName ? nameInput : null,
|
|
8230
|
-
class: "btn btn-primary btn-icon",
|
|
8231
|
-
type: "submit",
|
|
8232
|
-
"aria-label": this.t("sendReply"),
|
|
8233
|
-
title: this.t("sendReply"),
|
|
8234
|
-
disabled: Boolean(card.sendingReply)
|
|
8235
|
-
}, icon(ICONS.send))));
|
|
8522
|
+
}, needsName ? nameInput : null, h("div", { class: "reply-row" }, box, sendButton)));
|
|
8236
8523
|
}
|
|
8237
8524
|
positionCard() {
|
|
8238
8525
|
const el = this.uiLayer.querySelector("[data-card]");
|