joplin-plugin-explorer 1.1.4 → 1.2.1
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 +63 -66
- package/package.json +25 -26
- package/publish/index.js +1 -708
- package/publish/manifest.json +13 -18
- package/publish/plugin.jpl +0 -0
- package/publish/webview/panel.css +182 -8
- package/publish/webview/panel.js +1 -485
package/publish/webview/panel.js
CHANGED
|
@@ -1,485 +1 @@
|
|
|
1
|
-
/* Webview script for Notes In List panel */
|
|
2
|
-
|
|
3
|
-
// Persist scroll position across setHtml calls
|
|
4
|
-
var _savedScrollTop = 0;
|
|
5
|
-
var _isFirstRender = true;
|
|
6
|
-
|
|
7
|
-
function postMsg(msg) {
|
|
8
|
-
webviewApi.postMessage(msg);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// Save scroll position continuously
|
|
12
|
-
document.addEventListener('scroll', function(e) {
|
|
13
|
-
if (e.target && e.target.id === 'tree-container') {
|
|
14
|
-
_savedScrollTop = e.target.scrollTop;
|
|
15
|
-
}
|
|
16
|
-
}, true);
|
|
17
|
-
|
|
18
|
-
// Observe DOM changes to restore scroll position after setHtml
|
|
19
|
-
var _observer = new MutationObserver(function() {
|
|
20
|
-
var container = document.getElementById('tree-container');
|
|
21
|
-
if (!container) return;
|
|
22
|
-
|
|
23
|
-
if (_isFirstRender) {
|
|
24
|
-
_isFirstRender = false;
|
|
25
|
-
var selected = container.querySelector('.tree-item.note.selected');
|
|
26
|
-
if (selected) {
|
|
27
|
-
setTimeout(function() {
|
|
28
|
-
selected.scrollIntoView({ block: 'nearest', behavior: 'instant' });
|
|
29
|
-
}, 30);
|
|
30
|
-
}
|
|
31
|
-
} else {
|
|
32
|
-
container.scrollTop = _savedScrollTop;
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
_observer.observe(document.body, { childList: true, subtree: true });
|
|
36
|
-
|
|
37
|
-
function loadI18n() {
|
|
38
|
-
var root = document.getElementById('notes-in-list-root');
|
|
39
|
-
if (root && root.dataset.i18n) {
|
|
40
|
-
try { window._i18n = JSON.parse(root.dataset.i18n); } catch(e) {}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function T(key) {
|
|
45
|
-
if (!window._i18n) loadI18n();
|
|
46
|
-
return (window._i18n && window._i18n[key]) || key;
|
|
47
|
-
}
|
|
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
|
-
|
|
92
|
-
// Left click: open note / toggle folder
|
|
93
|
-
document.addEventListener('click', function(e) {
|
|
94
|
-
var existingMenu = document.getElementById('ctx-menu');
|
|
95
|
-
if (existingMenu) existingMenu.remove();
|
|
96
|
-
|
|
97
|
-
var item = e.target.closest('.tree-item');
|
|
98
|
-
if (!item) return;
|
|
99
|
-
|
|
100
|
-
var type = item.dataset.type;
|
|
101
|
-
var id = item.dataset.id;
|
|
102
|
-
|
|
103
|
-
if (type === 'note') {
|
|
104
|
-
document.querySelectorAll('.tree-item.note.selected').forEach(function(el) {
|
|
105
|
-
el.classList.remove('selected');
|
|
106
|
-
});
|
|
107
|
-
item.classList.add('selected');
|
|
108
|
-
postMsg({ name: 'openNote', id: id });
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (type === 'folder') {
|
|
112
|
-
postMsg({ name: 'toggleFolder', id: id });
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
// Right click: context menu
|
|
117
|
-
document.addEventListener('contextmenu', function(e) {
|
|
118
|
-
var existingMenu = document.getElementById('ctx-menu');
|
|
119
|
-
if (existingMenu) existingMenu.remove();
|
|
120
|
-
|
|
121
|
-
var item = e.target.closest('.tree-item');
|
|
122
|
-
if (!item) return;
|
|
123
|
-
|
|
124
|
-
e.preventDefault();
|
|
125
|
-
|
|
126
|
-
var type = item.dataset.type;
|
|
127
|
-
var id = item.dataset.id;
|
|
128
|
-
var title = '';
|
|
129
|
-
var labelEl = item.querySelector('.label');
|
|
130
|
-
if (labelEl) title = labelEl.textContent;
|
|
131
|
-
|
|
132
|
-
var menuHtml = '<div id="ctx-menu" class="context-menu" style="left:' + e.pageX + 'px;top:' + e.pageY + 'px;">';
|
|
133
|
-
|
|
134
|
-
if (type === 'folder') {
|
|
135
|
-
menuHtml += '<div class="ctx-item" data-action="newNote" data-id="' + id + '" data-type="folder">' + T('ctxNewNoteHere') + '</div>';
|
|
136
|
-
menuHtml += '<div class="ctx-item" data-action="newTodo" data-id="' + id + '" data-type="folder">' + T('ctxNewTodoHere') + '</div>';
|
|
137
|
-
menuHtml += '<div class="ctx-item" data-action="newSubNotebook" data-id="' + id + '" data-type="folder">' + T('ctxNewSubNotebook') + '</div>';
|
|
138
|
-
menuHtml += '<div class="ctx-sep"></div>';
|
|
139
|
-
menuHtml += '<div class="ctx-item" data-action="renameFolder" data-id="' + id + '" data-type="folder" data-title="' + title.replace(/"/g, '"') + '">' + T('ctxRenameFolder') + '</div>';
|
|
140
|
-
menuHtml += '<div class="ctx-item" data-action="exportFolder" data-id="' + id + '" data-type="folder">' + T('ctxExportFolder') + '</div>';
|
|
141
|
-
menuHtml += '<div class="ctx-sep"></div>';
|
|
142
|
-
menuHtml += '<div class="ctx-item ctx-danger" data-action="deleteFolder" data-id="' + id + '" data-type="folder">' + T('ctxDeleteFolder') + '</div>';
|
|
143
|
-
} else if (type === 'note') {
|
|
144
|
-
menuHtml += '<div class="ctx-item" data-action="openNote" data-id="' + id + '" data-type="note">' + T('ctxOpenNote') + '</div>';
|
|
145
|
-
menuHtml += '<div class="ctx-item" data-action="openInNewWindow" data-id="' + id + '" data-type="note">' + T('ctxOpenInNewWindow') + '</div>';
|
|
146
|
-
menuHtml += '<div class="ctx-sep"></div>';
|
|
147
|
-
menuHtml += '<div class="ctx-item" data-action="copyLink" data-id="' + id + '" data-type="note">' + T('ctxCopyLink') + '</div>';
|
|
148
|
-
menuHtml += '<div class="ctx-item" data-action="duplicateNote" data-id="' + id + '" data-type="note">' + T('ctxDuplicateNote') + '</div>';
|
|
149
|
-
menuHtml += '<div class="ctx-item" data-action="switchNoteType" data-id="' + id + '" data-type="note">' + T('ctxSwitchNoteType') + '</div>';
|
|
150
|
-
menuHtml += '<div class="ctx-item" data-action="toggleTodo" data-id="' + id + '" data-type="note">' + T('ctxToggleTodo') + '</div>';
|
|
151
|
-
menuHtml += '<div class="ctx-sep"></div>';
|
|
152
|
-
menuHtml += '<div class="ctx-item" data-action="renameNote" data-id="' + id + '" data-type="note" data-title="' + title.replace(/"/g, '"') + '">' + T('ctxRenameNote') + '</div>';
|
|
153
|
-
menuHtml += '<div class="ctx-item" data-action="noteInfo" data-id="' + id + '" data-type="note">' + T('ctxNoteInfo') + '</div>';
|
|
154
|
-
menuHtml += '<div class="ctx-sep"></div>';
|
|
155
|
-
menuHtml += '<div class="ctx-item ctx-danger" data-action="deleteNote" data-id="' + id + '" data-type="note">' + T('ctxDeleteNote') + '</div>';
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
menuHtml += '</div>';
|
|
159
|
-
document.body.insertAdjacentHTML('beforeend', menuHtml);
|
|
160
|
-
|
|
161
|
-
var menu = document.getElementById('ctx-menu');
|
|
162
|
-
var rect = menu.getBoundingClientRect();
|
|
163
|
-
if (rect.right > window.innerWidth) menu.style.left = (window.innerWidth - rect.width - 4) + 'px';
|
|
164
|
-
if (rect.bottom > window.innerHeight) menu.style.top = (window.innerHeight - rect.height - 4) + 'px';
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
// Context menu item click
|
|
168
|
-
document.addEventListener('click', function(e) {
|
|
169
|
-
var ctxItem = e.target.closest('.ctx-item');
|
|
170
|
-
if (!ctxItem) return;
|
|
171
|
-
|
|
172
|
-
var action = ctxItem.dataset.action;
|
|
173
|
-
var id = ctxItem.dataset.id;
|
|
174
|
-
var itemType = ctxItem.dataset.type;
|
|
175
|
-
|
|
176
|
-
// All dialogs handled by backend using native Joplin dialogs
|
|
177
|
-
postMsg({ name: 'contextMenu', action: action, id: id, itemType: itemType });
|
|
178
|
-
|
|
179
|
-
var menu = document.getElementById('ctx-menu');
|
|
180
|
-
if (menu) menu.remove();
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// Close context menu
|
|
184
|
-
document.addEventListener('mousedown', function(e) {
|
|
185
|
-
if (!e.target.closest('#ctx-menu')) {
|
|
186
|
-
var menu = document.getElementById('ctx-menu');
|
|
187
|
-
if (menu) menu.remove();
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// Toolbar buttons
|
|
192
|
-
document.addEventListener('click', function(e) {
|
|
193
|
-
var btn = e.target.closest('button');
|
|
194
|
-
if (!btn) return;
|
|
195
|
-
|
|
196
|
-
switch (btn.id) {
|
|
197
|
-
case 'btn-new-notebook': postMsg({ name: 'newNotebook' }); break;
|
|
198
|
-
case 'btn-new-note': postMsg({ name: 'newNote' }); break;
|
|
199
|
-
case 'btn-new-todo': postMsg({ name: 'newTodo' }); break;
|
|
200
|
-
case 'btn-sort': postMsg({ name: 'cycleSort' }); break;
|
|
201
|
-
case 'btn-collapse-all': postMsg({ name: 'collapseAll' }); break;
|
|
202
|
-
case 'btn-sync':
|
|
203
|
-
var syncBtn = document.getElementById('btn-sync');
|
|
204
|
-
if (syncBtn && !syncBtn.disabled) {
|
|
205
|
-
syncBtn.disabled = true;
|
|
206
|
-
syncBtn.classList.add('syncing');
|
|
207
|
-
syncBtn.textContent = '\uD83D\uDD04 ' + T('syncing');
|
|
208
|
-
postMsg({ name: 'sync' });
|
|
209
|
-
}
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// Listen for messages from plugin backend
|
|
215
|
-
webviewApi.onMessage(function(msg) {
|
|
216
|
-
if (!msg || !msg.message) return;
|
|
217
|
-
var m = msg.message;
|
|
218
|
-
|
|
219
|
-
if (m.name === 'syncState') {
|
|
220
|
-
var syncBtn = document.getElementById('btn-sync');
|
|
221
|
-
if (!syncBtn) return;
|
|
222
|
-
if (m.state === 'done') {
|
|
223
|
-
syncBtn.classList.remove('syncing');
|
|
224
|
-
syncBtn.classList.add('sync-done');
|
|
225
|
-
syncBtn.textContent = T('syncDone');
|
|
226
|
-
// Show "done" for 2 seconds, then restore
|
|
227
|
-
setTimeout(function() {
|
|
228
|
-
var btn = document.getElementById('btn-sync');
|
|
229
|
-
if (btn) {
|
|
230
|
-
btn.disabled = false;
|
|
231
|
-
btn.classList.remove('sync-done');
|
|
232
|
-
btn.textContent = '\uD83D\uDD04 ' + T('sync');
|
|
233
|
-
}
|
|
234
|
-
}, 2000);
|
|
235
|
-
}
|
|
236
|
-
} else if (m.name === 'searchResults') {
|
|
237
|
-
// Ignore stale search results
|
|
238
|
-
if (m.searchId !== undefined && m.searchId !== _searchId) return;
|
|
239
|
-
if (m.results === null) {
|
|
240
|
-
if (_searchMode) exitSearchMode();
|
|
241
|
-
} else {
|
|
242
|
-
renderSearchResults(m.results, m.query);
|
|
243
|
-
}
|
|
244
|
-
} else if (m.name === 'copyText') {
|
|
245
|
-
// Fallback clipboard copy via webview
|
|
246
|
-
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
247
|
-
navigator.clipboard.writeText(m.text);
|
|
248
|
-
} else {
|
|
249
|
-
var ta = document.createElement('textarea');
|
|
250
|
-
ta.value = m.text;
|
|
251
|
-
document.body.appendChild(ta);
|
|
252
|
-
ta.select();
|
|
253
|
-
document.execCommand('copy');
|
|
254
|
-
ta.remove();
|
|
255
|
-
}
|
|
256
|
-
} else if (m.name === 'selectNote') {
|
|
257
|
-
// Update selection without full re-render
|
|
258
|
-
document.querySelectorAll('.tree-item.note.selected').forEach(function(el) {
|
|
259
|
-
el.classList.remove('selected');
|
|
260
|
-
});
|
|
261
|
-
var noteEl = document.querySelector('.tree-item.note[data-id="' + m.id + '"]');
|
|
262
|
-
if (noteEl) {
|
|
263
|
-
noteEl.classList.add('selected');
|
|
264
|
-
noteEl.scrollIntoView({ block: 'nearest', behavior: 'instant' });
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
// ======================== Drag & Drop ========================
|
|
270
|
-
// Make tree items draggable
|
|
271
|
-
document.addEventListener('mousedown', function(e) {
|
|
272
|
-
var item = e.target.closest('.tree-item');
|
|
273
|
-
if (!item || e.button !== 0) return;
|
|
274
|
-
item.setAttribute('draggable', 'true');
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
document.addEventListener('dragstart', function(e) {
|
|
278
|
-
var item = e.target.closest('.tree-item');
|
|
279
|
-
if (!item) return;
|
|
280
|
-
e.dataTransfer.setData('text/plain', JSON.stringify({
|
|
281
|
-
id: item.dataset.id,
|
|
282
|
-
type: item.dataset.type,
|
|
283
|
-
}));
|
|
284
|
-
e.dataTransfer.effectAllowed = 'move';
|
|
285
|
-
item.classList.add('dragging');
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
document.addEventListener('dragend', function(e) {
|
|
289
|
-
var item = e.target.closest('.tree-item');
|
|
290
|
-
if (item) {
|
|
291
|
-
item.classList.remove('dragging');
|
|
292
|
-
item.removeAttribute('draggable');
|
|
293
|
-
}
|
|
294
|
-
// Clean up all drop indicators
|
|
295
|
-
document.querySelectorAll('.drop-target').forEach(function(el) { el.classList.remove('drop-target'); });
|
|
296
|
-
document.querySelectorAll('.drop-above').forEach(function(el) { el.classList.remove('drop-above'); });
|
|
297
|
-
document.querySelectorAll('.drop-below').forEach(function(el) { el.classList.remove('drop-below'); });
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
document.addEventListener('dragover', function(e) {
|
|
301
|
-
var target = e.target.closest('.tree-item');
|
|
302
|
-
if (!target) return;
|
|
303
|
-
|
|
304
|
-
e.preventDefault();
|
|
305
|
-
e.dataTransfer.dropEffect = 'move';
|
|
306
|
-
|
|
307
|
-
// Clean previous indicators
|
|
308
|
-
document.querySelectorAll('.drop-target').forEach(function(el) { el.classList.remove('drop-target'); });
|
|
309
|
-
document.querySelectorAll('.drop-above').forEach(function(el) { el.classList.remove('drop-above'); });
|
|
310
|
-
document.querySelectorAll('.drop-below').forEach(function(el) { el.classList.remove('drop-below'); });
|
|
311
|
-
|
|
312
|
-
var targetType = target.dataset.type;
|
|
313
|
-
var rect = target.getBoundingClientRect();
|
|
314
|
-
var y = e.clientY - rect.top;
|
|
315
|
-
var height = rect.height;
|
|
316
|
-
|
|
317
|
-
if (targetType === 'folder') {
|
|
318
|
-
// Top 25%: drop above, middle 50%: drop into, bottom 25%: drop below
|
|
319
|
-
if (y < height * 0.25) {
|
|
320
|
-
target.classList.add('drop-above');
|
|
321
|
-
} else if (y > height * 0.75) {
|
|
322
|
-
target.classList.add('drop-below');
|
|
323
|
-
} else {
|
|
324
|
-
target.classList.add('drop-target');
|
|
325
|
-
}
|
|
326
|
-
} else {
|
|
327
|
-
// Notes: just show drop-target (will move to same folder)
|
|
328
|
-
if (y < height * 0.5) {
|
|
329
|
-
target.classList.add('drop-above');
|
|
330
|
-
} else {
|
|
331
|
-
target.classList.add('drop-below');
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
document.addEventListener('dragleave', function(e) {
|
|
337
|
-
var target = e.target.closest('.tree-item');
|
|
338
|
-
if (target) {
|
|
339
|
-
target.classList.remove('drop-target');
|
|
340
|
-
target.classList.remove('drop-above');
|
|
341
|
-
target.classList.remove('drop-below');
|
|
342
|
-
}
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
document.addEventListener('drop', function(e) {
|
|
346
|
-
e.preventDefault();
|
|
347
|
-
var target = e.target.closest('.tree-item');
|
|
348
|
-
if (!target) return;
|
|
349
|
-
|
|
350
|
-
var data;
|
|
351
|
-
try { data = JSON.parse(e.dataTransfer.getData('text/plain')); } catch(err) { return; }
|
|
352
|
-
|
|
353
|
-
var targetId = target.dataset.id;
|
|
354
|
-
var targetType = target.dataset.type;
|
|
355
|
-
var dragId = data.id;
|
|
356
|
-
var dragType = data.type;
|
|
357
|
-
|
|
358
|
-
if (dragId === targetId) return; // Can't drop on self
|
|
359
|
-
|
|
360
|
-
var rect = target.getBoundingClientRect();
|
|
361
|
-
var y = e.clientY - rect.top;
|
|
362
|
-
var height = rect.height;
|
|
363
|
-
|
|
364
|
-
if (targetType === 'folder') {
|
|
365
|
-
if (y >= height * 0.25 && y <= height * 0.75) {
|
|
366
|
-
// Drop INTO folder
|
|
367
|
-
postMsg({ name: 'dragDrop', dragId: dragId, dragType: dragType, targetId: targetId, position: 'into' });
|
|
368
|
-
} else {
|
|
369
|
-
// Drop above/below folder (reorder)
|
|
370
|
-
var pos = y < height * 0.25 ? 'above' : 'below';
|
|
371
|
-
postMsg({ name: 'dragDrop', dragId: dragId, dragType: dragType, targetId: targetId, position: pos });
|
|
372
|
-
}
|
|
373
|
-
} else {
|
|
374
|
-
// Drop on note -> move to same folder as note
|
|
375
|
-
postMsg({ name: 'dragDrop', dragId: dragId, dragType: dragType, targetId: targetId, position: 'into' });
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Clean up
|
|
379
|
-
document.querySelectorAll('.drop-target, .drop-above, .drop-below').forEach(function(el) {
|
|
380
|
-
el.classList.remove('drop-target', 'drop-above', 'drop-below');
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
// ======================== Content Search ========================
|
|
385
|
-
var _searchTimer = null;
|
|
386
|
-
var _searchMode = false;
|
|
387
|
-
var _searchId = 0;
|
|
388
|
-
|
|
389
|
-
function escapeRegex(str) {
|
|
390
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function highlightText(text, query) {
|
|
394
|
-
if (!query) return text;
|
|
395
|
-
// Escape HTML first
|
|
396
|
-
var escaped = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
397
|
-
var regex = new RegExp('(' + escapeRegex(query) + ')', 'gi');
|
|
398
|
-
return escaped.replace(regex, '<mark class="search-highlight">$1</mark>');
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function showSearchContainer(show) {
|
|
402
|
-
var tree = document.getElementById('tree-container');
|
|
403
|
-
var search = document.getElementById('search-results');
|
|
404
|
-
if (tree) tree.style.display = show ? 'none' : '';
|
|
405
|
-
if (search) search.style.display = show ? '' : 'none';
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function renderSearchResults(results, query) {
|
|
409
|
-
var container = document.getElementById('search-results');
|
|
410
|
-
if (!container) return;
|
|
411
|
-
|
|
412
|
-
showSearchContainer(true);
|
|
413
|
-
|
|
414
|
-
if (!results || results.length === 0) {
|
|
415
|
-
container.innerHTML = '<div class="search-status">' + T('searchNoResult') + '</div>';
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
var countText = T('searchResultCount').replace('{count}', results.length);
|
|
420
|
-
var html = '<div class="search-status">' + countText + '</div>';
|
|
421
|
-
|
|
422
|
-
for (var i = 0; i < results.length; i++) {
|
|
423
|
-
var item = results[i];
|
|
424
|
-
var icon = '\uD83D\uDCDD';
|
|
425
|
-
if (item.is_todo) {
|
|
426
|
-
icon = item.todo_completed ? '\u2611' : '\u2610';
|
|
427
|
-
}
|
|
428
|
-
html += '<div class="search-result-item tree-item note" data-id="' + item.id + '" data-type="note">';
|
|
429
|
-
html += '<span class="icon note-icon">' + icon + '</span>';
|
|
430
|
-
html += '<div class="search-result-content">';
|
|
431
|
-
html += '<div class="search-result-title">' + highlightText(item.title, query) + '</div>';
|
|
432
|
-
if (item.folderName) {
|
|
433
|
-
html += '<div class="search-result-folder">\uD83D\uDCC2 ' + item.folderName + '</div>';
|
|
434
|
-
}
|
|
435
|
-
if (item.snippet) {
|
|
436
|
-
html += '<div class="search-result-snippet">' + highlightText(item.snippet, query) + '</div>';
|
|
437
|
-
}
|
|
438
|
-
html += '</div></div>';
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
container.innerHTML = html;
|
|
442
|
-
_searchMode = true;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
function exitSearchMode() {
|
|
446
|
-
_searchMode = false;
|
|
447
|
-
showSearchContainer(false);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
document.addEventListener('input', function(e) {
|
|
451
|
-
if (e.target.id !== 'search-input') return;
|
|
452
|
-
var query = e.target.value.trim();
|
|
453
|
-
|
|
454
|
-
if (_searchTimer) clearTimeout(_searchTimer);
|
|
455
|
-
_searchId++;
|
|
456
|
-
var currentSearchId = _searchId;
|
|
457
|
-
|
|
458
|
-
if (!query) {
|
|
459
|
-
if (_searchMode) exitSearchMode();
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
_searchMode = true;
|
|
464
|
-
var searchContainer = document.getElementById('search-results');
|
|
465
|
-
if (searchContainer) {
|
|
466
|
-
searchContainer.innerHTML = '<div class="search-status">' + T('searching') + '</div>';
|
|
467
|
-
}
|
|
468
|
-
showSearchContainer(true);
|
|
469
|
-
|
|
470
|
-
// Debounce: wait 400ms after typing stops
|
|
471
|
-
_searchTimer = setTimeout(function() {
|
|
472
|
-
postMsg({ name: 'search', query: query, searchId: currentSearchId });
|
|
473
|
-
}, 400);
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
// Handle Escape key to clear search
|
|
477
|
-
document.addEventListener('keydown', function(e) {
|
|
478
|
-
if (e.key === 'Escape') {
|
|
479
|
-
var input = document.getElementById('search-input');
|
|
480
|
-
if (input && input.value) {
|
|
481
|
-
input.value = '';
|
|
482
|
-
if (_searchMode) exitSearchMode();
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
});
|
|
1
|
+
var _savedScrollTop=0,_isFirstRender=!0;function postMsg(e){webviewApi.postMessage(e)}document.addEventListener("scroll",function(e){e.target&&"tree-container"===e.target.id&&(_savedScrollTop=e.target.scrollTop)},!0);var _observer=new MutationObserver(function(){var e=document.getElementById("tree-container");if(e)if(_isFirstRender){_isFirstRender=!1;var t=e.querySelector(".tree-item.note.selected");t&&setTimeout(function(){t.scrollIntoView({block:"nearest",behavior:"instant"})},30)}else e.scrollTop=_savedScrollTop});function loadI18n(){var e=document.getElementById("notes-in-list-root");if(e&&e.dataset.i18n)try{window._i18n=JSON.parse(e.dataset.i18n)}catch(e){}}function T(e){return window._i18n||loadI18n(),window._i18n&&window._i18n[e]||e}function getPinnedData(){var e=document.getElementById("notes-in-list-root");if(e&&e.dataset.pinned)try{return JSON.parse(e.dataset.pinned)}catch(e){}return[]}function isPinned(e){for(var t=getPinnedData(),a=0;a<t.length;a++)if(t[a].id===e)return!0;return!1}function showInlineInput(e,t,a){var n=document.getElementById("inline-input-overlay");n&&n.remove();var i=document.createElement("div");i.id="inline-input-overlay",i.className="inline-input-overlay",i.innerHTML='<div class="inline-input-dialog"><div class="inline-input-label">'+e+'</div><input class="inline-input-field" type="text" value="'+(t||"").replace(/"/g,""")+'" /><div class="inline-input-buttons"><button class="inline-input-ok">OK</button><button class="inline-input-cancel">'+(T("cancel")||"Cancel")+"</button></div></div>",document.body.appendChild(i);var s=i.querySelector(".inline-input-field");function d(){var e=s.value;i.remove(),a(e)}function o(){i.remove(),a(null)}s.focus(),s.select(),i.querySelector(".inline-input-ok").addEventListener("click",d),i.querySelector(".inline-input-cancel").addEventListener("click",o),s.addEventListener("keydown",function(e){"Enter"===e.key&&d(),"Escape"===e.key&&o()}),i.addEventListener("click",function(e){e.target===i&&o()})}function clearDropIndicators(){document.querySelectorAll(".drop-target").forEach(function(e){e.classList.remove("drop-target")}),document.querySelectorAll(".drop-above").forEach(function(e){e.classList.remove("drop-above")}),document.querySelectorAll(".drop-below").forEach(function(e){e.classList.remove("drop-below")})}function endDrag(){clearDropIndicators();var e=document.getElementById("tree-container");e&&e.classList.remove("dragging-active")}_observer.observe(document.body,{childList:!0,subtree:!0}),document.addEventListener("click",function(e){var t=document.getElementById("ctx-menu");t&&t.remove();var a=e.target.closest(".search-tag-item");if(a){var n=a.dataset.tagId;n&&postMsg({name:"loadTagNotes",tagId:n})}else{var i=e.target.closest(".search-folder-item");if(i){var s=i.dataset.folderId;s&&postMsg({name:"locateFolder",folderId:s})}else{var d=e.target.closest(".tree-item");if(d){var o=d.dataset.type,r=d.dataset.id,c=d.classList.contains("pinned-item");"note"===o&&(document.querySelectorAll(".tree-item.note.selected").forEach(function(e){e.classList.remove("selected")}),d.classList.add("selected"),postMsg({name:"openNote",id:r})),"folder"===o&&postMsg(c?{name:"locatePinnedFolder",folderId:r}:{name:"toggleFolder",id:r})}}}}),document.addEventListener("contextmenu",function(e){var t=document.getElementById("ctx-menu");t&&t.remove();var a=e.target.closest(".tree-item");if(a){e.preventDefault();var n=a.dataset.type,i=a.dataset.id,s="",d=a.querySelector(".label");d&&(s=d.textContent);var o='<div id="ctx-menu" class="context-menu" style="left:'+e.pageX+"px;top:"+e.pageY+'px;">';if("folder"===n){var r=isPinned(i);o+='<div class="ctx-item" data-action="'+(r?"unpinFolder":"pinFolder")+'" data-id="'+i+'" data-type="folder">'+T(r?"ctxUnpin":"ctxPin")+"</div>",o+='<div class="ctx-sep"></div>',o+='<div class="ctx-item" data-action="newNote" data-id="'+i+'" data-type="folder">'+T("ctxNewNoteHere")+"</div>",o+='<div class="ctx-item" data-action="newTodo" data-id="'+i+'" data-type="folder">'+T("ctxNewTodoHere")+"</div>",o+='<div class="ctx-item" data-action="newSubNotebook" data-id="'+i+'" data-type="folder">'+T("ctxNewSubNotebook")+"</div>",o+='<div class="ctx-sep"></div>',o+='<div class="ctx-item" data-action="renameFolder" data-id="'+i+'" data-type="folder" data-title="'+s.replace(/"/g,""")+'">'+T("ctxRenameFolder")+"</div>",o+='<div class="ctx-item" data-action="exportFolder" data-id="'+i+'" data-type="folder">'+T("ctxExportFolder")+"</div>",o+='<div class="ctx-sep"></div>',o+='<div class="ctx-item ctx-danger" data-action="deleteFolder" data-id="'+i+'" data-type="folder">'+T("ctxDeleteFolder")+"</div>"}else if("note"===n){var c=isPinned(i);o+='<div class="ctx-item" data-action="'+(c?"unpinNote":"pinNote")+'" data-id="'+i+'" data-type="note">'+T(c?"ctxUnpin":"ctxPin")+"</div>",o+='<div class="ctx-sep"></div>',o+='<div class="ctx-item" data-action="openNote" data-id="'+i+'" data-type="note">'+T("ctxOpenNote")+"</div>",o+='<div class="ctx-item" data-action="openInNewWindow" data-id="'+i+'" data-type="note">'+T("ctxOpenInNewWindow")+"</div>",o+='<div class="ctx-sep"></div>',o+='<div class="ctx-item" data-action="copyLink" data-id="'+i+'" data-type="note">'+T("ctxCopyLink")+"</div>",o+='<div class="ctx-item" data-action="duplicateNote" data-id="'+i+'" data-type="note">'+T("ctxDuplicateNote")+"</div>",o+='<div class="ctx-item" data-action="switchNoteType" data-id="'+i+'" data-type="note">'+T("ctxSwitchNoteType")+"</div>",o+='<div class="ctx-item" data-action="toggleTodo" data-id="'+i+'" data-type="note">'+T("ctxToggleTodo")+"</div>",o+='<div class="ctx-sep"></div>',o+='<div class="ctx-item" data-action="renameNote" data-id="'+i+'" data-type="note" data-title="'+s.replace(/"/g,""")+'">'+T("ctxRenameNote")+"</div>",o+='<div class="ctx-item" data-action="noteInfo" data-id="'+i+'" data-type="note">'+T("ctxNoteInfo")+"</div>",o+='<div class="ctx-sep"></div>',o+='<div class="ctx-item ctx-danger" data-action="deleteNote" data-id="'+i+'" data-type="note">'+T("ctxDeleteNote")+"</div>"}o+="</div>",document.body.insertAdjacentHTML("beforeend",o);var l=document.getElementById("ctx-menu"),v=l.getBoundingClientRect();v.right>window.innerWidth&&(l.style.left=window.innerWidth-v.width-4+"px"),v.bottom>window.innerHeight&&(l.style.top=window.innerHeight-v.height-4+"px")}}),document.addEventListener("click",function(e){var t=e.target.closest(".ctx-item");if(t){postMsg({name:"contextMenu",action:t.dataset.action,id:t.dataset.id,itemType:t.dataset.type});var a=document.getElementById("ctx-menu");a&&a.remove()}}),document.addEventListener("mousedown",function(e){if(!e.target.closest("#ctx-menu")){var t=document.getElementById("ctx-menu");t&&t.remove()}}),document.addEventListener("click",function(e){e.target.closest(".pinned-section-header")&&postMsg({name:"togglePinnedCollapse"})}),document.addEventListener("click",function(e){var t=e.target.closest(".search-section-header");if(t){var a=t.dataset.section,n=document.getElementById("search-section-"+a);if(n){var i=t.querySelector(".section-toggle");n.classList.contains("collapsed")?(n.classList.remove("collapsed"),i&&(i.textContent="▼")):(n.classList.add("collapsed"),i&&(i.textContent="▶"))}}}),document.addEventListener("click",function(e){var t=e.target.closest("button");if(t)switch(t.id){case"btn-new-notebook":postMsg({name:"newNotebook"});break;case"btn-new-note":postMsg({name:"newNote"});break;case"btn-new-todo":postMsg({name:"newTodo"});break;case"btn-sort":postMsg({name:"cycleSort"});break;case"btn-collapse-all":postMsg({name:"collapseAll"});break;case"btn-sync":var a=document.getElementById("btn-sync");a&&!a.disabled&&(a.disabled=!0,a.classList.add("syncing"),a.textContent="🔄 "+T("syncing"),postMsg({name:"sync"}))}}),webviewApi.onMessage(function(e){if(e&&e.message){var t=e.message;if("syncState"===t.name){var a=document.getElementById("btn-sync");if(!a)return;"done"===t.state&&(a.classList.remove("syncing"),a.classList.add("sync-done"),a.textContent=T("syncDone"),setTimeout(function(){var e=document.getElementById("btn-sync");e&&(e.disabled=!1,e.classList.remove("sync-done"),e.textContent="🔄 "+T("sync"))},2e3))}else if("searchResults"===t.name){if(void 0!==t.searchId&&t.searchId!==_searchId)return;t.notes||t.tags||t.folders?renderSearchResults(t.notes||[],t.tags||[],t.folders||[],t.query):_searchMode&&exitSearchMode()}else if("tagNotes"===t.name)expandTagNotes(t.tagId,t.notes);else if("scrollToFolder"===t.name)setTimeout(function(){var e=document.querySelector('.tree-item.folder:not(.pinned-item)[data-id="'+t.folderId+'"]');e&&(e.scrollIntoView({block:"center",behavior:"smooth"}),e.classList.add("locate-flash"),setTimeout(function(){e.classList.remove("locate-flash")},1500))},80);else if("exitSearch"===t.name){var n=document.getElementById("search-input");n&&(n.value=""),_searchMode&&exitSearchMode()}else if("exitSearchAndLocate"===t.name){var i=document.getElementById("search-input");i&&(i.value=""),_searchMode=!1,showSearchContainer(!1),setTimeout(function(){var e=document.querySelector('.tree-item.folder[data-id="'+t.folderId+'"]');e&&(e.scrollIntoView({block:"center",behavior:"smooth"}),e.classList.add("locate-flash"),setTimeout(function(){e.classList.remove("locate-flash")},1500))},80)}else if("copyText"===t.name)if(navigator.clipboard&&navigator.clipboard.writeText)navigator.clipboard.writeText(t.text);else{var s=document.createElement("textarea");s.value=t.text,document.body.appendChild(s),s.select(),document.execCommand("copy"),s.remove()}else if("selectNote"===t.name){document.querySelectorAll(".tree-item.note.selected").forEach(function(e){e.classList.remove("selected")});var d=document.querySelector('.tree-item.note[data-id="'+t.id+'"]');d&&(d.classList.add("selected"),d.scrollIntoView({block:"nearest",behavior:"instant"}))}}}),document.addEventListener("mousedown",function(e){var t=e.target.closest(".tree-item");t&&0===e.button&&t.setAttribute("draggable","true")}),document.addEventListener("dragstart",function(e){var t=e.target.closest(".tree-item");if(t){var a=t.classList.contains("pinned-item");e.dataTransfer.setData("text/plain",JSON.stringify({id:t.dataset.id,type:t.dataset.type,pinned:a})),e.dataTransfer.effectAllowed="move",t.classList.add("dragging");var n=document.getElementById("tree-container");n&&n.classList.add("dragging-active")}}),document.addEventListener("dragend",function(e){var t=e.target.closest(".tree-item");t&&(t.classList.remove("dragging"),t.removeAttribute("draggable")),endDrag()}),document.addEventListener("dragover",function(e){var t=e.target;if(t&&3===t.nodeType&&(t=t.parentElement),t){var a=t.closest(".tree-item");if(document.getElementById("tree-container"),t.closest("#drop-zone-empty"))return e.preventDefault(),void(e.dataTransfer.dropEffect="move");if(t.closest(".pinned-section-header")||t.closest(".pinned-section-body")){if(e.preventDefault(),e.dataTransfer.dropEffect="move",clearDropIndicators(),a&&a.classList.contains("pinned-item")){var n=a.getBoundingClientRect();e.clientY-n.top<.5*n.height?a.classList.add("drop-above"):a.classList.add("drop-below")}}else if(a){var i=document.querySelector(".tree-item.dragging");if(!i||!i.classList.contains("pinned-item")){e.preventDefault(),e.dataTransfer.dropEffect="move",clearDropIndicators();var s=a.dataset.type,d=a.getBoundingClientRect(),o=e.clientY-d.top,r=d.height;"folder"===s?o<.25*r?a.classList.add("drop-above"):o>.75*r?a.classList.add("drop-below"):a.classList.add("drop-target"):o<.5*r?a.classList.add("drop-above"):a.classList.add("drop-below")}}}}),document.addEventListener("dragleave",function(e){var t=e.target.closest(".tree-item");t&&(t.classList.remove("drop-target"),t.classList.remove("drop-above"),t.classList.remove("drop-below"));var a=document.getElementById("tree-container");a&&e.target===a&&a.classList.remove("drop-empty")}),document.addEventListener("drop",function(e){e.preventDefault();var t=e.target;t&&3===t.nodeType&&(t=t.parentElement);var a,n=t?t.closest(".tree-item"):null;document.getElementById("tree-container");try{a=JSON.parse(e.dataTransfer.getData("text/plain"))}catch(e){return void endDrag()}var i=a.id,s=a.type,d=a.pinned;if(t&&t.closest("#drop-zone-empty"))return postMsg({name:"dragToEmpty",dragId:i,dragType:s}),void endDrag();if(t&&(t.closest(".pinned-section-header")||t.closest(".pinned-section-body"))){if(d){var o=n&&n.classList.contains("pinned-item")?n:null;if(o&&o.dataset.id!==i){var r=o.getBoundingClientRect(),c=e.clientY-r.top<.5*r.height?"before":"after";postMsg({name:"reorderPinned",dragId:i,dragType:s,targetId:o.dataset.id,position:c})}}else"note"===s?postMsg({name:"contextMenu",action:"pinNote",id:i,itemType:"note"}):"folder"===s&&postMsg({name:"contextMenu",action:"pinFolder",id:i,itemType:"folder"});endDrag()}else if(d)endDrag();else if(n){var l=n.dataset.id,v=n.dataset.type;if(i!==l){var u=n.getBoundingClientRect(),m=e.clientY-u.top,p=u.height;postMsg("folder"===v?m>=.25*p&&m<=.75*p?{name:"dragDrop",dragId:i,dragType:s,targetId:l,position:"into"}:{name:"dragDrop",dragId:i,dragType:s,targetId:l,position:m<.25*p?"above":"below"}:{name:"dragDrop",dragId:i,dragType:s,targetId:l,position:"into"}),endDrag()}else endDrag()}else endDrag()});var _searchTimer=null,_searchMode=!1,_searchId=0;function escapeRegex(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function highlightText(e,t){if(!t)return e;var a=e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"),n=new RegExp("("+escapeRegex(t)+")","gi");return a.replace(n,'<mark class="search-highlight">$1</mark>')}function showSearchContainer(e){var t=document.getElementById("tree-container"),a=document.getElementById("search-results");t&&(t.style.display=e?"none":""),a&&(a.style.display=e?"":"none")}function renderNoteItem(e,t){var a="📝";e.is_todo&&(a=e.todo_completed?"☑":"☐");var n='<div class="search-result-item tree-item note" data-id="'+e.id+'" data-type="note">';return n+='<span class="icon note-icon">'+a+"</span>",n+='<div class="search-result-content">',n+='<div class="search-result-title">'+highlightText(e.title,t)+"</div>",e.folderName&&(n+='<div class="search-result-folder">📂 '+e.folderName+"</div>"),e.snippet&&(n+='<div class="search-result-snippet">'+highlightText(e.snippet,t)+"</div>"),n+"</div></div>"}function renderSearchResults(e,t,a,n){var i=document.getElementById("search-results");if(i){showSearchContainer(!0);var s=e.length+t.length+a.length;if(0!==s){var d='<div class="search-status">'+T("searchResultCount").replace("{count}",s)+"</div>";if(a.length>0){d+=p("📁",T("searchSectionFolders"),a.length,"folders"),d+='<div class="search-section-body" id="search-section-folders">';for(var o=0;o<a.length;o++){var r=a[o],c=r.icon&&r.icon.emoji?r.icon.emoji:"📁";d+='<div class="search-result-item search-folder-item" data-folder-id="'+r.id+'">',d+='<span class="icon">'+c+"</span>",d+='<div class="search-result-content">',d+='<div class="search-result-title">'+highlightText(r.title,n)+"</div>",d+="</div></div>"}d+="</div>"}if(e.length>0){d+=p("📝",T("searchSectionNotes"),e.length,"notes"),d+='<div class="search-section-body" id="search-section-notes">';for(var l=0;l<e.length;l++)d+=renderNoteItem(e[l],n);d+="</div>"}if(t.length>0){d+=p("🏷️",T("searchSectionTags"),t.length,"tags"),d+='<div class="search-section-body" id="search-section-tags">';for(var v=0;v<t.length;v++){var u=t[v],m=T("searchTagNoteCount").replace("{count}",u.noteCount);d+='<div class="search-result-item search-tag-item" data-tag-id="'+u.id+'">',d+='<span class="icon">🏷️</span>',d+='<div class="search-result-content">',d+='<div class="search-result-title">'+highlightText(u.title,n)+"</div>",d+='<div class="search-result-folder">'+m+"</div>",d+="</div>",d+='<span class="tag-expand-arrow">▶</span>',d+="</div>",d+='<div class="tag-notes-container" id="tag-notes-'+u.id+'"></div>'}d+="</div>"}i.innerHTML=d,_searchMode=!0}else i.innerHTML='<div class="search-status">'+T("searchNoResult")+"</div>"}function p(e,t,a,n){return'<div class="search-section-header" data-section="'+n+'"><span class="section-toggle">▼</span> '+e+" "+t+" ("+a+")</div>"}}function expandTagNotes(e,t){var a=document.getElementById("tag-notes-"+e);if(a){var n;if(a.children.length>0)return a.innerHTML="",void((n=document.querySelector('.search-tag-item[data-tag-id="'+e+'"] .tag-expand-arrow'))&&n.classList.remove("expanded"));for(var i="",s=0;s<t.length;s++)i+=renderNoteItem(t[s],"");0===t.length&&(i='<div class="search-status" style="padding-left:30px;">'+T("searchNoResult")+"</div>"),a.innerHTML=i,(n=document.querySelector('.search-tag-item[data-tag-id="'+e+'"] .tag-expand-arrow'))&&n.classList.add("expanded")}}function exitSearchMode(){_searchMode=!1,showSearchContainer(!1)}document.addEventListener("input",function(e){if("search-input"===e.target.id){var t=e.target.value.trim();_searchTimer&&clearTimeout(_searchTimer);var a=++_searchId;if(t){_searchMode=!0;var n=document.getElementById("search-results");n&&(n.innerHTML='<div class="search-status">'+T("searching")+"</div>"),showSearchContainer(!0),_searchTimer=setTimeout(function(){postMsg({name:"search",query:t,searchId:a})},400)}else _searchMode&&exitSearchMode()}}),document.addEventListener("keydown",function(e){if("Escape"===e.key){var t=document.getElementById("search-input");t&&t.value&&(t.value="",_searchMode&&exitSearchMode())}});
|