markdownr 0.6.5 → 0.6.7

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: af0a41eadcbe996f7165b92a56174857657a21698308b515a35a67ec81a9a3e0
4
- data.tar.gz: 12f87ff3043049997f895f4dc0bde07d6d7e41c0195286b1abe29c91eada81e2
3
+ metadata.gz: ecda563bb6ae0b22bb608e65f195cb9008db45856aa2027710fbfd34a0d61808
4
+ data.tar.gz: c92343f2c54e0d48d2f754f81b81f86a92dd30ac872eaa5ca1b5390b003131b1
5
5
  SHA512:
6
- metadata.gz: cc617683848f464907a1f8d4e17986a80842e631b9e68dcd6d3eb340bb0b658d080a9849c8f1a6ec952f8ceaa23cb95fda4760e44dc02b36a6620a7b30dcbf53
7
- data.tar.gz: 73bbfe99e65f7c50b4c4ffca35b4ab98d9a706f3c8367451b115e61919391ec95d987396cb75f5cbf7890822f4ec4b0456255a47a9872af5745b2dc90737e92b
6
+ metadata.gz: 930394dda20505d2b3e0e21188563000a5481bcf5423992491b3f8cc080ff6403c63084b4083c2069d6995bff25b7353bd1591cb2bdf16083126f498b9e69dbe
7
+ data.tar.gz: 3acb3a0135fe081c1827c4994df0f376c8afb7a9d65a0b1c335825ba00327ffb64516099b6f4cf2c2763a10d617bc73a0f43c6be9d87ba7f28ea4327fe87762d
@@ -750,7 +750,7 @@ module MarkdownServer
750
750
 
751
751
  def scrip_html(html)
752
752
  # Extract the passage content block
753
- content = html[/<div\s+class="passage-text[^"]*"[^>]*>([\s\S]*?)<\/div>\s*(?:<nav|\z)/im, 1] || ""
753
+ content = html[/<div\s+class="passage-text[^"]*"[^>]*>([\s\S]*)<\/div>\s*(?:<nav|<script|<style|\z)/im, 1] || ""
754
754
  return page_html(html) if content.empty?
755
755
 
756
756
  # Add data-verse attributes to verse spans for popup scrolling.
@@ -762,7 +762,7 @@ module MarkdownServer
762
762
  # Inline the essential substitution CSS (show replacement mode by default)
763
763
  css = <<~CSS
764
764
  <style>
