joplin-plugin-minimap 1.4.0 → 1.5.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 +3 -1
- package/package.json +17 -17
- package/publish/index.js +39 -0
- package/publish/manifest.json +6 -6
- package/publish/minimap-view.js +165 -13
- package/publish/plugin.jpl +0 -0
package/README.md
CHANGED
|
@@ -10,11 +10,12 @@ A hover-to-expand table of contents minimap for the [Joplin](https://joplinapp.o
|
|
|
10
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
|
|
11
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
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
|
+
- **Pin as a sidebar** — the pin button at the top of the expanded panel keeps the outline open as a permanent sidebar (Obsidian-style) and moves the note text aside so it is never covered; the active heading stays in view as you read. Also available as a setting
|
|
13
14
|
- **Heading Tiers** — Top-level headings render larger and heavier than nested ones, so the shape of the document is readable at a glance
|
|
14
15
|
- **Click to Jump** — Click any entry to smooth-scroll to that heading
|
|
15
16
|
- **Reading Position** — The section currently in view is highlighted automatically
|
|
16
17
|
- **Live Rebuild** — Rebuilds itself when you switch notes or edit content (listens to `joplin-noteDidUpdate` with a MutationObserver fallback)
|
|
17
|
-
- **RTL Aware** — Headings in right-to-left scripts (Persian, Arabic, Hebrew) are right-aligned in the expanded panel and indent inward from the right; direction is detected per heading, so mixed notes stay readable
|
|
18
|
+
- **RTL Aware** — Headings in right-to-left scripts (Persian, Arabic, Hebrew) are right-aligned in the expanded panel and indent inward from the right; direction is detected per heading, so mixed notes stay readable
|
|
18
19
|
- **Theme Aware** — Follows light and dark themes automatically (colors derive from `currentColor`)
|
|
19
20
|
- **Unobtrusive** — Hidden for notes with fewer than 2 headings, hidden when printing/exporting, never steals focus or keyboard input
|
|
20
21
|
|
|
@@ -56,6 +57,7 @@ Configurable in **Tools → Options → Joplin Minimap**:
|
|
|
56
57
|
- **Expanded panel width (px)** — maximum width of the hover-expanded table of contents (default 240)
|
|
57
58
|
- **Minimap side** — which edge of the viewer the minimap docks to, right or left (default right)
|
|
58
59
|
- **Default depth** — deepest heading level shown when a note opens; the `+`/`-` buttons change it on the fly (default 6, every level)
|
|
60
|
+
- **Keep outline open (pinned)** — show the outline as a permanent sidebar instead of on hover; the pin button in the panel toggles the same setting (default off)
|
|
59
61
|
- **Edge distance (px)** — gap between the minimap and the viewer edge it sits on (default 6)
|
|
60
62
|
- **Text size (%)** — scales the text in the expanded panel; raise it on high-DPI screens (default 100, under *Advanced*)
|
|
61
63
|
- **High contrast panel** — brighter text and a more solid panel background, using no fixed colours so it still follows your theme (default off, under *Advanced*)
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "joplin-plugin-minimap",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A table of contents minimap for the Joplin note viewer, on desktop and mobile",
|
|
5
|
-
"author": "lim0513",
|
|
6
|
-
"homepage": "https://github.com/lim0513/joplin-minimap",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "https://github.com/lim0513/joplin-minimap.git"
|
|
10
|
-
},
|
|
11
|
-
"scripts": {
|
|
12
|
-
"dist": "node build.js && node scripts/pack-jpl.js"
|
|
13
|
-
},
|
|
14
|
-
"keywords": ["joplin-plugin", "joplin", "toc", "outline", "minimap", "table-of-contents", "viewer"],
|
|
15
|
-
"license": "MIT",
|
|
16
|
-
"files": ["publish"]
|
|
17
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "joplin-plugin-minimap",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "A table of contents minimap for the Joplin note viewer, on desktop and mobile",
|
|
5
|
+
"author": "lim0513",
|
|
6
|
+
"homepage": "https://github.com/lim0513/joplin-minimap",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/lim0513/joplin-minimap.git"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"dist": "node build.js && node scripts/pack-jpl.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": ["joplin-plugin", "joplin", "toc", "outline", "minimap", "table-of-contents", "viewer"],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"files": ["publish"]
|
|
17
|
+
}
|
package/publish/index.js
CHANGED
|
@@ -31,6 +31,10 @@ const SETTINGS_I18N = {
|
|
|
31
31
|
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).',
|
|
32
32
|
showTodos: 'Show to-do markers',
|
|
33
33
|
showTodosDesc: 'Show a small red dot before a section\'s tick bar when it contains open to-dos. Default: on.',
|
|
34
|
+
pinned: 'Keep outline open (pinned)',
|
|
35
|
+
pinnedDesc: 'Show the expanded outline as a permanent sidebar instead of opening it on hover, and move the note text aside so the outline never covers it. The pin button at the top of the panel toggles this too. Default: off.',
|
|
36
|
+
pinTip: 'Pin the outline open',
|
|
37
|
+
unpinTip: 'Unpin (open on hover)',
|
|
34
38
|
},
|
|
35
39
|
zh_CN: {
|
|
36
40
|
minHeadings: '最少标题数',
|
|
@@ -51,6 +55,10 @@ const SETTINGS_I18N = {
|
|
|
51
55
|
maxLevelDesc: '打开笔记时显示到第几级标题。展开面板顶部的 +/- 按钮可随时调整。默认 6(全部层级)。',
|
|
52
56
|
showTodos: '显示待办标记',
|
|
53
57
|
showTodosDesc: '当章节内含未完成待办时,在该章节横线前显示一个小红点提醒。默认开启。',
|
|
58
|
+
pinned: '常驻展开(钉住)',
|
|
59
|
+
pinnedDesc: '把展开的目录作为常驻侧栏显示,而不是悬停时才展开,并把正文让开,避免目录遮住文字。面板顶部的钉子按钮也可以切换。默认关闭。',
|
|
60
|
+
pinTip: '钉住目录',
|
|
61
|
+
unpinTip: '取消钉住(悬停展开)',
|
|
54
62
|
},
|
|
55
63
|
zh_TW: {
|
|
56
64
|
minHeadings: '最少標題數',
|
|
@@ -71,6 +79,10 @@ const SETTINGS_I18N = {
|
|
|
71
79
|
maxLevelDesc: '開啟筆記時顯示到第幾級標題。展開面板頂部的 +/- 按鈕可隨時調整。預設 6(全部層級)。',
|
|
72
80
|
showTodos: '顯示待辦標記',
|
|
73
81
|
showTodosDesc: '當章節內含未完成待辦時,在該章節橫線前顯示一個小紅點提醒。預設開啟。',
|
|
82
|
+
pinned: '常駐展開(釘住)',
|
|
83
|
+
pinnedDesc: '把展開的目錄作為常駐側欄顯示,而不是懸停時才展開,並把內文讓開,避免目錄遮住文字。面板頂部的釘子按鈕也可以切換。預設關閉。',
|
|
84
|
+
pinTip: '釘住目錄',
|
|
85
|
+
unpinTip: '取消釘住(懸停展開)',
|
|
74
86
|
},
|
|
75
87
|
ru: {
|
|
76
88
|
minHeadings: 'Минимум заголовков',
|
|
@@ -91,6 +103,10 @@ const SETTINGS_I18N = {
|
|
|
91
103
|
maxLevelDesc: 'До какого уровня заголовков показывать при открытии заметки. Кнопки +/- вверху развёрнутой панели меняют её на лету. По умолчанию: 6 (все уровни).',
|
|
92
104
|
showTodos: 'Показывать метки задач',
|
|
93
105
|
showTodosDesc: 'Показывать маленькую красную точку перед линией раздела, если в нём есть открытые задачи. По умолчанию: вкл.',
|
|
106
|
+
pinned: 'Закрепить оглавление',
|
|
107
|
+
pinnedDesc: 'Показывать развёрнутое оглавление как постоянную боковую панель вместо раскрытия при наведении и сдвигать текст заметки, чтобы панель его не закрывала. Кнопка-булавка вверху панели тоже переключает режим. По умолчанию: выкл.',
|
|
108
|
+
pinTip: 'Закрепить оглавление',
|
|
109
|
+
unpinTip: 'Открепить (раскрывать при наведении)',
|
|
94
110
|
},
|
|
95
111
|
ja_JP: {
|
|
96
112
|
minHeadings: '最小見出し数',
|
|
@@ -111,6 +127,10 @@ const SETTINGS_I18N = {
|
|
|
111
127
|
maxLevelDesc: 'ノートを開いたときに表示する見出しの深さ。展開パネル上部の +/- ボタンでいつでも変更できます。既定値:6(すべての階層)。',
|
|
112
128
|
showTodos: 'ToDoマーカーを表示',
|
|
113
129
|
showTodosDesc: '未完了のToDoを含むセクションの線の前に小さな赤い点を表示します。既定値:オン。',
|
|
130
|
+
pinned: '目次を常時表示(ピン留め)',
|
|
131
|
+
pinnedDesc: '展開した目次をホバー時だけでなく常設のサイドバーとして表示し、本文を横にずらして目次が文字に重ならないようにします。パネル上部のピンボタンでも切り替えられます。既定値:オフ。',
|
|
132
|
+
pinTip: '目次をピン留め',
|
|
133
|
+
unpinTip: 'ピン留めを解除(ホバーで展開)',
|
|
114
134
|
},
|
|
115
135
|
};
|
|
116
136
|
|
|
@@ -213,6 +233,16 @@ joplin.plugins.register({
|
|
|
213
233
|
label: t.highContrast,
|
|
214
234
|
description: t.highContrastDesc,
|
|
215
235
|
},
|
|
236
|
+
// Also written by the pin button in the panel (see onMessage below), so
|
|
237
|
+
// the choice survives note switches and restarts.
|
|
238
|
+
'minimapPinned': {
|
|
239
|
+
value: false,
|
|
240
|
+
type: TYPE_BOOL,
|
|
241
|
+
section: 'minimap',
|
|
242
|
+
public: true,
|
|
243
|
+
label: t.pinned,
|
|
244
|
+
description: t.pinnedDesc,
|
|
245
|
+
},
|
|
216
246
|
'minimapShowTodos': {
|
|
217
247
|
value: true,
|
|
218
248
|
type: TYPE_BOOL,
|
|
@@ -243,8 +273,17 @@ joplin.plugins.register({
|
|
|
243
273
|
fontScale: await joplin.settings.value('minimapFontScale'),
|
|
244
274
|
highContrast: await joplin.settings.value('minimapHighContrast'),
|
|
245
275
|
showTodos: await joplin.settings.value('minimapShowTodos'),
|
|
276
|
+
pinned: await joplin.settings.value('minimapPinned'),
|
|
277
|
+
pinTip: t.pinTip,
|
|
278
|
+
unpinTip: t.unpinTip,
|
|
246
279
|
};
|
|
247
280
|
}
|
|
281
|
+
// The pin button in the viewer. Written back so every note - and the
|
|
282
|
+
// next session - opens in the same mode.
|
|
283
|
+
if (message && message.type === 'setPinned') {
|
|
284
|
+
await joplin.settings.setValue('minimapPinned', message.value === true);
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
248
287
|
return null;
|
|
249
288
|
});
|
|
250
289
|
},
|
package/publish/manifest.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 1,
|
|
3
3
|
"id": "com.github.joplin-minimap",
|
|
4
|
-
"app_min_version": "2.8",
|
|
5
|
-
"app_min_version_mobile": "3.2",
|
|
6
|
-
"platforms": [
|
|
7
|
-
"desktop",
|
|
8
|
-
"mobile"
|
|
4
|
+
"app_min_version": "2.8",
|
|
5
|
+
"app_min_version_mobile": "3.2",
|
|
6
|
+
"platforms": [
|
|
7
|
+
"desktop",
|
|
8
|
+
"mobile"
|
|
9
9
|
],
|
|
10
|
-
"version": "1.
|
|
10
|
+
"version": "1.5.0",
|
|
11
11
|
"name": "Joplin Minimap",
|
|
12
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.",
|
|
13
13
|
"author": "lim0513",
|
package/publish/minimap-view.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* Joplin Minimap - runs inside the rendered note viewer.
|
|
2
2
|
* Collects h1-h6 from the rendered DOM, draws collapsed tick bars on the
|
|
3
|
-
* right edge, expands into a full ToC on hover
|
|
3
|
+
* right edge, expands into a full ToC on hover (or stays open as a pinned
|
|
4
|
+
* sidebar), and jumps on click.
|
|
4
5
|
* Rebuilds itself whenever the note content changes (note switch, edit).
|
|
5
6
|
*/
|
|
6
7
|
(function () {
|
|
@@ -8,14 +9,24 @@
|
|
|
8
9
|
|
|
9
10
|
// Defaults; overridden from Joplin's plugin settings (Tools > Options > Minimap)
|
|
10
11
|
// when webviewApi is available in this webview.
|
|
11
|
-
var settings = { minHeadings: 2, panelWidth: 240, rightOffset: 6, side: 'right', maxLevel: 6, fontScale: 100, highContrast: false, showTodos: true };
|
|
12
|
+
var settings = { minHeadings: 2, panelWidth: 240, rightOffset: 6, side: 'right', maxLevel: 6, fontScale: 100, highContrast: false, showTodos: true, pinned: false, pinTip: 'Pin the outline open', unpinTip: 'Unpin (open on hover)' };
|
|
13
|
+
|
|
14
|
+
// Pin state set from the panel's own button. The write-back to the plugin
|
|
15
|
+
// setting is async, so a settings fetch already in flight can still return
|
|
16
|
+
// the OLD value and flip the panel back. Hold the local choice until a fetch
|
|
17
|
+
// agrees with it; after that the setting is authoritative again, so a change
|
|
18
|
+
// made on the options screen still takes effect.
|
|
19
|
+
var pinOverride = null;
|
|
12
20
|
|
|
13
21
|
function loadSettings() {
|
|
14
22
|
if (typeof webviewApi === 'undefined' || !webviewApi.postMessage) {
|
|
15
23
|
return Promise.resolve();
|
|
16
24
|
}
|
|
17
25
|
return webviewApi.postMessage('joplin-minimap', 'getSettings').then(function (s) {
|
|
18
|
-
if (s && typeof s.minHeadings === 'number')
|
|
26
|
+
if (s && typeof s.minHeadings === 'number') {
|
|
27
|
+
settings = s;
|
|
28
|
+
if (pinOverride !== null && (s.pinned === true) === pinOverride) pinOverride = null;
|
|
29
|
+
}
|
|
19
30
|
}).catch(function () { /* keep defaults (e.g. print/export context) */ });
|
|
20
31
|
}
|
|
21
32
|
|
|
@@ -47,7 +58,7 @@
|
|
|
47
58
|
// then renders UNSTYLED as flow content below the note (looks like a
|
|
48
59
|
// duplicated outline under the document). Keeping the CSS inline and
|
|
49
60
|
// 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: var(--jp-mm-font, 12.5px);\n\tline-height: 1.35;\n\tcolor: var(--jp-mm-color, 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: var(--jp-mm-dim, 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: var(--jp-mm-bg, 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: 0.88em;\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: 1.08em; 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: 0.92em; 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/* ---- high contrast (issue #5) ---- */\n\n/* Still routed through the same variables, so a userstyle.css override wins\n * over the mode rather than fighting it. */\n#jp-minimap.jp-mm-hc .jp-mm-item { opacity: var(--jp-mm-dim, 0.82); }\n#jp-minimap.jp-mm-hc.jp-mm-open .jp-mm-r2 .jp-mm-label { opacity: 0.95; }\n\n#jp-minimap.jp-mm-hc.jp-mm-open .jp-mm-list {\n\tbackground: var(--jp-mm-bg, rgba(127, 127, 127, 0.38));\n\tbox-shadow: 0 6px 28px rgba(0, 0, 0, 0.45);\n}\n\n#jp-minimap.jp-mm-hc.jp-mm-open .jp-mm-head { opacity: 1; }\n#jp-minimap.jp-mm-hc .jp-mm-step { background: rgba(127, 127, 127, 0.4); }\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: var(--jp-mm-dot, 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: 1.12em; }\n\t#jp-minimap.jp-mm-open .jp-mm-r0 .jp-mm-label { font-size: 1.2em; }\n\t#jp-minimap.jp-mm-open .jp-mm-r2 .jp-mm-label { font-size: 1.04em; }\n\n\t#jp-minimap.jp-mm-open .jp-mm-head { font-size: 1.04em; 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";
|
|
61
|
+
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: var(--jp-mm-font, 12.5px);\n\tline-height: 1.35;\n\tcolor: var(--jp-mm-color, 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: var(--jp-mm-dim, 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: var(--jp-mm-bg, 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: 0.88em;\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: 1.08em; 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: 0.92em; 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/* ---- high contrast (issue #5) ---- */\n\n/* Still routed through the same variables, so a userstyle.css override wins\n * over the mode rather than fighting it. */\n#jp-minimap.jp-mm-hc .jp-mm-item { opacity: var(--jp-mm-dim, 0.82); }\n#jp-minimap.jp-mm-hc.jp-mm-open .jp-mm-r2 .jp-mm-label { opacity: 0.95; }\n\n#jp-minimap.jp-mm-hc.jp-mm-open .jp-mm-list {\n\tbackground: var(--jp-mm-bg, rgba(127, 127, 127, 0.38));\n\tbox-shadow: 0 6px 28px rgba(0, 0, 0, 0.45);\n}\n\n#jp-minimap.jp-mm-hc.jp-mm-open .jp-mm-head { opacity: 1; }\n#jp-minimap.jp-mm-hc .jp-mm-step { background: rgba(127, 127, 127, 0.4); }\n\n/* ---- pinned (issue #6): a permanent sidebar instead of a hover panel.\n * Top-anchored rather than centred, like an outline pane: centring would\n * move the whole panel every time the depth stepper changes its height. ---- */\n#jp-minimap.jp-mm-pinned {\n\ttop: 12px;\n\ttransform: none;\n}\n\n#jp-minimap.jp-mm-pinned .jp-mm-list {\n\twidth: var(--jp-mm-width, 240px);\n\tbox-sizing: border-box;\n\tmax-height: calc(100vh - 24px);\n\toverflow-x: hidden;\n\tbox-shadow: 0 2px 12px rgba(0, 0, 0, 0.14);\n}\n\n/* A fixed width instead of hugging the longest title: the note text is\n * moved aside by exactly this much, so the width must not change with the\n * depth stepper. Long titles ellipsize inside it. */\n#jp-minimap.jp-mm-pinned .jp-mm-label {\n\tmax-width: none;\n\tmin-width: 0;\n\tflex: 1 1 auto;\n}\n\n/* The header always exists (it carries the pin), with or without a stepper.\n * Side padding keeps the centred stepper clear of the pin in a narrow panel. */\n#jp-minimap.jp-mm-open .jp-mm-head {\n\tposition: relative;\n\tmin-height: 16px;\n\tpadding-left: 22px;\n\tpadding-right: 22px;\n}\n\n.jp-mm-pin {\n\tposition: absolute;\n\tright: 0;\n\ttop: 0;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 18px;\n\theight: 16px;\n\tborder-radius: 4px;\n\tcursor: pointer;\n\topacity: 0.55;\n}\n\n#jp-minimap.jp-mm-open .jp-mm-pin:hover { opacity: 1; background: rgba(127, 127, 127, 0.3); }\n#jp-minimap.jp-mm-pinned .jp-mm-pin { opacity: 1; background: rgba(127, 127, 127, 0.25); }\n.jp-mm-pin svg { display: block; }\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: var(--jp-mm-dot, 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: 1.12em; }\n\t#jp-minimap.jp-mm-open .jp-mm-r0 .jp-mm-label { font-size: 1.2em; }\n\t#jp-minimap.jp-mm-open .jp-mm-r2 .jp-mm-label { font-size: 1.04em; }\n\n\t#jp-minimap.jp-mm-open .jp-mm-head { font-size: 1.04em; 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\t:root { --jp-mm-reserve: 0px !important; }\n}\n";
|
|
51
62
|
|
|
52
63
|
function ensureStyle() {
|
|
53
64
|
if (document.getElementById('jp-minimap-style')) return;
|
|
@@ -116,7 +127,7 @@
|
|
|
116
127
|
return Array.prototype.slice.call(root.querySelectorAll('h1, h2, h3, h4, h5, h6'));
|
|
117
128
|
}
|
|
118
129
|
|
|
119
|
-
function
|
|
130
|
+
function findHeading(index, text) {
|
|
120
131
|
var hs = liveHeadings();
|
|
121
132
|
var h = hs[index];
|
|
122
133
|
if (!h || (text && (h.textContent || '').trim() !== text)) {
|
|
@@ -124,7 +135,74 @@
|
|
|
124
135
|
if ((hs[i].textContent || '').trim() === text) { h = hs[i]; break; }
|
|
125
136
|
}
|
|
126
137
|
}
|
|
127
|
-
|
|
138
|
+
return h;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// A smooth scrollIntoView is fire-and-forget: the browser animates towards
|
|
142
|
+
// an offset worked out when it STARTS, and plenty can cut it short or make
|
|
143
|
+
// it land wrong - the reader nudging the wheel, Joplin re-rendering the note
|
|
144
|
+
// underneath, split view writing scrollTop of its own, or images above the
|
|
145
|
+
// target finishing layout and shifting it. The jump then stops somewhere in
|
|
146
|
+
// between, which is the "clicked H1 but only went up to 4.5" report.
|
|
147
|
+
// Some environments ignore behavior:'smooth' altogether (headless Chromium
|
|
148
|
+
// does, measured) and the jump never moves at all.
|
|
149
|
+
//
|
|
150
|
+
// So the animation is started for the feel, then the heading is watched
|
|
151
|
+
// until it stops moving and an INSTANT scroll closes whatever gap is left.
|
|
152
|
+
// The instant call uses the same alignment, so when the smooth scroll did
|
|
153
|
+
// land correctly it is a no-op.
|
|
154
|
+
var jumpWatch = null;
|
|
155
|
+
var jumpRelease = null;
|
|
156
|
+
|
|
157
|
+
function stopJumpWatch() {
|
|
158
|
+
if (jumpWatch) { clearInterval(jumpWatch); jumpWatch = null; }
|
|
159
|
+
if (jumpRelease) { jumpRelease(); jumpRelease = null; }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function watchJump(index, text) {
|
|
163
|
+
stopJumpWatch();
|
|
164
|
+
|
|
165
|
+
// The reader always wins: if they scroll themselves we are no longer
|
|
166
|
+
// correcting a broken jump, we are fighting them.
|
|
167
|
+
var takenOver = false;
|
|
168
|
+
function onUserScroll(e) {
|
|
169
|
+
// Wheeling the ToC list itself is not the reader taking over the note -
|
|
170
|
+
// the panel has its own wheel handler, and clicking a row then scrolling
|
|
171
|
+
// the list to pick another is a normal sequence.
|
|
172
|
+
var t = e && e.target;
|
|
173
|
+
if (t && t.closest && t.closest('#jp-minimap')) return;
|
|
174
|
+
takenOver = true;
|
|
175
|
+
}
|
|
176
|
+
document.addEventListener('wheel', onUserScroll, { passive: true, capture: true });
|
|
177
|
+
document.addEventListener('touchstart', onUserScroll, { passive: true, capture: true });
|
|
178
|
+
jumpRelease = function () {
|
|
179
|
+
document.removeEventListener('wheel', onUserScroll, { capture: true });
|
|
180
|
+
document.removeEventListener('touchstart', onUserScroll, { capture: true });
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
var ticks = 0;
|
|
184
|
+
var prevTop = null;
|
|
185
|
+
jumpWatch = setInterval(function () {
|
|
186
|
+
ticks++;
|
|
187
|
+
if (takenOver) { stopJumpWatch(); return; }
|
|
188
|
+
var h = findHeading(index, text);
|
|
189
|
+
// A rebuild can briefly detach it; keep waiting, within reason.
|
|
190
|
+
if (!h || !h.isConnected) { if (ticks > 20) stopJumpWatch(); return; }
|
|
191
|
+
var top = Math.round(h.getBoundingClientRect().top);
|
|
192
|
+
var settled = prevTop !== null && top === prevTop;
|
|
193
|
+
prevTop = top;
|
|
194
|
+
if (settled || ticks > 20) {
|
|
195
|
+
stopJumpWatch();
|
|
196
|
+
h.scrollIntoView({ block: 'start' });
|
|
197
|
+
}
|
|
198
|
+
}, 70);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function jumpTo(index, text) {
|
|
202
|
+
var h = findHeading(index, text);
|
|
203
|
+
if (!h || !h.isConnected) return;
|
|
204
|
+
h.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
205
|
+
watchJump(index, text);
|
|
128
206
|
}
|
|
129
207
|
|
|
130
208
|
// Width of the note viewer's right-edge scrollbar zone. Overlay scrollbars
|
|
@@ -145,6 +223,30 @@
|
|
|
145
223
|
return gap > 0 ? gap : 14;
|
|
146
224
|
}
|
|
147
225
|
|
|
226
|
+
// Pads the note container on the docked side until its content clears the
|
|
227
|
+
// pinned panel. Returns the undo, or null when nothing was reserved. Both
|
|
228
|
+
// rects are viewport-relative and the panel is position:fixed.
|
|
229
|
+
function reserveSpace(el, nav, side) {
|
|
230
|
+
var prop = side === 'left' ? 'padding-left' : 'padding-right';
|
|
231
|
+
var prevInline = el.style.getPropertyValue(prop);
|
|
232
|
+
var base = parseFloat(window.getComputedStyle(el).getPropertyValue(prop)) || 0;
|
|
233
|
+
var er = el.getBoundingClientRect();
|
|
234
|
+
var nr = nav.getBoundingClientRect();
|
|
235
|
+
var need = side === 'left' ? (nr.right + 12 - er.left - base) : (er.right - nr.left + 12 - base);
|
|
236
|
+
if (need <= 0) return null;
|
|
237
|
+
// Keep a readable column: in a narrow split view the pinned panel is
|
|
238
|
+
// better left overlapping than squeezing the note to a sliver.
|
|
239
|
+
var other = parseFloat(window.getComputedStyle(el).getPropertyValue(side === 'left' ? 'padding-right' : 'padding-left')) || 0;
|
|
240
|
+
if (er.width - other - base - need < 320) return null;
|
|
241
|
+
document.documentElement.style.setProperty('--jp-mm-reserve', Math.ceil(need) + 'px');
|
|
242
|
+
el.style.setProperty(prop, 'calc(' + base + 'px + var(--jp-mm-reserve, 0px))');
|
|
243
|
+
return function () {
|
|
244
|
+
if (prevInline) el.style.setProperty(prop, prevInline);
|
|
245
|
+
else el.style.removeProperty(prop);
|
|
246
|
+
document.documentElement.style.removeProperty('--jp-mm-reserve');
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
148
250
|
function build() {
|
|
149
251
|
if (cleanup) { cleanup(); cleanup = null; }
|
|
150
252
|
if (isEditableContext()) {
|
|
@@ -191,6 +293,8 @@
|
|
|
191
293
|
}
|
|
192
294
|
|
|
193
295
|
|
|
296
|
+
var pinned = pinOverride !== null ? pinOverride : settings.pinned === true;
|
|
297
|
+
|
|
194
298
|
var nav = document.createElement('nav');
|
|
195
299
|
nav.id = 'jp-minimap';
|
|
196
300
|
// Applied in BOTH collapsed and expanded states: shifting only on hover
|
|
@@ -202,6 +306,9 @@
|
|
|
202
306
|
// never sits on the left, so no gap is added there.
|
|
203
307
|
var navClasses = [];
|
|
204
308
|
if (settings.highContrast === true) navClasses.push('jp-mm-hc');
|
|
309
|
+
// Pinned is just "open, and never told to close": every close path goes
|
|
310
|
+
// through setOpen, which refuses while pinned.
|
|
311
|
+
if (pinned) navClasses.push('jp-mm-pinned', 'jp-mm-open');
|
|
205
312
|
if (settings.side === 'left') {
|
|
206
313
|
navClasses.push('jp-mm-left');
|
|
207
314
|
nav.style.left = settings.rightOffset + 'px';
|
|
@@ -240,6 +347,7 @@
|
|
|
240
347
|
}, { passive: false });
|
|
241
348
|
|
|
242
349
|
function setOpen(open) {
|
|
350
|
+
if (pinned && !open) return;
|
|
243
351
|
nav.classList.toggle('jp-mm-open', open);
|
|
244
352
|
// Drop the stepper's anchoring so the collapsed strip re-centres.
|
|
245
353
|
if (!open) { nav.style.top = ''; nav.style.transform = ''; }
|
|
@@ -284,13 +392,14 @@
|
|
|
284
392
|
// cursor over #jp-minimap, so the hover-expanded panel never collapses
|
|
285
393
|
// mid-interaction and no click-to-pin state has to be invented. Pointless
|
|
286
394
|
// when the note only has one heading level, so it is omitted entirely.
|
|
287
|
-
|
|
395
|
+
// The header always exists now: it carries the pin button (issue #6),
|
|
396
|
+
// and a note with a single heading level still needs a way to pin.
|
|
397
|
+
var head = document.createElement('div');
|
|
398
|
+
head.className = 'jp-mm-head';
|
|
288
399
|
var minusBtn = null;
|
|
289
400
|
var plusBtn = null;
|
|
290
401
|
var depthText = null;
|
|
291
402
|
if (levels.length > 1) {
|
|
292
|
-
head = document.createElement('div');
|
|
293
|
-
head.className = 'jp-mm-head';
|
|
294
403
|
minusBtn = stepButton('-', -1);
|
|
295
404
|
depthText = document.createElement('span');
|
|
296
405
|
depthText.className = 'jp-mm-depth';
|
|
@@ -298,8 +407,33 @@
|
|
|
298
407
|
head.appendChild(minusBtn);
|
|
299
408
|
head.appendChild(depthText);
|
|
300
409
|
head.appendChild(plusBtn);
|
|
301
|
-
|
|
302
|
-
|
|
410
|
+
}
|
|
411
|
+
head.appendChild(pinButton());
|
|
412
|
+
list.appendChild(head);
|
|
413
|
+
renderHead();
|
|
414
|
+
|
|
415
|
+
// currentColor SVG rather than the pushpin emoji: an emoji brings its own
|
|
416
|
+
// colours, and the panel stays within currentColor/neutral rgba so it
|
|
417
|
+
// reads on every theme (see CLAUDE.md).
|
|
418
|
+
function pinButton() {
|
|
419
|
+
var b = document.createElement('span');
|
|
420
|
+
b.className = 'jp-mm-pin';
|
|
421
|
+
b.title = pinned ? settings.unpinTip : settings.pinTip;
|
|
422
|
+
b.innerHTML = '<svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">'
|
|
423
|
+
+ '<path d="M15 2.5l6.5 6.5-1.8 1.1-2.4-.6-3.6 3.6.5 4.4-1.7 1.7-3.9-3.9-5.1 5.1-1.4-1.4 5.1-5.1-3.9-3.9 1.7-1.7 4.4.5 3.6-3.6-.6-2.4z"/></svg>';
|
|
424
|
+
// mousedown, like the rows and the stepper: a click would be lost to the
|
|
425
|
+
// re-render the press can trigger.
|
|
426
|
+
b.addEventListener('mousedown', function (e) {
|
|
427
|
+
if (e.button !== 0) return;
|
|
428
|
+
e.preventDefault();
|
|
429
|
+
e.stopPropagation();
|
|
430
|
+
pinOverride = !pinned;
|
|
431
|
+
if (typeof webviewApi !== 'undefined' && webviewApi.postMessage) {
|
|
432
|
+
webviewApi.postMessage('joplin-minimap', { type: 'setPinned', value: pinOverride }).catch(function () {});
|
|
433
|
+
}
|
|
434
|
+
build();
|
|
435
|
+
});
|
|
436
|
+
return b;
|
|
303
437
|
}
|
|
304
438
|
|
|
305
439
|
function stepButton(sign, delta) {
|
|
@@ -319,7 +453,7 @@
|
|
|
319
453
|
}
|
|
320
454
|
|
|
321
455
|
function renderHead() {
|
|
322
|
-
if (!
|
|
456
|
+
if (!depthText) return;
|
|
323
457
|
var lo = levels[0];
|
|
324
458
|
depthText.textContent = lo === maxLevel ? ('H' + lo) : ('H' + lo + '-H' + maxLevel);
|
|
325
459
|
minusBtn.classList.toggle('jp-mm-step-off', maxLevel === lo);
|
|
@@ -341,7 +475,8 @@
|
|
|
341
475
|
renderHead();
|
|
342
476
|
applyTodoDots();
|
|
343
477
|
var afterY = head.getBoundingClientRect().top;
|
|
344
|
-
|
|
478
|
+
// Pinned is top-anchored, so the header cannot drift there.
|
|
479
|
+
if (!pinned && afterY !== beforeY) {
|
|
345
480
|
var box = nav.getBoundingClientRect();
|
|
346
481
|
var limit = window.innerHeight - box.height - 4;
|
|
347
482
|
var top = box.top + (beforeY - afterY);
|
|
@@ -412,6 +547,14 @@
|
|
|
412
547
|
nav.appendChild(list);
|
|
413
548
|
document.body.appendChild(nav);
|
|
414
549
|
|
|
550
|
+
// Pinned: move the note text aside so the sidebar never covers it (the
|
|
551
|
+
// third point of issue #6). Padding is added on top of whatever the
|
|
552
|
+
// container already has, and the amount lives in a variable so the print
|
|
553
|
+
// stylesheet can zero it. Skipped when the viewer is too narrow to leave a
|
|
554
|
+
// readable column (see reserveSpace).
|
|
555
|
+
var releaseReserve = null;
|
|
556
|
+
if (pinned) releaseReserve = reserveSpace(root, nav, settings.side === 'left' ? 'left' : 'right');
|
|
557
|
+
|
|
415
558
|
// Open to-dos in a section the stepper has collapsed away roll up to the
|
|
416
559
|
// nearest visible ancestor. Without this, lowering the depth silently
|
|
417
560
|
// hides the only signal that a branch still holds unfinished work - which
|
|
@@ -445,6 +588,14 @@
|
|
|
445
588
|
for (var j = 0; j < items.length; j++) {
|
|
446
589
|
items[j].classList.toggle('jp-mm-active', j === activeRow);
|
|
447
590
|
}
|
|
591
|
+
// A pinned outline follows the reading position like an outline pane.
|
|
592
|
+
// scrollTop on the list only - scrollIntoView would scroll the note too.
|
|
593
|
+
if (pinned && items[activeRow]) {
|
|
594
|
+
var row = items[activeRow];
|
|
595
|
+
if (row.offsetTop < list.scrollTop || row.offsetTop + row.offsetHeight > list.scrollTop + list.clientHeight) {
|
|
596
|
+
list.scrollTop = Math.max(0, row.offsetTop - list.clientHeight * 0.3);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
448
599
|
}
|
|
449
600
|
|
|
450
601
|
document.addEventListener('scroll', updateActive, { passive: true, capture: true });
|
|
@@ -460,6 +611,7 @@
|
|
|
460
611
|
cleanup = function () {
|
|
461
612
|
document.removeEventListener('scroll', updateActive, { capture: true });
|
|
462
613
|
if (closeOnOutside) document.removeEventListener('touchstart', closeOnOutside);
|
|
614
|
+
if (releaseReserve) releaseReserve();
|
|
463
615
|
};
|
|
464
616
|
}
|
|
465
617
|
|
package/publish/plugin.jpl
CHANGED
|
Binary file
|