joplin-plugin-minimap 1.1.1 → 1.1.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.
- package/package.json +1 -1
- package/publish/manifest.json +1 -1
- package/publish/minimap-view.js +85 -5
- package/publish/minimapContentScript.js +3 -1
- package/publish/plugin.jpl +0 -0
- package/publish/minimap.css +0 -116
package/package.json
CHANGED
package/publish/manifest.json
CHANGED
|
@@ -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.
|
|
5
|
+
"version": "1.1.4",
|
|
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",
|
package/publish/minimap-view.js
CHANGED
|
@@ -25,8 +25,66 @@
|
|
|
25
25
|
|
|
26
26
|
var cleanup = null; // removes listeners belonging to the current build
|
|
27
27
|
|
|
28
|
+
// The stylesheet is injected BY THIS SCRIPT and re-checked on every build.
|
|
29
|
+
// Rationale: Joplin can re-render the document in ways that drop injected
|
|
30
|
+
// asset stylesheets while this script's watcher survives - the rebuilt nav
|
|
31
|
+
// then renders UNSTYLED as flow content below the note (looks like a
|
|
32
|
+
// duplicated outline under the document). Keeping the CSS inline and
|
|
33
|
+
// re-injecting guarantees the nav and its styling live and die together.
|
|
34
|
+
var MINIMAP_CSS = "/* Joplin Minimap \u2014 collapsed tick bars, hover-expanded ToC panel.\n * Colors use currentColor / rgba so it follows both light and dark themes.\n */\n\n#jp-minimap {\n\tuser-select: none;\n\t-webkit-user-select: none;\n\tcaret-color: transparent;\n\tcursor: default;\n\tposition: fixed;\n\ttop: 50%;\n\tright: 6px;\n\ttransform: translateY(-50%);\n\tz-index: 9999;\n\tfont-size: 12.5px;\n\tline-height: 1.35;\n\tcolor: inherit;\n}\n\n.jp-mm-list {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-end;\n\tpadding: 8px 6px;\n\tmax-height: 84vh;\n\toverflow: hidden;\n\tborder-radius: 10px;\n\ttransition: background 0.15s ease, box-shadow 0.15s ease;\n}\n\n.jp-mm-item {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-end;\n\tpadding: 3px 4px;\n\tborder-radius: 6px;\n\ttext-decoration: none;\n\tcolor: inherit;\n\topacity: 0.5;\n\tcursor: pointer;\n\toutline: none;\n}\n\n/* ---- collapsed state: tick bars, width by heading level ---- */\n\n.jp-mm-bar {\n\tdisplay: block;\n\theight: 2px;\n\tborder-radius: 1px;\n\tbackground: currentColor;\n}\n\n.jp-mm-l1 .jp-mm-bar { width: 18px; }\n.jp-mm-l2 .jp-mm-bar { width: 13px; }\n.jp-mm-l3 .jp-mm-bar { width: 9px; }\n.jp-mm-l4 .jp-mm-bar { width: 7px; }\n.jp-mm-l5 .jp-mm-bar { width: 5px; }\n.jp-mm-l6 .jp-mm-bar { width: 5px; }\n\n.jp-mm-label { display: none; }\n\n/* ---- expanded state (hover) ---- */\n\n#jp-minimap:hover .jp-mm-list {\n\talign-items: stretch;\n\toverflow-y: auto;\n\toverscroll-behavior: contain;\n\tbackground: rgba(127, 127, 127, 0.16);\n\tbackdrop-filter: blur(10px);\n\t-webkit-backdrop-filter: blur(10px);\n\tbox-shadow: 0 6px 28px rgba(0, 0, 0, 0.28);\n}\n\n#jp-minimap:hover .jp-mm-bar { display: none; }\n\n#jp-minimap:hover .jp-mm-item { justify-content: flex-start; }\n\n#jp-minimap:hover .jp-mm-label {\n\tdisplay: block;\n\tmax-width: var(--jp-mm-width, 240px);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* indent by heading level when expanded */\n#jp-minimap:hover .jp-mm-l2 { padding-left: 16px; }\n#jp-minimap:hover .jp-mm-l3 { padding-left: 28px; }\n#jp-minimap:hover .jp-mm-l4 { padding-left: 40px; }\n#jp-minimap:hover .jp-mm-l5 { padding-left: 52px; }\n#jp-minimap:hover .jp-mm-l6 { padding-left: 52px; }\n\n/* ---- shared states ---- */\n\n.jp-mm-item:hover {\n\topacity: 1;\n\tbackground: rgba(127, 127, 127, 0.22);\n}\n\n.jp-mm-active { opacity: 1; }\n\n#jp-minimap:hover .jp-mm-active {\n\tbackground: rgba(127, 127, 127, 0.18);\n}\n\n/* No scrollbar in the expanded panel: the wheel handler owns scrolling,\n * and a visible scrollbar at the panel edge invites overlay-scrollbar\n * style hover/click interference. */\n.jp-mm-list::-webkit-scrollbar { display: none; }\n.jp-mm-list { scrollbar-width: none; }\n\n/* don't show over printed/exported output */\n@media print {\n\t#jp-minimap { display: none; }\n}\n";
|
|
35
|
+
|
|
36
|
+
function ensureStyle() {
|
|
37
|
+
if (document.getElementById('jp-minimap-style')) return;
|
|
38
|
+
var styleEl = document.createElement('style');
|
|
39
|
+
styleEl.id = 'jp-minimap-style';
|
|
40
|
+
styleEl.textContent = MINIMAP_CSS;
|
|
41
|
+
(document.head || document.documentElement).appendChild(styleEl);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Clicking the minimap gives the webview focus, which can make Joplin
|
|
45
|
+
// re-render the whole note. That detaches every heading element we hold,
|
|
46
|
+
// and scrollIntoView on a detached node is a silent no-op. So jumps are
|
|
47
|
+
// index/text-based against the LIVE DOM, and if a rebuild happens right
|
|
48
|
+
// after a click (the re-render case), the jump is re-applied afterwards.
|
|
49
|
+
var pendingJump = null; // { index, text, until }
|
|
50
|
+
|
|
51
|
+
function liveHeadings() {
|
|
52
|
+
var root = document.getElementById('rendered-md') || document.body;
|
|
53
|
+
return Array.prototype.slice.call(root.querySelectorAll('h1, h2, h3, h4, h5, h6'));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function jumpTo(index, text) {
|
|
57
|
+
var hs = liveHeadings();
|
|
58
|
+
var h = hs[index];
|
|
59
|
+
if (!h || (text && (h.textContent || '').trim() !== text)) {
|
|
60
|
+
for (var i = 0; i < hs.length; i++) {
|
|
61
|
+
if ((hs[i].textContent || '').trim() === text) { h = hs[i]; break; }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (h && h.isConnected) h.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Width of the note viewer's right-edge scrollbar zone. Overlay scrollbars
|
|
68
|
+
// (e.g. Windows 11) reserve no layout space but still intercept clicks with
|
|
69
|
+
// priority over page content, so when nothing is measurable we keep a safety
|
|
70
|
+
// gap anyway. The minimap must stay clear of that zone or clicks on the
|
|
71
|
+
// rightmost part of the panel silently hit the scrollbar instead.
|
|
72
|
+
function scrollbarGap(root) {
|
|
73
|
+
var gap = 0;
|
|
74
|
+
var node = root;
|
|
75
|
+
while (node && node !== document.documentElement) {
|
|
76
|
+
if (node.scrollHeight > node.clientHeight + 1) {
|
|
77
|
+
var w = node.offsetWidth - node.clientWidth;
|
|
78
|
+
if (w > gap) gap = w;
|
|
79
|
+
}
|
|
80
|
+
node = node.parentElement;
|
|
81
|
+
}
|
|
82
|
+
return gap > 0 ? gap : 14;
|
|
83
|
+
}
|
|
84
|
+
|
|
28
85
|
function build() {
|
|
29
86
|
if (cleanup) { cleanup(); cleanup = null; }
|
|
87
|
+
ensureStyle();
|
|
30
88
|
|
|
31
89
|
var old = document.getElementById('jp-minimap');
|
|
32
90
|
if (old) old.remove();
|
|
@@ -39,12 +97,18 @@
|
|
|
39
97
|
|
|
40
98
|
var nav = document.createElement('nav');
|
|
41
99
|
nav.id = 'jp-minimap';
|
|
42
|
-
|
|
100
|
+
// Applied in BOTH collapsed and expanded states: shifting only on hover
|
|
101
|
+
// would move the panel out from under the cursor and cause a
|
|
102
|
+
// hover/unhover flicker loop.
|
|
103
|
+
nav.style.right = (settings.rightOffset + scrollbarGap(root)) + 'px';
|
|
43
104
|
nav.style.setProperty('--jp-mm-width', settings.panelWidth + 'px');
|
|
44
105
|
// The viewer DOM can be editable in some contexts; make sure the
|
|
45
106
|
// minimap never shows a caret or accepts keyboard input.
|
|
107
|
+
// NOTE: no preventDefault on mousedown! Blocking the default mouse-focus
|
|
108
|
+
// path makes Joplin's later programmatic focus count as keyboard-like,
|
|
109
|
+
// and the browser then draws a :focus-visible ring on the scroll
|
|
110
|
+
// container. Natural mouse focus never shows a ring.
|
|
46
111
|
nav.setAttribute('contenteditable', 'false');
|
|
47
|
-
nav.addEventListener('mousedown', function (e) { e.preventDefault(); });
|
|
48
112
|
// Own the wheel entirely while the cursor is over the minimap:
|
|
49
113
|
// scroll the ToC list ourselves and never let the event chain
|
|
50
114
|
// through to the note underneath (scroll chaining feels erratic).
|
|
@@ -56,7 +120,7 @@
|
|
|
56
120
|
var list = document.createElement('div');
|
|
57
121
|
list.className = 'jp-mm-list';
|
|
58
122
|
|
|
59
|
-
var items = headings.map(function (h) {
|
|
123
|
+
var items = headings.map(function (h, index) {
|
|
60
124
|
var level = Number(h.tagName.charAt(1));
|
|
61
125
|
|
|
62
126
|
// NOT an <a>: Joplin's viewer shows a "Ctrl+click to open" tooltip
|
|
@@ -74,8 +138,17 @@
|
|
|
74
138
|
item.appendChild(bar);
|
|
75
139
|
item.appendChild(label);
|
|
76
140
|
|
|
77
|
-
|
|
78
|
-
|
|
141
|
+
// MOUSEDOWN, not click: the press gives the webview focus, Joplin may
|
|
142
|
+
// re-render the note, and our panel gets rebuilt BETWEEN mousedown and
|
|
143
|
+
// mouseup - so the click event (which needs the same target for both)
|
|
144
|
+
// never fires. mousedown runs before any of that can happen.
|
|
145
|
+
item.addEventListener('mousedown', function (e) {
|
|
146
|
+
if (e.button !== 0) return;
|
|
147
|
+
var text = (label.textContent || '').trim();
|
|
148
|
+
// If the press triggers a note re-render, the rebuild will
|
|
149
|
+
// re-apply this jump against the fresh DOM.
|
|
150
|
+
pendingJump = { index: index, text: text, until: Date.now() + 1200 };
|
|
151
|
+
jumpTo(index, text);
|
|
79
152
|
});
|
|
80
153
|
|
|
81
154
|
list.appendChild(item);
|
|
@@ -98,6 +171,13 @@
|
|
|
98
171
|
document.addEventListener('scroll', updateActive, { passive: true, capture: true });
|
|
99
172
|
updateActive();
|
|
100
173
|
|
|
174
|
+
// A rebuild arriving right after a click means the note was re-rendered
|
|
175
|
+
// and the original scrollIntoView hit a detached node - redo the jump.
|
|
176
|
+
if (pendingJump && Date.now() < pendingJump.until) {
|
|
177
|
+
jumpTo(pendingJump.index, pendingJump.text);
|
|
178
|
+
pendingJump = null;
|
|
179
|
+
}
|
|
180
|
+
|
|
101
181
|
cleanup = function () {
|
|
102
182
|
document.removeEventListener('scroll', updateActive, { capture: true });
|
|
103
183
|
};
|
|
@@ -10,8 +10,10 @@ module.exports = {
|
|
|
10
10
|
// intentionally empty — rendering is untouched
|
|
11
11
|
},
|
|
12
12
|
assets: function () {
|
|
13
|
+
// CSS is injected by minimap-view.js itself (see ensureStyle) so
|
|
14
|
+
// that a re-render which drops asset stylesheets cannot leave an
|
|
15
|
+
// unstyled nav in the document flow.
|
|
13
16
|
return [
|
|
14
|
-
{ name: 'minimap.css' },
|
|
15
17
|
{ name: 'minimap-view.js' },
|
|
16
18
|
];
|
|
17
19
|
},
|
package/publish/plugin.jpl
CHANGED
|
Binary file
|
package/publish/minimap.css
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/* Joplin Minimap — collapsed tick bars, hover-expanded ToC panel.
|
|
2
|
-
* Colors use currentColor / rgba so it follows both light and dark themes.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
#jp-minimap {
|
|
6
|
-
user-select: none;
|
|
7
|
-
-webkit-user-select: none;
|
|
8
|
-
caret-color: transparent;
|
|
9
|
-
cursor: default;
|
|
10
|
-
position: fixed;
|
|
11
|
-
top: 50%;
|
|
12
|
-
right: 6px;
|
|
13
|
-
transform: translateY(-50%);
|
|
14
|
-
z-index: 9999;
|
|
15
|
-
font-size: 12.5px;
|
|
16
|
-
line-height: 1.35;
|
|
17
|
-
color: inherit;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
.jp-mm-list {
|
|
21
|
-
display: flex;
|
|
22
|
-
flex-direction: column;
|
|
23
|
-
align-items: flex-end;
|
|
24
|
-
padding: 8px 6px;
|
|
25
|
-
max-height: 84vh;
|
|
26
|
-
overflow: hidden;
|
|
27
|
-
border-radius: 10px;
|
|
28
|
-
transition: background 0.15s ease, box-shadow 0.15s ease;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
.jp-mm-item {
|
|
32
|
-
display: flex;
|
|
33
|
-
align-items: center;
|
|
34
|
-
justify-content: flex-end;
|
|
35
|
-
padding: 3px 4px;
|
|
36
|
-
border-radius: 6px;
|
|
37
|
-
text-decoration: none;
|
|
38
|
-
color: inherit;
|
|
39
|
-
opacity: 0.5;
|
|
40
|
-
cursor: pointer;
|
|
41
|
-
outline: none;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/* ---- collapsed state: tick bars, width by heading level ---- */
|
|
45
|
-
|
|
46
|
-
.jp-mm-bar {
|
|
47
|
-
display: block;
|
|
48
|
-
height: 2px;
|
|
49
|
-
border-radius: 1px;
|
|
50
|
-
background: currentColor;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
.jp-mm-l1 .jp-mm-bar { width: 18px; }
|
|
54
|
-
.jp-mm-l2 .jp-mm-bar { width: 13px; }
|
|
55
|
-
.jp-mm-l3 .jp-mm-bar { width: 9px; }
|
|
56
|
-
.jp-mm-l4 .jp-mm-bar { width: 7px; }
|
|
57
|
-
.jp-mm-l5 .jp-mm-bar { width: 5px; }
|
|
58
|
-
.jp-mm-l6 .jp-mm-bar { width: 5px; }
|
|
59
|
-
|
|
60
|
-
.jp-mm-label { display: none; }
|
|
61
|
-
|
|
62
|
-
/* ---- expanded state (hover) ---- */
|
|
63
|
-
|
|
64
|
-
#jp-minimap:hover .jp-mm-list {
|
|
65
|
-
align-items: stretch;
|
|
66
|
-
overflow-y: auto;
|
|
67
|
-
overscroll-behavior: contain;
|
|
68
|
-
background: rgba(127, 127, 127, 0.16);
|
|
69
|
-
backdrop-filter: blur(10px);
|
|
70
|
-
-webkit-backdrop-filter: blur(10px);
|
|
71
|
-
box-shadow: 0 6px 28px rgba(0, 0, 0, 0.28);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
#jp-minimap:hover .jp-mm-bar { display: none; }
|
|
75
|
-
|
|
76
|
-
#jp-minimap:hover .jp-mm-item { justify-content: flex-start; }
|
|
77
|
-
|
|
78
|
-
#jp-minimap:hover .jp-mm-label {
|
|
79
|
-
display: block;
|
|
80
|
-
max-width: var(--jp-mm-width, 240px);
|
|
81
|
-
white-space: nowrap;
|
|
82
|
-
overflow: hidden;
|
|
83
|
-
text-overflow: ellipsis;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/* indent by heading level when expanded */
|
|
87
|
-
#jp-minimap:hover .jp-mm-l2 { padding-left: 16px; }
|
|
88
|
-
#jp-minimap:hover .jp-mm-l3 { padding-left: 28px; }
|
|
89
|
-
#jp-minimap:hover .jp-mm-l4 { padding-left: 40px; }
|
|
90
|
-
#jp-minimap:hover .jp-mm-l5 { padding-left: 52px; }
|
|
91
|
-
#jp-minimap:hover .jp-mm-l6 { padding-left: 52px; }
|
|
92
|
-
|
|
93
|
-
/* ---- shared states ---- */
|
|
94
|
-
|
|
95
|
-
.jp-mm-item:hover {
|
|
96
|
-
opacity: 1;
|
|
97
|
-
background: rgba(127, 127, 127, 0.22);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.jp-mm-active { opacity: 1; }
|
|
101
|
-
|
|
102
|
-
#jp-minimap:hover .jp-mm-active {
|
|
103
|
-
background: rgba(127, 127, 127, 0.18);
|
|
104
|
-
}
|
|
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
|
-
}
|
|
112
|
-
|
|
113
|
-
/* don't show over printed/exported output */
|
|
114
|
-
@media print {
|
|
115
|
-
#jp-minimap { display: none; }
|
|
116
|
-
}
|