joplin-plugin-explorer 1.0.1 → 1.1.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/package.json +1 -1
- package/publish/index.js +85 -3
- package/publish/manifest.json +1 -1
- package/publish/plugin.jpl +0 -0
- package/publish/webview/panel.css +61 -0
- package/publish/webview/panel.js +90 -18
package/package.json
CHANGED
package/publish/index.js
CHANGED
|
@@ -5,7 +5,9 @@ var i18nData = {
|
|
|
5
5
|
'zh_CN': {
|
|
6
6
|
newNotebook: '新建笔记本', newNote: '新建笔记', newTodo: '新建待办',
|
|
7
7
|
sort: '排序', collapseAll: '全部折叠', expandAll: '全部展开',
|
|
8
|
-
search: '
|
|
8
|
+
search: '搜索笔记内容...', searchResultCount: '找到 {count} 条结果',
|
|
9
|
+
searchNoResult: '没有找到匹配的笔记', searching: '搜索中...',
|
|
10
|
+
sync: '同步', syncing: '同步中...', syncDone: '\u2714 同步完成', loading: '加载中...',
|
|
9
11
|
sortUpdatedDesc: '\u2193 修改时间', sortUpdatedAsc: '\u2191 修改时间',
|
|
10
12
|
sortTitleAsc: '\u2191 标题', sortTitleDesc: '\u2193 标题',
|
|
11
13
|
// Folder context menu
|
|
@@ -27,7 +29,9 @@ var i18nData = {
|
|
|
27
29
|
'zh_TW': {
|
|
28
30
|
newNotebook: '新建筆記本', newNote: '新建筆記', newTodo: '新建待辦',
|
|
29
31
|
sort: '排序', collapseAll: '全部摺疊', expandAll: '全部展開',
|
|
30
|
-
search: '
|
|
32
|
+
search: '搜尋筆記內容...', searchResultCount: '找到 {count} 條結果',
|
|
33
|
+
searchNoResult: '沒有找到匹配的筆記', searching: '搜尋中...',
|
|
34
|
+
sync: '同步', syncing: '同步中...', syncDone: '\u2714 同步完成', loading: '載入中...',
|
|
31
35
|
sortUpdatedDesc: '\u2193 修改時間', sortUpdatedAsc: '\u2191 修改時間',
|
|
32
36
|
sortTitleAsc: '\u2191 標題', sortTitleDesc: '\u2193 標題',
|
|
33
37
|
ctxNewNoteHere: '在此新建筆記', ctxNewTodoHere: '在此新建待辦',
|
|
@@ -45,7 +49,9 @@ var i18nData = {
|
|
|
45
49
|
'en_US': {
|
|
46
50
|
newNotebook: 'New Notebook', newNote: 'New Note', newTodo: 'New To-do',
|
|
47
51
|
sort: 'Sort', collapseAll: 'Collapse All', expandAll: 'Expand All',
|
|
48
|
-
search: 'Search
|
|
52
|
+
search: 'Search note contents...', searchResultCount: '{count} results found',
|
|
53
|
+
searchNoResult: 'No matching notes found', searching: 'Searching...',
|
|
54
|
+
sync: 'Synchronise', syncing: 'Syncing...', syncDone: '\u2714 Sync Done', loading: 'Loading...',
|
|
49
55
|
sortUpdatedDesc: '\u2193 Updated', sortUpdatedAsc: '\u2191 Updated',
|
|
50
56
|
sortTitleAsc: '\u2191 Title', sortTitleDesc: '\u2193 Title',
|
|
51
57
|
ctxNewNoteHere: 'New Note Here', ctxNewTodoHere: 'New To-do Here',
|
|
@@ -60,6 +66,26 @@ var i18nData = {
|
|
|
60
66
|
confirmDeleteNote: 'Delete this note?',
|
|
61
67
|
promptRename: 'Enter new name:', promptMoveNote: 'Enter target notebook name:',
|
|
62
68
|
},
|
|
69
|
+
'ja_JP': {
|
|
70
|
+
newNotebook: '\u65B0\u898F\u30CE\u30FC\u30C8\u30D6\u30C3\u30AF', newNote: '\u65B0\u898F\u30CE\u30FC\u30C8', newTodo: '\u65B0\u898F\u30BF\u30B9\u30AF',
|
|
71
|
+
sort: '\u4E26\u3079\u66FF\u3048', collapseAll: '\u3059\u3079\u3066\u6298\u308A\u305F\u305F\u3080', expandAll: '\u3059\u3079\u3066\u5C55\u958B',
|
|
72
|
+
search: '\u30CE\u30FC\u30C8\u5185\u5BB9\u3092\u691C\u7D22...', searchResultCount: '{count} \u4EF6\u306E\u7D50\u679C',
|
|
73
|
+
searchNoResult: '\u4E00\u81F4\u3059\u308B\u30CE\u30FC\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093', searching: '\u691C\u7D22\u4E2D...',
|
|
74
|
+
sync: '\u540C\u671F', syncing: '\u540C\u671F\u4E2D...', syncDone: '\u2714 \u540C\u671F\u5B8C\u4E86', loading: '\u8AAD\u307F\u8FBC\u307F\u4E2D...',
|
|
75
|
+
sortUpdatedDesc: '\u2193 \u66F4\u65B0\u65E5\u6642', sortUpdatedAsc: '\u2191 \u66F4\u65B0\u65E5\u6642',
|
|
76
|
+
sortTitleAsc: '\u2191 \u30BF\u30A4\u30C8\u30EB', sortTitleDesc: '\u2193 \u30BF\u30A4\u30C8\u30EB',
|
|
77
|
+
ctxNewNoteHere: '\u3053\u3053\u306B\u65B0\u898F\u30CE\u30FC\u30C8', ctxNewTodoHere: '\u3053\u3053\u306B\u65B0\u898F\u30BF\u30B9\u30AF',
|
|
78
|
+
ctxNewSubNotebook: '\u65B0\u898F\u30B5\u30D6\u30CE\u30FC\u30C8\u30D6\u30C3\u30AF', ctxRenameFolder: '\u540D\u524D\u3092\u5909\u66F4',
|
|
79
|
+
ctxExportFolder: '\u30CE\u30FC\u30C8\u30D6\u30C3\u30AF\u3092\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8', ctxDeleteFolder: '\u30CE\u30FC\u30C8\u30D6\u30C3\u30AF\u3092\u524A\u9664',
|
|
80
|
+
ctxOpenNote: '\u30CE\u30FC\u30C8\u3092\u958B\u304F', ctxOpenInNewWindow: '\u65B0\u3057\u3044\u30A6\u30A3\u30F3\u30C9\u30A6\u3067\u958B\u304F',
|
|
81
|
+
ctxCopyLink: 'Markdown\u30EA\u30F3\u30AF\u3092\u30B3\u30D4\u30FC', ctxDuplicateNote: '\u8907\u88FD',
|
|
82
|
+
ctxSwitchNoteType: '\u30CE\u30FC\u30C8/\u30BF\u30B9\u30AF\u5207\u308A\u66FF\u3048', ctxToggleTodo: '\u5B8C\u4E86\u72B6\u614B\u3092\u5207\u308A\u66FF\u3048',
|
|
83
|
+
ctxRenameNote: '\u540D\u524D\u3092\u5909\u66F4', ctxMoveNote: '\u30CE\u30FC\u30C8\u30D6\u30C3\u30AF\u306B\u79FB\u52D5...',
|
|
84
|
+
ctxNoteInfo: '\u30CE\u30FC\u30C8\u30D7\u30ED\u30D1\u30C6\u30A3', ctxDeleteNote: '\u30CE\u30FC\u30C8\u3092\u524A\u9664',
|
|
85
|
+
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
|
+
confirmDeleteNote: '\u3053\u306E\u30CE\u30FC\u30C8\u3092\u524A\u9664\u3057\u307E\u3059\u304B\uFF1F',
|
|
87
|
+
promptRename: '\u65B0\u3057\u3044\u540D\u524D\u3092\u5165\u529B\uFF1A', promptMoveNote: '\u79FB\u52D5\u5148\u306E\u30CE\u30FC\u30C8\u30D6\u30C3\u30AF\u540D\uFF1A',
|
|
88
|
+
},
|
|
63
89
|
};
|
|
64
90
|
|
|
65
91
|
function getI18n(locale) {
|
|
@@ -340,6 +366,62 @@ joplin.plugins.register({
|
|
|
340
366
|
} else if (msg.name === 'newTodo') {
|
|
341
367
|
await joplin.commands.execute('newTodo');
|
|
342
368
|
await refreshPanel();
|
|
369
|
+
} else if (msg.name === 'search') {
|
|
370
|
+
var query = msg.query;
|
|
371
|
+
if (!query || !query.trim()) {
|
|
372
|
+
// Empty query: tell webview to clear search results
|
|
373
|
+
await joplin.views.panels.postMessage(panel, { name: 'searchResults', results: null, query: '' });
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
var searchResults = [];
|
|
378
|
+
var page = 1;
|
|
379
|
+
var hasMore = true;
|
|
380
|
+
while (hasMore && searchResults.length < 100) {
|
|
381
|
+
var result = await joplin.data.get(['search'], {
|
|
382
|
+
query: query,
|
|
383
|
+
fields: ['id', 'title', 'body', 'parent_id', 'is_todo', 'todo_completed'],
|
|
384
|
+
page: page, limit: 20,
|
|
385
|
+
});
|
|
386
|
+
searchResults = searchResults.concat(result.items);
|
|
387
|
+
hasMore = result.has_more;
|
|
388
|
+
page++;
|
|
389
|
+
}
|
|
390
|
+
// Build folder name lookup
|
|
391
|
+
var folderNameMap = {};
|
|
392
|
+
for (var i = 0; i < allFoldersCache.length; i++) {
|
|
393
|
+
folderNameMap[allFoldersCache[i].id] = allFoldersCache[i].title;
|
|
394
|
+
}
|
|
395
|
+
// Extract snippet around the matched keyword
|
|
396
|
+
var items = [];
|
|
397
|
+
for (var i = 0; i < searchResults.length; i++) {
|
|
398
|
+
var note = searchResults[i];
|
|
399
|
+
var snippet = '';
|
|
400
|
+
var body = note.body || '';
|
|
401
|
+
var lowerBody = body.toLowerCase();
|
|
402
|
+
var lowerQuery = query.toLowerCase();
|
|
403
|
+
var matchIdx = lowerBody.indexOf(lowerQuery);
|
|
404
|
+
if (matchIdx >= 0) {
|
|
405
|
+
var start = Math.max(0, matchIdx - 40);
|
|
406
|
+
var end = Math.min(body.length, matchIdx + lowerQuery.length + 80);
|
|
407
|
+
snippet = (start > 0 ? '...' : '') + body.substring(start, end).replace(/\n/g, ' ') + (end < body.length ? '...' : '');
|
|
408
|
+
} else {
|
|
409
|
+
// Title match - show beginning of body
|
|
410
|
+
snippet = body.substring(0, 120).replace(/\n/g, ' ') + (body.length > 120 ? '...' : '');
|
|
411
|
+
}
|
|
412
|
+
items.push({
|
|
413
|
+
id: note.id,
|
|
414
|
+
title: note.title || '(untitled)',
|
|
415
|
+
is_todo: note.is_todo,
|
|
416
|
+
todo_completed: note.todo_completed,
|
|
417
|
+
snippet: snippet,
|
|
418
|
+
folderName: folderNameMap[note.parent_id] || '',
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
await joplin.views.panels.postMessage(panel, { name: 'searchResults', results: items, query: query });
|
|
422
|
+
} catch (err) {
|
|
423
|
+
console.error('Joplin Explorer: search error', err);
|
|
424
|
+
}
|
|
343
425
|
} else if (msg.name === 'cycleSort') {
|
|
344
426
|
var sortModes = ['updated_desc', 'updated_asc', 'title_asc', 'title_desc'];
|
|
345
427
|
var idx = sortModes.indexOf(currentSort);
|
package/publish/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"manifest_version": 1,
|
|
3
3
|
"id": "com.github.joplin-explorer",
|
|
4
4
|
"app_min_version": "2.6.0",
|
|
5
|
-
"version": "1.0
|
|
5
|
+
"version": "1.1.0",
|
|
6
6
|
"name": "Joplin Explorer",
|
|
7
7
|
"description": "A unified sidebar that displays notebooks and notes together in a single tree view",
|
|
8
8
|
"author": "user",
|
|
Binary file
|
|
@@ -273,3 +273,64 @@ html, body {
|
|
|
273
273
|
.tree-item.drop-below {
|
|
274
274
|
box-shadow: 0 2px 0 0 var(--joplin-color2, #4a9cf5);
|
|
275
275
|
}
|
|
276
|
+
|
|
277
|
+
/* Search results */
|
|
278
|
+
.search-status {
|
|
279
|
+
padding: 8px 12px;
|
|
280
|
+
font-size: 11px;
|
|
281
|
+
color: var(--joplin-color-faded, #888);
|
|
282
|
+
border-bottom: 1px solid var(--joplin-divider-color, #eee);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.search-result-item {
|
|
286
|
+
display: flex;
|
|
287
|
+
align-items: flex-start;
|
|
288
|
+
padding: 8px 10px;
|
|
289
|
+
gap: 8px;
|
|
290
|
+
min-height: auto;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.search-result-item .icon {
|
|
294
|
+
margin-top: 2px;
|
|
295
|
+
flex-shrink: 0;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.search-result-content {
|
|
299
|
+
flex: 1;
|
|
300
|
+
min-width: 0;
|
|
301
|
+
overflow: hidden;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.search-result-title {
|
|
305
|
+
font-weight: 600;
|
|
306
|
+
font-size: 12px;
|
|
307
|
+
line-height: 1.4;
|
|
308
|
+
white-space: nowrap;
|
|
309
|
+
overflow: hidden;
|
|
310
|
+
text-overflow: ellipsis;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.search-result-folder {
|
|
314
|
+
font-size: 10px;
|
|
315
|
+
color: var(--joplin-color-faded, #999);
|
|
316
|
+
margin-top: 1px;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.search-result-snippet {
|
|
320
|
+
font-size: 11px;
|
|
321
|
+
color: var(--joplin-color-faded, #777);
|
|
322
|
+
margin-top: 3px;
|
|
323
|
+
line-height: 1.4;
|
|
324
|
+
display: -webkit-box;
|
|
325
|
+
-webkit-line-clamp: 2;
|
|
326
|
+
-webkit-box-orient: vertical;
|
|
327
|
+
overflow: hidden;
|
|
328
|
+
word-break: break-all;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
mark.search-highlight {
|
|
332
|
+
background: rgba(255, 213, 0, 0.4);
|
|
333
|
+
color: inherit;
|
|
334
|
+
padding: 0 1px;
|
|
335
|
+
border-radius: 2px;
|
|
336
|
+
}
|
package/publish/webview/panel.js
CHANGED
|
@@ -213,6 +213,13 @@ webviewApi.onMessage(function(msg) {
|
|
|
213
213
|
}
|
|
214
214
|
}, 2000);
|
|
215
215
|
}
|
|
216
|
+
} else if (m.name === 'searchResults') {
|
|
217
|
+
if (m.results === null) {
|
|
218
|
+
// Cleared search
|
|
219
|
+
if (_searchMode) exitSearchMode();
|
|
220
|
+
} else {
|
|
221
|
+
renderSearchResults(m.results, m.query);
|
|
222
|
+
}
|
|
216
223
|
} else if (m.name === 'selectNote') {
|
|
217
224
|
// Update selection without full re-render
|
|
218
225
|
document.querySelectorAll('.tree-item.note.selected').forEach(function(el) {
|
|
@@ -341,28 +348,93 @@ document.addEventListener('drop', function(e) {
|
|
|
341
348
|
});
|
|
342
349
|
});
|
|
343
350
|
|
|
344
|
-
// Search
|
|
351
|
+
// ======================== Content Search ========================
|
|
352
|
+
var _searchTimer = null;
|
|
353
|
+
var _searchMode = false;
|
|
354
|
+
|
|
355
|
+
function escapeRegex(str) {
|
|
356
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function highlightText(text, query) {
|
|
360
|
+
if (!query) return text;
|
|
361
|
+
// Escape HTML first
|
|
362
|
+
var escaped = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
363
|
+
var regex = new RegExp('(' + escapeRegex(query) + ')', 'gi');
|
|
364
|
+
return escaped.replace(regex, '<mark class="search-highlight">$1</mark>');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function renderSearchResults(results, query) {
|
|
368
|
+
var container = document.getElementById('tree-container');
|
|
369
|
+
if (!container) return;
|
|
370
|
+
|
|
371
|
+
if (!results || results.length === 0) {
|
|
372
|
+
container.innerHTML = '<div class="search-status">' + T('searchNoResult') + '</div>';
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
var countText = T('searchResultCount').replace('{count}', results.length);
|
|
377
|
+
var html = '<div class="search-status">' + countText + '</div>';
|
|
378
|
+
|
|
379
|
+
for (var i = 0; i < results.length; i++) {
|
|
380
|
+
var item = results[i];
|
|
381
|
+
var icon = '\uD83D\uDCDD';
|
|
382
|
+
if (item.is_todo) {
|
|
383
|
+
icon = item.todo_completed ? '\u2611' : '\u2610';
|
|
384
|
+
}
|
|
385
|
+
html += '<div class="search-result-item tree-item note" data-id="' + item.id + '" data-type="note">';
|
|
386
|
+
html += '<span class="icon note-icon">' + icon + '</span>';
|
|
387
|
+
html += '<div class="search-result-content">';
|
|
388
|
+
html += '<div class="search-result-title">' + highlightText(item.title, query) + '</div>';
|
|
389
|
+
if (item.folderName) {
|
|
390
|
+
html += '<div class="search-result-folder">\uD83D\uDCC2 ' + item.folderName + '</div>';
|
|
391
|
+
}
|
|
392
|
+
if (item.snippet) {
|
|
393
|
+
html += '<div class="search-result-snippet">' + highlightText(item.snippet, query) + '</div>';
|
|
394
|
+
}
|
|
395
|
+
html += '</div></div>';
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
container.innerHTML = html;
|
|
399
|
+
_searchMode = true;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function exitSearchMode() {
|
|
403
|
+
_searchMode = false;
|
|
404
|
+
// Trigger a refresh to restore the tree
|
|
405
|
+
postMsg({ name: 'refresh' });
|
|
406
|
+
}
|
|
407
|
+
|
|
345
408
|
document.addEventListener('input', function(e) {
|
|
346
409
|
if (e.target.id !== 'search-input') return;
|
|
347
|
-
var query = e.target.value.
|
|
410
|
+
var query = e.target.value.trim();
|
|
348
411
|
|
|
349
|
-
|
|
350
|
-
var label = item.querySelector('.label').textContent.toLowerCase();
|
|
351
|
-
item.style.display = (label.indexOf(query) >= 0 || !query) ? '' : 'none';
|
|
352
|
-
});
|
|
412
|
+
if (_searchTimer) clearTimeout(_searchTimer);
|
|
353
413
|
|
|
354
|
-
if (query) {
|
|
355
|
-
|
|
356
|
-
|
|
414
|
+
if (!query) {
|
|
415
|
+
if (_searchMode) exitSearchMode();
|
|
416
|
+
return;
|
|
357
417
|
}
|
|
358
418
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
419
|
+
// Show "searching..." immediately
|
|
420
|
+
var container = document.getElementById('tree-container');
|
|
421
|
+
if (container) {
|
|
422
|
+
container.innerHTML = '<div class="search-status">' + T('searching') + '</div>';
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Debounce: wait 400ms after typing stops
|
|
426
|
+
_searchTimer = setTimeout(function() {
|
|
427
|
+
postMsg({ name: 'search', query: query });
|
|
428
|
+
}, 400);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Handle Escape key to clear search
|
|
432
|
+
document.addEventListener('keydown', function(e) {
|
|
433
|
+
if (e.key === 'Escape') {
|
|
434
|
+
var input = document.getElementById('search-input');
|
|
435
|
+
if (input && input.value) {
|
|
436
|
+
input.value = '';
|
|
437
|
+
if (_searchMode) exitSearchMode();
|
|
438
|
+
}
|
|
439
|
+
}
|
|
368
440
|
});
|