Use external open API in Files plugin
This commit is contained in:
parent
bf21df14f1
commit
084580d3fd
|
|
@ -9,7 +9,7 @@
|
||||||
| # | Feature | Приоритет | Зависит от |
|
| # | Feature | Приоритет | Зависит от |
|
||||||
|---|---------|-----------|------------|
|
|---|---------|-----------|------------|
|
||||||
| 1 | Контекстное меню (правый клик) | 🔥 High | — |
|
| 1 | Контекстное меню (правый клик) | 🔥 High | — |
|
||||||
| 2 | Open External / Show in Explorer fallback | ✅ Done | — |
|
| 2 | Open External / Show in Explorer | ✅ Done | `files.openExternal` |
|
||||||
| 3 | Кастомный ConfirmModal | 🔥 High | — |
|
| 3 | Кастомный ConfirmModal | 🔥 High | — |
|
||||||
| 4 | Duplicate | 🔥 High | — |
|
| 4 | Duplicate | 🔥 High | — |
|
||||||
| 5 | Cut/Copy/Paste | 🔥 High | — |
|
| 5 | Cut/Copy/Paste | 🔥 High | — |
|
||||||
|
|
@ -38,16 +38,15 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Feature 2: Open External / Show in Explorer fallback
|
### Feature 2: Open External / Show in Explorer
|
||||||
|
|
||||||
**Описание:** Пункты меню для будущего открытия файла во внешнем приложении и
|
**Описание:** Пункты меню для открытия файла во внешнем приложении и показа
|
||||||
показа папки в системном файловом менеджере.
|
файла/папки в системном файловом менеджере.
|
||||||
|
|
||||||
**Текущий v2 статус:** core/runtime API для внешнего открытия пока нет, поэтому
|
**Текущий v2 статус:** плагин использует публичные методы
|
||||||
пункты меню показывают fallback modal с vault-relative path и кнопкой `Copy Path`.
|
`api.files.openExternal(relativePath)` и `api.files.showInFolder(relativePath)`,
|
||||||
Никаких v1 Wails мостов или прямых backend calls.
|
guarded by `files.openExternal`. Fallback modal с vault-relative path и кнопкой
|
||||||
|
`Copy Path` показывается только если API недоступен или вернул ошибку.
|
||||||
Полное внешнее открытие остается deferred до отдельного публичного v2 API.
|
|
||||||
|
|
||||||
**Файлы:** `plugins/files/frontend/src/index.js`
|
**Файлы:** `plugins/files/frontend/src/index.js`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -271,14 +271,14 @@
|
||||||
return Promise.reject(new Error('clipboard unavailable'));
|
return Promise.reject(new Error('clipboard unavailable'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function showExternalFallback(entry, mode) {
|
function showExternalFallback(entry, mode, reason) {
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
var pathToShow = entry.relativePath;
|
var pathToShow = entry.relativePath;
|
||||||
if (mode === 'explorer' && entry.type !== 'folder') {
|
if (mode === 'explorer' && entry.type !== 'folder') {
|
||||||
pathToShow = parentPath(entry.relativePath) || entry.relativePath;
|
pathToShow = parentPath(entry.relativePath) || entry.relativePath;
|
||||||
}
|
}
|
||||||
var title = mode === 'explorer' ? 'Show in Explorer' : 'Open External';
|
var title = mode === 'explorer' ? 'Show in Explorer' : 'Open External';
|
||||||
var message = title + ' is not available in the current v2 runtime yet.\nVault-relative path:\n' + pathToShow;
|
var message = title + ' failed.\n' + (reason ? String(reason) + '\n' : '') + 'Vault-relative path:\n' + pathToShow;
|
||||||
confirmModal(message, { confirmText: 'Copy Path', cancelText: 'Close' }).then(function (copy) {
|
confirmModal(message, { confirmText: 'Copy Path', cancelText: 'Close' }).then(function (copy) {
|
||||||
if (!copy) return;
|
if (!copy) return;
|
||||||
copyTextToClipboard(pathToShow).catch(function (err) {
|
copyTextToClipboard(pathToShow).catch(function (err) {
|
||||||
|
|
@ -796,6 +796,19 @@
|
||||||
return el('div', { className: 'files-ctx-menu-sep' });
|
return el('div', { className: 'files-ctx-menu-sep' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openExternalEntry(entry, mode) {
|
||||||
|
if (!entry) return;
|
||||||
|
var filesApi = api && api.files;
|
||||||
|
var action = mode === 'explorer' ? filesApi && filesApi.showInFolder : filesApi && filesApi.openExternal;
|
||||||
|
if (typeof action !== 'function') {
|
||||||
|
showExternalFallback(entry, mode, 'files external-open API is unavailable.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
action(entry.relativePath).catch(function (err) {
|
||||||
|
showExternalFallback(entry, mode, err && err.message ? err.message : err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function showCtxMenu(x, y, entry) {
|
function showCtxMenu(x, y, entry) {
|
||||||
ctxTarget = entry;
|
ctxTarget = entry;
|
||||||
ctxMenu.innerHTML = '';
|
ctxMenu.innerHTML = '';
|
||||||
|
|
@ -808,8 +821,8 @@
|
||||||
}
|
}
|
||||||
var isFolder = entry.type === 'folder';
|
var isFolder = entry.type === 'folder';
|
||||||
ctxMenu.appendChild(ctxItem(isFolder ? 'Open Folder' : 'Open', '', function () { openEntry(entry); }, 'open', 'open'));
|
ctxMenu.appendChild(ctxItem(isFolder ? 'Open Folder' : 'Open', '', function () { openEntry(entry); }, 'open', 'open'));
|
||||||
ctxMenu.appendChild(ctxItem('Open External', '', function () { showExternalFallback(entry, 'external'); }, 'open-external', 'external'));
|
ctxMenu.appendChild(ctxItem('Open External', '', function () { openExternalEntry(entry, 'external'); }, 'open-external', 'external'));
|
||||||
ctxMenu.appendChild(ctxItem('Show in Explorer', '', function () { showExternalFallback(entry, 'explorer'); }, 'show-in-explorer', 'explorer'));
|
ctxMenu.appendChild(ctxItem('Show in Explorer', '', function () { openExternalEntry(entry, 'explorer'); }, 'show-in-explorer', 'explorer'));
|
||||||
ctxMenu.appendChild(ctxSep());
|
ctxMenu.appendChild(ctxSep());
|
||||||
ctxMenu.appendChild(ctxItem('Rename', '', function () { beginRename(entry); }, 'rename', 'rename'));
|
ctxMenu.appendChild(ctxItem('Rename', '', function () { beginRename(entry); }, 'rename', 'rename'));
|
||||||
if (entry.type !== 'folder') {
|
if (entry.type !== 'folder') {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
"files.read",
|
"files.read",
|
||||||
"files.write",
|
"files.write",
|
||||||
"files.delete",
|
"files.delete",
|
||||||
|
"files.openExternal",
|
||||||
"workbench.open",
|
"workbench.open",
|
||||||
"ui.register"
|
"ui.register"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,9 @@ function loadFilesComponent(document) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeApi() {
|
function makeApi() {
|
||||||
|
const externalCalls = [];
|
||||||
return {
|
return {
|
||||||
|
externalCalls,
|
||||||
files: {
|
files: {
|
||||||
list: async () => [
|
list: async () => [
|
||||||
{
|
{
|
||||||
|
|
@ -214,6 +216,8 @@ function makeApi() {
|
||||||
createFolder: async () => undefined,
|
createFolder: async () => undefined,
|
||||||
move: async () => undefined,
|
move: async () => undefined,
|
||||||
trash: async () => undefined,
|
trash: async () => undefined,
|
||||||
|
openExternal: async (relativePath) => { externalCalls.push({ action: 'open', path: relativePath }); },
|
||||||
|
showInFolder: async (relativePath) => { externalCalls.push({ action: 'show', path: relativePath }); },
|
||||||
},
|
},
|
||||||
workbench: {
|
workbench: {
|
||||||
openResource: async () => ({ status: 'opened' }),
|
openResource: async () => ({ status: 'opened' }),
|
||||||
|
|
@ -229,7 +233,8 @@ async function flush() {
|
||||||
const document = makeDocument();
|
const document = makeDocument();
|
||||||
const { component, clipboard } = loadFilesComponent(document);
|
const { component, clipboard } = loadFilesComponent(document);
|
||||||
const container = new FakeNode('div');
|
const container = new FakeNode('div');
|
||||||
component.mount(container, {}, makeApi());
|
const api = makeApi();
|
||||||
|
component.mount(container, {}, api);
|
||||||
await flush();
|
await flush();
|
||||||
|
|
||||||
const row = walk(container, (node) => node.getAttribute && node.getAttribute('data-file-path') === 'Docs/readme.md');
|
const row = walk(container, (node) => node.getAttribute && node.getAttribute('data-file-path') === 'Docs/readme.md');
|
||||||
|
|
@ -245,12 +250,21 @@ async function flush() {
|
||||||
if (!showInExplorer) throw new Error('Show in Explorer menu item not found');
|
if (!showInExplorer) throw new Error('Show in Explorer menu item not found');
|
||||||
|
|
||||||
openExternal.click();
|
openExternal.click();
|
||||||
const copyButton = walk(document.body, (node) => node.tagName === 'BUTTON' && node.textContent === 'Copy Path');
|
|
||||||
if (!copyButton) throw new Error('external fallback did not show Copy Path action');
|
|
||||||
copyButton.click();
|
|
||||||
await flush();
|
await flush();
|
||||||
if (clipboard.written[0] !== 'Docs/readme.md') {
|
if (!api.externalCalls.some((call) => call.action === 'open' && call.path === 'Docs/readme.md')) {
|
||||||
throw new Error(`expected copied path Docs/readme.md, got ${clipboard.written[0] || '<none>'}`);
|
throw new Error(`expected openExternal call for Docs/readme.md, got ${JSON.stringify(api.externalCalls)}`);
|
||||||
|
}
|
||||||
|
if (walk(document.body, (node) => node.tagName === 'BUTTON' && node.textContent === 'Copy Path')) {
|
||||||
|
throw new Error('external fallback should not show Copy Path after successful API call');
|
||||||
|
}
|
||||||
|
|
||||||
|
showInExplorer.click();
|
||||||
|
await flush();
|
||||||
|
if (!api.externalCalls.some((call) => call.action === 'show' && call.path === 'Docs/readme.md')) {
|
||||||
|
throw new Error(`expected showInFolder call for Docs/readme.md, got ${JSON.stringify(api.externalCalls)}`);
|
||||||
|
}
|
||||||
|
if (clipboard.written.length !== 0) {
|
||||||
|
throw new Error(`expected no copied path after successful external API calls, got ${clipboard.written.join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('files frontend smoke passed');
|
console.log('files frontend smoke passed');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue