markdownr 0.6.2 → 0.6.4
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/bin/markdownr +9 -1
- data/lib/markdown_server/app.rb +64 -0
- data/lib/markdown_server/plugins/bible_citations/citations.rb +17 -0
- data/lib/markdown_server/plugins/bible_citations/plugin.rb +13 -2
- data/lib/markdown_server/version.rb +1 -1
- data/views/layout.erb +49 -13
- data/views/popup_assets.erb +72 -10
- 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: 845f5eba9de7543ef7e3c386e54f127d8ecfaf48e368d5fb11de54ca5d96cc6c
|
|
4
|
+
data.tar.gz: 9b00fb58e4c3a4e9e3b5c8236f30b96577f7e8bd4d18a17d8576b6382d568380
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 619d4b659c762122321ee3215e06b996c111d88f36bcc83a57b7613f2c9d241a9dea383bb2fbd86c9afa3a579085c7ae7b42519f597e795cb327f23426a5de7d
|
|
7
|
+
data.tar.gz: cf8bc3acf215f5fae8fbbafa137a231e8b37a1db8e73691f95cdc4f1f4acdd9631b19200cf4483b3dcad8df4c95d1a5ee6487cc34e7fb9de16c5c7d0d516b7d7
|
data/bin/markdownr
CHANGED
|
@@ -49,7 +49,15 @@ OptionParser.new do |opts|
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
opts.on("--no-bible-citations", "Disable Bible verse citation auto-linking") do
|
|
52
|
-
options[:plugin_overrides] =
|
|
52
|
+
options[:plugin_overrides] = (options[:plugin_overrides] || {}).merge(
|
|
53
|
+
"bible_citations" => (options.dig(:plugin_overrides, "bible_citations") || {}).merge("enabled" => false)
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
opts.on("--biblegateway", "Link Bible citations to BibleGateway instead of scrip.risensavior.com") do
|
|
58
|
+
options[:plugin_overrides] = (options[:plugin_overrides] || {}).merge(
|
|
59
|
+
"bible_citations" => (options.dig(:plugin_overrides, "bible_citations") || {}).merge("link_target" => "biblegateway")
|
|
60
|
+
)
|
|
53
61
|
end
|
|
54
62
|
|
|
55
63
|
opts.on("-v", "--version", "Show version") do
|
data/lib/markdown_server/app.rb
CHANGED
|
@@ -748,6 +748,67 @@ module MarkdownServer
|
|
|
748
748
|
info_html + infl_html + usage_html + conc_html
|
|
749
749
|
end
|
|
750
750
|
|
|
751
|
+
def scrip_html(html)
|
|
752
|
+
# Extract the passage content block
|
|
753
|
+
content = html[/<div\s+class="passage-text[^"]*"[^>]*>([\s\S]*?)<\/div>\s*(?:<nav|$)/im, 1] || ""
|
|
754
|
+
return page_html(html) if content.empty?
|
|
755
|
+
|
|
756
|
+
# Add data-verse attributes to verse spans for popup scrolling.
|
|
757
|
+
# Verse numbers live in <sup class="verse-number">N </sup> inside verse spans.
|
|
758
|
+
content = content.gsub(/<sup class="verse-number">(\d+)[^<]*<\/sup>/) do
|
|
759
|
+
%(<sup class="verse-number" data-verse="#{$1}">#{$1} </sup>)
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
# Inline the essential substitution CSS (show replacement mode by default)
|
|
763
|
+
css = <<~CSS
|
|
764
|
+
<style>
|
|
765
|
+
.subst { cursor: help; border-radius: 2px; background: #f0ebff; position: relative; }
|
|
766
|
+
.subst:hover { background: #e0d4ff; outline: 1px solid #a78bfa; }
|
|
767
|
+
.subst-t, .subst-o, .subst-x, .subst-s { display: none; }
|
|
768
|
+
.subst-r { display: inline; color: #7c3aed; font-style: italic; }
|
|
769
|
+
.section-heading { font-size: 1rem; font-weight: bold; margin: 1.5rem 0 0.25rem; color: #444; }
|
|
770
|
+
.verse-number { font-size: 0.65em; font-weight: bold; color: #888; vertical-align: super; margin-right: 0.1em; line-height: 0; }
|
|
771
|
+
.chapter-number { font-size: 1.5em; font-weight: bold; vertical-align: 0.1em; margin-right: 0.15em; }
|
|
772
|
+
.footnotes { border-top: 1px solid #ddd; margin-top: 2rem; padding-top: 0.75rem; font-size: 0.85rem; color: #555; }
|
|
773
|
+
.footnotes ol { padding-left: 1.5rem; margin: 0.5rem 0 0; list-style-type: lower-alpha; }
|
|
774
|
+
.words-of-jesus { color: #8b0000; }
|
|
775
|
+
.poetry-stanza { margin: 0 0 0 3rem; }
|
|
776
|
+
.poetry-stanza.stanza-break { margin-top: 0.75rem; }
|
|
777
|
+
.poetry-line { margin: 0; padding: 0; line-height: 1.5; }
|
|
778
|
+
.poetry-line.indent-1 { padding-left: 2em; }
|
|
779
|
+
.poetry-line.indent-2 { padding-left: 4em; }
|
|
780
|
+
.subst-tip { display: none; position: absolute; bottom: calc(100% + 4px); left: 50%; transform: translateX(-50%); background: #1e293b; color: #f8fafc; font-size: 0.78rem; line-height: 1.5; padding: 0.4rem 0.6rem; border-radius: 5px; white-space: nowrap; z-index: 100; pointer-events: none; font-style: normal; }
|
|
781
|
+
.subst-tip::after { content: ""; position: absolute; top: 100%; left: 50%; transform: translateX(-50%); border: 5px solid transparent; border-top-color: #1e293b; }
|
|
782
|
+
.subst:hover .subst-tip { display: block; }
|
|
783
|
+
</style>
|
|
784
|
+
CSS
|
|
785
|
+
|
|
786
|
+
# Build tooltips for substitution spans via JS
|
|
787
|
+
js = <<~JS
|
|
788
|
+
<script>
|
|
789
|
+
document.querySelectorAll('.subst').forEach(function(el) {
|
|
790
|
+
var t = el.querySelector('.subst-t'), r = el.querySelector('.subst-r'),
|
|
791
|
+
o = el.querySelector('.subst-o'), x = el.querySelector('.subst-x'),
|
|
792
|
+
s = el.querySelector('.subst-s');
|
|
793
|
+
var lines = [];
|
|
794
|
+
if (t) lines.push(t.textContent);
|
|
795
|
+
if (r) lines.push(r.textContent);
|
|
796
|
+
if (o) lines.push(o.textContent);
|
|
797
|
+
if (x) lines.push(x.textContent);
|
|
798
|
+
if (s) lines.push(s.textContent);
|
|
799
|
+
if (lines.length > 1) {
|
|
800
|
+
var tip = document.createElement('span');
|
|
801
|
+
tip.className = 'subst-tip';
|
|
802
|
+
tip.innerHTML = lines.join('<br>');
|
|
803
|
+
el.appendChild(tip);
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
</script>
|
|
807
|
+
JS
|
|
808
|
+
|
|
809
|
+
css + '<div style="font-family:Georgia,serif;line-height:1.7">' + content + "</div>" + js
|
|
810
|
+
end
|
|
811
|
+
|
|
751
812
|
def inline_directory_html(dir_path, relative_dir)
|
|
752
813
|
entries = Dir.entries(dir_path).reject { |e| e.start_with?(".") || EXCLUDED.include?(e) }
|
|
753
814
|
items = entries.map do |name|
|
|
@@ -1074,6 +1135,9 @@ module MarkdownServer
|
|
|
1074
1135
|
title = raw.match(/^([GH]\d+ - \w+)/i)&.[](1)&.sub(" - ", " – ") ||
|
|
1075
1136
|
raw.sub(/ [-–] .*/, "").strip
|
|
1076
1137
|
JSON.dump({ title: title, html: blueletterbible_html(html, url) })
|
|
1138
|
+
elsif url.match?(/scrip\.risensavior\.com/i)
|
|
1139
|
+
title = page_title(html).sub(/ [-–] .*/, "").strip
|
|
1140
|
+
JSON.dump({ title: title, html: scrip_html(html) })
|
|
1077
1141
|
else
|
|
1078
1142
|
title = page_title(html).sub(/ [-–] .*/, "").strip
|
|
1079
1143
|
JSON.dump({ title: title, html: page_html(html, url) })
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "cgi"
|
|
4
|
+
require "uri"
|
|
4
5
|
|
|
5
6
|
module MarkdownServer
|
|
6
7
|
module Plugins
|
|
@@ -88,6 +89,22 @@ module MarkdownServer
|
|
|
88
89
|
"https://www.biblegateway.com/passage/?search=#{CGI.escape("#{canonical} #{verse}")}&version=#{version}"
|
|
89
90
|
end
|
|
90
91
|
|
|
92
|
+
def self.scrip_url(canonical, verse, version: DEFAULT_VERSION)
|
|
93
|
+
# Parse chapter and optional starting verse from the verse string
|
|
94
|
+
# verse is e.g. "1:1", "1:1-5", "1", "1-2", "1:1,3,5"
|
|
95
|
+
if verse =~ /\A(\d+)(?::(\d+))?/
|
|
96
|
+
chapter = format("%03d", $1.to_i)
|
|
97
|
+
start_verse = $2
|
|
98
|
+
else
|
|
99
|
+
return biblegateway_url(canonical, verse, version: version)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
encode = ->(s) { URI.encode_www_form_component(s).gsub("+", "%20") }
|
|
103
|
+
url = "https://scrip.risensavior.com/browse/biblegateway/etl/20.%20substitutions/#{encode[version]}/#{encode[canonical]}/#{chapter}.html"
|
|
104
|
+
url += "#v#{start_verse}" if start_verse
|
|
105
|
+
url
|
|
106
|
+
end
|
|
107
|
+
|
|
91
108
|
def self.link_citations(text)
|
|
92
109
|
text.gsub(BIBLE_CITATION_RE) do |m|
|
|
93
110
|
next m if $1 || $2 || $3
|
|
@@ -13,20 +13,31 @@ module MarkdownServer
|
|
|
13
13
|
|
|
14
14
|
def setup(config)
|
|
15
15
|
@version = config.fetch("version", Citations::DEFAULT_VERSION)
|
|
16
|
+
@link_target = config.fetch("link_target", "scrip")
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def transform_markdown(text)
|
|
19
20
|
Citations.link_citations(text) do |canonical, verse, citation|
|
|
20
|
-
"[#{citation}](#{
|
|
21
|
+
"[#{citation}](#{citation_url(canonical, verse)})"
|
|
21
22
|
end
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
def transform_html(html)
|
|
25
26
|
Citations.link_citations_html(html) do |canonical, verse, citation|
|
|
26
|
-
url =
|
|
27
|
+
url = citation_url(canonical, verse)
|
|
27
28
|
%(<a href="#{CGI.escapeHTML(url)}">#{CGI.escapeHTML(citation)}</a>)
|
|
28
29
|
end
|
|
29
30
|
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def citation_url(canonical, verse)
|
|
35
|
+
if @link_target == "biblegateway"
|
|
36
|
+
Citations.biblegateway_url(canonical, verse, version: @version)
|
|
37
|
+
else
|
|
38
|
+
Citations.scrip_url(canonical, verse, version: @version)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
30
41
|
end
|
|
31
42
|
end
|
|
32
43
|
end
|
data/views/layout.erb
CHANGED
|
@@ -316,13 +316,15 @@
|
|
|
316
316
|
.md-content tr:nth-child(even) { background: #fdfcf9; }
|
|
317
317
|
|
|
318
318
|
/* Bible Gateway citation links */
|
|
319
|
-
a[href*="biblegateway.com"]
|
|
319
|
+
a[href*="biblegateway.com"],
|
|
320
|
+
a[href*="scrip.risensavior.com"] {
|
|
320
321
|
color: #7b3fa0;
|
|
321
322
|
font-weight: bold;
|
|
322
323
|
text-decoration: none;
|
|
323
324
|
border-bottom: 1px solid #b07fd4;
|
|
324
325
|
}
|
|
325
|
-
a[href*="biblegateway.com"]:hover
|
|
326
|
+
a[href*="biblegateway.com"]:hover,
|
|
327
|
+
a[href*="scrip.risensavior.com"]:hover { color: #5a2c78; border-bottom-color: #5a2c78; }
|
|
326
328
|
|
|
327
329
|
/* Wiki links */
|
|
328
330
|
a.wiki-link {
|
|
@@ -881,6 +883,12 @@
|
|
|
881
883
|
margin-bottom: 0.5rem;
|
|
882
884
|
}
|
|
883
885
|
|
|
886
|
+
/* Verse range highlighting */
|
|
887
|
+
.verse-selected {
|
|
888
|
+
background: #e5eefb;
|
|
889
|
+
border-radius: 2px;
|
|
890
|
+
}
|
|
891
|
+
|
|
884
892
|
/* Mermaid diagrams */
|
|
885
893
|
.mermaid { text-align: center; margin: 1.2rem 0; overflow-x: auto; }
|
|
886
894
|
.mermaid svg { max-width: 100%; height: auto; }
|
|
@@ -1780,9 +1788,7 @@
|
|
|
1780
1788
|
handleLink(anchor, savedPos.x, savedPos.y, true);
|
|
1781
1789
|
}, { passive: false });
|
|
1782
1790
|
|
|
1783
|
-
|
|
1784
|
-
popup.addEventListener(ev, function(e) { e.stopPropagation(); }, { passive: true });
|
|
1785
|
-
});
|
|
1791
|
+
popup.addEventListener('touchstart', function(e) { e.stopPropagation(); }, { passive: true });
|
|
1786
1792
|
}
|
|
1787
1793
|
|
|
1788
1794
|
function pinPopup(el) {
|
|
@@ -2017,20 +2023,48 @@
|
|
|
2017
2023
|
historyStack = [];
|
|
2018
2024
|
}
|
|
2019
2025
|
|
|
2026
|
+
function findVerseSpans(root, verseNum) {
|
|
2027
|
+
var span = root.querySelector('[data-verse="' + verseNum + '"]');
|
|
2028
|
+
if (span) return [span];
|
|
2029
|
+
var spans = root.querySelectorAll('[data-verse$=":' + verseNum + '"]');
|
|
2030
|
+
return spans.length ? Array.prototype.slice.call(spans) : [];
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2020
2033
|
function applyPopupAnchor(hash) {
|
|
2021
2034
|
if (!hash || !popup) return;
|
|
2022
2035
|
var id = hash.replace(/^#/, '');
|
|
2023
2036
|
try {
|
|
2024
|
-
var
|
|
2037
|
+
var body = popup.querySelector('.link-ctx-popup-body');
|
|
2038
|
+
if (!body) return;
|
|
2039
|
+
|
|
2040
|
+
// Verse range: #vN or #vN-M
|
|
2041
|
+
var vm = id.match(/^v(\d+)(?:-(\d+))?$/);
|
|
2042
|
+
if (vm) {
|
|
2043
|
+
var start = parseInt(vm[1], 10);
|
|
2044
|
+
var end = vm[2] ? parseInt(vm[2], 10) : start;
|
|
2045
|
+
var first = null;
|
|
2046
|
+
for (var v = start; v <= end; v++) {
|
|
2047
|
+
var spans = findVerseSpans(body, v);
|
|
2048
|
+
for (var i = 0; i < spans.length; i++) {
|
|
2049
|
+
spans[i].classList.add('verse-selected');
|
|
2050
|
+
if (!first) first = spans[i];
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
if (first) {
|
|
2054
|
+
var bodyTop = body.getBoundingClientRect().top;
|
|
2055
|
+
var targetTop = first.getBoundingClientRect().top;
|
|
2056
|
+
body.scrollTop += targetTop - bodyTop - 8;
|
|
2057
|
+
}
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
// Standard anchor by ID
|
|
2062
|
+
var eid = id.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
2063
|
+
var target = popup.querySelector('[id="' + eid + '"]');
|
|
2025
2064
|
if (!target) return;
|
|
2026
|
-
|
|
2027
|
-
// accounting for the sticky header so the heading lands just below it
|
|
2028
|
-
var header = popup.querySelector('.link-ctx-popup-header');
|
|
2029
|
-
var headerHeight = header ? header.offsetHeight : 0;
|
|
2065
|
+
var bodyTop = body.getBoundingClientRect().top;
|
|
2030
2066
|
var targetTop = target.getBoundingClientRect().top;
|
|
2031
|
-
|
|
2032
|
-
popup.scrollTop += targetTop - popupTop - headerHeight - 8;
|
|
2033
|
-
// Append section heading text to the popup title
|
|
2067
|
+
body.scrollTop += targetTop - bodyTop - 8;
|
|
2034
2068
|
if (/^H[1-6]$/.test(target.tagName)) {
|
|
2035
2069
|
var titleEl = popup.querySelector('.link-ctx-popup-title span');
|
|
2036
2070
|
if (titleEl) titleEl.textContent = titleEl.textContent + ' \u203a ' + target.textContent;
|
|
@@ -2081,6 +2115,7 @@
|
|
|
2081
2115
|
var extCached = cache[extKey];
|
|
2082
2116
|
if (extCached && typeof extCached === 'object') {
|
|
2083
2117
|
showPopup(x, y, extCached.title || label, extCached.html, href, linkRect);
|
|
2118
|
+
applyPopupAnchor(linkHash);
|
|
2084
2119
|
} else if (extCached === false) {
|
|
2085
2120
|
showPopup(x, y, label,
|
|
2086
2121
|
'<div class="link-ctx-popup-url">' + escHtml(href) + '</div>' +
|
|
@@ -2101,6 +2136,7 @@
|
|
|
2101
2136
|
}
|
|
2102
2137
|
cache[extKey] = { title: data.title, html: data.html };
|
|
2103
2138
|
updatePopup(data.html, data.title || label);
|
|
2139
|
+
applyPopupAnchor(linkHash);
|
|
2104
2140
|
})
|
|
2105
2141
|
.catch(function() {
|
|
2106
2142
|
cache[extKey] = false;
|
data/views/popup_assets.erb
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
<style>
|
|
2
2
|
/* Bible Gateway citation links */
|
|
3
|
-
a[href*="biblegateway.com"]
|
|
3
|
+
a[href*="biblegateway.com"],
|
|
4
|
+
a[href*="scrip.risensavior.com"] {
|
|
4
5
|
color: #7b3fa0;
|
|
5
6
|
font-weight: bold;
|
|
6
7
|
text-decoration: none;
|
|
7
8
|
border-bottom: 1px solid #b07fd4;
|
|
8
9
|
}
|
|
9
|
-
a[href*="biblegateway.com"]:hover
|
|
10
|
+
a[href*="biblegateway.com"]:hover,
|
|
11
|
+
a[href*="scrip.risensavior.com"]:hover { color: #5a2c78; border-bottom-color: #5a2c78; }
|
|
10
12
|
|
|
11
13
|
/* Left-click / tap link context popup */
|
|
12
14
|
.link-ctx-popup {
|
|
@@ -140,6 +142,12 @@
|
|
|
140
142
|
margin-bottom: 0.5rem;
|
|
141
143
|
}
|
|
142
144
|
|
|
145
|
+
/* Verse range highlighting */
|
|
146
|
+
.verse-selected {
|
|
147
|
+
background: #e5eefb;
|
|
148
|
+
border-radius: 2px;
|
|
149
|
+
}
|
|
150
|
+
|
|
143
151
|
/* Shared popup content styles */
|
|
144
152
|
.link-ctx-popup-body {
|
|
145
153
|
font-family: Georgia, "Times New Roman", serif;
|
|
@@ -362,9 +370,7 @@
|
|
|
362
370
|
handleLink(anchor, savedPos.x, savedPos.y, true);
|
|
363
371
|
}, { passive: false });
|
|
364
372
|
|
|
365
|
-
|
|
366
|
-
popup.addEventListener(ev, function(e) { e.stopPropagation(); }, { passive: true });
|
|
367
|
-
});
|
|
373
|
+
popup.addEventListener('touchstart', function(e) { e.stopPropagation(); }, { passive: true });
|
|
368
374
|
}
|
|
369
375
|
|
|
370
376
|
function pinPopup(el) {
|
|
@@ -589,17 +595,48 @@
|
|
|
589
595
|
historyStack = [];
|
|
590
596
|
}
|
|
591
597
|
|
|
598
|
+
function findVerseSpans(root, verseNum) {
|
|
599
|
+
var span = root.querySelector('[data-verse="' + verseNum + '"]');
|
|
600
|
+
if (span) return [span];
|
|
601
|
+
var spans = root.querySelectorAll('[data-verse$=":' + verseNum + '"]');
|
|
602
|
+
return spans.length ? Array.prototype.slice.call(spans) : [];
|
|
603
|
+
}
|
|
604
|
+
|
|
592
605
|
function applyPopupAnchor(hash) {
|
|
593
606
|
if (!hash || !popup) return;
|
|
594
607
|
var id = hash.replace(/^#/, '');
|
|
595
608
|
try {
|
|
596
|
-
var
|
|
609
|
+
var body = popup.querySelector('.link-ctx-popup-body');
|
|
610
|
+
if (!body) return;
|
|
611
|
+
|
|
612
|
+
// Verse range: #vN or #vN-M
|
|
613
|
+
var vm = id.match(/^v(\d+)(?:-(\d+))?$/);
|
|
614
|
+
if (vm) {
|
|
615
|
+
var start = parseInt(vm[1], 10);
|
|
616
|
+
var end = vm[2] ? parseInt(vm[2], 10) : start;
|
|
617
|
+
var first = null;
|
|
618
|
+
for (var v = start; v <= end; v++) {
|
|
619
|
+
var spans = findVerseSpans(body, v);
|
|
620
|
+
for (var i = 0; i < spans.length; i++) {
|
|
621
|
+
spans[i].classList.add('verse-selected');
|
|
622
|
+
if (!first) first = spans[i];
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (first) {
|
|
626
|
+
var bodyTop = body.getBoundingClientRect().top;
|
|
627
|
+
var targetTop = first.getBoundingClientRect().top;
|
|
628
|
+
body.scrollTop += targetTop - bodyTop - 8;
|
|
629
|
+
}
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Standard anchor by ID
|
|
634
|
+
var eid = id.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
635
|
+
var target = popup.querySelector('[id="' + eid + '"]');
|
|
597
636
|
if (!target) return;
|
|
598
|
-
var
|
|
599
|
-
var headerHeight = header ? header.offsetHeight : 0;
|
|
637
|
+
var bodyTop = body.getBoundingClientRect().top;
|
|
600
638
|
var targetTop = target.getBoundingClientRect().top;
|
|
601
|
-
|
|
602
|
-
popup.scrollTop += targetTop - popupTop - headerHeight - 8;
|
|
639
|
+
body.scrollTop += targetTop - bodyTop - 8;
|
|
603
640
|
if (/^H[1-6]$/.test(target.tagName)) {
|
|
604
641
|
var titleEl = popup.querySelector('.link-ctx-popup-title span');
|
|
605
642
|
if (titleEl) titleEl.textContent = titleEl.textContent + ' \u203a ' + target.textContent;
|
|
@@ -649,6 +686,7 @@
|
|
|
649
686
|
var extCached = cache[extKey];
|
|
650
687
|
if (extCached && typeof extCached === 'object') {
|
|
651
688
|
showPopup(x, y, extCached.title || label, extCached.html, href, linkRect);
|
|
689
|
+
applyPopupAnchor(linkHash);
|
|
652
690
|
} else if (extCached === false) {
|
|
653
691
|
showPopup(x, y, label,
|
|
654
692
|
'<div class="link-ctx-popup-url">' + escHtml(href) + '</div>' +
|
|
@@ -669,6 +707,7 @@
|
|
|
669
707
|
}
|
|
670
708
|
cache[extKey] = { title: data.title, html: data.html };
|
|
671
709
|
updatePopup(data.html, data.title || label);
|
|
710
|
+
applyPopupAnchor(linkHash);
|
|
672
711
|
})
|
|
673
712
|
.catch(function() {
|
|
674
713
|
cache[extKey] = false;
|
|
@@ -835,4 +874,27 @@
|
|
|
835
874
|
})();
|
|
836
875
|
|
|
837
876
|
})();
|
|
877
|
+
|
|
878
|
+
// Full-page verse highlighting from URL hash (#vN or #vN-M)
|
|
879
|
+
(function() {
|
|
880
|
+
var m = window.location.hash.match(/^#v(\d+)(?:-(\d+))?$/);
|
|
881
|
+
if (!m) return;
|
|
882
|
+
var start = parseInt(m[1], 10);
|
|
883
|
+
var end = m[2] ? parseInt(m[2], 10) : start;
|
|
884
|
+
var first = null;
|
|
885
|
+
for (var v = start; v <= end; v++) {
|
|
886
|
+
var sel = '[data-verse$=":' + v + '"]';
|
|
887
|
+
var spans = document.querySelectorAll(sel);
|
|
888
|
+
if (!spans.length) spans = document.querySelectorAll('[data-verse="' + v + '"]');
|
|
889
|
+
for (var i = 0; i < spans.length; i++) {
|
|
890
|
+
spans[i].classList.add('verse-selected');
|
|
891
|
+
if (!first) first = spans[i];
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
if (!first) return;
|
|
895
|
+
var top = first.getBoundingClientRect().top + window.pageYOffset;
|
|
896
|
+
var toolbar = document.querySelector('.toolbar');
|
|
897
|
+
if (toolbar) top -= toolbar.offsetHeight + 4;
|
|
898
|
+
window.scrollTo({ top: top, behavior: 'instant' });
|
|
899
|
+
})();
|
|
838
900
|
</script>
|