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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3497ac05a68e88d5fd0ce66073edbee5300e64763a5d00633f5e98db3d75c3af
4
- data.tar.gz: c270878ab39accd8aa2ad814d27a36e8a23f0edc92f4395baed07e303c75d167
3
+ metadata.gz: 845f5eba9de7543ef7e3c386e54f127d8ecfaf48e368d5fb11de54ca5d96cc6c
4
+ data.tar.gz: 9b00fb58e4c3a4e9e3b5c8236f30b96577f7e8bd4d18a17d8576b6382d568380
5
5
  SHA512:
6
- metadata.gz: 4a50c60fe43c63f1383c341addbfe623a8d3721a8a50e72ca75d8694601df3f5c5d15d8c6c2ad03d85fff0d7f569676adb1e8353bd26f9d078309c2995e8e356
7
- data.tar.gz: 574158ce65f6567b7af4e9376d48cb582808f967203c2ff3b5529d182fde3d609b59d2f65435c47551cdc8a0561971bc7b7982ebbd50edfa3a0c5c641ea2e3ea
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] = { "bible_citations" => { "enabled" => false } }
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
@@ -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}](#{Citations.biblegateway_url(canonical, verse, version: @version)})"
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 = Citations.biblegateway_url(canonical, verse, version: @version)
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
@@ -1,3 +1,3 @@
1
1
  module MarkdownServer
2
- VERSION = "0.6.2"
2
+ VERSION = "0.6.4"
3
3
  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 { color: #5a2c78; border-bottom-color: #5a2c78; }
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
- ['touchstart','touchmove','touchend'].forEach(function(ev) {
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 target = popup.querySelector('[id="' + id.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"]');
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
- // Scroll the popup container (which has overflow-y:auto) to the target,
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
- var popupTop = popup.getBoundingClientRect().top;
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;
@@ -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 { color: #5a2c78; border-bottom-color: #5a2c78; }
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
- ['touchstart','touchmove','touchend'].forEach(function(ev) {
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 target = popup.querySelector('[id="' + id.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"]');
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 header = popup.querySelector('.link-ctx-popup-header');
599
- var headerHeight = header ? header.offsetHeight : 0;
637
+ var bodyTop = body.getBoundingClientRect().top;
600
638
  var targetTop = target.getBoundingClientRect().top;
601
- var popupTop = popup.getBoundingClientRect().top;
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>
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.6.2
4
+ version: 0.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Dunn