765
- .subst { cursor: help; border-radius: 2px; background: #f0ebff; position: relative; }
765
+ .subst { cursor: pointer; border-radius: 2px; background: #f0ebff; position: relative; }
766
766
  .subst:hover { background: #e0d4ff; outline: 1px solid #a78bfa; }
767
767
  .subst-t, .subst-o, .subst-x, .subst-s { display: none; }
768
768
  .subst-r { display: inline; color: #7c3aed; font-style: italic; }
@@ -777,36 +777,11 @@ module MarkdownServer
777
777
  .poetry-line { margin: 0; padding: 0; line-height: 1.5; }
778
778
  .poetry-line.indent-1 { padding-left: 2em; }
779
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; }
780
+ .subst-tip { display: none; }
783
781
  </style>
784
782
  CSS
785
783
 
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
784
+ css + '<div style="font-family:Georgia,serif;line-height:1.7">' + content + "</div>"
810
785
  end
811
786
 
812
787
  def inline_directory_html(dir_path, relative_dir)
@@ -1122,6 +1097,29 @@ module MarkdownServer
1122
1097
  JSON.dump({ title: title.to_s, html: html, frontmatter_html: frontmatter_html })
1123
1098
  end
1124
1099
 
1100
+ get "/fetch-json" do
1101
+ content_type :json
1102
+ url = params[:url].to_s.strip
1103
+ halt 400, '{"error":"invalid url"}' unless url.match?(/\Ahttps?:\/\//i)
1104
+
1105
+ uri = URI.parse(url)
1106
+ halt 400, '{"error":"invalid url"}' unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
1107
+
1108
+ http = Net::HTTP.new(uri.host, uri.port)
1109
+ http.use_ssl = (uri.scheme == "https")
1110
+ http.open_timeout = FETCH_TIMEOUT
1111
+ http.read_timeout = FETCH_TIMEOUT
1112
+ req = Net::HTTP::Get.new(uri.request_uri)
1113
+ req["Accept"] = "application/json"
1114
+ resp = http.request(req)
1115
+ halt 502, '{"error":"fetch failed"}' unless resp.is_a?(Net::HTTPSuccess)
1116
+
1117
+ headers "Cache-Control" => "public, max-age=3600"
1118
+ resp.body
1119
+ rescue StandardError
1120
+ halt 502, '{"error":"fetch failed"}'
1121
+ end
1122
+
1125
1123
  get "/fetch" do
1126
1124
  content_type :json
1127
1125
  url = params[:url].to_s.strip
@@ -1,3 +1,3 @@
1
1
  module MarkdownServer
2
- VERSION = "0.6.5"
2
+ VERSION = "0.6.7"
3
3
  end
data/views/layout.erb CHANGED
@@ -99,6 +99,12 @@
99
99
  border-bottom: 2px solid #d4b96a;
100
100
  padding-bottom: 0.5rem;
101
101
  }
102
+ .page-title-download {
103
+ color: inherit;
104
+ text-decoration: none;
105
+ border-bottom: none;
106
+ }
107
+ .page-title-download:hover { color: inherit; }
102
108
 
103
109
  /* Directory listing */
104
110
  .dir-listing {
@@ -199,6 +205,7 @@
199
205
  .md-content {
200
206
  line-height: 1.8;
201
207
  }
208
+ .md-content > :first-child { margin-top: 0; }
202
209
  .md-content h1 { font-size: 1.5rem; margin: 1.5rem 0 0.8rem; color: #3a3a3a; }
203
210
  .md-content h2 {
204
211
  font-size: 1.25rem;
data/views/markdown.erb CHANGED
@@ -1,15 +1,11 @@
1
1
  <% _file_path = @crumbs.map { |c| c[:name] }.drop(1).join("/") %>
2
2
  <div class="title-bar">
3
- <h1 class="page-title"><%= h(@title) %></h1>
3
+ <h1 class="page-title"><a href="<%= @download_href %>" class="page-title-download"><%= h(@title) %></a></h1>
4
4
  <form class="search-form" action="<%= search_form_path(_file_path) %>" method="get">
5
5
  <input type="text" name="q" placeholder="Search this document..." value="">
6
6
  </form>
7
7
  </div>
8
8
 
9
- <div class="toolbar">
10
- <a href="<%= @download_href %>">Download</a>
11
- </div>
12
-
13
9
  <% if @has_toc %>
14
10
  <button class="toc-fab" id="toc-fab" aria-label="Table of Contents">&#9776;</button>
15
11
  <div class="toc-overlay" id="toc-overlay"></div>
@@ -43,6 +39,10 @@
43
39
  <% end %>
44
40
 
45
41
  <div class="page-main">
42
+ <div class="md-content" data-file="<%= h(@title) %>">
43
+ <%= @content %>
44
+ </div>
45
+
46
46
  <% if @meta && !@meta.empty? %>
47
47
  <div class="frontmatter">
48
48
  <div class="frontmatter-heading">Frontmatter</div>
@@ -56,9 +56,5 @@
56
56
  </table>
57
57
  </div>
58
58
  <% end %>
59
-
60
- <div class="md-content" data-file="<%= h(@title) %>">
61
- <%= @content %>
62
- </div>
63
59
  </div>
64
60
  </div>
@@ -882,6 +882,167 @@
882
882
  document.head.appendChild(style);
883
883
  })();
884
884
 
885
+ // Substitution span popups — look up Strong's numbers in dictionary.json
886
+ (function() {
887
+ var substEls = document.querySelectorAll('.subst[data-strongs]');
888
+ if (!substEls.length) return;
889
+
890
+ var dictUrl = null;
891
+ var strongsMap = null; // strongs -> { url, blb, label }
892
+ var dictLoading = false;
893
+ var dictCallbacks = [];
894
+
895
+ function loadDict(cb) {
896
+ if (strongsMap) { cb(); return; }
897
+ dictCallbacks.push(cb);
898
+ if (dictLoading) return;
899
+ dictLoading = true;
900
+
901
+ // Discover dictionary.json URL from the first .subst's data-strongs
902
+ // Convention: definitions/dictionary.json on the bible server
903
+ var jsonUrl = 'https://bible2.risensavior.com/download/definitions/dictionary.json';
904
+ fetch('/fetch-json?url=' + encodeURIComponent(jsonUrl))
905
+ .then(function(r) { return r.ok ? r.json() : null; })
906
+ .then(function(data) {
907
+ if (!data || !data.stems) { strongsMap = {}; return; }
908
+ var urlTpl = data.url || '';
909
+ strongsMap = {};
910
+ ['greek', 'hebrew'].forEach(function(lang) {
911
+ var stems = data.stems[lang];
912
+ if (!stems) return;
913
+ Object.keys(stems).forEach(function(key) {
914
+ var entry = stems[key];
915
+ if (!entry.strongs) return;
916
+ var sn = entry.strongs.toUpperCase();
917
+ strongsMap[sn] = {
918
+ url: urlTpl.replace('{filename}', entry.filename || ''),
919
+ blb: entry.blueletterbible || null,
920
+ label: entry.original || entry.transliteration || key
921
+ };
922
+ });
923
+ });
924
+ })
925
+ .catch(function() { strongsMap = {}; })
926
+ .then(function() {
927
+ dictCallbacks.forEach(function(fn) { fn(); });
928
+ dictCallbacks = [];
929
+ });
930
+ }
931
+
932
+ function blbFallbackUrl(strongs) {
933
+ var s = strongs.toLowerCase();
934
+ var prefix = s.charAt(0) === 'h' ? 'wlc' : 'tr';
935
+ return 'https://www.blueletterbible.org/lexicon/' + s + '/nasb20/' + prefix + '/0-1/';
936
+ }
937
+
938
+ function handleSubst(el, x, y) {
939
+ var strongs = (el.dataset.strongs || '').toUpperCase();
940
+ if (!strongs) return;
941
+
942
+ var rect = el.getBoundingClientRect();
943
+ var replacement = el.dataset.replacement || '';
944
+ var original = el.dataset.original || '';
945
+ var translit = el.dataset.translit || '';
946
+
947
+ // Show loading popup immediately
948
+ var loadingTitle = replacement || original || strongs;
949
+ showPopup(x, y, loadingTitle,
950
+ '<p style="opacity:0.5;margin:0;font-family:sans-serif">Loading\u2026</p>', null, rect);
951
+
952
+ loadDict(function() {
953
+ var entry = strongsMap[strongs];
954
+ var href = entry ? entry.url : blbFallbackUrl(strongs);
955
+ var title = replacement || (entry && entry.label) || strongs;
956
+
957
+ // Fetch the target page through the existing proxy
958
+ var cacheKey = 'ext:' + href;
959
+ var cached = cache[cacheKey];
960
+ if (cached && typeof cached === 'object') {
961
+ showPopup(x, y, cached.title || title, cached.html, href, rect);
962
+ return;
963
+ }
964
+ if (cached === false) {
965
+ showPopup(x, y, title,
966
+ '<div class="link-ctx-popup-url">' + escHtml(href) + '</div>' +
967
+ '<p style="margin:0.5rem 0 0;color:#888;font-family:sans-serif;font-size:0.82rem">Could not fetch page content.</p>', href, rect);
968
+ return;
969
+ }
970
+
971
+ // Show popup with loading state and correct title/href
972
+ showPopup(x, y, title,
973
+ '<p style="opacity:0.5;margin:0;font-family:sans-serif">Loading\u2026</p>', href, rect);
974
+
975
+ if (cached === undefined) {
976
+ cache[cacheKey] = null;
977
+ fetch('/fetch?url=' + encodeURIComponent(href))
978
+ .then(function(r) { return r.ok ? r.json() : null; })
979
+ .then(function(data) {
980
+ if (!data || data.error) {
981
+ cache[cacheKey] = false;
982
+ updatePopup(
983
+ '<div class="link-ctx-popup-url">' + escHtml(href) + '</div>' +
984
+ '<p style="margin:0.5rem 0 0;color:#888;font-family:sans-serif;font-size:0.82rem">Could not fetch page content.</p>');
985
+ return;
986
+ }
987
+ cache[cacheKey] = { title: data.title, html: data.html };
988
+ updatePopup(data.html, data.title || title);
989
+ })
990
+ .catch(function() {
991
+ cache[cacheKey] = false;
992
+ updatePopup(
993
+ '<div class="link-ctx-popup-url">' + escHtml(href) + '</div>' +
994
+ '<p style="margin:0.5rem 0 0;color:#c44;font-family:sans-serif;font-size:0.82rem">Could not fetch page content.</p>');
995
+ });
996
+ }
997
+ });
998
+ }
999
+
1000
+ function findSubst(el) {
1001
+ while (el && el.tagName !== 'BODY') {
1002
+ if (el.classList && el.classList.contains('subst') && el.dataset.strongs) return el;
1003
+ el = el.parentElement;
1004
+ }
1005
+ return null;
1006
+ }
1007
+
1008
+ // Click
1009
+ document.addEventListener('click', function(e) {
1010
+ var el = findSubst(e.target);
1011
+ if (!el) return;
1012
+ e.preventDefault();
1013
+ e.stopPropagation();
1014
+ handleSubst(el, e.clientX, e.clientY);
1015
+ });
1016
+
1017
+ // Touch
1018
+ document.addEventListener('touchend', function(e) {
1019
+ if (touchMoved) return;
1020
+ var el = findSubst(e.target);
1021
+ if (!el) return;
1022
+ e.preventDefault();
1023
+ e.stopPropagation();
1024
+ var touch = e.changedTouches[0];
1025
+ handleSubst(el, touch.clientX, touch.clientY);
1026
+ }, { passive: false });
1027
+
1028
+ // Hover
1029
+ var hoverTimer = null;
1030
+ substEls.forEach(function(el) {
1031
+ el.addEventListener('mouseenter', function(e) {
1032
+ clearTimeout(hoverTimer);
1033
+ if (popup) return;
1034
+ var x = e.clientX, y = e.clientY;
1035
+ hoverTimer = setTimeout(function() {
1036
+ if (!popup) handleSubst(el, x, y);
1037
+ }, 300);
1038
+ });
1039
+ el.addEventListener('mouseleave', function() {
1040
+ clearTimeout(hoverTimer);
1041
+ if (popup) mouseLeaveTimer = setTimeout(hidePopup, 150);
1042
+ });
1043
+ });
1044
+ })();
1045
+
885
1046
  })();
886
1047
 
887
1048
  // Full-page verse highlighting from URL hash (#vN or #vN-M)
@@ -899,12 +1060,14 @@
899
1060
  var spans;
900
1061
  if (book && ch) {
901
1062
  spans = document.querySelectorAll('.verse[data-verse="' + book + ' ' + ch + ':' + v + '"]');
902
- } else {
903
- // No meta tags — filter all .verse spans by exact verse number
1063
+ }
1064
+ if (!spans || !spans.length) {
1065
+ // Fallback: match by plain verse number (e.g. data-verse="3")
904
1066
  var all = document.querySelectorAll('.verse[data-verse]');
905
1067
  spans = [];
906
1068
  for (var i = 0; i < all.length; i++) {
907
- if (all[i].getAttribute('data-verse').split(':').pop() === String(v)) spans.push(all[i]);
1069
+ var dv = all[i].getAttribute('data-verse');
1070
+ if (dv === String(v) || dv.split(':').pop() === String(v)) spans.push(all[i]);
908
1071
  }
909
1072
  }
910
1073
  for (var j = 0; j < spans.length; j++) {
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.5
4
+ version: 0.6.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Dunn