@loupekit/sdk 0.13.0 → 0.14.0-next.80
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.global.js
CHANGED
|
@@ -427,11 +427,35 @@ var Loupe = (() => {
|
|
|
427
427
|
.hfeed-p { font-weight: 700; }
|
|
428
428
|
.hfeed-p-critical { color: var(--pin); }
|
|
429
429
|
.hfeed-p-high { color: #e0a92c; }
|
|
430
|
+
.hfeed-a { display: flex; gap: 4px; margin-top: 3px; }
|
|
431
|
+
.hfeed-act {
|
|
432
|
+
font-size: 11px; padding: 3px 8px; border: 1px solid var(--line); border-radius: 6px;
|
|
433
|
+
background: var(--bg-3); color: var(--ink); cursor: pointer;
|
|
434
|
+
}
|
|
435
|
+
.hfeed-act:hover { border-color: var(--accent); }
|
|
436
|
+
.hfeed-act.danger:hover, .hfeed-act.danger[data-armed] { border-color: var(--pin); color: var(--pin); }
|
|
430
437
|
.hempty { padding: 14px; text-align: center; color: var(--muted); font-size: 12px; }
|
|
431
438
|
.hfoot { margin-top: 14px; padding-top: 10px; border-top: 1px solid var(--line); font-size: 11px; color: var(--muted); text-align: center; }
|
|
432
439
|
|
|
433
440
|
/* Timeline grouping (project scope) + the repo filter. */
|
|
434
|
-
.daylabel {
|
|
441
|
+
.daylabel {
|
|
442
|
+
display: flex; align-items: center; gap: 6px; width: 100%; margin: 8px 0 0; padding: 4px 2px;
|
|
443
|
+
border: 0; background: none; cursor: pointer; text-align: left;
|
|
444
|
+
font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); font-weight: 700;
|
|
445
|
+
}
|
|
446
|
+
.daylabel::before { content: "\u25BE"; font-size: 10px; width: 10px; }
|
|
447
|
+
.daylabel.shut::before { content: "\u25B8"; }
|
|
448
|
+
.daylabel::after {
|
|
449
|
+
content: attr(data-n); padding: 0 6px; border-radius: 999px; background: var(--bg-3);
|
|
450
|
+
font-size: 10px; letter-spacing: 0;
|
|
451
|
+
}
|
|
452
|
+
.daylabel:hover { color: var(--ink); }
|
|
453
|
+
.listfilters { display: flex; gap: 6px; margin: 6px 0 2px; }
|
|
454
|
+
.listfilters select {
|
|
455
|
+
flex: 1; min-width: 0; font-size: 12px; padding: 4px 6px; border: 1px solid var(--line); border-radius: 7px;
|
|
456
|
+
background: var(--bg-2); color: var(--ink); outline: none; cursor: pointer;
|
|
457
|
+
}
|
|
458
|
+
.listfilters select:focus { border-color: var(--accent); }
|
|
435
459
|
/* The page a comment belongs to, shown in the project scope (Settings \u2192 Page paths). */
|
|
436
460
|
.pathtag {
|
|
437
461
|
padding: 1px 6px; border-radius: 999px; background: var(--bg-3); color: var(--muted);
|
|
@@ -3983,7 +4007,7 @@ a.fwdchip { text-decoration: none; cursor: pointer; }
|
|
|
3983
4007
|
};
|
|
3984
4008
|
|
|
3985
4009
|
// src/app.ts
|
|
3986
|
-
var SDK_VERSION = true ? "0.
|
|
4010
|
+
var SDK_VERSION = true ? "0.14.0" : "dev";
|
|
3987
4011
|
var FAB_SIZE = 46;
|
|
3988
4012
|
var FAB_DRAG_THRESHOLD = 6;
|
|
3989
4013
|
var LAUNCHER_SHORTCUT = "Alt+Shift+L";
|
|
@@ -4047,6 +4071,7 @@ a.fwdchip { text-decoration: none; cursor: pointer; }
|
|
|
4047
4071
|
var DOCK_MODES = ["left", "right", "bottom", "float"];
|
|
4048
4072
|
var RECORD_MAX_MS = 2e4;
|
|
4049
4073
|
var MAX_FILES = 10;
|
|
4074
|
+
var SYNC_POLL_MS = 1e4;
|
|
4050
4075
|
var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
4051
4076
|
var MAX_VIDEO_BYTES = 25 * 1024 * 1024;
|
|
4052
4077
|
var uid2 = () => crypto.randomUUID ? crypto.randomUUID() : "c_" + Math.abs(hash(String(performance.now()))).toString(36);
|
|
@@ -4137,6 +4162,11 @@ a.fwdchip { text-decoration: none; cursor: pointer; }
|
|
|
4137
4162
|
this.allLoaded = false;
|
|
4138
4163
|
/** A clicked stat tile, which narrows the list. */
|
|
4139
4164
|
this.statFilter = "";
|
|
4165
|
+
/** One board stage to show, or "" for every stage. */
|
|
4166
|
+
this.statusFilter = "";
|
|
4167
|
+
this.sortOrder = "";
|
|
4168
|
+
/** Day groups the reader folded shut. Session only: "Today" means another day tomorrow. */
|
|
4169
|
+
this.collapsedDays = /* @__PURE__ */ new Set();
|
|
4140
4170
|
/** Accent preset id (see ACCENTS), applied as inline --accent / --accent-soft. */
|
|
4141
4171
|
this.accent = "indigo";
|
|
4142
4172
|
/** Collapsed to the one-line minimize bar. */
|
|
@@ -4170,6 +4200,7 @@ a.fwdchip { text-decoration: none; cursor: pointer; }
|
|
|
4170
4200
|
this.activitySince = "";
|
|
4171
4201
|
/** The backend answered that it keeps no feed, so the panel stops asking. */
|
|
4172
4202
|
this.activityNone = false;
|
|
4203
|
+
this.syncBusy = false;
|
|
4173
4204
|
/** Iteration history per thread — generating a change is a stack, not a one-shot. */
|
|
4174
4205
|
this.iterations = /* @__PURE__ */ new Map();
|
|
4175
4206
|
/** The thread whose generate pane is open, if any. */
|
|
@@ -4190,6 +4221,9 @@ a.fwdchip { text-decoration: none; cursor: pointer; }
|
|
|
4190
4221
|
this.floatDrag = null;
|
|
4191
4222
|
this.floatResize = null;
|
|
4192
4223
|
this.raf = 0;
|
|
4224
|
+
this.onSyncVisible = () => {
|
|
4225
|
+
if (document.visibilityState === "visible") void this.syncTick();
|
|
4226
|
+
};
|
|
4193
4227
|
/** Tab id → its hint slot (custom tabs only; the built-ins have named fields). */
|
|
4194
4228
|
this.customHintSlots = /* @__PURE__ */ new Map();
|
|
4195
4229
|
/** The tab strip, in order. */
|
|
@@ -4554,6 +4588,7 @@ a.fwdchip { text-decoration: none; cursor: pointer; }
|
|
|
4554
4588
|
void this.loadOrg();
|
|
4555
4589
|
void this.loadActivity();
|
|
4556
4590
|
this.syncActivityPoll();
|
|
4591
|
+
this.startSyncPoll();
|
|
4557
4592
|
void this.loadAllComments();
|
|
4558
4593
|
this.observe();
|
|
4559
4594
|
this.watchNavigation();
|
|
@@ -4595,6 +4630,61 @@ a.fwdchip { text-decoration: none; cursor: pointer; }
|
|
|
4595
4630
|
} catch {
|
|
4596
4631
|
}
|
|
4597
4632
|
}
|
|
4633
|
+
/**
|
|
4634
|
+
* Keep the panel current without a reload.
|
|
4635
|
+
*
|
|
4636
|
+
* A status moved in another app (CRM approving a ticket), a reply relayed through Hub,
|
|
4637
|
+
* or a teammate's new pin otherwise appears only after the page reloads. The bridge's
|
|
4638
|
+
* SSE covers threads when one is configured; this covers everything else, for every
|
|
4639
|
+
* host. It pauses while the tab is hidden and catches up the moment it is shown again.
|
|
4640
|
+
*/
|
|
4641
|
+
startSyncPoll() {
|
|
4642
|
+
if (this.syncPoll) return;
|
|
4643
|
+
this.syncPoll = setInterval(() => void this.syncTick(), SYNC_POLL_MS);
|
|
4644
|
+
document.addEventListener("visibilitychange", this.onSyncVisible);
|
|
4645
|
+
}
|
|
4646
|
+
async syncTick() {
|
|
4647
|
+
if (this.syncBusy || document.visibilityState === "hidden") return;
|
|
4648
|
+
const active = this.shadow?.activeElement;
|
|
4649
|
+
if (active && (active.tagName === "TEXTAREA" || active.tagName === "INPUT")) return;
|
|
4650
|
+
this.syncBusy = true;
|
|
4651
|
+
try {
|
|
4652
|
+
const url = this.url;
|
|
4653
|
+
const threads = [...this.messages.keys()];
|
|
4654
|
+
const [page, all, ...replies] = await Promise.all([
|
|
4655
|
+
this.store.list(this.cfg.projectKey, url).catch(() => null),
|
|
4656
|
+
this.allLoaded || this.scope === "all" ? this.store.listAll(this.cfg.projectKey).catch(() => null) : null,
|
|
4657
|
+
...threads.map((id) => this.store.listMessages(id).catch(() => null))
|
|
4658
|
+
]);
|
|
4659
|
+
if (url !== this.url) return;
|
|
4660
|
+
let changed = false;
|
|
4661
|
+
if (Array.isArray(page) && JSON.stringify(page) !== JSON.stringify(this.comments)) {
|
|
4662
|
+
this.comments = page;
|
|
4663
|
+
changed = true;
|
|
4664
|
+
}
|
|
4665
|
+
if (Array.isArray(all) && JSON.stringify(all) !== JSON.stringify(this.allComments)) {
|
|
4666
|
+
this.allComments = all;
|
|
4667
|
+
changed = true;
|
|
4668
|
+
}
|
|
4669
|
+
threads.forEach((id, i) => {
|
|
4670
|
+
const list = replies[i];
|
|
4671
|
+
if (!Array.isArray(list)) return;
|
|
4672
|
+
const local = (this.messages.get(id) ?? []).filter((m) => this.msgPending.has(m.id) || this.msgFailed.has(m.id));
|
|
4673
|
+
const next = [...list, ...local];
|
|
4674
|
+
if (JSON.stringify(next) === JSON.stringify(this.messages.get(id))) return;
|
|
4675
|
+
this.messages.set(id, next);
|
|
4676
|
+
changed = true;
|
|
4677
|
+
});
|
|
4678
|
+
if (changed) {
|
|
4679
|
+
this.renderPins();
|
|
4680
|
+
this.renderList();
|
|
4681
|
+
this.renderHome();
|
|
4682
|
+
}
|
|
4683
|
+
} finally {
|
|
4684
|
+
this.syncBusy = false;
|
|
4685
|
+
}
|
|
4686
|
+
void this.loadNotifications();
|
|
4687
|
+
}
|
|
4598
4688
|
// ---- DOM construction -----------------------------------------------------
|
|
4599
4689
|
buildDom() {
|
|
4600
4690
|
this.root = document.createElement("div");
|
|
@@ -4770,6 +4860,27 @@ a.fwdchip { text-decoration: none; cursor: pointer; }
|
|
|
4770
4860
|
this.renderList();
|
|
4771
4861
|
};
|
|
4772
4862
|
listHead.appendChild(search);
|
|
4863
|
+
const filters = el("div", "listfilters");
|
|
4864
|
+
this.statusSel = el("select", "lf-status");
|
|
4865
|
+
this.statusSel.setAttribute("aria-label", "Filter by status");
|
|
4866
|
+
this.statusSel.append(
|
|
4867
|
+
optionEl("All statuses", "", !this.statusFilter),
|
|
4868
|
+
...Object.keys(STAGE_LABELS).map((k) => optionEl(STAGE_LABELS[k], k, this.statusFilter === k))
|
|
4869
|
+
);
|
|
4870
|
+
this.statusSel.onchange = () => {
|
|
4871
|
+
this.statusFilter = this.statusSel.value;
|
|
4872
|
+
this.saveState();
|
|
4873
|
+
this.renderList();
|
|
4874
|
+
};
|
|
4875
|
+
this.sortSel = el("select", "lf-sort");
|
|
4876
|
+
this.sortSel.setAttribute("aria-label", "Order");
|
|
4877
|
+
this.sortSel.append(optionEl("Newest first", "newest", false), optionEl("Oldest first", "oldest", false), optionEl("Page order", "page", false));
|
|
4878
|
+
this.sortSel.onchange = () => {
|
|
4879
|
+
this.sortOrder = this.sortSel.value;
|
|
4880
|
+
this.saveState();
|
|
4881
|
+
this.renderList();
|
|
4882
|
+
};
|
|
4883
|
+
filters.append(this.statusSel, this.sortSel);
|
|
4773
4884
|
this.listEl = el("div", "list");
|
|
4774
4885
|
const homeView = el("div", "view home-view");
|
|
4775
4886
|
this.homeEl = homeView;
|
|
@@ -4777,7 +4888,7 @@ a.fwdchip { text-decoration: none; cursor: pointer; }
|
|
|
4777
4888
|
this.commentsHint = el("div", "hint-slot");
|
|
4778
4889
|
this.reviewBar = el("div", "reviewbar");
|
|
4779
4890
|
this.reviewBar.style.display = "none";
|
|
4780
|
-
commentsView.append(this.commentsHint, tools, listHead, this.reviewBar, this.listEl, this.buildIntegrations());
|
|
4891
|
+
commentsView.append(this.commentsHint, tools, listHead, filters, this.reviewBar, this.listEl, this.buildIntegrations());
|
|
4781
4892
|
const activityView = el("div", "view activity-view");
|
|
4782
4893
|
this.activityEl = activityView;
|
|
4783
4894
|
const chatView = el("div", "view chat-view");
|
|
@@ -5439,17 +5550,49 @@ a.fwdchip { text-decoration: none; cursor: pointer; }
|
|
|
5439
5550
|
feed.innerHTML = recent.length ? recent.map((c) => {
|
|
5440
5551
|
const stage = STAGE_LABELS[normalizeStatus(c.status)];
|
|
5441
5552
|
const prio = normalizePriority(c.priority);
|
|
5442
|
-
return `<
|
|
5553
|
+
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>`;
|
|
5443
5554
|
}).join("") : `<div class="hempty">Nothing here yet.</div>`;
|
|
5444
5555
|
feed.querySelectorAll(".hfeed-i").forEach((b) => {
|
|
5445
|
-
|
|
5446
|
-
|
|
5556
|
+
const id = b.dataset.id;
|
|
5557
|
+
const open = () => {
|
|
5447
5558
|
this.statFilter = "";
|
|
5559
|
+
this.statusFilter = "";
|
|
5448
5560
|
this.setTab("comments");
|
|
5449
5561
|
this.expanded.add(id);
|
|
5562
|
+
const c = recent.find((x) => x.id === id);
|
|
5563
|
+
if (c) void this.loadMessages(c);
|
|
5450
5564
|
this.renderList();
|
|
5451
5565
|
this.flash(id);
|
|
5452
5566
|
};
|
|
5567
|
+
b.onclick = open;
|
|
5568
|
+
b.onkeydown = (e) => {
|
|
5569
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
5570
|
+
e.preventDefault();
|
|
5571
|
+
open();
|
|
5572
|
+
}
|
|
5573
|
+
};
|
|
5574
|
+
b.querySelectorAll(".hfeed-act").forEach((btn) => {
|
|
5575
|
+
btn.onclick = (e) => {
|
|
5576
|
+
e.stopPropagation();
|
|
5577
|
+
const c = recent.find((x) => x.id === id);
|
|
5578
|
+
if (!c) return;
|
|
5579
|
+
if (btn.dataset.act === "open") open();
|
|
5580
|
+
else if (btn.dataset.act === "resolve") void this.toggleResolved(c);
|
|
5581
|
+
else if (btn.dataset.act === "delete") {
|
|
5582
|
+
if (btn.dataset.armed) void this.deleteComment(c);
|
|
5583
|
+
else {
|
|
5584
|
+
btn.dataset.armed = "1";
|
|
5585
|
+
btn.textContent = "Confirm delete";
|
|
5586
|
+
setTimeout(() => {
|
|
5587
|
+
if (btn.isConnected) {
|
|
5588
|
+
delete btn.dataset.armed;
|
|
5589
|
+
btn.textContent = "Delete";
|
|
5590
|
+
}
|
|
5591
|
+
}, 3e3);
|
|
5592
|
+
}
|
|
5593
|
+
}
|
|
5594
|
+
};
|
|
5595
|
+
});
|
|
5453
5596
|
});
|
|
5454
5597
|
}
|
|
5455
5598
|
async loadNotifications() {
|
|
@@ -6558,6 +6701,8 @@ a.fwdchip { text-decoration: none; cursor: pointer; }
|
|
|
6558
6701
|
if (typeof p?.tab === "string" && known.includes(p.tab)) this.tab = p.tab;
|
|
6559
6702
|
if (p?.scope === "page" || p?.scope === "all") this.scope = p.scope;
|
|
6560
6703
|
if (typeof p?.statFilter === "string") this.statFilter = p.statFilter;
|
|
6704
|
+
if (typeof p?.statusFilter === "string" && p.statusFilter in STAGE_LABELS) this.statusFilter = p.statusFilter;
|
|
6705
|
+
if (["newest", "oldest", "page"].includes(p?.sortOrder)) this.sortOrder = p.sortOrder;
|
|
6561
6706
|
if (p?.float && typeof p.float.w === "number") this.floatRect = { ...this.floatRect, ...p.float };
|
|
6562
6707
|
if (typeof p?.markersHidden === "boolean") this.markersHidden = p.markersHidden;
|
|
6563
6708
|
if (typeof p?.launcherHidden === "boolean") this.launcherHidden = p.launcherHidden;
|
|
@@ -6584,6 +6729,8 @@ a.fwdchip { text-decoration: none; cursor: pointer; }
|
|
|
6584
6729
|
fab: this.fabPos,
|
|
6585
6730
|
scope: this.scope,
|
|
6586
6731
|
statFilter: this.statFilter,
|
|
6732
|
+
statusFilter: this.statusFilter,
|
|
6733
|
+
sortOrder: this.sortOrder,
|
|
6587
6734
|
accent: this.accent,
|
|
6588
6735
|
minimized: this.minimized,
|
|
6589
6736
|
hoverHints: this.hoverHints,
|
|
@@ -6666,25 +6813,43 @@ a.fwdchip { text-decoration: none; cursor: pointer; }
|
|
|
6666
6813
|
if (this.statFilter === "needs_you" && !needsYou({ status: c.status }).needs) return false;
|
|
6667
6814
|
if (this.statFilter === "resolved" && stage !== "resolved") return false;
|
|
6668
6815
|
if (this.statFilter === "stale" && (stage === "resolved" || !(Date.parse(c.createdAt) < weekAgo))) return false;
|
|
6816
|
+
if (this.statusFilter && stage !== this.statusFilter) return false;
|
|
6669
6817
|
return !q || `${c.title ?? ""} ${c.body} ${c.author?.name ?? ""}`.toLowerCase().includes(q);
|
|
6670
6818
|
});
|
|
6671
|
-
|
|
6819
|
+
const order = this.sortOrder || (this.scope === "all" ? "newest" : "page");
|
|
6820
|
+
if (this.sortSel) this.sortSel.value = order;
|
|
6821
|
+
if (this.statusSel) this.statusSel.value = this.statusFilter;
|
|
6822
|
+
if (order === "newest") items = [...items].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
6823
|
+
if (order === "oldest") items = [...items].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
6824
|
+
const grouped = order !== "page";
|
|
6825
|
+
const perDay = /* @__PURE__ */ new Map();
|
|
6826
|
+
if (grouped) for (const c of items) perDay.set(dayLabel(c.createdAt), (perDay.get(dayLabel(c.createdAt)) ?? 0) + 1);
|
|
6672
6827
|
this.renderReviewBar();
|
|
6673
6828
|
if (!items.length) {
|
|
6674
6829
|
this.listEl.appendChild(el(
|
|
6675
6830
|
"div",
|
|
6676
6831
|
"empty",
|
|
6677
|
-
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."
|
|
6832
|
+
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."
|
|
6678
6833
|
));
|
|
6679
6834
|
}
|
|
6680
6835
|
let lastDay = "";
|
|
6681
6836
|
items.forEach((c, i) => {
|
|
6682
|
-
if (
|
|
6837
|
+
if (grouped) {
|
|
6683
6838
|
const day = dayLabel(c.createdAt);
|
|
6839
|
+
const shut = this.collapsedDays.has(day);
|
|
6684
6840
|
if (day !== lastDay) {
|
|
6685
|
-
|
|
6841
|
+
const head = el("button", "daylabel" + (shut ? " shut" : ""), day);
|
|
6842
|
+
head.dataset.n = String(perDay.get(day) ?? 0);
|
|
6843
|
+
head.setAttribute("aria-expanded", String(!shut));
|
|
6844
|
+
head.onclick = () => {
|
|
6845
|
+
if (shut) this.collapsedDays.delete(day);
|
|
6846
|
+
else this.collapsedDays.add(day);
|
|
6847
|
+
this.renderList();
|
|
6848
|
+
};
|
|
6849
|
+
this.listEl.appendChild(head);
|
|
6686
6850
|
lastDay = day;
|
|
6687
6851
|
}
|
|
6852
|
+
if (shut) return;
|
|
6688
6853
|
}
|
|
6689
6854
|
this.listEl.appendChild(this.itemView(c, i));
|
|
6690
6855
|
});
|
|
@@ -6831,31 +6996,14 @@ a.fwdchip { text-decoration: none; cursor: pointer; }
|
|
|
6831
6996
|
}
|
|
6832
6997
|
const actions = el("div", "actions");
|
|
6833
6998
|
const doneBtn = el("button", "", isResolved(c) ? "Reopen" : "Resolve");
|
|
6834
|
-
doneBtn.onclick =
|
|
6999
|
+
doneBtn.onclick = (e) => {
|
|
6835
7000
|
e.stopPropagation();
|
|
6836
|
-
|
|
6837
|
-
c.status = status;
|
|
6838
|
-
await this.store.update(c.id, { status });
|
|
6839
|
-
this.renderPins();
|
|
6840
|
-
this.renderList();
|
|
6841
|
-
this.addActivity({
|
|
6842
|
-
kind: status === "resolved" ? "comment.resolve" : "comment.reopen",
|
|
6843
|
-
label: `${status === "resolved" ? "Resolved" : "Reopened"} \u201C${c.title || c.body.split("\n")[0] || "comment"}\u201D`
|
|
6844
|
-
});
|
|
7001
|
+
void this.toggleResolved(c);
|
|
6845
7002
|
};
|
|
6846
7003
|
const del = el("button", "", "Delete");
|
|
6847
|
-
del.onclick =
|
|
7004
|
+
del.onclick = (e) => {
|
|
6848
7005
|
e.stopPropagation();
|
|
6849
|
-
|
|
6850
|
-
this.comments = this.comments.filter((x) => x.id !== c.id);
|
|
6851
|
-
this.resolved.delete(c.id);
|
|
6852
|
-
this.renderPins();
|
|
6853
|
-
this.renderList();
|
|
6854
|
-
this.addActivity({
|
|
6855
|
-
kind: "comment.delete",
|
|
6856
|
-
label: `Deleted \u201C${c.title || c.body.split("\n")[0] || "comment"}\u201D`,
|
|
6857
|
-
level: "warn"
|
|
6858
|
-
});
|
|
7006
|
+
void this.deleteComment(c);
|
|
6859
7007
|
};
|
|
6860
7008
|
actions.append(doneBtn, del);
|
|
6861
7009
|
detail.appendChild(actions);
|
|
@@ -6872,6 +7020,37 @@ a.fwdchip { text-decoration: none; cursor: pointer; }
|
|
|
6872
7020
|
};
|
|
6873
7021
|
return item;
|
|
6874
7022
|
}
|
|
7023
|
+
/**
|
|
7024
|
+
* Resolve or reopen a comment. Shared by the card and the Home feed. The page list
|
|
7025
|
+
* and the project list are separate reads, so the change is applied to both.
|
|
7026
|
+
*/
|
|
7027
|
+
async toggleResolved(c) {
|
|
7028
|
+
const status = isResolved(c) ? "queue" : "resolved";
|
|
7029
|
+
await this.store.update(c.id, { status });
|
|
7030
|
+
for (const x of [c, ...this.comments, ...this.allComments]) if (x.id === c.id) x.status = status;
|
|
7031
|
+
this.renderPins();
|
|
7032
|
+
this.renderList();
|
|
7033
|
+
this.renderHome();
|
|
7034
|
+
this.addActivity({
|
|
7035
|
+
kind: status === "resolved" ? "comment.resolve" : "comment.reopen",
|
|
7036
|
+
label: `${status === "resolved" ? "Resolved" : "Reopened"} \u201C${c.title || c.body.split("\n")[0] || "comment"}\u201D`
|
|
7037
|
+
});
|
|
7038
|
+
}
|
|
7039
|
+
/** Delete a comment, from the card or the Home feed. */
|
|
7040
|
+
async deleteComment(c) {
|
|
7041
|
+
await this.store.remove(c.id);
|
|
7042
|
+
this.comments = this.comments.filter((x) => x.id !== c.id);
|
|
7043
|
+
this.allComments = this.allComments.filter((x) => x.id !== c.id);
|
|
7044
|
+
this.resolved.delete(c.id);
|
|
7045
|
+
this.renderPins();
|
|
7046
|
+
this.renderList();
|
|
7047
|
+
this.renderHome();
|
|
7048
|
+
this.addActivity({
|
|
7049
|
+
kind: "comment.delete",
|
|
7050
|
+
label: `Deleted \u201C${c.title || c.body.split("\n")[0] || "comment"}\u201D`,
|
|
7051
|
+
level: "warn"
|
|
7052
|
+
});
|
|
7053
|
+
}
|
|
6875
7054
|
/**
|
|
6876
7055
|
* The review banner. This is the one place the panel asks a human to decide
|
|
6877
7056
|
* something, so it sits above everything else in the detail.
|
|
@@ -7911,6 +8090,9 @@ ${c.body}` : c.body)}</div>` + (c.context?.html ? `<pre class="or-code">${escape
|
|
|
7911
8090
|
if (this.companionPoll) clearInterval(this.companionPoll);
|
|
7912
8091
|
if (this.activityPoll) clearInterval(this.activityPoll);
|
|
7913
8092
|
this.activityPoll = void 0;
|
|
8093
|
+
if (this.syncPoll) clearInterval(this.syncPoll);
|
|
8094
|
+
this.syncPoll = void 0;
|
|
8095
|
+
document.removeEventListener("visibilitychange", this.onSyncVisible);
|
|
7914
8096
|
this.stopVoice();
|
|
7915
8097
|
this.stopPresence();
|
|
7916
8098
|
this.stopRecording?.();
|