sh3-core 0.23.2 → 0.24.0
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/dist/Sh3.svelte +4 -4
- package/dist/actions/listActive.js +1 -0
- package/dist/actions/listActive.test.js +13 -0
- package/dist/actions/types.d.ts +12 -0
- package/dist/api.d.ts +2 -0
- package/dist/api.js +2 -0
- package/dist/app/store/StoreView.svelte +1 -1
- package/dist/chrome/MenuSheet.svelte +19 -6
- package/dist/contributions/contextSource.d.ts +48 -0
- package/dist/contributions/contextSource.js +21 -0
- package/dist/documents/picker-primitive.d.ts +0 -9
- package/dist/documents/picker-primitive.js +0 -9
- package/dist/primitives/widgets/DocumentFilePicker.svelte +9 -7
- package/dist/primitives/widgets/DocumentFilePicker.svelte.d.ts +44 -27
- package/dist/primitives/widgets/_DocumentBrowser.svelte +4 -4
- package/dist/sh3core-shard/Sh3Home.svelte +0 -1
- package/dist/transport/apiFetch.js +21 -3
- package/dist/transport/apiFetch.test.js +63 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/Sh3.svelte
CHANGED
|
@@ -79,19 +79,19 @@
|
|
|
79
79
|
|
|
80
80
|
const edgePointers = new Set<number>();
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
const onPointerDown = (e: PointerEvent): void => {
|
|
83
83
|
const rect = el.getBoundingClientRect();
|
|
84
84
|
const local = e.clientX - rect.left;
|
|
85
85
|
if (local >= EDGE_PX && local <= rect.width - EDGE_PX) return;
|
|
86
86
|
const granted = claim(e.pointerId, { ownerId: 'sh3:edge', axis: 'x', priority: 'edge', depth: 0 });
|
|
87
87
|
if (granted) edgePointers.add(e.pointerId);
|
|
88
|
-
}
|
|
88
|
+
};
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
const onPointerEnd = (e: PointerEvent): void => {
|
|
91
91
|
if (!edgePointers.has(e.pointerId)) return;
|
|
92
92
|
revoke(e.pointerId, 'sh3:edge');
|
|
93
93
|
edgePointers.delete(e.pointerId);
|
|
94
|
-
}
|
|
94
|
+
};
|
|
95
95
|
|
|
96
96
|
el.addEventListener('pointerdown', onPointerDown);
|
|
97
97
|
el.addEventListener('pointerup', onPointerEnd);
|
|
@@ -50,6 +50,7 @@ export function listActionsFromEntries(entries, state) {
|
|
|
50
50
|
ownerShardId: entry.ownerShardId,
|
|
51
51
|
paletteItem: entry.action.paletteItem !== false,
|
|
52
52
|
contextItem: entry.action.contextItem !== false,
|
|
53
|
+
aiInvocable: entry.action.aiInvocable,
|
|
53
54
|
submenu: entry.action.submenu,
|
|
54
55
|
submenuOf: entry.action.submenuOf,
|
|
55
56
|
active,
|
|
@@ -57,6 +57,19 @@ describe('listActiveFromEntries', () => {
|
|
|
57
57
|
expect(out[0].paletteItem).toBe(false);
|
|
58
58
|
expect(out[0].contextItem).toBe(true); // defaults to true
|
|
59
59
|
});
|
|
60
|
+
it('propagates aiInvocable from the registered action, preserving undefined', () => {
|
|
61
|
+
const entries = [
|
|
62
|
+
mkEntry({ id: 'opt-out', scope: 'home', aiInvocable: false }),
|
|
63
|
+
mkEntry({ id: 'opt-in', scope: 'home', aiInvocable: true }),
|
|
64
|
+
mkEntry({ id: 'unset', scope: 'home' }),
|
|
65
|
+
];
|
|
66
|
+
const out = listActiveFromEntries(entries, mkState());
|
|
67
|
+
const byId = Object.fromEntries(out.map((d) => [d.id, d]));
|
|
68
|
+
expect(byId['opt-out'].aiInvocable).toBe(false);
|
|
69
|
+
expect(byId['opt-in'].aiInvocable).toBe(true);
|
|
70
|
+
// `undefined` is significant — consumers filter `=== false`, not falsy.
|
|
71
|
+
expect(byId['unset'].aiInvocable).toBeUndefined();
|
|
72
|
+
});
|
|
60
73
|
it('dedupes by action id', () => {
|
|
61
74
|
const entries = [
|
|
62
75
|
mkEntry({ id: 'dup', scope: 'home' }, 'shard.a'),
|
package/dist/actions/types.d.ts
CHANGED
|
@@ -14,6 +14,14 @@ export interface Action {
|
|
|
14
14
|
scope: ActionScope;
|
|
15
15
|
contextItem?: boolean;
|
|
16
16
|
paletteItem?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Opt-out flag for AI tool catalogs. Set `false` to hide this action
|
|
19
|
+
* from LLM-facing surfaces (e.g. `sh3-ai`'s action→tool adapter) — use
|
|
20
|
+
* for palette-only actions that need a UI picker and are meaningless
|
|
21
|
+
* to invoke programmatically. Defaults to `undefined` (catalog
|
|
22
|
+
* inclusion decided by the consumer).
|
|
23
|
+
*/
|
|
24
|
+
aiInvocable?: boolean;
|
|
17
25
|
/**
|
|
18
26
|
* Optional menu container id. When set and the active app's declared
|
|
19
27
|
* (or canonical fallback) menu list contains this id, the action
|
|
@@ -148,6 +156,8 @@ export interface ActiveActionDescriptor {
|
|
|
148
156
|
ownerShardId: string;
|
|
149
157
|
paletteItem: boolean;
|
|
150
158
|
contextItem: boolean;
|
|
159
|
+
/** Carried through from the registered action; see `Action.aiInvocable`. */
|
|
160
|
+
aiInvocable?: boolean;
|
|
151
161
|
/** True when this action is a submenu parent (children opened by drill). */
|
|
152
162
|
submenu?: true;
|
|
153
163
|
/** Parent action id when this action is a submenu child. */
|
|
@@ -187,6 +197,8 @@ export interface ActionDescriptor {
|
|
|
187
197
|
ownerShardId: string;
|
|
188
198
|
paletteItem: boolean;
|
|
189
199
|
contextItem: boolean;
|
|
200
|
+
/** Carried through from the registered action; see `Action.aiInvocable`. */
|
|
201
|
+
aiInvocable?: boolean;
|
|
190
202
|
/** True when this action is a submenu parent (children opened by drill). */
|
|
191
203
|
submenu?: true;
|
|
192
204
|
/** Parent action id when this action is a submenu child. */
|
package/dist/api.d.ts
CHANGED
|
@@ -65,6 +65,8 @@ export type { RunVerbOpts, RunVerbResult } from './runtime';
|
|
|
65
65
|
export { registerShellMode } from './shell-shard/registerShellMode';
|
|
66
66
|
export type { ShellModeDescriptor, ShellModeOutput, ShellModeDispatchHandler, ShellModeDispatchInput, ShellModeRunsOn, RichEntryHandle, StreamHandle, } from './shell-shard/contract';
|
|
67
67
|
export { SHELL_MODE_CONTRIBUTION_POINT } from './shell-shard/contract';
|
|
68
|
+
export { CONTEXT_SOURCE_POINT_ID } from './contributions/contextSource';
|
|
69
|
+
export type { ContextSource } from './contributions/contextSource';
|
|
68
70
|
export type { GestureRegistry, GestureHandle } from './gestures';
|
|
69
71
|
export type { GestureType, Axis, ClaimPriority, ClaimEntry, PanEvent, ScrollEvent, ButtonEvent, PanOptions, DragOptions, ButtonOptions, ScrollOptions, } from './gestures/types';
|
|
70
72
|
export { VERSION } from './version';
|
package/dist/api.js
CHANGED
|
@@ -65,6 +65,8 @@ export { runVerbProgrammatic } from './runtime';
|
|
|
65
65
|
// Sh3 mode contributions (external shards extend the sh3 with new modes).
|
|
66
66
|
export { registerShellMode } from './shell-shard/registerShellMode';
|
|
67
67
|
export { SHELL_MODE_CONTRIBUTION_POINT } from './shell-shard/contract';
|
|
68
|
+
// Context-source contributions (publishers register entries; consumers like sh3-ai pick them up).
|
|
69
|
+
export { CONTEXT_SOURCE_POINT_ID } from './contributions/contextSource';
|
|
68
70
|
// Package version.
|
|
69
71
|
export { VERSION } from './version';
|
|
70
72
|
// Framework shard IDs — shards that are always present (built-in to sh3-core).
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { storeContext } from './storeShard.svelte';
|
|
10
10
|
import { fetchArchive, buildPackageMeta } from '../../registry/client';
|
|
11
|
-
import { readFileFromArchive
|
|
11
|
+
import { readFileFromArchive } from '../../registry/archive';
|
|
12
12
|
import { installPackage } from '../../registry/installer';
|
|
13
13
|
import { loadBundleModule, type LoadedBundle } from '../../registry/loader';
|
|
14
14
|
import { extractBundlePermissions } from '../../registry/permission-descriptions';
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
resolveMenuContainers,
|
|
16
16
|
resolveMenuItems,
|
|
17
17
|
resolveSubmenuItems,
|
|
18
|
-
type MenuBarItem,
|
|
19
18
|
} from '../actions/menuBarModel';
|
|
20
19
|
import { listActions } from '../actions/registry';
|
|
21
20
|
import { getLiveDispatcherState } from '../actions/state.svelte';
|
|
@@ -54,7 +53,16 @@
|
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
// --- derived items for current nav level ---------------------------
|
|
57
|
-
|
|
56
|
+
interface SheetItem {
|
|
57
|
+
id: string;
|
|
58
|
+
label: string;
|
|
59
|
+
isContainer: boolean;
|
|
60
|
+
isSubmenu: boolean;
|
|
61
|
+
shortcut: string | null;
|
|
62
|
+
disabled: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const currentItems = $derived.by<SheetItem[]>(() => {
|
|
58
66
|
const entries = listActions();
|
|
59
67
|
const nav = currentNav;
|
|
60
68
|
|
|
@@ -65,7 +73,10 @@
|
|
|
65
73
|
.map((c) => ({
|
|
66
74
|
id: c.id,
|
|
67
75
|
label: c.label,
|
|
68
|
-
isContainer: true
|
|
76
|
+
isContainer: true,
|
|
77
|
+
isSubmenu: false,
|
|
78
|
+
shortcut: null,
|
|
79
|
+
disabled: false,
|
|
69
80
|
}));
|
|
70
81
|
}
|
|
71
82
|
|
|
@@ -74,8 +85,9 @@
|
|
|
74
85
|
return items.map((item) => ({
|
|
75
86
|
id: item.id,
|
|
76
87
|
label: item.label,
|
|
77
|
-
|
|
88
|
+
isContainer: false,
|
|
78
89
|
isSubmenu: item.submenu === true,
|
|
90
|
+
shortcut: item.shortcut,
|
|
79
91
|
disabled: item.disabled,
|
|
80
92
|
}));
|
|
81
93
|
}
|
|
@@ -85,14 +97,15 @@
|
|
|
85
97
|
return items.map((item) => ({
|
|
86
98
|
id: item.id,
|
|
87
99
|
label: item.label,
|
|
100
|
+
isContainer: false,
|
|
101
|
+
isSubmenu: item.submenu === true,
|
|
88
102
|
shortcut: item.shortcut,
|
|
89
103
|
disabled: item.disabled,
|
|
90
|
-
isSubmenu: item.submenu === true,
|
|
91
104
|
}));
|
|
92
105
|
});
|
|
93
106
|
|
|
94
107
|
// --- actions --------------------------------------------------------
|
|
95
|
-
function handleTap(entry:
|
|
108
|
+
function handleTap(entry: SheetItem) {
|
|
96
109
|
if (entry.isContainer) {
|
|
97
110
|
const c = containers.find((x) => x.id === entry.id);
|
|
98
111
|
if (c) push({ kind: 'container', containerId: c.id, label: c.label });
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contribution point id: shards register `ContextSource` descriptors here.
|
|
3
|
+
* Each registration adds one pickable entry in any consuming UI (e.g. the
|
|
4
|
+
* "SOURCES" section of the AI Edit modal when sh3-ai is installed) and may
|
|
5
|
+
* be picked up by future consumers. Lifecycle is publisher-owned — register
|
|
6
|
+
* when content becomes relevant (app activation, project load, selection),
|
|
7
|
+
* dispose when it stops being relevant.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CONTEXT_SOURCE_POINT_ID = "sh3.contextSource";
|
|
10
|
+
/** A single context-source contribution. */
|
|
11
|
+
export interface ContextSource {
|
|
12
|
+
/**
|
|
13
|
+
* Globally unique. Convention: `<shardId>:<slug>`. Used as the picker
|
|
14
|
+
* selection key, so it must be stable across re-renders. Re-registering
|
|
15
|
+
* with an existing id silently replaces — generally dispose the prior
|
|
16
|
+
* registration first when swapping content.
|
|
17
|
+
*/
|
|
18
|
+
id: string;
|
|
19
|
+
/** Short display name shown in the picker row and the chip body. */
|
|
20
|
+
label: string;
|
|
21
|
+
/**
|
|
22
|
+
* Tooltip in any consuming UI. Consumers may also surface this to
|
|
23
|
+
* downstream tools (e.g. as the description sh3-ai exposes when
|
|
24
|
+
* chat-side context tools land).
|
|
25
|
+
*/
|
|
26
|
+
description?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Drives prompt formatting (when consumed by sh3-ai) and the chip kind tag.
|
|
29
|
+
* - `text` (default): value coerced to string, dumped raw.
|
|
30
|
+
* - `markdown`: value coerced to string, wrapped in fenced ```markdown``` block.
|
|
31
|
+
* - `json`: value `JSON.stringify`-ed with 2-space indent, wrapped in fenced ```json``` block.
|
|
32
|
+
*/
|
|
33
|
+
kind?: 'text' | 'markdown' | 'json';
|
|
34
|
+
/**
|
|
35
|
+
* Sub-header under the picker's SOURCES section (e.g. the consuming
|
|
36
|
+
* shard's display name). Entries without a group fall under an "Other"
|
|
37
|
+
* sub-header.
|
|
38
|
+
*/
|
|
39
|
+
group?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Lazy fetcher. Called when the user picks the chip (for the expand
|
|
42
|
+
* preview pane) and again at consume time. May be sync or async.
|
|
43
|
+
* Returning null/undefined signals "no content available right now" —
|
|
44
|
+
* entry is silently omitted but the chip remains. Throwing/rejecting
|
|
45
|
+
* surfaces a toast and skips the entry.
|
|
46
|
+
*/
|
|
47
|
+
get(): unknown | Promise<unknown>;
|
|
48
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Public contract for context-source contributions. Shards register
|
|
3
|
+
* `ContextSource` descriptors at `CONTEXT_SOURCE_POINT_ID` via the
|
|
4
|
+
* standard `ctx.contributions.register` API; consumers (sh3-ai today,
|
|
5
|
+
* potentially inspectors / hover previews / chat-side context tools
|
|
6
|
+
* tomorrow) enumerate them via `ctx.contributions.list`.
|
|
7
|
+
*
|
|
8
|
+
* v1 has a single consumer (sh3-ai). The descriptor shape is hosted
|
|
9
|
+
* here so publisher shards do not need a devDependency on sh3-ai to
|
|
10
|
+
* contribute. Lifecycle is consumer-owned — see the JSDoc on
|
|
11
|
+
* `CONTEXT_SOURCE_POINT_ID` below.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Contribution point id: shards register `ContextSource` descriptors here.
|
|
15
|
+
* Each registration adds one pickable entry in any consuming UI (e.g. the
|
|
16
|
+
* "SOURCES" section of the AI Edit modal when sh3-ai is installed) and may
|
|
17
|
+
* be picked up by future consumers. Lifecycle is publisher-owned — register
|
|
18
|
+
* when content becomes relevant (app activation, project load, selection),
|
|
19
|
+
* dispose when it stops being relevant.
|
|
20
|
+
*/
|
|
21
|
+
export const CONTEXT_SOURCE_POINT_ID = 'sh3.contextSource';
|
|
@@ -37,13 +37,4 @@ export interface DocumentPickerOptions {
|
|
|
37
37
|
* own namespace, so the user can't navigate into a dead-end root. */
|
|
38
38
|
lockToShard?: boolean;
|
|
39
39
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Create a document picker API bound to a document listing function.
|
|
42
|
-
* The listFn is derived from the shard's document zone + browse permission
|
|
43
|
-
* and baked in at construction time so callers don't pass their own scope.
|
|
44
|
-
*
|
|
45
|
-
* When an `anchor` element is provided the browser opens as a popup
|
|
46
|
-
* (anchored near the element). Without an anchor it opens as a centered
|
|
47
|
-
* modal (the expected default for file-browser dialogs).
|
|
48
|
-
*/
|
|
49
40
|
export declare function createDocumentPicker(listFn: DocListFn, options?: DocumentPickerOptions): DocumentPickerApi;
|
|
@@ -2,15 +2,6 @@ import { sh3 } from '../sh3Runtime.svelte';
|
|
|
2
2
|
import DocumentBrowser from '../primitives/widgets/_DocumentBrowser.svelte';
|
|
3
3
|
const BOX_STYLE = 'max-width: min(800px, 95vw);';
|
|
4
4
|
const MODAL_OPTS = { dismissOnBackdrop: true, boxStyle: BOX_STYLE };
|
|
5
|
-
/**
|
|
6
|
-
* Create a document picker API bound to a document listing function.
|
|
7
|
-
* The listFn is derived from the shard's document zone + browse permission
|
|
8
|
-
* and baked in at construction time so callers don't pass their own scope.
|
|
9
|
-
*
|
|
10
|
-
* When an `anchor` element is provided the browser opens as a popup
|
|
11
|
-
* (anchored near the element). Without an anchor it opens as a centered
|
|
12
|
-
* modal (the expected default for file-browser dialogs).
|
|
13
|
-
*/
|
|
14
5
|
export function createDocumentPicker(listFn, options = {}) {
|
|
15
6
|
const { listFolders, handle, readOnlyShard, initialShardId, lockToShard } = options;
|
|
16
7
|
function openBrowser(browserProps, anchor) {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
<script lang="ts">
|
|
1
|
+
<script lang="ts" generics="M extends 'open' | 'save'">
|
|
2
2
|
import type { CommitOnlyEvents } from './_contract';
|
|
3
3
|
import { sh3 } from '../../sh3Runtime.svelte';
|
|
4
4
|
import DocumentBrowser from './_DocumentBrowser.svelte';
|
|
5
5
|
import type { DocumentMeta } from '../../documents/types';
|
|
6
6
|
import type { DocEntry, OpenerValue, SaverValue } from './DocumentFilePicker';
|
|
7
7
|
|
|
8
|
+
type ValueFor<Mode extends 'open' | 'save'> = Mode extends 'open' ? OpenerValue : SaverValue;
|
|
9
|
+
|
|
8
10
|
type DocListFn = () => Promise<Array<DocumentMeta & { shardId: string }>>;
|
|
9
11
|
type FolderListFn = (shardId: string, prefix: string) => Promise<string[]>;
|
|
10
12
|
type HandleFn = {
|
|
@@ -17,7 +19,7 @@
|
|
|
17
19
|
|
|
18
20
|
let {
|
|
19
21
|
mode,
|
|
20
|
-
value = $bindable<
|
|
22
|
+
value = $bindable<ValueFor<M>>(null as ValueFor<M>),
|
|
21
23
|
listDocuments,
|
|
22
24
|
listFolders,
|
|
23
25
|
handle,
|
|
@@ -29,8 +31,8 @@
|
|
|
29
31
|
selectable = 'file',
|
|
30
32
|
onchange,
|
|
31
33
|
}: {
|
|
32
|
-
mode:
|
|
33
|
-
value?:
|
|
34
|
+
mode: M;
|
|
35
|
+
value?: ValueFor<M>;
|
|
34
36
|
listDocuments: DocListFn;
|
|
35
37
|
listFolders?: FolderListFn;
|
|
36
38
|
handle?: HandleFn;
|
|
@@ -40,7 +42,7 @@
|
|
|
40
42
|
size?: 'sm' | 'md';
|
|
41
43
|
buttonLabel?: string;
|
|
42
44
|
selectable?: 'file' | 'folder' | 'both';
|
|
43
|
-
} & CommitOnlyEvents<
|
|
45
|
+
} & CommitOnlyEvents<ValueFor<M>> = $props();
|
|
44
46
|
|
|
45
47
|
let trigger = $state<HTMLButtonElement | undefined>(undefined);
|
|
46
48
|
let openFlag = $state(false);
|
|
@@ -56,8 +58,8 @@
|
|
|
56
58
|
);
|
|
57
59
|
|
|
58
60
|
function handleCommit(result: OpenerValue | SaverValue) {
|
|
59
|
-
value = result
|
|
60
|
-
onchange?.(result);
|
|
61
|
+
value = result as ValueFor<M>;
|
|
62
|
+
onchange?.(result as ValueFor<M>);
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
function onOpenClosed() {
|
|
@@ -1,32 +1,49 @@
|
|
|
1
1
|
import type { CommitOnlyEvents } from './_contract';
|
|
2
2
|
import type { DocumentMeta } from '../../documents/types';
|
|
3
3
|
import type { OpenerValue, SaverValue } from './DocumentFilePicker';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
declare function $$render<M extends 'open' | 'save'>(): {
|
|
5
|
+
props: {
|
|
6
|
+
mode: M;
|
|
7
|
+
value?: M extends "open" ? OpenerValue : SaverValue;
|
|
8
|
+
listDocuments: () => Promise<Array<DocumentMeta & {
|
|
9
|
+
shardId: string;
|
|
10
|
+
}>>;
|
|
11
|
+
listFolders?: (shardId: string, prefix: string) => Promise<string[]>;
|
|
12
|
+
handle?: {
|
|
13
|
+
mkdir: (shardId: string, path: string) => Promise<void>;
|
|
14
|
+
rmdir: (shardId: string, path: string, opts: {
|
|
15
|
+
recursive: boolean;
|
|
16
|
+
}) => Promise<void>;
|
|
17
|
+
renameFolder: (shardId: string, oldPath: string, newPath: string) => Promise<void>;
|
|
18
|
+
rename: (shardId: string, oldPath: string, newPath: string) => Promise<void>;
|
|
19
|
+
delete: (shardId: string, path: string) => Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
readOnlyShard?: (shardId: string) => boolean;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
invalid?: boolean;
|
|
24
|
+
size?: "sm" | "md";
|
|
25
|
+
buttonLabel?: string;
|
|
26
|
+
selectable?: "file" | "folder" | "both";
|
|
27
|
+
} & CommitOnlyEvents<M extends "open" ? OpenerValue : SaverValue>;
|
|
28
|
+
exports: {};
|
|
29
|
+
bindings: "value";
|
|
30
|
+
slots: {};
|
|
31
|
+
events: {};
|
|
16
32
|
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
declare class __sveltets_Render<M extends 'open' | 'save'> {
|
|
34
|
+
props(): ReturnType<typeof $$render<M>>['props'];
|
|
35
|
+
events(): ReturnType<typeof $$render<M>>['events'];
|
|
36
|
+
slots(): ReturnType<typeof $$render<M>>['slots'];
|
|
37
|
+
bindings(): "value";
|
|
38
|
+
exports(): {};
|
|
39
|
+
}
|
|
40
|
+
interface $$IsomorphicComponent {
|
|
41
|
+
new <M extends 'open' | 'save'>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<M>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<M>['props']>, ReturnType<__sveltets_Render<M>['events']>, ReturnType<__sveltets_Render<M>['slots']>> & {
|
|
42
|
+
$$bindings?: ReturnType<__sveltets_Render<M>['bindings']>;
|
|
43
|
+
} & ReturnType<__sveltets_Render<M>['exports']>;
|
|
44
|
+
<M extends 'open' | 'save'>(internal: unknown, props: ReturnType<__sveltets_Render<M>['props']> & {}): ReturnType<__sveltets_Render<M>['exports']>;
|
|
45
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
46
|
+
}
|
|
47
|
+
declare const DocumentFilePicker: $$IsomorphicComponent;
|
|
48
|
+
type DocumentFilePicker<M extends 'open' | 'save'> = InstanceType<typeof DocumentFilePicker<M>>;
|
|
32
49
|
export default DocumentFilePicker;
|
|
@@ -59,7 +59,6 @@
|
|
|
59
59
|
let selected = $state<Selected>(null);
|
|
60
60
|
let filename = $state(untrack(() => suggestedName));
|
|
61
61
|
let activeIdx = $state(0);
|
|
62
|
-
let listEl = $state<HTMLElement | undefined>(undefined);
|
|
63
62
|
|
|
64
63
|
// Folder state loaded via listFolders
|
|
65
64
|
let folders = $state<string[]>([]);
|
|
@@ -459,11 +458,12 @@
|
|
|
459
458
|
</div>
|
|
460
459
|
{/if}
|
|
461
460
|
|
|
462
|
-
<div class="sh3-doc-browser__list"
|
|
461
|
+
<div class="sh3-doc-browser__list">
|
|
463
462
|
{#if confirmDelete}
|
|
464
|
-
{@const
|
|
463
|
+
{@const folderPath = confirmDelete.kind === 'folder' ? confirmDelete.fullPath : null}
|
|
464
|
+
{@const childCount = folderPath
|
|
465
465
|
? docs.filter((d) =>
|
|
466
|
-
d.shardId === shardId && d.path.startsWith(
|
|
466
|
+
d.shardId === shardId && d.path.startsWith(folderPath + '/'),
|
|
467
467
|
).length
|
|
468
468
|
: 0}
|
|
469
469
|
<div class="sh3-doc-browser__confirm-overlay">
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
import { makeSelectionApi } from '../actions/selection.svelte';
|
|
19
19
|
import { getAppearance } from '../app-appearance';
|
|
20
20
|
import iconsUrl from '../assets/icons.svg';
|
|
21
|
-
import { manifest } from '../shell-shard/manifest';
|
|
22
21
|
|
|
23
22
|
const homeSelection = makeSelectionApi('__sh3core__');
|
|
24
23
|
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* defensive pattern as `platform/index.ts`. Vite code-splits it
|
|
15
15
|
* into a Tauri-only chunk that never loads in web builds.
|
|
16
16
|
*/
|
|
17
|
+
import { getEnvServerUrl } from '../env/serverUrl';
|
|
17
18
|
import { getAuthToken } from './authToken';
|
|
18
19
|
let tauriFetch = null;
|
|
19
20
|
let tauriProbed = false;
|
|
@@ -43,8 +44,25 @@ async function getTauriFetch() {
|
|
|
43
44
|
}
|
|
44
45
|
return tauriFetch;
|
|
45
46
|
}
|
|
47
|
+
// Resolve relative paths against the configured server URL so callers can
|
|
48
|
+
// use bare `/api/...` paths and have them hit the configured sh3-server
|
|
49
|
+
// regardless of the webview's origin. Mirrors ctx.fetch's resolveUrl —
|
|
50
|
+
// absolute URLs pass through, relatives get prefixed when a serverUrl is
|
|
51
|
+
// set. When no serverUrl is configured (e.g. early bootstrap, web builds
|
|
52
|
+
// hosted same-origin), the path is left untouched so `fetch` falls back
|
|
53
|
+
// to `window.location.origin` as before.
|
|
54
|
+
function resolveApiUrl(url) {
|
|
55
|
+
if (url.startsWith('http://') || url.startsWith('https://'))
|
|
56
|
+
return url;
|
|
57
|
+
const base = getEnvServerUrl();
|
|
58
|
+
if (!base)
|
|
59
|
+
return url;
|
|
60
|
+
const sep = url.startsWith('/') ? '' : '/';
|
|
61
|
+
return `${base}${sep}${url}`;
|
|
62
|
+
}
|
|
46
63
|
export function apiFetch(url, init) {
|
|
47
64
|
var _a;
|
|
65
|
+
const resolved = resolveApiUrl(url);
|
|
48
66
|
// Inject Authorization: Bearer <session-token> if a session is active
|
|
49
67
|
// and the caller didn't already supply one. Cookies don't survive the
|
|
50
68
|
// cross-origin hop (SameSite=Lax + plugin-http has no cookie store),
|
|
@@ -62,11 +80,11 @@ export function apiFetch(url, init) {
|
|
|
62
80
|
// call-timing assertions in tests still hold and web builds skip the
|
|
63
81
|
// dynamic-import probe entirely.
|
|
64
82
|
if (!inTauriRuntime()) {
|
|
65
|
-
return fetch(
|
|
83
|
+
return fetch(resolved, Object.assign({ credentials: 'include' }, finalInit));
|
|
66
84
|
}
|
|
67
85
|
return getTauriFetch().then((tf) => {
|
|
68
86
|
if (tf)
|
|
69
|
-
return tf(
|
|
70
|
-
return fetch(
|
|
87
|
+
return tf(resolved, finalInit);
|
|
88
|
+
return fetch(resolved, Object.assign({ credentials: 'include' }, finalInit));
|
|
71
89
|
});
|
|
72
90
|
}
|
|
@@ -34,4 +34,67 @@ describe('apiFetch', () => {
|
|
|
34
34
|
const [, init] = calls[0];
|
|
35
35
|
expect(init.credentials).toBe('omit');
|
|
36
36
|
});
|
|
37
|
+
it('resolves relative paths against the configured serverUrl', async () => {
|
|
38
|
+
const calls = [];
|
|
39
|
+
globalThis.fetch = vi.fn(async (input) => {
|
|
40
|
+
calls.push(String(input));
|
|
41
|
+
return new Response('ok');
|
|
42
|
+
});
|
|
43
|
+
const { __setEnvServerUrl } = await import('../env/serverUrl');
|
|
44
|
+
__setEnvServerUrl('https://remote.example.com');
|
|
45
|
+
try {
|
|
46
|
+
const { apiFetch } = await import('./apiFetch');
|
|
47
|
+
await apiFetch('/api/admin/users');
|
|
48
|
+
expect(calls[0]).toBe('https://remote.example.com/api/admin/users');
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
__setEnvServerUrl('');
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
it('prepends a slash to bare relative paths when serverUrl is set', async () => {
|
|
55
|
+
const calls = [];
|
|
56
|
+
globalThis.fetch = vi.fn(async (input) => {
|
|
57
|
+
calls.push(String(input));
|
|
58
|
+
return new Response('ok');
|
|
59
|
+
});
|
|
60
|
+
const { __setEnvServerUrl } = await import('../env/serverUrl');
|
|
61
|
+
__setEnvServerUrl('https://remote.example.com');
|
|
62
|
+
try {
|
|
63
|
+
const { apiFetch } = await import('./apiFetch');
|
|
64
|
+
await apiFetch('api/admin/users');
|
|
65
|
+
expect(calls[0]).toBe('https://remote.example.com/api/admin/users');
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
__setEnvServerUrl('');
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
it('passes absolute URLs through unchanged regardless of serverUrl', async () => {
|
|
72
|
+
const calls = [];
|
|
73
|
+
globalThis.fetch = vi.fn(async (input) => {
|
|
74
|
+
calls.push(String(input));
|
|
75
|
+
return new Response('ok');
|
|
76
|
+
});
|
|
77
|
+
const { __setEnvServerUrl } = await import('../env/serverUrl');
|
|
78
|
+
__setEnvServerUrl('https://remote.example.com');
|
|
79
|
+
try {
|
|
80
|
+
const { apiFetch } = await import('./apiFetch');
|
|
81
|
+
await apiFetch('https://other.example.com/api/bar');
|
|
82
|
+
expect(calls[0]).toBe('https://other.example.com/api/bar');
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
__setEnvServerUrl('');
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
it('leaves relative paths alone when no serverUrl is configured', async () => {
|
|
89
|
+
const calls = [];
|
|
90
|
+
globalThis.fetch = vi.fn(async (input) => {
|
|
91
|
+
calls.push(String(input));
|
|
92
|
+
return new Response('ok');
|
|
93
|
+
});
|
|
94
|
+
const { __setEnvServerUrl } = await import('../env/serverUrl');
|
|
95
|
+
__setEnvServerUrl('');
|
|
96
|
+
const { apiFetch } = await import('./apiFetch');
|
|
97
|
+
await apiFetch('/api/admin/users');
|
|
98
|
+
expect(calls[0]).toBe('/api/admin/users');
|
|
99
|
+
});
|
|
37
100
|
});
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Auto-generated from package.json — do not edit manually. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.24.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Auto-generated from package.json — do not edit manually. */
|
|
2
|
-
export const VERSION = '0.
|
|
2
|
+
export const VERSION = '0.24.0';
|