ikanban-web 0.2.11 → 0.2.13
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-BOsxUEnx.js → ghostty-web-C-SSEmFU.js} +1 -1
- package/dist/assets/home-C_ni123d.js +1 -0
- package/dist/assets/{index-Z4u1EVXA.css → index-7dPULZd1.css} +1 -1
- package/dist/assets/{index-CE2ZqGRp.js → index-BhuJVelU.js} +104 -104
- package/dist/assets/session-beMq99rD.js +24 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/components/dialog-select-server.test.ts +16 -0
- package/src/components/dialog-select-server.tsx +59 -51
- package/src/components/server-list-item-frame.tsx +15 -0
- package/src/components/titlebar.tsx +0 -30
- package/src/context/global-sync/bootstrap-mode.ts +17 -0
- package/src/context/global-sync/bootstrap.ts +18 -10
- package/src/context/global-sync.test.ts +57 -0
- package/src/context/global-sync.tsx +5 -0
- package/src/i18n/en.ts +13 -13
- package/src/i18n/zh.ts +13 -13
- package/src/pages/home/helpers.test.ts +62 -0
- package/src/pages/home/helpers.ts +80 -0
- package/src/pages/home.tsx +17 -53
- package/src/pages/layout/sidebar-project.tsx +1 -3
- package/src/pages/layout/sidebar-shell-helpers.ts +1 -1
- package/src/pages/layout/sidebar-shell.test.ts +2 -2
- package/src/pages/layout.tsx +41 -132
- package/dist/assets/home-DmwjxlRp.js +0 -1
- package/dist/assets/session-FqpMK7PV.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-BhuJVelU.js"></script>
|
|
31
|
+
<link rel="stylesheet" crossorigin href="/ikanban/assets/index-7dPULZd1.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
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { readFileSync } from "node:fs"
|
|
3
|
+
import { join } from "node:path"
|
|
4
|
+
|
|
5
|
+
const dialogSource = readFileSync(join(import.meta.dir, "dialog-select-server.tsx"), "utf8")
|
|
6
|
+
const frameSource = readFileSync(join(import.meta.dir, "server-list-item-frame.tsx"), "utf8")
|
|
7
|
+
|
|
8
|
+
describe("DialogSelectServer structure", () => {
|
|
9
|
+
test("wraps each http server row so menu actions stay outside the list button", () => {
|
|
10
|
+
expect(dialogSource).toContain("itemWrapper={(item, node) =>")
|
|
11
|
+
expect(dialogSource).toContain("<ServerListItemFrame")
|
|
12
|
+
expect(dialogSource).toContain("row={node}")
|
|
13
|
+
expect(dialogSource).toContain("actions={")
|
|
14
|
+
expect(frameSource).toContain('data-slot="server-list-item-actions"')
|
|
15
|
+
})
|
|
16
|
+
})
|
|
@@ -11,6 +11,7 @@ import { useNavigate } from "@solidjs/router"
|
|
|
11
11
|
import { createEffect, createMemo, createResource, onCleanup, Show } from "solid-js"
|
|
12
12
|
import { createStore, reconcile } from "solid-js/store"
|
|
13
13
|
import { ServerHealthIndicator, ServerRow } from "@/components/server/server-row"
|
|
14
|
+
import { ServerListItemFrame } from "@/components/server-list-item-frame"
|
|
14
15
|
import { useLanguage } from "@/context/language"
|
|
15
16
|
import { usePlatform } from "@/context/platform"
|
|
16
17
|
import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
|
|
@@ -532,6 +533,61 @@ export function DialogSelectServer() {
|
|
|
532
533
|
emptyMessage={language.t("dialog.server.empty")}
|
|
533
534
|
items={sortedItems}
|
|
534
535
|
key={(x) => x.http.url}
|
|
536
|
+
itemWrapper={(item, node) => (
|
|
537
|
+
<Show
|
|
538
|
+
when={item.type === "http"}
|
|
539
|
+
fallback={node}
|
|
540
|
+
>
|
|
541
|
+
<ServerListItemFrame
|
|
542
|
+
row={node}
|
|
543
|
+
actions={
|
|
544
|
+
<DropdownMenu>
|
|
545
|
+
<DropdownMenu.Trigger
|
|
546
|
+
as={IconButton}
|
|
547
|
+
icon="dot-grid"
|
|
548
|
+
variant="ghost"
|
|
549
|
+
class="shrink-0 size-8 hover:bg-surface-base-hover data-[expanded]:bg-surface-base-active"
|
|
550
|
+
onClick={(e: MouseEvent) => e.stopPropagation()}
|
|
551
|
+
onPointerDown={(e: PointerEvent) => e.stopPropagation()}
|
|
552
|
+
/>
|
|
553
|
+
<DropdownMenu.Portal>
|
|
554
|
+
<DropdownMenu.Content class="mt-1">
|
|
555
|
+
<DropdownMenu.Item
|
|
556
|
+
onSelect={() => {
|
|
557
|
+
if (item.type !== "http") return
|
|
558
|
+
startEdit(item)
|
|
559
|
+
}}
|
|
560
|
+
>
|
|
561
|
+
<DropdownMenu.ItemLabel>{language.t("dialog.server.menu.edit")}</DropdownMenu.ItemLabel>
|
|
562
|
+
</DropdownMenu.Item>
|
|
563
|
+
<Show when={canDefault() && defaultUrl() !== item.http.url}>
|
|
564
|
+
<DropdownMenu.Item onSelect={() => setDefault(item.http.url)}>
|
|
565
|
+
<DropdownMenu.ItemLabel>
|
|
566
|
+
{language.t("dialog.server.menu.default")}
|
|
567
|
+
</DropdownMenu.ItemLabel>
|
|
568
|
+
</DropdownMenu.Item>
|
|
569
|
+
</Show>
|
|
570
|
+
<Show when={canDefault() && defaultUrl() === item.http.url}>
|
|
571
|
+
<DropdownMenu.Item onSelect={() => setDefault(null)}>
|
|
572
|
+
<DropdownMenu.ItemLabel>
|
|
573
|
+
{language.t("dialog.server.menu.defaultRemove")}
|
|
574
|
+
</DropdownMenu.ItemLabel>
|
|
575
|
+
</DropdownMenu.Item>
|
|
576
|
+
</Show>
|
|
577
|
+
<DropdownMenu.Separator />
|
|
578
|
+
<DropdownMenu.Item
|
|
579
|
+
onSelect={() => handleRemove(ServerConnection.key(item))}
|
|
580
|
+
class="text-text-on-critical-base hover:bg-surface-critical-weak"
|
|
581
|
+
>
|
|
582
|
+
<DropdownMenu.ItemLabel>{language.t("dialog.server.menu.delete")}</DropdownMenu.ItemLabel>
|
|
583
|
+
</DropdownMenu.Item>
|
|
584
|
+
</DropdownMenu.Content>
|
|
585
|
+
</DropdownMenu.Portal>
|
|
586
|
+
</DropdownMenu>
|
|
587
|
+
}
|
|
588
|
+
/>
|
|
589
|
+
</Show>
|
|
590
|
+
)}
|
|
535
591
|
onSelect={(x) => {
|
|
536
592
|
if (x) select(x)
|
|
537
593
|
}}
|
|
@@ -559,57 +615,9 @@ export function DialogSelectServer() {
|
|
|
559
615
|
}
|
|
560
616
|
showCredentials
|
|
561
617
|
/>
|
|
562
|
-
<
|
|
563
|
-
<
|
|
564
|
-
|
|
565
|
-
</Show>
|
|
566
|
-
|
|
567
|
-
<Show when={i.type === "http"}>
|
|
568
|
-
<DropdownMenu>
|
|
569
|
-
<DropdownMenu.Trigger
|
|
570
|
-
as={IconButton}
|
|
571
|
-
icon="dot-grid"
|
|
572
|
-
variant="ghost"
|
|
573
|
-
class="shrink-0 size-8 hover:bg-surface-base-hover data-[expanded]:bg-surface-base-active"
|
|
574
|
-
onClick={(e: MouseEvent) => e.stopPropagation()}
|
|
575
|
-
onPointerDown={(e: PointerEvent) => e.stopPropagation()}
|
|
576
|
-
/>
|
|
577
|
-
<DropdownMenu.Portal>
|
|
578
|
-
<DropdownMenu.Content class="mt-1">
|
|
579
|
-
<DropdownMenu.Item
|
|
580
|
-
onSelect={() => {
|
|
581
|
-
if (i.type !== "http") return
|
|
582
|
-
startEdit(i)
|
|
583
|
-
}}
|
|
584
|
-
>
|
|
585
|
-
<DropdownMenu.ItemLabel>{language.t("dialog.server.menu.edit")}</DropdownMenu.ItemLabel>
|
|
586
|
-
</DropdownMenu.Item>
|
|
587
|
-
<Show when={canDefault() && defaultUrl() !== i.http.url}>
|
|
588
|
-
<DropdownMenu.Item onSelect={() => setDefault(i.http.url)}>
|
|
589
|
-
<DropdownMenu.ItemLabel>
|
|
590
|
-
{language.t("dialog.server.menu.default")}
|
|
591
|
-
</DropdownMenu.ItemLabel>
|
|
592
|
-
</DropdownMenu.Item>
|
|
593
|
-
</Show>
|
|
594
|
-
<Show when={canDefault() && defaultUrl() === i.http.url}>
|
|
595
|
-
<DropdownMenu.Item onSelect={() => setDefault(null)}>
|
|
596
|
-
<DropdownMenu.ItemLabel>
|
|
597
|
-
{language.t("dialog.server.menu.defaultRemove")}
|
|
598
|
-
</DropdownMenu.ItemLabel>
|
|
599
|
-
</DropdownMenu.Item>
|
|
600
|
-
</Show>
|
|
601
|
-
<DropdownMenu.Separator />
|
|
602
|
-
<DropdownMenu.Item
|
|
603
|
-
onSelect={() => handleRemove(ServerConnection.key(i))}
|
|
604
|
-
class="text-text-on-critical-base hover:bg-surface-critical-weak"
|
|
605
|
-
>
|
|
606
|
-
<DropdownMenu.ItemLabel>{language.t("dialog.server.menu.delete")}</DropdownMenu.ItemLabel>
|
|
607
|
-
</DropdownMenu.Item>
|
|
608
|
-
</DropdownMenu.Content>
|
|
609
|
-
</DropdownMenu.Portal>
|
|
610
|
-
</DropdownMenu>
|
|
611
|
-
</Show>
|
|
612
|
-
</div>
|
|
618
|
+
<Show when={ServerConnection.key(current()) === key}>
|
|
619
|
+
<Icon name="check" class="h-6" />
|
|
620
|
+
</Show>
|
|
613
621
|
</div>
|
|
614
622
|
)
|
|
615
623
|
}}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** @jsxImportSource solid-js */
|
|
2
|
+
import { Show, type JSX } from "solid-js"
|
|
3
|
+
|
|
4
|
+
export function ServerListItemFrame(props: { row: JSX.Element; actions?: JSX.Element }) {
|
|
5
|
+
return (
|
|
6
|
+
<div data-slot="server-list-item-frame" class="flex min-w-0 w-full items-center gap-1">
|
|
7
|
+
<div class="min-w-0 flex-1">{props.row}</div>
|
|
8
|
+
<Show when={props.actions}>
|
|
9
|
+
<div data-slot="server-list-item-actions" class="flex shrink-0 items-center justify-center pr-3">
|
|
10
|
+
{props.actions}
|
|
11
|
+
</div>
|
|
12
|
+
</Show>
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -2,7 +2,6 @@ import { createEffect, createMemo, Show, untrack } from "solid-js"
|
|
|
2
2
|
import { createStore } from "solid-js/store"
|
|
3
3
|
import { useLocation, useNavigate, useParams } from "@solidjs/router"
|
|
4
4
|
import { IconButton } from "ikanban-ui/icon-button"
|
|
5
|
-
import { Icon } from "ikanban-ui/icon"
|
|
6
5
|
import { Button } from "ikanban-ui/button"
|
|
7
6
|
import { Tooltip, TooltipKeybind } from "ikanban-ui/tooltip"
|
|
8
7
|
import { useTheme } from "ikanban-ui/theme"
|
|
@@ -47,7 +46,6 @@ export function Titlebar() {
|
|
|
47
46
|
|
|
48
47
|
const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos")
|
|
49
48
|
const windows = createMemo(() => platform.platform === "desktop" && platform.os === "windows")
|
|
50
|
-
const web = createMemo(() => platform.platform === "web")
|
|
51
49
|
const zoom = () => platform.webviewZoom?.() ?? 1
|
|
52
50
|
const minHeight = () => (mac() ? `${40 / zoom()}px` : undefined)
|
|
53
51
|
|
|
@@ -203,34 +201,6 @@ export function Titlebar() {
|
|
|
203
201
|
/>
|
|
204
202
|
</Tooltip>
|
|
205
203
|
</Show>
|
|
206
|
-
<TooltipKeybind
|
|
207
|
-
class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"}
|
|
208
|
-
placement="bottom"
|
|
209
|
-
title={language.t("command.sidebar.toggle")}
|
|
210
|
-
keybind={command.keybind("sidebar.toggle")}
|
|
211
|
-
>
|
|
212
|
-
<Button
|
|
213
|
-
variant="ghost"
|
|
214
|
-
class="group/sidebar-toggle titlebar-icon w-8 h-6 p-0 box-border"
|
|
215
|
-
onClick={layout.sidebar.toggle}
|
|
216
|
-
aria-label={language.t("command.sidebar.toggle")}
|
|
217
|
-
aria-expanded={layout.sidebar.opened()}
|
|
218
|
-
>
|
|
219
|
-
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
|
220
|
-
<Icon
|
|
221
|
-
size="small"
|
|
222
|
-
name={layout.sidebar.opened() ? "layout-left-partial" : "layout-left"}
|
|
223
|
-
class="group-hover/sidebar-toggle:hidden"
|
|
224
|
-
/>
|
|
225
|
-
<Icon size="small" name="layout-left-partial" class="hidden group-hover/sidebar-toggle:inline-block" />
|
|
226
|
-
<Icon
|
|
227
|
-
size="small"
|
|
228
|
-
name={layout.sidebar.opened() ? "layout-left" : "layout-left-partial"}
|
|
229
|
-
class="hidden group-active/sidebar-toggle:inline-block"
|
|
230
|
-
/>
|
|
231
|
-
</div>
|
|
232
|
-
</Button>
|
|
233
|
-
</TooltipKeybind>
|
|
234
204
|
<div class="hidden xl:flex items-center shrink-0">
|
|
235
205
|
<Show when={params.dir}>
|
|
236
206
|
<TooltipKeybind
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
function normalizeBase(base: string) {
|
|
2
|
+
const trimmed = base.replace(/\/+$/, "")
|
|
3
|
+
if (!trimmed) return "/"
|
|
4
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function stripBase(pathname: string, base: string) {
|
|
8
|
+
const normalizedBase = normalizeBase(base)
|
|
9
|
+
if (normalizedBase === "/") return pathname || "/"
|
|
10
|
+
if (pathname === normalizedBase) return "/"
|
|
11
|
+
if (pathname.startsWith(`${normalizedBase}/`)) return pathname.slice(normalizedBase.length) || "/"
|
|
12
|
+
return pathname || "/"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function shouldLoadProjectsOnBootstrap(pathname: string, base: string) {
|
|
16
|
+
return stripBase(pathname, base) !== "/"
|
|
17
|
+
}
|
|
@@ -39,6 +39,7 @@ export async function bootstrapGlobal(input: {
|
|
|
39
39
|
unknownError: string
|
|
40
40
|
invalidConfigurationError: string
|
|
41
41
|
formatMoreCount: (count: number) => string
|
|
42
|
+
loadProjects?: boolean
|
|
42
43
|
setGlobalStore: SetStoreFunction<GlobalStore>
|
|
43
44
|
}) {
|
|
44
45
|
const health = await input.globalSDK.global
|
|
@@ -66,16 +67,6 @@ export async function bootstrapGlobal(input: {
|
|
|
66
67
|
input.setGlobalStore("config", x.data!)
|
|
67
68
|
}),
|
|
68
69
|
),
|
|
69
|
-
retry(() =>
|
|
70
|
-
input.globalSDK.project.list().then((x) => {
|
|
71
|
-
const projects = (x.data ?? [])
|
|
72
|
-
.filter((p) => !!p?.id)
|
|
73
|
-
.filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
|
|
74
|
-
.slice()
|
|
75
|
-
.sort((a, b) => cmp(a.id, b.id))
|
|
76
|
-
input.setGlobalStore("project", projects)
|
|
77
|
-
}),
|
|
78
|
-
),
|
|
79
70
|
retry(() =>
|
|
80
71
|
input.globalSDK.provider.list().then((x) => {
|
|
81
72
|
input.setGlobalStore("provider", normalizeProviderList(x.data!))
|
|
@@ -88,6 +79,23 @@ export async function bootstrapGlobal(input: {
|
|
|
88
79
|
),
|
|
89
80
|
]
|
|
90
81
|
|
|
82
|
+
if (input.loadProjects !== false) {
|
|
83
|
+
tasks.splice(
|
|
84
|
+
2,
|
|
85
|
+
0,
|
|
86
|
+
retry(() =>
|
|
87
|
+
input.globalSDK.project.list().then((x) => {
|
|
88
|
+
const projects = (x.data ?? [])
|
|
89
|
+
.filter((p) => !!p?.id)
|
|
90
|
+
.filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
|
|
91
|
+
.slice()
|
|
92
|
+
.sort((a, b) => cmp(a.id, b.id))
|
|
93
|
+
input.setGlobalStore("project", projects)
|
|
94
|
+
}),
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
91
99
|
const results = await Promise.allSettled(tasks)
|
|
92
100
|
const errors = results.filter((r): r is PromiseRejectedResult => r.status === "rejected").map((r) => r.reason)
|
|
93
101
|
if (errors.length) {
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
loadRootSessionsWithFallback,
|
|
6
6
|
pickDirectoriesToEvict,
|
|
7
7
|
} from "./global-sync"
|
|
8
|
+
import { bootstrapGlobal } from "./global-sync/bootstrap"
|
|
9
|
+
import { shouldLoadProjectsOnBootstrap } from "./global-sync/bootstrap-mode"
|
|
8
10
|
|
|
9
11
|
describe("pickDirectoriesToEvict", () => {
|
|
10
12
|
test("keeps pinned stores and evicts idle stores", () => {
|
|
@@ -124,3 +126,58 @@ describe("canDisposeDirectory", () => {
|
|
|
124
126
|
).toBe(true)
|
|
125
127
|
})
|
|
126
128
|
})
|
|
129
|
+
|
|
130
|
+
describe("bootstrapGlobal", () => {
|
|
131
|
+
test("does not call project list when project loading is disabled", async () => {
|
|
132
|
+
let projectListCalls = 0
|
|
133
|
+
const project = {
|
|
134
|
+
list: async () => {
|
|
135
|
+
projectListCalls += 1
|
|
136
|
+
return { data: [] }
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const updates: Array<[string, unknown]> = []
|
|
141
|
+
|
|
142
|
+
await bootstrapGlobal({
|
|
143
|
+
globalSDK: {
|
|
144
|
+
global: {
|
|
145
|
+
health: async () => ({ data: { healthy: true } }),
|
|
146
|
+
config: { get: async () => ({ data: {} }) },
|
|
147
|
+
},
|
|
148
|
+
path: { get: async () => ({ data: { state: "", config: "", worktree: "", directory: "", home: "" } }) },
|
|
149
|
+
project,
|
|
150
|
+
provider: {
|
|
151
|
+
list: async () => ({ data: { all: [], connected: [], default: {} } }),
|
|
152
|
+
auth: async () => ({ data: {} }),
|
|
153
|
+
},
|
|
154
|
+
} as never,
|
|
155
|
+
connectErrorTitle: "connect",
|
|
156
|
+
connectErrorDescription: "connect desc",
|
|
157
|
+
requestFailedTitle: "request",
|
|
158
|
+
unknownError: "unknown",
|
|
159
|
+
invalidConfigurationError: "invalid",
|
|
160
|
+
formatMoreCount: () => "",
|
|
161
|
+
setGlobalStore: ((key: string, value: unknown) => {
|
|
162
|
+
updates.push([key, value])
|
|
163
|
+
return value
|
|
164
|
+
}) as never,
|
|
165
|
+
loadProjects: false,
|
|
166
|
+
} as never)
|
|
167
|
+
|
|
168
|
+
expect(projectListCalls).toBe(0)
|
|
169
|
+
expect(updates.some(([key]) => key === "ready")).toBe(true)
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
describe("shouldLoadProjectsOnBootstrap", () => {
|
|
174
|
+
test("skips project loading on the home route", () => {
|
|
175
|
+
expect(shouldLoadProjectsOnBootstrap("/", "/")).toBe(false)
|
|
176
|
+
expect(shouldLoadProjectsOnBootstrap("/ikanban", "/ikanban/")).toBe(false)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test("loads projects away from the home route", () => {
|
|
180
|
+
expect(shouldLoadProjectsOnBootstrap("/dGVzdA", "/")).toBe(true)
|
|
181
|
+
expect(shouldLoadProjectsOnBootstrap("/ikanban/dGVzdA", "/ikanban/")).toBe(true)
|
|
182
|
+
})
|
|
183
|
+
})
|
|
@@ -26,6 +26,7 @@ import { Persist, persisted } from "@/utils/persist"
|
|
|
26
26
|
import type { InitError } from "../pages/error"
|
|
27
27
|
import { useGlobalSDK } from "./global-sdk"
|
|
28
28
|
import { bootstrapDirectory, bootstrapGlobal } from "./global-sync/bootstrap"
|
|
29
|
+
import { shouldLoadProjectsOnBootstrap } from "./global-sync/bootstrap-mode"
|
|
29
30
|
import { createChildStoreManager } from "./global-sync/child-store"
|
|
30
31
|
import { applyDirectoryEvent, applyGlobalEvent } from "./global-sync/event-reducer"
|
|
31
32
|
import { createRefreshQueue } from "./global-sync/queue"
|
|
@@ -334,6 +335,10 @@ function createGlobalSync() {
|
|
|
334
335
|
unknownError: language.t("error.chain.unknown"),
|
|
335
336
|
invalidConfigurationError: language.t("error.server.invalidConfiguration"),
|
|
336
337
|
formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }),
|
|
338
|
+
loadProjects: shouldLoadProjectsOnBootstrap(
|
|
339
|
+
globalThis.location?.pathname ?? "/",
|
|
340
|
+
import.meta.env.BASE_URL ?? "/",
|
|
341
|
+
),
|
|
337
342
|
setGlobalStore: setBootStore,
|
|
338
343
|
})
|
|
339
344
|
}
|
package/src/i18n/en.ts
CHANGED
|
@@ -133,7 +133,7 @@ export const dict = {
|
|
|
133
133
|
"Customize which models appear in the model selector.",
|
|
134
134
|
"dialog.model.manage.provider.toggle": "Toggle all {{provider}} models",
|
|
135
135
|
|
|
136
|
-
"dialog.model.unpaid.freeModels.title": "Free models provided by
|
|
136
|
+
"dialog.model.unpaid.freeModels.title": "Free models provided by iKanban",
|
|
137
137
|
"dialog.model.unpaid.addMore.title": "Add more models from popular providers",
|
|
138
138
|
|
|
139
139
|
"dialog.provider.viewAll": "Show more providers",
|
|
@@ -146,7 +146,7 @@ export const dict = {
|
|
|
146
146
|
"provider.connect.status.waiting": "Waiting for authorization...",
|
|
147
147
|
"provider.connect.status.failed": "Authorization failed: {{error}}",
|
|
148
148
|
"provider.connect.apiKey.description":
|
|
149
|
-
"Enter your {{provider}} API key to connect your account and use {{provider}} models in
|
|
149
|
+
"Enter your {{provider}} API key to connect your account and use {{provider}} models in iKanban.",
|
|
150
150
|
"provider.connect.apiKey.label": "{{provider}} API key",
|
|
151
151
|
"provider.connect.apiKey.placeholder": "API key",
|
|
152
152
|
"provider.connect.apiKey.required": "API key is required",
|
|
@@ -160,7 +160,7 @@ export const dict = {
|
|
|
160
160
|
"provider.connect.oauth.code.visit.prefix": "Visit ",
|
|
161
161
|
"provider.connect.oauth.code.visit.link": "this link",
|
|
162
162
|
"provider.connect.oauth.code.visit.suffix":
|
|
163
|
-
" to collect your authorization code to connect your account and use {{provider}} models in
|
|
163
|
+
" to collect your authorization code to connect your account and use {{provider}} models in iKanban.",
|
|
164
164
|
"provider.connect.oauth.code.label": "{{method}} authorization code",
|
|
165
165
|
"provider.connect.oauth.code.placeholder": "Authorization code",
|
|
166
166
|
"provider.connect.oauth.code.required": "Authorization code is required",
|
|
@@ -168,7 +168,7 @@ export const dict = {
|
|
|
168
168
|
"provider.connect.oauth.auto.visit.prefix": "Visit ",
|
|
169
169
|
"provider.connect.oauth.auto.visit.link": "this link",
|
|
170
170
|
"provider.connect.oauth.auto.visit.suffix":
|
|
171
|
-
" and enter the code below to connect your account and use {{provider}} models in
|
|
171
|
+
" and enter the code below to connect your account and use {{provider}} models in iKanban.",
|
|
172
172
|
"provider.connect.oauth.auto.confirmationCode": "Confirmation code",
|
|
173
173
|
"provider.connect.toast.connected.title": "{{provider}} connected",
|
|
174
174
|
"provider.connect.toast.connected.description":
|
|
@@ -486,7 +486,7 @@ export const dict = {
|
|
|
486
486
|
|
|
487
487
|
"toast.update.title": "Update available",
|
|
488
488
|
"toast.update.description":
|
|
489
|
-
"A new version of
|
|
489
|
+
"A new version of iKanban ({{version}}) is now available to install.",
|
|
490
490
|
"toast.update.action.installRestart": "Install and restart",
|
|
491
491
|
"toast.update.action.notYet": "Not yet",
|
|
492
492
|
|
|
@@ -497,7 +497,7 @@ export const dict = {
|
|
|
497
497
|
"error.page.action.checking": "Checking...",
|
|
498
498
|
"error.page.action.checkUpdates": "Check for updates",
|
|
499
499
|
"error.page.action.updateTo": "Update to {{version}}",
|
|
500
|
-
"error.page.report.prefix": "Please report this error to the
|
|
500
|
+
"error.page.report.prefix": "Please report this error to the iKanban team",
|
|
501
501
|
"error.page.report.discord": "on Discord",
|
|
502
502
|
"error.page.version": "Version: {{version}}",
|
|
503
503
|
|
|
@@ -550,7 +550,7 @@ export const dict = {
|
|
|
550
550
|
"notification.session.error.fallbackDescription": "An error occurred",
|
|
551
551
|
|
|
552
552
|
"home.recentProjects": "Recent projects",
|
|
553
|
-
"home.sessionBoard": "
|
|
553
|
+
"home.sessionBoard": "Ikanban",
|
|
554
554
|
"home.sessionBoard.description":
|
|
555
555
|
"Live session activity grouped into Progress and Idle.",
|
|
556
556
|
"home.sessionBoard.progress": "Progress",
|
|
@@ -667,7 +667,7 @@ export const dict = {
|
|
|
667
667
|
"sidebar.workspaces.disable": "Disable workspaces",
|
|
668
668
|
"sidebar.gettingStarted.title": "Getting started",
|
|
669
669
|
"sidebar.gettingStarted.line1":
|
|
670
|
-
"
|
|
670
|
+
"iKanban includes free models so you can start immediately.",
|
|
671
671
|
"sidebar.gettingStarted.line2":
|
|
672
672
|
"Connect any provider to use models, inc. Claude, GPT, Gemini etc.",
|
|
673
673
|
"sidebar.project.recentSessions": "Recent sessions",
|
|
@@ -695,12 +695,12 @@ export const dict = {
|
|
|
695
695
|
|
|
696
696
|
"settings.general.row.language.title": "Language",
|
|
697
697
|
"settings.general.row.language.description":
|
|
698
|
-
"Change the display language for
|
|
698
|
+
"Change the display language for iKanban",
|
|
699
699
|
"settings.general.row.appearance.title": "Appearance",
|
|
700
700
|
"settings.general.row.appearance.description":
|
|
701
|
-
"Customise how
|
|
701
|
+
"Customise how iKanban looks on your device",
|
|
702
702
|
"settings.general.row.theme.title": "Theme",
|
|
703
|
-
"settings.general.row.theme.description": "Customise how
|
|
703
|
+
"settings.general.row.theme.description": "Customise how iKanban is themed.",
|
|
704
704
|
"settings.general.row.font.title": "Font",
|
|
705
705
|
"settings.general.row.font.description":
|
|
706
706
|
"Customise the mono font used in code blocks",
|
|
@@ -731,7 +731,7 @@ export const dict = {
|
|
|
731
731
|
|
|
732
732
|
"settings.updates.row.startup.title": "Check for updates on startup",
|
|
733
733
|
"settings.updates.row.startup.description":
|
|
734
|
-
"Automatically check for updates when
|
|
734
|
+
"Automatically check for updates when iKanban launches",
|
|
735
735
|
"settings.updates.row.check.title": "Check for updates",
|
|
736
736
|
"settings.updates.row.check.description":
|
|
737
737
|
"Manually check for updates and install if available",
|
|
@@ -739,7 +739,7 @@ export const dict = {
|
|
|
739
739
|
"settings.updates.action.checking": "Checking...",
|
|
740
740
|
"settings.updates.toast.latest.title": "You're up to date",
|
|
741
741
|
"settings.updates.toast.latest.description":
|
|
742
|
-
"You're running the latest version of
|
|
742
|
+
"You're running the latest version of iKanban.",
|
|
743
743
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
|
744
744
|
"font.option.cascadiaCode": "Cascadia Code",
|
|
745
745
|
"font.option.firaCode": "Fira Code",
|
package/src/i18n/zh.ts
CHANGED
|
@@ -145,7 +145,7 @@ export const dict = {
|
|
|
145
145
|
"dialog.model.recent": "最近",
|
|
146
146
|
"dialog.model.manage.description": "自定义模型选择器中显示的模型。",
|
|
147
147
|
"dialog.model.manage.provider.toggle": "切换 {{provider}} 的所有模型",
|
|
148
|
-
"dialog.model.unpaid.freeModels.title": "
|
|
148
|
+
"dialog.model.unpaid.freeModels.title": "iKanban 提供的免费模型",
|
|
149
149
|
"dialog.model.unpaid.addMore.title": "从热门提供商添加更多模型",
|
|
150
150
|
|
|
151
151
|
"dialog.provider.viewAll": "查看更多提供商",
|
|
@@ -158,7 +158,7 @@ export const dict = {
|
|
|
158
158
|
"provider.connect.status.waiting": "等待授权...",
|
|
159
159
|
"provider.connect.status.failed": "授权失败:{{error}}",
|
|
160
160
|
"provider.connect.apiKey.description":
|
|
161
|
-
"输入你的 {{provider}} API 密钥以连接帐户,并在
|
|
161
|
+
"输入你的 {{provider}} API 密钥以连接帐户,并在 iKanban 中使用 {{provider}} 模型。",
|
|
162
162
|
"provider.connect.apiKey.label": "{{provider}} API 密钥",
|
|
163
163
|
"provider.connect.apiKey.placeholder": "API 密钥",
|
|
164
164
|
"provider.connect.apiKey.required": "API 密钥为必填项",
|
|
@@ -172,7 +172,7 @@ export const dict = {
|
|
|
172
172
|
"provider.connect.oauth.code.visit.prefix": "访问 ",
|
|
173
173
|
"provider.connect.oauth.code.visit.link": "此链接",
|
|
174
174
|
"provider.connect.oauth.code.visit.suffix":
|
|
175
|
-
" 获取授权码,以连接你的帐户并在
|
|
175
|
+
" 获取授权码,以连接你的帐户并在 iKanban 中使用 {{provider}} 模型。",
|
|
176
176
|
"provider.connect.oauth.code.label": "{{method}} 授权码",
|
|
177
177
|
"provider.connect.oauth.code.placeholder": "授权码",
|
|
178
178
|
"provider.connect.oauth.code.required": "授权码为必填项",
|
|
@@ -180,7 +180,7 @@ export const dict = {
|
|
|
180
180
|
"provider.connect.oauth.auto.visit.prefix": "访问 ",
|
|
181
181
|
"provider.connect.oauth.auto.visit.link": "此链接",
|
|
182
182
|
"provider.connect.oauth.auto.visit.suffix":
|
|
183
|
-
" 并输入以下代码,以连接你的帐户并在
|
|
183
|
+
" 并输入以下代码,以连接你的帐户并在 iKanban 中使用 {{provider}} 模型。",
|
|
184
184
|
"provider.connect.oauth.auto.confirmationCode": "确认码",
|
|
185
185
|
"provider.connect.toast.connected.title": "{{provider}} 已连接",
|
|
186
186
|
"provider.connect.toast.connected.description":
|
|
@@ -461,7 +461,7 @@ export const dict = {
|
|
|
461
461
|
"toast.session.unshare.failed.description": "取消分享会话时发生错误",
|
|
462
462
|
"toast.session.listFailed.title": "无法加载 {{project}} 的会话",
|
|
463
463
|
"toast.update.title": "有可用更新",
|
|
464
|
-
"toast.update.description": "
|
|
464
|
+
"toast.update.description": "iKanban 有新版本 ({{version}}) 可安装。",
|
|
465
465
|
"toast.update.action.installRestart": "安装并重启",
|
|
466
466
|
"toast.update.action.notYet": "稍后",
|
|
467
467
|
|
|
@@ -472,7 +472,7 @@ export const dict = {
|
|
|
472
472
|
"error.page.action.checking": "检查中...",
|
|
473
473
|
"error.page.action.checkUpdates": "检查更新",
|
|
474
474
|
"error.page.action.updateTo": "更新到 {{version}}",
|
|
475
|
-
"error.page.report.prefix": "请将此错误报告给
|
|
475
|
+
"error.page.report.prefix": "请将此错误报告给 iKanban 团队",
|
|
476
476
|
"error.page.report.discord": "在 Discord 上",
|
|
477
477
|
"error.page.version": "版本:{{version}}",
|
|
478
478
|
"error.dev.rootNotFound":
|
|
@@ -520,7 +520,7 @@ export const dict = {
|
|
|
520
520
|
"notification.session.error.fallbackDescription": "发生错误",
|
|
521
521
|
|
|
522
522
|
"home.recentProjects": "最近项目",
|
|
523
|
-
"home.sessionBoard": "
|
|
523
|
+
"home.sessionBoard": "Ikanban",
|
|
524
524
|
"home.sessionBoard.description": "按 Progress 和 Idle 分组。",
|
|
525
525
|
"home.sessionBoard.progress": "Progress",
|
|
526
526
|
"home.sessionBoard.idle": "Idle",
|
|
@@ -624,7 +624,7 @@ export const dict = {
|
|
|
624
624
|
"sidebar.workspaces.enable": "启用工作区",
|
|
625
625
|
"sidebar.workspaces.disable": "禁用工作区",
|
|
626
626
|
"sidebar.gettingStarted.title": "入门",
|
|
627
|
-
"sidebar.gettingStarted.line1": "
|
|
627
|
+
"sidebar.gettingStarted.line1": "iKanban 提供免费模型,你可以立即开始使用。",
|
|
628
628
|
"sidebar.gettingStarted.line2":
|
|
629
629
|
"连接任意提供商即可使用更多模型,如 Claude、GPT、Gemini 等。",
|
|
630
630
|
"sidebar.project.recentSessions": "最近会话",
|
|
@@ -652,12 +652,12 @@ export const dict = {
|
|
|
652
652
|
"settings.general.section.feed": "动态",
|
|
653
653
|
"settings.general.section.display": "显示",
|
|
654
654
|
"settings.general.row.language.title": "语言",
|
|
655
|
-
"settings.general.row.language.description": "更改
|
|
655
|
+
"settings.general.row.language.description": "更改 iKanban 的显示语言",
|
|
656
656
|
"settings.general.row.appearance.title": "外观",
|
|
657
657
|
"settings.general.row.appearance.description":
|
|
658
|
-
"自定义
|
|
658
|
+
"自定义 iKanban 在你的设备上的外观",
|
|
659
659
|
"settings.general.row.theme.title": "主题",
|
|
660
|
-
"settings.general.row.theme.description": "自定义
|
|
660
|
+
"settings.general.row.theme.description": "自定义 iKanban 的主题。",
|
|
661
661
|
"settings.general.row.font.title": "字体",
|
|
662
662
|
"settings.general.row.font.description": "自定义代码块使用的等宽字体",
|
|
663
663
|
"settings.general.row.reasoningSummaries.title": "显示推理摘要",
|
|
@@ -682,14 +682,14 @@ export const dict = {
|
|
|
682
682
|
"settings.general.action.releaseNotesCurrent": "查看变更",
|
|
683
683
|
|
|
684
684
|
"settings.updates.row.startup.title": "启动时检查更新",
|
|
685
|
-
"settings.updates.row.startup.description": "在
|
|
685
|
+
"settings.updates.row.startup.description": "在 iKanban 启动时自动检查更新",
|
|
686
686
|
"settings.updates.row.check.title": "检查更新",
|
|
687
687
|
"settings.updates.row.check.description": "手动检查更新并在有更新时安装",
|
|
688
688
|
"settings.updates.action.checkNow": "立即检查",
|
|
689
689
|
"settings.updates.action.checking": "正在检查...",
|
|
690
690
|
"settings.updates.toast.latest.title": "已是最新版本",
|
|
691
691
|
"settings.updates.toast.latest.description":
|
|
692
|
-
"你正在使用最新版本的
|
|
692
|
+
"你正在使用最新版本的 iKanban。",
|
|
693
693
|
|
|
694
694
|
"font.option.ibmPlexMono": "IBM Plex Mono",
|
|
695
695
|
"font.option.cascadiaCode": "Cascadia Code",
|