markdownr 0.5.2 → 0.5.4
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/lib/markdown_server/version.rb +1 -1
- data/views/layout.erb +180 -27
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d8c9a7eee19198ff1effccac6edfac5938353e964073a25abaf44d90f3fcf9ce
|
|
4
|
+
data.tar.gz: 85bc3bc0825aceef42b0fe6ecbb5f3b753a0b0b8b3facc084b6e8c25f042439a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b3d9654e8b565eb97ba6571fe742855dd5e7580402dc558526fa445e388b803872d6c23dbbb4e684e8ae7b9276dc8e96d4bebc824c7dfa5c07f99cde2ca6624e
|
|
7
|
+
data.tar.gz: e81586f4f208d925c453a915b1601b5ccad5a1353ad139e19af984f810a49071a0b952eaf548b74e53159c6d7e1302d6bb86f3ab0814fd9615c5b398b381068d
|
data/views/layout.erb
CHANGED
|
@@ -20,10 +20,41 @@
|
|
|
20
20
|
max-width: 900px;
|
|
21
21
|
margin: 0 auto;
|
|
22
22
|
padding: 1.5rem 2rem 3rem;
|
|
23
|
+
transition: max-width 0.25s ease, margin-left 0.25s ease;
|
|
23
24
|
}
|
|
24
25
|
.container.has-toc {
|
|
25
26
|
max-width: 1150px;
|
|
26
27
|
}
|
|
28
|
+
body.wide-mode .container,
|
|
29
|
+
body.wide-mode .container.has-toc {
|
|
30
|
+
margin-left: 0 !important;
|
|
31
|
+
max-width: 100vw !important;
|
|
32
|
+
}
|
|
33
|
+
/* When right sidebar has content, shrink container to leave room */
|
|
34
|
+
body.wide-mode:has(.right-sidebar:not(:empty)) .container,
|
|
35
|
+
body.wide-mode:has(.right-sidebar:not(:empty)) .container.has-toc {
|
|
36
|
+
max-width: calc(100vw - 220px) !important;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Right sidebar — empty placeholder, shown in wide mode only when it has content */
|
|
40
|
+
.right-sidebar {
|
|
41
|
+
display: none;
|
|
42
|
+
position: fixed;
|
|
43
|
+
right: 0;
|
|
44
|
+
top: 0;
|
|
45
|
+
bottom: 0;
|
|
46
|
+
width: 220px;
|
|
47
|
+
border-left: 1px solid #e0d8c8;
|
|
48
|
+
background: #faf8f4;
|
|
49
|
+
padding: 1rem;
|
|
50
|
+
overflow-y: auto;
|
|
51
|
+
z-index: 10;
|
|
52
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
|
53
|
+
font-size: 0.8rem;
|
|
54
|
+
}
|
|
55
|
+
body.wide-mode .right-sidebar:not(:empty) {
|
|
56
|
+
display: block;
|
|
57
|
+
}
|
|
27
58
|
|
|
28
59
|
/* Breadcrumbs */
|
|
29
60
|
.breadcrumbs {
|
|
@@ -230,11 +261,40 @@
|
|
|
230
261
|
.md-content li { margin-bottom: 0.3rem; }
|
|
231
262
|
|
|
232
263
|
/* Tables */
|
|
264
|
+
.table-outer {
|
|
265
|
+
position: relative;
|
|
266
|
+
margin: 1rem 0;
|
|
267
|
+
}
|
|
233
268
|
.table-wrap {
|
|
234
269
|
overflow-x: auto;
|
|
235
|
-
margin: 1rem 0;
|
|
236
270
|
-webkit-overflow-scrolling: touch;
|
|
237
271
|
}
|
|
272
|
+
.table-expand-btn {
|
|
273
|
+
position: fixed;
|
|
274
|
+
right: 6px;
|
|
275
|
+
z-index: 50;
|
|
276
|
+
display: none;
|
|
277
|
+
background: rgba(250, 248, 244, 0.92);
|
|
278
|
+
border: 1px solid #ccc;
|
|
279
|
+
border-radius: 4px;
|
|
280
|
+
padding: 4px 7px;
|
|
281
|
+
cursor: pointer;
|
|
282
|
+
font-size: 1rem;
|
|
283
|
+
line-height: 1;
|
|
284
|
+
color: #aaa;
|
|
285
|
+
box-shadow: 0 1px 4px rgba(0,0,0,0.1);
|
|
286
|
+
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
|
287
|
+
transform: translateY(-50%);
|
|
288
|
+
}
|
|
289
|
+
.table-expand-btn:hover {
|
|
290
|
+
color: #555;
|
|
291
|
+
border-color: #999;
|
|
292
|
+
background: #fff;
|
|
293
|
+
}
|
|
294
|
+
.table-expand-btn.is-expanded {
|
|
295
|
+
color: #8b6914;
|
|
296
|
+
border-color: #c8a84b;
|
|
297
|
+
}
|
|
238
298
|
.md-content table {
|
|
239
299
|
border-collapse: collapse;
|
|
240
300
|
width: 100%;
|
|
@@ -944,6 +1004,8 @@
|
|
|
944
1004
|
<%= yield %>
|
|
945
1005
|
</div>
|
|
946
1006
|
|
|
1007
|
+
<div class="right-sidebar"></div>
|
|
1008
|
+
|
|
947
1009
|
<script>
|
|
948
1010
|
// Breadcrumb auto-hide: visible on load for 5s, hides on scroll down,
|
|
949
1011
|
// shows for 5s on scroll up then hides again
|
|
@@ -1017,16 +1079,71 @@
|
|
|
1017
1079
|
});
|
|
1018
1080
|
})();
|
|
1019
1081
|
|
|
1020
|
-
// Wrap tables in scrollable containers
|
|
1082
|
+
// Wrap tables in scrollable containers + add expand buttons
|
|
1083
|
+
var _tableItems = [];
|
|
1021
1084
|
document.querySelectorAll('.md-content table').forEach(function(table) {
|
|
1022
|
-
if (
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1085
|
+
if (table.closest('.table-wrap')) return;
|
|
1086
|
+
|
|
1087
|
+
var outer = document.createElement('div');
|
|
1088
|
+
outer.className = 'table-outer';
|
|
1089
|
+
|
|
1090
|
+
var wrapper = document.createElement('div');
|
|
1091
|
+
wrapper.className = 'table-wrap';
|
|
1092
|
+
|
|
1093
|
+
table.parentNode.insertBefore(outer, table);
|
|
1094
|
+
outer.appendChild(wrapper);
|
|
1095
|
+
wrapper.appendChild(table);
|
|
1096
|
+
|
|
1097
|
+
var btn = document.createElement('button');
|
|
1098
|
+
btn.className = 'table-expand-btn';
|
|
1099
|
+
btn.title = 'Expand table to full width';
|
|
1100
|
+
btn.setAttribute('aria-label', 'Expand table to full width');
|
|
1101
|
+
btn.innerHTML = '⤢';
|
|
1102
|
+
btn.addEventListener('click', function() {
|
|
1103
|
+
var isOn = document.body.classList.toggle('wide-mode');
|
|
1104
|
+
_tableItems.forEach(function(i) {
|
|
1105
|
+
var active = isOn && i.btn === btn;
|
|
1106
|
+
i.btn.classList.toggle('is-expanded', active);
|
|
1107
|
+
i.btn.innerHTML = active ? '⤡' : '⤢';
|
|
1108
|
+
i.btn.title = active ? 'Collapse table' : 'Expand table to full width';
|
|
1109
|
+
i.btn.setAttribute('aria-label', i.btn.title);
|
|
1110
|
+
});
|
|
1111
|
+
_updateTableBtns();
|
|
1112
|
+
});
|
|
1113
|
+
document.body.appendChild(btn);
|
|
1114
|
+
_tableItems.push({ outer: outer, btn: btn });
|
|
1028
1115
|
});
|
|
1029
1116
|
|
|
1117
|
+
function _updateTableBtns() {
|
|
1118
|
+
var containerEl = document.querySelector('.container');
|
|
1119
|
+
if (!containerEl) return;
|
|
1120
|
+
// Use the container's natural max-width (ignoring wide-mode expansion) so the
|
|
1121
|
+
// button position is stable before and after toggling wide mode.
|
|
1122
|
+
var naturalMaxWidth = containerEl.classList.contains('has-toc') ? 1150 : 900;
|
|
1123
|
+
var naturalWidth = Math.min(naturalMaxWidth, window.innerWidth);
|
|
1124
|
+
// Right gap = space to the right of a centered container of naturalWidth
|
|
1125
|
+
var rightGap = Math.round((window.innerWidth - naturalWidth) / 2);
|
|
1126
|
+
if (rightGap < 60) {
|
|
1127
|
+
_tableItems.forEach(function(item) { item.btn.style.display = 'none'; });
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
_tableItems.forEach(function(item) {
|
|
1131
|
+
var r = item.outer.getBoundingClientRect();
|
|
1132
|
+
var inView = r.top < window.innerHeight - 20 && r.bottom > 55;
|
|
1133
|
+
if (inView) {
|
|
1134
|
+
var visTop = Math.max(r.top, 55);
|
|
1135
|
+
var visBot = Math.min(r.bottom, window.innerHeight);
|
|
1136
|
+
item.btn.style.top = Math.round((visTop + visBot) / 2) + 'px';
|
|
1137
|
+
item.btn.style.display = 'block';
|
|
1138
|
+
} else {
|
|
1139
|
+
item.btn.style.display = 'none';
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
window.addEventListener('scroll', _updateTableBtns, { passive: true });
|
|
1144
|
+
window.addEventListener('resize', _updateTableBtns);
|
|
1145
|
+
_updateTableBtns();
|
|
1146
|
+
|
|
1030
1147
|
// TOC scroll spy — highlight the nearest heading (sidebar + drawer)
|
|
1031
1148
|
(function() {
|
|
1032
1149
|
var sidebarLinks = document.querySelectorAll('.toc-sidebar a');
|
|
@@ -1419,6 +1536,7 @@
|
|
|
1419
1536
|
var touchMoved = false;
|
|
1420
1537
|
var historyStack = [];
|
|
1421
1538
|
var currentPopupPos = { x: 0, y: 0 };
|
|
1539
|
+
var mouseLeaveTimer = null;
|
|
1422
1540
|
|
|
1423
1541
|
function escHtml(s) {
|
|
1424
1542
|
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
@@ -1446,11 +1564,11 @@
|
|
|
1446
1564
|
|
|
1447
1565
|
var extLinkIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:middle;flex-shrink:0"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>';
|
|
1448
1566
|
|
|
1449
|
-
function showPopup(x, y, title, bodyHtml, href) {
|
|
1567
|
+
function showPopup(x, y, title, bodyHtml, href, linkRect) {
|
|
1450
1568
|
// Remove old popup without clearing historyStack
|
|
1451
1569
|
if (popup && popup.parentNode) popup.parentNode.removeChild(popup);
|
|
1452
1570
|
popup = null;
|
|
1453
|
-
currentPopupPos = { x: x, y: y };
|
|
1571
|
+
currentPopupPos = { x: x, y: y, linkRect: linkRect || null };
|
|
1454
1572
|
|
|
1455
1573
|
popup = document.createElement('div');
|
|
1456
1574
|
popup.className = 'link-ctx-popup';
|
|
@@ -1469,6 +1587,14 @@
|
|
|
1469
1587
|
'<div class="link-ctx-popup-body">' + bodyHtml + '</div>';
|
|
1470
1588
|
document.body.appendChild(popup);
|
|
1471
1589
|
|
|
1590
|
+
clearTimeout(mouseLeaveTimer);
|
|
1591
|
+
popup.addEventListener('mouseleave', function() {
|
|
1592
|
+
mouseLeaveTimer = setTimeout(hidePopup, 150);
|
|
1593
|
+
});
|
|
1594
|
+
popup.addEventListener('mouseenter', function() {
|
|
1595
|
+
clearTimeout(mouseLeaveTimer);
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1472
1598
|
repositionPopup();
|
|
1473
1599
|
|
|
1474
1600
|
var backBtnEl = popup.querySelector('.link-ctx-popup-back');
|
|
@@ -1476,7 +1602,7 @@
|
|
|
1476
1602
|
backBtnEl.addEventListener('click', function(e) {
|
|
1477
1603
|
e.stopPropagation();
|
|
1478
1604
|
var prev = historyStack.pop();
|
|
1479
|
-
if (prev) showPopup(prev.x, prev.y, prev.title, prev.bodyHtml, prev.href);
|
|
1605
|
+
if (prev) showPopup(prev.x, prev.y, prev.title, prev.bodyHtml, prev.href, prev.linkRect);
|
|
1480
1606
|
});
|
|
1481
1607
|
}
|
|
1482
1608
|
popup.querySelector('.link-ctx-popup-close').addEventListener('click', hidePopup);
|
|
@@ -1492,10 +1618,10 @@
|
|
|
1492
1618
|
if (!linkHref || isAnchorOnly(linkHref)) return;
|
|
1493
1619
|
e.stopPropagation();
|
|
1494
1620
|
e.preventDefault();
|
|
1495
|
-
var savedPos = { x: currentPopupPos.x, y: currentPopupPos.y };
|
|
1621
|
+
var savedPos = { x: currentPopupPos.x, y: currentPopupPos.y, linkRect: currentPopupPos.linkRect };
|
|
1496
1622
|
var titleEl = popup.querySelector('.link-ctx-popup-title');
|
|
1497
1623
|
historyStack.push({
|
|
1498
|
-
x: savedPos.x, y: savedPos.y,
|
|
1624
|
+
x: savedPos.x, y: savedPos.y, linkRect: savedPos.linkRect,
|
|
1499
1625
|
title: titleEl ? titleEl.querySelector('span').textContent : '',
|
|
1500
1626
|
bodyHtml: body.innerHTML,
|
|
1501
1627
|
href: titleEl ? (titleEl.getAttribute('href') || '') : ''
|
|
@@ -1510,10 +1636,10 @@
|
|
|
1510
1636
|
if (!linkHref || isAnchorOnly(linkHref)) return;
|
|
1511
1637
|
e.stopPropagation();
|
|
1512
1638
|
e.preventDefault();
|
|
1513
|
-
var savedPos = { x: currentPopupPos.x, y: currentPopupPos.y };
|
|
1639
|
+
var savedPos = { x: currentPopupPos.x, y: currentPopupPos.y, linkRect: currentPopupPos.linkRect };
|
|
1514
1640
|
var titleEl = popup.querySelector('.link-ctx-popup-title');
|
|
1515
1641
|
historyStack.push({
|
|
1516
|
-
x: savedPos.x, y: savedPos.y,
|
|
1642
|
+
x: savedPos.x, y: savedPos.y, linkRect: savedPos.linkRect,
|
|
1517
1643
|
title: titleEl ? titleEl.querySelector('span').textContent : '',
|
|
1518
1644
|
bodyHtml: body.innerHTML,
|
|
1519
1645
|
href: titleEl ? (titleEl.getAttribute('href') || '') : ''
|
|
@@ -1529,13 +1655,35 @@
|
|
|
1529
1655
|
function repositionPopup() {
|
|
1530
1656
|
if (!popup) return;
|
|
1531
1657
|
var x = currentPopupPos.x, y = currentPopupPos.y;
|
|
1658
|
+
var rect = currentPopupPos.linkRect;
|
|
1532
1659
|
var vw = Math.min(window.innerWidth, document.documentElement.clientWidth);
|
|
1533
1660
|
var vh = window.innerHeight;
|
|
1534
|
-
var
|
|
1535
|
-
var
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1661
|
+
var pw = popup.offsetWidth;
|
|
1662
|
+
var ph = popup.offsetHeight;
|
|
1663
|
+
|
|
1664
|
+
// Horizontal: position so mouse is ~16px inside left edge, clamped to viewport
|
|
1665
|
+
var left = Math.min(x - 16, vw - pw - 8);
|
|
1666
|
+
if (left < 8) left = 8;
|
|
1667
|
+
|
|
1668
|
+
// Vertical: tight to link bounds when available
|
|
1669
|
+
var top;
|
|
1670
|
+
if (rect) {
|
|
1671
|
+
var gap = 3;
|
|
1672
|
+
var belowTop = rect.bottom + gap;
|
|
1673
|
+
var aboveTop = rect.top - ph - gap;
|
|
1674
|
+
if (belowTop + ph <= vh - 8) {
|
|
1675
|
+
top = belowTop; // fits below
|
|
1676
|
+
} else if (aboveTop >= 8) {
|
|
1677
|
+
top = aboveTop; // fits above
|
|
1678
|
+
} else {
|
|
1679
|
+
top = Math.max(8, vh - ph - 8); // clamp: covers link
|
|
1680
|
+
}
|
|
1681
|
+
} else {
|
|
1682
|
+
top = y + 12;
|
|
1683
|
+
if (top + ph > vh - 8) top = Math.max(8, y - ph - 12);
|
|
1684
|
+
if (top < 8) top = 8;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1539
1687
|
popup.style.left = left + 'px';
|
|
1540
1688
|
popup.style.top = top + 'px';
|
|
1541
1689
|
}
|
|
@@ -1550,28 +1698,32 @@
|
|
|
1550
1698
|
}
|
|
1551
1699
|
|
|
1552
1700
|
function hidePopup() {
|
|
1701
|
+
clearTimeout(mouseLeaveTimer);
|
|
1553
1702
|
if (popup && popup.parentNode) popup.parentNode.removeChild(popup);
|
|
1554
1703
|
popup = null;
|
|
1555
1704
|
historyStack = [];
|
|
1556
1705
|
}
|
|
1557
1706
|
|
|
1707
|
+
|
|
1558
1708
|
function handleLink(anchor, x, y, chained) {
|
|
1559
1709
|
if (!chained) historyStack = [];
|
|
1560
1710
|
var href = anchor.getAttribute('href');
|
|
1561
1711
|
if (!href || isAnchorOnly(href)) return;
|
|
1562
1712
|
var label = anchor.textContent.trim() || href;
|
|
1713
|
+
// For chained popup navigation keep the current link rect; for new popups measure the anchor
|
|
1714
|
+
var linkRect = chained ? currentPopupPos.linkRect : anchor.getBoundingClientRect();
|
|
1563
1715
|
|
|
1564
1716
|
if (isLocalMd(href)) {
|
|
1565
1717
|
var path = previewPath(href);
|
|
1566
1718
|
var cached = cache[path];
|
|
1567
1719
|
if (cached && typeof cached === 'object') {
|
|
1568
|
-
showPopup(x, y, cached.title || label, cached.html, href);
|
|
1720
|
+
showPopup(x, y, cached.title || label, cached.html, href, linkRect);
|
|
1569
1721
|
} else if (cached === false) {
|
|
1570
1722
|
showPopup(x, y, label,
|
|
1571
1723
|
'<div class="link-ctx-popup-url">' + escHtml(href) + '</div>' +
|
|
1572
|
-
'<p style="margin:0;color:#888;font-family:sans-serif;font-size:0.82rem">Preview not available</p>', href);
|
|
1724
|
+
'<p style="margin:0;color:#888;font-family:sans-serif;font-size:0.82rem">Preview not available</p>', href, linkRect);
|
|
1573
1725
|
} else {
|
|
1574
|
-
showPopup(x, y, label, '<p style="opacity:0.5;margin:0;font-family:sans-serif">Loading\u2026</p>', href);
|
|
1726
|
+
showPopup(x, y, label, '<p style="opacity:0.5;margin:0;font-family:sans-serif">Loading\u2026</p>', href, linkRect);
|
|
1575
1727
|
if (cached === undefined) {
|
|
1576
1728
|
cache[path] = null;
|
|
1577
1729
|
fetch(path)
|
|
@@ -1592,13 +1744,13 @@
|
|
|
1592
1744
|
var extKey = 'ext:' + href;
|
|
1593
1745
|
var extCached = cache[extKey];
|
|
1594
1746
|
if (extCached && typeof extCached === 'object') {
|
|
1595
|
-
showPopup(x, y, extCached.title || label, extCached.html, href);
|
|
1747
|
+
showPopup(x, y, extCached.title || label, extCached.html, href, linkRect);
|
|
1596
1748
|
} else if (extCached === false) {
|
|
1597
1749
|
showPopup(x, y, label,
|
|
1598
1750
|
'<div class="link-ctx-popup-url">' + escHtml(href) + '</div>' +
|
|
1599
|
-
'<p style="margin:0.5rem 0 0;color:#888;font-family:sans-serif;font-size:0.82rem">Could not fetch page content.</p>', href);
|
|
1751
|
+
'<p style="margin:0.5rem 0 0;color:#888;font-family:sans-serif;font-size:0.82rem">Could not fetch page content.</p>', href, linkRect);
|
|
1600
1752
|
} else {
|
|
1601
|
-
showPopup(x, y, label, '<p style="opacity:0.5;margin:0;font-family:sans-serif">Loading\u2026</p>', href);
|
|
1753
|
+
showPopup(x, y, label, '<p style="opacity:0.5;margin:0;font-family:sans-serif">Loading\u2026</p>', href, linkRect);
|
|
1602
1754
|
if (extCached === undefined) {
|
|
1603
1755
|
cache[extKey] = null;
|
|
1604
1756
|
fetch('/fetch?url=' + encodeURIComponent(href))
|
|
@@ -1623,7 +1775,7 @@
|
|
|
1623
1775
|
}
|
|
1624
1776
|
}
|
|
1625
1777
|
} else {
|
|
1626
|
-
showPopup(x, y, label, '<div class="link-ctx-popup-url">' + escHtml(href) + '</div>', href);
|
|
1778
|
+
showPopup(x, y, label, '<div class="link-ctx-popup-url">' + escHtml(href) + '</div>', href, linkRect);
|
|
1627
1779
|
}
|
|
1628
1780
|
}
|
|
1629
1781
|
|
|
@@ -1686,6 +1838,7 @@
|
|
|
1686
1838
|
});
|
|
1687
1839
|
a.addEventListener('mouseleave', function() {
|
|
1688
1840
|
clearTimeout(hoverTimer);
|
|
1841
|
+
if (popup) mouseLeaveTimer = setTimeout(hidePopup, 150);
|
|
1689
1842
|
});
|
|
1690
1843
|
});
|
|
1691
1844
|
})();
|