feat: bridge permanent trash deletion to plugins
This commit is contained in:
parent
8445ea11db
commit
3f27ba79a4
|
|
@ -85,7 +85,7 @@ Frontend bundles are mounted with a plugin-scoped API created by
|
|||
- `capabilities.list/get/has`
|
||||
- `commands.register/execute` for handlers declared in `contributes.commands`
|
||||
- `events.publish/subscribe` using the bundled frontend event bus
|
||||
- `files.list/metadata/readText/readBytes/writeText/createFolder/move/trash/openExternal/showInFolder`
|
||||
- `files.list/metadata/readText/readBytes/writeText/createFolder/move/trash/listTrash/restoreTrash/deleteTrash/openExternal/showInFolder`
|
||||
for canonical vault-relative slash paths guarded by `files.read`,
|
||||
`files.write`, `files.delete`, and `files.openExternal`. Backslashes,
|
||||
Windows absolute paths, UNC paths, traversal, `.verstak` variants, and
|
||||
|
|
|
|||
|
|
@ -125,6 +125,29 @@ test.describe('D: Plugin API bridge', () => {
|
|||
expect(result.disconnected.statusLabel).toBe('disabled');
|
||||
});
|
||||
|
||||
test('files API permanently deletes an existing trash entry through the bridge', async ({ page }) => {
|
||||
const result = await page.evaluate(async () => {
|
||||
const api = window.createPluginAPI('verstak.files');
|
||||
await api.files.writeText('Project/bridge-trash.txt', 'remove me', { createIfMissing: true });
|
||||
const trash = await api.files.trash('Project/bridge-trash.txt');
|
||||
const before = await api.files.listTrash();
|
||||
await api.files.deleteTrash(trash.trashId);
|
||||
const after = await api.files.listTrash();
|
||||
let readError = '';
|
||||
try {
|
||||
await api.files.readText('Project/bridge-trash.txt');
|
||||
} catch (error) {
|
||||
readError = String(error && error.message || error);
|
||||
}
|
||||
api.dispose();
|
||||
return { before, after, readError };
|
||||
});
|
||||
|
||||
expect(result.before).toHaveLength(1);
|
||||
expect(result.after).toEqual([]);
|
||||
expect(result.readError).toContain('not-found: Project/bridge-trash.txt');
|
||||
});
|
||||
|
||||
test('backend plugin events are dispatched to subscribed frontend handlers', async ({ page }) => {
|
||||
const result = await page.evaluate(async () => {
|
||||
const api = window.createPluginAPI('verstak.platform-test');
|
||||
|
|
|
|||
|
|
@ -380,6 +380,12 @@ export function createPluginAPI(pluginId) {
|
|||
return App.RestoreVaultTrash(pluginId, trashId, options || {});
|
||||
});
|
||||
},
|
||||
deleteTrash: function(trashId) {
|
||||
assertActive('files.deleteTrash(' + trashId + ')');
|
||||
return callBackendErrorString(pluginId, 'files.deleteTrash(' + trashId + ')', function() {
|
||||
return App.DeleteVaultTrash(pluginId, trashId);
|
||||
});
|
||||
},
|
||||
openExternal: function(relativePath) {
|
||||
assertActive('files.openExternal(' + relativePath + ')');
|
||||
return callBackendErrorString(pluginId, 'files.openExternal(' + relativePath + ')', function() {
|
||||
|
|
|
|||
|
|
@ -2608,13 +2608,15 @@
|
|||
if (!vaultFiles[norm.path]) return Promise.resolve([{}, 'not-found: ' + norm.path]);
|
||||
var trashId = 'mock-' + Date.now() + '-' + Math.random().toString(16).slice(2);
|
||||
var trashPath = '.verstak/trash/files/' + trashId + '/' + baseName(norm.path);
|
||||
var originalType = vaultFiles[norm.path].type || 'file';
|
||||
var originalNode = vaultFiles[norm.path];
|
||||
var originalType = originalNode.type || 'file';
|
||||
var originalSize = originalType === 'file' ? String(originalNode.content || '').length : 0;
|
||||
var moving = Object.keys(vaultFiles).filter(function (path) { return path === norm.path || path.indexOf(norm.path + '/') === 0; });
|
||||
trashPayloads[trashId] = moving.map(function (path) {
|
||||
return { suffix: path.slice(norm.path.length), entry: Object.assign({}, vaultFiles[path]) };
|
||||
});
|
||||
moving.forEach(function (path) { delete vaultFiles[path]; });
|
||||
var entry = { originalPath: norm.path, trashPath: trashPath, trashId: trashId, deletedAt: new Date().toISOString(), originalType: originalType, basename: baseName(norm.path) };
|
||||
var entry = { originalPath: norm.path, trashPath: trashPath, trashId: trashId, deletedAt: new Date().toISOString(), originalType: originalType, basename: baseName(norm.path), size: originalSize };
|
||||
trashEntries.unshift(entry);
|
||||
return Promise.resolve([entry, '']);
|
||||
},
|
||||
|
|
@ -2646,6 +2648,15 @@
|
|||
trashEntries = trashEntries.filter(function (item) { return item.trashId !== trashId; });
|
||||
return Promise.resolve([target.path, '']);
|
||||
},
|
||||
DeleteVaultTrash: function (pluginId, trashId) {
|
||||
var err = requirePluginPermission(pluginId, 'files.delete');
|
||||
if (err) return Promise.resolve(err);
|
||||
var entry = trashEntries.find(function (item) { return item.trashId === trashId; });
|
||||
if (!entry) return Promise.resolve('not-found: trash entry ' + trashId);
|
||||
delete trashPayloads[trashId];
|
||||
trashEntries = trashEntries.filter(function (item) { return item.trashId !== trashId; });
|
||||
return Promise.resolve('');
|
||||
},
|
||||
OpenVaultPathExternal: function (pluginId, relativePath) {
|
||||
var err = requirePluginPermission(pluginId, 'files.openExternal');
|
||||
if (err) return Promise.resolve(err);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ export function CreateWorkspace(arg1:string,arg2:string):Promise<workspace.Works
|
|||
|
||||
export function CreateWorkspaceNode(arg1:string,arg2:string,arg3:string):Promise<Record<string, any>>;
|
||||
|
||||
export function DeleteVaultTrash(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function DisablePlugin(arg1:string):Promise<string>;
|
||||
|
||||
export function EditWorkbenchResource(arg1:string,arg2:Record<string, any>):Promise<workbench.OpenResourceResult|string>;
|
||||
|
|
@ -80,6 +82,10 @@ export function OpenVaultPathExternal(arg1:string,arg2:string):Promise<string>;
|
|||
|
||||
export function OpenWorkbenchResource(arg1:string,arg2:Record<string, any>):Promise<workbench.OpenResourceResult|string>;
|
||||
|
||||
export function PluginBrowserReceiverPairing(arg1:string):Promise<Record<string, string>|string>;
|
||||
|
||||
export function PluginRotateBrowserReceiverToken(arg1:string):Promise<Record<string, string>|string>;
|
||||
|
||||
export function PluginSecretsCopyLink(arg1:string,arg2:string):Promise<string|string>;
|
||||
|
||||
export function PluginSecretsDelete(arg1:string,arg2:string):Promise<string>;
|
||||
|
|
@ -94,10 +100,6 @@ export function PluginSecretsUnlock(arg1:string,arg2:string):Promise<string>;
|
|||
|
||||
export function PluginSecretsWrite(arg1:string,arg2:Record<string, any>):Promise<Record<string, any>|string>;
|
||||
|
||||
export function PluginBrowserReceiverPairing(arg1:string):Promise<Record<string, string>|string>;
|
||||
|
||||
export function PluginRotateBrowserReceiverToken(arg1:string):Promise<Record<string, string>|string>;
|
||||
|
||||
export function PluginSyncConfigure(arg1:string,arg2:string,arg3:string,arg4:string):Promise<string>;
|
||||
|
||||
export function PluginSyncDisconnect(arg1:string):Promise<string>;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ export function CreateWorkspaceNode(arg1, arg2, arg3) {
|
|||
return window['go']['api']['App']['CreateWorkspaceNode'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function DeleteVaultTrash(arg1, arg2) {
|
||||
return window['go']['api']['App']['DeleteVaultTrash'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function DisablePlugin(arg1) {
|
||||
return window['go']['api']['App']['DisablePlugin'](arg1);
|
||||
}
|
||||
|
|
@ -146,6 +150,14 @@ export function OpenWorkbenchResource(arg1, arg2) {
|
|||
return window['go']['api']['App']['OpenWorkbenchResource'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function PluginBrowserReceiverPairing(arg1) {
|
||||
return window['go']['api']['App']['PluginBrowserReceiverPairing'](arg1);
|
||||
}
|
||||
|
||||
export function PluginRotateBrowserReceiverToken(arg1) {
|
||||
return window['go']['api']['App']['PluginRotateBrowserReceiverToken'](arg1);
|
||||
}
|
||||
|
||||
export function PluginSecretsCopyLink(arg1, arg2) {
|
||||
return window['go']['api']['App']['PluginSecretsCopyLink'](arg1, arg2);
|
||||
}
|
||||
|
|
@ -174,14 +186,6 @@ export function PluginSecretsWrite(arg1, arg2) {
|
|||
return window['go']['api']['App']['PluginSecretsWrite'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function PluginBrowserReceiverPairing(arg1) {
|
||||
return window['go']['api']['App']['PluginBrowserReceiverPairing'](arg1);
|
||||
}
|
||||
|
||||
export function PluginRotateBrowserReceiverToken(arg1) {
|
||||
return window['go']['api']['App']['PluginRotateBrowserReceiverToken'](arg1);
|
||||
}
|
||||
|
||||
export function PluginSyncConfigure(arg1, arg2, arg3, arg4) {
|
||||
return window['go']['api']['App']['PluginSyncConfigure'](arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -484,6 +484,7 @@ export namespace files {
|
|||
deletedAt: string;
|
||||
originalType: string;
|
||||
basename: string;
|
||||
size: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new TrashEntry(source);
|
||||
|
|
@ -497,6 +498,7 @@ export namespace files {
|
|||
this.deletedAt = source["deletedAt"];
|
||||
this.originalType = source["originalType"];
|
||||
this.basename = source["basename"];
|
||||
this.size = source["size"];
|
||||
}
|
||||
}
|
||||
export class TrashResult {
|
||||
|
|
@ -504,6 +506,7 @@ export namespace files {
|
|||
trashPath: string;
|
||||
trashId: string;
|
||||
deletedAt: string;
|
||||
size: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new TrashResult(source);
|
||||
|
|
@ -515,6 +518,7 @@ export namespace files {
|
|||
this.trashPath = source["trashPath"];
|
||||
this.trashId = source["trashId"];
|
||||
this.deletedAt = source["deletedAt"];
|
||||
this.size = source["size"];
|
||||
}
|
||||
}
|
||||
export class WriteOptions {
|
||||
|
|
@ -1315,4 +1319,3 @@ export namespace workspace {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue