joplin-plugin-explorer 1.1.1 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "joplin-plugin-explorer",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "A unified sidebar that displays notebooks and notes together in a single tree view",
5
5
  "author": "lim0513",
6
6
  "homepage": "https://github.com/lim0513/joplin-explorer",
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
 
@@ -447,10 +451,14 @@ joplin.plugins.register({
447
451
  if (itemType === 'folder') {
448
452
  switch (action) {
449
453
  case 'newNote':
450
- await joplin.data.post(['folders', id, 'notes'], null, { title: '' });
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;
451
457
  break;
452
458
  case 'newTodo':
453
- await joplin.data.post(['folders', id, 'notes'], null, { title: '', is_todo: 1 });
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;
454
462
  break;
455
463
  case 'newSubNotebook':
456
464
  await joplin.data.post(['folders'], null, { title: t.newNotebook, parent_id: id });
@@ -462,28 +470,39 @@ joplin.plugins.register({
462
470
  if (msg.newTitle) await joplin.data.put(['folders', id], null, { title: msg.newTitle });
463
471
  break;
464
472
  case 'exportFolder':
465
- await joplin.commands.execute('exportFolders', [id]);
473
+ try { await joplin.commands.execute('exportFolders', [id]); } catch(e) {}
466
474
  break;
467
475
  }
468
476
  } else if (itemType === 'note') {
469
477
  switch (action) {
470
478
  case 'openNote':
471
479
  await joplin.commands.execute('openNote', id);
480
+ selectedNoteId = id;
472
481
  break;
473
482
  case 'openInNewWindow':
474
- 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
+ }
475
487
  break;
476
488
  case 'copyLink':
477
489
  var linkNote = await joplin.data.get(['notes', id], { fields: ['id', 'title'] });
478
490
  var mdLink = '[' + linkNote.title + '](:/' + linkNote.id + ')';
479
- await joplin.clipboard.writeText(mdLink);
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
+ }
480
497
  break;
481
498
  case 'duplicateNote':
482
499
  var srcNote = await joplin.data.get(['notes', id], { fields: ['title', 'body', 'parent_id', 'is_todo'] });
483
- await joplin.data.post(['notes'], null, {
500
+ var dupNote = await joplin.data.post(['notes'], null, {
484
501
  title: srcNote.title + ' (copy)', body: srcNote.body,
485
502
  parent_id: srcNote.parent_id, is_todo: srcNote.is_todo,
486
503
  });
504
+ await joplin.commands.execute('openNote', dupNote.id);
505
+ selectedNoteId = dupNote.id;
487
506
  break;
488
507
  case 'switchNoteType':
489
508
  var sn = await joplin.data.get(['notes', id], { fields: ['is_todo'] });
@@ -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.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": "user",
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
  }
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: 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
+
350
420
  mark.search-highlight {
351
421
  background: rgba(255, 213, 0, 0.4);
352
422
  color: inherit;
@@ -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, '&quot;') + '" />'
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');
@@ -133,10 +176,11 @@ document.addEventListener('click', function(e) {
133
176
 
134
177
  if (action === 'renameFolder' || action === 'renameNote') {
135
178
  var currentTitle = ctxItem.dataset.title || '';
136
- var newTitle = prompt(T('promptRename'), currentTitle);
137
- if (newTitle !== null && newTitle.trim() !== '') {
138
- postMsg({ name: 'contextMenu', action: action, id: id, itemType: itemType, newTitle: newTitle.trim() });
139
- }
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
+ });
140
184
  } else if (action === 'deleteFolder') {
141
185
  if (confirm(T('confirmDeleteFolder'))) {
142
186
  postMsg({ name: 'contextMenu', action: action, id: id, itemType: itemType });
@@ -146,10 +190,11 @@ document.addEventListener('click', function(e) {
146
190
  postMsg({ name: 'contextMenu', action: action, id: id, itemType: itemType });
147
191
  }
148
192
  } else if (action === 'moveNote') {
149
- var folderName = prompt(T('promptMoveNote'));
150
- if (folderName !== null && folderName.trim() !== '') {
151
- postMsg({ name: 'contextMenu', action: action, id: id, itemType: itemType, targetFolderName: folderName.trim() });
152
- }
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
+ });
153
198
  } else {
154
199
  postMsg({ name: 'contextMenu', action: action, id: id, itemType: itemType });
155
200
  }
@@ -219,6 +264,18 @@ webviewApi.onMessage(function(msg) {
219
264
  } else {
220
265
  renderSearchResults(m.results, m.query);
221
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
+ }
222
279
  } else if (m.name === 'selectNote') {
223
280
  // Update selection without full re-render
224
281
  document.querySelectorAll('.tree-item.note.selected').forEach(function(el) {