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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 03b8c1c4665231a3fb1ead89abea54e0630131743216938a1c293945c942e710
4
- data.tar.gz: 004d43321fcda1b894c6481eed4b755f29a9fae14c6e0f6bbb5c1f9d82b6afd9
3
+ metadata.gz: cf955ffcbe5dfc1e1f5070b3fa36a6d2d15a092e7be6ca7f8ac5dcd4b863162a
4
+ data.tar.gz: 3ea514cfed258bc71a8a3e82f6ef883e1ed619fc63b46e9217eb4745c455fdd5
5
5
  SHA512:
6
- metadata.gz: 0d44c7b65617670e54d450d5d958294bd78914407cf925087f1547b551b5b91dd3b556820009aa197491bdd14bff9fc6e205a47bf6662fb5b5a495d8e5d8c71d
7
- data.tar.gz: 07f03bc67cc2b668c90c1c9467eb7eafe60f2c58c4030553adbec8af4309bdbf3943c9ffdd454cdde0283619f702c6a7ef75992ed679e84983557c5ed5fc3675
6
+ metadata.gz: 19f44c77aab4f8a5a2978c85f8fd900f6a0cc45d9399897a26acae80d5809ce57669716a1a5e488c276a4b53e0b2e5d5e3bc13d8b6463a5c8d9c76342eb39e4f
7
+ data.tar.gz: b8adb9e49b1def08d6d3f4061de5048d3cf14232d4ce0c3b9fde51f605f52fb9338a9b39ad49f569d9663fdf9e718e1396f37674e55c7c3f025ffa7a271d3548
@@ -1,3 +1,3 @@
1
1
  module MarkdownServer
2
- VERSION = "0.5.0"
2
+ VERSION = "0.5.1"
3
3
  end
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
- /* Right-click / long-press link context popup */
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
- // Right-click / long-press popup for all links
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 pendingTouch = null;
1393
- var longPressHandled = false;
1420
+ var historyStack = [];
1421
+ var currentPopupPos = { x: 0, y: 0 };
1394
1422
 
1395
1423
  function escHtml(s) {
1396
1424
  return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
@@ -1416,19 +1444,91 @@
1416
1444
  return '/preview/' + resolved.pathname.replace(/^\/browse\//, '');
1417
1445
  }
1418
1446
 
1419
- function showPopup(x, y, title, bodyHtml) {
1420
- hidePopup();
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
- '<span class="link-ctx-popup-title">' + escHtml(title) + '</span>' +
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
- // Position near pointer, clamped to viewport
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 titleEl = popup.querySelector('.link-ctx-popup-title');
1546
+ var titleTextEl = popup.querySelector('.link-ctx-popup-title span');
1454
1547
  if (body) body.innerHTML = bodyHtml;
1455
- if (title && titleEl) titleEl.textContent = 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
- // Right-click (desktop + most mobile browsers fire contextmenu on long-press)
1534
- document.addEventListener('contextmenu', function(e) {
1535
- if (popup && popup.contains(e.target)) { e.preventDefault(); return; }
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
- clearTimeout(longPressTimer);
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
- // Long-press fallback for touch devices that don't fire contextmenu
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('touchcancel', function() {
1582
- clearTimeout(longPressTimer);
1583
- pendingTouch = null;
1584
- longPressHandled = false;
1585
- }, { passive: true });
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
- // Link preview popup for local markdown links and external links
1598
- (function() {
1599
- var content = document.querySelector('.md-content');
1600
- if (!content) return;
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, '&amp;').replace(/</g, '&lt;')
1610
- .replace(/>/g, '&gt;').replace(/"/g, '&quot;');
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
- a.addEventListener('click', hidePopup);
1752
- a.addEventListener('mouseenter', function() {
1753
- hoverTimer = setTimeout(function() {
1754
- if (localMd) fetchAndShow(a);
1755
- else fetchExternalAndShow(a);
1756
- }, 300);
1757
- });
1758
- a.addEventListener('mouseleave', function() {
1759
- clearTimeout(hoverTimer);
1760
- hideTimer = setTimeout(hidePopup, 150);
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
- <% end %>
1694
+
1769
1695
 
1770
1696
  </script>
1771
1697
  </body>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: markdownr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Dunn