joplin-plugin-explorer 1.1.0 → 1.1.2
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 +28 -8
- package/publish/manifest.json +11 -3
- package/publish/plugin.jpl +0 -0
- package/publish/webview/panel.css +89 -0
- package/publish/webview/panel.js +87 -19
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
|
|
|
@@ -328,6 +332,7 @@ joplin.plugins.register({
|
|
|
328
332
|
+ ' <input id="search-input" type="text" placeholder="\uD83D\uDD0D ' + t.search + '" />'
|
|
329
333
|
+ ' </div>'
|
|
330
334
|
+ ' <div id="tree-container">' + treeHtml + '</div>'
|
|
335
|
+
+ ' <div id="search-results" style="display:none;"></div>'
|
|
331
336
|
+ ' <div class="bottom-bar">'
|
|
332
337
|
+ ' <button id="btn-sync" title="' + t.sync + '">\uD83D\uDD04 ' + t.sync + '</button>'
|
|
333
338
|
+ ' </div>'
|
|
@@ -370,7 +375,7 @@ joplin.plugins.register({
|
|
|
370
375
|
var query = msg.query;
|
|
371
376
|
if (!query || !query.trim()) {
|
|
372
377
|
// Empty query: tell webview to clear search results
|
|
373
|
-
await joplin.views.panels.postMessage(panel, { name: 'searchResults', results: null, query: '' });
|
|
378
|
+
await joplin.views.panels.postMessage(panel, { name: 'searchResults', results: null, query: '', searchId: msg.searchId });
|
|
374
379
|
return;
|
|
375
380
|
}
|
|
376
381
|
try {
|
|
@@ -418,7 +423,7 @@ joplin.plugins.register({
|
|
|
418
423
|
folderName: folderNameMap[note.parent_id] || '',
|
|
419
424
|
});
|
|
420
425
|
}
|
|
421
|
-
await joplin.views.panels.postMessage(panel, { name: 'searchResults', results: items, query: query });
|
|
426
|
+
await joplin.views.panels.postMessage(panel, { name: 'searchResults', results: items, query: query, searchId: msg.searchId });
|
|
422
427
|
} catch (err) {
|
|
423
428
|
console.error('Joplin Explorer: search error', err);
|
|
424
429
|
}
|
|
@@ -446,10 +451,14 @@ joplin.plugins.register({
|
|
|
446
451
|
if (itemType === 'folder') {
|
|
447
452
|
switch (action) {
|
|
448
453
|
case 'newNote':
|
|
449
|
-
await joplin.data.post(['
|
|
454
|
+
var newNote = await joplin.data.post(['notes'], null, { title: t.newNote, parent_id: id });
|
|
455
|
+
await joplin.commands.execute('openNote', newNote.id);
|
|
456
|
+
selectedNoteId = newNote.id;
|
|
450
457
|
break;
|
|
451
458
|
case 'newTodo':
|
|
452
|
-
await joplin.data.post(['
|
|
459
|
+
var newTodo = await joplin.data.post(['notes'], null, { title: t.newTodo, parent_id: id, is_todo: 1 });
|
|
460
|
+
await joplin.commands.execute('openNote', newTodo.id);
|
|
461
|
+
selectedNoteId = newTodo.id;
|
|
453
462
|
break;
|
|
454
463
|
case 'newSubNotebook':
|
|
455
464
|
await joplin.data.post(['folders'], null, { title: t.newNotebook, parent_id: id });
|
|
@@ -461,28 +470,39 @@ joplin.plugins.register({
|
|
|
461
470
|
if (msg.newTitle) await joplin.data.put(['folders', id], null, { title: msg.newTitle });
|
|
462
471
|
break;
|
|
463
472
|
case 'exportFolder':
|
|
464
|
-
await joplin.commands.execute('exportFolders', [id]);
|
|
473
|
+
try { await joplin.commands.execute('exportFolders', [id]); } catch(e) {}
|
|
465
474
|
break;
|
|
466
475
|
}
|
|
467
476
|
} else if (itemType === 'note') {
|
|
468
477
|
switch (action) {
|
|
469
478
|
case 'openNote':
|
|
470
479
|
await joplin.commands.execute('openNote', id);
|
|
480
|
+
selectedNoteId = id;
|
|
471
481
|
break;
|
|
472
482
|
case 'openInNewWindow':
|
|
473
|
-
await joplin.commands.execute('openNoteInNewWindow', id);
|
|
483
|
+
try { await joplin.commands.execute('openNoteInNewWindow', id); } catch(e) {
|
|
484
|
+
// Fallback: just open in main window
|
|
485
|
+
await joplin.commands.execute('openNote', id);
|
|
486
|
+
}
|
|
474
487
|
break;
|
|
475
488
|
case 'copyLink':
|
|
476
489
|
var linkNote = await joplin.data.get(['notes', id], { fields: ['id', 'title'] });
|
|
477
490
|
var mdLink = '[' + linkNote.title + '](:/' + linkNote.id + ')';
|
|
478
|
-
|
|
491
|
+
try {
|
|
492
|
+
await joplin.clipboard.writeText(mdLink);
|
|
493
|
+
} catch(e) {
|
|
494
|
+
// Fallback: send link text to webview for copying
|
|
495
|
+
await joplin.views.panels.postMessage(panel, { name: 'copyText', text: mdLink });
|
|
496
|
+
}
|
|
479
497
|
break;
|
|
480
498
|
case 'duplicateNote':
|
|
481
499
|
var srcNote = await joplin.data.get(['notes', id], { fields: ['title', 'body', 'parent_id', 'is_todo'] });
|
|
482
|
-
await joplin.data.post(['notes'], null, {
|
|
500
|
+
var dupNote = await joplin.data.post(['notes'], null, {
|
|
483
501
|
title: srcNote.title + ' (copy)', body: srcNote.body,
|
|
484
502
|
parent_id: srcNote.parent_id, is_todo: srcNote.is_todo,
|
|
485
503
|
});
|
|
504
|
+
await joplin.commands.execute('openNote', dupNote.id);
|
|
505
|
+
selectedNoteId = dupNote.id;
|
|
486
506
|
break;
|
|
487
507
|
case 'switchNoteType':
|
|
488
508
|
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.2",
|
|
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
|
|
@@ -274,6 +274,25 @@ html, body {
|
|
|
274
274
|
box-shadow: 0 2px 0 0 var(--joplin-color2, #4a9cf5);
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
#search-results {
|
|
278
|
+
flex: 1;
|
|
279
|
+
overflow-y: scroll;
|
|
280
|
+
padding: 4px 0;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
#search-results::-webkit-scrollbar {
|
|
284
|
+
width: 8px;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
#search-results::-webkit-scrollbar-track {
|
|
288
|
+
background: transparent;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
#search-results::-webkit-scrollbar-thumb {
|
|
292
|
+
background: var(--joplin-color-faded, #888);
|
|
293
|
+
border-radius: 4px;
|
|
294
|
+
}
|
|
295
|
+
|
|
277
296
|
/* Search results */
|
|
278
297
|
.search-status {
|
|
279
298
|
padding: 8px 12px;
|
|
@@ -328,6 +347,76 @@ html, body {
|
|
|
328
347
|
word-break: break-all;
|
|
329
348
|
}
|
|
330
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: var(--joplin-color2, #4a9cf5) !important;
|
|
412
|
+
color: #fff !important;
|
|
413
|
+
border-color: var(--joplin-color2, #4a9cf5) !important;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.inline-input-buttons button:hover {
|
|
417
|
+
opacity: 0.85;
|
|
418
|
+
}
|
|
419
|
+
|
|
331
420
|
mark.search-highlight {
|
|
332
421
|
background: rgba(255, 213, 0, 0.4);
|
|
333
422
|
color: inherit;
|
package/publish/webview/panel.js
CHANGED
|
@@ -21,7 +21,6 @@ var _observer = new MutationObserver(function() {
|
|
|
21
21
|
if (!container) return;
|
|
22
22
|
|
|
23
23
|
if (_isFirstRender) {
|
|
24
|
-
// First render: scroll to selected note
|
|
25
24
|
_isFirstRender = false;
|
|
26
25
|
var selected = container.querySelector('.tree-item.note.selected');
|
|
27
26
|
if (selected) {
|
|
@@ -30,7 +29,6 @@ var _observer = new MutationObserver(function() {
|
|
|
30
29
|
}, 30);
|
|
31
30
|
}
|
|
32
31
|
} else {
|
|
33
|
-
// Subsequent renders: restore previous scroll position
|
|
34
32
|
container.scrollTop = _savedScrollTop;
|
|
35
33
|
}
|
|
36
34
|
});
|
|
@@ -48,6 +46,49 @@ function T(key) {
|
|
|
48
46
|
return (window._i18n && window._i18n[key]) || key;
|
|
49
47
|
}
|
|
50
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
|
+
|
|
51
92
|
// Left click: open note / toggle folder
|
|
52
93
|
document.addEventListener('click', function(e) {
|
|
53
94
|
var existingMenu = document.getElementById('ctx-menu');
|
|
@@ -135,10 +176,11 @@ document.addEventListener('click', function(e) {
|
|
|
135
176
|
|
|
136
177
|
if (action === 'renameFolder' || action === 'renameNote') {
|
|
137
178
|
var currentTitle = ctxItem.dataset.title || '';
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
179
|
+
showInlineInput(T('promptRename'), currentTitle, function(newTitle) {
|
|
180
|
+
if (newTitle !== null && newTitle.trim() !== '') {
|
|
181
|
+
postMsg({ name: 'contextMenu', action: action, id: id, itemType: itemType, newTitle: newTitle.trim() });
|
|
182
|
+
}
|
|
183
|
+
});
|
|
142
184
|
} else if (action === 'deleteFolder') {
|
|
143
185
|
if (confirm(T('confirmDeleteFolder'))) {
|
|
144
186
|
postMsg({ name: 'contextMenu', action: action, id: id, itemType: itemType });
|
|
@@ -148,10 +190,11 @@ document.addEventListener('click', function(e) {
|
|
|
148
190
|
postMsg({ name: 'contextMenu', action: action, id: id, itemType: itemType });
|
|
149
191
|
}
|
|
150
192
|
} else if (action === 'moveNote') {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
193
|
+
showInlineInput(T('promptMoveNote'), '', function(folderName) {
|
|
194
|
+
if (folderName !== null && folderName.trim() !== '') {
|
|
195
|
+
postMsg({ name: 'contextMenu', action: action, id: id, itemType: itemType, targetFolderName: folderName.trim() });
|
|
196
|
+
}
|
|
197
|
+
});
|
|
155
198
|
} else {
|
|
156
199
|
postMsg({ name: 'contextMenu', action: action, id: id, itemType: itemType });
|
|
157
200
|
}
|
|
@@ -214,12 +257,25 @@ webviewApi.onMessage(function(msg) {
|
|
|
214
257
|
}, 2000);
|
|
215
258
|
}
|
|
216
259
|
} else if (m.name === 'searchResults') {
|
|
260
|
+
// Ignore stale search results
|
|
261
|
+
if (m.searchId !== undefined && m.searchId !== _searchId) return;
|
|
217
262
|
if (m.results === null) {
|
|
218
|
-
// Cleared search
|
|
219
263
|
if (_searchMode) exitSearchMode();
|
|
220
264
|
} else {
|
|
221
265
|
renderSearchResults(m.results, m.query);
|
|
222
266
|
}
|
|
267
|
+
} else if (m.name === 'copyText') {
|
|
268
|
+
// Fallback clipboard copy via webview
|
|
269
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
270
|
+
navigator.clipboard.writeText(m.text);
|
|
271
|
+
} else {
|
|
272
|
+
var ta = document.createElement('textarea');
|
|
273
|
+
ta.value = m.text;
|
|
274
|
+
document.body.appendChild(ta);
|
|
275
|
+
ta.select();
|
|
276
|
+
document.execCommand('copy');
|
|
277
|
+
ta.remove();
|
|
278
|
+
}
|
|
223
279
|
} else if (m.name === 'selectNote') {
|
|
224
280
|
// Update selection without full re-render
|
|
225
281
|
document.querySelectorAll('.tree-item.note.selected').forEach(function(el) {
|
|
@@ -351,6 +407,7 @@ document.addEventListener('drop', function(e) {
|
|
|
351
407
|
// ======================== Content Search ========================
|
|
352
408
|
var _searchTimer = null;
|
|
353
409
|
var _searchMode = false;
|
|
410
|
+
var _searchId = 0;
|
|
354
411
|
|
|
355
412
|
function escapeRegex(str) {
|
|
356
413
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
@@ -364,10 +421,19 @@ function highlightText(text, query) {
|
|
|
364
421
|
return escaped.replace(regex, '<mark class="search-highlight">$1</mark>');
|
|
365
422
|
}
|
|
366
423
|
|
|
424
|
+
function showSearchContainer(show) {
|
|
425
|
+
var tree = document.getElementById('tree-container');
|
|
426
|
+
var search = document.getElementById('search-results');
|
|
427
|
+
if (tree) tree.style.display = show ? 'none' : '';
|
|
428
|
+
if (search) search.style.display = show ? '' : 'none';
|
|
429
|
+
}
|
|
430
|
+
|
|
367
431
|
function renderSearchResults(results, query) {
|
|
368
|
-
var container = document.getElementById('
|
|
432
|
+
var container = document.getElementById('search-results');
|
|
369
433
|
if (!container) return;
|
|
370
434
|
|
|
435
|
+
showSearchContainer(true);
|
|
436
|
+
|
|
371
437
|
if (!results || results.length === 0) {
|
|
372
438
|
container.innerHTML = '<div class="search-status">' + T('searchNoResult') + '</div>';
|
|
373
439
|
return;
|
|
@@ -401,8 +467,7 @@ function renderSearchResults(results, query) {
|
|
|
401
467
|
|
|
402
468
|
function exitSearchMode() {
|
|
403
469
|
_searchMode = false;
|
|
404
|
-
|
|
405
|
-
postMsg({ name: 'refresh' });
|
|
470
|
+
showSearchContainer(false);
|
|
406
471
|
}
|
|
407
472
|
|
|
408
473
|
document.addEventListener('input', function(e) {
|
|
@@ -410,21 +475,24 @@ document.addEventListener('input', function(e) {
|
|
|
410
475
|
var query = e.target.value.trim();
|
|
411
476
|
|
|
412
477
|
if (_searchTimer) clearTimeout(_searchTimer);
|
|
478
|
+
_searchId++;
|
|
479
|
+
var currentSearchId = _searchId;
|
|
413
480
|
|
|
414
481
|
if (!query) {
|
|
415
482
|
if (_searchMode) exitSearchMode();
|
|
416
483
|
return;
|
|
417
484
|
}
|
|
418
485
|
|
|
419
|
-
|
|
420
|
-
var
|
|
421
|
-
if (
|
|
422
|
-
|
|
486
|
+
_searchMode = true;
|
|
487
|
+
var searchContainer = document.getElementById('search-results');
|
|
488
|
+
if (searchContainer) {
|
|
489
|
+
searchContainer.innerHTML = '<div class="search-status">' + T('searching') + '</div>';
|
|
423
490
|
}
|
|
491
|
+
showSearchContainer(true);
|
|
424
492
|
|
|
425
493
|
// Debounce: wait 400ms after typing stops
|
|
426
494
|
_searchTimer = setTimeout(function() {
|
|
427
|
-
postMsg({ name: 'search', query: query });
|
|
495
|
+
postMsg({ name: 'search', query: query, searchId: currentSearchId });
|
|
428
496
|
}, 400);
|
|
429
497
|
});
|
|
430
498
|
|