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.
- checksums.yaml +4 -4
- data/bin/markdownr +64 -0
- data/bin/start-claude +2 -0
- data/lib/markdown_server/app.rb +253 -40
- data/lib/markdown_server/assets/editor-loader.js +362 -0
- data/lib/markdown_server/helpers/admin_helpers.rb +10 -0
- data/lib/markdown_server/helpers/formatting_helpers.rb +3 -1
- data/lib/markdown_server/helpers/markdown_helpers.rb +132 -5
- data/lib/markdown_server/helpers/path_helpers.rb +56 -7
- data/lib/markdown_server/helpers/search_helpers.rb +31 -3
- data/lib/markdown_server/permitted_bases.rb +13 -0
- data/lib/markdown_server/plugins/bible_citations/citations.rb +4 -4
- data/lib/markdown_server/unhide.rb +114 -0
- data/lib/markdown_server/version.rb +1 -1
- data/views/browser.erb +1436 -50
- data/views/layout.erb +122 -5
- data/views/popup_assets.erb +52 -26
- metadata +6 -2
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:
|
|
209
|
+
.md-content h1 { font-size: 2rem; margin: 1.8rem 0 0.9rem; color: #2a2a2a; }
|
|
210
210
|
.md-content h2 {
|
|
211
|
-
font-size: 1.
|
|
211
|
+
font-size: 1.35rem;
|
|
212
212
|
margin: 1.8rem 0 0.6rem;
|
|
213
213
|
color: #3a3a3a;
|
|
214
|
-
border-bottom: 1px solid #
|
|
215
|
-
padding-bottom: 0.
|
|
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: #
|
|
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) {
|
data/views/popup_assets.erb
CHANGED
|
@@ -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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
@@ -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
|
-
|
|
372
|
-
|
|
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
|
-
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
|
|
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,
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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.
|
|
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:
|
|
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: []
|