joplin-plugin-minimap 1.1.10 → 1.3.0
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/README.md +4 -0
- package/package.json +2 -2
- package/publish/index.js +24 -0
- package/publish/manifest.json +8 -3
- package/publish/minimap-view.js +193 -11
- package/publish/plugin.jpl +0 -0
package/README.md
CHANGED
|
@@ -7,7 +7,10 @@ A hover-to-expand table of contents minimap for the [Joplin](https://joplinapp.o
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- **Tick Bar Minimap** — Each heading is shown as a small horizontal bar on the right edge of the note viewer; bar length reflects the heading level (H1 longest)
|
|
10
|
+
- **Desktop and Mobile** — Hover on desktop, tap on touch devices: the collapsed strip is a single tap target, rows and buttons grow to finger size when open, and tapping outside or picking a heading closes it again
|
|
10
11
|
- **Hover to Expand** — Move the mouse over the bars and they expand into a full table of contents overlay, indented by level, with long titles ellipsized
|
|
12
|
+
- **Depth Stepper** — `+` / `-` buttons at the top of the expanded panel raise or lower how deep the outline goes, so a long nested note collapses to its top-level structure in one click
|
|
13
|
+
- **Heading Tiers** — Top-level headings render larger and heavier than nested ones, so the shape of the document is readable at a glance
|
|
11
14
|
- **Click to Jump** — Click any entry to smooth-scroll to that heading
|
|
12
15
|
- **Reading Position** — The section currently in view is highlighted automatically
|
|
13
16
|
- **Live Rebuild** — Rebuilds itself when you switch notes or edit content (listens to `joplin-noteDidUpdate` with a MutationObserver fallback)
|
|
@@ -52,6 +55,7 @@ Configurable in **Tools → Options → Joplin Minimap**:
|
|
|
52
55
|
- **Minimum headings** — hide the minimap when the note has fewer headings than this (default 2)
|
|
53
56
|
- **Expanded panel width (px)** — maximum width of the hover-expanded table of contents (default 240)
|
|
54
57
|
- **Minimap side** — which edge of the viewer the minimap docks to, right or left (default right)
|
|
58
|
+
- **Default depth** — deepest heading level shown when a note opens; the `+`/`-` buttons change it on the fly (default 6, every level)
|
|
55
59
|
- **Edge distance (px)** — gap between the minimap and the viewer edge it sits on (default 6)
|
|
56
60
|
|
|
57
61
|
Changed settings apply on the next render (switch notes or edit the note).
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "joplin-plugin-minimap",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "A table of contents minimap for the Joplin note viewer, on desktop and mobile",
|
|
5
5
|
"author": "lim0513",
|
|
6
6
|
"homepage": "https://github.com/lim0513/joplin-minimap",
|
|
7
7
|
"repository": {
|
package/publish/index.js
CHANGED
|
@@ -23,6 +23,8 @@ const SETTINGS_I18N = {
|
|
|
23
23
|
sideDesc: 'Which edge of the viewer the minimap sits on. Default: right.',
|
|
24
24
|
sideRight: 'Right',
|
|
25
25
|
sideLeft: 'Left',
|
|
26
|
+
maxLevel: 'Default depth',
|
|
27
|
+
maxLevelDesc: 'Deepest heading level shown when a note opens. The +/- buttons at the top of the expanded panel change it on the fly. Default: 6 (every level).',
|
|
26
28
|
showTodos: 'Show to-do markers',
|
|
27
29
|
showTodosDesc: 'Show a small red dot before a section\'s tick bar when it contains open to-dos. Default: on.',
|
|
28
30
|
},
|
|
@@ -37,6 +39,8 @@ const SETTINGS_I18N = {
|
|
|
37
39
|
sideDesc: '小地图停靠在阅读器的哪一侧边缘。默认靠右。',
|
|
38
40
|
sideRight: '右侧',
|
|
39
41
|
sideLeft: '左侧',
|
|
42
|
+
maxLevel: '默认层级深度',
|
|
43
|
+
maxLevelDesc: '打开笔记时显示到第几级标题。展开面板顶部的 +/- 按钮可随时调整。默认 6(全部层级)。',
|
|
40
44
|
showTodos: '显示待办标记',
|
|
41
45
|
showTodosDesc: '当章节内含未完成待办时,在该章节横线前显示一个小红点提醒。默认开启。',
|
|
42
46
|
},
|
|
@@ -51,6 +55,8 @@ const SETTINGS_I18N = {
|
|
|
51
55
|
sideDesc: '小地圖停靠在檢視器的哪一側邊緣。預設靠右。',
|
|
52
56
|
sideRight: '右側',
|
|
53
57
|
sideLeft: '左側',
|
|
58
|
+
maxLevel: '預設層級深度',
|
|
59
|
+
maxLevelDesc: '開啟筆記時顯示到第幾級標題。展開面板頂部的 +/- 按鈕可隨時調整。預設 6(全部層級)。',
|
|
54
60
|
showTodos: '顯示待辦標記',
|
|
55
61
|
showTodosDesc: '當章節內含未完成待辦時,在該章節橫線前顯示一個小紅點提醒。預設開啟。',
|
|
56
62
|
},
|
|
@@ -65,6 +71,8 @@ const SETTINGS_I18N = {
|
|
|
65
71
|
sideDesc: 'У какого края области просмотра располагается миникарта. По умолчанию: справа.',
|
|
66
72
|
sideRight: 'Справа',
|
|
67
73
|
sideLeft: 'Слева',
|
|
74
|
+
maxLevel: 'Глубина по умолчанию',
|
|
75
|
+
maxLevelDesc: 'До какого уровня заголовков показывать при открытии заметки. Кнопки +/- вверху развёрнутой панели меняют её на лету. По умолчанию: 6 (все уровни).',
|
|
68
76
|
showTodos: 'Показывать метки задач',
|
|
69
77
|
showTodosDesc: 'Показывать маленькую красную точку перед линией раздела, если в нём есть открытые задачи. По умолчанию: вкл.',
|
|
70
78
|
},
|
|
@@ -79,6 +87,8 @@ const SETTINGS_I18N = {
|
|
|
79
87
|
sideDesc: 'ミニマップをビューアーのどちら側の端に表示するか。既定値:右。',
|
|
80
88
|
sideRight: '右',
|
|
81
89
|
sideLeft: '左',
|
|
90
|
+
maxLevel: '既定の階層の深さ',
|
|
91
|
+
maxLevelDesc: 'ノートを開いたときに表示する見出しの深さ。展開パネル上部の +/- ボタンでいつでも変更できます。既定値:6(すべての階層)。',
|
|
82
92
|
showTodos: 'ToDoマーカーを表示',
|
|
83
93
|
showTodosDesc: '未完了のToDoを含むセクションの線の前に小さな赤い点を表示します。既定値:オン。',
|
|
84
94
|
},
|
|
@@ -147,6 +157,19 @@ joplin.plugins.register({
|
|
|
147
157
|
label: t.side,
|
|
148
158
|
description: t.sideDesc,
|
|
149
159
|
},
|
|
160
|
+
// Only the STARTING depth. The stepper in the panel overrides it for
|
|
161
|
+
// the rest of the session without writing back here - a setting that
|
|
162
|
+
// rewrote itself on every click would fight the user's own default.
|
|
163
|
+
'minimapMaxLevel': {
|
|
164
|
+
value: 6,
|
|
165
|
+
minimum: 1,
|
|
166
|
+
maximum: 6,
|
|
167
|
+
type: TYPE_INT,
|
|
168
|
+
section: 'minimap',
|
|
169
|
+
public: true,
|
|
170
|
+
label: t.maxLevel,
|
|
171
|
+
description: t.maxLevelDesc,
|
|
172
|
+
},
|
|
150
173
|
'minimapShowTodos': {
|
|
151
174
|
value: true,
|
|
152
175
|
type: TYPE_BOOL,
|
|
@@ -173,6 +196,7 @@ joplin.plugins.register({
|
|
|
173
196
|
panelWidth: await joplin.settings.value('minimapPanelWidth'),
|
|
174
197
|
rightOffset: await joplin.settings.value('minimapRightOffset'),
|
|
175
198
|
side: await joplin.settings.value('minimapSide'),
|
|
199
|
+
maxLevel: await joplin.settings.value('minimapMaxLevel'),
|
|
176
200
|
showTodos: await joplin.settings.value('minimapShowTodos'),
|
|
177
201
|
};
|
|
178
202
|
}
|
package/publish/manifest.json
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 1,
|
|
3
3
|
"id": "com.github.joplin-minimap",
|
|
4
|
-
"app_min_version": "2.8",
|
|
5
|
-
"
|
|
4
|
+
"app_min_version": "2.8",
|
|
5
|
+
"app_min_version_mobile": "3.2",
|
|
6
|
+
"platforms": [
|
|
7
|
+
"desktop",
|
|
8
|
+
"mobile"
|
|
9
|
+
],
|
|
10
|
+
"version": "1.3.0",
|
|
6
11
|
"name": "Joplin Minimap",
|
|
7
|
-
"description": "A
|
|
12
|
+
"description": "A table of contents minimap for the note viewer. Shows heading tick marks along one edge; hover (or tap on mobile) to expand into a full outline, click to jump.",
|
|
8
13
|
"author": "lim0513",
|
|
9
14
|
"homepage_url": "https://github.com/lim0513/joplin-minimap",
|
|
10
15
|
"repository_url": "https://github.com/lim0513/joplin-minimap",
|
package/publish/minimap-view.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
// Defaults; overridden from Joplin's plugin settings (Tools > Options > Minimap)
|
|
10
10
|
// when webviewApi is available in this webview.
|
|
11
|
-
var settings = { minHeadings: 2, panelWidth: 240, rightOffset: 6, side: 'right', showTodos: true };
|
|
11
|
+
var settings = { minHeadings: 2, panelWidth: 240, rightOffset: 6, side: 'right', maxLevel: 6, showTodos: true };
|
|
12
12
|
|
|
13
13
|
function loadSettings() {
|
|
14
14
|
if (typeof webviewApi === 'undefined' || !webviewApi.postMessage) {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
// then renders UNSTYLED as flow content below the note (looks like a
|
|
48
48
|
// duplicated outline under the document). Keeping the CSS inline and
|
|
49
49
|
// re-injecting guarantees the nav and its styling live and die together.
|
|
50
|
-
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\t/* Collapsed geometry is direction-independent: the tick bars always\n\t * hug the docked edge, whichever side that is, even in an RTL note. Per-item direction applies\n\t * to the expanded panel only (see the dir=rtl rule below). */\n\tdirection: ltr;\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\tdirection: ltr;\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/* ---- docked on the left edge ---- */\n\n/* Mirror the collapsed alignment so the tick bars hug the left border.\n * direction is pinned to ltr above, so flex-start is unambiguously the left\n * side whatever language the note is in. Placed BEFORE the :hover rules on\n * purpose: they carry the same specificity, so source order decides. */\n#jp-minimap.jp-mm-left .jp-mm-list { align-items: flex-start; }\n#jp-minimap.jp-mm-left .jp-mm-item { justify-content: flex-start; }\n\n/* The to-do dot mirrors too: docked right it hangs left of the tick bar,\n * docked left it must hang RIGHT of it - otherwise a dotted row pushes its\n * bar 9px inward (4px dot + 5px gap) and the flush bar column goes ragged.\n * order= reorders the flex row without touching the DOM, so the expanded\n * panel can put the dot back in front of the label. */\n#jp-minimap.jp-mm-left .jp-mm-dot {\n\torder: 1;\n\tmargin-inline-start: 5px;\n\tmargin-inline-end: 0;\n}\n\n/* ---- expanded state (hover) ---- */\n\n#jp-minimap
|
|
50
|
+
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\t/* Collapsed geometry is direction-independent: the tick bars always\n\t * hug the docked edge, whichever side that is, even in an RTL note. Per-item direction applies\n\t * to the expanded panel only (see the dir=rtl rule below). */\n\tdirection: ltr;\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\tdirection: ltr;\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/* ---- depth stepper (issue #2) ---- */\n\n/* Rows deeper than the current depth are HIDDEN, not removed: keeping the\n * nav element alive is what lets the panel stay open across a depth change,\n * since the whole expansion rides on :hover over #jp-minimap. */\n#jp-minimap[data-depth=\"1\"] .jp-mm-l2,\n#jp-minimap[data-depth=\"1\"] .jp-mm-l3,\n#jp-minimap[data-depth=\"1\"] .jp-mm-l4,\n#jp-minimap[data-depth=\"1\"] .jp-mm-l5,\n#jp-minimap[data-depth=\"1\"] .jp-mm-l6,\n#jp-minimap[data-depth=\"2\"] .jp-mm-l3,\n#jp-minimap[data-depth=\"2\"] .jp-mm-l4,\n#jp-minimap[data-depth=\"2\"] .jp-mm-l5,\n#jp-minimap[data-depth=\"2\"] .jp-mm-l6,\n#jp-minimap[data-depth=\"3\"] .jp-mm-l4,\n#jp-minimap[data-depth=\"3\"] .jp-mm-l5,\n#jp-minimap[data-depth=\"3\"] .jp-mm-l6,\n#jp-minimap[data-depth=\"4\"] .jp-mm-l5,\n#jp-minimap[data-depth=\"4\"] .jp-mm-l6,\n#jp-minimap[data-depth=\"5\"] .jp-mm-l6 { display: none; }\n\n/* The stepper is part of the expanded panel only - collapsed, the minimap\n * stays the same narrow strip of tick marks it has always been. */\n.jp-mm-head { display: none; }\n\n\n/* ---- docked on the left edge ---- */\n\n/* Mirror the collapsed alignment so the tick bars hug the left border.\n * direction is pinned to ltr above, so flex-start is unambiguously the left\n * side whatever language the note is in. Placed BEFORE the :hover rules on\n * purpose: they carry the same specificity, so source order decides. */\n#jp-minimap.jp-mm-left .jp-mm-list { align-items: flex-start; }\n#jp-minimap.jp-mm-left .jp-mm-item { justify-content: flex-start; }\n\n/* The to-do dot mirrors too: docked right it hangs left of the tick bar,\n * docked left it must hang RIGHT of it - otherwise a dotted row pushes its\n * bar 9px inward (4px dot + 5px gap) and the flush bar column goes ragged.\n * order= reorders the flex row without touching the DOM, so the expanded\n * panel can put the dot back in front of the label. */\n#jp-minimap.jp-mm-left .jp-mm-dot {\n\torder: 1;\n\tmargin-inline-start: 5px;\n\tmargin-inline-end: 0;\n}\n\n/* ---- expanded state (hover) ---- */\n\n#jp-minimap.jp-mm-open .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.jp-mm-open .jp-mm-bar { display: none; }\n\n#jp-minimap.jp-mm-open .jp-mm-item { justify-content: flex-start; }\n\n#jp-minimap.jp-mm-open .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\ttext-align: start;\n}\n\n/* RTL headings (Persian/Arabic/Hebrew) read right-to-left in the expanded\n * panel: justify-content, padding-inline-start and text-align:start all flip\n * with the row direction, so the label hugs the right edge and nested levels\n * indent inward from the right. build() sets dir per row (first strong char). */\n#jp-minimap.jp-mm-open .jp-mm-item[dir=\"rtl\"] { direction: rtl; }\n\n/* indent by heading level when expanded (logical: left in LTR, right in RTL) */\n#jp-minimap.jp-mm-open .jp-mm-l2 { padding-inline-start: 16px; }\n#jp-minimap.jp-mm-open .jp-mm-l3 { padding-inline-start: 28px; }\n#jp-minimap.jp-mm-open .jp-mm-l4 { padding-inline-start: 40px; }\n#jp-minimap.jp-mm-open .jp-mm-l5 { padding-inline-start: 52px; }\n#jp-minimap.jp-mm-open .jp-mm-l6 { padding-inline-start: 52px; }\n\n#jp-minimap.jp-mm-open .jp-mm-head {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 7px;\n\tpadding: 0 4px 5px;\n\tmargin-bottom: 4px;\n\tborder-bottom: 1px solid rgba(127, 127, 127, 0.3);\n\tfont-size: 11px;\n\topacity: 0.8;\n\t/* The control reads the same either way round, and the panel is pinned\n\t * to ltr anyway - keep it out of the per-row bidi logic entirely. */\n\tdirection: ltr;\n}\n\n.jp-mm-step {\n\tmin-width: 15px;\n\ttext-align: center;\n\tborder-radius: 4px;\n\tfont-weight: 700;\n\tcursor: pointer;\n\tbackground: rgba(127, 127, 127, 0.22);\n}\n\n#jp-minimap.jp-mm-open .jp-mm-step:hover { background: rgba(127, 127, 127, 0.45); }\n\n/* Already at the shallowest / deepest level this note has. */\n.jp-mm-step-off { opacity: 0.3; cursor: default; }\n#jp-minimap.jp-mm-open .jp-mm-step-off:hover { background: rgba(127, 127, 127, 0.22); }\n\n.jp-mm-depth { font-variant-numeric: tabular-nums; }\n\n/* ---- heading tiers (issue #2): indentation alone is a weak cue when\n * scanning a long outline. Size and weight only - NEVER a colour. The\n * panel has to stay legible on every Joplin theme, so the palette is\n * limited to currentColor and neutral rgba (see CLAUDE.md). ---- */\n#jp-minimap.jp-mm-open .jp-mm-r0 .jp-mm-label { font-size: 13.5px; font-weight: 600; }\n#jp-minimap.jp-mm-open .jp-mm-r1 .jp-mm-label { font-weight: 500; }\n#jp-minimap.jp-mm-open .jp-mm-r2 .jp-mm-label { font-size: 11.5px; opacity: 0.8; }\n\n\n/* Expanded, the dot reads as a marker BEFORE the title on either edge. */\n#jp-minimap.jp-mm-open.jp-mm-left .jp-mm-dot {\n\torder: 0;\n\tmargin-inline-start: 0;\n\tmargin-inline-end: 5px;\n}\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.jp-mm-open .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/* ---- open-to-do section dot: one muted red dot before the tick bar of\n * any section that contains at least one unchecked checkbox ---- */\n.jp-mm-dot {\n\tdisplay: none;\n\twidth: 4px;\n\theight: 4px;\n\tborder-radius: 50%;\n\tbackground: rgba(205, 97, 85, 0.85);\n\tmargin-inline-end: 5px;\n\tflex: none;\n}\n\n.jp-mm-dot.jp-mm-on { display: block; }\n\n/* don't show over printed/exported output */\n/* ---- touch devices ---- */\n\n/* (hover: none) is the device capability, not a screen width: a tablet with\n * a mouse keeps the desktop sizes, a touchscreen laptop gets these. */\n@media (hover: none) {\n\t/* Collapsed, the whole strip is the tap target - give it room. */\n\t.jp-mm-list { padding: 12px 14px; }\n\t.jp-mm-bar { height: 3px; }\n\n\t/* Expanded, each row has to be hittable on its own. */\n\t#jp-minimap.jp-mm-open .jp-mm-item { padding-top: 12px; padding-bottom: 12px; }\n\t#jp-minimap.jp-mm-open .jp-mm-label { font-size: 14px; }\n\t#jp-minimap.jp-mm-open .jp-mm-r0 .jp-mm-label { font-size: 15px; }\n\t#jp-minimap.jp-mm-open .jp-mm-r2 .jp-mm-label { font-size: 13px; }\n\n\t#jp-minimap.jp-mm-open .jp-mm-head { font-size: 13px; gap: 10px; padding-bottom: 8px; }\n\t.jp-mm-step { min-width: 32px; padding: 5px 0; }\n}\n@media print {\n\t#jp-minimap { display: none; }\n}\n";
|
|
51
51
|
|
|
52
52
|
function ensureStyle() {
|
|
53
53
|
if (document.getElementById('jp-minimap-style')) return;
|
|
@@ -89,6 +89,28 @@
|
|
|
89
89
|
// after a click (the re-render case), the jump is re-applied afterwards.
|
|
90
90
|
var pendingJump = null; // { index, text, until }
|
|
91
91
|
|
|
92
|
+
// Deepest heading level the panel shows. null = follow the setting; once the
|
|
93
|
+
// reader touches the stepper their choice sticks for the rest of the webview
|
|
94
|
+
// session. It CANNOT live on the DOM: build() rebuilds the whole nav on every
|
|
95
|
+
// note render, so anything held there is gone on the next keystroke.
|
|
96
|
+
var userDepth = null;
|
|
97
|
+
|
|
98
|
+
// Touch or mouse. This is a device CAPABILITY question, not a screen-size
|
|
99
|
+
// one - a tablet with a mouse should behave like the desktop. The expanded
|
|
100
|
+
// panel is driven by a class rather than :hover precisely because of this:
|
|
101
|
+
// on touch, :hover latches after a tap and the panel gets stuck open.
|
|
102
|
+
var isTouch = !(window.matchMedia && window.matchMedia('(hover: hover)').matches);
|
|
103
|
+
|
|
104
|
+
// Snap a wanted depth onto a level this note actually has (notes routinely
|
|
105
|
+
// start at H2 or skip a level).
|
|
106
|
+
function clampDepth(levels, want) {
|
|
107
|
+
var best = levels[0];
|
|
108
|
+
for (var i = 0; i < levels.length; i++) {
|
|
109
|
+
if (levels[i] <= want) best = levels[i];
|
|
110
|
+
}
|
|
111
|
+
return best;
|
|
112
|
+
}
|
|
113
|
+
|
|
92
114
|
function liveHeadings() {
|
|
93
115
|
var root = document.getElementById('rendered-md') || document.body;
|
|
94
116
|
return Array.prototype.slice.call(root.querySelectorAll('h1, h2, h3, h4, h5, h6'));
|
|
@@ -141,6 +163,16 @@
|
|
|
141
163
|
);
|
|
142
164
|
if (headings.length < settings.minHeadings) return;
|
|
143
165
|
|
|
166
|
+
// Distinct levels present, shallow to deep. The stepper walks THIS list
|
|
167
|
+
// rather than 1-6, so every click visibly changes something.
|
|
168
|
+
var levels = [];
|
|
169
|
+
for (var li = 0; li < headings.length; li++) {
|
|
170
|
+
var lvl = Number(headings[li].tagName.charAt(1));
|
|
171
|
+
if (levels.indexOf(lvl) < 0) levels.push(lvl);
|
|
172
|
+
}
|
|
173
|
+
levels.sort(function (a, b) { return a - b; });
|
|
174
|
+
var maxLevel = clampDepth(levels, userDepth === null ? settings.maxLevel : userDepth);
|
|
175
|
+
|
|
144
176
|
// Which sections contain at least one OPEN checkbox: walk headings and
|
|
145
177
|
// checkboxes in one document-ordered pass, attributing each unchecked
|
|
146
178
|
// box to the nearest preceding heading. One dot per section, however
|
|
@@ -158,6 +190,7 @@
|
|
|
158
190
|
}
|
|
159
191
|
}
|
|
160
192
|
|
|
193
|
+
|
|
161
194
|
var nav = document.createElement('nav');
|
|
162
195
|
nav.id = 'jp-minimap';
|
|
163
196
|
// Applied in BOTH collapsed and expanded states: shifting only on hover
|
|
@@ -172,7 +205,10 @@
|
|
|
172
205
|
nav.style.left = settings.rightOffset + 'px';
|
|
173
206
|
nav.style.right = 'auto';
|
|
174
207
|
} else {
|
|
175
|
-
|
|
208
|
+
// No scrollbar zone to dodge on touch: scrollbarGap() falls back to a
|
|
209
|
+
// 14px safety margin when it cannot measure one, which on a phone is
|
|
210
|
+
// just 14px of wasted inset.
|
|
211
|
+
nav.style.right = (settings.rightOffset + (isTouch ? 0 : scrollbarGap(root))) + 'px';
|
|
176
212
|
nav.style.left = 'auto';
|
|
177
213
|
}
|
|
178
214
|
nav.style.setProperty('--jp-mm-width', settings.panelWidth + 'px');
|
|
@@ -183,6 +219,7 @@
|
|
|
183
219
|
// and the browser then draws a :focus-visible ring on the scroll
|
|
184
220
|
// container. Natural mouse focus never shows a ring.
|
|
185
221
|
nav.setAttribute('contenteditable', 'false');
|
|
222
|
+
nav.setAttribute('data-depth', maxLevel);
|
|
186
223
|
// Own the wheel entirely while the cursor is over the minimap:
|
|
187
224
|
// scroll the ToC list ourselves and never let the event chain
|
|
188
225
|
// through to the note underneath (scroll chaining feels erratic).
|
|
@@ -191,16 +228,132 @@
|
|
|
191
228
|
list.scrollTop += e.deltaY;
|
|
192
229
|
}, { passive: false });
|
|
193
230
|
|
|
231
|
+
function setOpen(open) {
|
|
232
|
+
nav.classList.toggle('jp-mm-open', open);
|
|
233
|
+
// Drop the stepper's anchoring so the collapsed strip re-centres.
|
|
234
|
+
if (!open) { nav.style.top = ''; nav.style.transform = ''; }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
var closeOnOutside = null;
|
|
238
|
+
if (isTouch) {
|
|
239
|
+
// Collapsed, the whole strip is one tap target: an individual tick bar
|
|
240
|
+
// is 18x2px, which no finger can hit. Once open, the rows take over.
|
|
241
|
+
var touchAt = null;
|
|
242
|
+
nav.addEventListener('touchstart', function (e) {
|
|
243
|
+
var t = e.touches[0];
|
|
244
|
+
touchAt = t ? { x: t.clientX, y: t.clientY, at: Date.now() } : null;
|
|
245
|
+
}, { passive: true });
|
|
246
|
+
nav.addEventListener('touchend', function (e) {
|
|
247
|
+
if (nav.classList.contains('jp-mm-open')) return;
|
|
248
|
+
var t = e.changedTouches[0];
|
|
249
|
+
// A drag is the reader scrolling the note, not tapping the minimap.
|
|
250
|
+
if (!touchAt || !t) return;
|
|
251
|
+
if (Date.now() - touchAt.at > 700) return;
|
|
252
|
+
if (Math.abs(t.clientX - touchAt.x) > 10 || Math.abs(t.clientY - touchAt.y) > 10) return;
|
|
253
|
+
// Also suppresses the synthetic mousedown, so the tap that OPENS the
|
|
254
|
+
// panel cannot fall through to whatever row lands under the finger.
|
|
255
|
+
e.preventDefault();
|
|
256
|
+
setOpen(true);
|
|
257
|
+
});
|
|
258
|
+
// touchstart, not click: it beats the browser's synthetic-click delay,
|
|
259
|
+
// so the panel is already shut by the time the tap lands on the note.
|
|
260
|
+
closeOnOutside = function (e) {
|
|
261
|
+
if (!nav.contains(e.target)) setOpen(false);
|
|
262
|
+
};
|
|
263
|
+
document.addEventListener('touchstart', closeOnOutside, { passive: true });
|
|
264
|
+
} else {
|
|
265
|
+
nav.addEventListener('mouseenter', function () { setOpen(true); });
|
|
266
|
+
nav.addEventListener('mouseleave', function () { setOpen(false); });
|
|
267
|
+
}
|
|
268
|
+
|
|
194
269
|
var list = document.createElement('div');
|
|
195
270
|
list.className = 'jp-mm-list';
|
|
196
271
|
|
|
272
|
+
// Depth stepper. It lives INSIDE the nav on purpose: clicking it keeps the
|
|
273
|
+
// cursor over #jp-minimap, so the hover-expanded panel never collapses
|
|
274
|
+
// mid-interaction and no click-to-pin state has to be invented. Pointless
|
|
275
|
+
// when the note only has one heading level, so it is omitted entirely.
|
|
276
|
+
var head = null;
|
|
277
|
+
var minusBtn = null;
|
|
278
|
+
var plusBtn = null;
|
|
279
|
+
var depthText = null;
|
|
280
|
+
if (levels.length > 1) {
|
|
281
|
+
head = document.createElement('div');
|
|
282
|
+
head.className = 'jp-mm-head';
|
|
283
|
+
minusBtn = stepButton('-', -1);
|
|
284
|
+
depthText = document.createElement('span');
|
|
285
|
+
depthText.className = 'jp-mm-depth';
|
|
286
|
+
plusBtn = stepButton('+', 1);
|
|
287
|
+
head.appendChild(minusBtn);
|
|
288
|
+
head.appendChild(depthText);
|
|
289
|
+
head.appendChild(plusBtn);
|
|
290
|
+
list.appendChild(head);
|
|
291
|
+
renderHead();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function stepButton(sign, delta) {
|
|
295
|
+
var b = document.createElement('span');
|
|
296
|
+
b.className = 'jp-mm-step';
|
|
297
|
+
b.textContent = sign;
|
|
298
|
+
// mousedown for the same reason the rows use it (see below), and the
|
|
299
|
+
// event must not reach anything underneath: adjusting the depth should
|
|
300
|
+
// never scroll the note.
|
|
301
|
+
b.addEventListener('mousedown', function (e) {
|
|
302
|
+
if (e.button !== 0) return;
|
|
303
|
+
e.preventDefault();
|
|
304
|
+
e.stopPropagation();
|
|
305
|
+
setDepth(levels[levels.indexOf(maxLevel) + delta]);
|
|
306
|
+
});
|
|
307
|
+
return b;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function renderHead() {
|
|
311
|
+
if (!head) return;
|
|
312
|
+
var lo = levels[0];
|
|
313
|
+
depthText.textContent = lo === maxLevel ? ('H' + lo) : ('H' + lo + '-H' + maxLevel);
|
|
314
|
+
minusBtn.classList.toggle('jp-mm-step-off', maxLevel === lo);
|
|
315
|
+
plusBtn.classList.toggle('jp-mm-step-off', maxLevel === levels[levels.length - 1]);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function setDepth(next) {
|
|
319
|
+
if (next === undefined) return;
|
|
320
|
+
// The panel is vertically centred (top: 50% + translateY(-50%)), so
|
|
321
|
+
// hiding rows slides its top edge DOWN and the stepper crawls out from
|
|
322
|
+
// under the cursor - after a couple of clicks the pointer leaves the
|
|
323
|
+
// panel entirely and it collapses. Measure the header, apply the
|
|
324
|
+
// change, then pin the header back where it was. The inline top lasts
|
|
325
|
+
// only until the next build(), which re-centres from scratch.
|
|
326
|
+
var beforeY = head.getBoundingClientRect().top;
|
|
327
|
+
maxLevel = next;
|
|
328
|
+
userDepth = next;
|
|
329
|
+
nav.setAttribute('data-depth', maxLevel);
|
|
330
|
+
renderHead();
|
|
331
|
+
applyTodoDots();
|
|
332
|
+
var afterY = head.getBoundingClientRect().top;
|
|
333
|
+
if (afterY !== beforeY) {
|
|
334
|
+
var box = nav.getBoundingClientRect();
|
|
335
|
+
var limit = window.innerHeight - box.height - 4;
|
|
336
|
+
var top = box.top + (beforeY - afterY);
|
|
337
|
+
nav.style.top = Math.max(4, Math.min(top, limit > 4 ? limit : 4)) + 'px';
|
|
338
|
+
nav.style.transform = 'none';
|
|
339
|
+
}
|
|
340
|
+
updateActive();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// A row is built for EVERY heading; the stepper hides the deep ones with
|
|
344
|
+
// CSS rather than rebuilding, so the nav element - and with it the :hover
|
|
345
|
+
// state holding the panel open - survives a depth change untouched.
|
|
197
346
|
var items = headings.map(function (h, index) {
|
|
198
347
|
var level = Number(h.tagName.charAt(1));
|
|
348
|
+
// Size/weight tier keys off the RANK among the levels this note uses,
|
|
349
|
+
// not the absolute level: a note that starts at H2 still gets a proper
|
|
350
|
+
// top tier instead of looking uniformly like sub-headings.
|
|
351
|
+
var rank = Math.min(levels.indexOf(level), 2);
|
|
199
352
|
|
|
200
353
|
// NOT an <a>: Joplin's viewer shows a "Ctrl+click to open" tooltip
|
|
201
354
|
// on anchors and treats them as external links.
|
|
202
355
|
var item = document.createElement('div');
|
|
203
|
-
item.className = 'jp-mm-item jp-mm-l' + level;
|
|
356
|
+
item.className = 'jp-mm-item jp-mm-l' + level + ' jp-mm-r' + rank;
|
|
204
357
|
|
|
205
358
|
var bar = document.createElement('span');
|
|
206
359
|
bar.className = 'jp-mm-bar';
|
|
@@ -216,12 +369,12 @@
|
|
|
216
369
|
item.setAttribute('dir', textDirection(label.textContent, h));
|
|
217
370
|
|
|
218
371
|
// Muted red reminder dot before the bar (and before the label when
|
|
219
|
-
// expanded - same element, flex order does the work).
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
372
|
+
// expanded - same element, flex order does the work). Built for every
|
|
373
|
+
// row and shown with a class, because WHICH row carries a dot depends
|
|
374
|
+
// on the current depth and the stepper changes that without rebuilding.
|
|
375
|
+
var dot = document.createElement('span');
|
|
376
|
+
dot.className = 'jp-mm-dot';
|
|
377
|
+
item.appendChild(dot);
|
|
225
378
|
item.appendChild(bar);
|
|
226
379
|
item.appendChild(label);
|
|
227
380
|
|
|
@@ -236,6 +389,9 @@
|
|
|
236
389
|
// re-apply this jump against the fresh DOM.
|
|
237
390
|
pendingJump = { index: index, text: text, until: Date.now() + 1200 };
|
|
238
391
|
jumpTo(index, text);
|
|
392
|
+
// On touch there is no cursor to move away, so the panel would sit
|
|
393
|
+
// over the heading the reader just asked to see.
|
|
394
|
+
if (isTouch) setOpen(false);
|
|
239
395
|
});
|
|
240
396
|
|
|
241
397
|
list.appendChild(item);
|
|
@@ -245,13 +401,38 @@
|
|
|
245
401
|
nav.appendChild(list);
|
|
246
402
|
document.body.appendChild(nav);
|
|
247
403
|
|
|
404
|
+
// Open to-dos in a section the stepper has collapsed away roll up to the
|
|
405
|
+
// nearest visible ancestor. Without this, lowering the depth silently
|
|
406
|
+
// hides the only signal that a branch still holds unfinished work - which
|
|
407
|
+
// is worse than having no dots at all, because the panel looks clean.
|
|
408
|
+
function applyTodoDots() {
|
|
409
|
+
var owner = {};
|
|
410
|
+
if (settings.showTodos !== false) {
|
|
411
|
+
var visible = -1;
|
|
412
|
+
for (var i = 0; i < headings.length; i++) {
|
|
413
|
+
if (Number(headings[i].tagName.charAt(1)) <= maxLevel) visible = i;
|
|
414
|
+
if (sectionHasTodo[i] && visible >= 0) owner[visible] = true;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
for (var j = 0; j < items.length; j++) {
|
|
418
|
+
items[j].firstChild.classList.toggle('jp-mm-on', owner[j] === true);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
applyTodoDots();
|
|
422
|
+
|
|
248
423
|
function updateActive() {
|
|
249
424
|
var activeIndex = 0;
|
|
250
425
|
for (var i = 0; i < headings.length; i++) {
|
|
251
426
|
if (headings[i].getBoundingClientRect().top <= 90) activeIndex = i;
|
|
252
427
|
}
|
|
428
|
+
// A heading hidden by the stepper highlights its nearest VISIBLE
|
|
429
|
+
// ancestor instead, so the panel always shows where you are.
|
|
430
|
+
var activeRow = 0;
|
|
431
|
+
for (var k = 0; k <= activeIndex; k++) {
|
|
432
|
+
if (Number(headings[k].tagName.charAt(1)) <= maxLevel) activeRow = k;
|
|
433
|
+
}
|
|
253
434
|
for (var j = 0; j < items.length; j++) {
|
|
254
|
-
items[j].classList.toggle('jp-mm-active', j ===
|
|
435
|
+
items[j].classList.toggle('jp-mm-active', j === activeRow);
|
|
255
436
|
}
|
|
256
437
|
}
|
|
257
438
|
|
|
@@ -267,6 +448,7 @@
|
|
|
267
448
|
|
|
268
449
|
cleanup = function () {
|
|
269
450
|
document.removeEventListener('scroll', updateActive, { capture: true });
|
|
451
|
+
if (closeOnOutside) document.removeEventListener('touchstart', closeOnOutside);
|
|
270
452
|
};
|
|
271
453
|
}
|
|
272
454
|
|
package/publish/plugin.jpl
CHANGED
|
Binary file
|