generated from coulomb/repo-seed
- ref-cell.js: REF column component — click=copy deeplink, dblclick=open
- field-help.js: field registry + fieldRow helper with help-tip decoration;
FK fields (task_id, workstream_id, repo_id) render as async-linked cells
with entity-title bubble-help on hover
- GET /token-events/{id} endpoint + get-by-id tests
- GET /repos/by-id/{repo_id} UUID lookup endpoint
- Landing pages: /token-events/[id], /workstreams/[id], /repos/[slug], /tasks/[id]
- token-cost.md: REF + Name columns on all three tables; parallel fetch of
workstreams/tasks for title resolution
- reference.md: entity detail page URL scheme documented
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
102 lines
2.9 KiB
JavaScript
102 lines
2.9 KiB
JavaScript
// refCell(index, recordType, id) → HTMLElement
|
|
//
|
|
// Renders a 1-based row number in a table cell.
|
|
// Single click — copies deep-link to clipboard and flashes "Copied!".
|
|
// Double click — opens deep-link in a new tab.
|
|
//
|
|
// Deep-link format: <origin>/data/<recordType>/<id>
|
|
//
|
|
// Usage:
|
|
// import {refCell} from "./components/ref-cell.js";
|
|
// // in an Inputs.table format callback:
|
|
// format: { id: (_, i) => refCell(i + 1, "token-events", row.id) }
|
|
|
|
const _STYLE_ID = "refcell-global-style";
|
|
if (!document.getElementById(_STYLE_ID)) {
|
|
const s = document.createElement("style");
|
|
s.id = _STYLE_ID;
|
|
s.textContent = `
|
|
.ref-cell {
|
|
display: inline-block;
|
|
font-family: var(--monospace, monospace);
|
|
font-size: 0.78rem;
|
|
color: var(--theme-foreground-focus, #3b82f6);
|
|
cursor: pointer;
|
|
user-select: none;
|
|
padding: 0 2px;
|
|
border-radius: 3px;
|
|
transition: background 0.1s;
|
|
}
|
|
.ref-cell:hover {
|
|
background: var(--theme-foreground-faint, #e8f0fe);
|
|
}
|
|
.ref-cell-toast {
|
|
position: fixed;
|
|
z-index: 10000;
|
|
background: var(--theme-background, #fff);
|
|
border: 1px solid var(--theme-foreground-faint, #ddd);
|
|
border-radius: 6px;
|
|
padding: 0.3rem 0.65rem;
|
|
font-size: 0.75rem;
|
|
color: var(--theme-foreground, #333);
|
|
box-shadow: 0 4px 14px rgba(0,0,0,0.12);
|
|
opacity: 0;
|
|
transition: opacity 0.1s ease;
|
|
pointer-events: none;
|
|
}
|
|
.ref-cell-toast.ref-cell-toast-visible { opacity: 1; }
|
|
`;
|
|
document.head.appendChild(s);
|
|
}
|
|
|
|
function _showToast(anchorEl, text) {
|
|
const toast = document.createElement("div");
|
|
toast.className = "ref-cell-toast";
|
|
toast.textContent = text;
|
|
document.body.appendChild(toast);
|
|
|
|
const rect = anchorEl.getBoundingClientRect();
|
|
const gap = 6;
|
|
toast.style.left = `${rect.left}px`;
|
|
toast.style.top = `${rect.top - toast.offsetHeight - gap}px`;
|
|
|
|
requestAnimationFrame(() => toast.classList.add("ref-cell-toast-visible"));
|
|
setTimeout(() => {
|
|
toast.classList.remove("ref-cell-toast-visible");
|
|
toast.addEventListener("transitionend", () => toast.remove(), {once: true});
|
|
}, 1200);
|
|
}
|
|
|
|
export function refCell(index, recordType, id) {
|
|
const deepLink = `${location.origin}/${recordType}/${id}`;
|
|
|
|
const el = document.createElement("span");
|
|
el.className = "ref-cell";
|
|
el.title = `Click to copy link · Double-click to open\n${deepLink}`;
|
|
el.textContent = String(index);
|
|
|
|
let clickTimer = null;
|
|
|
|
el.addEventListener("click", (e) => {
|
|
e.stopPropagation();
|
|
// Use a short delay so a double-click cancels the single-click handler.
|
|
clickTimer = setTimeout(async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(deepLink);
|
|
_showToast(el, "Copied!");
|
|
} catch {
|
|
// Fallback for environments where clipboard API is blocked.
|
|
_showToast(el, deepLink);
|
|
}
|
|
}, 180);
|
|
});
|
|
|
|
el.addEventListener("dblclick", (e) => {
|
|
e.stopPropagation();
|
|
clearTimeout(clickTimer);
|
|
window.open(deepLink, "_blank", "noopener,noreferrer");
|
|
});
|
|
|
|
return el;
|
|
}
|