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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e91138e164e026f52b6957f44693eb86d75b79eda7e4404d094f1ea0d4e35d26
4
- data.tar.gz: b549ff9d07b45f43cc1639c2b4a47171f3a38bb76e2e98c1bb0ffe03ddc53fbe
3
+ metadata.gz: d8c9a7eee19198ff1effccac6edfac5938353e964073a25abaf44d90f3fcf9ce
4
+ data.tar.gz: 85bc3bc0825aceef42b0fe6ecbb5f3b753a0b0b8b3facc084b6e8c25f042439a
5
5
  SHA512:
6
- metadata.gz: cfc1004e65a89e09263f3a35f2da2b3b354afe8351efa87e49194b0aec3bef5aaeedd7d4aba70f00c01ebca6468150a296be2bdad0d8ca238f87b5bc73ebca0d
7
- data.tar.gz: c0cc3d73580b42c40c3f9244ed5acc69a74363237c54af0d21877bf4f1a64d2b642150fc4c384d13a6efb74a1bf86f12819d10b913f935ff60298e1e055e8460
6
+ metadata.gz: b3d9654e8b565eb97ba6571fe742855dd5e7580402dc558526fa445e388b803872d6c23dbbb4e684e8ae7b9276dc8e96d4bebc824c7dfa5c07f99cde2ca6624e
7
+ data.tar.gz: e81586f4f208d925c453a915b1601b5ccad5a1353ad139e19af984f810a49071a0b952eaf548b74e53159c6d7e1302d6bb86f3ab0814fd9615c5b398b381068d
@@ -1,3 +1,3 @@
1
1
  module MarkdownServer
2
- VERSION = "0.5.2"
2
+ VERSION = "0.5.4"
3
3
  end
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 (!table.parentElement.classList.contains('table-wrap')) {
1023
- var wrapper = document.createElement('div');
1024
- wrapper.className = 'table-wrap';
1025
- table.parentNode.insertBefore(wrapper, table);
1026
- wrapper.appendChild(table);
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 = '&#x2922;';
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 ? '&#x2921;' : '&#x2922;';
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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
@@ -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 left = x + 12;
1535
- var top = y + 12;
1536
- if (left + popup.offsetWidth > vw - 8) left = Math.max(8, vw - popup.offsetWidth - 8);
1537
- if (top + popup.offsetHeight > vh - 8) top = Math.max(8, y - popup.offsetHeight - 12);
1538
- if (top < 8) top = 8;
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
  })();
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.5.2
4
+ version: 0.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Dunn