Bot:Gadget-figureSource.js: Difference between revisions
Content deleted Content added
Popover tags in address order: caption -> column header -> row label |
Footnote-pattern peek: hover card / touch banner, tabindex tap fix, click = viewer |
||
Line 234:
var tag = function (el, entry) {
el.classList.add('fig-src');
// tabindex is the LOAD-BEARING iOS detail (the footnote cue
// template ships it too): a focusable element passes Safari's
// "clickable" heuristic, so taps reliably synthesize click
// events for document-delegated listeners. Without it, taps on
// a bare td/span inside a scroll wrapper produce NOTHING.
el.setAttribute('tabindex', '0');
byEl.set(el, entry);
};
Line 268 ⟶ 274:
skipped + ' skipped');
return byEl;
}
Line 319 ⟶ 308:
}
// ── source peek — the FOOTNOTE pattern (Common.js .ed-fn machinery) ────
// One body-appended surface, never an in-flow element: a fixed CARD on
// hover (desktop), a full-width top BANNER on tap (touch). Body-appended
// means root stacking context — no sticky column or table scroll wrapper
// can cover or clip it, the bug class the old in-flow popover had.
// Desktop: hover = peek (card is pointer-events:none, like .ed-fn-popover,
// so it can hold no link), click = open the viewer. Touch: tap = banner
// (interactive, carries the open link), next tap anywhere dismisses.
function addressOf(entry) {
return entry.page
? 'p. ' + entry.page
: (entry.reason === 'ambiguous'
? 'no source location (ambiguous ×' + entry.candidates + ')'
: 'no source location (' + (entry.reason || 'unmatched') + ')');
}
function peekHtml(el, entry, withLink) {
var openHref = pageUrl(VIEWER_PAGE, {
doc: mw.config.get('wgPageName'), fig: entry.id || '' });
(entry.rect ? '<canvas></canvas>' : '') + '</div>' +
tagsHtml(el, entry) +
'<div class="fig-
'</span>' + (withLink && entry.id ? '<a href="' + openHref +
'" target="_blank">open ↗</a>' : '') + '</div>';
}
function renderCrop(container, entry, pdf, onDone) {
if (!entry.rect) { return; }
pdf.page(entry.page).then(function (pg) {
if (onDone) { setTimeout(onDone, 0); }
var s = pg.viewport.scale;
var x0 = Math.max(0, (entry.rect[0] - CROP_MARGIN_X) * s);
Line 350 ⟶ 347:
var h = Math.min(pg.canvas.height - y0,
(entry.rect[3] - entry.rect[1] + 2 * CROP_MARGIN_Y) * s);
var cv =
if (!cv) { return; } // peek already dismissed
cv.width = w; cv.height = h;
var ctx = cv.getContext('2d');
Line 362 ⟶ 359:
(entry.rect[3] - entry.rect[1]) * s + 4);
}, function () {
if (crop) { crop.textContent = 'mirror PDF not available'; }
});
}
function attachPeek(byEl, pdf) {
var box = null, activeCue = null;
function close() {
if (box) { box.remove(); box = null; }
activeCue = null;
document.removeEventListener('click', onAway, true);
}
function onAway(e) {
if (!cueOf(e)) { close(); }
}
function cueOf(e) {
var t = e.target;
if (!t || !t.closest) { return null; }
if (t.closest('.ed-fn')) { return null; } // footnote cues win
var el = t.closest('.fig-src');
return el && byEl.has(el) ? el : null;
}
var touch = window.matchMedia &&
window.matchMedia('(hover: none)').matches;
if (touch) {
// tap -> top banner (interactive: it carries the open link);
// the next tap anywhere dismisses — .ed-fn banner behavior
document.addEventListener('click', function (e) {
var cue = cueOf(e);
if (!cue) { return; }
e.preventDefault();
e.stopPropagation();
close();
var entry = byEl.get(cue);
box = document.createElement('div');
box.className = 'fig-banner';
box.innerHTML = peekHtml(cue, entry, true);
document.body.appendChild(box);
renderCrop(box, entry, pdf);
setTimeout(function () {
document.addEventListener('click', onAway, true);
}, 0);
}, false);
} else {
// hover -> peek card beside the cue (never covering the cell)
document.addEventListener('mouseover', function (e) {
var cue = cueOf(e);
if (!cue || cue === activeCue) { return; }
close();
activeCue = cue;
var entry = byEl.get(cue);
box = document.createElement('div');
box.className = 'fig-card';
box.innerHTML = peekHtml(cue, entry, false);
document.body.appendChild(box);
var place = function () {
if (!box || activeCue !== cue) { return; }
var r = cue.getBoundingClientRect();
var left = Math.max(8, Math.min(r.left,
window.innerWidth - box.offsetWidth - 8));
var top = r.top - box.offsetHeight - 8; // above the cue…
if (top < 8) { top = r.bottom + 8; } // …or below
box.style.left = left + 'px';
box.style.top = top + 'px';
};
place();
// the crop arrives async and changes the card's height —
// re-place so an above-the-cue card grows UP, not down
// over the cell being compared
renderCrop(box, entry, pdf, place);
});
document.addEventListener('mouseout', function (e) {
if (!activeCue) { return; }
var to = e.relatedTarget;
if (to && to.closest &&
to.closest('.fig-src') === activeCue) { return; }
close();
});
window.addEventListener('scroll', function () {
if (box) { close(); }
}, true);
// click = drill into the full viewer (desktop click is reliable)
document.addEventListener('click', function (e) {
var cue = cueOf(e);
if (!cue) { return; }
var entry = byEl.get(cue);
if (!entry.id) { return; }
window.open(pageUrl(VIEWER_PAGE, {
doc: mw.config.get('wgPageName'), fig: entry.id }),
'_blank');
});
}
}
Line 453 ⟶ 540:
var byEl = buildArchiveMode(sidecar);
if (!byEl.size) { return; }
});
}
Line 470 ⟶ 551:
// (no inner padding/margins); only the text block below is inset.
// All popover text follows the house small size, footer in page ink.
//
// .fig-card = .ed-fn-popover's shape: fixed, body-appended (root
//
// pointer-events:none, ink frame, house shadow. Color is pinned to
// --text-dark because body-attached elements inherit the SKIN's
// #202122, not the content area's ink.
'
'max-width:min(420px,90vw);background:#fff;padding:0;' +
'border:1px solid #0d0d0d;color:var(--text-dark,#0d0d0d);' +
'box-shadow:0 4px 16px rgba(43,41,38,.18);pointer-events:none}' +
// omitted property lets a stale stacked stylesheet rule survive)
'.fig-
'display:block;border:0;margin:0}' +
'
'.fig-
'.fig-tags{font-size:var(--ed-small,14px);
'
'.fig-addr{display:flex;justify-content:space-between;' +
'font-size:var(--ed-small,14px);color:inherit}' +
'.fig-card .fig-addr{padding:6px 10px 10px}' +
// .fig-banner = .ed-fn-banner's shape: full-width, pinned to the
// top, near-black, safe-area padded — the touch surface, and it IS
// interactive (carries the open link), unlike the hover card
'.fig-banner{position:fixed;top:0;left:0;right:0;' +
'z-index:2147483647;box-sizing:border-box;' +
'padding:calc(0.9em + env(safe-area-inset-top)) 1.2em 0.9em;' +
'background:rgba(20,18,16,0.92);color:#fff;' +
'font-size:var(--ed-small,14px);line-height:1.5}' +
'.fig-banner canvas{margin-bottom:8px}' +
'.fig-banner .fig-addr{margin-top:4px}' +
'.fig-banner a{color:#fff;text-decoration:underline}' +
// one explicit size for every bar item — name, page label, buttons —
// so nothing inherits a divergent size from the surrounding skin
| |||