@nuniapp/widget 0.1.6 → 0.1.8
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 +11 -5
- package/dist/index.js +650 -255
- package/dist/nuni.global.js +260 -66
- 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.8";
|
|
169
169
|
function resolveConfig(options) {
|
|
170
170
|
return {
|
|
171
171
|
project: options.project,
|
|
@@ -191,8 +191,10 @@ function resolveConfig(options) {
|
|
|
191
191
|
}
|
|
192
192
|
const POSITIONS = [
|
|
193
193
|
"bottom-right",
|
|
194
|
+
"bottom-center",
|
|
194
195
|
"bottom-left",
|
|
195
196
|
"top-right",
|
|
197
|
+
"top-center",
|
|
196
198
|
"top-left"
|
|
197
199
|
];
|
|
198
200
|
const THEMES = [
|
|
@@ -5555,6 +5557,8 @@ const EN = {
|
|
|
5555
5557
|
noOpenKey: "No open comments on this page. Press {key} to add one.",
|
|
5556
5558
|
noResolved: "Nothing resolved yet.",
|
|
5557
5559
|
notFoundHere: "Couldn't find on this page",
|
|
5560
|
+
searchComments: "Search comments",
|
|
5561
|
+
noMatches: "No comments match “{query}”.",
|
|
5558
5562
|
otherPages: "Other pages",
|
|
5559
5563
|
openOnPage: "{count} open",
|
|
5560
5564
|
replyCount: {
|
|
@@ -5567,14 +5571,15 @@ const EN = {
|
|
|
5567
5571
|
yourName: "Your name",
|
|
5568
5572
|
leaveComment: "Leave a comment",
|
|
5569
5573
|
notYou: "Not you?",
|
|
5570
|
-
screenshotAlt: "Screenshot that will be attached",
|
|
5571
|
-
screenshotNote: "Screenshot attached. Only the site owner sees it.",
|
|
5572
|
-
remove: "Remove",
|
|
5573
5574
|
cancel: "Cancel",
|
|
5574
5575
|
post: "Post",
|
|
5575
5576
|
posting: "Posting…",
|
|
5576
5577
|
save: "Save",
|
|
5577
5578
|
send: "{key} + Enter",
|
|
5579
|
+
charsLeft: {
|
|
5580
|
+
one: "{count} character left",
|
|
5581
|
+
other: "{count} characters left"
|
|
5582
|
+
},
|
|
5578
5583
|
pinLabel: "Comment by {name}: {body}",
|
|
5579
5584
|
commentBy: "Comment by {name}",
|
|
5580
5585
|
openScreenshot: "Open the screenshot",
|
|
@@ -5687,6 +5692,9 @@ const ICONS = {
|
|
|
5687
5692
|
send: s("<path d=\"m22 2-7 20-4-9-9-4z\"/><path d=\"M22 2 11 13\"/>"),
|
|
5688
5693
|
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
5694
|
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\"/>"),
|
|
5695
|
+
user: s("<circle cx=\"12\" cy=\"8\" r=\"4\"/><path d=\"M4 21a8 8 0 0 1 16 0\"/>"),
|
|
5696
|
+
arrowUp: s("<path d=\"M12 19V5M5 12l7-7 7 7\"/>"),
|
|
5697
|
+
search: s("<circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m21 21-4.3-4.3\"/>"),
|
|
5690
5698
|
target: s("<circle cx=\"12\" cy=\"12\" r=\"9\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>"),
|
|
5691
5699
|
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
5700
|
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\"/>")
|
|
@@ -5942,7 +5950,11 @@ function write(key, value) {
|
|
|
5942
5950
|
const KEYS = {
|
|
5943
5951
|
name: "nuni:name",
|
|
5944
5952
|
secret: "nuni:author-secret",
|
|
5945
|
-
session: (project) => `nuni:session:${project}
|
|
5953
|
+
session: (project) => `nuni:session:${project}`,
|
|
5954
|
+
/** Where this visitor dragged the toolbar. */
|
|
5955
|
+
position: (project) => `nuni:position:${project}`,
|
|
5956
|
+
/** The comment this visitor opened last, marked in the panel. */
|
|
5957
|
+
lastViewed: (project) => `nuni:last-viewed:${project}`
|
|
5946
5958
|
};
|
|
5947
5959
|
//#endregion
|
|
5948
5960
|
//#region src/styles.ts
|
|
@@ -6019,7 +6031,18 @@ button { cursor: pointer; }
|
|
|
6019
6031
|
border: 1px solid var(--n-border);
|
|
6020
6032
|
box-shadow: var(--n-shadow);
|
|
6021
6033
|
pointer-events: auto;
|
|
6022
|
-
|
|
6034
|
+
cursor: grab;
|
|
6035
|
+
touch-action: none;
|
|
6036
|
+
user-select: none;
|
|
6037
|
+
-webkit-user-select: none;
|
|
6038
|
+
}
|
|
6039
|
+
.toolbar[data-dragging] {
|
|
6040
|
+
cursor: grabbing;
|
|
6041
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.08), 0 16px 40px rgba(0,0,0,0.2);
|
|
6042
|
+
}
|
|
6043
|
+
/* While dragging, the host covers the page so no move is lost to it. */
|
|
6044
|
+
:host([data-dragging]) { pointer-events: auto; cursor: grabbing; }
|
|
6045
|
+
:host([data-dragging]) .tb-btn { pointer-events: none; }
|
|
6023
6046
|
.tb-btn {
|
|
6024
6047
|
display: inline-flex;
|
|
6025
6048
|
align-items: center;
|
|
@@ -6176,7 +6199,6 @@ button { cursor: pointer; }
|
|
|
6176
6199
|
gap: 12px;
|
|
6177
6200
|
}
|
|
6178
6201
|
.confirm p { margin: 0; }
|
|
6179
|
-
.btn-danger-solid { background: var(--n-danger); color: #fff; border-color: transparent; }
|
|
6180
6202
|
|
|
6181
6203
|
/* Pins */
|
|
6182
6204
|
.pin {
|
|
@@ -6226,59 +6248,34 @@ button { cursor: pointer; }
|
|
|
6226
6248
|
/* Cards (composer + thread) */
|
|
6227
6249
|
.card {
|
|
6228
6250
|
position: fixed;
|
|
6229
|
-
width:
|
|
6251
|
+
width: 340px;
|
|
6230
6252
|
max-width: calc(100vw - 24px);
|
|
6231
6253
|
background: var(--n-bg);
|
|
6232
6254
|
color: var(--n-fg);
|
|
6233
6255
|
border: 1px solid var(--n-border);
|
|
6234
|
-
border-radius:
|
|
6256
|
+
border-radius: 14px;
|
|
6235
6257
|
box-shadow: var(--n-shadow);
|
|
6236
6258
|
pointer-events: auto;
|
|
6237
6259
|
overflow: hidden;
|
|
6238
6260
|
}
|
|
6239
|
-
.card-body { padding: 12px; display: grid; gap: 10px; }
|
|
6261
|
+
.card-body { padding: 12px 14px; display: grid; gap: 10px; }
|
|
6240
6262
|
.card[data-card="thread"] { display: flex; flex-direction: column; max-height: calc(100vh - 24px); }
|
|
6241
|
-
.
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
.reply-form .field { flex: 1 1 100%; }
|
|
6248
|
-
/* A fixed size: growing on focus would move the Send button mid-click. */
|
|
6249
|
-
.reply-form .field-reply { flex: 1 1 0; min-height: 56px; height: 56px; resize: none; }
|
|
6250
|
-
.reactions { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; position: relative; }
|
|
6251
|
-
.reaction {
|
|
6252
|
-
display: inline-flex;
|
|
6253
|
-
align-items: center;
|
|
6254
|
-
gap: 4px;
|
|
6255
|
-
height: 24px;
|
|
6256
|
-
padding: 0 8px;
|
|
6257
|
-
border: 1px solid var(--n-border);
|
|
6258
|
-
border-radius: 999px;
|
|
6259
|
-
background: var(--n-bg);
|
|
6263
|
+
.card .quote {
|
|
6264
|
+
margin: 0;
|
|
6265
|
+
padding: 6px 10px;
|
|
6266
|
+
border-left: 0;
|
|
6267
|
+
border-radius: 8px;
|
|
6268
|
+
background: var(--n-accent-soft);
|
|
6260
6269
|
color: var(--n-fg);
|
|
6261
|
-
font-
|
|
6262
|
-
|
|
6263
|
-
|
|
6264
|
-
.reaction[aria-pressed="true"] { border-color: var(--n-accent); background: var(--n-accent-soft); }
|
|
6265
|
-
.reaction-add { color: var(--n-fg-muted); padding: 0 6px; }
|
|
6266
|
-
.reaction-picker {
|
|
6267
|
-
display: flex;
|
|
6268
|
-
gap: 2px;
|
|
6269
|
-
padding: 4px;
|
|
6270
|
-
border: 1px solid var(--n-border);
|
|
6271
|
-
border-radius: 999px;
|
|
6272
|
-
background: var(--n-bg);
|
|
6273
|
-
box-shadow: var(--n-shadow);
|
|
6270
|
+
font-style: normal;
|
|
6271
|
+
font-size: 13px;
|
|
6272
|
+
box-shadow: inset 2px 0 0 var(--n-accent);
|
|
6274
6273
|
}
|
|
6275
|
-
.
|
|
6276
|
-
.btn-xs { width: 24px; height: 24px; }
|
|
6277
|
-
.card-head { display: flex; align-items: center; gap: 8px; }
|
|
6274
|
+
.card-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
6278
6275
|
.card-head .spacer { flex: 1; }
|
|
6279
6276
|
.avatar {
|
|
6280
|
-
width:
|
|
6281
|
-
height:
|
|
6277
|
+
width: 26px;
|
|
6278
|
+
height: 26px;
|
|
6282
6279
|
border-radius: 999px;
|
|
6283
6280
|
color: #fff;
|
|
6284
6281
|
font-size: 10px;
|
|
@@ -6288,8 +6285,8 @@ button { cursor: pointer; }
|
|
|
6288
6285
|
justify-content: center;
|
|
6289
6286
|
flex: none;
|
|
6290
6287
|
}
|
|
6291
|
-
.author { font-weight: 600; }
|
|
6292
|
-
.meta { color: var(--n-fg-muted); font-size: 12px; }
|
|
6288
|
+
.author { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
6289
|
+
.meta { color: var(--n-fg-muted); font-size: 12px; white-space: nowrap; }
|
|
6293
6290
|
.badge {
|
|
6294
6291
|
display: inline-flex;
|
|
6295
6292
|
align-items: center;
|
|
@@ -6304,28 +6301,147 @@ button { cursor: pointer; }
|
|
|
6304
6301
|
text-overflow: ellipsis;
|
|
6305
6302
|
white-space: nowrap;
|
|
6306
6303
|
}
|
|
6304
|
+
.badge .icon { width: 12px; height: 12px; }
|
|
6307
6305
|
.badge-ok { background: rgba(31,157,85,0.14); color: var(--n-ok); }
|
|
6308
|
-
.
|
|
6306
|
+
.badge-owner { background: var(--n-accent-soft); color: var(--n-accent); }
|
|
6307
|
+
.comment-body { white-space: pre-wrap; word-break: break-word; line-height: 1.5; }
|
|
6309
6308
|
.shot {
|
|
6310
6309
|
display: block;
|
|
6311
6310
|
border: 1px solid var(--n-border);
|
|
6312
|
-
border-radius:
|
|
6311
|
+
border-radius: 10px;
|
|
6313
6312
|
overflow: hidden;
|
|
6314
6313
|
background: var(--n-bg-subtle);
|
|
6315
6314
|
}
|
|
6316
6315
|
.shot img { display: block; width: 100%; max-height: 160px; object-fit: cover; object-position: top; }
|
|
6317
|
-
.
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
width:
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
border: 1px solid var(--n-border);
|
|
6316
|
+
.icon-btn {
|
|
6317
|
+
display: inline-grid;
|
|
6318
|
+
place-items: center;
|
|
6319
|
+
width: 28px;
|
|
6320
|
+
height: 28px;
|
|
6321
|
+
padding: 0;
|
|
6322
|
+
border: 0;
|
|
6325
6323
|
border-radius: 8px;
|
|
6326
|
-
background:
|
|
6324
|
+
background: none;
|
|
6325
|
+
color: var(--n-fg-muted);
|
|
6326
|
+
flex: none;
|
|
6327
|
+
transition: background 120ms ease, color 120ms ease;
|
|
6328
|
+
}
|
|
6329
|
+
.icon-btn:hover { background: var(--n-bg-subtle); color: var(--n-fg); }
|
|
6330
|
+
.icon-btn:disabled { opacity: 0.4; cursor: default; background: none; }
|
|
6331
|
+
.icon-btn-danger:hover { background: color-mix(in srgb, var(--n-danger) 12%, transparent); color: var(--n-danger); }
|
|
6332
|
+
.icon-btn.is-resolved, .icon-btn.is-resolved:hover { background: rgba(31,157,85,0.14); color: var(--n-ok); }
|
|
6333
|
+
|
|
6334
|
+
/* A message (the comment or a reply): text lines up with the name. */
|
|
6335
|
+
.msg { gap: 2px; padding: 12px 8px 8px 14px; }
|
|
6336
|
+
.msg-main { display: grid; gap: 8px; padding: 0 6px 0 34px; min-width: 0; }
|
|
6337
|
+
.msg .card-head { min-height: 28px; }
|
|
6338
|
+
.badges { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
6339
|
+
.msg-foot { display: flex; align-items: center; gap: 2px; margin-right: -6px; }
|
|
6340
|
+
.msg-foot .spacer { flex: 1; }
|
|
6341
|
+
/* A reply's tools float over its header on hover, so names never truncate. */
|
|
6342
|
+
.msg-tools:empty { display: none; }
|
|
6343
|
+
.msg-tools {
|
|
6344
|
+
position: absolute;
|
|
6345
|
+
top: 4px;
|
|
6346
|
+
right: 8px;
|
|
6347
|
+
display: flex;
|
|
6348
|
+
gap: 2px;
|
|
6349
|
+
padding: 2px;
|
|
6350
|
+
border: 1px solid var(--n-border);
|
|
6351
|
+
border-radius: 10px;
|
|
6352
|
+
background: var(--n-bg);
|
|
6353
|
+
box-shadow: 0 1px 2px rgba(0,0,0,0.06), 0 4px 12px rgba(0,0,0,0.08);
|
|
6354
|
+
opacity: 0;
|
|
6355
|
+
pointer-events: none;
|
|
6356
|
+
transition: opacity 120ms ease;
|
|
6357
|
+
}
|
|
6358
|
+
.reply:hover .msg-tools, .reply:focus-within .msg-tools { opacity: 1; pointer-events: auto; }
|
|
6359
|
+
@media (hover: none) {
|
|
6360
|
+
.msg-tools { position: static; opacity: 1; pointer-events: auto; padding: 0; border: 0; box-shadow: none; background: none; }
|
|
6361
|
+
}
|
|
6362
|
+
|
|
6363
|
+
/* Replies */
|
|
6364
|
+
.thread { border-top: 1px solid var(--n-border); display: grid; min-height: 0; }
|
|
6365
|
+
.replies { display: grid; padding: 4px 0; overflow-y: auto; max-height: 45vh; }
|
|
6366
|
+
.reply.msg { position: relative; display: grid; padding: 8px 8px 8px 14px; }
|
|
6367
|
+
.reply-form {
|
|
6368
|
+
display: grid;
|
|
6369
|
+
margin: 4px 12px 12px;
|
|
6370
|
+
border: 1px solid var(--n-border);
|
|
6371
|
+
border-radius: 12px;
|
|
6372
|
+
background: var(--n-bg);
|
|
6373
|
+
transition: box-shadow 140ms ease, border-color 140ms ease;
|
|
6374
|
+
}
|
|
6375
|
+
.reply-form:focus-within {
|
|
6376
|
+
border-color: color-mix(in srgb, var(--n-accent) 45%, var(--n-border));
|
|
6377
|
+
box-shadow: 0 0 0 3px var(--n-accent-soft);
|
|
6378
|
+
}
|
|
6379
|
+
.reply-name {
|
|
6380
|
+
padding: 8px 12px 6px;
|
|
6381
|
+
border: 0;
|
|
6382
|
+
border-bottom: 1px solid var(--n-border);
|
|
6383
|
+
background: none;
|
|
6384
|
+
outline: none;
|
|
6385
|
+
font-weight: 600;
|
|
6386
|
+
}
|
|
6387
|
+
.reply-row { display: flex; align-items: flex-end; gap: 4px; padding: 4px; }
|
|
6388
|
+
.reply-text {
|
|
6389
|
+
flex: 1;
|
|
6390
|
+
min-width: 0;
|
|
6391
|
+
min-height: 32px;
|
|
6392
|
+
max-height: 160px;
|
|
6393
|
+
padding: 6px 8px;
|
|
6394
|
+
border: 0;
|
|
6395
|
+
background: none;
|
|
6396
|
+
outline: none;
|
|
6397
|
+
resize: none;
|
|
6398
|
+
line-height: 1.4;
|
|
6399
|
+
}
|
|
6400
|
+
.reply-name::placeholder, .reply-text::placeholder { color: var(--n-fg-muted); opacity: 1; font-weight: 400; }
|
|
6401
|
+
.reply-send {
|
|
6402
|
+
display: inline-grid;
|
|
6403
|
+
place-items: center;
|
|
6404
|
+
width: 32px;
|
|
6405
|
+
height: 32px;
|
|
6406
|
+
border: 0;
|
|
6407
|
+
border-radius: 999px;
|
|
6408
|
+
background: var(--n-accent);
|
|
6409
|
+
color: var(--n-accent-fg);
|
|
6410
|
+
flex: none;
|
|
6327
6411
|
}
|
|
6328
|
-
.
|
|
6412
|
+
.reply-send:hover { filter: brightness(1.08); }
|
|
6413
|
+
.reply-send:disabled { opacity: 0.4; cursor: default; filter: none; }
|
|
6414
|
+
|
|
6415
|
+
/* Reactions */
|
|
6416
|
+
.reactions { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; position: relative; }
|
|
6417
|
+
.reaction {
|
|
6418
|
+
display: inline-flex;
|
|
6419
|
+
align-items: center;
|
|
6420
|
+
gap: 4px;
|
|
6421
|
+
height: 26px;
|
|
6422
|
+
padding: 0 8px;
|
|
6423
|
+
border: 1px solid var(--n-border);
|
|
6424
|
+
border-radius: 999px;
|
|
6425
|
+
background: var(--n-bg);
|
|
6426
|
+
color: var(--n-fg);
|
|
6427
|
+
font-size: 12px;
|
|
6428
|
+
}
|
|
6429
|
+
.reaction:hover { background: var(--n-bg-subtle); }
|
|
6430
|
+
.reaction[aria-pressed="true"] { border-color: color-mix(in srgb, var(--n-accent) 50%, transparent); background: var(--n-accent-soft); }
|
|
6431
|
+
.reaction-add { color: var(--n-fg-muted); padding: 0 6px; border-color: transparent; }
|
|
6432
|
+
.reaction-add:hover, .reaction-add[aria-expanded="true"] { color: var(--n-fg); }
|
|
6433
|
+
.reaction-picker {
|
|
6434
|
+
display: flex;
|
|
6435
|
+
gap: 2px;
|
|
6436
|
+
padding: 4px;
|
|
6437
|
+
border: 1px solid var(--n-border);
|
|
6438
|
+
border-radius: 999px;
|
|
6439
|
+
background: var(--n-bg);
|
|
6440
|
+
box-shadow: var(--n-shadow);
|
|
6441
|
+
}
|
|
6442
|
+
.reaction-picker .reaction { border-color: transparent; font-size: 14px; }
|
|
6443
|
+
|
|
6444
|
+
/* Shared controls */
|
|
6329
6445
|
.field {
|
|
6330
6446
|
width: 100%;
|
|
6331
6447
|
padding: 8px 10px;
|
|
@@ -6357,11 +6473,94 @@ textarea.field { resize: vertical; min-height: 72px; max-height: 240px; }
|
|
|
6357
6473
|
.btn-ghost { border-color: transparent; background: transparent; }
|
|
6358
6474
|
.btn-icon { width: 32px; padding: 0; justify-content: center; }
|
|
6359
6475
|
.btn-danger { color: var(--n-danger); }
|
|
6360
|
-
.
|
|
6476
|
+
/* After .btn, which would otherwise paint over the red with the card's background. */
|
|
6477
|
+
.btn-danger-solid,
|
|
6478
|
+
.btn-danger-solid:hover { background: var(--n-danger); border-color: var(--n-danger); color: #fff; }
|
|
6479
|
+
.btn-danger-solid:hover { filter: brightness(1.08); }
|
|
6361
6480
|
.error { color: var(--n-danger); font-size: 12px; }
|
|
6362
6481
|
.divider { height: 1px; background: var(--n-border); }
|
|
6363
|
-
|
|
6364
|
-
|
|
6482
|
+
|
|
6483
|
+
/* Composer: one surface, the card itself is the input. */
|
|
6484
|
+
.composer {
|
|
6485
|
+
display: grid;
|
|
6486
|
+
transition: box-shadow 140ms ease, border-color 140ms ease;
|
|
6487
|
+
}
|
|
6488
|
+
.composer:focus-within {
|
|
6489
|
+
border-color: color-mix(in srgb, var(--n-accent) 45%, var(--n-border));
|
|
6490
|
+
box-shadow: 0 0 0 4px var(--n-accent-soft), var(--n-shadow);
|
|
6491
|
+
}
|
|
6492
|
+
.composer.sheet:focus-within { border-color: var(--n-border); box-shadow: var(--n-shadow); }
|
|
6493
|
+
.composer .spacer { flex: 1; }
|
|
6494
|
+
.cmp-head { display: flex; align-items: center; gap: 8px; min-height: 48px; padding: 10px 44px 4px 14px; }
|
|
6495
|
+
.avatar-empty { background: var(--n-bg-subtle); color: var(--n-fg-muted); }
|
|
6496
|
+
.avatar-empty .icon { width: 14px; height: 14px; }
|
|
6497
|
+
.cmp-name {
|
|
6498
|
+
flex: 1;
|
|
6499
|
+
min-width: 0;
|
|
6500
|
+
padding: 0;
|
|
6501
|
+
border: 0;
|
|
6502
|
+
background: none;
|
|
6503
|
+
outline: none;
|
|
6504
|
+
font-weight: 600;
|
|
6505
|
+
}
|
|
6506
|
+
.cmp-link {
|
|
6507
|
+
border: 0;
|
|
6508
|
+
background: none;
|
|
6509
|
+
padding: 2px 4px;
|
|
6510
|
+
border-radius: 6px;
|
|
6511
|
+
color: var(--n-fg-muted);
|
|
6512
|
+
font-size: 12px;
|
|
6513
|
+
white-space: nowrap;
|
|
6514
|
+
}
|
|
6515
|
+
.cmp-link:hover { color: var(--n-fg); }
|
|
6516
|
+
.composer .quote { margin: 6px 14px 0; }
|
|
6517
|
+
.cmp-text {
|
|
6518
|
+
display: block;
|
|
6519
|
+
width: 100%;
|
|
6520
|
+
min-height: 76px;
|
|
6521
|
+
max-height: 240px;
|
|
6522
|
+
padding: 8px 14px;
|
|
6523
|
+
border: 0;
|
|
6524
|
+
background: none;
|
|
6525
|
+
outline: none;
|
|
6526
|
+
resize: none;
|
|
6527
|
+
line-height: 1.5;
|
|
6528
|
+
}
|
|
6529
|
+
.cmp-name::placeholder, .cmp-text::placeholder { color: var(--n-fg-muted); opacity: 1; }
|
|
6530
|
+
.cmp-name::placeholder { font-weight: 500; }
|
|
6531
|
+
.cmp-error { padding: 0 14px 4px; }
|
|
6532
|
+
.cmp-foot { display: flex; align-items: center; gap: 8px; padding: 6px 8px 8px 14px; }
|
|
6533
|
+
.cmp-keys { display: inline-flex; gap: 3px; }
|
|
6534
|
+
.cmp-keys kbd {
|
|
6535
|
+
display: inline-grid;
|
|
6536
|
+
place-items: center;
|
|
6537
|
+
min-width: 20px;
|
|
6538
|
+
height: 20px;
|
|
6539
|
+
padding: 0 4px;
|
|
6540
|
+
border: 1px solid var(--n-border);
|
|
6541
|
+
border-bottom-width: 2px;
|
|
6542
|
+
border-radius: 5px;
|
|
6543
|
+
color: var(--n-fg-muted);
|
|
6544
|
+
font: 500 11px/1 var(--n-font);
|
|
6545
|
+
}
|
|
6546
|
+
.cmp-count { color: var(--n-fg-muted); font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
6547
|
+
.cmp-post {
|
|
6548
|
+
display: inline-flex;
|
|
6549
|
+
align-items: center;
|
|
6550
|
+
gap: 6px;
|
|
6551
|
+
height: 32px;
|
|
6552
|
+
padding: 0 10px 0 14px;
|
|
6553
|
+
border: 0;
|
|
6554
|
+
border-radius: 999px;
|
|
6555
|
+
background: var(--n-accent);
|
|
6556
|
+
color: var(--n-accent-fg);
|
|
6557
|
+
font-weight: 600;
|
|
6558
|
+
font-size: 13px;
|
|
6559
|
+
}
|
|
6560
|
+
.cmp-post:hover { filter: brightness(1.08); }
|
|
6561
|
+
.cmp-post:disabled { opacity: 0.4; cursor: default; filter: none; }
|
|
6562
|
+
.cmp-close { position: absolute; top: 13px; right: 8px; }
|
|
6563
|
+
@media (hover: none) { .cmp-keys { display: none; } }
|
|
6365
6564
|
|
|
6366
6565
|
/* Panel */
|
|
6367
6566
|
.panel {
|
|
@@ -6383,7 +6582,7 @@ textarea.field { resize: vertical; min-height: 72px; max-height: 240px; }
|
|
|
6383
6582
|
}
|
|
6384
6583
|
.panel-head { display: flex; align-items: center; gap: 8px; padding: 12px; border-bottom: 1px solid var(--n-border); }
|
|
6385
6584
|
.panel-title { font-weight: 650; flex: 1; }
|
|
6386
|
-
.tabs { display: flex; gap: 4px; padding:
|
|
6585
|
+
.tabs { display: flex; gap: 4px; padding: 0 12px; margin-top: 8px; }
|
|
6387
6586
|
.tab {
|
|
6388
6587
|
border: 0;
|
|
6389
6588
|
background: transparent;
|
|
@@ -6406,6 +6605,11 @@ textarea.field { resize: vertical; min-height: 72px; max-height: 240px; }
|
|
|
6406
6605
|
border-radius: 10px;
|
|
6407
6606
|
}
|
|
6408
6607
|
.item:hover { background: var(--n-bg-subtle); }
|
|
6608
|
+
/* The comment opened last, so it's easy to find your place again. */
|
|
6609
|
+
.item[aria-current="true"] { background: var(--n-accent-soft); box-shadow: inset 3px 0 0 var(--n-accent); }
|
|
6610
|
+
.panel-search { position: relative; padding: 0 12px; margin-top: 8px; }
|
|
6611
|
+
.panel-search .icon { position: absolute; left: 22px; top: 50%; transform: translateY(-50%); color: var(--n-fg-muted); pointer-events: none; }
|
|
6612
|
+
.panel-search .field { padding-left: 32px; }
|
|
6409
6613
|
.item-text { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; word-break: break-word; }
|
|
6410
6614
|
.section-label { padding: 12px 10px 4px; font-size: 11px; font-weight: 650; letter-spacing: 0.04em; text-transform: uppercase; color: var(--n-fg-muted); }
|
|
6411
6615
|
.empty { padding: 32px 16px; text-align: center; color: var(--n-fg-muted); }
|
|
@@ -6449,8 +6653,10 @@ a.item { color: inherit; text-decoration: none; }
|
|
|
6449
6653
|
* { transition: none !important; animation: none !important; }
|
|
6450
6654
|
}
|
|
6451
6655
|
|
|
6452
|
-
/* The toolbar's
|
|
6656
|
+
/* The toolbar's spot (the position option, or where it was dragged) */
|
|
6453
6657
|
:host([data-position$="left"]) .toolbar { right: auto; left: 16px; }
|
|
6658
|
+
:host([data-position$="center"]) .toolbar { left: 0; right: 0; width: max-content; margin-inline: auto; }
|
|
6659
|
+
:host([data-position="top-center"]) .pick-hint { top: auto; bottom: 12px; }
|
|
6454
6660
|
:host([data-position^="top"]) .toolbar { bottom: auto; top: 16px; }
|
|
6455
6661
|
:host([data-position$="left"]) .panel { right: auto; left: 12px; }
|
|
6456
6662
|
:host([data-position^="top"]) .panel { top: 72px; bottom: 12px; }
|
|
@@ -6509,13 +6715,11 @@ function isTypingTarget(target) {
|
|
|
6509
6715
|
const el = target;
|
|
6510
6716
|
return el.isContentEditable || el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT";
|
|
6511
6717
|
}
|
|
6512
|
-
function
|
|
6513
|
-
return
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
reader.readAsDataURL(blob);
|
|
6518
|
-
});
|
|
6718
|
+
function avatar(name) {
|
|
6719
|
+
return h("span", {
|
|
6720
|
+
class: "avatar",
|
|
6721
|
+
style: `background:${colorFor(name)}`
|
|
6722
|
+
}, initials(name));
|
|
6519
6723
|
}
|
|
6520
6724
|
function describeElement(el) {
|
|
6521
6725
|
const tag = el.tagName.toLowerCase();
|
|
@@ -6564,6 +6768,8 @@ var NuniWidget = class {
|
|
|
6564
6768
|
_defineProperty(this, "picking", false);
|
|
6565
6769
|
_defineProperty(this, "panelOpen", false);
|
|
6566
6770
|
_defineProperty(this, "tab", "open");
|
|
6771
|
+
_defineProperty(this, "search", "");
|
|
6772
|
+
_defineProperty(this, "lastViewedId", void 0);
|
|
6567
6773
|
_defineProperty(this, "card", null);
|
|
6568
6774
|
_defineProperty(this, "activeId", null);
|
|
6569
6775
|
_defineProperty(this, "hoverEl", null);
|
|
@@ -6599,7 +6805,12 @@ var NuniWidget = class {
|
|
|
6599
6805
|
_defineProperty(this, "layoutFrame", 0);
|
|
6600
6806
|
_defineProperty(this, "pinPositions", /* @__PURE__ */ new Map());
|
|
6601
6807
|
_defineProperty(this, "resolveTimer", 0);
|
|
6808
|
+
_defineProperty(this, "shownPlacements", "");
|
|
6602
6809
|
_defineProperty(this, "i18n", void 0);
|
|
6810
|
+
_defineProperty(this, "position", void 0);
|
|
6811
|
+
_defineProperty(this, "toolbarDrag", null);
|
|
6812
|
+
_defineProperty(this, "endToolbarDrag", null);
|
|
6813
|
+
_defineProperty(this, "toolbarDropped", false);
|
|
6603
6814
|
_defineProperty(this, "isIgnored", (el) => el === this.host || this.host.contains(el));
|
|
6604
6815
|
_defineProperty(this, "onFrameKey", (e) => this.onKeyDown(e));
|
|
6605
6816
|
_defineProperty(this, "swallow", (e) => {
|
|
@@ -6679,6 +6890,8 @@ var NuniWidget = class {
|
|
|
6679
6890
|
}
|
|
6680
6891
|
this.secret = secret;
|
|
6681
6892
|
this.ownerToken = read(KEYS.session(config.project));
|
|
6893
|
+
this.lastViewedId = read(KEYS.lastViewed(config.project));
|
|
6894
|
+
this.position = oneOf(read(KEYS.position(config.project)) ?? void 0, POSITIONS, config.position);
|
|
6682
6895
|
this.host = document.createElement("div");
|
|
6683
6896
|
this.host.id = "nuni-root";
|
|
6684
6897
|
this.host.setAttribute("data-nuni", "");
|
|
@@ -6709,6 +6922,11 @@ var NuniWidget = class {
|
|
|
6709
6922
|
});
|
|
6710
6923
|
this.root.append(style, this.markLayer, this.pinLayer, this.overlayLayer, this.uiLayer, this.selectButton, this.live);
|
|
6711
6924
|
this.uiLayer.addEventListener("keydown", (e) => this.trapFocus(e));
|
|
6925
|
+
this.root.addEventListener("click", (e) => {
|
|
6926
|
+
if (!this.toolbarDropped) return;
|
|
6927
|
+
e.preventDefault();
|
|
6928
|
+
e.stopPropagation();
|
|
6929
|
+
}, true);
|
|
6712
6930
|
this.api = new NuniApi(config);
|
|
6713
6931
|
}
|
|
6714
6932
|
t(key, vars) {
|
|
@@ -6721,8 +6939,8 @@ var NuniWidget = class {
|
|
|
6721
6939
|
}
|
|
6722
6940
|
/** Position, theme, brand color and stacking order from the options. */
|
|
6723
6941
|
applyAppearance() {
|
|
6724
|
-
const {
|
|
6725
|
-
this.host.dataset.position = position;
|
|
6942
|
+
const { theme, accentColor, zIndex } = this.config;
|
|
6943
|
+
this.host.dataset.position = this.position;
|
|
6726
6944
|
this.host.dataset.theme = theme;
|
|
6727
6945
|
if (zIndex !== null) this.host.style.zIndex = String(zIndex);
|
|
6728
6946
|
const rgb = accentColor ? parseColor(accentColor) : null;
|
|
@@ -6769,6 +6987,7 @@ var NuniWidget = class {
|
|
|
6769
6987
|
if (e.composedPath().includes(this.host)) return;
|
|
6770
6988
|
this.pointerDown = true;
|
|
6771
6989
|
this.scheduleSelection();
|
|
6990
|
+
this.closeCardFromOutside(e);
|
|
6772
6991
|
};
|
|
6773
6992
|
const onUp = () => {
|
|
6774
6993
|
this.pointerDown = false;
|
|
@@ -6816,6 +7035,7 @@ var NuniWidget = class {
|
|
|
6816
7035
|
}
|
|
6817
7036
|
destroy() {
|
|
6818
7037
|
this.setPicking(false);
|
|
7038
|
+
this.endToolbarDrag?.();
|
|
6819
7039
|
this.pageUnsub?.();
|
|
6820
7040
|
this.sessionUnsub?.();
|
|
6821
7041
|
this.ownerDetailUnsub?.();
|
|
@@ -6960,10 +7180,18 @@ var NuniWidget = class {
|
|
|
6960
7180
|
this.renderPins();
|
|
6961
7181
|
this.layout();
|
|
6962
7182
|
if (queue.length) whenIdle(slice);
|
|
6963
|
-
else if ((onlyStale || run > 1) && this.panelOpen) this.render();
|
|
7183
|
+
else if ((onlyStale || run > 1) && this.panelOpen && this.placementSummary() !== this.shownPlacements) this.render();
|
|
6964
7184
|
};
|
|
6965
7185
|
slice();
|
|
6966
7186
|
}
|
|
7187
|
+
/** What the panel and cards show from the placements: found, approximate or lost. */
|
|
7188
|
+
placementSummary() {
|
|
7189
|
+
return this.comments.map((c) => {
|
|
7190
|
+
const placement = this.placements.get(c._id);
|
|
7191
|
+
if (!placement?.element) return "-";
|
|
7192
|
+
return placement.confidence === "low" ? "~" : "+";
|
|
7193
|
+
}).join("");
|
|
7194
|
+
}
|
|
6967
7195
|
/**
|
|
6968
7196
|
* Pins inside shadow roots and iframes: the page's observers don't see
|
|
6969
7197
|
* changes or scrolling in there, so watch each of those scopes too.
|
|
@@ -7245,12 +7473,12 @@ var NuniWidget = class {
|
|
|
7245
7473
|
this.activeId = null;
|
|
7246
7474
|
this.render();
|
|
7247
7475
|
this.focusComposer();
|
|
7248
|
-
this.takeScreenshot(draft);
|
|
7476
|
+
draft.screenshot = this.takeScreenshot(draft);
|
|
7249
7477
|
}
|
|
7250
7478
|
/** Capture while the person types, so posting stays instant. */
|
|
7251
7479
|
async takeScreenshot(draft) {
|
|
7252
7480
|
const load = this.runtime.loadScreenshot;
|
|
7253
|
-
if (!load || draft.element.ownerDocument !== document) return;
|
|
7481
|
+
if (!load || draft.element.ownerDocument !== document) return null;
|
|
7254
7482
|
const accent = getComputedStyle(this.host).getPropertyValue("--n-accent").trim() || "#d6246e";
|
|
7255
7483
|
try {
|
|
7256
7484
|
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
@@ -7265,23 +7493,14 @@ var NuniWidget = class {
|
|
|
7265
7493
|
bottom: box.top + box.height
|
|
7266
7494
|
};
|
|
7267
7495
|
} : void 0;
|
|
7268
|
-
|
|
7496
|
+
return capture ? await capture(element, {
|
|
7269
7497
|
exclude: this.host,
|
|
7270
7498
|
accent,
|
|
7271
7499
|
focus
|
|
7272
7500
|
}) : null;
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
draft.screenshot = {
|
|
7277
|
-
blob,
|
|
7278
|
-
preview
|
|
7279
|
-
};
|
|
7280
|
-
this.render();
|
|
7281
|
-
} catch {}
|
|
7282
|
-
}
|
|
7283
|
-
isComposing(draft) {
|
|
7284
|
-
return this.card?.kind === "composer" && this.card.draft === draft;
|
|
7501
|
+
} catch {
|
|
7502
|
+
return null;
|
|
7503
|
+
}
|
|
7285
7504
|
}
|
|
7286
7505
|
collectContext(draft) {
|
|
7287
7506
|
const { collectors } = this.runtime;
|
|
@@ -7293,9 +7512,10 @@ var NuniWidget = class {
|
|
|
7293
7512
|
if (draft.dom) context.dom = draft.dom;
|
|
7294
7513
|
return Object.keys(context).length ? context : void 0;
|
|
7295
7514
|
}
|
|
7296
|
-
async attachScreenshot(id,
|
|
7515
|
+
async attachScreenshot(id, shot) {
|
|
7297
7516
|
try {
|
|
7298
|
-
|
|
7517
|
+
const image = await shot;
|
|
7518
|
+
if (image) await this.api.uploadScreenshot(id, this.secret, image);
|
|
7299
7519
|
} catch {}
|
|
7300
7520
|
}
|
|
7301
7521
|
copyForAgent(comment) {
|
|
@@ -7340,7 +7560,10 @@ var NuniWidget = class {
|
|
|
7340
7560
|
if (this.confirming) this.answer(false);
|
|
7341
7561
|
else if (this.picking) this.setPicking(false);
|
|
7342
7562
|
else if (this.card) this.closeCard();
|
|
7343
|
-
else if (this.panelOpen
|
|
7563
|
+
else if (this.panelOpen && this.search) {
|
|
7564
|
+
this.search = "";
|
|
7565
|
+
this.render();
|
|
7566
|
+
} else if (this.panelOpen) this.togglePanel(false);
|
|
7344
7567
|
else return;
|
|
7345
7568
|
e.stopPropagation();
|
|
7346
7569
|
return;
|
|
@@ -7595,7 +7818,7 @@ var NuniWidget = class {
|
|
|
7595
7818
|
},
|
|
7596
7819
|
context: this.collectContext(draft)
|
|
7597
7820
|
});
|
|
7598
|
-
if (shot) this.attachScreenshot(id, shot
|
|
7821
|
+
if (shot) this.attachScreenshot(id, shot);
|
|
7599
7822
|
this.placements.set(id, {
|
|
7600
7823
|
element: draft.element,
|
|
7601
7824
|
confidence: "exact",
|
|
@@ -7747,10 +7970,22 @@ var NuniWidget = class {
|
|
|
7747
7970
|
this.keepFocus();
|
|
7748
7971
|
this.focusNext = ".panel [role=\"tab\"][aria-selected=\"true\"]";
|
|
7749
7972
|
if (window.matchMedia(MOBILE_QUERY).matches) this.card = null;
|
|
7750
|
-
}
|
|
7973
|
+
} else this.search = "";
|
|
7751
7974
|
this.render();
|
|
7975
|
+
if (open) this.uiLayer.querySelector(".panel .item[aria-current=\"true\"]")?.scrollIntoView({ block: "nearest" });
|
|
7752
7976
|
if (!open && !this.card) this.restoreFocus();
|
|
7753
7977
|
}
|
|
7978
|
+
/**
|
|
7979
|
+
* A press on the page closes the open card, like Figma, unless the card
|
|
7980
|
+
* holds words that haven't been sent yet.
|
|
7981
|
+
*/
|
|
7982
|
+
closeCardFromOutside(e) {
|
|
7983
|
+
if (!this.card || this.picking || this.confirming || !e.isPrimary) return;
|
|
7984
|
+
if (onScrollbar(e)) return;
|
|
7985
|
+
if ([...this.uiLayer.querySelector("[data-card]")?.querySelectorAll("textarea") ?? []].some((t) => t.value.trim())) return;
|
|
7986
|
+
if (!this.panelOpen) this.returnFocus = null;
|
|
7987
|
+
this.closeCard();
|
|
7988
|
+
}
|
|
7754
7989
|
closeCard() {
|
|
7755
7990
|
const had = this.card !== null;
|
|
7756
7991
|
this.card = null;
|
|
@@ -7762,11 +7997,13 @@ var NuniWidget = class {
|
|
|
7762
7997
|
openThread(id) {
|
|
7763
7998
|
this.keepFocus();
|
|
7764
7999
|
this.activeId = id;
|
|
8000
|
+
this.lastViewedId = id;
|
|
8001
|
+
write(KEYS.lastViewed(this.config.project), id);
|
|
7765
8002
|
this.card = {
|
|
7766
8003
|
kind: "thread",
|
|
7767
8004
|
id
|
|
7768
8005
|
};
|
|
7769
|
-
this.focusNext = "[data-card=\"thread\"] .card-
|
|
8006
|
+
this.focusNext = "[data-card=\"thread\"] .card-close";
|
|
7770
8007
|
}
|
|
7771
8008
|
render() {
|
|
7772
8009
|
this.syncOwnerDetail();
|
|
@@ -7829,6 +8066,7 @@ var NuniWidget = class {
|
|
|
7829
8066
|
}
|
|
7830
8067
|
}
|
|
7831
8068
|
renderUi() {
|
|
8069
|
+
this.shownPlacements = this.placementSummary();
|
|
7832
8070
|
const nodes = [this.renderToolbar()];
|
|
7833
8071
|
if (this.panelOpen) nodes.push(this.renderPanel());
|
|
7834
8072
|
const card = this.renderCard();
|
|
@@ -7859,10 +8097,14 @@ var NuniWidget = class {
|
|
|
7859
8097
|
}
|
|
7860
8098
|
renderToolbar() {
|
|
7861
8099
|
const open = this.comments.filter((c) => c.status === "open").length;
|
|
8100
|
+
const drag = this.toolbarDrag?.moving ? this.toolbarDrag : null;
|
|
7862
8101
|
return h("div", {
|
|
7863
8102
|
class: "toolbar",
|
|
7864
8103
|
role: "toolbar",
|
|
7865
|
-
"aria-label": this.t("toolbar")
|
|
8104
|
+
"aria-label": this.t("toolbar"),
|
|
8105
|
+
"data-dragging": !!drag,
|
|
8106
|
+
style: drag && `transform: translate(${drag.dx}px, ${drag.dy}px)`,
|
|
8107
|
+
onpointerdown: (e) => this.startToolbarDrag(e)
|
|
7866
8108
|
}, h("button", {
|
|
7867
8109
|
class: "tb-btn",
|
|
7868
8110
|
type: "button",
|
|
@@ -7881,16 +8123,94 @@ var NuniWidget = class {
|
|
|
7881
8123
|
onclick: () => this.togglePanel()
|
|
7882
8124
|
}, icon(ICONS.list), h("span", { class: "tb-count" }, String(open))));
|
|
7883
8125
|
}
|
|
8126
|
+
/**
|
|
8127
|
+
* Drag the toolbar anywhere; on release it glides to the nearest corner or
|
|
8128
|
+
* edge middle, which is remembered for this visitor.
|
|
8129
|
+
*/
|
|
8130
|
+
startToolbarDrag(e) {
|
|
8131
|
+
if (e.button !== 0 || !e.isPrimary) return;
|
|
8132
|
+
this.endToolbarDrag?.();
|
|
8133
|
+
const drag = {
|
|
8134
|
+
id: e.pointerId,
|
|
8135
|
+
x: e.clientX,
|
|
8136
|
+
y: e.clientY,
|
|
8137
|
+
dx: 0,
|
|
8138
|
+
dy: 0,
|
|
8139
|
+
moving: false
|
|
8140
|
+
};
|
|
8141
|
+
this.toolbarDrag = drag;
|
|
8142
|
+
const toolbar = () => this.uiLayer.querySelector(".toolbar");
|
|
8143
|
+
const onMove = (ev) => {
|
|
8144
|
+
if (ev.pointerId !== drag.id) return;
|
|
8145
|
+
drag.dx = ev.clientX - drag.x;
|
|
8146
|
+
drag.dy = ev.clientY - drag.y;
|
|
8147
|
+
if (!drag.moving && Math.hypot(drag.dx, drag.dy) < 5) return;
|
|
8148
|
+
if (!drag.moving) {
|
|
8149
|
+
drag.moving = true;
|
|
8150
|
+
this.host.dataset.dragging = "";
|
|
8151
|
+
toolbar()?.setAttribute("data-dragging", "");
|
|
8152
|
+
}
|
|
8153
|
+
ev.preventDefault();
|
|
8154
|
+
toolbar()?.style.setProperty("transform", `translate(${drag.dx}px, ${drag.dy}px)`);
|
|
8155
|
+
};
|
|
8156
|
+
const onUp = (ev) => {
|
|
8157
|
+
if (ev.pointerId !== drag.id) return;
|
|
8158
|
+
this.endToolbarDrag?.();
|
|
8159
|
+
if (drag.moving) this.dropToolbar(toolbar(), true);
|
|
8160
|
+
};
|
|
8161
|
+
const onCancel = (ev) => {
|
|
8162
|
+
if (ev instanceof PointerEvent && ev.pointerId !== drag.id) return;
|
|
8163
|
+
this.endToolbarDrag?.();
|
|
8164
|
+
if (drag.moving) this.dropToolbar(toolbar(), false);
|
|
8165
|
+
};
|
|
8166
|
+
window.addEventListener("pointermove", onMove, true);
|
|
8167
|
+
window.addEventListener("pointerup", onUp, true);
|
|
8168
|
+
window.addEventListener("pointercancel", onCancel, true);
|
|
8169
|
+
window.addEventListener("blur", onCancel);
|
|
8170
|
+
this.endToolbarDrag = () => {
|
|
8171
|
+
window.removeEventListener("pointermove", onMove, true);
|
|
8172
|
+
window.removeEventListener("pointerup", onUp, true);
|
|
8173
|
+
window.removeEventListener("pointercancel", onCancel, true);
|
|
8174
|
+
window.removeEventListener("blur", onCancel);
|
|
8175
|
+
delete this.host.dataset.dragging;
|
|
8176
|
+
this.toolbarDrag = null;
|
|
8177
|
+
this.endToolbarDrag = null;
|
|
8178
|
+
};
|
|
8179
|
+
}
|
|
8180
|
+
/** Glide the toolbar to the spot nearest where it was let go, or back. */
|
|
8181
|
+
dropToolbar(el, snap) {
|
|
8182
|
+
this.toolbarDropped = true;
|
|
8183
|
+
setTimeout(() => this.toolbarDropped = false);
|
|
8184
|
+
if (!el) return;
|
|
8185
|
+
const from = el.getBoundingClientRect();
|
|
8186
|
+
if (snap) {
|
|
8187
|
+
const position = nearestPosition(from.left + from.width / 2, from.top + from.height / 2);
|
|
8188
|
+
this.position = position;
|
|
8189
|
+
this.host.dataset.position = position;
|
|
8190
|
+
write(KEYS.position(this.config.project), position === this.config.position ? null : position);
|
|
8191
|
+
}
|
|
8192
|
+
el.removeAttribute("data-dragging");
|
|
8193
|
+
el.style.removeProperty("transform");
|
|
8194
|
+
const to = el.getBoundingClientRect();
|
|
8195
|
+
if (matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
8196
|
+
el.animate([{ transform: `translate(${from.left - to.left}px, ${from.top - to.top}px)` }, { transform: "none" }], {
|
|
8197
|
+
duration: 320,
|
|
8198
|
+
easing: "cubic-bezier(0.2, 0.9, 0.3, 1.15)"
|
|
8199
|
+
});
|
|
8200
|
+
}
|
|
7884
8201
|
renderPanel() {
|
|
7885
8202
|
const open = this.comments.filter((c) => c.status === "open");
|
|
7886
8203
|
const resolved = this.comments.filter((c) => c.status === "resolved");
|
|
7887
|
-
const
|
|
8204
|
+
const query = this.search.trim().toLocaleLowerCase();
|
|
8205
|
+
const matches = (...texts) => !query || texts.some((t) => t?.toLocaleLowerCase().includes(query));
|
|
8206
|
+
const list = (this.tab === "open" ? open : resolved).filter((c) => matches(c.body, c.authorName, c.anchor.quote?.exact));
|
|
7888
8207
|
const placed = list.filter((c) => this.placements.get(c._id)?.element);
|
|
7889
8208
|
const lost = list.filter((c) => !this.placements.get(c._id)?.element);
|
|
7890
|
-
const otherPages = this.pages.filter((p) => p.path !== this.pageKey);
|
|
8209
|
+
const otherPages = this.pages.filter((p) => p.path !== this.pageKey && matches(p.path));
|
|
7891
8210
|
const item = (c) => h("button", {
|
|
7892
8211
|
class: "item",
|
|
7893
8212
|
type: "button",
|
|
8213
|
+
"aria-current": c._id === this.lastViewedId ? "true" : null,
|
|
7894
8214
|
"data-focus-key": `item-${c._id}`,
|
|
7895
8215
|
onclick: () => this.focusComment(c._id)
|
|
7896
8216
|
}, h("div", { class: "row" }, h("span", {
|
|
@@ -7902,6 +8222,7 @@ var NuniWidget = class {
|
|
|
7902
8222
|
}, new URL(c.page.origin).host) : null), c.anchor.quote ? h("div", { class: "item-quote" }, c.anchor.quote.exact) : null, h("div", { class: "item-text" }, c.body));
|
|
7903
8223
|
const children = [];
|
|
7904
8224
|
if (!this.loaded) children.push(h("div", { class: "empty" }, this.t("loading")));
|
|
8225
|
+
else if (!list.length && query) children.push(h("div", { class: "empty" }, this.t("noMatches", { query: this.search.trim() })));
|
|
7905
8226
|
else if (!list.length) children.push(h("div", { class: "empty" }, this.tab === "open" ? this.withKey("noOpen", "noOpenKey") : this.t("noResolved")));
|
|
7906
8227
|
else {
|
|
7907
8228
|
children.push(...placed.map(item));
|
|
@@ -7929,7 +8250,19 @@ var NuniWidget = class {
|
|
|
7929
8250
|
type: "button",
|
|
7930
8251
|
"aria-label": this.t("close"),
|
|
7931
8252
|
onclick: () => this.togglePanel(false)
|
|
7932
|
-
}, icon(ICONS.close))), h("div", {
|
|
8253
|
+
}, icon(ICONS.close))), this.comments.length ? h("div", { class: "panel-search" }, icon(ICONS.search), h("input", {
|
|
8254
|
+
class: "field",
|
|
8255
|
+
type: "search",
|
|
8256
|
+
value: this.search,
|
|
8257
|
+
placeholder: this.t("searchComments"),
|
|
8258
|
+
"aria-label": this.t("searchComments"),
|
|
8259
|
+
"aria-controls": "nuni-panel-list",
|
|
8260
|
+
"data-focus-key": "panel-search",
|
|
8261
|
+
oninput: (e) => {
|
|
8262
|
+
this.search = e.target.value;
|
|
8263
|
+
this.render();
|
|
8264
|
+
}
|
|
8265
|
+
})) : null, h("div", {
|
|
7933
8266
|
class: "tabs",
|
|
7934
8267
|
role: "tablist",
|
|
7935
8268
|
"aria-label": this.t("comments"),
|
|
@@ -7973,80 +8306,112 @@ var NuniWidget = class {
|
|
|
7973
8306
|
onclick: () => this.openClaim()
|
|
7974
8307
|
}, icon(ICONS.github), this.t("claim"))));
|
|
7975
8308
|
}
|
|
8309
|
+
renderComposer(draft, cls) {
|
|
8310
|
+
const needsName = !this.name;
|
|
8311
|
+
const avatar = h("span", {
|
|
8312
|
+
class: "avatar cmp-avatar",
|
|
8313
|
+
"aria-hidden": "true"
|
|
8314
|
+
});
|
|
8315
|
+
const paintAvatar = (name) => {
|
|
8316
|
+
const clean = name.trim();
|
|
8317
|
+
avatar.classList.toggle("avatar-empty", !clean);
|
|
8318
|
+
if (clean) {
|
|
8319
|
+
avatar.textContent = initials(clean);
|
|
8320
|
+
avatar.style.background = colorFor(clean);
|
|
8321
|
+
} else {
|
|
8322
|
+
avatar.replaceChildren(icon(ICONS.user));
|
|
8323
|
+
avatar.style.background = "";
|
|
8324
|
+
}
|
|
8325
|
+
};
|
|
8326
|
+
const nameInput = h("input", {
|
|
8327
|
+
class: "cmp-name",
|
|
8328
|
+
placeholder: this.t("yourName"),
|
|
8329
|
+
value: this.name,
|
|
8330
|
+
maxlength: LIMITS.nameMaxLength,
|
|
8331
|
+
autocomplete: "name",
|
|
8332
|
+
"data-focus-key": "name",
|
|
8333
|
+
"aria-label": this.t("yourName")
|
|
8334
|
+
});
|
|
8335
|
+
const textarea = h("textarea", {
|
|
8336
|
+
class: "cmp-text",
|
|
8337
|
+
placeholder: this.t("leaveComment"),
|
|
8338
|
+
maxlength: LIMITS.bodyMaxLength,
|
|
8339
|
+
rows: 3,
|
|
8340
|
+
"data-focus-key": "body",
|
|
8341
|
+
"aria-label": this.t("comment")
|
|
8342
|
+
});
|
|
8343
|
+
const prev = this.uiLayer.querySelector("[data-focus-key=\"body\"]");
|
|
8344
|
+
if (prev) textarea.value = prev.value;
|
|
8345
|
+
const prevName = this.uiLayer.querySelector("[data-focus-key=\"name\"]");
|
|
8346
|
+
if (prevName) nameInput.value = prevName.value;
|
|
8347
|
+
paintAvatar(needsName ? nameInput.value : this.name);
|
|
8348
|
+
nameInput.addEventListener("input", () => paintAvatar(nameInput.value));
|
|
8349
|
+
const counter = h("span", {
|
|
8350
|
+
class: "cmp-count",
|
|
8351
|
+
"aria-live": "polite"
|
|
8352
|
+
});
|
|
8353
|
+
const post = h("button", {
|
|
8354
|
+
class: "cmp-post",
|
|
8355
|
+
type: "submit",
|
|
8356
|
+
disabled: Boolean(draft.sending) || !textarea.value.trim()
|
|
8357
|
+
}, draft.sending ? this.t("posting") : this.t("post"), icon(ICONS.arrowUp));
|
|
8358
|
+
const update = () => {
|
|
8359
|
+
post.disabled = Boolean(draft.sending) || !textarea.value.trim();
|
|
8360
|
+
const left = LIMITS.bodyMaxLength - textarea.value.length;
|
|
8361
|
+
counter.textContent = left <= LIMITS.bodyMaxLength * .1 ? this.t("charsLeft", { count: left }) : "";
|
|
8362
|
+
textarea.style.height = "auto";
|
|
8363
|
+
textarea.style.height = `${textarea.scrollHeight + 2}px`;
|
|
8364
|
+
};
|
|
8365
|
+
textarea.addEventListener("input", () => {
|
|
8366
|
+
update();
|
|
8367
|
+
this.positionCard();
|
|
8368
|
+
});
|
|
8369
|
+
requestAnimationFrame(() => {
|
|
8370
|
+
if (!textarea.isConnected) return;
|
|
8371
|
+
update();
|
|
8372
|
+
this.positionCard();
|
|
8373
|
+
});
|
|
8374
|
+
const submit = () => void this.submitDraft(draft, textarea.value, needsName ? nameInput.value : this.name);
|
|
8375
|
+
textarea.addEventListener("keydown", (e) => {
|
|
8376
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
8377
|
+
e.preventDefault();
|
|
8378
|
+
submit();
|
|
8379
|
+
}
|
|
8380
|
+
});
|
|
8381
|
+
return h("form", {
|
|
8382
|
+
class: `${cls} composer`,
|
|
8383
|
+
"data-card": "composer",
|
|
8384
|
+
onsubmit: (e) => {
|
|
8385
|
+
e.preventDefault();
|
|
8386
|
+
submit();
|
|
8387
|
+
}
|
|
8388
|
+
}, h("div", { class: "cmp-head" }, avatar, needsName ? nameInput : h("span", { class: "author" }, this.name), h("span", { class: "spacer" }), needsName ? null : h("button", {
|
|
8389
|
+
class: "cmp-link",
|
|
8390
|
+
type: "button",
|
|
8391
|
+
onclick: () => {
|
|
8392
|
+
this.name = "";
|
|
8393
|
+
this.render();
|
|
8394
|
+
this.focusComposer();
|
|
8395
|
+
}
|
|
8396
|
+
}, this.t("notYou"))), draft.anchor.quote ? h("blockquote", { class: "quote" }, draft.anchor.quote.exact) : null, textarea, draft.error ? h("div", {
|
|
8397
|
+
class: "error cmp-error",
|
|
8398
|
+
role: "alert"
|
|
8399
|
+
}, draft.error) : null, h("div", { class: "cmp-foot" }, h("span", {
|
|
8400
|
+
class: "cmp-keys",
|
|
8401
|
+
title: this.t("send", { key: this.i18n.mod })
|
|
8402
|
+
}, h("kbd", {}, this.i18n.mod), h("kbd", {}, "↵")), counter, h("span", { class: "spacer" }), post), h("button", {
|
|
8403
|
+
class: "icon-btn cmp-close",
|
|
8404
|
+
type: "button",
|
|
8405
|
+
"aria-label": this.t("cancel"),
|
|
8406
|
+
title: this.t("cancel"),
|
|
8407
|
+
onclick: () => this.closeCard()
|
|
8408
|
+
}, icon(ICONS.close)));
|
|
8409
|
+
}
|
|
7976
8410
|
renderCard() {
|
|
7977
8411
|
const card = this.card;
|
|
7978
8412
|
if (!card) return null;
|
|
7979
8413
|
const cls = `card${window.matchMedia(MOBILE_QUERY).matches ? " sheet" : ""}`;
|
|
7980
|
-
if (card.kind === "composer")
|
|
7981
|
-
const { draft } = card;
|
|
7982
|
-
const needsName = !this.name;
|
|
7983
|
-
const nameInput = h("input", {
|
|
7984
|
-
class: "field",
|
|
7985
|
-
placeholder: this.t("yourName"),
|
|
7986
|
-
value: this.name,
|
|
7987
|
-
maxlength: LIMITS.nameMaxLength,
|
|
7988
|
-
autocomplete: "name",
|
|
7989
|
-
"data-focus-key": "name",
|
|
7990
|
-
"aria-label": this.t("yourName")
|
|
7991
|
-
});
|
|
7992
|
-
const textarea = h("textarea", {
|
|
7993
|
-
class: "field",
|
|
7994
|
-
placeholder: this.t("leaveComment"),
|
|
7995
|
-
maxlength: LIMITS.bodyMaxLength,
|
|
7996
|
-
"data-focus-key": "body",
|
|
7997
|
-
"aria-label": this.t("comment")
|
|
7998
|
-
});
|
|
7999
|
-
const prev = this.uiLayer.querySelector("[data-focus-key=\"body\"]");
|
|
8000
|
-
if (prev) textarea.value = prev.value;
|
|
8001
|
-
const prevName = this.uiLayer.querySelector("[data-focus-key=\"name\"]");
|
|
8002
|
-
if (prevName) nameInput.value = prevName.value;
|
|
8003
|
-
const submit = () => void this.submitDraft(draft, textarea.value, needsName ? nameInput.value : this.name);
|
|
8004
|
-
textarea.addEventListener("keydown", (e) => {
|
|
8005
|
-
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
8006
|
-
e.preventDefault();
|
|
8007
|
-
submit();
|
|
8008
|
-
}
|
|
8009
|
-
});
|
|
8010
|
-
return h("form", {
|
|
8011
|
-
class: cls,
|
|
8012
|
-
"data-card": "composer",
|
|
8013
|
-
onsubmit: (e) => {
|
|
8014
|
-
e.preventDefault();
|
|
8015
|
-
submit();
|
|
8016
|
-
}
|
|
8017
|
-
}, h("div", { class: "card-body" }, needsName ? nameInput : h("div", { class: "row" }, h("span", {
|
|
8018
|
-
class: "avatar",
|
|
8019
|
-
style: `background:${colorFor(this.name)}`
|
|
8020
|
-
}, initials(this.name)), h("span", { class: "author" }, this.name), h("span", { class: "spacer" }), h("button", {
|
|
8021
|
-
class: "link",
|
|
8022
|
-
type: "button",
|
|
8023
|
-
onclick: () => {
|
|
8024
|
-
this.name = "";
|
|
8025
|
-
this.render();
|
|
8026
|
-
}
|
|
8027
|
-
}, 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", {
|
|
8028
|
-
src: draft.screenshot.preview,
|
|
8029
|
-
alt: this.t("screenshotAlt")
|
|
8030
|
-
}), h("div", { class: "shot-note" }, h("span", {}, this.t("screenshotNote")), h("span", { class: "spacer" }), h("button", {
|
|
8031
|
-
class: "link",
|
|
8032
|
-
type: "button",
|
|
8033
|
-
onclick: () => {
|
|
8034
|
-
draft.screenshot = void 0;
|
|
8035
|
-
this.render();
|
|
8036
|
-
}
|
|
8037
|
-
}, this.t("remove")))) : null, draft.error ? h("div", {
|
|
8038
|
-
class: "error",
|
|
8039
|
-
role: "alert"
|
|
8040
|
-
}, draft.error) : null, h("div", { class: "row" }, h("span", { class: "kbd" }, this.t("send", { key: this.i18n.mod })), h("span", { class: "spacer" }), h("button", {
|
|
8041
|
-
class: "btn btn-ghost",
|
|
8042
|
-
type: "button",
|
|
8043
|
-
onclick: () => this.closeCard()
|
|
8044
|
-
}, this.t("cancel")), h("button", {
|
|
8045
|
-
class: "btn btn-primary",
|
|
8046
|
-
type: "submit",
|
|
8047
|
-
disabled: Boolean(draft.sending)
|
|
8048
|
-
}, icon(ICONS.send), draft.sending ? this.t("posting") : this.t("post")))));
|
|
8049
|
-
}
|
|
8414
|
+
if (card.kind === "composer") return this.renderComposer(card.draft, cls);
|
|
8050
8415
|
const comment = this.comments.find((c) => c._id === card.id);
|
|
8051
8416
|
if (!comment) return null;
|
|
8052
8417
|
const mine = this.isMine(comment);
|
|
@@ -8074,61 +8439,55 @@ var NuniWidget = class {
|
|
|
8074
8439
|
disabled: Boolean(card.busy),
|
|
8075
8440
|
onclick: () => this.saveEdit(comment, textarea.value)
|
|
8076
8441
|
}, this.t("save"))));
|
|
8077
|
-
} else
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
class: "
|
|
8096
|
-
|
|
8097
|
-
}, new URL(comment.page.origin).host) : null, placement?.confidence === "low" ? h("span", {
|
|
8098
|
-
class: "badge",
|
|
8099
|
-
title: this.t("approximateHint")
|
|
8100
|
-
}, this.t("approximate")) : null, !placement?.element ? h("span", {
|
|
8101
|
-
class: "badge",
|
|
8102
|
-
title: this.t("originallyOn", { text: comment.anchor.text || comment.anchor.tag })
|
|
8103
|
-
}, this.t("notFound")) : null), this.renderReactions(comment, comment._id), card.error ? h("div", {
|
|
8104
|
-
class: "error",
|
|
8105
|
-
role: "alert"
|
|
8106
|
-
}, card.error) : null);
|
|
8107
|
-
const actions = [];
|
|
8108
|
-
if (!card.editing) {
|
|
8109
|
-
if (owner) actions.push(comment.status === "open" ? h("button", {
|
|
8110
|
-
class: "btn",
|
|
8442
|
+
} else {
|
|
8443
|
+
const badges = [
|
|
8444
|
+
comment.status === "resolved" ? h("span", { class: "badge badge-ok" }, icon(ICONS.check), this.t("resolved")) : null,
|
|
8445
|
+
comment.page.origin !== location.origin ? h("span", {
|
|
8446
|
+
class: "badge",
|
|
8447
|
+
title: this.t("leftOn", { origin: comment.page.origin })
|
|
8448
|
+
}, new URL(comment.page.origin).host) : null,
|
|
8449
|
+
placement?.confidence === "low" ? h("span", {
|
|
8450
|
+
class: "badge",
|
|
8451
|
+
title: this.t("approximateHint")
|
|
8452
|
+
}, this.t("approximate")) : null,
|
|
8453
|
+
!placement?.element ? h("span", {
|
|
8454
|
+
class: "badge",
|
|
8455
|
+
title: this.t("originallyOn", { text: comment.anchor.text || comment.anchor.tag })
|
|
8456
|
+
}, this.t("notFound")) : null
|
|
8457
|
+
].filter((badge) => badge !== null);
|
|
8458
|
+
const contextPending = owner && !(this.ownerDetailLoaded && this.ownerDetailId === comment._id);
|
|
8459
|
+
const resolved = comment.status === "resolved";
|
|
8460
|
+
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", {
|
|
8461
|
+
class: `icon-btn${resolved ? " is-resolved" : ""}`,
|
|
8111
8462
|
type: "button",
|
|
8463
|
+
"aria-label": this.t(resolved ? "reopen" : "resolve"),
|
|
8464
|
+
title: this.t(resolved ? "reopen" : "resolve"),
|
|
8112
8465
|
disabled: Boolean(card.busy),
|
|
8113
|
-
onclick: () => this.resolveComment(comment)
|
|
8114
|
-
}, icon(ICONS.check)
|
|
8115
|
-
class: "btn",
|
|
8466
|
+
onclick: () => resolved ? this.reopenComment(comment) : this.resolveComment(comment)
|
|
8467
|
+
}, icon(ICONS.check)) : null, h("button", {
|
|
8468
|
+
class: "icon-btn card-close",
|
|
8116
8469
|
type: "button",
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8470
|
+
"aria-label": this.t("close"),
|
|
8471
|
+
title: this.t("close"),
|
|
8472
|
+
onclick: () => this.closeCard()
|
|
8473
|
+
}, 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", {
|
|
8474
|
+
class: "shot",
|
|
8475
|
+
href: this.ownerDetail.screenshotUrl,
|
|
8476
|
+
target: "_blank",
|
|
8477
|
+
rel: "noreferrer",
|
|
8478
|
+
title: this.t("openScreenshot")
|
|
8479
|
+
}, h("img", {
|
|
8480
|
+
src: this.ownerDetail.screenshotUrl,
|
|
8481
|
+
alt: this.t("screenshotTaken")
|
|
8482
|
+
})) : 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", {
|
|
8483
|
+
class: "icon-btn",
|
|
8124
8484
|
type: "button",
|
|
8125
8485
|
"aria-label": this.t("copyForAgent"),
|
|
8126
8486
|
title: contextPending ? this.t("loadingContext") : this.t("copyForAgentHint"),
|
|
8127
8487
|
disabled: contextPending,
|
|
8128
8488
|
onclick: () => this.copyForAgent(comment)
|
|
8129
|
-
}, icon(ICONS.bot))
|
|
8130
|
-
|
|
8131
|
-
class: "btn btn-ghost btn-icon",
|
|
8489
|
+
}, icon(ICONS.bot)), mine ? h("button", {
|
|
8490
|
+
class: "icon-btn",
|
|
8132
8491
|
type: "button",
|
|
8133
8492
|
"aria-label": this.t("edit"),
|
|
8134
8493
|
title: this.t("edit"),
|
|
@@ -8136,30 +8495,36 @@ var NuniWidget = class {
|
|
|
8136
8495
|
card.editing = true;
|
|
8137
8496
|
this.render();
|
|
8138
8497
|
}
|
|
8139
|
-
}, icon(ICONS.edit))
|
|
8140
|
-
|
|
8141
|
-
class: "btn btn-ghost btn-icon btn-danger",
|
|
8498
|
+
}, icon(ICONS.edit)) : null, mine || owner ? h("button", {
|
|
8499
|
+
class: "icon-btn icon-btn-danger",
|
|
8142
8500
|
type: "button",
|
|
8143
8501
|
"aria-label": this.t("delete"),
|
|
8144
8502
|
title: this.t("delete"),
|
|
8145
8503
|
"data-focus-key": "delete",
|
|
8146
8504
|
disabled: Boolean(card.busy),
|
|
8147
8505
|
onclick: () => void this.deleteComment(comment)
|
|
8148
|
-
}, icon(ICONS.trash)))
|
|
8506
|
+
}, icon(ICONS.trash)) : null), card.error ? h("div", {
|
|
8507
|
+
class: "error",
|
|
8508
|
+
role: "alert"
|
|
8509
|
+
}, card.error) : null));
|
|
8149
8510
|
}
|
|
8150
8511
|
return h("div", {
|
|
8151
8512
|
class: cls,
|
|
8152
8513
|
"data-card": "thread",
|
|
8153
8514
|
role: "dialog",
|
|
8154
8515
|
"aria-label": this.t("commentBy", { name: comment.authorName })
|
|
8155
|
-
}, body,
|
|
8516
|
+
}, body, card.editing ? null : this.renderThread(comment));
|
|
8156
8517
|
}
|
|
8157
|
-
/**
|
|
8158
|
-
|
|
8518
|
+
/**
|
|
8519
|
+
* Emoji chips for one message and the picker to add one. Replies keep
|
|
8520
|
+
* their add button with their other tools, so `withAdd` is off for them.
|
|
8521
|
+
*/
|
|
8522
|
+
renderReactions(comment, targetId, withAdd = true) {
|
|
8159
8523
|
const card = this.card;
|
|
8160
8524
|
if (card?.kind !== "thread") return null;
|
|
8161
8525
|
const summaries = this.threadId === comment._id && this.thread ? this.thread.reactions.filter((r) => r.targetId === targetId) : [];
|
|
8162
8526
|
const open = card.picker === targetId;
|
|
8527
|
+
if (!withAdd && !summaries.length && !open) return null;
|
|
8163
8528
|
return h("div", { class: "reactions" }, ...summaries.map((r) => {
|
|
8164
8529
|
const mine = r.authorKeyHashes.includes(this.secretHash);
|
|
8165
8530
|
return h("button", {
|
|
@@ -8169,17 +8534,7 @@ var NuniWidget = class {
|
|
|
8169
8534
|
"aria-label": `${r.emoji} ${r.count}${mine ? ", including you" : ""}`,
|
|
8170
8535
|
onclick: () => this.toggleReaction(comment, targetId, r.emoji)
|
|
8171
8536
|
}, `${r.emoji} ${r.count}`);
|
|
8172
|
-
}), h("
|
|
8173
|
-
class: "reaction reaction-add",
|
|
8174
|
-
type: "button",
|
|
8175
|
-
"aria-label": this.t("addReaction"),
|
|
8176
|
-
"aria-expanded": String(open),
|
|
8177
|
-
title: this.t("addReaction"),
|
|
8178
|
-
onclick: () => {
|
|
8179
|
-
card.picker = open ? null : targetId;
|
|
8180
|
-
this.render();
|
|
8181
|
-
}
|
|
8182
|
-
}, icon(ICONS.smile)), open ? h("div", {
|
|
8537
|
+
}), withAdd ? this.reactionAdd(targetId, "reaction reaction-add") : null, open ? h("div", {
|
|
8183
8538
|
class: "reaction-picker",
|
|
8184
8539
|
role: "group",
|
|
8185
8540
|
"aria-label": this.t("reactions")
|
|
@@ -8194,6 +8549,22 @@ var NuniWidget = class {
|
|
|
8194
8549
|
}
|
|
8195
8550
|
}, emoji))) : null);
|
|
8196
8551
|
}
|
|
8552
|
+
reactionAdd(targetId, cls) {
|
|
8553
|
+
const card = this.card;
|
|
8554
|
+
if (card?.kind !== "thread") return null;
|
|
8555
|
+
const open = card.picker === targetId;
|
|
8556
|
+
return h("button", {
|
|
8557
|
+
class: cls,
|
|
8558
|
+
type: "button",
|
|
8559
|
+
"aria-label": this.t("addReaction"),
|
|
8560
|
+
"aria-expanded": String(open),
|
|
8561
|
+
title: this.t("addReaction"),
|
|
8562
|
+
onclick: () => {
|
|
8563
|
+
card.picker = open ? null : targetId;
|
|
8564
|
+
this.render();
|
|
8565
|
+
}
|
|
8566
|
+
}, icon(ICONS.smile));
|
|
8567
|
+
}
|
|
8197
8568
|
/** The replies under a comment and the box to add one. */
|
|
8198
8569
|
renderThread(comment) {
|
|
8199
8570
|
const card = this.card;
|
|
@@ -8226,13 +8597,10 @@ var NuniWidget = class {
|
|
|
8226
8597
|
}, this.t("save"))));
|
|
8227
8598
|
}
|
|
8228
8599
|
return h("div", {
|
|
8229
|
-
class: "reply",
|
|
8600
|
+
class: "reply msg",
|
|
8230
8601
|
"data-reply": reply._id
|
|
8231
|
-
}, h("div", { class: "card-head" }, h("span", {
|
|
8232
|
-
class: "
|
|
8233
|
-
style: `background:${colorFor(reply.authorName)}`
|
|
8234
|
-
}, 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", {
|
|
8235
|
-
class: "btn btn-ghost btn-icon btn-xs",
|
|
8602
|
+
}, 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", {
|
|
8603
|
+
class: "icon-btn",
|
|
8236
8604
|
type: "button",
|
|
8237
8605
|
"aria-label": this.t("editReply"),
|
|
8238
8606
|
title: this.t("edit"),
|
|
@@ -8241,18 +8609,18 @@ var NuniWidget = class {
|
|
|
8241
8609
|
this.render();
|
|
8242
8610
|
}
|
|
8243
8611
|
}, icon(ICONS.edit)) : null, mine || owner ? h("button", {
|
|
8244
|
-
class: "btn
|
|
8612
|
+
class: "icon-btn icon-btn-danger",
|
|
8245
8613
|
type: "button",
|
|
8246
8614
|
"aria-label": this.t("deleteReply"),
|
|
8247
8615
|
title: this.t("delete"),
|
|
8248
8616
|
"data-focus-key": `delete-${reply._id}`,
|
|
8249
8617
|
disabled: Boolean(card.busy),
|
|
8250
8618
|
onclick: () => void this.deleteReply(comment, reply)
|
|
8251
|
-
}, icon(ICONS.trash)) : null), h("div", { class: "comment-body" }, reply.body), this.renderReactions(comment, reply._id));
|
|
8619
|
+
}, icon(ICONS.trash)) : null)), h("div", { class: "msg-main" }, h("div", { class: "comment-body" }, reply.body), this.renderReactions(comment, reply._id, false)));
|
|
8252
8620
|
});
|
|
8253
8621
|
const needsName = !owner && !this.name;
|
|
8254
8622
|
const nameInput = h("input", {
|
|
8255
|
-
class: "
|
|
8623
|
+
class: "reply-name",
|
|
8256
8624
|
placeholder: this.t("yourName"),
|
|
8257
8625
|
value: this.name,
|
|
8258
8626
|
maxlength: LIMITS.nameMaxLength,
|
|
@@ -8263,14 +8631,31 @@ var NuniWidget = class {
|
|
|
8263
8631
|
const prevName = this.uiLayer.querySelector("[data-focus-key=\"reply-name\"]");
|
|
8264
8632
|
if (prevName) nameInput.value = prevName.value;
|
|
8265
8633
|
const box = h("textarea", {
|
|
8266
|
-
class: "
|
|
8634
|
+
class: "reply-text",
|
|
8267
8635
|
placeholder: replies.length ? this.t("reply") : this.t("replyFirst"),
|
|
8268
8636
|
maxlength: LIMITS.bodyMaxLength,
|
|
8637
|
+
rows: 1,
|
|
8269
8638
|
"data-focus-key": "reply",
|
|
8270
8639
|
"aria-label": this.t("reply")
|
|
8271
8640
|
});
|
|
8272
8641
|
const prev = this.uiLayer.querySelector("[data-focus-key=\"reply\"]");
|
|
8273
8642
|
if (prev) box.value = prev.value;
|
|
8643
|
+
const sendButton = h("button", {
|
|
8644
|
+
class: "reply-send",
|
|
8645
|
+
type: "submit",
|
|
8646
|
+
"aria-label": this.t("sendReply"),
|
|
8647
|
+
title: this.t("sendReply"),
|
|
8648
|
+
disabled: Boolean(card.sendingReply) || !box.value.trim()
|
|
8649
|
+
}, icon(ICONS.arrowUp));
|
|
8650
|
+
const grow = () => {
|
|
8651
|
+
sendButton.disabled = Boolean(card.sendingReply) || !box.value.trim();
|
|
8652
|
+
box.style.height = "auto";
|
|
8653
|
+
box.style.height = `${box.scrollHeight}px`;
|
|
8654
|
+
};
|
|
8655
|
+
box.addEventListener("input", grow);
|
|
8656
|
+
requestAnimationFrame(() => {
|
|
8657
|
+
if (box.isConnected) grow();
|
|
8658
|
+
});
|
|
8274
8659
|
const send = () => void this.sendReply(comment, box.value, needsName ? nameInput.value : this.name);
|
|
8275
8660
|
box.addEventListener("keydown", (e) => {
|
|
8276
8661
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
@@ -8291,13 +8676,7 @@ var NuniWidget = class {
|
|
|
8291
8676
|
e.preventDefault();
|
|
8292
8677
|
send();
|
|
8293
8678
|
}
|
|
8294
|
-
}, needsName ? nameInput : null,
|
|
8295
|
-
class: "btn btn-primary btn-icon",
|
|
8296
|
-
type: "submit",
|
|
8297
|
-
"aria-label": this.t("sendReply"),
|
|
8298
|
-
title: this.t("sendReply"),
|
|
8299
|
-
disabled: Boolean(card.sendingReply)
|
|
8300
|
-
}, icon(ICONS.send))));
|
|
8679
|
+
}, needsName ? nameInput : null, h("div", { class: "reply-row" }, box, sendButton)));
|
|
8301
8680
|
}
|
|
8302
8681
|
positionCard() {
|
|
8303
8682
|
const el = this.uiLayer.querySelector("[data-card]");
|
|
@@ -8321,8 +8700,8 @@ var NuniWidget = class {
|
|
|
8321
8700
|
top = point.y - 28;
|
|
8322
8701
|
if (left + width > vw - 12) left = point.x - width - 12;
|
|
8323
8702
|
} else {
|
|
8324
|
-
const { position } = this
|
|
8325
|
-
left = position.endsWith("left") ? 16 : vw - width - 16;
|
|
8703
|
+
const { position } = this;
|
|
8704
|
+
left = position.endsWith("left") ? 16 : position.endsWith("center") ? (vw - width) / 2 : vw - width - 16;
|
|
8326
8705
|
top = position.startsWith("top") ? 72 : vh - height - 72;
|
|
8327
8706
|
}
|
|
8328
8707
|
left = Math.max(12, Math.min(left, vw - width - 12));
|
|
@@ -8336,6 +8715,22 @@ var NuniWidget = class {
|
|
|
8336
8715
|
});
|
|
8337
8716
|
}
|
|
8338
8717
|
};
|
|
8718
|
+
/** The toolbar spot nearest a point: thirds across the screen, halves down. */
|
|
8719
|
+
function nearestPosition(x, y) {
|
|
8720
|
+
const width = window.innerWidth;
|
|
8721
|
+
const across = x < width / 3 ? "left" : x > width * 2 / 3 ? "right" : "center";
|
|
8722
|
+
return `${y < window.innerHeight / 2 ? "top" : "bottom"}-${across}`;
|
|
8723
|
+
}
|
|
8724
|
+
/** A press on a scrollbar, which scrolls rather than points at the page. */
|
|
8725
|
+
function onScrollbar(e) {
|
|
8726
|
+
const el = e.composedPath()[0];
|
|
8727
|
+
if (!(el instanceof Element)) return false;
|
|
8728
|
+
const root = document.documentElement;
|
|
8729
|
+
if (el === root) return e.clientX >= root.clientWidth || e.clientY >= root.clientHeight;
|
|
8730
|
+
if (!(el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth)) return false;
|
|
8731
|
+
const box = el.getBoundingClientRect();
|
|
8732
|
+
return e.clientX - box.left - el.clientLeft >= el.clientWidth || e.clientY - box.top - el.clientTop >= el.clientHeight;
|
|
8733
|
+
}
|
|
8339
8734
|
//#endregion
|
|
8340
8735
|
//#region src/mount.ts
|
|
8341
8736
|
/** Nuni's own requests are not the page's problem. */
|