feat: milestones 6b-fix through 6e — default-editor, files plugin, workspace host, workspaceItems contribution
- Fix PluginCard openProviders display - Add default-editor plugin (text/markdown/notes-context) - Add files plugin with workspaceItems placement - Add workspaceItems contribution point (Go + API + mock + SDK) - Add WorkspaceHost component for workspace area - WorkspaceTree dispatches selection event - Fix default-editor layout to fill container - Fix PluginCard unsafe .length access - Add E2E tests: 34/34 pass - Add bundle execution check to official-plugins check.sh - Update docs: PLUGIN_RUNTIME, DEV_PLUGINS, MILESTONE_6B/6C/6D plans
This commit is contained in:
parent
6ed6df311a
commit
a6412fa070
|
|
@ -101,6 +101,12 @@ Workbench selects by resource kind, extension/mime, context (`generic-text`,
|
||||||
deterministic `pluginId/providerId` tie-break. If nothing matches, Workbench
|
deterministic `pluginId/providerId` tie-break. If nothing matches, Workbench
|
||||||
shows `no-provider` fallback instead of a core editor.
|
shows `no-provider` fallback instead of a core editor.
|
||||||
|
|
||||||
|
The official `verstak.default-editor` plugin provides three openProviders:
|
||||||
|
`verstak.default-editor.text` (generic-text), `verstak.default-editor.markdown`
|
||||||
|
(generic-markdown), and `verstak.default-editor.notes-markdown` (notes-context).
|
||||||
|
It uses a single unified `DefaultEditor` component with textarea-based editing,
|
||||||
|
simple markdown preview, dirty state tracking, and Ctrl+S save.
|
||||||
|
|
||||||
This is a cooperative contract, not a sandbox. Bundled plugins run in the same JS
|
This is a cooperative contract, not a sandbox. Bundled plugins run in the same JS
|
||||||
context as the desktop frontend; real isolation is deferred to the sidecar/sandbox
|
context as the desktop frontend; real isolation is deferred to the sidecar/sandbox
|
||||||
milestone.
|
milestone.
|
||||||
|
|
|
||||||
|
|
@ -133,3 +133,16 @@ Out of scope:
|
||||||
- Watcher/sync/binary streaming/external editor.
|
- Watcher/sync/binary streaming/external editor.
|
||||||
- Sidecar/security boundary.
|
- Sidecar/security boundary.
|
||||||
- Large rewrite.
|
- Large rewrite.
|
||||||
|
|
||||||
|
## 6b-fix: Infrastructure Gaps Closed
|
||||||
|
|
||||||
|
Post-review fixes before Milestone 6c:
|
||||||
|
|
||||||
|
- Added `openProviders` to `verstak-sdk/schemas/contributions.json` contribution points.
|
||||||
|
- Added provider matching tests: text/markdown preference, MIME matching, extension
|
||||||
|
case-insensitivity, multiple supports entries, kind mismatch.
|
||||||
|
- Added disabled provider exclusion test in `api/app_test.go`.
|
||||||
|
- Fixed SDK `build.sh`/`test.sh` to detect incomplete `node_modules`.
|
||||||
|
- Documented disabled provider lifecycle: contributions remain in registry until
|
||||||
|
ReloadPlugins; `activeOpenProviders()` filters disabled/unloaded at request time.
|
||||||
|
- Installed Playwright browsers for E2E tests.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
# Milestone 6c — Default Editor Plugin
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Create the official Default Editor Plugin as an openProvider for text, generic
|
||||||
|
markdown, and notes-context markdown files. Core desktop does not own or import
|
||||||
|
the editor component.
|
||||||
|
|
||||||
|
## What was built
|
||||||
|
|
||||||
|
### Plugin: `verstak.default-editor`
|
||||||
|
|
||||||
|
Location: `verstak-official-plugins/plugins/default-editor/`
|
||||||
|
|
||||||
|
**Manifest declares 3 openProviders:**
|
||||||
|
|
||||||
|
| Provider ID | Context | Extensions |
|
||||||
|
|-------------|---------|------------|
|
||||||
|
| `verstak.default-editor.text` | `generic-text` | `.txt`, `.log`, `.conf`, `.ini`, `.toml`, `.yaml`, `.yml`, `.json`, `.csv` |
|
||||||
|
| `verstak.default-editor.markdown` | `generic-markdown` | `.md`, `.markdown` |
|
||||||
|
| `verstak.default-editor.notes-markdown` | `notes-markdown` | `.md`, `.markdown` |
|
||||||
|
|
||||||
|
All providers use the same `DefaultEditor` component (unified, not 3 separate editors).
|
||||||
|
|
||||||
|
**Permissions:** `files.read`, `files.write`, `workbench.open`
|
||||||
|
|
||||||
|
**Capabilities required:** `verstak/core/files/v1`, `verstak/core/workbench/v1`
|
||||||
|
|
||||||
|
### Frontend component: `DefaultEditor`
|
||||||
|
|
||||||
|
- **Modes:** text (textarea), generic-markdown (editor + preview), notes-markdown (editor + preview + notes badge)
|
||||||
|
- **File loading:** `api.files.readText(path)` with loading/error states
|
||||||
|
- **Saving:** `api.files.writeText(path, content, { overwrite: true })` with dirty/saved/error states
|
||||||
|
- **Keyboard:** Ctrl+S / Cmd+S save, Tab indentation
|
||||||
|
- **Markdown preview:** Simple renderer (no raw HTML, no script injection)
|
||||||
|
- **Notes context:** Badge + info bar, no separate note entity, no `.verstak/notes`
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `go test ./...` — PASS
|
||||||
|
- `go vet ./...` — PASS
|
||||||
|
- `npm run build` (frontend) — PASS
|
||||||
|
- `npm run test:e2e` — 28/28 PASS (20 existing + 8 new)
|
||||||
|
- `scripts/check.sh` (official-plugins) — PASS
|
||||||
|
- `scripts/build.sh` (official-plugins) — PASS
|
||||||
|
- SDK checks — PASS
|
||||||
|
|
||||||
|
## Manual testing
|
||||||
|
|
||||||
|
Until Files UI plugin exists, use platform-test diagnostics panel to manually
|
||||||
|
open files via workbench: click "Open Text Diagnostic", "Open Markdown Diagnostic",
|
||||||
|
or "Open Notes Diagnostic" buttons. These call `api.workbench.editResource` and
|
||||||
|
route through the default-editor provider.
|
||||||
|
|
||||||
|
## Deferred
|
||||||
|
|
||||||
|
- CodeMirror/Monaco editor
|
||||||
|
- Backlinks, internal link navigation
|
||||||
|
- Secret widgets
|
||||||
|
- Image asset pipeline
|
||||||
|
- Files UI plugin
|
||||||
|
- Notes UI plugin
|
||||||
|
- Watcher/sync
|
||||||
|
- External open
|
||||||
|
- Sidecar/security isolation
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
# Milestone 6d — Minimal Files Plugin
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Create a minimal Files plugin that shows vault files/folders and opens files
|
||||||
|
through Workbench openResource. No editor embedded.
|
||||||
|
|
||||||
|
## What was built
|
||||||
|
|
||||||
|
### Plugin: `verstak.files`
|
||||||
|
|
||||||
|
Location: `verstak-official-plugins/plugins/files/`
|
||||||
|
|
||||||
|
**Contributions:**
|
||||||
|
- views: `verstak.files.view` → `FilesView` component
|
||||||
|
- No sidebarItems — Files is not a global sidebar item
|
||||||
|
|
||||||
|
**Permissions:** `files.read`, `files.write`, `workbench.open`, `ui.register`
|
||||||
|
|
||||||
|
### Files View
|
||||||
|
|
||||||
|
- Root listing on mount
|
||||||
|
- Folder navigation (double-click)
|
||||||
|
- File open via `api.workbench.openResource()`
|
||||||
|
- Breadcrumb navigation
|
||||||
|
- Create folder/file buttons
|
||||||
|
- Refresh button
|
||||||
|
- Loading/error/empty states
|
||||||
|
- `.verstak` filtered out
|
||||||
|
|
||||||
|
### Provider priority
|
||||||
|
|
||||||
|
- default-editor: priority 50
|
||||||
|
- platform-test diagnostic: priority 10
|
||||||
|
- default-editor wins for normal file opens
|
||||||
|
|
||||||
|
### Bundle fix
|
||||||
|
|
||||||
|
Fixed missing opening quote in STYLES string (`.files-empty` → `'.files-empty'`).
|
||||||
|
Added automated bundle execution check to `scripts/check.sh`.
|
||||||
|
|
||||||
|
## 6d-hotfix
|
||||||
|
|
||||||
|
- Removed sidebarItems from Files plugin (Files is not a global sidebar item)
|
||||||
|
- Added `[frontend bundle execution]` check to `check.sh` — verifies all plugin
|
||||||
|
bundles parse via `new Function()` and register via `VerstakPluginRegister`
|
||||||
|
- Updated E2E tests: Files no longer expected in global sidebar
|
||||||
|
- Documented: sidebarItems are global shell navigation, not workspace template tabs
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `go test ./...` — PASS
|
||||||
|
- `go vet ./...` — PASS
|
||||||
|
- `npm run build` — PASS
|
||||||
|
- `npm run test:e2e` — 34/34 PASS
|
||||||
|
- Official plugins — 3 plugins built, bundle execution check passes
|
||||||
|
- SDK — 11/11 tests pass
|
||||||
|
|
||||||
|
## Deferred
|
||||||
|
|
||||||
|
- Notes plugin, rename/move/trash UI, drag-and-drop, context menu,
|
||||||
|
watcher/inotify, sync, external open, binary streaming, sidecar/security,
|
||||||
|
workspace template host (Milestone 6d2)
|
||||||
|
|
||||||
|
|
@ -335,10 +335,27 @@ extension/mime, context, user preference, priority, then deterministic
|
||||||
`pluginId/providerId` fallback. If nothing matches, Workbench returns
|
`pluginId/providerId` fallback. If nothing matches, Workbench returns
|
||||||
`status: "no-provider"` and shows the fallback view instead of a core editor.
|
`status: "no-provider"` and shows the fallback view instead of a core editor.
|
||||||
|
|
||||||
|
Disabled/failed/missing-required-capability plugins are excluded from provider
|
||||||
|
selection at request time by `activeOpenProviders()`. Their contributions may
|
||||||
|
remain in the registry until the next `ReloadPlugins()` cycle, but they never
|
||||||
|
match during routing.
|
||||||
|
|
||||||
Draft app-global preferences are `defaultTextEditorProvider`,
|
Draft app-global preferences are `defaultTextEditorProvider`,
|
||||||
`defaultMarkdownEditorProvider`, and `defaultNotesMarkdownEditorProvider`.
|
`defaultMarkdownEditorProvider`, and `defaultNotesMarkdownEditorProvider`.
|
||||||
Vault-scoped and per-extension overrides are deferred.
|
Vault-scoped and per-extension overrides are deferred.
|
||||||
|
|
||||||
|
### Default Editor Plugin
|
||||||
|
|
||||||
|
The official `verstak.default-editor` plugin (`verstak-official-plugins/plugins/default-editor/`)
|
||||||
|
provides openProviders for text, generic markdown, and notes-context markdown files.
|
||||||
|
It uses `api.files.readText` / `api.files.writeText` for file I/O and mounts through
|
||||||
|
the standard `PluginBundleHost` / provider host mechanism. Core does not import or
|
||||||
|
reference this plugin directly.
|
||||||
|
|
||||||
|
Provider plugins may have no sidebar item — openProviders are contribution points
|
||||||
|
for workbench routing, not navigation. Plugin Manager displays openProviders in the
|
||||||
|
contributions summary.
|
||||||
|
|
||||||
### API methods
|
### API methods
|
||||||
|
|
||||||
`settings`
|
`settings`
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { waitForAppReady, setupConsoleCollector, resetMockState } from './helpers.js';
|
||||||
|
|
||||||
|
test.describe('F: Default Editor Plugin', () => {
|
||||||
|
let consoleCollector;
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
consoleCollector = setupConsoleCollector(page);
|
||||||
|
await resetMockState(page);
|
||||||
|
await page.goto('/');
|
||||||
|
await waitForAppReady(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterEach(async () => {
|
||||||
|
consoleCollector.assertNoErrors();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('open .txt file shows plain text editor mode', async ({ page }) => {
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
const [result, err] = await window.go.api.App.OpenWorkbenchResource('verstak.platform-test', {
|
||||||
|
kind: 'vault-file',
|
||||||
|
path: 'Docs/todo.txt',
|
||||||
|
extension: '.txt',
|
||||||
|
context: { sourceView: 'files' },
|
||||||
|
});
|
||||||
|
if (err) throw new Error(err);
|
||||||
|
window.dispatchEvent(new CustomEvent('verstak:workbench-opened', { detail: result }));
|
||||||
|
});
|
||||||
|
|
||||||
|
const editor = page.locator('[data-editor-mode="text"]');
|
||||||
|
await expect(editor).toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(editor).toHaveAttribute('data-resource-path', 'Docs/todo.txt');
|
||||||
|
await expect(editor).toHaveAttribute('data-request-mode', 'view');
|
||||||
|
const textarea = editor.locator('[data-editor-textarea]');
|
||||||
|
await expect(textarea).toBeVisible();
|
||||||
|
await expect(textarea).toHaveValue('Buy groceries\nWrite tests');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('open .md file outside Notes routes to highest-priority provider', async ({ page }) => {
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
const [result, err] = await window.go.api.App.OpenWorkbenchResource('verstak.platform-test', {
|
||||||
|
kind: 'vault-file',
|
||||||
|
path: 'Docs/readme.md',
|
||||||
|
extension: '.md',
|
||||||
|
context: { sourceView: 'files' },
|
||||||
|
});
|
||||||
|
if (err) throw new Error(err);
|
||||||
|
window.dispatchEvent(new CustomEvent('verstak:workbench-opened', { detail: result }));
|
||||||
|
});
|
||||||
|
|
||||||
|
const workbench = page.locator('.workbench-host');
|
||||||
|
await expect(workbench).toBeVisible({ timeout: 10000 });
|
||||||
|
const title = workbench.locator('.workbench-title');
|
||||||
|
await expect(title).toHaveText('Docs/readme.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('open .md with notes context routes to highest-priority provider', async ({ page }) => {
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
const [result, err] = await window.go.api.App.OpenWorkbenchResource('verstak.platform-test', {
|
||||||
|
kind: 'vault-file',
|
||||||
|
path: 'Notes/Overview.md',
|
||||||
|
extension: '.md',
|
||||||
|
context: { sourceView: 'notes', isInsideNotesFolder: true, notesMode: true },
|
||||||
|
});
|
||||||
|
if (err) throw new Error(err);
|
||||||
|
window.dispatchEvent(new CustomEvent('verstak:workbench-opened', { detail: result }));
|
||||||
|
});
|
||||||
|
|
||||||
|
const workbench = page.locator('.workbench-host');
|
||||||
|
await expect(workbench).toBeVisible({ timeout: 10000 });
|
||||||
|
const title = workbench.locator('.workbench-title');
|
||||||
|
await expect(title).toHaveText('Notes/Overview.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('default-editor plugin is listed as loaded in plugin manager', async ({ page }) => {
|
||||||
|
await page.locator('.sidebar .nav-item').filter({ hasText: 'Plugin Manager' }).click();
|
||||||
|
const card = page.locator('.plugin-card').filter({ hasText: 'verstak.default-editor' });
|
||||||
|
await expect(card).toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(card.locator('.status-badge')).toHaveText('loaded');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('disable default-editor plugin removes its providers', async ({ page }) => {
|
||||||
|
await page.locator('.sidebar .nav-item').filter({ hasText: 'Plugin Manager' }).click();
|
||||||
|
const card = page.locator('.plugin-card').filter({ hasText: 'verstak.default-editor' });
|
||||||
|
await card.locator('button.btn-disable').click();
|
||||||
|
await expect(card.locator('button.btn-enable')).toBeVisible({ timeout: 10000 });
|
||||||
|
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
const [result, err] = await window.go.api.App.OpenWorkbenchResource('verstak.platform-test', {
|
||||||
|
kind: 'vault-file',
|
||||||
|
path: 'Docs/todo.txt',
|
||||||
|
extension: '.txt',
|
||||||
|
context: { sourceView: 'files' },
|
||||||
|
});
|
||||||
|
if (err) throw new Error(err);
|
||||||
|
window.dispatchEvent(new CustomEvent('verstak:workbench-opened', { detail: result }));
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page.locator('[data-workbench-status="no-provider"]')).toBeVisible({ timeout: 10000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('default-editor plugin card shows openProviders contribution count', async ({ page }) => {
|
||||||
|
await page.locator('.sidebar .nav-item').filter({ hasText: 'Plugin Manager' }).click();
|
||||||
|
const card = page.locator('.plugin-card').filter({ hasText: 'verstak.default-editor' });
|
||||||
|
await expect(card).toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(card.locator('.meta-row').filter({ hasText: 'Contributions:' })).toContainText('3 openProviders');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('default-editor does not add sidebar item', async ({ page }) => {
|
||||||
|
const sidebarItems = page.locator('.sidebar .plugin-item');
|
||||||
|
const count = await sidebarItems.count();
|
||||||
|
const hasDefaultEditor = await page.locator('.sidebar .plugin-item').filter({ hasText: /default.editor/i }).count();
|
||||||
|
expect(hasDefaultEditor).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('platform-test workbench buttons open files via default-editor', async ({ page }) => {
|
||||||
|
await page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' }).click();
|
||||||
|
await expect(page.locator('.pt-command-result')).toContainText('Command: handled', { timeout: 10000 });
|
||||||
|
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
const [result, err] = await window.go.api.App.OpenWorkbenchResource('verstak.platform-test', {
|
||||||
|
kind: 'vault-file',
|
||||||
|
path: 'Docs/todo.txt',
|
||||||
|
extension: '.txt',
|
||||||
|
context: { sourceView: 'files' },
|
||||||
|
});
|
||||||
|
if (err) throw new Error(err);
|
||||||
|
window.dispatchEvent(new CustomEvent('verstak:workbench-opened', { detail: result }));
|
||||||
|
});
|
||||||
|
|
||||||
|
const editor = page.locator('[data-editor-mode="text"]');
|
||||||
|
await expect(editor).toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(editor).toHaveAttribute('data-resource-path', 'Docs/todo.txt');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { waitForAppReady, setupConsoleCollector, resetMockState } from './helpers.js';
|
||||||
|
|
||||||
|
test.describe('G: Files Plugin', () => {
|
||||||
|
let consoleCollector;
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
consoleCollector = setupConsoleCollector(page);
|
||||||
|
await resetMockState(page);
|
||||||
|
await page.goto('/');
|
||||||
|
await waitForAppReady(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.afterEach(async () => {
|
||||||
|
consoleCollector.assertNoErrors();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('files plugin appears in Plugin Manager as loaded', async ({ page }) => {
|
||||||
|
await page.locator('.sidebar .nav-item').filter({ hasText: 'Plugin Manager' }).click();
|
||||||
|
const card = page.locator('.plugin-card').filter({ hasText: 'verstak.files' });
|
||||||
|
await expect(card).toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(card.locator('.status-badge')).toHaveText('loaded');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('files plugin does not add global sidebar item', async ({ page }) => {
|
||||||
|
const sidebarItem = page.locator('.sidebar .plugin-item').filter({ hasText: 'Files' });
|
||||||
|
await expect(sidebarItem).toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('open .txt via workbench from files context shows default-editor', async ({ page }) => {
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
const [result, err] = await window.go.api.App.OpenWorkbenchResource('verstak.files', {
|
||||||
|
kind: 'vault-file',
|
||||||
|
path: 'Docs/todo.txt',
|
||||||
|
extension: '.txt',
|
||||||
|
context: { sourcePluginId: 'verstak.files', sourceView: 'files' },
|
||||||
|
});
|
||||||
|
if (err) throw new Error(err);
|
||||||
|
window.dispatchEvent(new CustomEvent('verstak:workbench-opened', { detail: result }));
|
||||||
|
});
|
||||||
|
|
||||||
|
const editor = page.locator('[data-editor-mode="text"]');
|
||||||
|
await expect(editor).toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(editor).toHaveAttribute('data-resource-path', 'Docs/todo.txt');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('open .md via workbench from files context shows generic-markdown', async ({ page }) => {
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
const [result, err] = await window.go.api.App.OpenWorkbenchResource('verstak.files', {
|
||||||
|
kind: 'vault-file',
|
||||||
|
path: 'Docs/readme.md',
|
||||||
|
extension: '.md',
|
||||||
|
context: { sourcePluginId: 'verstak.files', sourceView: 'files' },
|
||||||
|
});
|
||||||
|
if (err) throw new Error(err);
|
||||||
|
window.dispatchEvent(new CustomEvent('verstak:workbench-opened', { detail: result }));
|
||||||
|
});
|
||||||
|
|
||||||
|
const workbench = page.locator('.workbench-host');
|
||||||
|
await expect(workbench).toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(workbench.locator('.workbench-title')).toHaveText('Docs/readme.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('open notes markdown via workbench from files context shows notes-markdown', async ({ page }) => {
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
const [result, err] = await window.go.api.App.OpenWorkbenchResource('verstak.files', {
|
||||||
|
kind: 'vault-file',
|
||||||
|
path: 'Notes/Overview.md',
|
||||||
|
extension: '.md',
|
||||||
|
context: { sourcePluginId: 'verstak.files', sourceView: 'files', isInsideNotesFolder: true, notesMode: true },
|
||||||
|
});
|
||||||
|
if (err) throw new Error(err);
|
||||||
|
window.dispatchEvent(new CustomEvent('verstak:workbench-opened', { detail: result }));
|
||||||
|
});
|
||||||
|
|
||||||
|
const workbench = page.locator('.workbench-host');
|
||||||
|
await expect(workbench).toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(workbench.locator('.workbench-title')).toHaveText('Notes/Overview.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('files plugin card shows openProviders in contributions', async ({ page }) => {
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
const [result, err] = await window.go.api.App.OpenWorkbenchResource('verstak.files', {
|
||||||
|
kind: 'vault-file', path: 'test.txt', extension: '.txt',
|
||||||
|
context: { sourcePluginId: 'verstak.files', sourceView: 'files' },
|
||||||
|
});
|
||||||
|
if (err) throw new Error(err);
|
||||||
|
window.dispatchEvent(new CustomEvent('verstak:workbench-opened', { detail: result }));
|
||||||
|
});
|
||||||
|
const editor = page.locator('[data-editor-mode="text"]');
|
||||||
|
await expect(editor).toBeVisible({ timeout: 5000 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -33,6 +33,7 @@ test.describe('D: Plugin API bridge', () => {
|
||||||
await expect(page.locator('.plugin-card').filter({ hasText: 'verstak.platform-test' }).locator('.status-badge')).toHaveText('loaded', { timeout: 10000 });
|
await expect(page.locator('.plugin-card').filter({ hasText: 'verstak.platform-test' }).locator('.status-badge')).toHaveText('loaded', { timeout: 10000 });
|
||||||
|
|
||||||
await page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' }).click();
|
await page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' }).click();
|
||||||
|
|
||||||
await expect(page.locator('.pt-saved-setting')).toHaveText('Saved setting: persisted through bridge', { timeout: 10000 });
|
await expect(page.locator('.pt-saved-setting')).toHaveText('Saved setting: persisted through bridge', { timeout: 10000 });
|
||||||
await expect(page.locator('.pt-badge')).toHaveAttribute('data-command-status', 'handled');
|
await expect(page.locator('.pt-badge')).toHaveAttribute('data-command-status', 'handled');
|
||||||
await expect(page.locator('.pt-badge')).toContainText('capability available');
|
await expect(page.locator('.pt-badge')).toContainText('capability available');
|
||||||
|
|
@ -44,28 +45,38 @@ test.describe('D: Plugin API bridge', () => {
|
||||||
await expect(page.locator('.pt-files-error-result')).toHaveAttribute('data-files-error-status', 'expected');
|
await expect(page.locator('.pt-files-error-result')).toHaveAttribute('data-files-error-status', 'expected');
|
||||||
await expect(page.locator('.pt-files-error-result')).toContainText('Files error path: rejected reserved-path');
|
await expect(page.locator('.pt-files-error-result')).toContainText('Files error path: rejected reserved-path');
|
||||||
|
|
||||||
await page.locator('.pt-open-workbench-notes').click();
|
await page.evaluate(async () => {
|
||||||
await expect(page.locator('.pt-workbench-result')).toHaveAttribute('data-workbench-status', 'ok');
|
const [result, err] = await window.go.api.App.OpenWorkbenchResource('verstak.platform-test', {
|
||||||
await expect(page.locator('.pt-workbench-result')).toContainText('Workbench: opened Notes/Overview.md with verstak.platform-test.markdown-diagnostic');
|
kind: 'vault-file',
|
||||||
await expect(page.locator('.pt-workbench-result')).toHaveAttribute('data-resource-context', 'notes-markdown');
|
path: 'Notes/Overview.md',
|
||||||
|
extension: '.md',
|
||||||
|
context: { sourceView: 'notes', isInsideNotesFolder: true, notesMode: true },
|
||||||
|
});
|
||||||
|
if (err) throw new Error(err);
|
||||||
|
window.dispatchEvent(new CustomEvent('verstak:workbench-opened', { detail: result }));
|
||||||
|
});
|
||||||
|
|
||||||
|
const workbench = page.locator('.workbench-host');
|
||||||
|
await expect(workbench).toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(workbench.locator('.workbench-title')).toHaveText('Notes/Overview.md');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('platform-test diagnostic provider routes text, markdown, and notes markdown contexts', async ({ page }) => {
|
test('workbench routes markdown files to default-editor provider', async ({ page }) => {
|
||||||
async function openFromDiagnostics(buttonClass, path, context) {
|
await page.evaluate(async () => {
|
||||||
await page.locator('.sidebar .plugin-item').filter({ hasText: 'Platform Test' }).click();
|
const [result, err] = await window.go.api.App.OpenWorkbenchResource('verstak.platform-test', {
|
||||||
await expect(page.locator('.pt-command-result')).toContainText('Command: handled', { timeout: 10000 });
|
kind: 'vault-file',
|
||||||
await page.locator(buttonClass).click();
|
path: 'Docs/readme.md',
|
||||||
const result = page.locator('.pt-workbench-result');
|
extension: '.md',
|
||||||
await expect(result).toHaveAttribute('data-workbench-status', 'ok', { timeout: 10000 });
|
context: { sourceView: 'files' },
|
||||||
await expect(result).toHaveAttribute('data-resource-path', path);
|
});
|
||||||
await expect(result).toHaveAttribute('data-resource-mode', 'edit');
|
if (err) throw new Error(err);
|
||||||
await expect(result).toHaveAttribute('data-resource-context', context);
|
window.dispatchEvent(new CustomEvent('verstak:workbench-opened', { detail: result }));
|
||||||
await expect(result).toContainText('verstak.platform-test.markdown-diagnostic');
|
});
|
||||||
}
|
|
||||||
|
|
||||||
await openFromDiagnostics('.pt-open-workbench-text', 'Docs/todo.txt', 'generic-text');
|
const workbench = page.locator('.workbench-host');
|
||||||
await openFromDiagnostics('.pt-open-workbench-markdown', 'Docs/readme.md', 'generic-markdown');
|
await expect(workbench).toBeVisible({ timeout: 10000 });
|
||||||
await openFromDiagnostics('.pt-open-workbench-notes', 'Notes/Overview.md', 'notes-markdown');
|
const title = workbench.locator('.workbench-title');
|
||||||
|
await expect(title).toHaveText('Docs/readme.md');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('workbench shows no-provider fallback when no provider matches', async ({ page }) => {
|
test('workbench shows no-provider fallback when no provider matches', async ({ page }) => {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ test.describe('E: Plugin Manager layout', () => {
|
||||||
test('plugin list scrolls through the global main scroll surface and stays responsive', async ({ page }) => {
|
test('plugin list scrolls through the global main scroll surface and stays responsive', async ({ page }) => {
|
||||||
await page.evaluate(() => window.__wailsMock.addSyntheticPlugins(18));
|
await page.evaluate(() => window.__wailsMock.addSyntheticPlugins(18));
|
||||||
await page.locator('button.reload-btn').click();
|
await page.locator('button.reload-btn').click();
|
||||||
await expect(page.locator('.plugin-card')).toHaveCount(19, { timeout: 10000 });
|
await expect(page.locator('.plugin-card')).toHaveCount(21, { timeout: 10000 });
|
||||||
|
|
||||||
const manager = page.locator('.plugin-manager');
|
const manager = page.locator('.plugin-manager');
|
||||||
const scrollSurface = page.locator('.content.scroll-surface');
|
const scrollSurface = page.locator('.content.scroll-surface');
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
import ViewContainer from './lib/shell/ViewContainer.svelte';
|
import ViewContainer from './lib/shell/ViewContainer.svelte';
|
||||||
import VaultSelection from './lib/shell/VaultSelection.svelte';
|
import VaultSelection from './lib/shell/VaultSelection.svelte';
|
||||||
import WorkbenchHost from './lib/shell/WorkbenchHost.svelte';
|
import WorkbenchHost from './lib/shell/WorkbenchHost.svelte';
|
||||||
|
import WorkspaceHost from './lib/shell/WorkspaceHost.svelte';
|
||||||
import * as App from '../wailsjs/go/api/App';
|
import * as App from '../wailsjs/go/api/App';
|
||||||
import { debug } from './lib/log/debug.js';
|
import { debug } from './lib/log/debug.js';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
@ -20,6 +21,9 @@
|
||||||
let activeSettingsPanelId = '';
|
let activeSettingsPanelId = '';
|
||||||
let openedResource = null;
|
let openedResource = null;
|
||||||
|
|
||||||
|
let workspaceNodes = [];
|
||||||
|
let currentWorkspaceNodeId = '';
|
||||||
|
|
||||||
function flog(msg) {
|
function flog(msg) {
|
||||||
App.WriteFrontendLog('App', msg);
|
App.WriteFrontendLog('App', msg);
|
||||||
}
|
}
|
||||||
|
|
@ -91,6 +95,15 @@
|
||||||
currentView = 'workbench';
|
currentView = 'workbench';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onWorkspaceNodeSelected(e) {
|
||||||
|
debug.log('[App] onWorkspaceNodeSelected:', e.detail?.nodeId);
|
||||||
|
currentWorkspaceNodeId = e.detail?.nodeId || '';
|
||||||
|
workspaceNodes = e.detail?.nodes || workspaceNodes;
|
||||||
|
if (currentWorkspaceNodeId) {
|
||||||
|
currentView = 'workspace';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onCloseSettings() {
|
function onCloseSettings() {
|
||||||
debug.log('[App] onCloseSettings');
|
debug.log('[App] onCloseSettings');
|
||||||
activeSettingsPluginId = '';
|
activeSettingsPluginId = '';
|
||||||
|
|
@ -105,6 +118,7 @@
|
||||||
window.addEventListener('verstak:open-settings', onOpenSettings);
|
window.addEventListener('verstak:open-settings', onOpenSettings);
|
||||||
window.addEventListener('verstak:close-settings', onCloseSettings);
|
window.addEventListener('verstak:close-settings', onCloseSettings);
|
||||||
window.addEventListener('verstak:workbench-opened', onWorkbenchOpened);
|
window.addEventListener('verstak:workbench-opened', onWorkbenchOpened);
|
||||||
|
window.addEventListener('verstak:workspace-node-selected', onWorkspaceNodeSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => { checkVault(); });
|
onMount(() => { checkVault(); });
|
||||||
|
|
@ -125,6 +139,8 @@
|
||||||
<PluginManager {activeSettingsPluginId} {activeSettingsPanelId} />
|
<PluginManager {activeSettingsPluginId} {activeSettingsPanelId} />
|
||||||
{:else if currentView === 'workbench'}
|
{:else if currentView === 'workbench'}
|
||||||
<WorkbenchHost {openedResource} />
|
<WorkbenchHost {openedResource} />
|
||||||
|
{:else if currentView === 'workspace'}
|
||||||
|
<WorkspaceHost currentNodeId={currentWorkspaceNodeId} nodes={workspaceNodes} />
|
||||||
{:else}
|
{:else}
|
||||||
<ViewContainer {activeView} {activeViewPluginId} />
|
<ViewContainer {activeView} {activeViewPluginId} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@
|
||||||
commands: (contributions.commands || []).filter(c => c.pluginId === pluginId).length,
|
commands: (contributions.commands || []).filter(c => c.pluginId === pluginId).length,
|
||||||
sidebar: (contributions.sidebarItems || []).filter(s => s.pluginId === pluginId).length,
|
sidebar: (contributions.sidebarItems || []).filter(s => s.pluginId === pluginId).length,
|
||||||
statusbar: (contributions.statusBarItems || []).filter(s => s.pluginId === pluginId).length,
|
statusbar: (contributions.statusBarItems || []).filter(s => s.pluginId === pluginId).length,
|
||||||
|
openProviders: (contributions.openProviders || []).filter(o => o.pluginId === pluginId).length,
|
||||||
};
|
};
|
||||||
|
|
||||||
$: contribSummary = (() => {
|
$: contribSummary = (() => {
|
||||||
|
|
@ -40,6 +41,7 @@
|
||||||
if (contribCounts.commands > 0) parts.push(contribCounts.commands + ' command' + (contribCounts.commands !== 1 ? 's' : ''));
|
if (contribCounts.commands > 0) parts.push(contribCounts.commands + ' command' + (contribCounts.commands !== 1 ? 's' : ''));
|
||||||
if (contribCounts.sidebar > 0) parts.push(contribCounts.sidebar + ' sidebar' + (contribCounts.sidebar !== 1 ? 's' : ''));
|
if (contribCounts.sidebar > 0) parts.push(contribCounts.sidebar + ' sidebar' + (contribCounts.sidebar !== 1 ? 's' : ''));
|
||||||
if (contribCounts.statusbar > 0) parts.push(contribCounts.statusbar + ' statusbar' + (contribCounts.statusbar !== 1 ? 's' : ''));
|
if (contribCounts.statusbar > 0) parts.push(contribCounts.statusbar + ' statusbar' + (contribCounts.statusbar !== 1 ? 's' : ''));
|
||||||
|
if (contribCounts.openProviders > 0) parts.push(contribCounts.openProviders + ' openProvider' + (contribCounts.openProviders !== 1 ? 's' : ''));
|
||||||
return parts.length > 0 ? parts.join(', ') : 'none';
|
return parts.length > 0 ? parts.join(', ') : 'none';
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
@ -200,7 +202,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Permission warnings -->
|
<!-- Permission warnings -->
|
||||||
{#if !hasUIPermission && (m.contributes && (m.contributes.views || m.contributes.sidebarItems || m.contributes.settingsPanels).length > 0)}
|
{#if !hasUIPermission && m.contributes && ((m.contributes.views || []).length > 0 || (m.contributes.sidebarItems || []).length > 0 || (m.contributes.settingsPanels || []).length > 0)}
|
||||||
<p class="warning"><Icon name="warning" size={12} /> Plugin has UI contributions but lacks ui.register permission</p>
|
<p class="warning"><Icon name="warning" size={12} /> Plugin has UI contributions but lacks ui.register permission</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -89,8 +89,11 @@
|
||||||
.workbench-content {
|
.workbench-content {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
height: 100%;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 1rem;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workbench-empty {
|
.workbench-empty {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
<script>
|
||||||
|
import PluginBundleHost from '../plugin-host/PluginBundleHost.svelte';
|
||||||
|
import * as App from '../../../wailsjs/go/api/App';
|
||||||
|
|
||||||
|
export let currentNodeId = '';
|
||||||
|
export let nodes = [];
|
||||||
|
|
||||||
|
let contributions = {};
|
||||||
|
let plugins = [];
|
||||||
|
let workspaceTools = [];
|
||||||
|
|
||||||
|
$: currentNode = nodes.find(n => n.id === currentNodeId) || null;
|
||||||
|
$: if (currentNodeId) loadTools();
|
||||||
|
|
||||||
|
async function loadTools() {
|
||||||
|
try {
|
||||||
|
const [c, p] = await Promise.all([
|
||||||
|
App.GetContributions().catch(() => ({})),
|
||||||
|
App.GetPlugins().catch(() => []),
|
||||||
|
]);
|
||||||
|
contributions = c || {};
|
||||||
|
plugins = p || [];
|
||||||
|
|
||||||
|
const enabledIds = new Set(
|
||||||
|
plugins.filter(pl => pl.enabled && (pl.status === 'loaded' || pl.status === 'degraded')).map(pl => pl.manifest?.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
workspaceTools = (contributions.workspaceItems || []).filter(tool => enabledIds.has(tool.pluginId));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[WorkspaceHost] loadTools error:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="workspace-host">
|
||||||
|
{#if currentNode}
|
||||||
|
<div class="workspace-header">
|
||||||
|
<span class="workspace-title">{currentNode.title}</span>
|
||||||
|
<span class="workspace-type">{currentNode.type}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if workspaceTools.length > 0}
|
||||||
|
<div class="workspace-tools">
|
||||||
|
{#each workspaceTools as tool (tool.id + tool.pluginId)}
|
||||||
|
<div class="workspace-tool">
|
||||||
|
<div class="tool-header">
|
||||||
|
<span class="tool-title">{tool.title || tool.id}</span>
|
||||||
|
<span class="tool-plugin">{tool.pluginId}</span>
|
||||||
|
</div>
|
||||||
|
<div class="tool-content">
|
||||||
|
<PluginBundleHost
|
||||||
|
pluginId={tool.pluginId}
|
||||||
|
componentId={tool.component}
|
||||||
|
componentProps={{ workspaceNodeId: currentNodeId, workspaceNode: currentNode }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="workspace-empty">
|
||||||
|
<p>No workspace tools available</p>
|
||||||
|
<p class="workspace-hint">Install plugins that contribute workspaceItems to see tools here.</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<div class="workspace-empty">
|
||||||
|
<p>Select a workspace node from the sidebar</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.workspace-host {
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
background: #1a1a2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-bottom: 1px solid #16213e;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-title {
|
||||||
|
color: #e0e0f0;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-type {
|
||||||
|
color: #4ecca3;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.1rem 0.4rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #1a2a3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-tools {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-tool {
|
||||||
|
border: 1px solid #16213e;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.4rem 0.75rem;
|
||||||
|
background: #12122a;
|
||||||
|
border-bottom: 1px solid #16213e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-title {
|
||||||
|
color: #e0e0f0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-plugin {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-content {
|
||||||
|
min-height: 300px;
|
||||||
|
max-height: 60vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-empty {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #666;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-hint {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #555;
|
||||||
|
max-width: 300px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -78,6 +78,9 @@
|
||||||
if (err) { localError = err; return; }
|
if (err) { localError = err; return; }
|
||||||
currentNodeId = id;
|
currentNodeId = id;
|
||||||
activeWorkspaceNodeId.set(id);
|
activeWorkspaceNodeId.set(id);
|
||||||
|
window.dispatchEvent(new CustomEvent('verstak:workspace-node-selected', {
|
||||||
|
detail: { nodeId: id, nodes: nodes }
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function openCreate(parentId, type) {
|
function openCreate(parentId, type) {
|
||||||
|
|
|
||||||
|
|
@ -47,11 +47,10 @@
|
||||||
{
|
{
|
||||||
id: 'verstak.platform-test.markdown-diagnostic',
|
id: 'verstak.platform-test.markdown-diagnostic',
|
||||||
title: 'Platform Test Markdown Diagnostic',
|
title: 'Platform Test Markdown Diagnostic',
|
||||||
priority: 100,
|
priority: 10,
|
||||||
component: 'MarkdownDiagnosticProvider',
|
component: 'MarkdownDiagnosticProvider',
|
||||||
supports: [
|
supports: [
|
||||||
{ kind: 'vault-file', extensions: ['.md', '.markdown'], contexts: ['generic-markdown', 'notes-markdown'] },
|
{ kind: 'vault-file', extensions: ['.md', '.markdown'], contexts: ['generic-markdown', 'notes-markdown'] }
|
||||||
{ kind: 'vault-file', extensions: ['.txt', '.log'], mime: ['text/plain'], contexts: ['generic-text'] }
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -59,11 +58,86 @@
|
||||||
},
|
},
|
||||||
rootPath: '/tmp/verstak-test/plugins/platform-test',
|
rootPath: '/tmp/verstak-test/plugins/platform-test',
|
||||||
error: ''
|
error: ''
|
||||||
|
},
|
||||||
|
'verstak.default-editor': {
|
||||||
|
status: 'loaded',
|
||||||
|
enabled: true,
|
||||||
|
manifest: {
|
||||||
|
schemaVersion: 1,
|
||||||
|
id: 'verstak.default-editor',
|
||||||
|
name: 'Default Editor',
|
||||||
|
version: '0.1.0',
|
||||||
|
apiVersion: '0.1.0',
|
||||||
|
description: 'Built-in text and markdown editor/viewer.',
|
||||||
|
source: 'official',
|
||||||
|
icon: 'edit',
|
||||||
|
provides: ['verstak/default-editor/v1'],
|
||||||
|
requires: ['verstak/core/files/v1', 'verstak/core/workbench/v1'],
|
||||||
|
permissions: ['files.read', 'files.write', 'workbench.open'],
|
||||||
|
frontend: { entry: 'frontend/dist/index.js' },
|
||||||
|
contributes: {
|
||||||
|
openProviders: [
|
||||||
|
{
|
||||||
|
id: 'verstak.default-editor.text',
|
||||||
|
title: 'Default Text Editor',
|
||||||
|
priority: 50,
|
||||||
|
component: 'DefaultEditor',
|
||||||
|
supports: [
|
||||||
|
{ kind: 'vault-file', extensions: ['.txt', '.log', '.conf', '.ini', '.toml', '.yaml', '.yml', '.json', '.csv'], mime: ['text/plain', 'application/json'], contexts: ['generic-text'] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'verstak.default-editor.markdown',
|
||||||
|
title: 'Default Markdown Editor',
|
||||||
|
priority: 50,
|
||||||
|
component: 'DefaultEditor',
|
||||||
|
supports: [
|
||||||
|
{ kind: 'vault-file', extensions: ['.md', '.markdown'], contexts: ['generic-markdown'] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'verstak.default-editor.notes-markdown',
|
||||||
|
title: 'Default Notes Markdown Editor',
|
||||||
|
priority: 50,
|
||||||
|
component: 'DefaultEditor',
|
||||||
|
supports: [
|
||||||
|
{ kind: 'vault-file', extensions: ['.md', '.markdown'], contexts: ['notes-markdown'] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rootPath: '/tmp/verstak-test/plugins/default-editor',
|
||||||
|
error: ''
|
||||||
|
},
|
||||||
|
'verstak.files': {
|
||||||
|
status: 'loaded',
|
||||||
|
enabled: true,
|
||||||
|
manifest: {
|
||||||
|
schemaVersion: 1,
|
||||||
|
id: 'verstak.files',
|
||||||
|
name: 'Files',
|
||||||
|
version: '0.1.0',
|
||||||
|
apiVersion: '0.1.0',
|
||||||
|
description: 'Minimal vault file navigator.',
|
||||||
|
source: 'official',
|
||||||
|
icon: 'folder',
|
||||||
|
provides: ['verstak/files/v1'],
|
||||||
|
requires: ['verstak/core/files/v1', 'verstak/core/workbench/v1'],
|
||||||
|
permissions: ['files.read', 'files.write', 'workbench.open', 'ui.register'],
|
||||||
|
frontend: { entry: 'frontend/dist/index.js' },
|
||||||
|
contributes: {
|
||||||
|
views: [{ id: 'verstak.files.view', title: 'Files', icon: 'folder', component: 'FilesView' }],
|
||||||
|
workspaceItems: [{ id: 'verstak.files.workspace', title: 'Files', icon: 'folder', component: 'FilesView' }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rootPath: '/tmp/verstak-test/plugins/files',
|
||||||
|
error: ''
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var vaultStatus = { status: 'open', path: '/tmp/verstak-test/vault', vaultId: 'test-vault-001' };
|
var vaultStatus = { status: 'open', path: '/tmp/verstak-test/vault', vaultId: 'test-vault-001' };
|
||||||
var vaultPluginState = { enabledPlugins: ['verstak.platform-test'], disabledPlugins: [], desiredPlugins: [{ id: 'verstak.platform-test', version: '0.1.0', source: 'official' }] };
|
var vaultPluginState = { enabledPlugins: ['verstak.platform-test', 'verstak.default-editor', 'verstak.files'], disabledPlugins: [], desiredPlugins: [{ id: 'verstak.platform-test', version: '0.1.0', source: 'official' }, { id: 'verstak.default-editor', version: '0.1.0', source: 'official' }, { id: 'verstak.files', version: '0.1.0', source: 'official' }] };
|
||||||
var appSettings = { currentVaultPath: '/tmp/verstak-test/vault', recentVaults: [] };
|
var appSettings = { currentVaultPath: '/tmp/verstak-test/vault', recentVaults: [] };
|
||||||
var workbenchPreferences = {};
|
var workbenchPreferences = {};
|
||||||
var openedResources = [];
|
var openedResources = [];
|
||||||
|
|
@ -97,7 +171,12 @@
|
||||||
|
|
||||||
function makeDefaultVaultFiles() {
|
function makeDefaultVaultFiles() {
|
||||||
return {
|
return {
|
||||||
'': { type: 'folder', modifiedAt: new Date().toISOString() }
|
'': { type: 'folder', modifiedAt: new Date().toISOString() },
|
||||||
|
'Docs': { type: 'folder', modifiedAt: new Date().toISOString() },
|
||||||
|
'Docs/todo.txt': { type: 'file', content: 'Buy groceries\nWrite tests', modifiedAt: new Date().toISOString() },
|
||||||
|
'Docs/readme.md': { type: 'file', content: '# Hello World\n\nThis is a **test** document.\n\n- item 1\n- item 2', modifiedAt: new Date().toISOString() },
|
||||||
|
'Notes': { type: 'folder', modifiedAt: new Date().toISOString() },
|
||||||
|
'Notes/Overview.md': { type: 'file', content: '# Notes Overview\n\nMy notes content here.', modifiedAt: new Date().toISOString() }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,7 +283,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function allContributions() {
|
function allContributions() {
|
||||||
var views = [], commands = [], sidebarItems = [], statusBarItems = [], settingsPanels = [], openProviders = [];
|
var views = [], commands = [], sidebarItems = [], statusBarItems = [], settingsPanels = [], openProviders = [], workspaceItems = [];
|
||||||
for (var id in pluginStates) {
|
for (var id in pluginStates) {
|
||||||
var s = pluginStates[id];
|
var s = pluginStates[id];
|
||||||
var c = (s.manifest && s.manifest.contributes) || {};
|
var c = (s.manifest && s.manifest.contributes) || {};
|
||||||
|
|
@ -214,8 +293,9 @@
|
||||||
if (c.statusBarItems) c.statusBarItems.forEach(function (st) { statusBarItems.push(Object.assign({}, st, { pluginId: id })); });
|
if (c.statusBarItems) c.statusBarItems.forEach(function (st) { statusBarItems.push(Object.assign({}, st, { pluginId: id })); });
|
||||||
if (c.settingsPanels) c.settingsPanels.forEach(function (sp) { settingsPanels.push(Object.assign({}, sp, { pluginId: id })); });
|
if (c.settingsPanels) c.settingsPanels.forEach(function (sp) { settingsPanels.push(Object.assign({}, sp, { pluginId: id })); });
|
||||||
if (c.openProviders) c.openProviders.forEach(function (op) { openProviders.push(Object.assign({}, op, { pluginId: id })); });
|
if (c.openProviders) c.openProviders.forEach(function (op) { openProviders.push(Object.assign({}, op, { pluginId: id })); });
|
||||||
|
if (c.workspaceItems) c.workspaceItems.forEach(function (wi) { workspaceItems.push(Object.assign({}, wi, { pluginId: id })); });
|
||||||
}
|
}
|
||||||
return { views: views, commands: commands, sidebarItems: sidebarItems, statusBarItems: statusBarItems, settingsPanels: settingsPanels, openProviders: openProviders };
|
return { views: views, commands: commands, sidebarItems: sidebarItems, statusBarItems: statusBarItems, settingsPanels: settingsPanels, openProviders: openProviders, workspaceItems: workspaceItems };
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestExtension(request) {
|
function requestExtension(request) {
|
||||||
|
|
@ -294,6 +374,118 @@
|
||||||
return Promise.resolve([result, '']);
|
return Promise.resolve([result, '']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function defaultEditorBundle() {
|
||||||
|
return [
|
||||||
|
'(function(){',
|
||||||
|
'var DefaultEditor={',
|
||||||
|
'mount:function(c,p,api){',
|
||||||
|
'c.innerHTML="";',
|
||||||
|
'c.className="de-root";',
|
||||||
|
'var req=p.request||{};',
|
||||||
|
'var path=req.path||"";',
|
||||||
|
'var mode=req.mode||"view";',
|
||||||
|
'var ctx=req.context||{};',
|
||||||
|
'var isNotes=ctx.notesMode||ctx.isInsideNotesFolder;',
|
||||||
|
'var ext=(req.extension||"").toLowerCase();',
|
||||||
|
'var isMd=ext===".md"||ext===".markdown";',
|
||||||
|
'var editorMode=isNotes?"notes-markdown":isMd?"generic-markdown":"text";',
|
||||||
|
'c.setAttribute("data-editor-mode",editorMode);',
|
||||||
|
'c.setAttribute("data-resource-path",path);',
|
||||||
|
'c.setAttribute("data-request-mode",mode);',
|
||||||
|
'var toolbar=document.createElement("div");',
|
||||||
|
'toolbar.className="de-toolbar";',
|
||||||
|
'var modeLabel=document.createElement("span");',
|
||||||
|
'modeLabel.className="de-toolbar-mode";',
|
||||||
|
'modeLabel.textContent=editorMode;',
|
||||||
|
'toolbar.appendChild(modeLabel);',
|
||||||
|
'var pathLabel=document.createElement("span");',
|
||||||
|
'pathLabel.className="de-toolbar-context";',
|
||||||
|
'pathLabel.textContent=path;',
|
||||||
|
'toolbar.appendChild(pathLabel);',
|
||||||
|
'if(isNotes){var badge=document.createElement("span");badge.className="de-notes-badge";badge.textContent="notes context";badge.setAttribute("data-notes-badge","");toolbar.appendChild(badge);}',
|
||||||
|
'c.appendChild(toolbar);',
|
||||||
|
'var content=document.createElement("div");',
|
||||||
|
'content.className="de-editor-wrap";',
|
||||||
|
'content.textContent="Loading...";',
|
||||||
|
'c.appendChild(content);',
|
||||||
|
'api.files.readText(path).then(function(text){',
|
||||||
|
'content.textContent="";',
|
||||||
|
'if(isMd){',
|
||||||
|
'var preview=document.createElement("div");',
|
||||||
|
'preview.className="de-preview";',
|
||||||
|
'preview.setAttribute("data-preview","");',
|
||||||
|
'preview.textContent=text;',
|
||||||
|
'content.appendChild(preview);',
|
||||||
|
'}else{',
|
||||||
|
'var ta=document.createElement("textarea");',
|
||||||
|
'ta.className="de-textarea";',
|
||||||
|
'ta.value=text;',
|
||||||
|
'ta.setAttribute("data-editor-textarea","");',
|
||||||
|
'content.appendChild(ta);',
|
||||||
|
'}',
|
||||||
|
'}).catch(function(err){',
|
||||||
|
'content.textContent="Error: "+(err.message||err);',
|
||||||
|
'});',
|
||||||
|
'},',
|
||||||
|
'unmount:function(c){c.innerHTML="";}',
|
||||||
|
'};',
|
||||||
|
'window.VerstakPluginRegister("verstak.default-editor",{components:{DefaultEditor:DefaultEditor}});',
|
||||||
|
'})();'
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function filesPluginBundle() {
|
||||||
|
return [
|
||||||
|
"(function(){",
|
||||||
|
"var FilesView={",
|
||||||
|
"mount:function(c,p,api){",
|
||||||
|
"c.innerHTML='';",
|
||||||
|
"c.className='files-root';",
|
||||||
|
"c.setAttribute('data-plugin-id','verstak.files');",
|
||||||
|
"var list=document.createElement('div');",
|
||||||
|
"list.className='files-list';",
|
||||||
|
"list.setAttribute('data-files-list','');",
|
||||||
|
"c.appendChild(list);",
|
||||||
|
"function load(){",
|
||||||
|
"list.textContent='Loading...';",
|
||||||
|
"api.files.list('').then(function(entries){",
|
||||||
|
"list.innerHTML='';",
|
||||||
|
"if(!entries||!entries.length){list.textContent='Empty folder';return;}",
|
||||||
|
"entries.forEach(function(e){",
|
||||||
|
"if(e.isHidden||e.isReserved)return;",
|
||||||
|
"var item=document.createElement('div');",
|
||||||
|
"item.className='files-item';",
|
||||||
|
"item.setAttribute('data-file-name',e.name);",
|
||||||
|
"item.setAttribute('data-file-type',e.type);",
|
||||||
|
"item.setAttribute('data-file-path',e.relativePath);",
|
||||||
|
"var icon=document.createElement('span');",
|
||||||
|
"icon.className='files-item-icon';",
|
||||||
|
"icon.textContent=e.type==='folder'?'[D]':'[F]';",
|
||||||
|
"var name=document.createElement('span');",
|
||||||
|
"name.className='files-item-name';",
|
||||||
|
"name.textContent=e.name;",
|
||||||
|
"item.appendChild(icon);",
|
||||||
|
"item.appendChild(name);",
|
||||||
|
"if(e.type!=='folder'){",
|
||||||
|
"item.addEventListener('dblclick',function(){",
|
||||||
|
"var ext=e.extension?'.'+e.extension:'';",
|
||||||
|
"var ctx={sourcePluginId:'verstak.files',sourceView:'files'};",
|
||||||
|
"api.workbench.openResource({kind:'vault-file',path:e.relativePath,mode:'view',extension:ext,context:ctx});",
|
||||||
|
"});",
|
||||||
|
"}",
|
||||||
|
"list.appendChild(item);",
|
||||||
|
"});",
|
||||||
|
"}).catch(function(err){list.textContent='Error: '+(err.message||err);});",
|
||||||
|
"}",
|
||||||
|
"load();",
|
||||||
|
"},",
|
||||||
|
"unmount:function(c){c.innerHTML='';}",
|
||||||
|
"};",
|
||||||
|
"window.VerstakPluginRegister('verstak.files',{components:{FilesView:FilesView}});",
|
||||||
|
"})();"
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
function platformTestBundle() {
|
function platformTestBundle() {
|
||||||
return [
|
return [
|
||||||
"(function(){",
|
"(function(){",
|
||||||
|
|
@ -448,6 +640,12 @@
|
||||||
if (pluginId === 'verstak.platform-test' && assetPath === 'frontend/dist/index.js') {
|
if (pluginId === 'verstak.platform-test' && assetPath === 'frontend/dist/index.js') {
|
||||||
return Promise.resolve(platformTestBundle());
|
return Promise.resolve(platformTestBundle());
|
||||||
}
|
}
|
||||||
|
if (pluginId === 'verstak.default-editor' && assetPath === 'frontend/dist/index.js') {
|
||||||
|
return Promise.resolve(defaultEditorBundle());
|
||||||
|
}
|
||||||
|
if (pluginId === 'verstak.files' && assetPath === 'frontend/dist/index.js') {
|
||||||
|
return Promise.resolve(filesPluginBundle());
|
||||||
|
}
|
||||||
return Promise.resolve('');
|
return Promise.resolve('');
|
||||||
},
|
},
|
||||||
GetPluginCapability: function (pluginId, capId) {
|
GetPluginCapability: function (pluginId, capId) {
|
||||||
|
|
@ -675,11 +873,10 @@
|
||||||
{
|
{
|
||||||
id: 'verstak.platform-test.markdown-diagnostic',
|
id: 'verstak.platform-test.markdown-diagnostic',
|
||||||
title: 'Platform Test Markdown Diagnostic',
|
title: 'Platform Test Markdown Diagnostic',
|
||||||
priority: 100,
|
priority: 10,
|
||||||
component: 'MarkdownDiagnosticProvider',
|
component: 'MarkdownDiagnosticProvider',
|
||||||
supports: [
|
supports: [
|
||||||
{ kind: 'vault-file', extensions: ['.md', '.markdown'], contexts: ['generic-markdown', 'notes-markdown'] },
|
{ kind: 'vault-file', extensions: ['.md', '.markdown'], contexts: ['generic-markdown', 'notes-markdown'] }
|
||||||
{ kind: 'vault-file', extensions: ['.txt', '.log'], mime: ['text/plain'], contexts: ['generic-text'] }
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -687,10 +884,85 @@
|
||||||
},
|
},
|
||||||
rootPath: '/tmp/verstak-test/plugins/platform-test',
|
rootPath: '/tmp/verstak-test/plugins/platform-test',
|
||||||
error: ''
|
error: ''
|
||||||
|
},
|
||||||
|
'verstak.default-editor': {
|
||||||
|
status: 'loaded',
|
||||||
|
enabled: true,
|
||||||
|
manifest: {
|
||||||
|
schemaVersion: 1,
|
||||||
|
id: 'verstak.default-editor',
|
||||||
|
name: 'Default Editor',
|
||||||
|
version: '0.1.0',
|
||||||
|
apiVersion: '0.1.0',
|
||||||
|
description: 'Built-in text and markdown editor/viewer.',
|
||||||
|
source: 'official',
|
||||||
|
icon: 'edit',
|
||||||
|
provides: ['verstak/default-editor/v1'],
|
||||||
|
requires: ['verstak/core/files/v1', 'verstak/core/workbench/v1'],
|
||||||
|
permissions: ['files.read', 'files.write', 'workbench.open'],
|
||||||
|
frontend: { entry: 'frontend/dist/index.js' },
|
||||||
|
contributes: {
|
||||||
|
openProviders: [
|
||||||
|
{
|
||||||
|
id: 'verstak.default-editor.text',
|
||||||
|
title: 'Default Text Editor',
|
||||||
|
priority: 50,
|
||||||
|
component: 'DefaultEditor',
|
||||||
|
supports: [
|
||||||
|
{ kind: 'vault-file', extensions: ['.txt', '.log', '.conf', '.ini', '.toml', '.yaml', '.yml', '.json', '.csv'], mime: ['text/plain', 'application/json'], contexts: ['generic-text'] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'verstak.default-editor.markdown',
|
||||||
|
title: 'Default Markdown Editor',
|
||||||
|
priority: 50,
|
||||||
|
component: 'DefaultEditor',
|
||||||
|
supports: [
|
||||||
|
{ kind: 'vault-file', extensions: ['.md', '.markdown'], contexts: ['generic-markdown'] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'verstak.default-editor.notes-markdown',
|
||||||
|
title: 'Default Notes Markdown Editor',
|
||||||
|
priority: 50,
|
||||||
|
component: 'DefaultEditor',
|
||||||
|
supports: [
|
||||||
|
{ kind: 'vault-file', extensions: ['.md', '.markdown'], contexts: ['notes-markdown'] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rootPath: '/tmp/verstak-test/plugins/default-editor',
|
||||||
|
error: ''
|
||||||
|
},
|
||||||
|
'verstak.files': {
|
||||||
|
status: 'loaded',
|
||||||
|
enabled: true,
|
||||||
|
manifest: {
|
||||||
|
schemaVersion: 1,
|
||||||
|
id: 'verstak.files',
|
||||||
|
name: 'Files',
|
||||||
|
version: '0.1.0',
|
||||||
|
apiVersion: '0.1.0',
|
||||||
|
description: 'Minimal vault file navigator.',
|
||||||
|
source: 'official',
|
||||||
|
icon: 'folder',
|
||||||
|
provides: ['verstak/files/v1'],
|
||||||
|
requires: ['verstak/core/files/v1', 'verstak/core/workbench/v1'],
|
||||||
|
permissions: ['files.read', 'files.write', 'workbench.open', 'ui.register'],
|
||||||
|
frontend: { entry: 'frontend/dist/index.js' },
|
||||||
|
contributes: {
|
||||||
|
views: [{ id: 'verstak.files.view', title: 'Files', icon: 'folder', component: 'FilesView' }],
|
||||||
|
workspaceItems: [{ id: 'verstak.files.workspace', title: 'Files', icon: 'folder', component: 'FilesView' }]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rootPath: '/tmp/verstak-test/plugins/files',
|
||||||
|
error: ''
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
vaultStatus = { status: 'open', path: '/tmp/verstak-test/vault', vaultId: 'test-vault-001' };
|
vaultStatus = { status: 'open', path: '/tmp/verstak-test/vault', vaultId: 'test-vault-001' };
|
||||||
vaultPluginState = { enabledPlugins: ['verstak.platform-test'], disabledPlugins: [], desiredPlugins: [{ id: 'verstak.platform-test', version: '0.1.0', source: 'official' }] };
|
vaultPluginState = { enabledPlugins: ['verstak.platform-test', 'verstak.default-editor', 'verstak.files'], disabledPlugins: [], desiredPlugins: [{ id: 'verstak.platform-test', version: '0.1.0', source: 'official' }, { id: 'verstak.default-editor', version: '0.1.0', source: 'official' }, { id: 'verstak.files', version: '0.1.0', source: 'official' }] };
|
||||||
appSettings = { currentVaultPath: '/tmp/verstak-test/vault', recentVaults: [] };
|
appSettings = { currentVaultPath: '/tmp/verstak-test/vault', recentVaults: [] };
|
||||||
workbenchPreferences = {};
|
workbenchPreferences = {};
|
||||||
openedResources = [];
|
openedResources = [];
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
// This file is automatically generated. DO NOT EDIT
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
import {workbench} from '../models';
|
||||||
import {capability} from '../models';
|
import {capability} from '../models';
|
||||||
import {api} from '../models';
|
import {api} from '../models';
|
||||||
import {permissions} from '../models';
|
import {permissions} from '../models';
|
||||||
import {plugin} from '../models';
|
import {plugin} from '../models';
|
||||||
import {files} from '../models';
|
import {files} from '../models';
|
||||||
import {workbench} from '../models';
|
|
||||||
|
|
||||||
export function ArchiveWorkspaceNode(arg1:string):Promise<string>;
|
export function ArchiveWorkspaceNode(arg1:string):Promise<string>;
|
||||||
|
|
||||||
|
|
@ -19,12 +19,12 @@ export function CreateWorkspaceNode(arg1:string,arg2:string,arg3:string):Promise
|
||||||
|
|
||||||
export function DisablePlugin(arg1:string):Promise<string>;
|
export function DisablePlugin(arg1:string):Promise<string>;
|
||||||
|
|
||||||
|
export function EditWorkbenchResource(arg1:string,arg2:Record<string, any>):Promise<workbench.OpenResourceResult|string>;
|
||||||
|
|
||||||
export function EnablePlugin(arg1:string):Promise<string>;
|
export function EnablePlugin(arg1:string):Promise<string>;
|
||||||
|
|
||||||
export function ExecutePluginCommand(arg1:string,arg2:string,arg3:Record<string, any>):Promise<Record<string, any>|string>;
|
export function ExecutePluginCommand(arg1:string,arg2:string,arg3:Record<string, any>):Promise<Record<string, any>|string>;
|
||||||
|
|
||||||
export function EditWorkbenchResource(arg1:string,arg2:Record<string, any>):Promise<workbench.OpenResourceResult|string>;
|
|
||||||
|
|
||||||
export function GetAppSettings():Promise<Record<string, any>>;
|
export function GetAppSettings():Promise<Record<string, any>>;
|
||||||
|
|
||||||
export function GetCapabilities():Promise<Array<capability.Entry>>;
|
export function GetCapabilities():Promise<Array<capability.Entry>>;
|
||||||
|
|
@ -49,12 +49,12 @@ export function GetVaultPluginState():Promise<Record<string, any>>;
|
||||||
|
|
||||||
export function GetVaultStatus():Promise<Record<string, string>>;
|
export function GetVaultStatus():Promise<Record<string, string>>;
|
||||||
|
|
||||||
export function GetWorkspaceTree():Promise<Record<string, any>>;
|
|
||||||
|
|
||||||
export function GetWorkbenchOpenedResources():Promise<Array<workbench.OpenedResource>>;
|
export function GetWorkbenchOpenedResources():Promise<Array<workbench.OpenedResource>>;
|
||||||
|
|
||||||
export function GetWorkbenchPreferences():Promise<workbench.Preferences>;
|
export function GetWorkbenchPreferences():Promise<workbench.Preferences>;
|
||||||
|
|
||||||
|
export function GetWorkspaceTree():Promise<Record<string, any>>;
|
||||||
|
|
||||||
export function ListPluginCapabilities(arg1:string):Promise<Array<capability.Entry>|string>;
|
export function ListPluginCapabilities(arg1:string):Promise<Array<capability.Entry>|string>;
|
||||||
|
|
||||||
export function ListVaultFiles(arg1:string,arg2:string):Promise<Array<files.FileEntry>|string>;
|
export function ListVaultFiles(arg1:string,arg2:string):Promise<Array<files.FileEntry>|string>;
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,10 @@ export function DisablePlugin(arg1) {
|
||||||
return window['go']['api']['App']['DisablePlugin'](arg1);
|
return window['go']['api']['App']['DisablePlugin'](arg1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function EditWorkbenchResource(arg1, arg2) {
|
||||||
|
return window['go']['api']['App']['EditWorkbenchResource'](arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
export function EnablePlugin(arg1) {
|
export function EnablePlugin(arg1) {
|
||||||
return window['go']['api']['App']['EnablePlugin'](arg1);
|
return window['go']['api']['App']['EnablePlugin'](arg1);
|
||||||
}
|
}
|
||||||
|
|
@ -34,10 +38,6 @@ export function ExecutePluginCommand(arg1, arg2, arg3) {
|
||||||
return window['go']['api']['App']['ExecutePluginCommand'](arg1, arg2, arg3);
|
return window['go']['api']['App']['ExecutePluginCommand'](arg1, arg2, arg3);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EditWorkbenchResource(arg1, arg2) {
|
|
||||||
return window['go']['api']['App']['EditWorkbenchResource'](arg1, arg2);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GetAppSettings() {
|
export function GetAppSettings() {
|
||||||
return window['go']['api']['App']['GetAppSettings']();
|
return window['go']['api']['App']['GetAppSettings']();
|
||||||
}
|
}
|
||||||
|
|
@ -86,10 +86,6 @@ export function GetVaultStatus() {
|
||||||
return window['go']['api']['App']['GetVaultStatus']();
|
return window['go']['api']['App']['GetVaultStatus']();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GetWorkspaceTree() {
|
|
||||||
return window['go']['api']['App']['GetWorkspaceTree']();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GetWorkbenchOpenedResources() {
|
export function GetWorkbenchOpenedResources() {
|
||||||
return window['go']['api']['App']['GetWorkbenchOpenedResources']();
|
return window['go']['api']['App']['GetWorkbenchOpenedResources']();
|
||||||
}
|
}
|
||||||
|
|
@ -98,6 +94,10 @@ export function GetWorkbenchPreferences() {
|
||||||
return window['go']['api']['App']['GetWorkbenchPreferences']();
|
return window['go']['api']['App']['GetWorkbenchPreferences']();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GetWorkspaceTree() {
|
||||||
|
return window['go']['api']['App']['GetWorkspaceTree']();
|
||||||
|
}
|
||||||
|
|
||||||
export function ListPluginCapabilities(arg1) {
|
export function ListPluginCapabilities(arg1) {
|
||||||
return window['go']['api']['App']['ListPluginCapabilities'](arg1);
|
return window['go']['api']['App']['ListPluginCapabilities'](arg1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,35 @@
|
||||||
export namespace api {
|
export namespace api {
|
||||||
|
|
||||||
|
export class FlatWorkspaceItem {
|
||||||
|
pluginId: string;
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
icon?: string;
|
||||||
|
component: string;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new FlatWorkspaceItem(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.pluginId = source["pluginId"];
|
||||||
|
this.id = source["id"];
|
||||||
|
this.title = source["title"];
|
||||||
|
this.icon = source["icon"];
|
||||||
|
this.component = source["component"];
|
||||||
|
}
|
||||||
|
}
|
||||||
export class FlatOpenProviderSupport {
|
export class FlatOpenProviderSupport {
|
||||||
kind: string;
|
kind: string;
|
||||||
mime?: string[];
|
mime?: string[];
|
||||||
extensions?: string[];
|
extensions?: string[];
|
||||||
contexts?: string[];
|
contexts?: string[];
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new FlatOpenProviderSupport(source);
|
return new FlatOpenProviderSupport(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.kind = source["kind"];
|
this.kind = source["kind"];
|
||||||
|
|
@ -25,11 +45,11 @@ export namespace api {
|
||||||
priority?: number;
|
priority?: number;
|
||||||
component: string;
|
component: string;
|
||||||
supports: FlatOpenProviderSupport[];
|
supports: FlatOpenProviderSupport[];
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new FlatOpenProvider(source);
|
return new FlatOpenProvider(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.pluginId = source["pluginId"];
|
this.pluginId = source["pluginId"];
|
||||||
|
|
@ -39,7 +59,7 @@ export namespace api {
|
||||||
this.component = source["component"];
|
this.component = source["component"];
|
||||||
this.supports = this.convertValues(source["supports"], FlatOpenProviderSupport);
|
this.supports = this.convertValues(source["supports"], FlatOpenProviderSupport);
|
||||||
}
|
}
|
||||||
|
|
||||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||||
if (!a) {
|
if (!a) {
|
||||||
return a;
|
return a;
|
||||||
|
|
@ -65,11 +85,11 @@ export namespace api {
|
||||||
icon?: string;
|
icon?: string;
|
||||||
view: string;
|
view: string;
|
||||||
position?: number;
|
position?: number;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new FlatSidebarItem(source);
|
return new FlatSidebarItem(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.pluginId = source["pluginId"];
|
this.pluginId = source["pluginId"];
|
||||||
|
|
@ -86,11 +106,11 @@ export namespace api {
|
||||||
title: string;
|
title: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
component: string;
|
component: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new FlatSettingsPanel(source);
|
return new FlatSettingsPanel(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.pluginId = source["pluginId"];
|
this.pluginId = source["pluginId"];
|
||||||
|
|
@ -106,11 +126,11 @@ export namespace api {
|
||||||
title: string;
|
title: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
handler?: string;
|
handler?: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new FlatCommand(source);
|
return new FlatCommand(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.pluginId = source["pluginId"];
|
this.pluginId = source["pluginId"];
|
||||||
|
|
@ -126,11 +146,11 @@ export namespace api {
|
||||||
title: string;
|
title: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
component: string;
|
component: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new FlatView(source);
|
return new FlatView(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.pluginId = source["pluginId"];
|
this.pluginId = source["pluginId"];
|
||||||
|
|
@ -146,11 +166,12 @@ export namespace api {
|
||||||
settingsPanels: FlatSettingsPanel[];
|
settingsPanels: FlatSettingsPanel[];
|
||||||
sidebarItems: FlatSidebarItem[];
|
sidebarItems: FlatSidebarItem[];
|
||||||
openProviders: FlatOpenProvider[];
|
openProviders: FlatOpenProvider[];
|
||||||
|
workspaceItems: FlatWorkspaceItem[];
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new ContributionSummary(source);
|
return new ContributionSummary(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.views = this.convertValues(source["views"], FlatView);
|
this.views = this.convertValues(source["views"], FlatView);
|
||||||
|
|
@ -158,8 +179,9 @@ export namespace api {
|
||||||
this.settingsPanels = this.convertValues(source["settingsPanels"], FlatSettingsPanel);
|
this.settingsPanels = this.convertValues(source["settingsPanels"], FlatSettingsPanel);
|
||||||
this.sidebarItems = this.convertValues(source["sidebarItems"], FlatSidebarItem);
|
this.sidebarItems = this.convertValues(source["sidebarItems"], FlatSidebarItem);
|
||||||
this.openProviders = this.convertValues(source["openProviders"], FlatOpenProvider);
|
this.openProviders = this.convertValues(source["openProviders"], FlatOpenProvider);
|
||||||
|
this.workspaceItems = this.convertValues(source["workspaceItems"], FlatWorkspaceItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||||
if (!a) {
|
if (!a) {
|
||||||
return a;
|
return a;
|
||||||
|
|
@ -178,185 +200,27 @@ export namespace api {
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace workbench {
|
|
||||||
|
|
||||||
export class OpenResourceContext {
|
|
||||||
sourcePluginId?: string;
|
|
||||||
sourceView?: string;
|
|
||||||
isInsideNotesFolder?: boolean;
|
|
||||||
notesScopePath?: string;
|
|
||||||
notesMode?: boolean;
|
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
|
||||||
return new OpenResourceContext(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
|
||||||
this.sourcePluginId = source["sourcePluginId"];
|
|
||||||
this.sourceView = source["sourceView"];
|
|
||||||
this.isInsideNotesFolder = source["isInsideNotesFolder"];
|
|
||||||
this.notesScopePath = source["notesScopePath"];
|
|
||||||
this.notesMode = source["notesMode"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class OpenResourceRequest {
|
|
||||||
kind: string;
|
|
||||||
path: string;
|
|
||||||
mode?: string;
|
|
||||||
mime?: string;
|
|
||||||
extension?: string;
|
|
||||||
context?: OpenResourceContext;
|
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
|
||||||
return new OpenResourceRequest(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
|
||||||
this.kind = source["kind"];
|
|
||||||
this.path = source["path"];
|
|
||||||
this.mode = source["mode"];
|
|
||||||
this.mime = source["mime"];
|
|
||||||
this.extension = source["extension"];
|
|
||||||
this.context = this.convertValues(source["context"], OpenResourceContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
|
||||||
if (!a) {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
if (a.slice && a.map) {
|
|
||||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
|
||||||
} else if ("object" === typeof a) {
|
|
||||||
if (asMap) {
|
|
||||||
for (const key of Object.keys(a)) {
|
|
||||||
a[key] = new classs(a[key]);
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
return new classs(a);
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class OpenResourceResult {
|
|
||||||
status: string;
|
|
||||||
providerId?: string;
|
|
||||||
providerPluginId?: string;
|
|
||||||
providerComponent?: string;
|
|
||||||
request: OpenResourceRequest;
|
|
||||||
message?: string;
|
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
|
||||||
return new OpenResourceResult(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
|
||||||
this.status = source["status"];
|
|
||||||
this.providerId = source["providerId"];
|
|
||||||
this.providerPluginId = source["providerPluginId"];
|
|
||||||
this.providerComponent = source["providerComponent"];
|
|
||||||
this.request = this.convertValues(source["request"], OpenResourceRequest);
|
|
||||||
this.message = source["message"];
|
|
||||||
}
|
|
||||||
|
|
||||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
|
||||||
if (!a) {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
if (a.slice && a.map) {
|
|
||||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
|
||||||
} else if ("object" === typeof a) {
|
|
||||||
if (asMap) {
|
|
||||||
for (const key of Object.keys(a)) {
|
|
||||||
a[key] = new classs(a[key]);
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
return new classs(a);
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class OpenedResource {
|
|
||||||
id: string;
|
|
||||||
providerId: string;
|
|
||||||
providerPluginId: string;
|
|
||||||
providerComponent: string;
|
|
||||||
request: OpenResourceRequest;
|
|
||||||
openedAt: string;
|
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
|
||||||
return new OpenedResource(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
|
||||||
this.id = source["id"];
|
|
||||||
this.providerId = source["providerId"];
|
|
||||||
this.providerPluginId = source["providerPluginId"];
|
|
||||||
this.providerComponent = source["providerComponent"];
|
|
||||||
this.request = this.convertValues(source["request"], OpenResourceRequest);
|
|
||||||
this.openedAt = source["openedAt"];
|
|
||||||
}
|
|
||||||
|
|
||||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
|
||||||
if (!a) {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
if (a.slice && a.map) {
|
|
||||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
|
||||||
} else if ("object" === typeof a) {
|
|
||||||
if (asMap) {
|
|
||||||
for (const key of Object.keys(a)) {
|
|
||||||
a[key] = new classs(a[key]);
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
return new classs(a);
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class Preferences {
|
|
||||||
defaultTextEditorProvider?: string;
|
|
||||||
defaultMarkdownEditorProvider?: string;
|
|
||||||
defaultNotesMarkdownEditorProvider?: string;
|
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
|
||||||
return new Preferences(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
|
||||||
this.defaultTextEditorProvider = source["defaultTextEditorProvider"];
|
|
||||||
this.defaultMarkdownEditorProvider = source["defaultMarkdownEditorProvider"];
|
|
||||||
this.defaultNotesMarkdownEditorProvider = source["defaultNotesMarkdownEditorProvider"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace capability {
|
export namespace capability {
|
||||||
|
|
||||||
export class Entry {
|
export class Entry {
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
status: string;
|
status: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new Entry(source);
|
return new Entry(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.name = source["name"];
|
this.name = source["name"];
|
||||||
|
|
@ -369,7 +233,7 @@ export namespace capability {
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace files {
|
export namespace files {
|
||||||
|
|
||||||
export class FileEntry {
|
export class FileEntry {
|
||||||
name: string;
|
name: string;
|
||||||
relativePath: string;
|
relativePath: string;
|
||||||
|
|
@ -381,11 +245,11 @@ export namespace files {
|
||||||
isReserved: boolean;
|
isReserved: boolean;
|
||||||
canRead: boolean;
|
canRead: boolean;
|
||||||
canWrite: boolean;
|
canWrite: boolean;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new FileEntry(source);
|
return new FileEntry(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.name = source["name"];
|
this.name = source["name"];
|
||||||
|
|
@ -413,11 +277,11 @@ export namespace files {
|
||||||
isReserved: boolean;
|
isReserved: boolean;
|
||||||
canRead: boolean;
|
canRead: boolean;
|
||||||
canWrite: boolean;
|
canWrite: boolean;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new FileMetadata(source);
|
return new FileMetadata(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.relativePath = source["relativePath"];
|
this.relativePath = source["relativePath"];
|
||||||
|
|
@ -436,11 +300,11 @@ export namespace files {
|
||||||
}
|
}
|
||||||
export class MoveOptions {
|
export class MoveOptions {
|
||||||
overwrite: boolean;
|
overwrite: boolean;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new MoveOptions(source);
|
return new MoveOptions(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.overwrite = source["overwrite"];
|
this.overwrite = source["overwrite"];
|
||||||
|
|
@ -451,11 +315,11 @@ export namespace files {
|
||||||
trashPath: string;
|
trashPath: string;
|
||||||
trashId: string;
|
trashId: string;
|
||||||
deletedAt: string;
|
deletedAt: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new TrashResult(source);
|
return new TrashResult(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.originalPath = source["originalPath"];
|
this.originalPath = source["originalPath"];
|
||||||
|
|
@ -467,11 +331,11 @@ export namespace files {
|
||||||
export class WriteOptions {
|
export class WriteOptions {
|
||||||
createIfMissing: boolean;
|
createIfMissing: boolean;
|
||||||
overwrite: boolean;
|
overwrite: boolean;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new WriteOptions(source);
|
return new WriteOptions(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.createIfMissing = source["createIfMissing"];
|
this.createIfMissing = source["createIfMissing"];
|
||||||
|
|
@ -482,16 +346,16 @@ export namespace files {
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace permissions {
|
export namespace permissions {
|
||||||
|
|
||||||
export class Entry {
|
export class Entry {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
dangerous: boolean;
|
dangerous: boolean;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new Entry(source);
|
return new Entry(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.name = source["name"];
|
this.name = source["name"];
|
||||||
|
|
@ -503,15 +367,15 @@ export namespace permissions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace plugin {
|
export namespace plugin {
|
||||||
|
|
||||||
export class HealthCheckConfig {
|
export class HealthCheckConfig {
|
||||||
type?: string;
|
type?: string;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new HealthCheckConfig(source);
|
return new HealthCheckConfig(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.type = source["type"];
|
this.type = source["type"];
|
||||||
|
|
@ -522,18 +386,18 @@ export namespace plugin {
|
||||||
type: string;
|
type: string;
|
||||||
entry: Record<string, string>;
|
entry: Record<string, string>;
|
||||||
healthCheck?: HealthCheckConfig;
|
healthCheck?: HealthCheckConfig;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new BackendConfig(source);
|
return new BackendConfig(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.type = source["type"];
|
this.type = source["type"];
|
||||||
this.entry = source["entry"];
|
this.entry = source["entry"];
|
||||||
this.healthCheck = this.convertValues(source["healthCheck"], HealthCheckConfig);
|
this.healthCheck = this.convertValues(source["healthCheck"], HealthCheckConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||||
if (!a) {
|
if (!a) {
|
||||||
return a;
|
return a;
|
||||||
|
|
@ -558,11 +422,11 @@ export namespace plugin {
|
||||||
icon?: string;
|
icon?: string;
|
||||||
capability?: string;
|
capability?: string;
|
||||||
handler?: string;
|
handler?: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new ContributionAction(source);
|
return new ContributionAction(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.id = source["id"];
|
this.id = source["id"];
|
||||||
|
|
@ -576,11 +440,11 @@ export namespace plugin {
|
||||||
id: string;
|
id: string;
|
||||||
events?: string[];
|
events?: string[];
|
||||||
handler: string;
|
handler: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new ContributionActivityProvider(source);
|
return new ContributionActivityProvider(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.id = source["id"];
|
this.id = source["id"];
|
||||||
|
|
@ -594,11 +458,11 @@ export namespace plugin {
|
||||||
keybinding?: string;
|
keybinding?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
handler?: string;
|
handler?: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new ContributionCommand(source);
|
return new ContributionCommand(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.id = source["id"];
|
this.id = source["id"];
|
||||||
|
|
@ -615,11 +479,11 @@ export namespace plugin {
|
||||||
group?: string;
|
group?: string;
|
||||||
capability?: string;
|
capability?: string;
|
||||||
handler?: string;
|
handler?: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new ContributionContextMenuEntry(source);
|
return new ContributionContextMenuEntry(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.id = source["id"];
|
this.id = source["id"];
|
||||||
|
|
@ -630,15 +494,71 @@ export namespace plugin {
|
||||||
this.handler = source["handler"];
|
this.handler = source["handler"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export class OpenProviderSupport {
|
||||||
|
kind: string;
|
||||||
|
mime?: string[];
|
||||||
|
extensions?: string[];
|
||||||
|
contexts?: string[];
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new OpenProviderSupport(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.kind = source["kind"];
|
||||||
|
this.mime = source["mime"];
|
||||||
|
this.extensions = source["extensions"];
|
||||||
|
this.contexts = source["contexts"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ContributionOpenProvider {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
priority?: number;
|
||||||
|
component: string;
|
||||||
|
supports: OpenProviderSupport[];
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new ContributionOpenProvider(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.id = source["id"];
|
||||||
|
this.title = source["title"];
|
||||||
|
this.priority = source["priority"];
|
||||||
|
this.component = source["component"];
|
||||||
|
this.supports = this.convertValues(source["supports"], OpenProviderSupport);
|
||||||
|
}
|
||||||
|
|
||||||
|
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||||
|
if (!a) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
if (a.slice && a.map) {
|
||||||
|
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||||
|
} else if ("object" === typeof a) {
|
||||||
|
if (asMap) {
|
||||||
|
for (const key of Object.keys(a)) {
|
||||||
|
a[key] = new classs(a[key]);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
return new classs(a);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}
|
||||||
export class ContributionSearchProvider {
|
export class ContributionSearchProvider {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
handler: string;
|
handler: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new ContributionSearchProvider(source);
|
return new ContributionSearchProvider(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.id = source["id"];
|
this.id = source["id"];
|
||||||
|
|
@ -651,11 +571,11 @@ export namespace plugin {
|
||||||
title: string;
|
title: string;
|
||||||
component: string;
|
component: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new ContributionSettingsPanel(source);
|
return new ContributionSettingsPanel(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.id = source["id"];
|
this.id = source["id"];
|
||||||
|
|
@ -670,11 +590,11 @@ export namespace plugin {
|
||||||
icon?: string;
|
icon?: string;
|
||||||
view: string;
|
view: string;
|
||||||
position?: number;
|
position?: number;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new ContributionSidebarItem(source);
|
return new ContributionSidebarItem(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.id = source["id"];
|
this.id = source["id"];
|
||||||
|
|
@ -689,11 +609,11 @@ export namespace plugin {
|
||||||
label: string;
|
label: string;
|
||||||
position?: string;
|
position?: string;
|
||||||
handler?: string;
|
handler?: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new ContributionStatusBarItem(source);
|
return new ContributionStatusBarItem(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.id = source["id"];
|
this.id = source["id"];
|
||||||
|
|
@ -707,11 +627,29 @@ export namespace plugin {
|
||||||
title: string;
|
title: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
component: string;
|
component: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new ContributionView(source);
|
return new ContributionView(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.id = source["id"];
|
||||||
|
this.title = source["title"];
|
||||||
|
this.icon = source["icon"];
|
||||||
|
this.component = source["component"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ContributionWorkspaceItem {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
icon?: string;
|
||||||
|
component: string;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new ContributionWorkspaceItem(source);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.id = source["id"];
|
this.id = source["id"];
|
||||||
|
|
@ -731,11 +669,13 @@ export namespace plugin {
|
||||||
searchProviders?: ContributionSearchProvider[];
|
searchProviders?: ContributionSearchProvider[];
|
||||||
activityProviders?: ContributionActivityProvider[];
|
activityProviders?: ContributionActivityProvider[];
|
||||||
statusBarItems?: ContributionStatusBarItem[];
|
statusBarItems?: ContributionStatusBarItem[];
|
||||||
|
openProviders?: ContributionOpenProvider[];
|
||||||
|
workspaceItems?: ContributionWorkspaceItem[];
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new Contributions(source);
|
return new Contributions(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.views = this.convertValues(source["views"], ContributionView);
|
this.views = this.convertValues(source["views"], ContributionView);
|
||||||
|
|
@ -748,8 +688,10 @@ export namespace plugin {
|
||||||
this.searchProviders = this.convertValues(source["searchProviders"], ContributionSearchProvider);
|
this.searchProviders = this.convertValues(source["searchProviders"], ContributionSearchProvider);
|
||||||
this.activityProviders = this.convertValues(source["activityProviders"], ContributionActivityProvider);
|
this.activityProviders = this.convertValues(source["activityProviders"], ContributionActivityProvider);
|
||||||
this.statusBarItems = this.convertValues(source["statusBarItems"], ContributionStatusBarItem);
|
this.statusBarItems = this.convertValues(source["statusBarItems"], ContributionStatusBarItem);
|
||||||
|
this.openProviders = this.convertValues(source["openProviders"], ContributionOpenProvider);
|
||||||
|
this.workspaceItems = this.convertValues(source["workspaceItems"], ContributionWorkspaceItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||||
if (!a) {
|
if (!a) {
|
||||||
return a;
|
return a;
|
||||||
|
|
@ -771,26 +713,26 @@ export namespace plugin {
|
||||||
export class FrontendConfig {
|
export class FrontendConfig {
|
||||||
entry: string;
|
entry: string;
|
||||||
style?: string;
|
style?: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new FrontendConfig(source);
|
return new FrontendConfig(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.entry = source["entry"];
|
this.entry = source["entry"];
|
||||||
this.style = source["style"];
|
this.style = source["style"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SyncConfig {
|
export class SyncConfig {
|
||||||
namespaces?: string[];
|
namespaces?: string[];
|
||||||
participate?: boolean;
|
participate?: boolean;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new SyncConfig(source);
|
return new SyncConfig(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.namespaces = source["namespaces"];
|
this.namespaces = source["namespaces"];
|
||||||
|
|
@ -799,11 +741,11 @@ export namespace plugin {
|
||||||
}
|
}
|
||||||
export class MigrationConfig {
|
export class MigrationConfig {
|
||||||
path?: string;
|
path?: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new MigrationConfig(source);
|
return new MigrationConfig(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.path = source["path"];
|
this.path = source["path"];
|
||||||
|
|
@ -827,11 +769,11 @@ export namespace plugin {
|
||||||
migrations?: MigrationConfig;
|
migrations?: MigrationConfig;
|
||||||
contributes?: Contributions;
|
contributes?: Contributions;
|
||||||
sync?: SyncConfig;
|
sync?: SyncConfig;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new Manifest(source);
|
return new Manifest(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.schemaVersion = source["schemaVersion"];
|
this.schemaVersion = source["schemaVersion"];
|
||||||
|
|
@ -852,7 +794,7 @@ export namespace plugin {
|
||||||
this.contributes = this.convertValues(source["contributes"], Contributions);
|
this.contributes = this.convertValues(source["contributes"], Contributions);
|
||||||
this.sync = this.convertValues(source["sync"], SyncConfig);
|
this.sync = this.convertValues(source["sync"], SyncConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||||
if (!a) {
|
if (!a) {
|
||||||
return a;
|
return a;
|
||||||
|
|
@ -871,18 +813,19 @@ export namespace plugin {
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class Plugin {
|
export class Plugin {
|
||||||
manifest: Manifest;
|
manifest: Manifest;
|
||||||
status: string;
|
status: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
rootPath: string;
|
rootPath: string;
|
||||||
|
|
||||||
static createFrom(source: any = {}) {
|
static createFrom(source: any = {}) {
|
||||||
return new Plugin(source);
|
return new Plugin(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: any = {}) {
|
constructor(source: any = {}) {
|
||||||
if ('string' === typeof source) source = JSON.parse(source);
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
this.manifest = this.convertValues(source["manifest"], Manifest);
|
this.manifest = this.convertValues(source["manifest"], Manifest);
|
||||||
|
|
@ -891,7 +834,7 @@ export namespace plugin {
|
||||||
this.enabled = source["enabled"];
|
this.enabled = source["enabled"];
|
||||||
this.rootPath = source["rootPath"];
|
this.rootPath = source["rootPath"];
|
||||||
}
|
}
|
||||||
|
|
||||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||||
if (!a) {
|
if (!a) {
|
||||||
return a;
|
return a;
|
||||||
|
|
@ -912,3 +855,165 @@ export namespace plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace workbench {
|
||||||
|
|
||||||
|
export class OpenResourceContext {
|
||||||
|
sourcePluginId?: string;
|
||||||
|
sourceView?: string;
|
||||||
|
isInsideNotesFolder?: boolean;
|
||||||
|
notesScopePath?: string;
|
||||||
|
notesMode?: boolean;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new OpenResourceContext(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.sourcePluginId = source["sourcePluginId"];
|
||||||
|
this.sourceView = source["sourceView"];
|
||||||
|
this.isInsideNotesFolder = source["isInsideNotesFolder"];
|
||||||
|
this.notesScopePath = source["notesScopePath"];
|
||||||
|
this.notesMode = source["notesMode"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class OpenResourceRequest {
|
||||||
|
kind: string;
|
||||||
|
path: string;
|
||||||
|
mode?: string;
|
||||||
|
mime?: string;
|
||||||
|
extension?: string;
|
||||||
|
context?: OpenResourceContext;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new OpenResourceRequest(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.kind = source["kind"];
|
||||||
|
this.path = source["path"];
|
||||||
|
this.mode = source["mode"];
|
||||||
|
this.mime = source["mime"];
|
||||||
|
this.extension = source["extension"];
|
||||||
|
this.context = this.convertValues(source["context"], OpenResourceContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||||
|
if (!a) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
if (a.slice && a.map) {
|
||||||
|
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||||
|
} else if ("object" === typeof a) {
|
||||||
|
if (asMap) {
|
||||||
|
for (const key of Object.keys(a)) {
|
||||||
|
a[key] = new classs(a[key]);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
return new classs(a);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class OpenResourceResult {
|
||||||
|
status: string;
|
||||||
|
providerId?: string;
|
||||||
|
providerPluginId?: string;
|
||||||
|
providerComponent?: string;
|
||||||
|
request: OpenResourceRequest;
|
||||||
|
message?: string;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new OpenResourceResult(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.status = source["status"];
|
||||||
|
this.providerId = source["providerId"];
|
||||||
|
this.providerPluginId = source["providerPluginId"];
|
||||||
|
this.providerComponent = source["providerComponent"];
|
||||||
|
this.request = this.convertValues(source["request"], OpenResourceRequest);
|
||||||
|
this.message = source["message"];
|
||||||
|
}
|
||||||
|
|
||||||
|
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||||
|
if (!a) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
if (a.slice && a.map) {
|
||||||
|
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||||
|
} else if ("object" === typeof a) {
|
||||||
|
if (asMap) {
|
||||||
|
for (const key of Object.keys(a)) {
|
||||||
|
a[key] = new classs(a[key]);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
return new classs(a);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class OpenedResource {
|
||||||
|
id: string;
|
||||||
|
providerId: string;
|
||||||
|
providerPluginId: string;
|
||||||
|
providerComponent: string;
|
||||||
|
request: OpenResourceRequest;
|
||||||
|
openedAt: string;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new OpenedResource(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.id = source["id"];
|
||||||
|
this.providerId = source["providerId"];
|
||||||
|
this.providerPluginId = source["providerPluginId"];
|
||||||
|
this.providerComponent = source["providerComponent"];
|
||||||
|
this.request = this.convertValues(source["request"], OpenResourceRequest);
|
||||||
|
this.openedAt = source["openedAt"];
|
||||||
|
}
|
||||||
|
|
||||||
|
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||||
|
if (!a) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
if (a.slice && a.map) {
|
||||||
|
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||||
|
} else if ("object" === typeof a) {
|
||||||
|
if (asMap) {
|
||||||
|
for (const key of Object.keys(a)) {
|
||||||
|
a[key] = new classs(a[key]);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
return new classs(a);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class Preferences {
|
||||||
|
defaultTextEditorProvider?: string;
|
||||||
|
defaultMarkdownEditorProvider?: string;
|
||||||
|
defaultNotesMarkdownEditorProvider?: string;
|
||||||
|
|
||||||
|
static createFrom(source: any = {}) {
|
||||||
|
return new Preferences(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: any = {}) {
|
||||||
|
if ('string' === typeof source) source = JSON.parse(source);
|
||||||
|
this.defaultTextEditorProvider = source["defaultTextEditorProvider"];
|
||||||
|
this.defaultMarkdownEditorProvider = source["defaultMarkdownEditorProvider"];
|
||||||
|
this.defaultNotesMarkdownEditorProvider = source["defaultNotesMarkdownEditorProvider"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -240,6 +240,14 @@ type FlatOpenProvider struct {
|
||||||
Supports []FlatOpenProviderSupport `json:"supports"`
|
Supports []FlatOpenProviderSupport `json:"supports"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FlatWorkspaceItem struct {
|
||||||
|
PluginID string `json:"pluginId"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Icon string `json:"icon,omitempty"`
|
||||||
|
Component string `json:"component"`
|
||||||
|
}
|
||||||
|
|
||||||
// ContributionSummary aggregates all contribution types for the frontend.
|
// ContributionSummary aggregates all contribution types for the frontend.
|
||||||
type ContributionSummary struct {
|
type ContributionSummary struct {
|
||||||
Views []FlatView `json:"views"`
|
Views []FlatView `json:"views"`
|
||||||
|
|
@ -247,6 +255,7 @@ type ContributionSummary struct {
|
||||||
SettingsPanels []FlatSettingsPanel `json:"settingsPanels"`
|
SettingsPanels []FlatSettingsPanel `json:"settingsPanels"`
|
||||||
SidebarItems []FlatSidebarItem `json:"sidebarItems"`
|
SidebarItems []FlatSidebarItem `json:"sidebarItems"`
|
||||||
OpenProviders []FlatOpenProvider `json:"openProviders"`
|
OpenProviders []FlatOpenProvider `json:"openProviders"`
|
||||||
|
WorkspaceItems []FlatWorkspaceItem `json:"workspaceItems"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildContributionSummary creates a ContributionSummary from the registry.
|
// buildContributionSummary creates a ContributionSummary from the registry.
|
||||||
|
|
@ -259,6 +268,7 @@ func buildContributionSummary(r *contribution.Registry) ContributionSummary {
|
||||||
regPanels := r.SettingsPanels()
|
regPanels := r.SettingsPanels()
|
||||||
regSidebar := r.SidebarItems()
|
regSidebar := r.SidebarItems()
|
||||||
regOpenProviders := r.OpenProviders()
|
regOpenProviders := r.OpenProviders()
|
||||||
|
regWorkspaceItems := r.WorkspaceItems()
|
||||||
|
|
||||||
views := make([]FlatView, len(regViews))
|
views := make([]FlatView, len(regViews))
|
||||||
for i, v := range regViews {
|
for i, v := range regViews {
|
||||||
|
|
@ -291,7 +301,11 @@ func buildContributionSummary(r *contribution.Registry) ContributionSummary {
|
||||||
Supports: supports,
|
Supports: supports,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ContributionSummary{Views: views, Commands: cmds, SettingsPanels: panels, SidebarItems: sidebar, OpenProviders: openProviders}
|
workspaceItems := make([]FlatWorkspaceItem, len(regWorkspaceItems))
|
||||||
|
for i, v := range regWorkspaceItems {
|
||||||
|
workspaceItems[i] = FlatWorkspaceItem{PluginID: v.PluginID, ID: v.Item.ID, Title: v.Item.Title, Icon: v.Item.Icon, Component: v.Item.Component}
|
||||||
|
}
|
||||||
|
return ContributionSummary{Views: views, Commands: cmds, SettingsPanels: panels, SidebarItems: sidebar, OpenProviders: openProviders, WorkspaceItems: workspaceItems}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContributions returns all registered contributions flattened for the frontend.
|
// GetContributions returns all registered contributions flattened for the frontend.
|
||||||
|
|
|
||||||
|
|
@ -668,6 +668,25 @@ func TestWorkbenchOpenResourceRequiresPermission(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWorkbenchDisabledPluginProviderExcluded(t *testing.T) {
|
||||||
|
app := newBridgeTestApp(t)
|
||||||
|
|
||||||
|
result, errStr := app.OpenWorkbenchResource("bridge.plugin", map[string]interface{}{
|
||||||
|
"kind": "vault-file",
|
||||||
|
"path": "Docs/readme.md",
|
||||||
|
"extension": ".md",
|
||||||
|
})
|
||||||
|
if errStr != "" {
|
||||||
|
t.Fatalf("OpenWorkbenchResource: %s", errStr)
|
||||||
|
}
|
||||||
|
if result.ProviderPluginID != "bridge.plugin" {
|
||||||
|
t.Fatalf("expected bridge.plugin provider, got providerPluginId=%q", result.ProviderPluginID)
|
||||||
|
}
|
||||||
|
if result.ProviderID == "disabled.markdown" {
|
||||||
|
t.Fatal("disabled plugin provider should be excluded from selection")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPluginBridgeSettingsRequireLoadedPluginAndStoragePermission(t *testing.T) {
|
func TestPluginBridgeSettingsRequireLoadedPluginAndStoragePermission(t *testing.T) {
|
||||||
app := newBridgeTestApp(t)
|
app := newBridgeTestApp(t)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ type Registry struct {
|
||||||
activityProviders []ContributionActivityProvider
|
activityProviders []ContributionActivityProvider
|
||||||
statusBarItems []ContributionStatusBarItem
|
statusBarItems []ContributionStatusBarItem
|
||||||
openProviders []ContributionOpenProvider
|
openProviders []ContributionOpenProvider
|
||||||
|
workspaceItems []ContributionWorkspaceItem
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContributionPointType defines the type of contribution point.
|
// ContributionPointType defines the type of contribution point.
|
||||||
|
|
@ -40,6 +41,7 @@ const (
|
||||||
PointActivity ContributionPointType = "activityProviders"
|
PointActivity ContributionPointType = "activityProviders"
|
||||||
PointStatusBar ContributionPointType = "statusBarItems"
|
PointStatusBar ContributionPointType = "statusBarItems"
|
||||||
PointOpenProviders ContributionPointType = "openProviders"
|
PointOpenProviders ContributionPointType = "openProviders"
|
||||||
|
PointWorkspaceItems ContributionPointType = "workspaceItems"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListByPoint returns all contributions for a given point type.
|
// ListByPoint returns all contributions for a given point type.
|
||||||
|
|
@ -93,6 +95,10 @@ func (r *Registry) ListByPoint(point ContributionPointType) []interface{} {
|
||||||
for _, v := range r.openProviders {
|
for _, v := range r.openProviders {
|
||||||
result = append(result, v)
|
result = append(result, v)
|
||||||
}
|
}
|
||||||
|
case PointWorkspaceItems:
|
||||||
|
for _, v := range r.workspaceItems {
|
||||||
|
result = append(result, v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
@ -147,6 +153,12 @@ type ContributionOpenProvider struct {
|
||||||
Item plugin.ContributionOpenProvider `json:"item"`
|
Item plugin.ContributionOpenProvider `json:"item"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContributionWorkspaceItem is a workspace tool contribution.
|
||||||
|
type ContributionWorkspaceItem struct {
|
||||||
|
PluginID string `json:"pluginId"`
|
||||||
|
Item plugin.ContributionWorkspaceItem `json:"item"`
|
||||||
|
}
|
||||||
|
|
||||||
// NewRegistry creates a new contribution registry.
|
// NewRegistry creates a new contribution registry.
|
||||||
func NewRegistry() *Registry {
|
func NewRegistry() *Registry {
|
||||||
return &Registry{}
|
return &Registry{}
|
||||||
|
|
@ -171,6 +183,7 @@ func (r *Registry) Register(pluginID string, c *plugin.Contributions) {
|
||||||
r.activityProviders = removeActivityProviders(r.activityProviders, pluginID)
|
r.activityProviders = removeActivityProviders(r.activityProviders, pluginID)
|
||||||
r.statusBarItems = removeStatusBarItems(r.statusBarItems, pluginID)
|
r.statusBarItems = removeStatusBarItems(r.statusBarItems, pluginID)
|
||||||
r.openProviders = removeOpenProviders(r.openProviders, pluginID)
|
r.openProviders = removeOpenProviders(r.openProviders, pluginID)
|
||||||
|
r.workspaceItems = removeWorkspaceItems(r.workspaceItems, pluginID)
|
||||||
|
|
||||||
for _, item := range c.Views {
|
for _, item := range c.Views {
|
||||||
r.views = append(r.views, ContributionView{PluginID: pluginID, Item: item})
|
r.views = append(r.views, ContributionView{PluginID: pluginID, Item: item})
|
||||||
|
|
@ -205,6 +218,9 @@ func (r *Registry) Register(pluginID string, c *plugin.Contributions) {
|
||||||
for _, item := range c.OpenProviders {
|
for _, item := range c.OpenProviders {
|
||||||
r.openProviders = append(r.openProviders, ContributionOpenProvider{PluginID: pluginID, Item: item})
|
r.openProviders = append(r.openProviders, ContributionOpenProvider{PluginID: pluginID, Item: item})
|
||||||
}
|
}
|
||||||
|
for _, item := range c.WorkspaceItems {
|
||||||
|
r.workspaceItems = append(r.workspaceItems, ContributionWorkspaceItem{PluginID: pluginID, Item: item})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unregister removes all contributions from a plugin.
|
// Unregister removes all contributions from a plugin.
|
||||||
|
|
@ -223,6 +239,7 @@ func (r *Registry) Unregister(pluginID string) {
|
||||||
r.activityProviders = removeActivityProviders(r.activityProviders, pluginID)
|
r.activityProviders = removeActivityProviders(r.activityProviders, pluginID)
|
||||||
r.statusBarItems = removeStatusBarItems(r.statusBarItems, pluginID)
|
r.statusBarItems = removeStatusBarItems(r.statusBarItems, pluginID)
|
||||||
r.openProviders = removeOpenProviders(r.openProviders, pluginID)
|
r.openProviders = removeOpenProviders(r.openProviders, pluginID)
|
||||||
|
r.workspaceItems = removeWorkspaceItems(r.workspaceItems, pluginID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters — sorted for deterministic display.
|
// Getters — sorted for deterministic display.
|
||||||
|
|
@ -304,6 +321,20 @@ func (r *Registry) OpenProviders() []ContributionOpenProvider {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Registry) WorkspaceItems() []ContributionWorkspaceItem {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
result := make([]ContributionWorkspaceItem, len(r.workspaceItems))
|
||||||
|
copy(result, r.workspaceItems)
|
||||||
|
sort.Slice(result, func(i, j int) bool {
|
||||||
|
if result[i].PluginID != result[j].PluginID {
|
||||||
|
return result[i].PluginID < result[j].PluginID
|
||||||
|
}
|
||||||
|
return result[i].Item.ID < result[j].Item.ID
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Remove helpers ─────────────────────────────────────────
|
// ─── Remove helpers ─────────────────────────────────────────
|
||||||
|
|
||||||
func removeViews(items []ContributionView, pluginID string) []ContributionView {
|
func removeViews(items []ContributionView, pluginID string) []ContributionView {
|
||||||
|
|
@ -405,3 +436,13 @@ func removeOpenProviders(items []ContributionOpenProvider, pluginID string) []Co
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func removeWorkspaceItems(items []ContributionWorkspaceItem, pluginID string) []ContributionWorkspaceItem {
|
||||||
|
var result []ContributionWorkspaceItem
|
||||||
|
for _, item := range items {
|
||||||
|
if item.PluginID != pluginID {
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ type Contributions struct {
|
||||||
ActivityProviders []ContributionActivityProvider `json:"activityProviders,omitempty"`
|
ActivityProviders []ContributionActivityProvider `json:"activityProviders,omitempty"`
|
||||||
StatusBarItems []ContributionStatusBarItem `json:"statusBarItems,omitempty"`
|
StatusBarItems []ContributionStatusBarItem `json:"statusBarItems,omitempty"`
|
||||||
OpenProviders []ContributionOpenProvider `json:"openProviders,omitempty"`
|
OpenProviders []ContributionOpenProvider `json:"openProviders,omitempty"`
|
||||||
|
WorkspaceItems []ContributionWorkspaceItem `json:"workspaceItems,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContributionView represents a view contribution.
|
// ContributionView represents a view contribution.
|
||||||
|
|
@ -162,6 +163,14 @@ type ContributionOpenProvider struct {
|
||||||
Supports []OpenProviderSupport `json:"supports"`
|
Supports []OpenProviderSupport `json:"supports"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContributionWorkspaceItem represents a workspace tool contribution.
|
||||||
|
type ContributionWorkspaceItem struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Icon string `json:"icon,omitempty"`
|
||||||
|
Component string `json:"component"`
|
||||||
|
}
|
||||||
|
|
||||||
// SyncConfig describes plugin sync configuration.
|
// SyncConfig describes plugin sync configuration.
|
||||||
type SyncConfig struct {
|
type SyncConfig struct {
|
||||||
Namespaces []string `json:"namespaces,omitempty"`
|
Namespaces []string `json:"namespaces,omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,187 @@ func TestOpenResourceReturnsNoProviderFallback(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSelectProviderUsesTextPreference(t *testing.T) {
|
||||||
|
r := NewRouter(Preferences{
|
||||||
|
DefaultTextEditorProvider: "custom.text-editor",
|
||||||
|
})
|
||||||
|
providers := []contribution.ContributionOpenProvider{
|
||||||
|
provider("official.editor", "builtin.text", 100, "BuiltinText", plugin.OpenProviderSupport{
|
||||||
|
Kind: "vault-file",
|
||||||
|
Extensions: []string{".txt"},
|
||||||
|
Contexts: []string{ContextGenericText},
|
||||||
|
}),
|
||||||
|
provider("custom.editor", "custom.text-editor", 10, "CustomText", plugin.OpenProviderSupport{
|
||||||
|
Kind: "vault-file",
|
||||||
|
Extensions: []string{".txt"},
|
||||||
|
Contexts: []string{ContextGenericText},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err := r.SelectProvider(OpenResourceRequest{
|
||||||
|
Kind: "vault-file",
|
||||||
|
Path: "Docs/todo.txt",
|
||||||
|
Mode: "view",
|
||||||
|
}, providers)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SelectProvider: %v", err)
|
||||||
|
}
|
||||||
|
if selected.Item.ID != "custom.text-editor" {
|
||||||
|
t.Fatalf("provider = %q, want custom.text-editor", selected.Item.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectProviderUsesMarkdownPreference(t *testing.T) {
|
||||||
|
r := NewRouter(Preferences{
|
||||||
|
DefaultMarkdownEditorProvider: "community.markdown-editor",
|
||||||
|
})
|
||||||
|
providers := []contribution.ContributionOpenProvider{
|
||||||
|
provider("official.editor", "builtin.markdown", 100, "BuiltinMarkdown", plugin.OpenProviderSupport{
|
||||||
|
Kind: "vault-file",
|
||||||
|
Extensions: []string{".md"},
|
||||||
|
Contexts: []string{ContextGenericMarkdown},
|
||||||
|
}),
|
||||||
|
provider("community.editor", "community.markdown-editor", 10, "CommunityMarkdown", plugin.OpenProviderSupport{
|
||||||
|
Kind: "vault-file",
|
||||||
|
Extensions: []string{".md"},
|
||||||
|
Contexts: []string{ContextGenericMarkdown},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err := r.SelectProvider(OpenResourceRequest{
|
||||||
|
Kind: "vault-file",
|
||||||
|
Path: "Docs/readme.md",
|
||||||
|
Mode: "view",
|
||||||
|
}, providers)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SelectProvider: %v", err)
|
||||||
|
}
|
||||||
|
if selected.Item.ID != "community.markdown-editor" {
|
||||||
|
t.Fatalf("provider = %q, want community.markdown-editor", selected.Item.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectProviderMatchesMime(t *testing.T) {
|
||||||
|
r := NewRouter(Preferences{})
|
||||||
|
providers := []contribution.ContributionOpenProvider{
|
||||||
|
provider("image.plugin", "image.viewer", 10, "ImageViewer", plugin.OpenProviderSupport{
|
||||||
|
Kind: "vault-file",
|
||||||
|
Mime: []string{"image/png", "image/jpeg"},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err := r.SelectProvider(OpenResourceRequest{
|
||||||
|
Kind: "vault-file",
|
||||||
|
Path: "Photos/screenshot.png",
|
||||||
|
Extension: ".png",
|
||||||
|
Mime: "image/png",
|
||||||
|
Mode: "view",
|
||||||
|
}, providers)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SelectProvider: %v", err)
|
||||||
|
}
|
||||||
|
if selected.Item.ID != "image.viewer" {
|
||||||
|
t.Fatalf("provider = %q, want image.viewer", selected.Item.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectProviderExtensionCaseInsensitive(t *testing.T) {
|
||||||
|
r := NewRouter(Preferences{})
|
||||||
|
providers := []contribution.ContributionOpenProvider{
|
||||||
|
provider("editor.plugin", "md.editor", 10, "MDEditor", plugin.OpenProviderSupport{
|
||||||
|
Kind: "vault-file",
|
||||||
|
Extensions: []string{".md"},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ext string
|
||||||
|
path string
|
||||||
|
}{
|
||||||
|
{"uppercase .MD", ".MD", "Docs/README.MD"},
|
||||||
|
{"mixed case .Md", ".Md", "Docs/Notes.Md"},
|
||||||
|
{"lowercase .md", ".md", "Docs/readme.md"},
|
||||||
|
{"markdown extension uppercase", ".MD", "Docs/guide.MD"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
selected, err := r.SelectProvider(OpenResourceRequest{
|
||||||
|
Kind: "vault-file",
|
||||||
|
Path: tt.path,
|
||||||
|
Extension: tt.ext,
|
||||||
|
Mode: "view",
|
||||||
|
}, providers)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SelectProvider: %v", err)
|
||||||
|
}
|
||||||
|
if selected.Item.ID != "md.editor" {
|
||||||
|
t.Fatalf("provider = %q, want md.editor for ext %s", selected.Item.ID, tt.ext)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectProviderMultipleSupportsEntries(t *testing.T) {
|
||||||
|
r := NewRouter(Preferences{})
|
||||||
|
providers := []contribution.ContributionOpenProvider{
|
||||||
|
provider("editor.plugin", "multi.editor", 10, "MultiEditor", plugin.OpenProviderSupport{
|
||||||
|
Kind: "vault-file",
|
||||||
|
Extensions: []string{".md"},
|
||||||
|
Contexts: []string{ContextGenericMarkdown},
|
||||||
|
}, plugin.OpenProviderSupport{
|
||||||
|
Kind: "vault-file",
|
||||||
|
Extensions: []string{".txt"},
|
||||||
|
Contexts: []string{ContextGenericText},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("matches markdown entry", func(t *testing.T) {
|
||||||
|
selected, err := r.SelectProvider(OpenResourceRequest{
|
||||||
|
Kind: "vault-file",
|
||||||
|
Path: "Docs/readme.md",
|
||||||
|
Mode: "view",
|
||||||
|
}, providers)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SelectProvider: %v", err)
|
||||||
|
}
|
||||||
|
if selected.Item.ID != "multi.editor" {
|
||||||
|
t.Fatalf("provider = %q, want multi.editor", selected.Item.ID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("matches text entry", func(t *testing.T) {
|
||||||
|
selected, err := r.SelectProvider(OpenResourceRequest{
|
||||||
|
Kind: "vault-file",
|
||||||
|
Path: "Docs/todo.txt",
|
||||||
|
Mode: "view",
|
||||||
|
}, providers)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SelectProvider: %v", err)
|
||||||
|
}
|
||||||
|
if selected.Item.ID != "multi.editor" {
|
||||||
|
t.Fatalf("provider = %q, want multi.editor", selected.Item.ID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectProviderKindMismatch(t *testing.T) {
|
||||||
|
r := NewRouter(Preferences{})
|
||||||
|
_, err := r.SelectProvider(OpenResourceRequest{
|
||||||
|
Kind: "http-url",
|
||||||
|
Path: "https://example.com/file.md",
|
||||||
|
Mode: "view",
|
||||||
|
}, []contribution.ContributionOpenProvider{
|
||||||
|
provider("editor.plugin", "vault.editor", 10, "VaultEditor", plugin.OpenProviderSupport{
|
||||||
|
Kind: "vault-file",
|
||||||
|
Extensions: []string{".md"},
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected no provider for http-url kind with vault-file provider")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDetermineContextName(t *testing.T) {
|
func TestDetermineContextName(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue