ikanban-web 0.2.9 → 0.2.11
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/assets/{ghostty-web-CveYg7XI.js → ghostty-web-BOsxUEnx.js} +1 -1
- package/dist/assets/home-DmwjxlRp.js +1 -0
- package/dist/assets/{index-BlcL7wgr.js → index-CE2ZqGRp.js} +103 -103
- package/dist/assets/index-Z4u1EVXA.css +1 -0
- package/dist/assets/session-FqpMK7PV.js +24 -0
- package/dist/index.html +2 -2
- package/package.json +2 -2
- package/src/app.tsx +82 -57
- package/src/components/dialog-command-palette.tsx +70 -0
- package/src/components/dialog-fork.tsx +1 -1
- package/src/components/dialog-select-file.tsx +1 -1
- package/src/components/prompt-input/submit.ts +1 -1
- package/src/components/titlebar.tsx +12 -1
- package/src/context/notification.tsx +2 -2
- package/src/i18n/en.ts +173 -83
- package/src/i18n/zh.ts +98 -47
- package/src/pages/directory-layout.tsx +2 -2
- package/src/pages/home.tsx +374 -89
- package/src/pages/layout/sidebar-items.tsx +3 -3
- package/src/pages/layout/sidebar-workspace.tsx +1 -1
- package/src/pages/layout.tsx +175 -237
- package/src/pages/session/message-timeline.tsx +6 -5
- package/src/pages/session/use-session-commands.tsx +1 -1
- package/src/pages/session.tsx +2 -1
- package/dist/assets/home-OHGVA1hY.js +0 -1
- package/dist/assets/index-B0TEjAod.css +0 -1
- package/dist/assets/session-CdIG8nTN.js +0 -24
package/dist/index.html
CHANGED
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
<meta property="og:image" content="/ikanban/social-share.png" />
|
|
28
28
|
<meta property="twitter:image" content="/social-share.png" />
|
|
29
29
|
<script id="oc-theme-preload-script" src="/ikanban/oc-theme-preload.js"></script>
|
|
30
|
-
<script type="module" crossorigin src="/ikanban/assets/index-
|
|
31
|
-
<link rel="stylesheet" crossorigin href="/ikanban/assets/index-
|
|
30
|
+
<script type="module" crossorigin src="/ikanban/assets/index-CE2ZqGRp.js"></script>
|
|
31
|
+
<link rel="stylesheet" crossorigin href="/ikanban/assets/index-Z4u1EVXA.css">
|
|
32
32
|
</head>
|
|
33
33
|
<body class="antialiased overscroll-none text-12-regular overflow-hidden">
|
|
34
34
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ikanban-web",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "iKanban web app",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@kobalte/core": "0.13.11",
|
|
33
|
-
"@opencode-ai/sdk": "^1.2.
|
|
33
|
+
"@opencode-ai/sdk": "^1.2.21",
|
|
34
34
|
"@shikijs/transformers": "4.0.1",
|
|
35
35
|
"@solid-primitives/active-element": "2.1.5",
|
|
36
36
|
"@solid-primitives/audio": "1.4.4",
|
package/src/app.tsx
CHANGED
|
@@ -1,43 +1,54 @@
|
|
|
1
|
-
import "@/index.css"
|
|
2
|
-
import { File } from "ikanban-ui/file"
|
|
3
|
-
import { I18nProvider } from "ikanban-ui/context"
|
|
4
|
-
import { DialogProvider } from "ikanban-ui/context/dialog"
|
|
5
|
-
import { FileComponentProvider } from "ikanban-ui/context/file"
|
|
6
|
-
import { MarkedProvider } from "ikanban-ui/context/marked"
|
|
7
|
-
import { Font } from "ikanban-ui/font"
|
|
8
|
-
import { ThemeProvider } from "ikanban-ui/theme"
|
|
9
|
-
import { MetaProvider } from "@solidjs/meta"
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import
|
|
29
|
-
import
|
|
30
|
-
import {
|
|
1
|
+
import "@/index.css";
|
|
2
|
+
import { File } from "ikanban-ui/file";
|
|
3
|
+
import { I18nProvider } from "ikanban-ui/context";
|
|
4
|
+
import { DialogProvider } from "ikanban-ui/context/dialog";
|
|
5
|
+
import { FileComponentProvider } from "ikanban-ui/context/file";
|
|
6
|
+
import { MarkedProvider } from "ikanban-ui/context/marked";
|
|
7
|
+
import { Font } from "ikanban-ui/font";
|
|
8
|
+
import { ThemeProvider } from "ikanban-ui/theme";
|
|
9
|
+
import { MetaProvider } from "@solidjs/meta";
|
|
10
|
+
import { Route, Router } from "@solidjs/router";
|
|
11
|
+
import {
|
|
12
|
+
ErrorBoundary,
|
|
13
|
+
type JSX,
|
|
14
|
+
lazy,
|
|
15
|
+
type ParentProps,
|
|
16
|
+
Show,
|
|
17
|
+
Suspense,
|
|
18
|
+
} from "solid-js";
|
|
19
|
+
import { CommandProvider } from "@/context/command";
|
|
20
|
+
import { CommentsProvider } from "@/context/comments";
|
|
21
|
+
import { FileProvider } from "@/context/file";
|
|
22
|
+
import { GlobalSDKProvider } from "@/context/global-sdk";
|
|
23
|
+
import { GlobalSyncProvider } from "@/context/global-sync";
|
|
24
|
+
import { HighlightsProvider } from "@/context/highlights";
|
|
25
|
+
import { LanguageProvider, useLanguage } from "@/context/language";
|
|
26
|
+
import { LayoutProvider } from "@/context/layout";
|
|
27
|
+
import { ModelsProvider } from "@/context/models";
|
|
28
|
+
import { NotificationProvider } from "@/context/notification";
|
|
29
|
+
import { PermissionProvider } from "@/context/permission";
|
|
30
|
+
import { usePlatform } from "@/context/platform";
|
|
31
|
+
import { PromptProvider } from "@/context/prompt";
|
|
32
|
+
import {
|
|
33
|
+
type ServerConnection,
|
|
34
|
+
ServerProvider,
|
|
35
|
+
useServer,
|
|
36
|
+
} from "@/context/server";
|
|
37
|
+
import { SettingsProvider } from "@/context/settings";
|
|
38
|
+
import { TerminalProvider } from "@/context/terminal";
|
|
39
|
+
import DirectoryLayout from "@/pages/directory-layout";
|
|
40
|
+
import Layout from "@/pages/layout";
|
|
41
|
+
import { ErrorPage } from "./pages/error";
|
|
31
42
|
|
|
32
|
-
const Home = lazy(() => import("@/pages/home"))
|
|
33
|
-
const Session = lazy(() => import("@/pages/session"))
|
|
34
|
-
const Loading = () => <div class="size-full"
|
|
43
|
+
const Home = lazy(() => import("@/pages/home"));
|
|
44
|
+
const Session = lazy(() => import("@/pages/session"));
|
|
45
|
+
const Loading = () => <div class="size-full" />;
|
|
35
46
|
|
|
36
47
|
const HomeRoute = () => (
|
|
37
48
|
<Suspense fallback={<Loading />}>
|
|
38
49
|
<Home />
|
|
39
50
|
</Suspense>
|
|
40
|
-
)
|
|
51
|
+
);
|
|
41
52
|
|
|
42
53
|
const SessionRoute = () => (
|
|
43
54
|
<SessionProviders>
|
|
@@ -45,28 +56,36 @@ const SessionRoute = () => (
|
|
|
45
56
|
<Session />
|
|
46
57
|
</Suspense>
|
|
47
58
|
</SessionProviders>
|
|
48
|
-
)
|
|
59
|
+
);
|
|
49
60
|
|
|
50
|
-
const SessionIndexRoute = () => <
|
|
61
|
+
const SessionIndexRoute = () => <SessionRoute />;
|
|
51
62
|
|
|
52
63
|
function UiI18nBridge(props: ParentProps) {
|
|
53
|
-
const language = useLanguage()
|
|
54
|
-
return
|
|
64
|
+
const language = useLanguage();
|
|
65
|
+
return (
|
|
66
|
+
<I18nProvider value={{ locale: language.locale, t: language.t }}>
|
|
67
|
+
{props.children}
|
|
68
|
+
</I18nProvider>
|
|
69
|
+
);
|
|
55
70
|
}
|
|
56
71
|
|
|
57
72
|
declare global {
|
|
58
73
|
interface Window {
|
|
59
74
|
__OPENCODE__?: {
|
|
60
|
-
updaterEnabled?: boolean
|
|
61
|
-
deepLinks?: string[]
|
|
62
|
-
wsl?: boolean
|
|
63
|
-
}
|
|
75
|
+
updaterEnabled?: boolean;
|
|
76
|
+
deepLinks?: string[];
|
|
77
|
+
wsl?: boolean;
|
|
78
|
+
};
|
|
64
79
|
}
|
|
65
80
|
}
|
|
66
81
|
|
|
67
82
|
function MarkedProviderWithNativeParser(props: ParentProps) {
|
|
68
|
-
const platform = usePlatform()
|
|
69
|
-
return
|
|
83
|
+
const platform = usePlatform();
|
|
84
|
+
return (
|
|
85
|
+
<MarkedProvider nativeParser={platform.parseMarkdown}>
|
|
86
|
+
{props.children}
|
|
87
|
+
</MarkedProvider>
|
|
88
|
+
);
|
|
70
89
|
}
|
|
71
90
|
|
|
72
91
|
function AppShellProviders(props: ParentProps) {
|
|
@@ -86,7 +105,7 @@ function AppShellProviders(props: ParentProps) {
|
|
|
86
105
|
</LayoutProvider>
|
|
87
106
|
</PermissionProvider>
|
|
88
107
|
</SettingsProvider>
|
|
89
|
-
)
|
|
108
|
+
);
|
|
90
109
|
}
|
|
91
110
|
|
|
92
111
|
function SessionProviders(props: ParentProps) {
|
|
@@ -98,7 +117,7 @@ function SessionProviders(props: ParentProps) {
|
|
|
98
117
|
</PromptProvider>
|
|
99
118
|
</FileProvider>
|
|
100
119
|
</TerminalProvider>
|
|
101
|
-
)
|
|
120
|
+
);
|
|
102
121
|
}
|
|
103
122
|
|
|
104
123
|
function RouterRoot(props: ParentProps<{ appChildren?: JSX.Element }>) {
|
|
@@ -107,7 +126,7 @@ function RouterRoot(props: ParentProps<{ appChildren?: JSX.Element }>) {
|
|
|
107
126
|
{props.appChildren}
|
|
108
127
|
{props.children}
|
|
109
128
|
</AppShellProviders>
|
|
110
|
-
)
|
|
129
|
+
);
|
|
111
130
|
}
|
|
112
131
|
|
|
113
132
|
export function AppBaseProviders(props: ParentProps) {
|
|
@@ -120,7 +139,9 @@ export function AppBaseProviders(props: ParentProps) {
|
|
|
120
139
|
<ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
|
|
121
140
|
<DialogProvider>
|
|
122
141
|
<MarkedProviderWithNativeParser>
|
|
123
|
-
<FileComponentProvider component={File}>
|
|
142
|
+
<FileComponentProvider component={File}>
|
|
143
|
+
{props.children}
|
|
144
|
+
</FileComponentProvider>
|
|
124
145
|
</MarkedProviderWithNativeParser>
|
|
125
146
|
</DialogProvider>
|
|
126
147
|
</ErrorBoundary>
|
|
@@ -128,22 +149,22 @@ export function AppBaseProviders(props: ParentProps) {
|
|
|
128
149
|
</LanguageProvider>
|
|
129
150
|
</ThemeProvider>
|
|
130
151
|
</MetaProvider>
|
|
131
|
-
)
|
|
152
|
+
);
|
|
132
153
|
}
|
|
133
154
|
|
|
134
155
|
function ServerKey(props: ParentProps) {
|
|
135
|
-
const server = useServer()
|
|
156
|
+
const server = useServer();
|
|
136
157
|
return (
|
|
137
158
|
<Show when={server.key} keyed>
|
|
138
159
|
{props.children}
|
|
139
160
|
</Show>
|
|
140
|
-
)
|
|
161
|
+
);
|
|
141
162
|
}
|
|
142
163
|
|
|
143
164
|
export function AppInterface(props: {
|
|
144
|
-
children?: JSX.Element
|
|
145
|
-
defaultServer: ServerConnection.Key
|
|
146
|
-
servers?: Array<ServerConnection.Any
|
|
165
|
+
children?: JSX.Element;
|
|
166
|
+
defaultServer: ServerConnection.Key;
|
|
167
|
+
servers?: Array<ServerConnection.Any>;
|
|
147
168
|
}) {
|
|
148
169
|
return (
|
|
149
170
|
<ServerProvider defaultServer={props.defaultServer} servers={props.servers}>
|
|
@@ -152,17 +173,21 @@ export function AppInterface(props: {
|
|
|
152
173
|
<GlobalSyncProvider>
|
|
153
174
|
<Router
|
|
154
175
|
base={(import.meta.env.BASE_URL ?? "/").replace(/\/+$/, "")}
|
|
155
|
-
root={(routerProps) =>
|
|
176
|
+
root={(routerProps) => (
|
|
177
|
+
<RouterRoot appChildren={props.children}>
|
|
178
|
+
{routerProps.children}
|
|
179
|
+
</RouterRoot>
|
|
180
|
+
)}
|
|
156
181
|
>
|
|
157
182
|
<Route path="/" component={HomeRoute} />
|
|
158
183
|
<Route path="/:dir" component={DirectoryLayout}>
|
|
159
184
|
<Route path="/" component={SessionIndexRoute} />
|
|
160
|
-
<Route path="
|
|
185
|
+
<Route path="/:id?" component={SessionRoute} />
|
|
161
186
|
</Route>
|
|
162
187
|
</Router>
|
|
163
188
|
</GlobalSyncProvider>
|
|
164
189
|
</GlobalSDKProvider>
|
|
165
190
|
</ServerKey>
|
|
166
191
|
</ServerProvider>
|
|
167
|
-
)
|
|
192
|
+
);
|
|
168
193
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { useDialog } from "ikanban-ui/context/dialog"
|
|
2
|
+
import { Dialog } from "ikanban-ui/dialog"
|
|
3
|
+
import { Keybind } from "ikanban-ui/keybind"
|
|
4
|
+
import { List } from "ikanban-ui/list"
|
|
5
|
+
import { createMemo, onCleanup, Show } from "solid-js"
|
|
6
|
+
import { type CommandOption, formatKeybind, useCommand } from "@/context/command"
|
|
7
|
+
import { useLanguage } from "@/context/language"
|
|
8
|
+
|
|
9
|
+
export function DialogCommandPalette() {
|
|
10
|
+
const command = useCommand()
|
|
11
|
+
const dialog = useDialog()
|
|
12
|
+
const language = useLanguage()
|
|
13
|
+
const state = {
|
|
14
|
+
cleanup: undefined as (() => void) | void,
|
|
15
|
+
committed: false,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const items = createMemo(() =>
|
|
19
|
+
command.options.filter((option) => !option.disabled && !option.id.startsWith("suggested.") && option.id !== "file.open"),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
const handleMove = (item: CommandOption | undefined) => {
|
|
23
|
+
state.cleanup?.()
|
|
24
|
+
state.cleanup = item?.onHighlight?.()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
onCleanup(() => {
|
|
28
|
+
if (state.committed) return
|
|
29
|
+
state.cleanup?.()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Dialog class="pt-3 pb-0 !max-h-[480px]" transition>
|
|
34
|
+
<List
|
|
35
|
+
search={{
|
|
36
|
+
placeholder: language.t("palette.search.placeholder"),
|
|
37
|
+
autofocus: true,
|
|
38
|
+
hideIcon: true,
|
|
39
|
+
}}
|
|
40
|
+
emptyMessage={language.t("palette.empty")}
|
|
41
|
+
items={items}
|
|
42
|
+
key={(item) => item.id}
|
|
43
|
+
filterKeys={["title", "description", "category"]}
|
|
44
|
+
groupBy={(item) => item.category ?? language.t("palette.group.commands")}
|
|
45
|
+
onMove={handleMove}
|
|
46
|
+
onSelect={(item) => {
|
|
47
|
+
if (!item) return
|
|
48
|
+
state.committed = true
|
|
49
|
+
state.cleanup = undefined
|
|
50
|
+
dialog.close()
|
|
51
|
+
item.onSelect?.("palette")
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
{(item) => (
|
|
55
|
+
<div class="w-full flex items-center justify-between gap-4">
|
|
56
|
+
<div class="flex items-center gap-2 min-w-0">
|
|
57
|
+
<span class="text-14-regular text-text-strong whitespace-nowrap">{item.title}</span>
|
|
58
|
+
<Show when={item.description}>
|
|
59
|
+
<span class="text-14-regular text-text-weak truncate">{item.description}</span>
|
|
60
|
+
</Show>
|
|
61
|
+
</div>
|
|
62
|
+
<Show when={item.keybind}>
|
|
63
|
+
<Keybind class="rounded-[4px]">{formatKeybind(item.keybind ?? "")}</Keybind>
|
|
64
|
+
</Show>
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
</List>
|
|
68
|
+
</Dialog>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
@@ -75,7 +75,7 @@ export const DialogFork: Component = () => {
|
|
|
75
75
|
return
|
|
76
76
|
}
|
|
77
77
|
dialog.close()
|
|
78
|
-
navigate(`/${base64Encode(sdk.directory)}
|
|
78
|
+
navigate(`/${base64Encode(sdk.directory)}/${forked.data.id}`)
|
|
79
79
|
requestAnimationFrame(() => {
|
|
80
80
|
prompt.set(restored)
|
|
81
81
|
})
|
|
@@ -365,7 +365,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|
|
365
365
|
|
|
366
366
|
if (item.type === "session") {
|
|
367
367
|
if (!item.directory || !item.sessionID) return
|
|
368
|
-
navigate(`/${base64Encode(item.directory)}
|
|
368
|
+
navigate(`/${base64Encode(item.directory)}/${item.sessionID}`)
|
|
369
369
|
return
|
|
370
370
|
}
|
|
371
371
|
|
|
@@ -203,7 +203,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
|
|
203
203
|
if (session) {
|
|
204
204
|
if (shouldAutoAccept) permission.enableAutoAccept(session.id, sessionDirectory)
|
|
205
205
|
layout.handoff.setTabs(base64Encode(sessionDirectory), session.id)
|
|
206
|
-
navigate(`/${base64Encode(sessionDirectory)}
|
|
206
|
+
navigate(`/${base64Encode(sessionDirectory)}/${session.id}`)
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
if (!session) {
|
|
@@ -192,6 +192,17 @@ export function Titlebar() {
|
|
|
192
192
|
</div>
|
|
193
193
|
</Show>
|
|
194
194
|
<div class="flex items-center gap-1 shrink-0">
|
|
195
|
+
<Show when={params.dir}>
|
|
196
|
+
<Tooltip placement="bottom" value={language.t("home.title")} openDelay={2000}>
|
|
197
|
+
<Button
|
|
198
|
+
variant="ghost"
|
|
199
|
+
icon="arrow-left"
|
|
200
|
+
class="titlebar-icon w-8 h-6 p-0 box-border"
|
|
201
|
+
onClick={() => navigate("/")}
|
|
202
|
+
aria-label={language.t("home.title")}
|
|
203
|
+
/>
|
|
204
|
+
</Tooltip>
|
|
205
|
+
</Show>
|
|
195
206
|
<TooltipKeybind
|
|
196
207
|
class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"}
|
|
197
208
|
placement="bottom"
|
|
@@ -234,7 +245,7 @@ export function Titlebar() {
|
|
|
234
245
|
class="titlebar-icon w-8 h-6 p-0 box-border"
|
|
235
246
|
onClick={() => {
|
|
236
247
|
if (!params.dir) return
|
|
237
|
-
navigate(`/${params.dir}
|
|
248
|
+
navigate(`/${params.dir}`)
|
|
238
249
|
}}
|
|
239
250
|
aria-label={language.t("command.session.new")}
|
|
240
251
|
/>
|
|
@@ -245,7 +245,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|
|
245
245
|
session: sessionID,
|
|
246
246
|
})
|
|
247
247
|
|
|
248
|
-
const href = `/${base64Encode(directory)}
|
|
248
|
+
const href = `/${base64Encode(directory)}/${sessionID}`
|
|
249
249
|
if (settings.notifications.agent()) {
|
|
250
250
|
void platform.notify(language.t("notification.session.responseReady.title"), session.title ?? sessionID, href)
|
|
251
251
|
}
|
|
@@ -278,7 +278,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
|
|
|
278
278
|
const description =
|
|
279
279
|
session?.title ??
|
|
280
280
|
(typeof error === "string" ? error : language.t("notification.session.error.fallbackDescription"))
|
|
281
|
-
const href = sessionID ? `/${base64Encode(directory)}
|
|
281
|
+
const href = sessionID ? `/${base64Encode(directory)}/${sessionID}` : `/${base64Encode(directory)}`
|
|
282
282
|
if (settings.notifications.errors()) {
|
|
283
283
|
void platform.notify(language.t("notification.session.error.title"), description, href)
|
|
284
284
|
}
|