markdownr 0.5.0 → 0.5.1
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.
- checksums.yaml +4 -4
- data/lib/markdown_server/version.rb +1 -1
- data/views/layout.erb +164 -238
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cf955ffcbe5dfc1e1f5070b3fa36a6d2d15a092e7be6ca7f8ac5dcd4b863162a
|
|
4
|
+
data.tar.gz: 3ea514cfed258bc71a8a3e82f6ef883e1ed619fc63b46e9217eb4745c455fdd5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 19f44c77aab4f8a5a2978c85f8fd900f6a0cc45d9399897a26acae80d5809ce57669716a1a5e488c276a4b53e0b2e5d5e3bc13d8b6463a5c8d9c76342eb39e4f
|
|
7
|
+
data.tar.gz: b8adb9e49b1def08d6d3f4061de5048d3cf14232d4ce0c3b9fde51f605f52fb9338a9b39ad49f569d9663fdf9e718e1396f37674e55c7c3f025ffa7a271d3548
|
data/views/layout.erb
CHANGED
|
@@ -678,7 +678,7 @@
|
|
|
678
678
|
/* Footnote list — hide numeric markers; labels are inline via <strong> */
|
|
679
679
|
.footnotes ol { list-style: none; padding-left: 1.2em; }
|
|
680
680
|
|
|
681
|
-
/*
|
|
681
|
+
/* Left-click / tap link context popup */
|
|
682
682
|
.link-ctx-popup {
|
|
683
683
|
position: fixed;
|
|
684
684
|
z-index: 500;
|
|
@@ -705,6 +705,13 @@
|
|
|
705
705
|
background: #faf8f4;
|
|
706
706
|
z-index: 1;
|
|
707
707
|
}
|
|
708
|
+
.link-ctx-popup-title-wrap {
|
|
709
|
+
flex: 1;
|
|
710
|
+
min-width: 0;
|
|
711
|
+
display: flex;
|
|
712
|
+
align-items: center;
|
|
713
|
+
gap: 4px;
|
|
714
|
+
}
|
|
708
715
|
.link-ctx-popup-title {
|
|
709
716
|
font-family: Georgia, "Times New Roman", serif;
|
|
710
717
|
font-weight: 700;
|
|
@@ -712,7 +719,9 @@
|
|
|
712
719
|
color: #3a3a3a;
|
|
713
720
|
min-width: 0;
|
|
714
721
|
word-break: break-word;
|
|
722
|
+
text-decoration: none;
|
|
715
723
|
}
|
|
724
|
+
a.link-ctx-popup-title:hover { color: #8b6914; }
|
|
716
725
|
.link-ctx-popup-close {
|
|
717
726
|
background: none;
|
|
718
727
|
border: none;
|
|
@@ -724,6 +733,26 @@
|
|
|
724
733
|
flex-shrink: 0;
|
|
725
734
|
}
|
|
726
735
|
.link-ctx-popup-close:hover { color: #2c2c2c; }
|
|
736
|
+
.link-ctx-popup-newtab {
|
|
737
|
+
color: #666;
|
|
738
|
+
flex-shrink: 0;
|
|
739
|
+
display: flex;
|
|
740
|
+
align-items: center;
|
|
741
|
+
text-decoration: none;
|
|
742
|
+
padding: 0 0.2rem;
|
|
743
|
+
}
|
|
744
|
+
.link-ctx-popup-newtab:hover { color: #8b6914; }
|
|
745
|
+
.link-ctx-popup-back {
|
|
746
|
+
background: none;
|
|
747
|
+
border: none;
|
|
748
|
+
font-size: 1rem;
|
|
749
|
+
line-height: 1;
|
|
750
|
+
color: #888;
|
|
751
|
+
cursor: pointer;
|
|
752
|
+
padding: 0 0.3rem 0 0;
|
|
753
|
+
flex-shrink: 0;
|
|
754
|
+
}
|
|
755
|
+
.link-ctx-popup-back:hover { color: #2c2c2c; }
|
|
727
756
|
.link-ctx-popup-body {
|
|
728
757
|
padding: 0.75rem 1rem;
|
|
729
758
|
}
|
|
@@ -1383,14 +1412,13 @@
|
|
|
1383
1412
|
}, { passive: true });
|
|
1384
1413
|
})();
|
|
1385
1414
|
|
|
1386
|
-
//
|
|
1415
|
+
// Left-click / tap popup for all links
|
|
1387
1416
|
(function() {
|
|
1388
1417
|
var cache = Object.create(null);
|
|
1389
1418
|
var popup = null;
|
|
1390
|
-
var longPressTimer = null;
|
|
1391
1419
|
var touchMoved = false;
|
|
1392
|
-
var
|
|
1393
|
-
var
|
|
1420
|
+
var historyStack = [];
|
|
1421
|
+
var currentPopupPos = { x: 0, y: 0 };
|
|
1394
1422
|
|
|
1395
1423
|
function escHtml(s) {
|
|
1396
1424
|
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
@@ -1416,19 +1444,91 @@
|
|
|
1416
1444
|
return '/preview/' + resolved.pathname.replace(/^\/browse\//, '');
|
|
1417
1445
|
}
|
|
1418
1446
|
|
|
1419
|
-
|
|
1420
|
-
|
|
1447
|
+
var extLinkIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:middle;flex-shrink:0"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>';
|
|
1448
|
+
|
|
1449
|
+
function showPopup(x, y, title, bodyHtml, href) {
|
|
1450
|
+
// Remove old popup without clearing historyStack
|
|
1451
|
+
if (popup && popup.parentNode) popup.parentNode.removeChild(popup);
|
|
1452
|
+
popup = null;
|
|
1453
|
+
currentPopupPos = { x: x, y: y };
|
|
1454
|
+
|
|
1421
1455
|
popup = document.createElement('div');
|
|
1422
1456
|
popup.className = 'link-ctx-popup';
|
|
1457
|
+
var backBtn = historyStack.length > 0 ? '<button class="link-ctx-popup-back" aria-label="Back">\u2190</button>' : '';
|
|
1458
|
+
var titleHtml = href
|
|
1459
|
+
? '<div class="link-ctx-popup-title-wrap">' +
|
|
1460
|
+
'<a class="link-ctx-popup-title" href="' + escHtml(href) + '"><span>' + escHtml(title) + '</span></a>' +
|
|
1461
|
+
'<a class="link-ctx-popup-newtab" href="' + escHtml(href) + '" target="_blank" rel="noopener" title="Open in new tab">' + extLinkIcon + '</a>' +
|
|
1462
|
+
'</div>'
|
|
1463
|
+
: '<span class="link-ctx-popup-title-wrap"><span class="link-ctx-popup-title"><span>' + escHtml(title) + '</span></span></span>';
|
|
1423
1464
|
popup.innerHTML =
|
|
1424
1465
|
'<div class="link-ctx-popup-header">' +
|
|
1425
|
-
|
|
1466
|
+
backBtn + titleHtml +
|
|
1426
1467
|
'<button class="link-ctx-popup-close" aria-label="Close">\u00d7</button>' +
|
|
1427
1468
|
'</div>' +
|
|
1428
1469
|
'<div class="link-ctx-popup-body">' + bodyHtml + '</div>';
|
|
1429
1470
|
document.body.appendChild(popup);
|
|
1430
1471
|
|
|
1431
|
-
|
|
1472
|
+
repositionPopup();
|
|
1473
|
+
|
|
1474
|
+
var backBtnEl = popup.querySelector('.link-ctx-popup-back');
|
|
1475
|
+
if (backBtnEl) {
|
|
1476
|
+
backBtnEl.addEventListener('click', function(e) {
|
|
1477
|
+
e.stopPropagation();
|
|
1478
|
+
var prev = historyStack.pop();
|
|
1479
|
+
if (prev) showPopup(prev.x, prev.y, prev.title, prev.bodyHtml, prev.href);
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
popup.querySelector('.link-ctx-popup-close').addEventListener('click', hidePopup);
|
|
1483
|
+
popup.addEventListener('click', function(e) { e.stopPropagation(); });
|
|
1484
|
+
|
|
1485
|
+
// Links in the popup body push current state to history, then open a new popup
|
|
1486
|
+
// at the same screen position as the current popup
|
|
1487
|
+
var body = popup.querySelector('.link-ctx-popup-body');
|
|
1488
|
+
body.addEventListener('click', function(e) {
|
|
1489
|
+
var anchor = findLink(e.target);
|
|
1490
|
+
if (!anchor) return;
|
|
1491
|
+
var linkHref = anchor.getAttribute('href');
|
|
1492
|
+
if (!linkHref || isAnchorOnly(linkHref)) return;
|
|
1493
|
+
e.stopPropagation();
|
|
1494
|
+
e.preventDefault();
|
|
1495
|
+
var savedPos = { x: currentPopupPos.x, y: currentPopupPos.y };
|
|
1496
|
+
var titleEl = popup.querySelector('.link-ctx-popup-title');
|
|
1497
|
+
historyStack.push({
|
|
1498
|
+
x: savedPos.x, y: savedPos.y,
|
|
1499
|
+
title: titleEl ? titleEl.querySelector('span').textContent : '',
|
|
1500
|
+
bodyHtml: body.innerHTML,
|
|
1501
|
+
href: titleEl ? (titleEl.getAttribute('href') || '') : ''
|
|
1502
|
+
});
|
|
1503
|
+
handleLink(anchor, savedPos.x, savedPos.y, true);
|
|
1504
|
+
});
|
|
1505
|
+
body.addEventListener('touchend', function(e) {
|
|
1506
|
+
if (touchMoved) return;
|
|
1507
|
+
var anchor = findLink(e.target);
|
|
1508
|
+
if (!anchor) return;
|
|
1509
|
+
var linkHref = anchor.getAttribute('href');
|
|
1510
|
+
if (!linkHref || isAnchorOnly(linkHref)) return;
|
|
1511
|
+
e.stopPropagation();
|
|
1512
|
+
e.preventDefault();
|
|
1513
|
+
var savedPos = { x: currentPopupPos.x, y: currentPopupPos.y };
|
|
1514
|
+
var titleEl = popup.querySelector('.link-ctx-popup-title');
|
|
1515
|
+
historyStack.push({
|
|
1516
|
+
x: savedPos.x, y: savedPos.y,
|
|
1517
|
+
title: titleEl ? titleEl.querySelector('span').textContent : '',
|
|
1518
|
+
bodyHtml: body.innerHTML,
|
|
1519
|
+
href: titleEl ? (titleEl.getAttribute('href') || '') : ''
|
|
1520
|
+
});
|
|
1521
|
+
handleLink(anchor, savedPos.x, savedPos.y, true);
|
|
1522
|
+
}, { passive: false });
|
|
1523
|
+
|
|
1524
|
+
['touchstart','touchmove','touchend'].forEach(function(ev) {
|
|
1525
|
+
popup.addEventListener(ev, function(e) { e.stopPropagation(); }, { passive: true });
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
function repositionPopup() {
|
|
1530
|
+
if (!popup) return;
|
|
1531
|
+
var x = currentPopupPos.x, y = currentPopupPos.y;
|
|
1432
1532
|
var vw = Math.min(window.innerWidth, document.documentElement.clientWidth);
|
|
1433
1533
|
var vh = window.innerHeight;
|
|
1434
1534
|
var left = x + 12;
|
|
@@ -1438,29 +1538,25 @@
|
|
|
1438
1538
|
if (top < 8) top = 8;
|
|
1439
1539
|
popup.style.left = left + 'px';
|
|
1440
1540
|
popup.style.top = top + 'px';
|
|
1441
|
-
|
|
1442
|
-
popup.querySelector('.link-ctx-popup-close').addEventListener('click', hidePopup);
|
|
1443
|
-
popup.addEventListener('click', function(e) { e.stopPropagation(); });
|
|
1444
|
-
popup.addEventListener('contextmenu', function(e) { e.preventDefault(); e.stopPropagation(); });
|
|
1445
|
-
['touchstart','touchmove','touchend'].forEach(function(ev) {
|
|
1446
|
-
popup.addEventListener(ev, function(e) { e.stopPropagation(); }, { passive: true });
|
|
1447
|
-
});
|
|
1448
1541
|
}
|
|
1449
1542
|
|
|
1450
1543
|
function updatePopup(bodyHtml, title) {
|
|
1451
1544
|
if (!popup) return;
|
|
1452
1545
|
var body = popup.querySelector('.link-ctx-popup-body');
|
|
1453
|
-
var
|
|
1546
|
+
var titleTextEl = popup.querySelector('.link-ctx-popup-title span');
|
|
1454
1547
|
if (body) body.innerHTML = bodyHtml;
|
|
1455
|
-
if (title &&
|
|
1548
|
+
if (title && titleTextEl) titleTextEl.textContent = title;
|
|
1549
|
+
repositionPopup();
|
|
1456
1550
|
}
|
|
1457
1551
|
|
|
1458
1552
|
function hidePopup() {
|
|
1459
1553
|
if (popup && popup.parentNode) popup.parentNode.removeChild(popup);
|
|
1460
1554
|
popup = null;
|
|
1555
|
+
historyStack = [];
|
|
1461
1556
|
}
|
|
1462
1557
|
|
|
1463
|
-
function handleLink(anchor, x, y) {
|
|
1558
|
+
function handleLink(anchor, x, y, chained) {
|
|
1559
|
+
if (!chained) historyStack = [];
|
|
1464
1560
|
var href = anchor.getAttribute('href');
|
|
1465
1561
|
if (!href || isAnchorOnly(href)) return;
|
|
1466
1562
|
var label = anchor.textContent.trim() || href;
|
|
@@ -1469,13 +1565,13 @@
|
|
|
1469
1565
|
var path = previewPath(href);
|
|
1470
1566
|
var cached = cache[path];
|
|
1471
1567
|
if (cached && typeof cached === 'object') {
|
|
1472
|
-
showPopup(x, y, cached.title || label, cached.html);
|
|
1568
|
+
showPopup(x, y, cached.title || label, cached.html, href);
|
|
1473
1569
|
} else if (cached === false) {
|
|
1474
1570
|
showPopup(x, y, label,
|
|
1475
1571
|
'<div class="link-ctx-popup-url">' + escHtml(href) + '</div>' +
|
|
1476
|
-
'<p style="margin:0;color:#888;font-family:sans-serif;font-size:0.82rem">Preview not available</p>');
|
|
1572
|
+
'<p style="margin:0;color:#888;font-family:sans-serif;font-size:0.82rem">Preview not available</p>', href);
|
|
1477
1573
|
} else {
|
|
1478
|
-
showPopup(x, y, label, '<p style="opacity:0.5;margin:0;font-family:sans-serif">Loading\u2026</p>');
|
|
1574
|
+
showPopup(x, y, label, '<p style="opacity:0.5;margin:0;font-family:sans-serif">Loading\u2026</p>', href);
|
|
1479
1575
|
if (cached === undefined) {
|
|
1480
1576
|
cache[path] = null;
|
|
1481
1577
|
fetch(path)
|
|
@@ -1495,13 +1591,13 @@
|
|
|
1495
1591
|
var extKey = 'ext:' + href;
|
|
1496
1592
|
var extCached = cache[extKey];
|
|
1497
1593
|
if (extCached && typeof extCached === 'object') {
|
|
1498
|
-
showPopup(x, y, extCached.title || label, extCached.html);
|
|
1594
|
+
showPopup(x, y, extCached.title || label, extCached.html, href);
|
|
1499
1595
|
} else if (extCached === false) {
|
|
1500
1596
|
showPopup(x, y, label,
|
|
1501
1597
|
'<div class="link-ctx-popup-url">' + escHtml(href) + '</div>' +
|
|
1502
|
-
'<p style="margin:0.5rem 0 0;color:#888;font-family:sans-serif;font-size:0.82rem">Could not fetch page content.</p>');
|
|
1598
|
+
'<p style="margin:0.5rem 0 0;color:#888;font-family:sans-serif;font-size:0.82rem">Could not fetch page content.</p>', href);
|
|
1503
1599
|
} else {
|
|
1504
|
-
showPopup(x, y, label, '<p style="opacity:0.5;margin:0;font-family:sans-serif">Loading\u2026</p>');
|
|
1600
|
+
showPopup(x, y, label, '<p style="opacity:0.5;margin:0;font-family:sans-serif">Loading\u2026</p>', href);
|
|
1505
1601
|
if (extCached === undefined) {
|
|
1506
1602
|
cache[extKey] = null;
|
|
1507
1603
|
fetch('/fetch?url=' + encodeURIComponent(href))
|
|
@@ -1526,246 +1622,76 @@
|
|
|
1526
1622
|
}
|
|
1527
1623
|
}
|
|
1528
1624
|
} else {
|
|
1529
|
-
showPopup(x, y, label, '<div class="link-ctx-popup-url">' + escHtml(href) + '</div>');
|
|
1625
|
+
showPopup(x, y, label, '<div class="link-ctx-popup-url">' + escHtml(href) + '</div>', href);
|
|
1530
1626
|
}
|
|
1531
1627
|
}
|
|
1532
1628
|
|
|
1533
|
-
//
|
|
1534
|
-
document.addEventListener('
|
|
1535
|
-
if (popup && popup.contains(e.target))
|
|
1629
|
+
// Left-click on desktop
|
|
1630
|
+
document.addEventListener('click', function(e) {
|
|
1631
|
+
if (popup && popup.contains(e.target)) return;
|
|
1536
1632
|
var anchor = findLink(e.target);
|
|
1537
|
-
if (!anchor) return;
|
|
1633
|
+
if (!anchor) { hidePopup(); return; }
|
|
1538
1634
|
var href = anchor.getAttribute('href');
|
|
1539
|
-
if (!href || isAnchorOnly(href)) return;
|
|
1635
|
+
if (!href || isAnchorOnly(href)) { hidePopup(); return; }
|
|
1636
|
+
if (!anchor.closest('.md-content')) { hidePopup(); return; }
|
|
1540
1637
|
e.preventDefault();
|
|
1541
|
-
|
|
1542
|
-
pendingTouch = null;
|
|
1543
|
-
if (!longPressHandled) handleLink(anchor, e.clientX, e.clientY);
|
|
1544
|
-
longPressHandled = false;
|
|
1638
|
+
handleLink(anchor, e.clientX, e.clientY);
|
|
1545
1639
|
});
|
|
1546
1640
|
|
|
1547
|
-
//
|
|
1641
|
+
// Touch tap
|
|
1548
1642
|
document.addEventListener('touchstart', function(e) {
|
|
1549
1643
|
if (e.touches.length !== 1) return;
|
|
1550
|
-
if (popup && popup.contains(e.target)) return;
|
|
1551
|
-
var anchor = findLink(e.target);
|
|
1552
|
-
if (!anchor) return;
|
|
1553
|
-
var href = anchor.getAttribute('href');
|
|
1554
|
-
if (!href || isAnchorOnly(href)) return;
|
|
1555
1644
|
touchMoved = false;
|
|
1556
|
-
longPressHandled = false;
|
|
1557
|
-
var t = e.touches[0];
|
|
1558
|
-
pendingTouch = { anchor: anchor, x: t.clientX, y: t.clientY };
|
|
1559
|
-
clearTimeout(longPressTimer);
|
|
1560
|
-
longPressTimer = setTimeout(function() {
|
|
1561
|
-
if (!touchMoved && pendingTouch) {
|
|
1562
|
-
longPressHandled = true;
|
|
1563
|
-
handleLink(pendingTouch.anchor, pendingTouch.x, pendingTouch.y);
|
|
1564
|
-
pendingTouch = null;
|
|
1565
|
-
setTimeout(function() { longPressHandled = false; }, 400);
|
|
1566
|
-
}
|
|
1567
|
-
}, 600);
|
|
1568
1645
|
}, { passive: true });
|
|
1569
1646
|
|
|
1570
1647
|
document.addEventListener('touchmove', function() {
|
|
1571
1648
|
touchMoved = true;
|
|
1572
|
-
clearTimeout(longPressTimer);
|
|
1573
|
-
pendingTouch = null;
|
|
1574
|
-
}, { passive: true });
|
|
1575
|
-
|
|
1576
|
-
document.addEventListener('touchend', function() {
|
|
1577
|
-
clearTimeout(longPressTimer);
|
|
1578
|
-
pendingTouch = null;
|
|
1579
1649
|
}, { passive: true });
|
|
1580
1650
|
|
|
1581
|
-
document.addEventListener('
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1651
|
+
document.addEventListener('touchend', function(e) {
|
|
1652
|
+
if (touchMoved) return;
|
|
1653
|
+
if (popup && popup.contains(e.target)) return;
|
|
1654
|
+
var anchor = findLink(e.target);
|
|
1655
|
+
if (!anchor) { hidePopup(); return; }
|
|
1656
|
+
var href = anchor.getAttribute('href');
|
|
1657
|
+
if (!href || isAnchorOnly(href)) { hidePopup(); return; }
|
|
1658
|
+
if (!anchor.closest('.md-content')) { hidePopup(); return; }
|
|
1659
|
+
e.preventDefault();
|
|
1660
|
+
var touch = e.changedTouches[0];
|
|
1661
|
+
handleLink(anchor, touch.clientX, touch.clientY);
|
|
1662
|
+
}, { passive: false });
|
|
1586
1663
|
|
|
1587
|
-
document.addEventListener('click', function(e) {
|
|
1588
|
-
if (popup && !popup.contains(e.target)) hidePopup();
|
|
1589
|
-
});
|
|
1590
1664
|
document.addEventListener('keydown', function(e) {
|
|
1591
1665
|
if (e.key === 'Escape' && popup) hidePopup();
|
|
1592
1666
|
});
|
|
1593
1667
|
|
|
1594
|
-
})();
|
|
1595
|
-
|
|
1596
1668
|
<% if settings.link_tooltips %>
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
var cache = Object.create(null);
|
|
1603
|
-
var activePopup = null;
|
|
1604
|
-
var activeAnchor = null;
|
|
1605
|
-
var hideTimer = null;
|
|
1606
|
-
|
|
1607
|
-
function escHtml(s) {
|
|
1608
|
-
return String(s)
|
|
1609
|
-
.replace(/&/g, '&').replace(/</g, '<')
|
|
1610
|
-
.replace(/>/g, '>').replace(/"/g, '"');
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
|
-
function isLocalMdLink(a) {
|
|
1614
|
-
if (a.classList.contains('broken')) return false;
|
|
1615
|
-
var href = a.getAttribute('href');
|
|
1616
|
-
if (!href) return false;
|
|
1617
|
-
if (/^https?:\/\//i.test(href)) return false;
|
|
1618
|
-
var path = href.split('?')[0].split('#')[0];
|
|
1619
|
-
return /\.md$/i.test(path);
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
function isExternalLink(a) {
|
|
1623
|
-
var href = a.getAttribute('href');
|
|
1624
|
-
return href && /^https?:\/\//i.test(href);
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
function previewUrl(a) {
|
|
1628
|
-
var resolved = new URL(a.getAttribute('href'), location.href);
|
|
1629
|
-
return '/preview/' + resolved.pathname.replace(/^\/browse\//, '');
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
// Returns true if el has an ancestor that clips overflow (prevents absolute children escaping)
|
|
1633
|
-
function hasOverflowClip(el) {
|
|
1634
|
-
var parent = el.parentElement;
|
|
1635
|
-
while (parent && parent !== document.body) {
|
|
1636
|
-
var s = window.getComputedStyle(parent);
|
|
1637
|
-
if (s.overflow !== 'visible' || s.overflowX !== 'visible' || s.overflowY !== 'visible') return true;
|
|
1638
|
-
parent = parent.parentElement;
|
|
1639
|
-
}
|
|
1640
|
-
return false;
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
function showPopup(anchor, html) {
|
|
1644
|
-
hidePopup();
|
|
1645
|
-
var pop = document.createElement('div');
|
|
1646
|
-
pop.className = 'link-preview-popup';
|
|
1647
|
-
pop.innerHTML = html;
|
|
1648
|
-
activePopup = pop;
|
|
1649
|
-
activeAnchor = anchor;
|
|
1650
|
-
|
|
1651
|
-
var vw = Math.min(window.innerWidth, document.documentElement.clientWidth);
|
|
1652
|
-
var vh = window.innerHeight;
|
|
1653
|
-
|
|
1654
|
-
if (hasOverflowClip(anchor)) {
|
|
1655
|
-
// Anchor is inside an overflow container — use fixed positioning to escape clipping
|
|
1656
|
-
pop.style.position = 'fixed';
|
|
1657
|
-
pop.style.bottom = 'auto'; // override the CSS class's bottom: 100%
|
|
1658
|
-
document.body.appendChild(pop);
|
|
1659
|
-
var aRect = anchor.getBoundingClientRect();
|
|
1660
|
-
var popH = pop.offsetHeight;
|
|
1661
|
-
var popW = pop.offsetWidth;
|
|
1662
|
-
// Prefer above the link, fall back to below
|
|
1663
|
-
var top = aRect.top - popH - 4;
|
|
1664
|
-
if (top < 8) top = aRect.bottom + 4;
|
|
1665
|
-
if (top + popH > vh - 8) top = Math.max(8, vh - popH - 8);
|
|
1666
|
-
var left = aRect.left;
|
|
1667
|
-
if (left + popW > vw - 8) left = Math.max(8, vw - popW - 8);
|
|
1668
|
-
pop.style.top = top + 'px';
|
|
1669
|
-
pop.style.left = left + 'px';
|
|
1670
|
-
} else {
|
|
1671
|
-
// Normal anchor-attached positioning
|
|
1672
|
-
anchor.appendChild(pop);
|
|
1673
|
-
var rect = pop.getBoundingClientRect();
|
|
1674
|
-
var shift = 0;
|
|
1675
|
-
if (rect.right > vw - 8) shift = (vw - 8) - rect.right;
|
|
1676
|
-
if (rect.left + shift < 8) shift = 8 - rect.left;
|
|
1677
|
-
if (shift !== 0) pop.style.left = (parseFloat(pop.style.left || 0) + shift) + 'px';
|
|
1678
|
-
// If top goes off screen, flip below the link instead
|
|
1679
|
-
rect = pop.getBoundingClientRect();
|
|
1680
|
-
if (rect.top < 8) {
|
|
1681
|
-
pop.style.bottom = 'auto';
|
|
1682
|
-
pop.style.top = '100%';
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
|
-
// Keep popup open while hovering over it
|
|
1687
|
-
pop.addEventListener('mouseenter', function() { clearTimeout(hideTimer); });
|
|
1688
|
-
pop.addEventListener('mouseleave', function() { hideTimer = setTimeout(hidePopup, 10); });
|
|
1689
|
-
|
|
1690
|
-
// Stop clicks inside the popup from triggering the outer link
|
|
1691
|
-
pop.addEventListener('click', function(e) {
|
|
1692
|
-
e.stopPropagation();
|
|
1693
|
-
if (!e.target.closest('a')) e.preventDefault();
|
|
1694
|
-
});
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
function hidePopup() {
|
|
1698
|
-
if (activePopup && activePopup.parentNode) activePopup.parentNode.removeChild(activePopup);
|
|
1699
|
-
activePopup = null;
|
|
1700
|
-
activeAnchor = null;
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
function fetchAndShow(anchor) {
|
|
1704
|
-
var url = previewUrl(anchor);
|
|
1705
|
-
var cached = cache[url];
|
|
1706
|
-
if (cached === undefined) {
|
|
1707
|
-
cache[url] = null; // in-flight
|
|
1708
|
-
showPopup(anchor, '<p style="opacity:0.5;margin:0">Loading\u2026</p>');
|
|
1709
|
-
fetch(url)
|
|
1710
|
-
.then(function(r) { return r.ok ? r.json() : null; })
|
|
1711
|
-
.then(function(data) {
|
|
1712
|
-
if (!data) { cache[url] = false; if (activeAnchor === anchor) hidePopup(); return; }
|
|
1713
|
-
var html = '<div class="link-preview-popup-title">' + escHtml(data.title) + '</div>' + data.html;
|
|
1714
|
-
cache[url] = html;
|
|
1715
|
-
if (activeAnchor === anchor) showPopup(anchor, html);
|
|
1716
|
-
})
|
|
1717
|
-
.catch(function() { cache[url] = false; if (activeAnchor === anchor) hidePopup(); });
|
|
1718
|
-
} else if (typeof cached === 'string') {
|
|
1719
|
-
showPopup(anchor, cached);
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
function fetchExternalAndShow(anchor) {
|
|
1724
|
-
var href = anchor.getAttribute('href');
|
|
1725
|
-
var key = 'ext:' + href;
|
|
1726
|
-
var cached = cache[key];
|
|
1727
|
-
if (cached === undefined) {
|
|
1728
|
-
cache[key] = null; // in-flight
|
|
1729
|
-
showPopup(anchor, '<p style="opacity:0.5;margin:0">Loading\u2026</p>');
|
|
1730
|
-
fetch('/fetch?url=' + encodeURIComponent(href))
|
|
1731
|
-
.then(function(r) { return r.ok ? r.json() : null; })
|
|
1732
|
-
.then(function(data) {
|
|
1733
|
-
if (!data || data.error) { cache[key] = false; if (activeAnchor === anchor) hidePopup(); return; }
|
|
1734
|
-
var html = '<div class="link-preview-popup-title">' + escHtml(data.title) + '</div>' + data.html;
|
|
1735
|
-
cache[key] = html;
|
|
1736
|
-
if (activeAnchor === anchor) showPopup(anchor, html);
|
|
1737
|
-
})
|
|
1738
|
-
.catch(function() { cache[key] = false; if (activeAnchor === anchor) hidePopup(); });
|
|
1739
|
-
} else if (typeof cached === 'string') {
|
|
1740
|
-
showPopup(anchor, cached);
|
|
1741
|
-
}
|
|
1742
|
-
}
|
|
1743
|
-
|
|
1744
|
-
content.querySelectorAll('a').forEach(function(a) {
|
|
1745
|
-
var localMd = isLocalMdLink(a);
|
|
1746
|
-
var external = isExternalLink(a);
|
|
1747
|
-
if (!localMd && !external) return;
|
|
1748
|
-
a.classList.add('link-tooltip-anchor');
|
|
1749
|
-
|
|
1669
|
+
// Hover — same popup as click, triggered after a short delay when no popup is active
|
|
1670
|
+
(function() {
|
|
1671
|
+
var content = document.querySelector('.md-content');
|
|
1672
|
+
if (!content) return;
|
|
1750
1673
|
var hoverTimer = null;
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1674
|
+
content.querySelectorAll('a').forEach(function(a) {
|
|
1675
|
+
var href = a.getAttribute('href');
|
|
1676
|
+
if (!href || isAnchorOnly(href)) return;
|
|
1677
|
+
if (!isLocalMd(href) && !isExternal(href)) return;
|
|
1678
|
+
a.addEventListener('mouseenter', function(e) {
|
|
1679
|
+
clearTimeout(hoverTimer);
|
|
1680
|
+
if (popup) return;
|
|
1681
|
+
var x = e.clientX, y = e.clientY;
|
|
1682
|
+
hoverTimer = setTimeout(function() {
|
|
1683
|
+
if (!popup) handleLink(a, x, y, false);
|
|
1684
|
+
}, 300);
|
|
1685
|
+
});
|
|
1686
|
+
a.addEventListener('mouseleave', function() {
|
|
1687
|
+
clearTimeout(hoverTimer);
|
|
1688
|
+
});
|
|
1761
1689
|
});
|
|
1762
|
-
});
|
|
1690
|
+
})();
|
|
1691
|
+
<% end %>
|
|
1763
1692
|
|
|
1764
|
-
document.addEventListener('click', function(e) {
|
|
1765
|
-
if (activePopup && !activePopup.contains(e.target) && activeAnchor && !activeAnchor.contains(e.target)) hidePopup();
|
|
1766
|
-
});
|
|
1767
1693
|
})();
|
|
1768
|
-
|
|
1694
|
+
|
|
1769
1695
|
|
|
1770
1696
|
</script>
|
|
1771
1697
|
</body>
|