markdownr 0.4.9 → 0.5.0
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/app.rb +30 -8
- data/lib/markdown_server/version.rb +1 -1
- data/views/layout.erb +111 -110
- 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: 03b8c1c4665231a3fb1ead89abea54e0630131743216938a1c293945c942e710
|
|
4
|
+
data.tar.gz: 004d43321fcda1b894c6481eed4b755f29a9fae14c6e0f6bbb5c1f9d82b6afd9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0d44c7b65617670e54d450d5d958294bd78914407cf925087f1547b551b5b91dd3b556820009aa197491bdd14bff9fc6e205a47bf6662fb5b5a495d8e5d8c71d
|
|
7
|
+
data.tar.gz: 07f03bc67cc2b668c90c1c9467eb7eafe60f2c58c4030553adbec8af4309bdbf3943c9ffdd454cdde0283619f702c6a7ef75992ed679e84983557c5ed5fc3675
|
data/lib/markdown_server/app.rb
CHANGED
|
@@ -491,7 +491,7 @@ module MarkdownServer
|
|
|
491
491
|
} || ""
|
|
492
492
|
end
|
|
493
493
|
|
|
494
|
-
def page_html(raw)
|
|
494
|
+
def page_html(raw, base_url = nil)
|
|
495
495
|
w = raw.dup
|
|
496
496
|
# Remove inert elements and their entire contents
|
|
497
497
|
STRIP_FULL.each { |t| w.gsub!(/<#{t}[^>]*>.*?<\/#{t}>/im, " ") }
|
|
@@ -508,12 +508,30 @@ module MarkdownServer
|
|
|
508
508
|
w.match(/<body[^>]*>(.*?)<\/body>/im)&.[](1) ||
|
|
509
509
|
w
|
|
510
510
|
|
|
511
|
-
# Rewrite tags: keep allowed (strip attrs), block→newline, rest→empty
|
|
512
|
-
out = content.gsub(/<(\/?)(\w+)[^>]
|
|
513
|
-
slash, tag = $1, $2.downcase
|
|
514
|
-
if ALLOWED_HTML.include?(tag)
|
|
515
|
-
|
|
516
|
-
|
|
511
|
+
# Rewrite tags: keep allowed (strip attrs), preserve <a href>, block→newline, rest→empty
|
|
512
|
+
out = content.gsub(/<(\/?)(\w+)([^>]*)>/) do
|
|
513
|
+
slash, tag, attrs = $1, $2.downcase, $3
|
|
514
|
+
if ALLOWED_HTML.include?(tag)
|
|
515
|
+
"<#{slash}#{tag}>"
|
|
516
|
+
elsif tag == "a"
|
|
517
|
+
if slash.empty?
|
|
518
|
+
m = attrs.match(/href\s*=\s*["']([^"']*)["']/i)
|
|
519
|
+
if m && !m[1].match?(/\Ajavascript:/i)
|
|
520
|
+
href = m[1]
|
|
521
|
+
if base_url && href !~ /\Ahttps?:\/\//i && !href.start_with?("#")
|
|
522
|
+
href = (URI.join(base_url, href).to_s rescue href)
|
|
523
|
+
end
|
|
524
|
+
%(<a href="#{h(href)}" target="_blank" rel="noopener">)
|
|
525
|
+
else
|
|
526
|
+
""
|
|
527
|
+
end
|
|
528
|
+
else
|
|
529
|
+
"</a>"
|
|
530
|
+
end
|
|
531
|
+
elsif BLOCK_HTML.include?(tag)
|
|
532
|
+
"\n"
|
|
533
|
+
else
|
|
534
|
+
""
|
|
517
535
|
end
|
|
518
536
|
end
|
|
519
537
|
|
|
@@ -533,6 +551,9 @@ module MarkdownServer
|
|
|
533
551
|
.gsub(/<(\w+)>\s*<\/\1>/, "") # drop empty tags
|
|
534
552
|
.strip
|
|
535
553
|
|
|
554
|
+
# Strip all footer navigation after "Read full chapter" up to (but not including) copyright
|
|
555
|
+
out.gsub!(/(<a[^>]*>Read\s+full\s+chapter<\/a>)[\s\S]*?(?=©|Copyright\b)/i, "\\1\n")
|
|
556
|
+
|
|
536
557
|
out.length > 10_000 ? out[0, 10_000] : out
|
|
537
558
|
end
|
|
538
559
|
|
|
@@ -627,7 +648,8 @@ module MarkdownServer
|
|
|
627
648
|
html = fetch_external_page(url)
|
|
628
649
|
halt 502, '{"error":"fetch failed"}' unless html
|
|
629
650
|
|
|
630
|
-
|
|
651
|
+
title = page_title(html).sub(/ [-–] .*/, "").strip
|
|
652
|
+
JSON.dump({ title: title, html: page_html(html, url) })
|
|
631
653
|
end
|
|
632
654
|
|
|
633
655
|
get "/search/?*" do
|
data/views/layout.erb
CHANGED
|
@@ -726,10 +726,6 @@
|
|
|
726
726
|
.link-ctx-popup-close:hover { color: #2c2c2c; }
|
|
727
727
|
.link-ctx-popup-body {
|
|
728
728
|
padding: 0.75rem 1rem;
|
|
729
|
-
font-family: Georgia, "Times New Roman", serif;
|
|
730
|
-
font-size: 0.85rem;
|
|
731
|
-
line-height: 1.6;
|
|
732
|
-
color: #2c2c2c;
|
|
733
729
|
}
|
|
734
730
|
.link-ctx-popup-url {
|
|
735
731
|
font-family: "SF Mono", Menlo, Consolas, monospace;
|
|
@@ -741,49 +737,8 @@
|
|
|
741
737
|
border-radius: 4px;
|
|
742
738
|
margin-bottom: 0.5rem;
|
|
743
739
|
}
|
|
744
|
-
.link-ctx-popup-body h1, .link-ctx-popup-body h2, .link-ctx-popup-body h3,
|
|
745
|
-
.link-ctx-popup-body h4, .link-ctx-popup-body h5, .link-ctx-popup-body h6 {
|
|
746
|
-
margin: 0.7rem 0 0.3rem; color: #3a3a3a;
|
|
747
|
-
}
|
|
748
|
-
.link-ctx-popup-body h1 { font-size: 1.2rem; }
|
|
749
|
-
.link-ctx-popup-body h2 { font-size: 1.05rem; border-bottom: none; padding-bottom: 0; }
|
|
750
|
-
.link-ctx-popup-body h3 { font-size: 0.95rem; }
|
|
751
|
-
.link-ctx-popup-body p { margin: 0 0 0.5rem; }
|
|
752
|
-
.link-ctx-popup-body p:last-child { margin-bottom: 0; }
|
|
753
|
-
.link-ctx-popup-body a { color: #8b6914; text-decoration: none; border-bottom: 1px solid #d4b96a; }
|
|
754
|
-
.link-ctx-popup-body a.wiki-link { color: #6a8e3e; border-bottom-color: #6a8e3e; }
|
|
755
|
-
.link-ctx-popup-body code {
|
|
756
|
-
font-family: "SF Mono", Menlo, Consolas, monospace;
|
|
757
|
-
font-size: 0.82em;
|
|
758
|
-
background: #f0ece3;
|
|
759
|
-
padding: 0.1em 0.3em;
|
|
760
|
-
border-radius: 3px;
|
|
761
|
-
}
|
|
762
|
-
.link-ctx-popup-body pre {
|
|
763
|
-
background: #2d2d2d;
|
|
764
|
-
color: #f0f0f0;
|
|
765
|
-
padding: 0.6rem 0.8rem;
|
|
766
|
-
border-radius: 4px;
|
|
767
|
-
font-size: 0.78rem;
|
|
768
|
-
line-height: 1.4;
|
|
769
|
-
overflow-x: auto;
|
|
770
|
-
margin: 0.4rem 0;
|
|
771
|
-
}
|
|
772
|
-
.link-ctx-popup-body pre code { background: none; padding: 0; font-size: 1em; color: inherit; }
|
|
773
|
-
.link-ctx-popup-body ul, .link-ctx-popup-body ol { padding-left: 1.4rem; margin: 0.3rem 0; }
|
|
774
|
-
.link-ctx-popup-body li { margin-bottom: 0.2rem; }
|
|
775
|
-
.link-ctx-popup-body blockquote {
|
|
776
|
-
border-left: 3px solid #d4b96a;
|
|
777
|
-
margin: 0.5rem 0;
|
|
778
|
-
padding: 0.3rem 0.8rem;
|
|
779
|
-
color: #4a4a4a;
|
|
780
|
-
font-style: italic;
|
|
781
|
-
}
|
|
782
|
-
.link-ctx-popup-body table { border-collapse: collapse; font-size: 0.8rem; margin: 0.5rem 0; }
|
|
783
|
-
.link-ctx-popup-body th, .link-ctx-popup-body td { border: 1px solid #ddd; padding: 0.3rem 0.5rem; }
|
|
784
|
-
.link-ctx-popup-body th { background: #f5f0e4; }
|
|
785
740
|
|
|
786
|
-
/* Link preview popup */
|
|
741
|
+
/* Link preview popup (hover) */
|
|
787
742
|
.link-tooltip-anchor { position: relative; }
|
|
788
743
|
.link-preview-popup {
|
|
789
744
|
position: absolute;
|
|
@@ -799,10 +754,6 @@
|
|
|
799
754
|
border-radius: 6px;
|
|
800
755
|
box-shadow: 0 4px 20px rgba(0,0,0,0.18);
|
|
801
756
|
padding: 0.8rem 1rem;
|
|
802
|
-
font-family: Georgia, "Times New Roman", serif;
|
|
803
|
-
font-size: 0.85rem;
|
|
804
|
-
line-height: 1.6;
|
|
805
|
-
color: #2c2c2c;
|
|
806
757
|
cursor: auto;
|
|
807
758
|
-webkit-overflow-scrolling: touch;
|
|
808
759
|
}
|
|
@@ -814,26 +765,36 @@
|
|
|
814
765
|
border-bottom: 1px solid #e0d8c8;
|
|
815
766
|
color: #3a3a3a;
|
|
816
767
|
}
|
|
768
|
+
|
|
769
|
+
/* Shared popup content styles */
|
|
770
|
+
.link-ctx-popup-body,
|
|
771
|
+
.link-preview-popup {
|
|
772
|
+
font-family: Georgia, "Times New Roman", serif;
|
|
773
|
+
font-size: 0.85rem;
|
|
774
|
+
line-height: 1.6;
|
|
775
|
+
color: #2c2c2c;
|
|
776
|
+
}
|
|
777
|
+
.link-ctx-popup-body h1, .link-ctx-popup-body h2, .link-ctx-popup-body h3,
|
|
778
|
+
.link-ctx-popup-body h4, .link-ctx-popup-body h5, .link-ctx-popup-body h6,
|
|
817
779
|
.link-preview-popup h1, .link-preview-popup h2, .link-preview-popup h3,
|
|
818
780
|
.link-preview-popup h4, .link-preview-popup h5, .link-preview-popup h6 {
|
|
819
|
-
margin: 0.7rem 0 0.3rem;
|
|
820
|
-
color: #3a3a3a;
|
|
781
|
+
margin: 0.7rem 0 0.3rem; color: #3a3a3a;
|
|
821
782
|
}
|
|
822
|
-
.link-preview-popup h1 { font-size: 1.2rem; }
|
|
823
|
-
.link-preview-popup h2 { font-size: 1.05rem; border-bottom: none; padding-bottom: 0; }
|
|
824
|
-
.link-preview-popup h3 { font-size: 0.95rem; }
|
|
825
|
-
.link-preview-popup p { margin: 0 0 0.5rem; }
|
|
826
|
-
.link-preview-popup p:last-child { margin-bottom: 0; }
|
|
827
|
-
.link-preview-popup a { color: #8b6914; text-decoration: none; border-bottom: 1px solid #d4b96a; }
|
|
828
|
-
.link-preview-popup a.wiki-link { color: #6a8e3e; border-bottom-color: #6a8e3e; }
|
|
829
|
-
.link-preview-popup code {
|
|
783
|
+
.link-ctx-popup-body h1, .link-preview-popup h1 { font-size: 1.2rem; }
|
|
784
|
+
.link-ctx-popup-body h2, .link-preview-popup h2 { font-size: 1.05rem; border-bottom: none; padding-bottom: 0; }
|
|
785
|
+
.link-ctx-popup-body h3, .link-preview-popup h3 { font-size: 0.95rem; }
|
|
786
|
+
.link-ctx-popup-body p, .link-preview-popup p { margin: 0 0 0.5rem; }
|
|
787
|
+
.link-ctx-popup-body p:last-child, .link-preview-popup p:last-child { margin-bottom: 0; }
|
|
788
|
+
.link-ctx-popup-body a, .link-preview-popup a { color: #8b6914; text-decoration: none; border-bottom: 1px solid #d4b96a; }
|
|
789
|
+
.link-ctx-popup-body a.wiki-link, .link-preview-popup a.wiki-link { color: #6a8e3e; border-bottom-color: #6a8e3e; }
|
|
790
|
+
.link-ctx-popup-body code, .link-preview-popup code {
|
|
830
791
|
font-family: "SF Mono", Menlo, Consolas, monospace;
|
|
831
792
|
font-size: 0.82em;
|
|
832
793
|
background: #f0ece3;
|
|
833
794
|
padding: 0.1em 0.3em;
|
|
834
795
|
border-radius: 3px;
|
|
835
796
|
}
|
|
836
|
-
.link-preview-popup pre {
|
|
797
|
+
.link-ctx-popup-body pre, .link-preview-popup pre {
|
|
837
798
|
background: #2d2d2d;
|
|
838
799
|
color: #f0f0f0;
|
|
839
800
|
padding: 0.6rem 0.8rem;
|
|
@@ -843,26 +804,21 @@
|
|
|
843
804
|
overflow-x: auto;
|
|
844
805
|
margin: 0.4rem 0;
|
|
845
806
|
}
|
|
846
|
-
.link-preview-popup pre code { background: none; padding: 0; font-size: 1em; }
|
|
807
|
+
.link-ctx-popup-body pre code, .link-preview-popup pre code { background: none; padding: 0; font-size: 1em; color: inherit; }
|
|
808
|
+
.link-ctx-popup-body ul, .link-ctx-popup-body ol,
|
|
847
809
|
.link-preview-popup ul, .link-preview-popup ol { padding-left: 1.4rem; margin: 0.3rem 0; }
|
|
848
|
-
.link-preview-popup li { margin-bottom: 0.2rem; }
|
|
849
|
-
.link-preview-popup blockquote {
|
|
810
|
+
.link-ctx-popup-body li, .link-preview-popup li { margin-bottom: 0.2rem; }
|
|
811
|
+
.link-ctx-popup-body blockquote, .link-preview-popup blockquote {
|
|
850
812
|
border-left: 3px solid #d4b96a;
|
|
851
813
|
margin: 0.5rem 0;
|
|
852
814
|
padding: 0.3rem 0.8rem;
|
|
853
815
|
color: #4a4a4a;
|
|
854
816
|
font-style: italic;
|
|
855
817
|
}
|
|
856
|
-
.link-preview-popup table {
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
}
|
|
861
|
-
.link-preview-popup th, .link-preview-popup td {
|
|
862
|
-
border: 1px solid #ddd;
|
|
863
|
-
padding: 0.3rem 0.5rem;
|
|
864
|
-
}
|
|
865
|
-
.link-preview-popup th { background: #f5f0e4; }
|
|
818
|
+
.link-ctx-popup-body table, .link-preview-popup table { border-collapse: collapse; font-size: 0.8rem; margin: 0.5rem 0; }
|
|
819
|
+
.link-ctx-popup-body th, .link-ctx-popup-body td,
|
|
820
|
+
.link-preview-popup th, .link-preview-popup td { border: 1px solid #ddd; padding: 0.3rem 0.5rem; }
|
|
821
|
+
.link-ctx-popup-body th, .link-preview-popup th { background: #f5f0e4; }
|
|
866
822
|
|
|
867
823
|
/* Footnote tooltips */
|
|
868
824
|
.footnote-tooltip {
|
|
@@ -1634,10 +1590,11 @@
|
|
|
1634
1590
|
document.addEventListener('keydown', function(e) {
|
|
1635
1591
|
if (e.key === 'Escape' && popup) hidePopup();
|
|
1636
1592
|
});
|
|
1593
|
+
|
|
1637
1594
|
})();
|
|
1638
1595
|
|
|
1639
1596
|
<% if settings.link_tooltips %>
|
|
1640
|
-
// Link preview popup for local markdown links
|
|
1597
|
+
// Link preview popup for local markdown links and external links
|
|
1641
1598
|
(function() {
|
|
1642
1599
|
var content = document.querySelector('.md-content');
|
|
1643
1600
|
if (!content) return;
|
|
@@ -1662,48 +1619,79 @@
|
|
|
1662
1619
|
return /\.md$/i.test(path);
|
|
1663
1620
|
}
|
|
1664
1621
|
|
|
1622
|
+
function isExternalLink(a) {
|
|
1623
|
+
var href = a.getAttribute('href');
|
|
1624
|
+
return href && /^https?:\/\//i.test(href);
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1665
1627
|
function previewUrl(a) {
|
|
1666
1628
|
var resolved = new URL(a.getAttribute('href'), location.href);
|
|
1667
1629
|
return '/preview/' + resolved.pathname.replace(/^\/browse\//, '');
|
|
1668
1630
|
}
|
|
1669
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
|
+
|
|
1670
1643
|
function showPopup(anchor, html) {
|
|
1671
1644
|
hidePopup();
|
|
1672
1645
|
var pop = document.createElement('div');
|
|
1673
1646
|
pop.className = 'link-preview-popup';
|
|
1674
1647
|
pop.innerHTML = html;
|
|
1675
|
-
anchor.appendChild(pop);
|
|
1676
1648
|
activePopup = pop;
|
|
1677
1649
|
activeAnchor = anchor;
|
|
1678
1650
|
|
|
1679
|
-
// Clamp right edge to viewport
|
|
1680
1651
|
var vw = Math.min(window.innerWidth, document.documentElement.clientWidth);
|
|
1681
|
-
var
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
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
|
+
}
|
|
1690
1684
|
}
|
|
1691
1685
|
|
|
1692
1686
|
// Keep popup open while hovering over it
|
|
1693
1687
|
pop.addEventListener('mouseenter', function() { clearTimeout(hideTimer); });
|
|
1694
1688
|
pop.addEventListener('mouseleave', function() { hideTimer = setTimeout(hidePopup, 10); });
|
|
1695
1689
|
|
|
1696
|
-
// Stop clicks
|
|
1690
|
+
// Stop clicks inside the popup from triggering the outer link
|
|
1697
1691
|
pop.addEventListener('click', function(e) {
|
|
1698
1692
|
e.stopPropagation();
|
|
1699
1693
|
if (!e.target.closest('a')) e.preventDefault();
|
|
1700
1694
|
});
|
|
1701
|
-
pop.addEventListener('touchstart', function(e) { e.stopPropagation(); }, { passive: true });
|
|
1702
|
-
pop.addEventListener('touchmove', function(e) { e.stopPropagation(); }, { passive: true });
|
|
1703
|
-
pop.addEventListener('touchend', function(e) {
|
|
1704
|
-
e.stopPropagation();
|
|
1705
|
-
if (!e.target.closest('a')) e.preventDefault();
|
|
1706
|
-
});
|
|
1707
1695
|
}
|
|
1708
1696
|
|
|
1709
1697
|
function hidePopup() {
|
|
@@ -1732,36 +1720,49 @@
|
|
|
1732
1720
|
}
|
|
1733
1721
|
}
|
|
1734
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
|
+
|
|
1735
1744
|
content.querySelectorAll('a').forEach(function(a) {
|
|
1736
|
-
|
|
1745
|
+
var localMd = isLocalMdLink(a);
|
|
1746
|
+
var external = isExternalLink(a);
|
|
1747
|
+
if (!localMd && !external) return;
|
|
1737
1748
|
a.classList.add('link-tooltip-anchor');
|
|
1738
1749
|
|
|
1739
1750
|
var hoverTimer = null;
|
|
1740
1751
|
a.addEventListener('click', hidePopup);
|
|
1741
1752
|
a.addEventListener('mouseenter', function() {
|
|
1742
|
-
hoverTimer = setTimeout(function() {
|
|
1753
|
+
hoverTimer = setTimeout(function() {
|
|
1754
|
+
if (localMd) fetchAndShow(a);
|
|
1755
|
+
else fetchExternalAndShow(a);
|
|
1756
|
+
}, 300);
|
|
1743
1757
|
});
|
|
1744
1758
|
a.addEventListener('mouseleave', function() {
|
|
1745
1759
|
clearTimeout(hoverTimer);
|
|
1746
|
-
hideTimer = setTimeout(hidePopup,
|
|
1747
|
-
});
|
|
1748
|
-
|
|
1749
|
-
var touchMoved = false;
|
|
1750
|
-
a.addEventListener('touchstart', function() { touchMoved = false; }, { passive: true });
|
|
1751
|
-
a.addEventListener('touchmove', function() { touchMoved = true; }, { passive: true });
|
|
1752
|
-
a.addEventListener('touchend', function(e) {
|
|
1753
|
-
if (touchMoved) return;
|
|
1754
|
-
if (activeAnchor === a && activePopup) return; // second tap → navigate
|
|
1755
|
-
e.preventDefault();
|
|
1756
|
-
document.querySelectorAll('.link-preview-popup').forEach(function(p) {
|
|
1757
|
-
if (p.parentNode) p.parentNode.removeChild(p);
|
|
1758
|
-
});
|
|
1759
|
-
fetchAndShow(a);
|
|
1760
|
+
hideTimer = setTimeout(hidePopup, 150);
|
|
1760
1761
|
});
|
|
1761
1762
|
});
|
|
1762
1763
|
|
|
1763
1764
|
document.addEventListener('click', function(e) {
|
|
1764
|
-
if (activePopup && activeAnchor && !activeAnchor.contains(e.target)) hidePopup();
|
|
1765
|
+
if (activePopup && !activePopup.contains(e.target) && activeAnchor && !activeAnchor.contains(e.target)) hidePopup();
|
|
1765
1766
|
});
|
|
1766
1767
|
})();
|
|
1767
1768
|
<% end %>
|