markdownr 0.6.1 → 0.6.3
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 +66 -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 +23 -11
- data/views/popup_assets.erb +22 -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: 722e15f2df1b9be481803fab089a9cbcd03feca4074066af6e9c1fd32172eb95
|
|
4
|
+
data.tar.gz: 7a35e644b28724d93e14147688ed34f3a824d8eec1f2e972eb8dd2f599a94b8e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 689bdd5757bec61a6284b63b3ab6b1a67dfa9e160b7343e63787962f9ed2cbf2c4b2ad677ec482d4e7712c445a17a94e068b80b551392f737eb1ec7546b55754
|
|
7
|
+
data.tar.gz: 74cfee882d280839673ee560c3dded04c6cc3ca7f605766f037ea531538c5a8a04b697928081a74480a8830cca07192cd5389629949a96cc16b8c6565390eb58
|
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|
|
|
@@ -848,6 +909,8 @@ module MarkdownServer
|
|
|
848
909
|
end
|
|
849
910
|
|
|
850
911
|
before do
|
|
912
|
+
cache_control :public, max_age: 14400
|
|
913
|
+
|
|
851
914
|
if settings.verbose
|
|
852
915
|
$stdout.puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} #{client_ip} #{request.request_method} #{request.fullpath}"
|
|
853
916
|
$stdout.flush
|
|
@@ -1072,6 +1135,9 @@ module MarkdownServer
|
|
|
1072
1135
|
title = raw.match(/^([GH]\d+ - \w+)/i)&.[](1)&.sub(" - ", " – ") ||
|
|
1073
1136
|
raw.sub(/ [-–] .*/, "").strip
|
|
1074
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) })
|
|
1075
1141
|
else
|
|
1076
1142
|
title = page_title(html).sub(/ [-–] .*/, "").strip
|
|
1077
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 {
|
|
@@ -1780,9 +1782,7 @@
|
|
|
1780
1782
|
handleLink(anchor, savedPos.x, savedPos.y, true);
|
|
1781
1783
|
}, { passive: false });
|
|
1782
1784
|
|
|
1783
|
-
|
|
1784
|
-
popup.addEventListener(ev, function(e) { e.stopPropagation(); }, { passive: true });
|
|
1785
|
-
});
|
|
1785
|
+
popup.addEventListener('touchstart', function(e) { e.stopPropagation(); }, { passive: true });
|
|
1786
1786
|
}
|
|
1787
1787
|
|
|
1788
1788
|
function pinPopup(el) {
|
|
@@ -2021,15 +2021,25 @@
|
|
|
2021
2021
|
if (!hash || !popup) return;
|
|
2022
2022
|
var id = hash.replace(/^#/, '');
|
|
2023
2023
|
try {
|
|
2024
|
-
var
|
|
2024
|
+
var eid = id.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
2025
|
+
var target = popup.querySelector('[id="' + eid + '"]');
|
|
2026
|
+
// For scrip verse anchors like #v15, find by data-verse attribute
|
|
2027
|
+
if (!target) {
|
|
2028
|
+
var vm = id.match(/^v(\d+)$/);
|
|
2029
|
+
if (vm) {
|
|
2030
|
+
target = popup.querySelector('[data-verse="' + vm[1] + '"]');
|
|
2031
|
+
// Also match "Book Ch:Verse" format like data-verse="Acts 17:25"
|
|
2032
|
+
if (!target) target = popup.querySelector('[data-verse$=":' + vm[1] + '"]');
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2025
2035
|
if (!target) return;
|
|
2026
|
-
// Scroll the
|
|
2036
|
+
// Scroll the body container (which has overflow-y:auto) to the target,
|
|
2027
2037
|
// accounting for the sticky header so the heading lands just below it
|
|
2028
|
-
var
|
|
2029
|
-
|
|
2038
|
+
var body = popup.querySelector('.link-ctx-popup-body');
|
|
2039
|
+
if (!body) return;
|
|
2040
|
+
var bodyTop = body.getBoundingClientRect().top;
|
|
2030
2041
|
var targetTop = target.getBoundingClientRect().top;
|
|
2031
|
-
|
|
2032
|
-
popup.scrollTop += targetTop - popupTop - headerHeight - 8;
|
|
2042
|
+
body.scrollTop += targetTop - bodyTop - 8;
|
|
2033
2043
|
// Append section heading text to the popup title
|
|
2034
2044
|
if (/^H[1-6]$/.test(target.tagName)) {
|
|
2035
2045
|
var titleEl = popup.querySelector('.link-ctx-popup-title span');
|
|
@@ -2081,6 +2091,7 @@
|
|
|
2081
2091
|
var extCached = cache[extKey];
|
|
2082
2092
|
if (extCached && typeof extCached === 'object') {
|
|
2083
2093
|
showPopup(x, y, extCached.title || label, extCached.html, href, linkRect);
|
|
2094
|
+
applyPopupAnchor(linkHash);
|
|
2084
2095
|
} else if (extCached === false) {
|
|
2085
2096
|
showPopup(x, y, label,
|
|
2086
2097
|
'<div class="link-ctx-popup-url">' + escHtml(href) + '</div>' +
|
|
@@ -2101,6 +2112,7 @@
|
|
|
2101
2112
|
}
|
|
2102
2113
|
cache[extKey] = { title: data.title, html: data.html };
|
|
2103
2114
|
updatePopup(data.html, data.title || label);
|
|
2115
|
+
applyPopupAnchor(linkHash);
|
|
2104
2116
|
})
|
|
2105
2117
|
.catch(function() {
|
|
2106
2118
|
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 {
|
|
@@ -362,9 +364,7 @@
|
|
|
362
364
|
handleLink(anchor, savedPos.x, savedPos.y, true);
|
|
363
365
|
}, { passive: false });
|
|
364
366
|
|
|
365
|
-
|
|
366
|
-
popup.addEventListener(ev, function(e) { e.stopPropagation(); }, { passive: true });
|
|
367
|
-
});
|
|
367
|
+
popup.addEventListener('touchstart', function(e) { e.stopPropagation(); }, { passive: true });
|
|
368
368
|
}
|
|
369
369
|
|
|
370
370
|
function pinPopup(el) {
|
|
@@ -593,13 +593,23 @@
|
|
|
593
593
|
if (!hash || !popup) return;
|
|
594
594
|
var id = hash.replace(/^#/, '');
|
|
595
595
|
try {
|
|
596
|
-
var
|
|
596
|
+
var eid = id.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
597
|
+
var target = popup.querySelector('[id="' + eid + '"]');
|
|
598
|
+
// For scrip verse anchors like #v15, find by data-verse attribute
|
|
599
|
+
if (!target) {
|
|
600
|
+
var vm = id.match(/^v(\d+)$/);
|
|
601
|
+
if (vm) {
|
|
602
|
+
target = popup.querySelector('[data-verse="' + vm[1] + '"]');
|
|
603
|
+
// Also match "Book Ch:Verse" format like data-verse="Acts 17:25"
|
|
604
|
+
if (!target) target = popup.querySelector('[data-verse$=":' + vm[1] + '"]');
|
|
605
|
+
}
|
|
606
|
+
}
|
|
597
607
|
if (!target) return;
|
|
598
|
-
var
|
|
599
|
-
|
|
608
|
+
var body = popup.querySelector('.link-ctx-popup-body');
|
|
609
|
+
if (!body) return;
|
|
610
|
+
var bodyTop = body.getBoundingClientRect().top;
|
|
600
611
|
var targetTop = target.getBoundingClientRect().top;
|
|
601
|
-
|
|
602
|
-
popup.scrollTop += targetTop - popupTop - headerHeight - 8;
|
|
612
|
+
body.scrollTop += targetTop - bodyTop - 8;
|
|
603
613
|
if (/^H[1-6]$/.test(target.tagName)) {
|
|
604
614
|
var titleEl = popup.querySelector('.link-ctx-popup-title span');
|
|
605
615
|
if (titleEl) titleEl.textContent = titleEl.textContent + ' \u203a ' + target.textContent;
|
|
@@ -649,6 +659,7 @@
|
|
|
649
659
|
var extCached = cache[extKey];
|
|
650
660
|
if (extCached && typeof extCached === 'object') {
|
|
651
661
|
showPopup(x, y, extCached.title || label, extCached.html, href, linkRect);
|
|
662
|
+
applyPopupAnchor(linkHash);
|
|
652
663
|
} else if (extCached === false) {
|
|
653
664
|
showPopup(x, y, label,
|
|
654
665
|
'<div class="link-ctx-popup-url">' + escHtml(href) + '</div>' +
|
|
@@ -669,6 +680,7 @@
|
|
|
669
680
|
}
|
|
670
681
|
cache[extKey] = { title: data.title, html: data.html };
|
|
671
682
|
updatePopup(data.html, data.title || label);
|
|
683
|
+
applyPopupAnchor(linkHash);
|
|
672
684
|
})
|
|
673
685
|
.catch(function() {
|
|
674
686
|
cache[extKey] = false;
|