markdownr 0.8.0 → 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 {
@@ -1154,6 +1217,60 @@
1154
1217
  });
1155
1218
  })();
1156
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
+
1157
1274
  // Wrap tables in scrollable containers + add expand buttons
1158
1275
  var _tableItems = [];
1159
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.8.0
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Dunn
@@ -132,8 +132,10 @@ files:
132
132
  - bin/build-and-push-to-docker
133
133
  - bin/markdownr
134
134
  - bin/markdownr-servers.yaml
135
+ - bin/start-claude
135
136
  - lib/markdown_server.rb
136
137
  - lib/markdown_server/app.rb
138
+ - lib/markdown_server/assets/editor-loader.js
137
139
  - lib/markdown_server/csv_browser/addon_registry.rb
138
140
  - lib/markdown_server/csv_browser/config_loader.rb
139
141
  - lib/markdown_server/csv_browser/row_context.rb
@@ -144,10 +146,12 @@ files:
144
146
  - lib/markdown_server/helpers/markdown_helpers.rb
145
147
  - lib/markdown_server/helpers/path_helpers.rb
146
148
  - lib/markdown_server/helpers/search_helpers.rb
149
+ - lib/markdown_server/permitted_bases.rb
147
150
  - lib/markdown_server/plugin.rb
148
151
  - lib/markdown_server/plugins/bible_citations/citations.rb
149
152
  - lib/markdown_server/plugins/bible_citations/helpers.rb
150
153
  - lib/markdown_server/plugins/bible_citations/plugin.rb
154
+ - lib/markdown_server/unhide.rb
151
155
  - lib/markdown_server/version.rb
152
156
  - views/admin_login.erb
153
157
  - views/browser.erb
@@ -178,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
178
182
  - !ruby/object:Gem::Version
179
183
  version: '0'
180
184
  requirements: []
181
- rubygems_version: 3.6.9
185
+ rubygems_version: 4.0.10
182
186
  specification_version: 4
183
187
  summary: A local markdown file server with directory browsing
184
188
  test_files: []