markdownr 0.7.2 → 0.8.1

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.
data/views/layout.erb CHANGED
@@ -206,15 +206,15 @@
206
206
  line-height: 1.8;
207
207
  }
208
208
  .md-content > :first-child { margin-top: 0; }
209
- .md-content h1 { font-size: 1.5rem; margin: 1.5rem 0 0.8rem; color: #3a3a3a; }
209
+ .md-content h1 { font-size: 2rem; margin: 1.8rem 0 0.9rem; color: #2a2a2a; }
210
210
  .md-content h2 {
211
- font-size: 1.25rem;
211
+ font-size: 1.35rem;
212
212
  margin: 1.8rem 0 0.6rem;
213
213
  color: #3a3a3a;
214
- border-bottom: 1px solid #e0d8c8;
215
- padding-bottom: 0.3rem;
214
+ border-bottom: 1px solid #b5a78f;
215
+ padding-bottom: 0.1rem;
216
216
  }
217
- .md-content h3 { font-size: 1.1rem; margin: 1.4rem 0 0.5rem; color: #555; }
217
+ .md-content h3 { font-size: 1.1rem; margin: 1.4rem 0 0.5rem 1rem; color: #3a3a3a; }
218
218
 
219
219
  .md-content blockquote {
220
220
  border-left: 4px solid #d4b96a;
@@ -229,6 +229,38 @@
229
229
  margin: 0;
230
230
  }
231
231
 
232
+ .md-content .callout {
233
+ border-left: 4px solid var(--callout-color, #888);
234
+ background: color-mix(in srgb, var(--callout-color, #888) 8%, transparent);
235
+ border-radius: 4px;
236
+ margin: 1rem 0;
237
+ padding: 0.6rem 0.9rem;
238
+ color: #2a2a2a;
239
+ font-style: normal;
240
+ }
241
+ .md-content .callout .callout-title {
242
+ display: flex;
243
+ align-items: center;
244
+ gap: 0.4rem;
245
+ font-weight: 600;
246
+ color: var(--callout-color, #444);
247
+ cursor: default;
248
+ }
249
+ .md-content .callout details > summary.callout-title { cursor: pointer; list-style: none; }
250
+ .md-content .callout details > summary.callout-title::-webkit-details-marker { display: none; }
251
+ .md-content .callout details > summary.callout-title::after {
252
+ content: "▸";
253
+ margin-left: auto;
254
+ font-size: 0.7rem;
255
+ transition: transform 0.15s;
256
+ }
257
+ .md-content .callout details[open] > summary.callout-title::after { transform: rotate(90deg); }
258
+ .md-content .callout .callout-icon { flex-shrink: 0; }
259
+ .md-content .callout .callout-content { margin-top: 0.4rem; }
260
+ .md-content .callout .callout-content > :first-child { margin-top: 0; }
261
+ .md-content .callout .callout-content > :last-child { margin-bottom: 0; }
262
+ .md-content .callout .callout-content p { white-space: normal; margin: 0.5rem 0; }
263
+
232
264
  .md-content a {
233
265
  color: #8b6914;
234
266
  text-decoration: none;
@@ -251,6 +283,7 @@
251
283
  overflow-x: auto;
252
284
  font-size: 0.85rem;
253
285
  line-height: 1.5;
286
+ position: relative;
254
287
  }
255
288
  .md-content pre code {
256
289
  background: none;
@@ -258,6 +291,34 @@
258
291
  color: inherit;
259
292
  }
260
293
 
294
+ /* Copy-to-clipboard button on <pre> blocks */
295
+ .copy-btn {
296
+ position: absolute;
297
+ top: 6px;
298
+ right: 6px;
299
+ background: rgba(255, 255, 255, 0.08);
300
+ color: #ddd;
301
+ border: 1px solid rgba(255, 255, 255, 0.18);
302
+ border-radius: 4px;
303
+ padding: 3px 6px;
304
+ cursor: pointer;
305
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
306
+ line-height: 0;
307
+ opacity: 0;
308
+ transition: opacity 0.15s ease, background 0.15s ease, color 0.15s ease;
309
+ z-index: 1;
310
+ }
311
+ .copy-btn svg { display: block; }
312
+ pre:hover > .copy-btn,
313
+ .copy-btn:focus { opacity: 1; }
314
+ .copy-btn:hover { background: rgba(255, 255, 255, 0.18); color: #fff; }
315
+ .copy-btn.copied {
316
+ background: #2e7d32;
317
+ color: #fff;
318
+ border-color: #2e7d32;
319
+ opacity: 1;
320
+ }
321
+
261
322
  .md-content hr {
262
323
  border: none;
263
324
  border-top: 1px solid #e0d8c8;
@@ -355,7 +416,9 @@
355
416
  font-family: "SF Mono", Menlo, Consolas, monospace;
356
417
  font-size: 0.85rem;
357
418
  line-height: 1.5;
419
+ position: relative;
358
420
  }
421
+ .raw-view > pre { position: static; }
359
422
 
360
423
  /* Download link */
361
424
  .toolbar {
@@ -403,21 +466,8 @@
403
466
  }
404
467
  .sort-arrow { font-size: 0.6rem; }
405
468
 
406
- /* Rouge syntax highlighting (Monokai-inspired) */
407
- .highlight .k, .highlight .kd, .highlight .kn, .highlight .kp,
408
- .highlight .kr, .highlight .kt { color: #66d9ef; }
409
- .highlight .s, .highlight .s1, .highlight .s2, .highlight .sb,
410
- .highlight .sc, .highlight .sd, .highlight .sh, .highlight .sx { color: #e6db74; }
411
- .highlight .c, .highlight .c1, .highlight .cm, .highlight .cs { color: #75715e; font-style: italic; }
412
- .highlight .na { color: #a6e22e; }
413
- .highlight .nf, .highlight .nb { color: #a6e22e; }
414
- .highlight .nn, .highlight .nc { color: #66d9ef; }
415
- .highlight .no { color: #ae81ff; }
416
- .highlight .mi, .highlight .mf, .highlight .mh, .highlight .mo { color: #ae81ff; }
417
- .highlight .o, .highlight .ow { color: #f92672; }
418
- .highlight .p { color: #f0f0f0; }
419
- .highlight .gi { color: #a6e22e; }
420
- .highlight .gd { color: #f92672; }
469
+ /* Rouge Monokai syntax highlighting */
470
+ <%= Rouge::Themes::Monokai.render(scope: '.highlight').gsub(/^\.highlight \{\n.*?background-color:.*?\n\}$/m, '') %>
421
471
 
422
472
  /* TOC sidebar */
423
473
  .page-with-toc {
@@ -1167,6 +1217,60 @@
1167
1217
  });
1168
1218
  })();
1169
1219
 
1220
+ // Add copy-to-clipboard buttons to <pre> code blocks
1221
+ var COPY_ICON_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>';
1222
+ var CHECK_ICON_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>';
1223
+
1224
+ function _legacyCopy(text) {
1225
+ var ta = document.createElement('textarea');
1226
+ ta.value = text;
1227
+ ta.style.position = 'fixed';
1228
+ ta.style.left = '-9999px';
1229
+ document.body.appendChild(ta);
1230
+ ta.select();
1231
+ try { document.execCommand('copy'); } catch (e) {}
1232
+ document.body.removeChild(ta);
1233
+ }
1234
+
1235
+ function _addCopyButtons(root) {
1236
+ var scope = root || document;
1237
+ var pres = scope.querySelectorAll('.md-content pre, .raw-view pre');
1238
+ pres.forEach(function(pre) {
1239
+ if (pre.querySelector(':scope > .copy-btn')) return;
1240
+ var btn = document.createElement('button');
1241
+ btn.type = 'button';
1242
+ btn.className = 'copy-btn';
1243
+ btn.title = 'Copy';
1244
+ btn.setAttribute('aria-label', 'Copy code');
1245
+ btn.innerHTML = COPY_ICON_SVG;
1246
+ btn.addEventListener('click', function(e) {
1247
+ e.stopPropagation();
1248
+ e.preventDefault();
1249
+ var clone = pre.cloneNode(true);
1250
+ var existing = clone.querySelector('.copy-btn');
1251
+ if (existing) existing.remove();
1252
+ var codeEl = clone.querySelector('code');
1253
+ var text = (codeEl || clone).textContent;
1254
+ var done = function() {
1255
+ btn.classList.add('copied');
1256
+ btn.innerHTML = CHECK_ICON_SVG;
1257
+ setTimeout(function() {
1258
+ btn.classList.remove('copied');
1259
+ btn.innerHTML = COPY_ICON_SVG;
1260
+ }, 1500);
1261
+ };
1262
+ if (navigator.clipboard && navigator.clipboard.writeText) {
1263
+ navigator.clipboard.writeText(text).then(done, function() { _legacyCopy(text); done(); });
1264
+ } else {
1265
+ _legacyCopy(text);
1266
+ done();
1267
+ }
1268
+ });
1269
+ pre.appendChild(btn);
1270
+ });
1271
+ }
1272
+ _addCopyButtons();
1273
+
1170
1274
  // Wrap tables in scrollable containers + add expand buttons
1171
1275
  var _tableItems = [];
1172
1276
  document.querySelectorAll('.md-content table').forEach(function(table) {
@@ -235,6 +235,21 @@
235
235
  .link-ctx-popup-body .subst-p,
236
236
  .link-ctx-popup-body .subst-s { display: none !important; }
237
237
 
238
+ /*
239
+ * Scrip's CSS gates display of verse numbers, footnotes, headings, etc. on
240
+ * body classes (e.g. `body:not(.show-verse-numbers) .verse-number { display: none }`).
241
+ * Inside a popup the host page's <body> has none of those classes, so the
242
+ * passage content disappears. Restore visibility for popup contexts.
243
+ */
244
+ .link-ctx-popup-body .verse-number,
245
+ .link-ctx-popup-body .footnote-marker,
246
+ .link-ctx-popup-body .crossref-marker { display: inline !important; }
247
+ .link-ctx-popup-body .section-heading,
248
+ .link-ctx-popup-body .section-footnote,
249
+ .link-ctx-popup-body .footnotes,
250
+ .link-ctx-popup-body .crossrefs { display: block !important; }
251
+ .link-ctx-popup-body .section-summary { display: inline !important; }
252
+
238
253
  /* Blue Letter Bible popup tables */
239
254
  .blb-table { width: 100%; border-collapse: collapse; font-size: 0.85rem; margin-bottom: 0.6rem; }
240
255
  .blb-table th, .blb-table td { padding: 3px 7px; border: 1px solid #ddd; }
@@ -262,6 +277,10 @@
262
277
  var popupDragging = false;
263
278
  var currentPopupPos = { x: 0, y: 0 };
264
279
  var mouseLeaveTimer = null;
280
+ // Filled in below by the .subst IIFE so the popup body click handler can
281
+ // route .subst clicks (which never reach document-level handlers because
282
+ // popup.click stops propagation) to the Strong's-definition fetcher.
283
+ var substHandlers = null;
265
284
 
266
285
  function escHtml(s) {
267
286
  return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
@@ -368,14 +387,8 @@
368
387
  popup.addEventListener('click', function(e) { e.stopPropagation(); });
369
388
 
370
389
  var body = popup.querySelector('.link-ctx-popup-body');
371
- body.addEventListener('click', function(e) {
372
- var anchor = findLink(e.target);
373
- if (!anchor) return;
374
- var linkHref = anchor.getAttribute('href');
375
- if (!linkHref || isAnchorOnly(linkHref)) return;
376
- e.stopPropagation();
377
- e.preventDefault();
378
- if (thisPopup.dataset.pinned) { handleLink(anchor, e.clientX, e.clientY, false); return; }
390
+
391
+ function pushHistoryAndChain(navigate, x, y) {
379
392
  var savedPos = { x: currentPopupPos.x, y: currentPopupPos.y, linkRect: currentPopupPos.linkRect };
380
393
  var titleEl = thisPopup.querySelector('.link-ctx-popup-title');
381
394
  historyStack.push({
@@ -385,27 +398,36 @@
385
398
  href: titleEl ? (titleEl.getAttribute('href') || '') : '',
386
399
  scrollTop: thisPopup.scrollTop
387
400
  });
388
- handleLink(anchor, savedPos.x, savedPos.y, true);
389
- });
390
- body.addEventListener('touchend', function(e) {
391
- if (touchMoved) return;
401
+ navigate(savedPos.x, savedPos.y);
402
+ }
403
+
404
+ function handleBodyActivation(e, x, y) {
405
+ var subst = substHandlers && substHandlers.find(e.target);
406
+ if (subst) {
407
+ e.stopPropagation();
408
+ e.preventDefault();
409
+ if (thisPopup.dataset.pinned) { substHandlers.handle(subst, x, y); return true; }
410
+ pushHistoryAndChain(function(sx, sy) { substHandlers.handle(subst, sx, sy); }, x, y);
411
+ return true;
412
+ }
392
413
  var anchor = findLink(e.target);
393
- if (!anchor) return;
414
+ if (!anchor) return false;
394
415
  var linkHref = anchor.getAttribute('href');
395
- if (!linkHref || isAnchorOnly(linkHref)) return;
416
+ if (!linkHref || isAnchorOnly(linkHref)) return false;
396
417
  e.stopPropagation();
397
418
  e.preventDefault();
398
- if (thisPopup.dataset.pinned) { handleLink(anchor, e.clientX, e.clientY, false); return; }
399
- var savedPos = { x: currentPopupPos.x, y: currentPopupPos.y, linkRect: currentPopupPos.linkRect };
400
- var titleEl = thisPopup.querySelector('.link-ctx-popup-title');
401
- historyStack.push({
402
- x: savedPos.x, y: savedPos.y, linkRect: savedPos.linkRect,
403
- title: titleEl ? titleEl.querySelector('span').textContent : '',
404
- bodyHtml: body.innerHTML,
405
- href: titleEl ? (titleEl.getAttribute('href') || '') : '',
406
- scrollTop: thisPopup.scrollTop
407
- });
408
- handleLink(anchor, savedPos.x, savedPos.y, true);
419
+ if (thisPopup.dataset.pinned) { handleLink(anchor, x, y, false); return true; }
420
+ pushHistoryAndChain(function(sx, sy) { handleLink(anchor, sx, sy, true); }, x, y);
421
+ return true;
422
+ }
423
+
424
+ body.addEventListener('click', function(e) {
425
+ handleBodyActivation(e, e.clientX, e.clientY);
426
+ });
427
+ body.addEventListener('touchend', function(e) {
428
+ if (touchMoved) return;
429
+ var touch = e.changedTouches[0];
430
+ handleBodyActivation(e, touch ? touch.clientX : 0, touch ? touch.clientY : 0);
409
431
  }, { passive: false });
410
432
 
411
433
  popup.addEventListener('touchstart', function(e) { e.stopPropagation(); }, { passive: true });
@@ -945,7 +967,6 @@
945
967
  // Substitution span popups — use server-injected data-popup-url
946
968
  (function() {
947
969
  var substEls = document.querySelectorAll('.subst[data-strongs]');
948
- if (!substEls.length) return;
949
970
 
950
971
  function blbFallbackUrl(strongs) {
951
972
  var s = strongs.toLowerCase();
@@ -1011,6 +1032,11 @@
1011
1032
  return null;
1012
1033
  }
1013
1034
 
1035
+ // Expose for the popup body click handler — popup-internal .subst
1036
+ // clicks never reach the document-level click handler below because the
1037
+ // popup stops propagation.
1038
+ substHandlers = { find: findSubst, handle: handleSubst };
1039
+
1014
1040
  // Click
1015
1041
  document.addEventListener('click', function(e) {
1016
1042
  var el = findSubst(e.target);
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.7.2
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Dunn
@@ -93,6 +93,34 @@ dependencies:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
95
  version: '4.0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: json_schemer
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '2.0'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '2.0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: csv
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '3.0'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '3.0'
96
124
  description: Serve markdown files from any directory with a clean web interface, syntax
97
125
  highlighting, YAML frontmatter support, and wiki-link resolution.
98
126
  executables:
@@ -103,20 +131,30 @@ files:
103
131
  - bin/Dockerfile.markdownr
104
132
  - bin/build-and-push-to-docker
105
133
  - bin/markdownr
134
+ - bin/markdownr-servers.yaml
135
+ - bin/start-claude
106
136
  - lib/markdown_server.rb
107
137
  - lib/markdown_server/app.rb
138
+ - lib/markdown_server/assets/editor-loader.js
139
+ - lib/markdown_server/csv_browser/addon_registry.rb
140
+ - lib/markdown_server/csv_browser/config_loader.rb
141
+ - lib/markdown_server/csv_browser/row_context.rb
142
+ - lib/markdown_server/csv_browser/table_reader.rb
108
143
  - lib/markdown_server/helpers/admin_helpers.rb
109
144
  - lib/markdown_server/helpers/fetch_helpers.rb
110
145
  - lib/markdown_server/helpers/formatting_helpers.rb
111
146
  - lib/markdown_server/helpers/markdown_helpers.rb
112
147
  - lib/markdown_server/helpers/path_helpers.rb
113
148
  - lib/markdown_server/helpers/search_helpers.rb
149
+ - lib/markdown_server/permitted_bases.rb
114
150
  - lib/markdown_server/plugin.rb
115
151
  - lib/markdown_server/plugins/bible_citations/citations.rb
116
152
  - lib/markdown_server/plugins/bible_citations/helpers.rb
117
153
  - lib/markdown_server/plugins/bible_citations/plugin.rb
154
+ - lib/markdown_server/unhide.rb
118
155
  - lib/markdown_server/version.rb
119
156
  - views/admin_login.erb
157
+ - views/browser.erb
120
158
  - views/directory.erb
121
159
  - views/layout.erb
122
160
  - views/markdown.erb
@@ -144,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
182
  - !ruby/object:Gem::Version
145
183
  version: '0'
146
184
  requirements: []
147
- rubygems_version: 3.6.9
185
+ rubygems_version: 4.0.10
148
186
  specification_version: 4
149
187
  summary: A local markdown file server with directory browsing
150
188
  test_files: []