@loupekit/sdk 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.global.js +213 -31
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +213 -31
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -390,11 +390,35 @@ var STYLES = (
|
|
|
390
390
|
.hfeed-p { font-weight: 700; }
|
|
391
391
|
.hfeed-p-critical { color: var(--pin); }
|
|
392
392
|
.hfeed-p-high { color: #e0a92c; }
|
|
393
|
+
.hfeed-a { display: flex; gap: 4px; margin-top: 3px; }
|
|
394
|
+
.hfeed-act {
|
|
395
|
+
font-size: 11px; padding: 3px 8px; border: 1px solid var(--line); border-radius: 6px;
|
|
396
|
+
background: var(--bg-3); color: var(--ink); cursor: pointer;
|
|
397
|
+
}
|
|
398
|
+
.hfeed-act:hover { border-color: var(--accent); }
|
|
399
|
+
.hfeed-act.danger:hover, .hfeed-act.danger[data-armed] { border-color: var(--pin); color: var(--pin); }
|
|
393
400
|
.hempty { padding: 14px; text-align: center; color: var(--muted); font-size: 12px; }
|
|
394
401
|
.hfoot { margin-top: 14px; padding-top: 10px; border-top: 1px solid var(--line); font-size: 11px; color: var(--muted); text-align: center; }
|
|
395
402
|
|
|
396
403
|
/* Timeline grouping (project scope) + the repo filter. */
|
|
397
|
-
.daylabel {
|
|
404
|
+
.daylabel {
|
|
405
|
+
display: flex; align-items: center; gap: 6px; width: 100%; margin: 8px 0 0; padding: 4px 2px;
|
|
406
|
+
border: 0; background: none; cursor: pointer; text-align: left;
|
|
407
|
+
font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); font-weight: 700;
|
|
408
|
+
}
|
|
409
|
+
.daylabel::before { content: "\u25BE"; font-size: 10px; width: 10px; }
|
|
410
|
+
.daylabel.shut::before { content: "\u25B8"; }
|
|
411
|
+
.daylabel::after {
|
|
412
|
+
content: attr(data-n); padding: 0 6px; border-radius: 999px; background: var(--bg-3);
|
|
413
|
+
font-size: 10px; letter-spacing: 0;
|
|
414
|
+
}
|
|
415
|
+
.daylabel:hover { color: var(--ink); }
|
|
416
|
+
.listfilters { display: flex; gap: 6px; margin: 6px 0 2px; }
|
|
417
|
+
.listfilters select {
|
|
418
|
+
flex: 1; min-width: 0; font-size: 12px; padding: 4px 6px; border: 1px solid var(--line); border-radius: 7px;
|
|
419
|
+
background: var(--bg-2); color: var(--ink); outline: none; cursor: pointer;
|
|
420
|
+
}
|
|
421
|
+
.listfilters select:focus { border-color: var(--accent); }
|
|
398
422
|
/* The page a comment belongs to, shown in the project scope (Settings \u2192 Page paths). */
|
|
399
423
|
.pathtag {
|
|
400
424
|
padding: 1px 6px; border-radius: 999px; background: var(--bg-3); color: var(--muted);
|
|
@@ -3946,7 +3970,7 @@ var HttpAdapter = class {
|
|
|
3946
3970
|
};
|
|
3947
3971
|
|
|
3948
3972
|
// src/app.ts
|
|
3949
|
-
var SDK_VERSION = true ? "0.
|
|
3973
|
+
var SDK_VERSION = true ? "0.14.0" : "dev";
|
|
3950
3974
|
var FAB_SIZE = 46;
|
|
3951
3975
|
var FAB_DRAG_THRESHOLD = 6;
|
|
3952
3976
|
var LAUNCHER_SHORTCUT = "Alt+Shift+L";
|
|
@@ -4010,6 +4034,7 @@ var BUILTIN_TABS = [
|
|
|
4010
4034
|
var DOCK_MODES = ["left", "right", "bottom", "float"];
|
|
4011
4035
|
var RECORD_MAX_MS = 2e4;
|
|
4012
4036
|
var MAX_FILES = 10;
|
|
4037
|
+
var SYNC_POLL_MS = 1e4;
|
|
4013
4038
|
var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
4014
4039
|
var MAX_VIDEO_BYTES = 25 * 1024 * 1024;
|
|
4015
4040
|
var uid2 = () => crypto.randomUUID ? crypto.randomUUID() : "c_" + Math.abs(hash(String(performance.now()))).toString(36);
|
|
@@ -4100,6 +4125,11 @@ var LoupeApp = class {
|
|
|
4100
4125
|
this.allLoaded = false;
|
|
4101
4126
|
/** A clicked stat tile, which narrows the list. */
|
|
4102
4127
|
this.statFilter = "";
|
|
4128
|
+
/** One board stage to show, or "" for every stage. */
|
|
4129
|
+
this.statusFilter = "";
|
|
4130
|
+
this.sortOrder = "";
|
|
4131
|
+
/** Day groups the reader folded shut. Session only: "Today" means another day tomorrow. */
|
|
4132
|
+
this.collapsedDays = /* @__PURE__ */ new Set();
|
|
4103
4133
|
/** Accent preset id (see ACCENTS), applied as inline --accent / --accent-soft. */
|
|
4104
4134
|
this.accent = "indigo";
|
|
4105
4135
|
/** Collapsed to the one-line minimize bar. */
|
|
@@ -4133,6 +4163,7 @@ var LoupeApp = class {
|
|
|
4133
4163
|
this.activitySince = "";
|
|
4134
4164
|
/** The backend answered that it keeps no feed, so the panel stops asking. */
|
|
4135
4165
|
this.activityNone = false;
|
|
4166
|
+
this.syncBusy = false;
|
|
4136
4167
|
/** Iteration history per thread — generating a change is a stack, not a one-shot. */
|
|
4137
4168
|
this.iterations = /* @__PURE__ */ new Map();
|
|
4138
4169
|
/** The thread whose generate pane is open, if any. */
|
|
@@ -4153,6 +4184,9 @@ var LoupeApp = class {
|
|
|
4153
4184
|
this.floatDrag = null;
|
|
4154
4185
|
this.floatResize = null;
|
|
4155
4186
|
this.raf = 0;
|
|
4187
|
+
this.onSyncVisible = () => {
|
|
4188
|
+
if (document.visibilityState === "visible") void this.syncTick();
|
|
4189
|
+
};
|
|
4156
4190
|
/** Tab id → its hint slot (custom tabs only; the built-ins have named fields). */
|
|
4157
4191
|
this.customHintSlots = /* @__PURE__ */ new Map();
|
|
4158
4192
|
/** The tab strip, in order. */
|
|
@@ -4517,6 +4551,7 @@ var LoupeApp = class {
|
|
|
4517
4551
|
void this.loadOrg();
|
|
4518
4552
|
void this.loadActivity();
|
|
4519
4553
|
this.syncActivityPoll();
|
|
4554
|
+
this.startSyncPoll();
|
|
4520
4555
|
void this.loadAllComments();
|
|
4521
4556
|
this.observe();
|
|
4522
4557
|
this.watchNavigation();
|
|
@@ -4558,6 +4593,61 @@ var LoupeApp = class {
|
|
|
4558
4593
|
} catch {
|
|
4559
4594
|
}
|
|
4560
4595
|
}
|
|
4596
|
+
/**
|
|
4597
|
+
* Keep the panel current without a reload.
|
|
4598
|
+
*
|
|
4599
|
+
* A status moved in another app (CRM approving a ticket), a reply relayed through Hub,
|
|
4600
|
+
* or a teammate's new pin otherwise appears only after the page reloads. The bridge's
|
|
4601
|
+
* SSE covers threads when one is configured; this covers everything else, for every
|
|
4602
|
+
* host. It pauses while the tab is hidden and catches up the moment it is shown again.
|
|
4603
|
+
*/
|
|
4604
|
+
startSyncPoll() {
|
|
4605
|
+
if (this.syncPoll) return;
|
|
4606
|
+
this.syncPoll = setInterval(() => void this.syncTick(), SYNC_POLL_MS);
|
|
4607
|
+
document.addEventListener("visibilitychange", this.onSyncVisible);
|
|
4608
|
+
}
|
|
4609
|
+
async syncTick() {
|
|
4610
|
+
if (this.syncBusy || document.visibilityState === "hidden") return;
|
|
4611
|
+
const active = this.shadow?.activeElement;
|
|
4612
|
+
if (active && (active.tagName === "TEXTAREA" || active.tagName === "INPUT")) return;
|
|
4613
|
+
this.syncBusy = true;
|
|
4614
|
+
try {
|
|
4615
|
+
const url = this.url;
|
|
4616
|
+
const threads = [...this.messages.keys()];
|
|
4617
|
+
const [page, all, ...replies] = await Promise.all([
|
|
4618
|
+
this.store.list(this.cfg.projectKey, url).catch(() => null),
|
|
4619
|
+
this.allLoaded || this.scope === "all" ? this.store.listAll(this.cfg.projectKey).catch(() => null) : null,
|
|
4620
|
+
...threads.map((id) => this.store.listMessages(id).catch(() => null))
|
|
4621
|
+
]);
|
|
4622
|
+
if (url !== this.url) return;
|
|
4623
|
+
let changed = false;
|
|
4624
|
+
if (Array.isArray(page) && JSON.stringify(page) !== JSON.stringify(this.comments)) {
|
|
4625
|
+
this.comments = page;
|
|
4626
|
+
changed = true;
|
|
4627
|
+
}
|
|
4628
|
+
if (Array.isArray(all) && JSON.stringify(all) !== JSON.stringify(this.allComments)) {
|
|
4629
|
+
this.allComments = all;
|
|
4630
|
+
changed = true;
|
|
4631
|
+
}
|
|
4632
|
+
threads.forEach((id, i) => {
|
|
4633
|
+
const list = replies[i];
|
|
4634
|
+
if (!Array.isArray(list)) return;
|
|
4635
|
+
const local = (this.messages.get(id) ?? []).filter((m) => this.msgPending.has(m.id) || this.msgFailed.has(m.id));
|
|
4636
|
+
const next = [...list, ...local];
|
|
4637
|
+
if (JSON.stringify(next) === JSON.stringify(this.messages.get(id))) return;
|
|
4638
|
+
this.messages.set(id, next);
|
|
4639
|
+
changed = true;
|
|
4640
|
+
});
|
|
4641
|
+
if (changed) {
|
|
4642
|
+
this.renderPins();
|
|
4643
|
+
this.renderList();
|
|
4644
|
+
this.renderHome();
|
|
4645
|
+
}
|
|
4646
|
+
} finally {
|
|
4647
|
+
this.syncBusy = false;
|
|
4648
|
+
}
|
|
4649
|
+
void this.loadNotifications();
|
|
4650
|
+
}
|
|
4561
4651
|
// ---- DOM construction -----------------------------------------------------
|
|
4562
4652
|
buildDom() {
|
|
4563
4653
|
this.root = document.createElement("div");
|
|
@@ -4733,6 +4823,27 @@ var LoupeApp = class {
|
|
|
4733
4823
|
this.renderList();
|
|
4734
4824
|
};
|
|
4735
4825
|
listHead.appendChild(search);
|
|
4826
|
+
const filters = el("div", "listfilters");
|
|
4827
|
+
this.statusSel = el("select", "lf-status");
|
|
4828
|
+
this.statusSel.setAttribute("aria-label", "Filter by status");
|
|
4829
|
+
this.statusSel.append(
|
|
4830
|
+
optionEl("All statuses", "", !this.statusFilter),
|
|
4831
|
+
...Object.keys(STAGE_LABELS).map((k) => optionEl(STAGE_LABELS[k], k, this.statusFilter === k))
|
|
4832
|
+
);
|
|
4833
|
+
this.statusSel.onchange = () => {
|
|
4834
|
+
this.statusFilter = this.statusSel.value;
|
|
4835
|
+
this.saveState();
|
|
4836
|
+
this.renderList();
|
|
4837
|
+
};
|
|
4838
|
+
this.sortSel = el("select", "lf-sort");
|
|
4839
|
+
this.sortSel.setAttribute("aria-label", "Order");
|
|
4840
|
+
this.sortSel.append(optionEl("Newest first", "newest", false), optionEl("Oldest first", "oldest", false), optionEl("Page order", "page", false));
|
|
4841
|
+
this.sortSel.onchange = () => {
|
|
4842
|
+
this.sortOrder = this.sortSel.value;
|
|
4843
|
+
this.saveState();
|
|
4844
|
+
this.renderList();
|
|
4845
|
+
};
|
|
4846
|
+
filters.append(this.statusSel, this.sortSel);
|
|
4736
4847
|
this.listEl = el("div", "list");
|
|
4737
4848
|
const homeView = el("div", "view home-view");
|
|
4738
4849
|
this.homeEl = homeView;
|
|
@@ -4740,7 +4851,7 @@ var LoupeApp = class {
|
|
|
4740
4851
|
this.commentsHint = el("div", "hint-slot");
|
|
4741
4852
|
this.reviewBar = el("div", "reviewbar");
|
|
4742
4853
|
this.reviewBar.style.display = "none";
|
|
4743
|
-
commentsView.append(this.commentsHint, tools, listHead, this.reviewBar, this.listEl, this.buildIntegrations());
|
|
4854
|
+
commentsView.append(this.commentsHint, tools, listHead, filters, this.reviewBar, this.listEl, this.buildIntegrations());
|
|
4744
4855
|
const activityView = el("div", "view activity-view");
|
|
4745
4856
|
this.activityEl = activityView;
|
|
4746
4857
|
const chatView = el("div", "view chat-view");
|
|
@@ -5402,17 +5513,49 @@ var LoupeApp = class {
|
|
|
5402
5513
|
feed.innerHTML = recent.length ? recent.map((c) => {
|
|
5403
5514
|
const stage = STAGE_LABELS[normalizeStatus(c.status)];
|
|
5404
5515
|
const prio = normalizePriority(c.priority);
|
|
5405
|
-
return `<
|
|
5516
|
+
return `<div class="hfeed-i" role="button" tabindex="0" data-id="${escapeAttr(c.id)}"><span class="hfeed-t">${escapeHtml(c.title || (c.body.split("\n")[0] ?? "").slice(0, 60))}</span><span class="hfeed-m">${escapeHtml(c.author?.name ?? "")} \xB7 <span title="${escapeAttr(this.fmtWhen(c.createdAt))}">${fmtAgo(c.createdAt)}</span> \xB7 <span class="hfeed-s hfeed-s-${normalizeStatus(c.status)}">${stage}</span><span class="hfeed-p hfeed-p-${prio}">${PRIORITY_LABELS[prio]}</span></span><span class="hfeed-a"><button class="hfeed-act" data-act="resolve">${isResolved(c) ? "Reopen" : "Resolve"}</button><button class="hfeed-act" data-act="open">Open</button><button class="hfeed-act danger" data-act="delete">Delete</button></span></div>`;
|
|
5406
5517
|
}).join("") : `<div class="hempty">Nothing here yet.</div>`;
|
|
5407
5518
|
feed.querySelectorAll(".hfeed-i").forEach((b) => {
|
|
5408
|
-
|
|
5409
|
-
|
|
5519
|
+
const id = b.dataset.id;
|
|
5520
|
+
const open = () => {
|
|
5410
5521
|
this.statFilter = "";
|
|
5522
|
+
this.statusFilter = "";
|
|
5411
5523
|
this.setTab("comments");
|
|
5412
5524
|
this.expanded.add(id);
|
|
5525
|
+
const c = recent.find((x) => x.id === id);
|
|
5526
|
+
if (c) void this.loadMessages(c);
|
|
5413
5527
|
this.renderList();
|
|
5414
5528
|
this.flash(id);
|
|
5415
5529
|
};
|
|
5530
|
+
b.onclick = open;
|
|
5531
|
+
b.onkeydown = (e) => {
|
|
5532
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
5533
|
+
e.preventDefault();
|
|
5534
|
+
open();
|
|
5535
|
+
}
|
|
5536
|
+
};
|
|
5537
|
+
b.querySelectorAll(".hfeed-act").forEach((btn) => {
|
|
5538
|
+
btn.onclick = (e) => {
|
|
5539
|
+
e.stopPropagation();
|
|
5540
|
+
const c = recent.find((x) => x.id === id);
|
|
5541
|
+
if (!c) return;
|
|
5542
|
+
if (btn.dataset.act === "open") open();
|
|
5543
|
+
else if (btn.dataset.act === "resolve") void this.toggleResolved(c);
|
|
5544
|
+
else if (btn.dataset.act === "delete") {
|
|
5545
|
+
if (btn.dataset.armed) void this.deleteComment(c);
|
|
5546
|
+
else {
|
|
5547
|
+
btn.dataset.armed = "1";
|
|
5548
|
+
btn.textContent = "Confirm delete";
|
|
5549
|
+
setTimeout(() => {
|
|
5550
|
+
if (btn.isConnected) {
|
|
5551
|
+
delete btn.dataset.armed;
|
|
5552
|
+
btn.textContent = "Delete";
|
|
5553
|
+
}
|
|
5554
|
+
}, 3e3);
|
|
5555
|
+
}
|
|
5556
|
+
}
|
|
5557
|
+
};
|
|
5558
|
+
});
|
|
5416
5559
|
});
|
|
5417
5560
|
}
|
|
5418
5561
|
async loadNotifications() {
|
|
@@ -6521,6 +6664,8 @@ var LoupeApp = class {
|
|
|
6521
6664
|
if (typeof p?.tab === "string" && known.includes(p.tab)) this.tab = p.tab;
|
|
6522
6665
|
if (p?.scope === "page" || p?.scope === "all") this.scope = p.scope;
|
|
6523
6666
|
if (typeof p?.statFilter === "string") this.statFilter = p.statFilter;
|
|
6667
|
+
if (typeof p?.statusFilter === "string" && p.statusFilter in STAGE_LABELS) this.statusFilter = p.statusFilter;
|
|
6668
|
+
if (["newest", "oldest", "page"].includes(p?.sortOrder)) this.sortOrder = p.sortOrder;
|
|
6524
6669
|
if (p?.float && typeof p.float.w === "number") this.floatRect = { ...this.floatRect, ...p.float };
|
|
6525
6670
|
if (typeof p?.markersHidden === "boolean") this.markersHidden = p.markersHidden;
|
|
6526
6671
|
if (typeof p?.launcherHidden === "boolean") this.launcherHidden = p.launcherHidden;
|
|
@@ -6547,6 +6692,8 @@ var LoupeApp = class {
|
|
|
6547
6692
|
fab: this.fabPos,
|
|
6548
6693
|
scope: this.scope,
|
|
6549
6694
|
statFilter: this.statFilter,
|
|
6695
|
+
statusFilter: this.statusFilter,
|
|
6696
|
+
sortOrder: this.sortOrder,
|
|
6550
6697
|
accent: this.accent,
|
|
6551
6698
|
minimized: this.minimized,
|
|
6552
6699
|
hoverHints: this.hoverHints,
|
|
@@ -6629,25 +6776,43 @@ var LoupeApp = class {
|
|
|
6629
6776
|
if (this.statFilter === "needs_you" && !needsYou({ status: c.status }).needs) return false;
|
|
6630
6777
|
if (this.statFilter === "resolved" && stage !== "resolved") return false;
|
|
6631
6778
|
if (this.statFilter === "stale" && (stage === "resolved" || !(Date.parse(c.createdAt) < weekAgo))) return false;
|
|
6779
|
+
if (this.statusFilter && stage !== this.statusFilter) return false;
|
|
6632
6780
|
return !q || `${c.title ?? ""} ${c.body} ${c.author?.name ?? ""}`.toLowerCase().includes(q);
|
|
6633
6781
|
});
|
|
6634
|
-
|
|
6782
|
+
const order = this.sortOrder || (this.scope === "all" ? "newest" : "page");
|
|
6783
|
+
if (this.sortSel) this.sortSel.value = order;
|
|
6784
|
+
if (this.statusSel) this.statusSel.value = this.statusFilter;
|
|
6785
|
+
if (order === "newest") items = [...items].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
6786
|
+
if (order === "oldest") items = [...items].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
6787
|
+
const grouped = order !== "page";
|
|
6788
|
+
const perDay = /* @__PURE__ */ new Map();
|
|
6789
|
+
if (grouped) for (const c of items) perDay.set(dayLabel(c.createdAt), (perDay.get(dayLabel(c.createdAt)) ?? 0) + 1);
|
|
6635
6790
|
this.renderReviewBar();
|
|
6636
6791
|
if (!items.length) {
|
|
6637
6792
|
this.listEl.appendChild(el(
|
|
6638
6793
|
"div",
|
|
6639
6794
|
"empty",
|
|
6640
|
-
this.statFilter ? "Nothing in this bucket." : q ? "No comments match your search." : this.scope === "all" ? "No feedback in this project yet." : "No comments yet. Use Inspect to pick an element, or Note to drop a comment anywhere on the page."
|
|
6795
|
+
this.statFilter ? "Nothing in this bucket." : this.statusFilter ? `Nothing in ${STAGE_LABELS[this.statusFilter]}.` : q ? "No comments match your search." : this.scope === "all" ? "No feedback in this project yet." : "No comments yet. Use Inspect to pick an element, or Note to drop a comment anywhere on the page."
|
|
6641
6796
|
));
|
|
6642
6797
|
}
|
|
6643
6798
|
let lastDay = "";
|
|
6644
6799
|
items.forEach((c, i) => {
|
|
6645
|
-
if (
|
|
6800
|
+
if (grouped) {
|
|
6646
6801
|
const day = dayLabel(c.createdAt);
|
|
6802
|
+
const shut = this.collapsedDays.has(day);
|
|
6647
6803
|
if (day !== lastDay) {
|
|
6648
|
-
|
|
6804
|
+
const head = el("button", "daylabel" + (shut ? " shut" : ""), day);
|
|
6805
|
+
head.dataset.n = String(perDay.get(day) ?? 0);
|
|
6806
|
+
head.setAttribute("aria-expanded", String(!shut));
|
|
6807
|
+
head.onclick = () => {
|
|
6808
|
+
if (shut) this.collapsedDays.delete(day);
|
|
6809
|
+
else this.collapsedDays.add(day);
|
|
6810
|
+
this.renderList();
|
|
6811
|
+
};
|
|
6812
|
+
this.listEl.appendChild(head);
|
|
6649
6813
|
lastDay = day;
|
|
6650
6814
|
}
|
|
6815
|
+
if (shut) return;
|
|
6651
6816
|
}
|
|
6652
6817
|
this.listEl.appendChild(this.itemView(c, i));
|
|
6653
6818
|
});
|
|
@@ -6794,31 +6959,14 @@ var LoupeApp = class {
|
|
|
6794
6959
|
}
|
|
6795
6960
|
const actions = el("div", "actions");
|
|
6796
6961
|
const doneBtn = el("button", "", isResolved(c) ? "Reopen" : "Resolve");
|
|
6797
|
-
doneBtn.onclick =
|
|
6962
|
+
doneBtn.onclick = (e) => {
|
|
6798
6963
|
e.stopPropagation();
|
|
6799
|
-
|
|
6800
|
-
c.status = status;
|
|
6801
|
-
await this.store.update(c.id, { status });
|
|
6802
|
-
this.renderPins();
|
|
6803
|
-
this.renderList();
|
|
6804
|
-
this.addActivity({
|
|
6805
|
-
kind: status === "resolved" ? "comment.resolve" : "comment.reopen",
|
|
6806
|
-
label: `${status === "resolved" ? "Resolved" : "Reopened"} \u201C${c.title || c.body.split("\n")[0] || "comment"}\u201D`
|
|
6807
|
-
});
|
|
6964
|
+
void this.toggleResolved(c);
|
|
6808
6965
|
};
|
|
6809
6966
|
const del = el("button", "", "Delete");
|
|
6810
|
-
del.onclick =
|
|
6967
|
+
del.onclick = (e) => {
|
|
6811
6968
|
e.stopPropagation();
|
|
6812
|
-
|
|
6813
|
-
this.comments = this.comments.filter((x) => x.id !== c.id);
|
|
6814
|
-
this.resolved.delete(c.id);
|
|
6815
|
-
this.renderPins();
|
|
6816
|
-
this.renderList();
|
|
6817
|
-
this.addActivity({
|
|
6818
|
-
kind: "comment.delete",
|
|
6819
|
-
label: `Deleted \u201C${c.title || c.body.split("\n")[0] || "comment"}\u201D`,
|
|
6820
|
-
level: "warn"
|
|
6821
|
-
});
|
|
6969
|
+
void this.deleteComment(c);
|
|
6822
6970
|
};
|
|
6823
6971
|
actions.append(doneBtn, del);
|
|
6824
6972
|
detail.appendChild(actions);
|
|
@@ -6835,6 +6983,37 @@ var LoupeApp = class {
|
|
|
6835
6983
|
};
|
|
6836
6984
|
return item;
|
|
6837
6985
|
}
|
|
6986
|
+
/**
|
|
6987
|
+
* Resolve or reopen a comment. Shared by the card and the Home feed. The page list
|
|
6988
|
+
* and the project list are separate reads, so the change is applied to both.
|
|
6989
|
+
*/
|
|
6990
|
+
async toggleResolved(c) {
|
|
6991
|
+
const status = isResolved(c) ? "queue" : "resolved";
|
|
6992
|
+
await this.store.update(c.id, { status });
|
|
6993
|
+
for (const x of [c, ...this.comments, ...this.allComments]) if (x.id === c.id) x.status = status;
|
|
6994
|
+
this.renderPins();
|
|
6995
|
+
this.renderList();
|
|
6996
|
+
this.renderHome();
|
|
6997
|
+
this.addActivity({
|
|
6998
|
+
kind: status === "resolved" ? "comment.resolve" : "comment.reopen",
|
|
6999
|
+
label: `${status === "resolved" ? "Resolved" : "Reopened"} \u201C${c.title || c.body.split("\n")[0] || "comment"}\u201D`
|
|
7000
|
+
});
|
|
7001
|
+
}
|
|
7002
|
+
/** Delete a comment, from the card or the Home feed. */
|
|
7003
|
+
async deleteComment(c) {
|
|
7004
|
+
await this.store.remove(c.id);
|
|
7005
|
+
this.comments = this.comments.filter((x) => x.id !== c.id);
|
|
7006
|
+
this.allComments = this.allComments.filter((x) => x.id !== c.id);
|
|
7007
|
+
this.resolved.delete(c.id);
|
|
7008
|
+
this.renderPins();
|
|
7009
|
+
this.renderList();
|
|
7010
|
+
this.renderHome();
|
|
7011
|
+
this.addActivity({
|
|
7012
|
+
kind: "comment.delete",
|
|
7013
|
+
label: `Deleted \u201C${c.title || c.body.split("\n")[0] || "comment"}\u201D`,
|
|
7014
|
+
level: "warn"
|
|
7015
|
+
});
|
|
7016
|
+
}
|
|
6838
7017
|
/**
|
|
6839
7018
|
* The review banner. This is the one place the panel asks a human to decide
|
|
6840
7019
|
* something, so it sits above everything else in the detail.
|
|
@@ -7874,6 +8053,9 @@ ${c.body}` : c.body)}</div>` + (c.context?.html ? `<pre class="or-code">${escape
|
|
|
7874
8053
|
if (this.companionPoll) clearInterval(this.companionPoll);
|
|
7875
8054
|
if (this.activityPoll) clearInterval(this.activityPoll);
|
|
7876
8055
|
this.activityPoll = void 0;
|
|
8056
|
+
if (this.syncPoll) clearInterval(this.syncPoll);
|
|
8057
|
+
this.syncPoll = void 0;
|
|
8058
|
+
document.removeEventListener("visibilitychange", this.onSyncVisible);
|
|
7877
8059
|
this.stopVoice();
|
|
7878
8060
|
this.stopPresence();
|
|
7879
8061
|
this.stopRecording?.();
|