import { hostCore, hostState } from "./chunk-KL66PICH.js"; // src/plugins/bookmarks.ts var bridge = window; function bmEl(id) { const element = document.getElementById(id); if (!element) throw new Error(`Missing bookmark element #${id}`); return element; } function bmOptionalEl(id) { return document.getElementById(id); } function errorMessage(error) { return error instanceof Error ? error.message : String(error); } var bmScope = "general"; function bmScopeParam(prefix, scope) { const sep = prefix ? "&" : "?"; return sep + "scope=" + encodeURIComponent(scope != null ? scope : bmScope); } var bmList = []; var bmOverlayList = []; var bmOverlayRevision = 0; var bmFilteredList = []; var bmEditScope = null; var bmCurrentPage = 1; var BM_PAGE_SIZE = 25; var bmSelected = /* @__PURE__ */ new Set(); function bmFmtFreq(hz) { if (!Number.isFinite(hz) || hz <= 0) return "--"; if (hz >= 1e9) return (hz / 1e9).toFixed(6).replace(/\.?0+$/, "") + " GHz"; if (hz >= 1e6) return (hz / 1e6).toFixed(6).replace(/\.?0+$/, "") + " MHz"; if (hz >= 1e3) return (hz / 1e3).toFixed(3).replace(/\.?0+$/, "") + " kHz"; return `${hz} Hz`; } function bmEsc(str) { const d = document.createElement("div"); d.appendChild(document.createTextNode(String(str))); return d.innerHTML; } function bmCanControl() { return !hostState.authEnabled || hostState.authRole === "control"; } function bmSyncAccess() { const canCtrl = bmCanControl(); const addBtn = bmEl("bm-add-btn"); const selectAllBtn = bmEl("bm-select-all-btn"); if (addBtn) addBtn.style.display = canCtrl ? "" : "none"; if (selectAllBtn) selectAllBtn.style.display = canCtrl ? "" : "none"; } function bmListScope() { return hostState.lastActiveRigId || "general"; } async function bmFetchOverlay() { const overlayScope = bmListScope(); try { const resp = await fetch("/bookmarks" + bmScopeParam(false, overlayScope)); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); bmOverlayList = await resp.json(); } catch (e) { console.error("Failed to fetch overlay bookmarks:", e); bmOverlayList = []; } bmOverlayRevision++; if (typeof bridge.syncBookmarkMapLocators === "function") { bridge.syncBookmarkMapLocators(bmOverlayList); } hostCore.scheduleSpectrumDraw(); } async function bmFetch(categoryFilter) { let url = "/bookmarks"; let hasQuery = false; if (categoryFilter && categoryFilter !== "") { url += "?category=" + encodeURIComponent(categoryFilter); hasQuery = true; } url += bmScopeParam(hasQuery); const overlayPromise = bmFetchOverlay(); try { const resp = await fetch(url); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); bmList = await resp.json(); } catch (e) { console.error("Failed to fetch bookmarks:", e); bmList = []; } bmSelected.clear(); bmUpdateSelectionUi(); bmSyncAccess(); bmApplyFilters(); void bmRefreshCategoryFilter(categoryFilter); await overlayPromise; } function bmApplyFilters() { const text = (bmEl("bm-text-filter")?.value || "").trim().toLowerCase(); const modeFilter = (bmEl("bm-mode-filter")?.value || "").trim().toUpperCase(); let filtered = modeFilter ? bmList.filter((bm) => (bm.mode || "").toUpperCase() === modeFilter) : bmList; filtered = text ? filtered.filter( (bm) => (bm.name || "").toLowerCase().includes(text) || (bm.locator || "").toLowerCase().includes(text) || (bm.category || "").toLowerCase().includes(text) || (bm.comment || "").toLowerCase().includes(text) ) : filtered; bmFilteredList = filtered; bmCurrentPage = 1; bmRender(filtered); } async function bmRefreshCategoryFilter(keepValue) { const sel = bmEl("bm-category-filter"); const modeSel = bmEl("bm-mode-filter"); if (!sel && !modeSel) return; try { const resp = await fetch("/bookmarks" + bmScopeParam(false)); if (!resp.ok) return; const all = await resp.json(); if (sel) { const cats = [...new Set(all.map((b) => b.category || "").filter(Boolean))].sort(); while (sel.options.length > 1) sel.remove(1); cats.forEach((cat) => { const opt = document.createElement("option"); opt.value = cat; opt.textContent = cat; sel.add(opt); }); if (keepValue && cats.includes(keepValue)) sel.value = keepValue; } if (modeSel) { const keepMode = modeSel.value; const modes = [...new Set(all.map((b) => (b.mode || "").trim().toUpperCase()).filter(Boolean))].sort(); while (modeSel.options.length > 1) modeSel.remove(1); modes.forEach((mode) => { const opt = document.createElement("option"); opt.value = mode; opt.textContent = mode; modeSel.add(opt); }); if (keepMode && modes.includes(keepMode)) modeSel.value = keepMode; } } catch { } } function bmRender(list) { const tbody = bmEl("bm-tbody"); const emptyEl = bmEl("bm-empty"); const paginatorEl = bmEl("bm-paginator"); const pageSummaryEl = bmEl("bm-page-summary"); const pageIndicatorEl = bmEl("bm-page-indicator"); const prevBtn = bmEl("bm-page-prev"); const nextBtn = bmEl("bm-page-next"); if (!tbody) return; tbody.innerHTML = ""; if (list.length === 0) { if (emptyEl) emptyEl.style.display = ""; if (paginatorEl) paginatorEl.style.display = "none"; return; } if (emptyEl) emptyEl.style.display = "none"; const canControl = bmCanControl(); const totalPages = Math.max(1, Math.ceil(list.length / BM_PAGE_SIZE)); const page = Math.min(Math.max(bmCurrentPage, 1), totalPages); bmCurrentPage = page; const startIndex = (page - 1) * BM_PAGE_SIZE; const endIndex = Math.min(startIndex + BM_PAGE_SIZE, list.length); const pageItems = list.slice(startIndex, endIndex); const showScope = bmScope !== "general"; pageItems.forEach((bm) => { const tr = document.createElement("tr"); tr.dataset.bmId = bm.id; const bwCell = bm.bandwidth_hz ? bmFmtFreq(bm.bandwidth_hz) : "--"; const locatorCell = bm.locator || "--"; const catCell = bm.category || "Uncategorised"; const decoderCell = (bm.decoders || []).join(", ").toUpperCase() || "--"; const commentCell = bm.comment || ""; const checked = bmSelected.has(bm.id) ? " checked" : ""; const scopeBadge = showScope && bm.scope === "general" ? ' G' : ""; tr.innerHTML = `${bmEsc(bm.name)}${scopeBadge}${bmFmtFreq(bm.freq_hz)}${bmEsc(bm.mode)}${bwCell}${bmEsc(locatorCell)}${bmEsc(catCell)}${bmEsc(decoderCell)}${bmEsc(commentCell)}` + (canControl ? `` : "") + ``; tbody.appendChild(tr); }); bmSyncSelectAllCheckbox(); if (paginatorEl) paginatorEl.style.display = totalPages > 1 ? "flex" : ""; if (pageSummaryEl) pageSummaryEl.textContent = `Showing ${startIndex + 1}-${endIndex} of ${list.length}`; if (pageIndicatorEl) pageIndicatorEl.textContent = `Page ${page} of ${totalPages}`; if (prevBtn) prevBtn.disabled = page <= 1; if (nextBtn) nextBtn.disabled = page >= totalPages; } function bmChangePage(delta) { const totalPages = Math.max(1, Math.ceil(bmFilteredList.length / BM_PAGE_SIZE)); const nextPage = Math.min(Math.max(bmCurrentPage + delta, 1), totalPages); if (nextPage === bmCurrentPage) return; bmCurrentPage = nextPage; bmRender(bmFilteredList); } function bmReadDecoders() { return hostState.decoderRegistry.filter((d) => d.bookmark_selectable).filter((d) => bmOptionalEl("bm-dec-" + d.id)?.checked).map((d) => d.id); } function bmWriteDecoders(decoders) { const set = new Set(decoders || []); hostState.decoderRegistry.filter((d) => d.bookmark_selectable).forEach((d) => { const el = bmOptionalEl("bm-dec-" + d.id); if (el) el.checked = set.has(d.id); }); } function bmBuildDecoderCheckboxes() { const container = bmEl("bm-decoder-checkboxes"); if (!container) return; container.innerHTML = ""; hostState.decoderRegistry.filter((d) => d.bookmark_selectable).forEach((d) => { const label = document.createElement("label"); label.className = "bm-decoder-check"; label.innerHTML = ' ' + d.label; container.appendChild(label); }); } function bmOpenForm(bm) { const wrap = bmEl("bm-form-wrap"); if (!wrap) return; bmEditScope = bm ? bm.scope || bmScope : null; bmBuildDecoderCheckboxes(); bmEl("bm-id").value = bm ? bm.id : ""; bmEl("bm-name").value = bm ? bm.name : ""; bmEl("bm-freq").value = bm ? String(bm.freq_hz) : ""; bmEl("bm-mode").value = bm ? bm.mode : ""; bmEl("bm-bw").value = bm?.bandwidth_hz ? String(bm.bandwidth_hz) : ""; bmEl("bm-locator").value = bm ? bm.locator || "" : ""; bmEl("bm-category-input").value = bm ? bm.category || "" : ""; bmEl("bm-comment").value = bm ? bm.comment || "" : ""; bmWriteDecoders(bm?.decoders ?? []); bmEl("bm-form-title").textContent = bm ? "Edit Bookmark" : "Add Bookmark"; wrap.style.display = "flex"; bmEl("bm-name").focus(); } function bmCloseForm() { const wrap = bmEl("bm-form-wrap"); if (wrap) wrap.style.display = "none"; } function bmPrefillFromStatus() { const freqHz = hostState.lastFreqHz; if (freqHz != null && Number.isFinite(freqHz)) { bmEl("bm-freq").value = String(Math.round(freqHz)); } if (hostState.lastModeName) { bmEl("bm-mode").value = hostState.lastModeName; } if (hostState.currentBandwidthHz > 0) { bmEl("bm-bw").value = String(Math.round(hostState.currentBandwidthHz)); } const activeDecoders = hostState.decoderRegistry.filter((d) => d.bookmark_selectable && d.activation === "toggle").filter((d) => bmOptionalEl(d.id + "-decode-toggle-btn")?.dataset.enabled === "true").map((d) => d.id); bmWriteDecoders(activeDecoders); } async function bmSave(e) { e.preventDefault(); const id = bmEl("bm-id").value; const name = bmEl("bm-name").value.trim(); const freqStr = bmEl("bm-freq").value; const freq_hz = parseInt(freqStr, 10); const mode = bmEl("bm-mode").value.trim(); const bwStr = bmEl("bm-bw").value; const bandwidth_hz = bwStr ? parseInt(bwStr, 10) : null; const locator = bmEl("bm-locator").value.trim().toUpperCase(); const category = bmEl("bm-category-input").value.trim(); const comment = bmEl("bm-comment").value.trim(); const decoders = bmReadDecoders(); const formError = bmEl("bm-form-error"); if (formError) formError.textContent = ""; if (!name || !Number.isFinite(freq_hz) || !mode) { if (formError) formError.textContent = "Enter a name, a valid frequency, and a mode."; const invalid = !name ? bmEl("bm-name") : !Number.isFinite(freq_hz) ? bmEl("bm-freq") : bmEl("bm-mode"); invalid?.focus(); return; } const body = { name, freq_hz, mode, bandwidth_hz, locator: locator || null, category, comment, decoders }; try { let resp; if (id) { resp = await fetch("/bookmarks/" + encodeURIComponent(id) + bmScopeParam(false, bmEditScope), { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); } else { resp = await fetch("/bookmarks" + bmScopeParam(false), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); } if (!resp.ok) { const text = await resp.text(); if (resp.status === 409) { throw new Error("A bookmark for that frequency already exists."); } throw new Error(text || `HTTP ${resp.status}`); } bmCloseForm(); await bmFetch(bmEl("bm-category-filter").value); } catch (err) { console.error("Failed to save bookmark:", err); if (formError) formError.textContent = "Failed to save bookmark: " + errorMessage(err); bridge.trxUi.notify?.("Bookmark could not be saved", { kind: "error" }); } } async function bmDelete(id) { if (!await bridge.trxUi.confirm({ title: "Delete bookmark?", message: "This bookmark will be permanently removed.", confirmLabel: "Delete" })) return; const bm = bmList.find((b) => b.id === id); const scope = bm ? bm.scope : void 0; try { const resp = await fetch("/bookmarks/" + encodeURIComponent(id) + bmScopeParam(false, scope), { method: "DELETE" }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); await bmFetch(bmEl("bm-category-filter").value); } catch (err) { console.error("Failed to delete bookmark:", err); bridge.trxUi.notify?.("Failed to delete bookmark: " + errorMessage(err), { kind: "error" }); } } function bmApply(bm) { try { const modeEl = document.getElementById("mode"); if (modeEl) { modeEl.value = (bm.mode || "").toUpperCase(); } if (bm.bandwidth_hz) { hostState.currentBandwidthHz = bm.bandwidth_hz; hostCore.syncBandwidthInput(bm.bandwidth_hz); } hostCore.armOptimisticFrequency(bm.freq_hz); hostCore.applyLocalTunedFrequency(bm.freq_hz, true); if (hostState.lastSpectrumData) { hostCore.scheduleSpectrumDraw(); } const tunePromise = (async () => { await bridge.trx.modules.vchan?.takeSchedulerControl(); const onVirtual = await bridge.trx.modules.vchan?.interceptMode(bm.mode) ?? false; if (!onVirtual) { await hostCore.postPath("/set_mode?mode=" + encodeURIComponent(bm.mode)); } if (bm.bandwidth_hz) { const bwHandledByVchan = await bridge.trx.modules.vchan?.interceptBandwidth(bm.bandwidth_hz) ?? false; if (!bwHandledByVchan) { await hostCore.postPath(`/set_bandwidth?hz=${bm.bandwidth_hz}`); } } hostCore.setRigFrequency(bm.freq_hz); })(); const hasDecoders = Array.isArray(bm.decoders) && bm.decoders.length > 0; const modeUp = (bm.mode || "").toUpperCase(); const allToggleDecoders = hostState.decoderRegistry.filter( (d) => d.activation === "toggle" ); const decoderPromise = allToggleDecoders.length ? (async () => { let statusUrl = "/status"; const rigId = hostState.lastActiveRigId; if (rigId) { statusUrl += "?remote=" + encodeURIComponent(rigId); } const statusResp = await fetch(statusUrl); if (!statusResp.ok) return; const st = await statusResp.json(); const toggles = []; for (const d of allToggleDecoders) { const statusKey = d.id.replace(/-/g, "_") + "_decode_enabled"; const currentlyOn = !!st[statusKey]; const compatible = Array.isArray(d.active_modes) && d.active_modes.includes(modeUp); let wanted; if (!compatible) { wanted = false; } else if (hasDecoders) { wanted = bm.decoders?.includes(d.id) ?? false; } else { wanted = currentlyOn; } if (wanted !== currentlyOn) { toggles.push(hostCore.postPath("/toggle_" + d.id.replace(/-/g, "_") + "_decode")); } } if (toggles.length) await Promise.all(toggles); })() : Promise.resolve(); void Promise.all([tunePromise, decoderPromise]).catch((error) => { console.error("Bookmark apply background error:", error); }); } catch (err) { console.error("Failed to apply bookmark:", err); } } bridge.trx.modules.bookmarks = { get overlayList() { return bmOverlayList; }, get overlayRevision() { return bmOverlayRevision; }, refreshOverlay: bmFetchOverlay, invalidateColors() { bmOverlayRevision += 1; }, apply: bmApply, formatFrequency: bmFmtFreq, fetch: bmFetch, populateScopePicker: bmPopulateScopePicker }; function bmUpdateSelectionUi() { const count = bmSelected.size; const canCtrl = bmCanControl(); const visible = count > 0 && canCtrl; const btn = bmEl("bm-del-selected-btn"); const countEl = bmEl("bm-del-selected-count"); if (btn) btn.style.display = visible ? "" : "none"; if (countEl) countEl.textContent = String(count); const moveWrap = bmEl("bm-move-selected-wrap"); const moveCountEl = bmEl("bm-move-selected-count"); if (moveWrap) moveWrap.style.display = visible ? "" : "none"; if (moveCountEl) moveCountEl.textContent = String(count); if (visible) bmPopulateMoveTarget(); const selectAllBtn = bmEl("bm-select-all-btn"); if (selectAllBtn && bmCanControl()) { const allSelected = bmFilteredList.length > 0 && bmFilteredList.every((bm) => bmSelected.has(bm.id)); selectAllBtn.textContent = allSelected ? "Deselect All" : "Select All"; } } function bmPopulateMoveTarget() { const sel = bmEl("bm-move-target"); if (!sel) return; const rigIds = hostState.lastRigIds; const displayNames = hostState.lastRigDisplayNames; const prev = sel.value; sel.innerHTML = ""; if (bmScope !== "general") { const opt = document.createElement("option"); opt.value = "general"; opt.textContent = "General"; sel.appendChild(opt); } rigIds.forEach((id) => { if (id === bmScope) return; const opt = document.createElement("option"); opt.value = id; opt.textContent = displayNames[id] || id; sel.appendChild(opt); }); if (prev && sel.querySelector(`option[value="${CSS.escape(prev)}"]`)) { sel.value = prev; } } async function bmMoveSelected() { const ids = Array.from(bmSelected); if (ids.length === 0) return; const target = bmEl("bm-move-target")?.value; if (!target) return; const targetLabel = bmEl("bm-move-target")?.selectedOptions[0]?.textContent || target; if (!await bridge.trxUi.confirm({ title: "Move selected bookmarks?", message: `${ids.length} bookmark${ids.length > 1 ? "s" : ""} will move to “${targetLabel}”.`, confirmLabel: "Move", danger: false })) return; try { const byScope = {}; for (const id of ids) { const bm = bmList.find((b) => b.id === id); const scope = bm?.scope || bmScope; if (scope === target) continue; (byScope[scope] ||= []).push(id); } await Promise.all(Object.entries(byScope).map( ([scope, scopeIds]) => fetch("/bookmarks/batch_move" + bmScopeParam(false, scope), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ids: scopeIds, to: target }) }).then((r) => { if (!r.ok) throw new Error(`HTTP ${r.status}`); }) )); bmSelected.clear(); bmUpdateSelectionUi(); await bmFetch(bmEl("bm-category-filter").value); } catch (err) { console.error("Failed to move bookmarks:", err); bridge.trxUi.notify?.("Failed to move bookmarks: " + errorMessage(err), { kind: "error" }); } } function bmSyncSelectAllCheckbox() { const selectAll = bmEl("bm-select-all"); if (!selectAll) return; const checkboxes = document.querySelectorAll(".bm-row-sel"); if (checkboxes.length === 0) { selectAll.checked = false; selectAll.indeterminate = false; return; } const checkedCount = Array.from(checkboxes).filter((cb) => cb.checked).length; selectAll.checked = checkedCount === checkboxes.length; selectAll.indeterminate = checkedCount > 0 && checkedCount < checkboxes.length; } async function bmDeleteSelected() { const ids = Array.from(bmSelected); if (ids.length === 0) return; if (!await bridge.trxUi.confirm({ title: "Delete selected bookmarks?", message: `${ids.length} bookmark${ids.length > 1 ? "s" : ""} will be permanently removed.`, confirmLabel: "Delete" })) return; try { const byScope = {}; for (const id of ids) { const bm = bmList.find((b) => b.id === id); const scope = bm?.scope || bmScope; (byScope[scope] ||= []).push(id); } await Promise.all(Object.entries(byScope).map( ([scope, scopeIds]) => fetch("/bookmarks/batch_delete" + bmScopeParam(false, scope), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ids: scopeIds }) }).then((r) => { if (!r.ok) throw new Error(`HTTP ${r.status}`); }) )); bmSelected.clear(); bmUpdateSelectionUi(); await bmFetch(bmEl("bm-category-filter").value); } catch (err) { console.error("Failed to delete bookmarks:", err); bridge.trxUi.notify?.("Failed to delete bookmarks: " + errorMessage(err), { kind: "error" }); } } function bmPopulateScopePicker() { const picker = bmEl("bm-scope-picker"); if (!picker) return; const rigIds = hostState.lastRigIds; const displayNames = hostState.lastRigDisplayNames; const prev = picker.value; while (picker.options.length > 1) picker.remove(1); rigIds.forEach((id) => { const opt = document.createElement("option"); opt.value = id; opt.textContent = displayNames[id] || id; picker.appendChild(opt); }); if (prev && (prev === "general" || rigIds.includes(prev))) { picker.value = prev; } else { picker.value = "general"; } bmScope = picker.value; } (function initBookmarks() { bmSyncAccess(); bmBuildDecoderCheckboxes(); hostCore.onDecoderRegistryReady(bmBuildDecoderCheckboxes); bmPopulateScopePicker(); const scopePicker = bmEl("bm-scope-picker"); if (scopePicker) { scopePicker.addEventListener("change", (e) => { bmScope = e.currentTarget.value; void bmFetch(bmEl("bm-category-filter").value || ""); }); } document.querySelector(".tab-bar")?.addEventListener("click", (e) => { const btn = e.target instanceof Element ? e.target.closest('.tab[data-tab="bookmarks"]') : null; if (!btn) return; void bmFetch(bmEl("bm-category-filter").value); }); bmEl("bm-add-btn").addEventListener("click", () => { bmOpenForm(null); bmPrefillFromStatus(); }); bmEl("bm-category-filter").addEventListener("change", (e) => { void bmFetch(e.currentTarget.value); }); bmEl("bm-mode-filter").addEventListener("change", () => { bmApplyFilters(); }); bmEl("bm-text-filter").addEventListener("input", () => { bmApplyFilters(); }); bmEl("bm-page-prev").addEventListener("click", () => { bmChangePage(-1); }); bmEl("bm-page-next").addEventListener("click", () => { bmChangePage(1); }); bmEl("bm-form").addEventListener("submit", (event) => { void bmSave(event); }); bmEl("bm-form-cancel").addEventListener("click", bmCloseForm); const formWrap = bmEl("bm-form-wrap"); if (formWrap) { formWrap.addEventListener("click", (e) => { if (e.target === formWrap) bmCloseForm(); }); } document.addEventListener("keydown", (e) => { if (e.key === "Escape" && bmEl("bm-form-wrap")?.style.display === "flex") { bmCloseForm(); } }); bmEl("bm-select-all").addEventListener("change", (e) => { const checked = e.currentTarget.checked; document.querySelectorAll(".bm-row-sel").forEach((cb) => { cb.checked = checked; const id = cb.dataset.bmId; if (!id) return; if (checked) bmSelected.add(id); else bmSelected.delete(id); }); bmUpdateSelectionUi(); }); bmEl("bm-select-all-btn").addEventListener("click", () => { const allSelected = bmFilteredList.length > 0 && bmFilteredList.every((bm) => bmSelected.has(bm.id)); if (allSelected) { bmSelected.clear(); } else { bmFilteredList.forEach((bm) => bmSelected.add(bm.id)); } document.querySelectorAll(".bm-row-sel").forEach((cb) => { cb.checked = !!cb.dataset.bmId && bmSelected.has(cb.dataset.bmId); }); bmSyncSelectAllCheckbox(); bmUpdateSelectionUi(); }); bmEl("bm-del-selected-btn").addEventListener("click", () => { void bmDeleteSelected(); }); bmEl("bm-move-selected-btn").addEventListener("click", () => { void bmMoveSelected(); }); bmEl("bm-tbody").addEventListener("click", (e) => { void (async () => { if (!(e.target instanceof Element)) return; const checkbox = e.target.closest(".bm-row-sel"); if (checkbox) { const id = checkbox.dataset.bmId; if (!id) return; if (checkbox.checked) bmSelected.add(id); else bmSelected.delete(id); bmSyncSelectAllCheckbox(); bmUpdateSelectionUi(); return; } const tuneBtn = e.target.closest(".bm-tune-btn"); const editBtn = e.target.closest(".bm-edit-btn"); const delBtn = e.target.closest(".bm-del-btn"); if (tuneBtn) { const bm = bmList.find((b) => b.id === tuneBtn.dataset.bmId); if (bm) bmApply(bm); } else if (editBtn) { const bm = bmList.find((b) => b.id === editBtn.dataset.bmId); if (bm) bmOpenForm(bm); } else if (delBtn) { const id = delBtn.dataset.bmId; if (id) await bmDelete(id); } })(); }); void bmFetch(""); })();