joplin-plugin-explorer 1.1.1 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/publish/index.js +43 -6
- package/publish/manifest.json +11 -3
- package/publish/plugin.jpl +0 -0
- package/publish/webview/panel.css +70 -0
- package/publish/webview/panel.js +65 -9
package/package.json
CHANGED
package/publish/index.js
CHANGED
|
@@ -25,6 +25,7 @@ var i18nData = {
|
|
|
25
25
|
confirmDeleteNote: '确定删除此笔记吗?',
|
|
26
26
|
promptRename: '请输入新名称:',
|
|
27
27
|
promptMoveNote: '请输入目标笔记本名称:',
|
|
28
|
+
cancel: '取消',
|
|
28
29
|
},
|
|
29
30
|
'zh_TW': {
|
|
30
31
|
newNotebook: '新建筆記本', newNote: '新建筆記', newTodo: '新建待辦',
|
|
@@ -45,6 +46,7 @@ var i18nData = {
|
|
|
45
46
|
confirmDeleteFolder: '確定刪除此筆記本及其所有內容嗎?',
|
|
46
47
|
confirmDeleteNote: '確定刪除此筆記嗎?',
|
|
47
48
|
promptRename: '請輸入新名稱:', promptMoveNote: '請輸入目標筆記本名稱:',
|
|
49
|
+
cancel: '取消',
|
|
48
50
|
},
|
|
49
51
|
'en_US': {
|
|
50
52
|
newNotebook: 'New Notebook', newNote: 'New Note', newTodo: 'New To-do',
|
|
@@ -65,6 +67,7 @@ var i18nData = {
|
|
|
65
67
|
confirmDeleteFolder: 'Delete this notebook and all its contents?',
|
|
66
68
|
confirmDeleteNote: 'Delete this note?',
|
|
67
69
|
promptRename: 'Enter new name:', promptMoveNote: 'Enter target notebook name:',
|
|
70
|
+
cancel: 'Cancel',
|
|
68
71
|
},
|
|
69
72
|
'ja_JP': {
|
|
70
73
|
newNotebook: '\u65B0\u898F\u30CE\u30FC\u30C8\u30D6\u30C3\u30AF', newNote: '\u65B0\u898F\u30CE\u30FC\u30C8', newTodo: '\u65B0\u898F\u30BF\u30B9\u30AF',
|
|
@@ -85,6 +88,7 @@ var i18nData = {
|
|
|
85
88
|
confirmDeleteFolder: '\u3053\u306E\u30CE\u30FC\u30C8\u30D6\u30C3\u30AF\u3068\u305D\u306E\u5185\u5BB9\u3092\u3059\u3079\u3066\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F',
|
|
86
89
|
confirmDeleteNote: '\u3053\u306E\u30CE\u30FC\u30C8\u3092\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F',
|
|
87
90
|
promptRename: '\u65B0\u3057\u3044\u540D\u524D\u3092\u5165\u529B\uFF1A', promptMoveNote: '\u79FB\u52D5\u5148\u306E\u30CE\u30FC\u30C8\u30D6\u30C3\u30AF\u540D\uFF1A',
|
|
91
|
+
cancel: '\u30AD\u30E3\u30F3\u30BB\u30EB',
|
|
88
92
|
},
|
|
89
93
|
};
|
|
90
94
|
|
|
@@ -256,6 +260,18 @@ joplin.plugins.register({
|
|
|
256
260
|
var allFoldersCache = [];
|
|
257
261
|
var isFirstLoad = true;
|
|
258
262
|
|
|
263
|
+
function expandToFolder(folderId) {
|
|
264
|
+
var parentId = folderId;
|
|
265
|
+
while (parentId) {
|
|
266
|
+
delete collapsedFolders[parentId];
|
|
267
|
+
var found = null;
|
|
268
|
+
for (var i = 0; i < allFoldersCache.length; i++) {
|
|
269
|
+
if (allFoldersCache[i].id === parentId) { found = allFoldersCache[i]; break; }
|
|
270
|
+
}
|
|
271
|
+
parentId = found ? found.parent_id : null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
259
275
|
function sortNotes(notes, sortMode) {
|
|
260
276
|
var sorted = notes.slice();
|
|
261
277
|
switch (sortMode) {
|
|
@@ -363,9 +379,13 @@ joplin.plugins.register({
|
|
|
363
379
|
await refreshPanel();
|
|
364
380
|
} else if (msg.name === 'newNote') {
|
|
365
381
|
await joplin.commands.execute('newNote');
|
|
382
|
+
var nn = await joplin.workspace.selectedNote();
|
|
383
|
+
if (nn) { selectedNoteId = nn.id; expandToFolder(nn.parent_id); }
|
|
366
384
|
await refreshPanel();
|
|
367
385
|
} else if (msg.name === 'newTodo') {
|
|
368
386
|
await joplin.commands.execute('newTodo');
|
|
387
|
+
var nt = await joplin.workspace.selectedNote();
|
|
388
|
+
if (nt) { selectedNoteId = nt.id; expandToFolder(nt.parent_id); }
|
|
369
389
|
await refreshPanel();
|
|
370
390
|
} else if (msg.name === 'search') {
|
|
371
391
|
var query = msg.query;
|
|
@@ -447,10 +467,16 @@ joplin.plugins.register({
|
|
|
447
467
|
if (itemType === 'folder') {
|
|
448
468
|
switch (action) {
|
|
449
469
|
case 'newNote':
|
|
450
|
-
await joplin.data.post(['
|
|
470
|
+
var newNote = await joplin.data.post(['notes'], null, { title: t.newNote, parent_id: id });
|
|
471
|
+
await joplin.commands.execute('openNote', newNote.id);
|
|
472
|
+
selectedNoteId = newNote.id;
|
|
473
|
+
expandToFolder(id);
|
|
451
474
|
break;
|
|
452
475
|
case 'newTodo':
|
|
453
|
-
await joplin.data.post(['
|
|
476
|
+
var newTodo = await joplin.data.post(['notes'], null, { title: t.newTodo, parent_id: id, is_todo: 1 });
|
|
477
|
+
await joplin.commands.execute('openNote', newTodo.id);
|
|
478
|
+
selectedNoteId = newTodo.id;
|
|
479
|
+
expandToFolder(id);
|
|
454
480
|
break;
|
|
455
481
|
case 'newSubNotebook':
|
|
456
482
|
await joplin.data.post(['folders'], null, { title: t.newNotebook, parent_id: id });
|
|
@@ -462,28 +488,39 @@ joplin.plugins.register({
|
|
|
462
488
|
if (msg.newTitle) await joplin.data.put(['folders', id], null, { title: msg.newTitle });
|
|
463
489
|
break;
|
|
464
490
|
case 'exportFolder':
|
|
465
|
-
await joplin.commands.execute('exportFolders', [id]);
|
|
491
|
+
try { await joplin.commands.execute('exportFolders', [id]); } catch(e) {}
|
|
466
492
|
break;
|
|
467
493
|
}
|
|
468
494
|
} else if (itemType === 'note') {
|
|
469
495
|
switch (action) {
|
|
470
496
|
case 'openNote':
|
|
471
497
|
await joplin.commands.execute('openNote', id);
|
|
498
|
+
selectedNoteId = id;
|
|
472
499
|
break;
|
|
473
500
|
case 'openInNewWindow':
|
|
474
|
-
await joplin.commands.execute('openNoteInNewWindow', id);
|
|
501
|
+
try { await joplin.commands.execute('openNoteInNewWindow', id); } catch(e) {
|
|
502
|
+
// Fallback: just open in main window
|
|
503
|
+
await joplin.commands.execute('openNote', id);
|
|
504
|
+
}
|
|
475
505
|
break;
|
|
476
506
|
case 'copyLink':
|
|
477
507
|
var linkNote = await joplin.data.get(['notes', id], { fields: ['id', 'title'] });
|
|
478
508
|
var mdLink = '[' + linkNote.title + '](:/' + linkNote.id + ')';
|
|
479
|
-
|
|
509
|
+
try {
|
|
510
|
+
await joplin.clipboard.writeText(mdLink);
|
|
511
|
+
} catch(e) {
|
|
512
|
+
// Fallback: send link text to webview for copying
|
|
513
|
+
await joplin.views.panels.postMessage(panel, { name: 'copyText', text: mdLink });
|
|
514
|
+
}
|
|
480
515
|
break;
|
|
481
516
|
case 'duplicateNote':
|
|
482
517
|
var srcNote = await joplin.data.get(['notes', id], { fields: ['title', 'body', 'parent_id', 'is_todo'] });
|
|
483
|
-
await joplin.data.post(['notes'], null, {
|
|
518
|
+
var dupNote = await joplin.data.post(['notes'], null, {
|
|
484
519
|
title: srcNote.title + ' (copy)', body: srcNote.body,
|
|
485
520
|
parent_id: srcNote.parent_id, is_todo: srcNote.is_todo,
|
|
486
521
|
});
|
|
522
|
+
await joplin.commands.execute('openNote', dupNote.id);
|
|
523
|
+
selectedNoteId = dupNote.id;
|
|
487
524
|
break;
|
|
488
525
|
case 'switchNoteType':
|
|
489
526
|
var sn = await joplin.data.get(['notes', id], { fields: ['is_todo'] });
|
package/publish/manifest.json
CHANGED
|
@@ -2,9 +2,17 @@
|
|
|
2
2
|
"manifest_version": 1,
|
|
3
3
|
"id": "com.github.joplin-explorer",
|
|
4
4
|
"app_min_version": "2.6.0",
|
|
5
|
-
"version": "1.1.
|
|
5
|
+
"version": "1.1.3",
|
|
6
6
|
"name": "Joplin Explorer",
|
|
7
7
|
"description": "A unified sidebar that displays notebooks and notes together in a single tree view",
|
|
8
|
-
"author": "
|
|
9
|
-
"homepage_url": "https://github.com"
|
|
8
|
+
"author": "lim0513",
|
|
9
|
+
"homepage_url": "https://github.com/lim0513/joplin-explorer",
|
|
10
|
+
"repository_url": "https://github.com/lim0513/joplin-explorer",
|
|
11
|
+
"keywords": ["sidebar", "explorer", "tree-view", "notebooks", "notes"],
|
|
12
|
+
"categories": ["appearance", "productivity"],
|
|
13
|
+
"screenshots": [
|
|
14
|
+
{"src": "screenshots/tree-view.png", "label": "Unified tree view of notebooks and notes"},
|
|
15
|
+
{"src": "screenshots/search.png", "label": "Full-text search with keyword highlighting"},
|
|
16
|
+
{"src": "screenshots/context-menu.png", "label": "Right-click context menu"}
|
|
17
|
+
]
|
|
10
18
|
}
|
package/publish/plugin.jpl
CHANGED
|
Binary file
|
|
@@ -347,6 +347,76 @@ html, body {
|
|
|
347
347
|
word-break: break-all;
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
+
/* Inline input dialog */
|
|
351
|
+
.inline-input-overlay {
|
|
352
|
+
position: fixed;
|
|
353
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
354
|
+
background: rgba(0, 0, 0, 0.3);
|
|
355
|
+
display: flex;
|
|
356
|
+
align-items: center;
|
|
357
|
+
justify-content: center;
|
|
358
|
+
z-index: 10000;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.inline-input-dialog {
|
|
362
|
+
background: var(--joplin-background-color, #fff);
|
|
363
|
+
border: 1px solid var(--joplin-divider-color, #ccc);
|
|
364
|
+
border-radius: 8px;
|
|
365
|
+
padding: 16px;
|
|
366
|
+
min-width: 240px;
|
|
367
|
+
max-width: 90%;
|
|
368
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.inline-input-label {
|
|
372
|
+
font-size: 12px;
|
|
373
|
+
margin-bottom: 8px;
|
|
374
|
+
color: var(--joplin-color);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.inline-input-field {
|
|
378
|
+
width: 100%;
|
|
379
|
+
box-sizing: border-box;
|
|
380
|
+
padding: 6px 8px;
|
|
381
|
+
border: 1px solid var(--joplin-divider-color, #ccc);
|
|
382
|
+
border-radius: 4px;
|
|
383
|
+
background: var(--joplin-background-color, #fff);
|
|
384
|
+
color: var(--joplin-color);
|
|
385
|
+
font-size: 13px;
|
|
386
|
+
outline: none;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.inline-input-field:focus {
|
|
390
|
+
border-color: var(--joplin-color2, #4a9cf5);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.inline-input-buttons {
|
|
394
|
+
display: flex;
|
|
395
|
+
gap: 8px;
|
|
396
|
+
margin-top: 12px;
|
|
397
|
+
justify-content: flex-end;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.inline-input-buttons button {
|
|
401
|
+
padding: 5px 16px;
|
|
402
|
+
border: 1px solid var(--joplin-divider-color, #ccc);
|
|
403
|
+
border-radius: 4px;
|
|
404
|
+
cursor: pointer;
|
|
405
|
+
font-size: 12px;
|
|
406
|
+
color: var(--joplin-color);
|
|
407
|
+
background: var(--joplin-background-color3, #f0f0f0);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.inline-input-ok {
|
|
411
|
+
background: #1a73e8 !important;
|
|
412
|
+
color: #fff !important;
|
|
413
|
+
border-color: #1a73e8 !important;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.inline-input-buttons button:hover {
|
|
417
|
+
opacity: 0.85;
|
|
418
|
+
}
|
|
419
|
+
|
|
350
420
|
mark.search-highlight {
|
|
351
421
|
background: rgba(255, 213, 0, 0.4);
|
|
352
422
|
color: inherit;
|
package/publish/webview/panel.js
CHANGED
|
@@ -46,6 +46,49 @@ function T(key) {
|
|
|
46
46
|
return (window._i18n && window._i18n[key]) || key;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
// Inline input dialog (replaces prompt() which may not work in webview)
|
|
50
|
+
function showInlineInput(label, defaultValue, callback) {
|
|
51
|
+
var existing = document.getElementById('inline-input-overlay');
|
|
52
|
+
if (existing) existing.remove();
|
|
53
|
+
|
|
54
|
+
var overlay = document.createElement('div');
|
|
55
|
+
overlay.id = 'inline-input-overlay';
|
|
56
|
+
overlay.className = 'inline-input-overlay';
|
|
57
|
+
overlay.innerHTML = '<div class="inline-input-dialog">'
|
|
58
|
+
+ '<div class="inline-input-label">' + label + '</div>'
|
|
59
|
+
+ '<input class="inline-input-field" type="text" value="' + (defaultValue || '').replace(/"/g, '"') + '" />'
|
|
60
|
+
+ '<div class="inline-input-buttons">'
|
|
61
|
+
+ '<button class="inline-input-ok">OK</button>'
|
|
62
|
+
+ '<button class="inline-input-cancel">' + (T('cancel') || 'Cancel') + '</button>'
|
|
63
|
+
+ '</div></div>';
|
|
64
|
+
|
|
65
|
+
document.body.appendChild(overlay);
|
|
66
|
+
|
|
67
|
+
var input = overlay.querySelector('.inline-input-field');
|
|
68
|
+
input.focus();
|
|
69
|
+
input.select();
|
|
70
|
+
|
|
71
|
+
function submit() {
|
|
72
|
+
var val = input.value;
|
|
73
|
+
overlay.remove();
|
|
74
|
+
callback(val);
|
|
75
|
+
}
|
|
76
|
+
function cancel() {
|
|
77
|
+
overlay.remove();
|
|
78
|
+
callback(null);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
overlay.querySelector('.inline-input-ok').addEventListener('click', submit);
|
|
82
|
+
overlay.querySelector('.inline-input-cancel').addEventListener('click', cancel);
|
|
83
|
+
input.addEventListener('keydown', function(e) {
|
|
84
|
+
if (e.key === 'Enter') submit();
|
|
85
|
+
if (e.key === 'Escape') cancel();
|
|
86
|
+
});
|
|
87
|
+
overlay.addEventListener('click', function(e) {
|
|
88
|
+
if (e.target === overlay) cancel();
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
49
92
|
// Left click: open note / toggle folder
|
|
50
93
|
document.addEventListener('click', function(e) {
|
|
51
94
|
var existingMenu = document.getElementById('ctx-menu');
|
|
@@ -107,7 +150,6 @@ document.addEventListener('contextmenu', function(e) {
|
|
|
107
150
|
menuHtml += '<div class="ctx-item" data-action="toggleTodo" data-id="' + id + '" data-type="note">' + T('ctxToggleTodo') + '</div>';
|
|
108
151
|
menuHtml += '<div class="ctx-sep"></div>';
|
|
109
152
|
menuHtml += '<div class="ctx-item" data-action="renameNote" data-id="' + id + '" data-type="note" data-title="' + title.replace(/"/g, '"') + '">' + T('ctxRenameNote') + '</div>';
|
|
110
|
-
menuHtml += '<div class="ctx-item" data-action="moveNote" data-id="' + id + '" data-type="note">' + T('ctxMoveNote') + '</div>';
|
|
111
153
|
menuHtml += '<div class="ctx-item" data-action="noteInfo" data-id="' + id + '" data-type="note">' + T('ctxNoteInfo') + '</div>';
|
|
112
154
|
menuHtml += '<div class="ctx-sep"></div>';
|
|
113
155
|
menuHtml += '<div class="ctx-item ctx-danger" data-action="deleteNote" data-id="' + id + '" data-type="note">' + T('ctxDeleteNote') + '</div>';
|
|
@@ -133,10 +175,11 @@ document.addEventListener('click', function(e) {
|
|
|
133
175
|
|
|
134
176
|
if (action === 'renameFolder' || action === 'renameNote') {
|
|
135
177
|
var currentTitle = ctxItem.dataset.title || '';
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
178
|
+
showInlineInput(T('promptRename'), currentTitle, function(newTitle) {
|
|
179
|
+
if (newTitle !== null && newTitle.trim() !== '') {
|
|
180
|
+
postMsg({ name: 'contextMenu', action: action, id: id, itemType: itemType, newTitle: newTitle.trim() });
|
|
181
|
+
}
|
|
182
|
+
});
|
|
140
183
|
} else if (action === 'deleteFolder') {
|
|
141
184
|
if (confirm(T('confirmDeleteFolder'))) {
|
|
142
185
|
postMsg({ name: 'contextMenu', action: action, id: id, itemType: itemType });
|
|
@@ -146,10 +189,11 @@ document.addEventListener('click', function(e) {
|
|
|
146
189
|
postMsg({ name: 'contextMenu', action: action, id: id, itemType: itemType });
|
|
147
190
|
}
|
|
148
191
|
} else if (action === 'moveNote') {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
192
|
+
showInlineInput(T('promptMoveNote'), '', function(folderName) {
|
|
193
|
+
if (folderName !== null && folderName.trim() !== '') {
|
|
194
|
+
postMsg({ name: 'contextMenu', action: action, id: id, itemType: itemType, targetFolderName: folderName.trim() });
|
|
195
|
+
}
|
|
196
|
+
});
|
|
153
197
|
} else {
|
|
154
198
|
postMsg({ name: 'contextMenu', action: action, id: id, itemType: itemType });
|
|
155
199
|
}
|
|
@@ -219,6 +263,18 @@ webviewApi.onMessage(function(msg) {
|
|
|
219
263
|
} else {
|
|
220
264
|
renderSearchResults(m.results, m.query);
|
|
221
265
|
}
|
|
266
|
+
} else if (m.name === 'copyText') {
|
|
267
|
+
// Fallback clipboard copy via webview
|
|
268
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
269
|
+
navigator.clipboard.writeText(m.text);
|
|
270
|
+
} else {
|
|
271
|
+
var ta = document.createElement('textarea');
|
|
272
|
+
ta.value = m.text;
|
|
273
|
+
document.body.appendChild(ta);
|
|
274
|
+
ta.select();
|
|
275
|
+
document.execCommand('copy');
|
|
276
|
+
ta.remove();
|
|
277
|
+
}
|
|
222
278
|
} else if (m.name === 'selectNote') {
|
|
223
279
|
// Update selection without full re-render
|
|
224
280
|
document.querySelectorAll('.tree-item.note.selected').forEach(function(el) {
|