dsh-coding-sidebar 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -42
- package/lib/client-editor.js +297 -4179
- package/lib/client-registry.js +639 -1341
- package/lib/client-terminal.js +176 -266
- package/lib/client.js +640 -1342
- package/lib/index.js +4 -110
- package/lib/types/bundle-route.d.ts +1 -1
- package/lib/types/client/SideCardSection.d.ts +3 -3
- package/lib/types/client/TextEditor.d.ts +20 -10
- package/lib/types/client/add-plugin-modal.d.ts +0 -4
- package/lib/types/client/api.d.ts +8 -11
- package/lib/types/client/builtins/index.d.ts +10 -10
- package/lib/types/client/chunk-loader.d.ts +7 -8
- package/lib/types/client/locales.d.ts +0 -26
- package/lib/types/client/native-avoid.d.ts +2 -0
- package/lib/types/client/plugins-shared.d.ts +6 -6
- package/lib/types/client/service.d.ts +13 -83
- package/lib/types/index.d.ts +1 -1
- package/lib/types/prefs-shared.d.ts +1 -24
- package/package.json +26 -29
- package/src/bundle-route.ts +1 -1
- package/src/client/EditorHost.tsx +52 -122
- package/src/client/SandboxStatusBar.tsx +12 -9
- package/src/client/SideCardSection.tsx +28 -81
- package/src/client/TextEditor.tsx +89 -266
- package/src/client/add-plugin-modal.tsx +16 -24
- package/src/client/api.ts +1 -25
- package/src/client/builtins/index.ts +10 -14
- package/src/client/chunk-loader.ts +7 -8
- package/src/client/index.tsx +12 -3
- package/src/client/locales-ar.ts +0 -6
- package/src/client/locales-de.ts +0 -6
- package/src/client/locales-fr.ts +0 -6
- package/src/client/locales-hi.ts +0 -6
- package/src/client/locales-id.ts +0 -6
- package/src/client/locales-it.ts +0 -6
- package/src/client/locales-ja.ts +0 -6
- package/src/client/locales-ko.ts +0 -6
- package/src/client/locales-nl.ts +0 -6
- package/src/client/locales-pl.ts +0 -6
- package/src/client/locales-pt.ts +0 -6
- package/src/client/locales-ru.ts +0 -6
- package/src/client/locales-sv.ts +0 -6
- package/src/client/locales-th.ts +0 -6
- package/src/client/locales-tr.ts +0 -6
- package/src/client/locales-vi.ts +0 -6
- package/src/client/locales-zh-HK.ts +0 -6
- package/src/client/locales-zh-MO.ts +0 -6
- package/src/client/locales-zh-TW.ts +0 -6
- package/src/client/locales.ts +0 -52
- package/src/client/native-avoid.ts +155 -0
- package/src/client/plugins-shared.ts +6 -6
- package/src/client/prefs.ts +0 -7
- package/src/client/service.ts +13 -150
- package/src/client/sidebar.module.css +1 -425
- package/src/config.ts +1 -4
- package/src/index.ts +7 -74
- package/src/prefs-shared.ts +1 -27
- package/lib/client-mermaid.js +0 -200776
- package/lib/types/client/MarkdownHtml.d.ts +0 -32
- package/lib/types/client/PdfView.d.ts +0 -6
- package/lib/types/client/builtins/viewers.d.ts +0 -3
- package/lib/types/client/chunks/mermaid.d.ts +0 -10
- package/lib/types/client/editor-load.d.ts +0 -66
- package/lib/types/client/image-types.d.ts +0 -3
- package/lib/types/client/markdown-html.d.ts +0 -96
- package/lib/types/client/markdown-images.d.ts +0 -45
- package/lib/types/client/md-toc.d.ts +0 -4
- package/lib/types/client/mermaid-blocks.d.ts +0 -47
- package/lib/types/client/mermaid-sanitize.d.ts +0 -2
- package/lib/types/client/mermaid.d.ts +0 -12
- package/lib/types/client/pdf-types.d.ts +0 -2
- package/lib/types/client/plugins-viewers.d.ts +0 -3
- package/lib/types/html-route.d.ts +0 -59
- package/src/client/MarkdownHtml.tsx +0 -296
- package/src/client/PdfView.tsx +0 -110
- package/src/client/builtins/viewers.tsx +0 -125
- package/src/client/chunks/mermaid.tsx +0 -10
- package/src/client/editor-load.ts +0 -92
- package/src/client/image-types.ts +0 -8
- package/src/client/markdown-html.ts +0 -331
- package/src/client/markdown-images.ts +0 -137
- package/src/client/md-toc.tsx +0 -127
- package/src/client/mermaid-blocks.ts +0 -110
- package/src/client/mermaid-sanitize.ts +0 -93
- package/src/client/mermaid.tsx +0 -406
- package/src/client/pdf-types.ts +0 -4
- package/src/client/plugins-viewers.ts +0 -30
- package/src/html-route.ts +0 -106
package/lib/index.js
CHANGED
|
@@ -42,15 +42,12 @@ const SIDEBAR_PREFS_DEFAULTS = {
|
|
|
42
42
|
customCss: "",
|
|
43
43
|
titleBarCompat: false,
|
|
44
44
|
titleBarStripPx: 40,
|
|
45
|
-
htmlViewerNoSandbox: false,
|
|
46
|
-
htmlViewerDefaultUnsafe: false,
|
|
47
45
|
browserNoSandbox: false,
|
|
48
46
|
browserInterceptLinks: true,
|
|
49
47
|
browserInterceptHttp: true,
|
|
50
48
|
browserInterceptHttps: false,
|
|
51
49
|
browserAllowedLoopback: "",
|
|
52
50
|
tabsEnabled: {},
|
|
53
|
-
viewersEnabled: {},
|
|
54
51
|
pluginSettings: {}
|
|
55
52
|
};
|
|
56
53
|
//#endregion
|
|
@@ -114,15 +111,12 @@ const PrefsSchema = z.object({
|
|
|
114
111
|
customCss: z.string(),
|
|
115
112
|
titleBarCompat: z.boolean().default(false),
|
|
116
113
|
titleBarStripPx: z.number().step(1).min(0).max(120).default(40),
|
|
117
|
-
htmlViewerNoSandbox: z.boolean().default(false),
|
|
118
|
-
htmlViewerDefaultUnsafe: z.boolean().default(false),
|
|
119
114
|
browserNoSandbox: z.boolean().default(false),
|
|
120
115
|
browserInterceptLinks: z.boolean().default(true),
|
|
121
116
|
browserInterceptHttp: z.boolean().default(true),
|
|
122
117
|
browserInterceptHttps: z.boolean().default(false),
|
|
123
118
|
browserAllowedLoopback: z.string().default(""),
|
|
124
119
|
tabsEnabled: z.dict(z.boolean()).default({}),
|
|
125
|
-
viewersEnabled: z.dict(z.boolean()).default({}),
|
|
126
120
|
pluginSettings: z.dict(z.dict(z.any())).default({})
|
|
127
121
|
});
|
|
128
122
|
//#endregion
|
|
@@ -541,60 +535,6 @@ async function searchFiles(root, query, opts = {}) {
|
|
|
541
535
|
truncated
|
|
542
536
|
};
|
|
543
537
|
}
|
|
544
|
-
/**
|
|
545
|
-
* Decode a route pathname into the session + absolute file path. Rejects
|
|
546
|
-
* a wrong prefix (404), an empty path, malformed percent encoding, and a
|
|
547
|
-
* missing sessionId or file path (400). The caller still must bound the
|
|
548
|
-
* decoded path with the workspace real-path guard — a decoded `..`
|
|
549
|
-
* segment resolves outside the cwd and is refused there.
|
|
550
|
-
*/
|
|
551
|
-
function decodeHtmlUrl(pathname) {
|
|
552
|
-
if (!pathname.startsWith("/sidebar/html/")) return {
|
|
553
|
-
ok: false,
|
|
554
|
-
status: 404,
|
|
555
|
-
message: "not an html route"
|
|
556
|
-
};
|
|
557
|
-
const rest = pathname.slice(14);
|
|
558
|
-
if (rest === "") return {
|
|
559
|
-
ok: false,
|
|
560
|
-
status: 400,
|
|
561
|
-
message: "invalid html route path"
|
|
562
|
-
};
|
|
563
|
-
let segments;
|
|
564
|
-
try {
|
|
565
|
-
segments = rest.split("/").map((segment) => decodeURIComponent(segment));
|
|
566
|
-
} catch {
|
|
567
|
-
return {
|
|
568
|
-
ok: false,
|
|
569
|
-
status: 400,
|
|
570
|
-
message: "malformed URL encoding"
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
const [sessionId, ...pathSegments] = segments;
|
|
574
|
-
if (sessionId === void 0 || sessionId === "") return {
|
|
575
|
-
ok: false,
|
|
576
|
-
status: 400,
|
|
577
|
-
message: "sessionId and file path are required"
|
|
578
|
-
};
|
|
579
|
-
const unc = pathSegments[0] === "";
|
|
580
|
-
const tail = unc ? pathSegments.slice(1) : pathSegments;
|
|
581
|
-
if (tail.length === 0 || tail.some((segment) => segment === "")) return {
|
|
582
|
-
ok: false,
|
|
583
|
-
status: 400,
|
|
584
|
-
message: "sessionId and file path are required"
|
|
585
|
-
};
|
|
586
|
-
let path;
|
|
587
|
-
if (unc) path = `//${tail.join("/")}`;
|
|
588
|
-
else if (/^[A-Za-z]:$/.test(tail[0] ?? "")) path = tail.join("/");
|
|
589
|
-
else path = `/${tail.join("/")}`;
|
|
590
|
-
return {
|
|
591
|
-
ok: true,
|
|
592
|
-
ref: {
|
|
593
|
-
sessionId,
|
|
594
|
-
path
|
|
595
|
-
}
|
|
596
|
-
};
|
|
597
|
-
}
|
|
598
538
|
//#endregion
|
|
599
539
|
//#region src/browser-probe.ts
|
|
600
540
|
/**
|
|
@@ -692,11 +632,7 @@ function isTrustedApiRequest(request, trustedHosts) {
|
|
|
692
632
|
* only allowlisted chunk names are servable (no path traversal).
|
|
693
633
|
*/
|
|
694
634
|
/** The chunk names the client may request (mirror of src/client/chunk-loader.ts). */
|
|
695
|
-
const CHUNK_NAMES = [
|
|
696
|
-
"terminal",
|
|
697
|
-
"editor",
|
|
698
|
-
"mermaid"
|
|
699
|
-
];
|
|
635
|
+
const CHUNK_NAMES = ["terminal", "editor"];
|
|
700
636
|
/** Directory of this host-half module (lib/ — the chunk scripts live next to it). */
|
|
701
637
|
const LIB_DIR = dirname(fileURLToPath(import.meta.url));
|
|
702
638
|
/** sha1 content hash shortened to 12 hex chars (same shape as the client-modules rev). */
|
|
@@ -3689,8 +3625,8 @@ function buildSidechatApi(ctx) {
|
|
|
3689
3625
|
//#region src/index.ts
|
|
3690
3626
|
/**
|
|
3691
3627
|
* dsh-coding-sidebar host half: the /sidebar JSON API (explorer listing, file
|
|
3692
|
-
* read/write, git), the /sidebar/file media route (
|
|
3693
|
-
*
|
|
3628
|
+
* read/write, git), the /sidebar/file media route (file bytes / downloads),
|
|
3629
|
+
* the /sidebar/bundle lazy-chunk route (client code splits),
|
|
3694
3630
|
* and the terminal WebSocket upgrade. Every route passes the same
|
|
3695
3631
|
* browser-trust fence as the /api gateway — Host-header loopback or the
|
|
3696
3632
|
* web runtime's `trustedHosts` (LAN IP literals sampled at boot plus
|
|
@@ -3721,10 +3657,7 @@ const MEDIA_TYPES = {
|
|
|
3721
3657
|
".svg": "image/svg+xml",
|
|
3722
3658
|
".bmp": "image/bmp",
|
|
3723
3659
|
".ico": "image/x-icon",
|
|
3724
|
-
".avif": "image/avif"
|
|
3725
|
-
".pdf": "application/pdf",
|
|
3726
|
-
".html": "text/html",
|
|
3727
|
-
".htm": "text/html"
|
|
3660
|
+
".avif": "image/avif"
|
|
3728
3661
|
};
|
|
3729
3662
|
/** Content type served by /sidebar/file (binary-safe fallback for unknowns). */
|
|
3730
3663
|
function mediaTypeForPath(path) {
|
|
@@ -4302,45 +4235,6 @@ function apply(ctx, config) {
|
|
|
4302
4235
|
}
|
|
4303
4236
|
}
|
|
4304
4237
|
}), "dsh-coding-sidebar: /sidebar/file media route");
|
|
4305
|
-
ctx.effect(() => ctx.webServer.register({
|
|
4306
|
-
kind: "prefix",
|
|
4307
|
-
path: "/sidebar/html",
|
|
4308
|
-
handler: async (req, res) => {
|
|
4309
|
-
if (!fence(req)) {
|
|
4310
|
-
res.writeHead(403);
|
|
4311
|
-
res.end("forbidden");
|
|
4312
|
-
return;
|
|
4313
|
-
}
|
|
4314
|
-
if (req.method !== "GET") {
|
|
4315
|
-
res.writeHead(405);
|
|
4316
|
-
res.end();
|
|
4317
|
-
return;
|
|
4318
|
-
}
|
|
4319
|
-
try {
|
|
4320
|
-
const decoded = decodeHtmlUrl(new URL(req.url ?? "/", "http://dsh.internal").pathname);
|
|
4321
|
-
if (!decoded.ok) {
|
|
4322
|
-
writeError(res, new SidebarError("bad-request", decoded.message, decoded.status));
|
|
4323
|
-
return;
|
|
4324
|
-
}
|
|
4325
|
-
const { sessionId, path } = decoded.ref;
|
|
4326
|
-
const absolute = await ensureWorkspacePath(await sessionCwdOf(ctx, sessionId), path);
|
|
4327
|
-
const info = await stat(absolute);
|
|
4328
|
-
if (!info.isFile() || info.size > resolved.mediaLimit) throw new SidebarError("fs-error", "not a file or too large", 400);
|
|
4329
|
-
const type = mediaTypeForPath(absolute);
|
|
4330
|
-
const body = await readFile(absolute);
|
|
4331
|
-
res.writeHead(200, {
|
|
4332
|
-
"content-type": type === "text/html" ? "text/html; charset=utf-8" : type,
|
|
4333
|
-
"cache-control": "no-cache",
|
|
4334
|
-
"x-content-type-options": "nosniff",
|
|
4335
|
-
"referrer-policy": "no-referrer",
|
|
4336
|
-
"content-security-policy": "sandbox allow-scripts allow-popups allow-downloads allow-modals; object-src 'none'"
|
|
4337
|
-
});
|
|
4338
|
-
res.end(body);
|
|
4339
|
-
} catch (error) {
|
|
4340
|
-
writeError(res, error);
|
|
4341
|
-
}
|
|
4342
|
-
}
|
|
4343
|
-
}), "dsh-coding-sidebar: /sidebar/html preview route");
|
|
4344
4238
|
const wss = new WebSocketServer({ noServer: true });
|
|
4345
4239
|
ctx.effect(() => ctx.webServer.registerUpgrade({
|
|
4346
4240
|
path: "/sidebar/ws/terminal",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Context, SidebarHttpRequest, SidebarHttpResponse } from './context-types.ts';
|
|
2
2
|
/** The chunk names the client may request (mirror of src/client/chunk-loader.ts). */
|
|
3
|
-
export declare const CHUNK_NAMES: readonly ["terminal", "editor"
|
|
3
|
+
export declare const CHUNK_NAMES: readonly ["terminal", "editor"];
|
|
4
4
|
export type ChunkName = (typeof CHUNK_NAMES)[number];
|
|
5
5
|
/**
|
|
6
6
|
* Build the /sidebar/bundle route handler. `fence` is the shared browser-
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
2
|
import { type SidebarPrefs } from '../prefs-shared.ts';
|
|
3
3
|
import type { SidebarStore } from './state.ts';
|
|
4
|
-
import type { BetterSidebarService,
|
|
4
|
+
import type { BetterSidebarService, SidebarSettingToggle, TabDescriptor } from './service.ts';
|
|
5
5
|
/** Injected business face: the shared store (prefs cache) + the sidebar service (registries). */
|
|
6
6
|
export interface SideCardSectionInjected {
|
|
7
7
|
store: SidebarStore;
|
|
@@ -43,7 +43,7 @@ export declare function FeatureSettingsRows(props: {
|
|
|
43
43
|
valueSource?: (key: string) => unknown;
|
|
44
44
|
}): import("react").JSX.Element;
|
|
45
45
|
/**
|
|
46
|
-
* The secondary settings popup body of one
|
|
46
|
+
* The secondary settings popup body of one registered tab:
|
|
47
47
|
* - the host-prefs `toggles` rows, then the plugin-owned `pluginToggles`
|
|
48
48
|
* rows (their values live in `pluginSettings[feature.id]`, projected onto
|
|
49
49
|
* the prefs face so the shared row renderer reads them);
|
|
@@ -53,7 +53,7 @@ export declare function FeatureSettingsRows(props: {
|
|
|
53
53
|
* open-behavior picker) and still ship a custom configuration area.
|
|
54
54
|
*/
|
|
55
55
|
export declare function SettingsBody(props: {
|
|
56
|
-
feature: TabDescriptor
|
|
56
|
+
feature: TabDescriptor;
|
|
57
57
|
prefs: SidebarPrefs;
|
|
58
58
|
store: SidebarStore;
|
|
59
59
|
service: BetterSidebarService;
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import type { EditorToolbarControls, EditorToolbarState } from './service.ts';
|
|
2
|
+
import type { Context } from '../context-types.ts';
|
|
3
|
+
import type { SessionScope } from './api.ts';
|
|
4
|
+
import type { SidebarStore } from './state.ts';
|
|
5
|
+
/** Props of the sidebar text editor (the editor tab's content). */
|
|
6
|
+
export interface TextEditorProps {
|
|
7
|
+
ctx: Context;
|
|
8
|
+
store?: SidebarStore;
|
|
9
|
+
scope: SessionScope;
|
|
10
|
+
path: string;
|
|
11
|
+
/** fs.read text content (undefined while loading / for non-editable reads). */
|
|
12
|
+
content?: string;
|
|
13
|
+
truncated?: boolean;
|
|
14
|
+
/** 'host' skips the own toolbar row — the editor host's merged-mode header
|
|
15
|
+
* renders it instead, fed through the two callbacks below. */
|
|
16
|
+
toolbar?: 'self' | 'host';
|
|
17
|
+
onToolbarState?: (state: EditorToolbarState) => void;
|
|
18
|
+
onToolbarControls?: (controls: EditorToolbarControls | null) => void;
|
|
19
|
+
}
|
|
20
|
+
export declare function TextEditor(props: TextEditorProps): import("react").JSX.Element;
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import type { BetterSidebarService } from './service.ts';
|
|
2
|
-
/** Which extension point the modal is adding a plugin for. */
|
|
3
|
-
export type PluginKind = 'tab' | 'viewer';
|
|
4
2
|
/** The modal body: the GitHub topic button + the recommended plugin list
|
|
5
3
|
* with per-entry jump/copy buttons (extracted for direct testing). */
|
|
6
4
|
export declare function PluginListBody(props: {
|
|
7
5
|
service: BetterSidebarService;
|
|
8
|
-
kind: PluginKind;
|
|
9
6
|
}): import("react").JSX.Element;
|
|
10
7
|
/** The modal itself (mounted only while open — see the module comment). */
|
|
11
8
|
export declare function AddPluginModal(props: {
|
|
12
9
|
service: BetterSidebarService;
|
|
13
10
|
onClose: () => void;
|
|
14
|
-
kind: PluginKind;
|
|
15
11
|
}): import("react").JSX.Element;
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed fetch wrapper over the /sidebar JSON API. Every call posts to
|
|
3
|
+
* `/sidebar/api/<method>` with the sessionId and — when known — the session's
|
|
4
|
+
* cwd from the client's own list summary. The host prefers its attached
|
|
5
|
+
* session header and uses the summary cwd only while the session is still
|
|
6
|
+
* hydrating at page load (a detached session would otherwise fail the
|
|
7
|
+
* request). Failures surface as {@link SidebarApiError} with the wire code.
|
|
8
|
+
*/
|
|
1
9
|
import type { LastActivity } from '../subagent-activity.ts';
|
|
2
10
|
import type { SidechatThreadInfo } from '../sidechat-core.ts';
|
|
3
11
|
import type { BrowserProbeResult } from './browser.ts';
|
|
@@ -259,17 +267,6 @@ export declare const api: {
|
|
|
259
267
|
started: boolean;
|
|
260
268
|
}>;
|
|
261
269
|
};
|
|
262
|
-
/** Absolute URL of the media route for one path (images only). */
|
|
263
|
-
export declare function mediaUrl(scope: SessionScope, path: string): string;
|
|
264
270
|
/** Absolute URL of the download route: serves raw bytes (binary-safe) with
|
|
265
271
|
* `Content-Disposition: attachment`, so the browser saves the file. */
|
|
266
272
|
export declare function downloadUrl(scope: SessionScope, path: string): string;
|
|
267
|
-
/**
|
|
268
|
-
* Absolute URL of the HTML preview route (see html-route.ts): the path is
|
|
269
|
-
* fully encoded so the previewed page's relative assets resolve back into
|
|
270
|
-
* the same route with the session scope intact. The UNC marker is
|
|
271
|
-
* platform-neutral — the host's requireAbsolute resolves the decoded
|
|
272
|
-
* forward-slash `//server/share/...` form on both win32 and POSIX — so no
|
|
273
|
-
* client-side platform signal is needed.
|
|
274
|
-
*/
|
|
275
|
-
export declare function htmlUrl(scope: SessionScope, path: string): string;
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Built-in registration: the plugin registers its own tab pages
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
2
|
+
* Built-in registration: the plugin registers its own tab pages through the
|
|
3
|
+
* same {@link BetterSidebarService} external plugins use — eating its own
|
|
4
|
+
* dogfood. The descriptors live next to their feature modules (tabs.tsx);
|
|
5
|
+
* this module only aggregates them and owns the disposer lifecycle (cordis
|
|
6
|
+
* auto-invokes it on fiber disposal, HMR-safe).
|
|
7
|
+
*
|
|
8
|
+
* (v1.0.4: the built-in file viewers were retired with the file-viewer
|
|
9
|
+
* registry — file preview is the host's job now.)
|
|
8
10
|
*/
|
|
9
11
|
import type { Context } from '../../context-types.ts';
|
|
10
12
|
import type { BetterSidebarService } from '../service.ts';
|
|
11
13
|
import { type BuiltinTabOptions } from './tabs.tsx';
|
|
12
14
|
/**
|
|
13
|
-
* Register all built-in tabs
|
|
14
|
-
*
|
|
15
|
-
* disposal). The `ctx` is threaded into tab descriptors that need it
|
|
16
|
-
* (EditorHost reads `ctx.betterSidebar` for file-viewer matching).
|
|
15
|
+
* Register all built-in tabs with the service. Returns a disposer that
|
|
16
|
+
* unregisters everything (cordis auto-invokes it on fiber disposal).
|
|
17
17
|
*/
|
|
18
18
|
export declare function registerBuiltins(ctx: Context, service: BetterSidebarService, options?: BuiltinTabOptions): () => void;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Lazy chunk loader for the client bundle. The heavy
|
|
3
|
-
* libraries (CodeMirror, xterm —
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* recommended office plugin, see plugins-viewers.ts.)
|
|
2
|
+
* Lazy chunk loader for the client bundle. The heavy editor/terminal
|
|
3
|
+
* libraries (CodeMirror, xterm — several MB) live in separate build-time
|
|
4
|
+
* bundles (`lib/client-<name>.js`) fetched only on first use of the feature
|
|
5
|
+
* that needs them, so startup downloads/parses only the ~1MB core bundle.
|
|
6
|
+
* (v1.0.4: the mermaid chunk was retired with the file-viewer line; the
|
|
7
|
+
* office stack moved to the ecosystem even earlier.)
|
|
9
8
|
*
|
|
10
9
|
* How a chunk script works (see tsdown.config.ts chunkBundle):
|
|
11
10
|
*
|
|
@@ -49,7 +48,7 @@
|
|
|
49
48
|
* client.js); an edit that does land while a core HMR happens is caught by
|
|
50
49
|
* the ETag comparison on the next activation.
|
|
51
50
|
*/
|
|
52
|
-
export type ChunkName = 'terminal' | 'editor'
|
|
51
|
+
export type ChunkName = 'terminal' | 'editor';
|
|
53
52
|
/** The module exports a chunk factory provides (namespace-ish record). */
|
|
54
53
|
export type ChunkExports = Record<string, unknown>;
|
|
55
54
|
/**
|
|
@@ -94,14 +94,6 @@ export declare const zh: {
|
|
|
94
94
|
terminalDepsFailed: string;
|
|
95
95
|
terminalDepsHint: string;
|
|
96
96
|
terminalDepsProfile: string;
|
|
97
|
-
preview: string;
|
|
98
|
-
toc: string;
|
|
99
|
-
edit: string;
|
|
100
|
-
mermaidError: string;
|
|
101
|
-
mermaidZoomIn: string;
|
|
102
|
-
mermaidZoomOut: string;
|
|
103
|
-
mermaidZoomReset: string;
|
|
104
|
-
mermaidZoomHint: string;
|
|
105
97
|
refresh: string;
|
|
106
98
|
refreshUnsavedConfirm: string;
|
|
107
99
|
save: string;
|
|
@@ -244,16 +236,8 @@ export declare const zh: {
|
|
|
244
236
|
settingsShellArgsDesc: string;
|
|
245
237
|
settingsShellArgsPlaceholder: string;
|
|
246
238
|
settingsTabsTitle: string;
|
|
247
|
-
settingsViewersTitle: string;
|
|
248
239
|
settingsGeneralTitle: string;
|
|
249
240
|
settingsPopup: string;
|
|
250
|
-
settingsViewerCatchAll: string;
|
|
251
|
-
viewerImage: string;
|
|
252
|
-
viewerPdf: string;
|
|
253
|
-
viewerMarkdown: string;
|
|
254
|
-
viewerCode: string;
|
|
255
|
-
viewerBinary: string;
|
|
256
|
-
viewerHtml: string;
|
|
257
241
|
browser: string;
|
|
258
242
|
browserPlaceholder: string;
|
|
259
243
|
browserGo: string;
|
|
@@ -264,14 +248,9 @@ export declare const zh: {
|
|
|
264
248
|
browserBlockedLoopback: string;
|
|
265
249
|
browserInvalid: string;
|
|
266
250
|
browserNoSandboxWarning: string;
|
|
267
|
-
htmlNoSandboxWarning: string;
|
|
268
251
|
sandboxStatusOn: string;
|
|
269
252
|
sandboxUnlock: string;
|
|
270
253
|
sandboxRestore: string;
|
|
271
|
-
settingsHtmlDefaultUnsafeTitle: string;
|
|
272
|
-
settingsHtmlDefaultUnsafeDesc: string;
|
|
273
|
-
settingsHtmlSandboxTitle: string;
|
|
274
|
-
settingsHtmlSandboxDesc: string;
|
|
275
254
|
settingsBrowserSandboxTitle: string;
|
|
276
255
|
settingsBrowserSandboxDesc: string;
|
|
277
256
|
settingsBrowserLinksTitle: string;
|
|
@@ -352,10 +331,7 @@ export declare const zh: {
|
|
|
352
331
|
jobKillError: string;
|
|
353
332
|
addPluginsTabCard: string;
|
|
354
333
|
addPluginsTabCardDesc: string;
|
|
355
|
-
addPluginsViewerCard: string;
|
|
356
|
-
addPluginsViewerCardDesc: string;
|
|
357
334
|
addPluginsTabDesc: string;
|
|
358
|
-
addPluginsViewerDesc: string;
|
|
359
335
|
addPluginsBrowseMore: string;
|
|
360
336
|
addPluginsSearch: string;
|
|
361
337
|
addPluginsNoMatch: string;
|
|
@@ -363,7 +339,6 @@ export declare const zh: {
|
|
|
363
339
|
addPluginsEmpty: string;
|
|
364
340
|
openPlugin: string;
|
|
365
341
|
copyInstall: string;
|
|
366
|
-
pluginOfficeDesc: string;
|
|
367
342
|
pluginFlowglassDesc: string;
|
|
368
343
|
pluginGitForgeDesc: string;
|
|
369
344
|
pluginGitRemotesDesc: string;
|
|
@@ -371,7 +346,6 @@ export declare const zh: {
|
|
|
371
346
|
pluginSidebarQaDesc: string;
|
|
372
347
|
pluginSshTunnelDesc: string;
|
|
373
348
|
pluginTurnReviewDesc: string;
|
|
374
|
-
pluginVideoPreviewDesc: string;
|
|
375
349
|
pluginDocsPanelDesc: string;
|
|
376
350
|
pluginEgoBrowserDesc: string;
|
|
377
351
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared vocabulary of the recommended plugin
|
|
3
|
-
* the GitHub topic URL. The
|
|
4
|
-
* `plugins-tabs.ts` (tab registrations) and
|
|
5
|
-
*
|
|
6
|
-
* (
|
|
7
|
-
*
|
|
2
|
+
* Shared vocabulary of the recommended plugin catalog: the entry shape and
|
|
3
|
+
* the GitHub topic URL. The catalog lives in the sibling module
|
|
4
|
+
* `plugins-tabs.ts` (tab registrations) and is shown in the "add plugin"
|
|
5
|
+
* modal (Side card settings → the dashed card at the end of the 侧边栏内容
|
|
6
|
+
* grid). (v1.0.4: the file-previewer catalog was retired with the
|
|
7
|
+
* file-viewer registry.)
|
|
8
8
|
*/
|
|
9
9
|
/** The GitHub topic page listing every repo tagged `dsh-coding-sidebar`. */
|
|
10
10
|
export declare const PLUGIN_TOPIC_URL = "https://github.com/topics/dsh-coding-sidebar";
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The BetterSidebar client service: a registry that external plugins use
|
|
3
|
-
* to contribute sidebar tab types
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* to contribute sidebar tab types. The service is published to the cordis
|
|
4
|
+
* context as `ctx.betterSidebar` (see {@link ../context-types.ts});
|
|
5
|
+
* consumers declare it in `inject` and call `registerTab`, which returns a
|
|
6
|
+
* disposer that cordis auto-invokes on fiber disposal (HMR-safe).
|
|
7
|
+
*
|
|
8
|
+
* (v1.0.4: the file-viewer registry — `registerFileViewer` /
|
|
9
|
+
* `matchFileViewer` — was retired; file preview is the host's job now and
|
|
10
|
+
* the sidebar edits text only.)
|
|
8
11
|
*
|
|
9
12
|
* Design notes:
|
|
10
13
|
* - The registry is synchronous-snapshot (Map + listener set) so React
|
|
@@ -15,9 +18,6 @@
|
|
|
15
18
|
* `single: true` is sugar for `dedupeKey: () => id`.
|
|
16
19
|
* - `createTab` lets a descriptor own tab instantiation (the terminal
|
|
17
20
|
* builtin uses it to mint `terminal:<n>` ids and bump `nextTerminal`).
|
|
18
|
-
* - `matchFileViewer` walks descriptors in priority order (desc, stable):
|
|
19
|
-
* per descriptor it tries `detect` first (when `head` bytes are given),
|
|
20
|
-
* then `exts`; `exts: []` is a catch-all that matches any path.
|
|
21
21
|
*/
|
|
22
22
|
import type { ReactNode } from 'react';
|
|
23
23
|
import type { Context } from '../context-types.ts';
|
|
@@ -50,7 +50,7 @@ export interface SidebarSettingSelectOption {
|
|
|
50
50
|
* option's icon too; without icons both are a single line of text. */
|
|
51
51
|
icon?: ReactNode | ((size: number) => ReactNode);
|
|
52
52
|
}
|
|
53
|
-
/** One declarative setting of a tab
|
|
53
|
+
/** One declarative setting of a registered tab, rendered as a nested row in the
|
|
54
54
|
* Side card settings page (e.g. the Subagent page's "auto-open when a
|
|
55
55
|
* subagent appears" switch, or the terminal's custom font rows). `type`
|
|
56
56
|
* selects the control: 'switch' (default) renders the custom switch,
|
|
@@ -93,7 +93,7 @@ export interface SidebarSettingsRenderProps {
|
|
|
93
93
|
/** Close the settings popup. */
|
|
94
94
|
close(): void;
|
|
95
95
|
}
|
|
96
|
-
/** Declarative settings of one registered tab
|
|
96
|
+
/** Declarative settings of one registered tab. */
|
|
97
97
|
export interface SidebarSettingsDeclaration {
|
|
98
98
|
/**
|
|
99
99
|
* Extra settings rows rendered under the feature's own row in the
|
|
@@ -223,78 +223,17 @@ export interface TabDescriptor {
|
|
|
223
223
|
onClose?: (tab: SidebarTab, scope: SessionScope) => void;
|
|
224
224
|
component: (props: TabComponentProps) => ReactNode;
|
|
225
225
|
}
|
|
226
|
-
/**
|
|
227
|
-
export type FileFetchStrategy = 'none' | 'fsRead' | 'mediaUrl' | 'custom' | 'binary-download';
|
|
228
|
-
/** Props every file viewer component receives. */
|
|
229
|
-
export interface FileViewerProps {
|
|
230
|
-
ctx: Context;
|
|
231
|
-
store: SidebarStore;
|
|
232
|
-
scope: SessionScope;
|
|
233
|
-
path: string;
|
|
234
|
-
title: string;
|
|
235
|
-
/** The matching descriptor's id (`'code'`, `'my-plugin:csv'`). */
|
|
236
|
-
viewerId: string;
|
|
237
|
-
/** fsRead text content (fetchStrategy='fsRead'). */
|
|
238
|
-
content?: string;
|
|
239
|
-
truncated?: boolean;
|
|
240
|
-
/** mediaUrl for the path (fetchStrategy='mediaUrl'). */
|
|
241
|
-
mediaUrl?: string;
|
|
242
|
-
/** custom load() return value (fetchStrategy='custom'). */
|
|
243
|
-
customData?: unknown;
|
|
244
|
-
/** Internal (built-in text editor): 'host' asks the viewer to skip its own
|
|
245
|
-
* toolbar row — the editor host's merged-mode header renders it instead,
|
|
246
|
-
* fed through the two callbacks below. Viewers that ignore these fields
|
|
247
|
-
* render exactly as before. */
|
|
248
|
-
toolbar?: 'self' | 'host';
|
|
249
|
-
/** Internal: the viewer reports its toolbar state (mode/dirty/save). */
|
|
250
|
-
onToolbarState?: (state: EditorToolbarState) => void;
|
|
251
|
-
/** Internal: the viewer registers its toolbar commands on mount (null on
|
|
252
|
-
* unmount). */
|
|
253
|
-
onToolbarControls?: (controls: EditorToolbarControls | null) => void;
|
|
254
|
-
}
|
|
255
|
-
/** The toolbar state a text editor reports to the host's merged-mode header. */
|
|
226
|
+
/** The toolbar state the text editor reports to the host's merged-mode header. */
|
|
256
227
|
export interface EditorToolbarState {
|
|
257
|
-
/** Whether the preview/edit mode toggle applies (markdown/html). */
|
|
258
|
-
modes: boolean;
|
|
259
|
-
mode: 'preview' | 'edit';
|
|
260
228
|
dirty: boolean;
|
|
261
229
|
/** Whether saving applies (text content loaded). */
|
|
262
230
|
editable: boolean;
|
|
263
231
|
saveState: 'idle' | 'saving' | 'saved' | 'failed';
|
|
264
232
|
}
|
|
265
|
-
/** The commands the host's merged-mode header sends back to the
|
|
233
|
+
/** The commands the host's merged-mode header sends back to the editor. */
|
|
266
234
|
export interface EditorToolbarControls {
|
|
267
|
-
setMode(mode: 'preview' | 'edit'): void;
|
|
268
235
|
save(): void;
|
|
269
236
|
}
|
|
270
|
-
/** Describes one file previewer (builtins register themselves too). */
|
|
271
|
-
export interface FileViewerDescriptor {
|
|
272
|
-
/** Unique id (`'image'`, `'pdf'`, `'my-plugin:csv'`). */
|
|
273
|
-
id: string;
|
|
274
|
-
/** Display name for the settings inventory (falls back to `id` when absent). */
|
|
275
|
-
title?: string | (() => string);
|
|
276
|
-
/** Icon shown in the settings inventory. */
|
|
277
|
-
icon?: ReactNode | ((size: number) => ReactNode);
|
|
278
|
-
/** Lowercase extensions without leading dot (`['png','jpg']`). `[]` = match any (catch-all). */
|
|
279
|
-
exts: readonly string[];
|
|
280
|
-
/** Higher wins; default 0. Builtins use 0; the catch-all `code` viewer uses -100. */
|
|
281
|
-
priority?: number;
|
|
282
|
-
fetchStrategy: FileFetchStrategy;
|
|
283
|
-
/**
|
|
284
|
-
* Content sniff: when `head` bytes are available the descriptor's `detect`
|
|
285
|
-
* is consulted before its `exts` (per-descriptor, in priority order).
|
|
286
|
-
*/
|
|
287
|
-
detect?: (path: string, head: Uint8Array) => boolean;
|
|
288
|
-
/** fetchStrategy='custom' loader. `signal` (v0.12.0+) aborts on viewer
|
|
289
|
-
* teardown / re-match; loaders that ignore it keep working. */
|
|
290
|
-
load?: (path: string, scope: SessionScope, signal?: AbortSignal) => Promise<unknown>;
|
|
291
|
-
/**
|
|
292
|
-
* Declarative settings shown in the Side card settings page: every
|
|
293
|
-
* registered viewer gets an enable/disable switch (icon + title + exts).
|
|
294
|
-
*/
|
|
295
|
-
settings?: SidebarSettingsDeclaration;
|
|
296
|
-
component: (props: FileViewerProps) => ReactNode;
|
|
297
|
-
}
|
|
298
237
|
/** One `openTab` request. */
|
|
299
238
|
export interface OpenTabSeed {
|
|
300
239
|
type: string;
|
|
@@ -316,9 +255,7 @@ export interface OpenTabSeed {
|
|
|
316
255
|
*/
|
|
317
256
|
export interface BetterSidebarService {
|
|
318
257
|
registerTab(descriptor: TabDescriptor): () => void;
|
|
319
|
-
registerFileViewer(descriptor: FileViewerDescriptor): () => void;
|
|
320
258
|
getTabs(): readonly TabDescriptor[];
|
|
321
|
-
getFileViewers(): readonly FileViewerDescriptor[];
|
|
322
259
|
/** Find a tab descriptor by id (undefined if not registered). */
|
|
323
260
|
getTab(id: string): TabDescriptor | undefined;
|
|
324
261
|
/**
|
|
@@ -328,13 +265,6 @@ export interface BetterSidebarService {
|
|
|
328
265
|
* derived flows gate on it).
|
|
329
266
|
*/
|
|
330
267
|
isTabEnabled(id: string): boolean;
|
|
331
|
-
/** Whether a file viewer is enabled (absent `viewersEnabled[id]` = enabled). */
|
|
332
|
-
isViewerEnabled(id: string): boolean;
|
|
333
|
-
/**
|
|
334
|
-
* Find a file viewer for a path (priority desc; detect first, then exts).
|
|
335
|
-
* Disabled viewers are skipped, so files fall through to the next match.
|
|
336
|
-
*/
|
|
337
|
-
matchFileViewer(path: string, head?: Uint8Array): FileViewerDescriptor | undefined;
|
|
338
268
|
/**
|
|
339
269
|
* Open a tab (used by external tabs and the + menu). `title` overrides
|
|
340
270
|
* the descriptor's title when given (the editor tab shows the file name);
|
|
@@ -433,7 +363,7 @@ export declare const SIDEBAR_SERVICE_VERSION: string;
|
|
|
433
363
|
export declare const SIDEBAR_FEATURES: readonly ["badge", "tabLifecycle", "updateTab", "openFile", "targetedOpen", "stateSubscription", "tabMeta", "pluginSettings", "urlTarget", "settingSelect", "floatWindows"];
|
|
434
364
|
/**
|
|
435
365
|
* Create one BetterSidebar service bound to a store. The service owns the
|
|
436
|
-
* tab
|
|
366
|
+
* tab registry (Map + listener set) and proxies openTab/closeTab
|
|
437
367
|
* to the store's reducer. One instance per client plugin activation.
|
|
438
368
|
*/
|
|
439
369
|
export declare function createBetterSidebarService(store: SidebarStore): BetterSidebarService;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Config, type ResolvedSidebarConfig, type SidebarConfig } from './config
|
|
|
3
3
|
export { Config };
|
|
4
4
|
export type { SidebarConfig, ResolvedSidebarConfig };
|
|
5
5
|
export type { Context } from './context-types.ts';
|
|
6
|
-
export type { BetterSidebarService, TabDescriptor, TabComponentProps,
|
|
6
|
+
export type { BetterSidebarService, TabDescriptor, TabComponentProps, } from './client/service.ts';
|
|
7
7
|
/** Plugin identity for cordis.yml rows. */
|
|
8
8
|
export declare const name = "dsh-coding-sidebar";
|
|
9
9
|
/** Services required before mounting: the webserver routes, the session store, the web runtime's trusted hosts, and the tool registry. */
|
|
@@ -124,22 +124,6 @@ export interface SidebarPrefs {
|
|
|
124
124
|
* starts `strip` px below its top edge.
|
|
125
125
|
*/
|
|
126
126
|
titleBarStripPx: number;
|
|
127
|
-
/**
|
|
128
|
-
* Whether the HTML previewer drops its sandboxed iframe. Sandbox ON (the
|
|
129
|
-
* default) renders previewed HTML in an opaque-origin iframe that cannot
|
|
130
|
-
* touch the GUI; turning it OFF runs the previewed page with the GUI's
|
|
131
|
-
* own origin — full read/write access to session files and internal
|
|
132
|
-
* APIs. Only for trusted local content; the setting copy warns.
|
|
133
|
-
*/
|
|
134
|
-
htmlViewerNoSandbox: boolean;
|
|
135
|
-
/**
|
|
136
|
-
* Whether a newly opened HTML preview starts UNSANDBOXED (the per-surface
|
|
137
|
-
* temporary unlock pre-applied). Off by default: previews open sandboxed
|
|
138
|
-
* and the status row offers the one-tap unlock; when on, previews open
|
|
139
|
-
* in the red unsandboxed state and the status row offers a one-tap
|
|
140
|
-
* restore for the current file.
|
|
141
|
-
*/
|
|
142
|
-
htmlViewerDefaultUnsafe: boolean;
|
|
143
127
|
/**
|
|
144
128
|
* Whether the browser tab drops its sandboxed iframe. Sandbox ON (the
|
|
145
129
|
* default) keeps browsed sites in an opaque origin with no GUI access;
|
|
@@ -194,16 +178,9 @@ export interface SidebarPrefs {
|
|
|
194
178
|
* own layouts" rule.
|
|
195
179
|
*/
|
|
196
180
|
tabsEnabled: Record<string, boolean>;
|
|
197
|
-
/**
|
|
198
|
-
* Per-viewer enable switches, keyed by file viewer descriptor id
|
|
199
|
-
* (`'image'`, `'my-plugin:csv'`). An ABSENT key means enabled; a disabled
|
|
200
|
-
* viewer is skipped by `matchFileViewer` so files fall through to the
|
|
201
|
-
* next matching viewer (or the download button when none match).
|
|
202
|
-
*/
|
|
203
|
-
viewersEnabled: Record<string, boolean>;
|
|
204
181
|
/**
|
|
205
182
|
* Plugin-owned settings blobs (v0.12.0+), keyed by descriptor id: each
|
|
206
|
-
* registered tab
|
|
183
|
+
* registered tab that declares `settings.pluginToggles` (or writes
|
|
207
184
|
* through `settings.render`'s `updatePluginSetting`) persists its values
|
|
208
185
|
* here — an open map, so third-party keys need no host PrefsSchema field.
|
|
209
186
|
* Values are JSON-serializable (the row controls produce strings /
|