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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3ae72dd628c263c9d8657afdb7e29d5449c0515e636494322dd9e9f256167c78
4
- data.tar.gz: fd1996de9f2bb8bdd5460189d2aaae3448e30874facea35a653822f6916d3ebe
3
+ metadata.gz: 722e15f2df1b9be481803fab089a9cbcd03feca4074066af6e9c1fd32172eb95
4
+ data.tar.gz: 7a35e644b28724d93e14147688ed34f3a824d8eec1f2e972eb8dd2f599a94b8e
5
5
  SHA512:
6
- metadata.gz: 07e413a60a5a9f8066ee9ad42f33528aa793e2f5d076eb7b5feb1d4beb214d0090d0715e7468cc962903a5a686a3d6a88a31be2c5632b4ef7721a432e6d3cbac
7
- data.tar.gz: 63e6c9ba052569bccb6ca25452ddf77b8e88e94dd36f809326a3574113b54d580a2194c669040afb23c60b2fef7287155d908c2bf57511df255022c28357d51c
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] = { "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|
@@ -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}](#{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.1"
2
+ VERSION = "0.6.3"
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 {
@@ -1780,9 +1782,7 @@
1780
1782
  handleLink(anchor, savedPos.x, savedPos.y, true);
1781
1783
  }, { passive: false });
1782
1784
 
1783
- ['touchstart','touchmove','touchend'].forEach(function(ev) {
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 target = popup.querySelector('[id="' + id.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"]');
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 popup container (which has overflow-y:auto) to the target,
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 header = popup.querySelector('.link-ctx-popup-header');
2029
- var headerHeight = header ? header.offsetHeight : 0;
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
- var popupTop = popup.getBoundingClientRect().top;
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;
@@ -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 {
@@ -362,9 +364,7 @@
362
364
  handleLink(anchor, savedPos.x, savedPos.y, true);
363
365
  }, { passive: false });
364
366
 
365
- ['touchstart','touchmove','touchend'].forEach(function(ev) {
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 target = popup.querySelector('[id="' + id.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"]');
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 header = popup.querySelector('.link-ctx-popup-header');
599
- var headerHeight = header ? header.offsetHeight : 0;
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
- var popupTop = popup.getBoundingClientRect().top;
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;
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.1
4
+ version: 0.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Dunn