joplin-plugin-minimap 1.1.1 → 1.1.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "joplin-plugin-minimap",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "A hover-to-expand table of contents minimap for the Joplin note viewer",
5
5
  "author": "lim0513",
6
6
  "homepage": "https://github.com/lim0513/joplin-minimap",
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 1,
3
3
  "id": "com.github.joplin-minimap",
4
4
  "app_min_version": "2.8",
5
- "version": "1.1.1",
5
+ "version": "1.1.3",
6
6
  "name": "Joplin Minimap",
7
7
  "description": "A hover-to-expand table of contents minimap for the note viewer. Shows heading tick marks on the right edge; hover to expand into a full ToC, click to jump.",
8
8
  "author": "lim0513",
@@ -25,6 +25,47 @@
25
25
 
26
26
  var cleanup = null; // removes listeners belonging to the current build
27
27
 
28
+ // Clicking the minimap gives the webview focus, which can make Joplin
29
+ // re-render the whole note. That detaches every heading element we hold,
30
+ // and scrollIntoView on a detached node is a silent no-op. So jumps are
31
+ // index/text-based against the LIVE DOM, and if a rebuild happens right
32
+ // after a click (the re-render case), the jump is re-applied afterwards.
33
+ var pendingJump = null; // { index, text, until }
34
+
35
+ function liveHeadings() {
36
+ var root = document.getElementById('rendered-md') || document.body;
37
+ return Array.prototype.slice.call(root.querySelectorAll('h1, h2, h3, h4, h5, h6'));
38
+ }
39
+
40
+ function jumpTo(index, text) {
41
+ var hs = liveHeadings();
42
+ var h = hs[index];
43
+ if (!h || (text && (h.textContent || '').trim() !== text)) {
44
+ for (var i = 0; i < hs.length; i++) {
45
+ if ((hs[i].textContent || '').trim() === text) { h = hs[i]; break; }
46
+ }
47
+ }
48
+ if (h && h.isConnected) h.scrollIntoView({ behavior: 'smooth', block: 'start' });
49
+ }
50
+
51
+ // Width of the note viewer's right-edge scrollbar zone. Overlay scrollbars
52
+ // (e.g. Windows 11) reserve no layout space but still intercept clicks with
53
+ // priority over page content, so when nothing is measurable we keep a safety
54
+ // gap anyway. The minimap must stay clear of that zone or clicks on the
55
+ // rightmost part of the panel silently hit the scrollbar instead.
56
+ function scrollbarGap(root) {
57
+ var gap = 0;
58
+ var node = root;
59
+ while (node && node !== document.documentElement) {
60
+ if (node.scrollHeight > node.clientHeight + 1) {
61
+ var w = node.offsetWidth - node.clientWidth;
62
+ if (w > gap) gap = w;
63
+ }
64
+ node = node.parentElement;
65
+ }
66
+ return gap > 0 ? gap : 14;
67
+ }
68
+
28
69
  function build() {
29
70
  if (cleanup) { cleanup(); cleanup = null; }
30
71
 
@@ -39,12 +80,18 @@
39
80
 
40
81
  var nav = document.createElement('nav');
41
82
  nav.id = 'jp-minimap';
42
- nav.style.right = settings.rightOffset + 'px';
83
+ // Applied in BOTH collapsed and expanded states: shifting only on hover
84
+ // would move the panel out from under the cursor and cause a
85
+ // hover/unhover flicker loop.
86
+ nav.style.right = (settings.rightOffset + scrollbarGap(root)) + 'px';
43
87
  nav.style.setProperty('--jp-mm-width', settings.panelWidth + 'px');
44
88
  // The viewer DOM can be editable in some contexts; make sure the
45
89
  // minimap never shows a caret or accepts keyboard input.
90
+ // NOTE: no preventDefault on mousedown! Blocking the default mouse-focus
91
+ // path makes Joplin's later programmatic focus count as keyboard-like,
92
+ // and the browser then draws a :focus-visible ring on the scroll
93
+ // container. Natural mouse focus never shows a ring.
46
94
  nav.setAttribute('contenteditable', 'false');
47
- nav.addEventListener('mousedown', function (e) { e.preventDefault(); });
48
95
  // Own the wheel entirely while the cursor is over the minimap:
49
96
  // scroll the ToC list ourselves and never let the event chain
50
97
  // through to the note underneath (scroll chaining feels erratic).
@@ -56,7 +103,7 @@
56
103
  var list = document.createElement('div');
57
104
  list.className = 'jp-mm-list';
58
105
 
59
- var items = headings.map(function (h) {
106
+ var items = headings.map(function (h, index) {
60
107
  var level = Number(h.tagName.charAt(1));
61
108
 
62
109
  // NOT an <a>: Joplin's viewer shows a "Ctrl+click to open" tooltip
@@ -74,8 +121,17 @@
74
121
  item.appendChild(bar);
75
122
  item.appendChild(label);
76
123
 
77
- item.addEventListener('click', function () {
78
- h.scrollIntoView({ behavior: 'smooth', block: 'start' });
124
+ // MOUSEDOWN, not click: the press gives the webview focus, Joplin may
125
+ // re-render the note, and our panel gets rebuilt BETWEEN mousedown and
126
+ // mouseup - so the click event (which needs the same target for both)
127
+ // never fires. mousedown runs before any of that can happen.
128
+ item.addEventListener('mousedown', function (e) {
129
+ if (e.button !== 0) return;
130
+ var text = (label.textContent || '').trim();
131
+ // If the press triggers a note re-render, the rebuild will
132
+ // re-apply this jump against the fresh DOM.
133
+ pendingJump = { index: index, text: text, until: Date.now() + 1200 };
134
+ jumpTo(index, text);
79
135
  });
80
136
 
81
137
  list.appendChild(item);
@@ -98,6 +154,13 @@
98
154
  document.addEventListener('scroll', updateActive, { passive: true, capture: true });
99
155
  updateActive();
100
156
 
157
+ // A rebuild arriving right after a click means the note was re-rendered
158
+ // and the original scrollIntoView hit a detached node - redo the jump.
159
+ if (pendingJump && Date.now() < pendingJump.until) {
160
+ jumpTo(pendingJump.index, pendingJump.text);
161
+ pendingJump = null;
162
+ }
163
+
101
164
  cleanup = function () {
102
165
  document.removeEventListener('scroll', updateActive, { capture: true });
103
166
  };
@@ -103,12 +103,11 @@
103
103
  background: rgba(127, 127, 127, 0.18);
104
104
  }
105
105
 
106
- /* slim scrollbar in the expanded panel */
107
- .jp-mm-list::-webkit-scrollbar { width: 4px; }
108
- .jp-mm-list::-webkit-scrollbar-thumb {
109
- background: rgba(127, 127, 127, 0.4);
110
- border-radius: 2px;
111
- }
106
+ /* No scrollbar in the expanded panel: the wheel handler owns scrolling,
107
+ * and a visible scrollbar at the panel edge invites overlay-scrollbar
108
+ * style hover/click interference. */
109
+ .jp-mm-list::-webkit-scrollbar { display: none; }
110
+ .jp-mm-list { scrollbar-width: none; }
112
111
 
113
112
  /* don't show over printed/exported output */
114
113
  @media print {
Binary file