@chatcode/cco-better-sidebar 0.18.0-rc.6 → 0.18.0-rc.7
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 +1 -1
- package/README_EN.md +1 -1
- package/lib/client-editor.js +277 -265
- package/lib/client-mermaid.js +276 -264
- package/lib/client-registry.js +476 -413
- package/lib/client-terminal.js +281 -269
- package/lib/client.js +478 -415
- package/lib/types/client/locales.d.ts +4 -0
- package/package.json +1 -1
- package/src/client/PluginMarketView.module.css +1 -0
- package/src/client/PluginMarketView.tsx +36 -9
- package/src/client/locales-ja.ts +4 -0
- package/src/client/locales.ts +12 -0
|
@@ -40,6 +40,10 @@ export declare const zh: {
|
|
|
40
40
|
pluginMarketChooseProject: string;
|
|
41
41
|
pluginMarketProjectHint: string;
|
|
42
42
|
pluginMarketChoosingDirectory: string;
|
|
43
|
+
pluginMarketAwaitingConfirmation: string;
|
|
44
|
+
pluginMarketConfirmProjectInstall: string;
|
|
45
|
+
pluginMarketConfirmProjectDescription: string;
|
|
46
|
+
pluginMarketConfirmInstall: string;
|
|
43
47
|
pluginMarketInstalledAt: string;
|
|
44
48
|
pluginMarketInstallPath: string;
|
|
45
49
|
pluginMarketDirectoryPath: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatcode/cco-better-sidebar",
|
|
3
|
-
"version": "0.18.0-rc.
|
|
3
|
+
"version": "0.18.0-rc.7",
|
|
4
4
|
"description": "ChatCode CLI Web plugin: a VS Code-like right sidebar (explorer / editor / terminal / git / browser), isolated per conversation session. Exposes a service for other plugins to register sidebar tabs and file viewers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
.projectRow > button { flex-shrink: 0; }
|
|
10
10
|
.installPath { margin: 4px 0; overflow-wrap: anywhere; color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 18px; }
|
|
11
11
|
.projectPicker { width: min(620px, calc(100vw - 32px)); }
|
|
12
|
+
.confirmPath { margin: 0; padding: 12px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; color: var(--dsw-alias-label-primary); overflow-wrap: anywhere; white-space: pre-wrap; }
|
|
12
13
|
.directoryPath { display: flex; align-items: center; gap: 8px; }
|
|
13
14
|
.directoryPath > span { flex: 1; min-width: 0; }
|
|
14
15
|
.directoryCrumbs { display: flex; flex-wrap: wrap; gap: 4px; margin: 12px 0; }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Two catalog levels with project/user actions directly on plugin cards. */
|
|
2
2
|
import { useEffect, useRef, useState, useSyncExternalStore } from 'react'
|
|
3
|
-
import { Button, Input, Pill } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
3
|
+
import { Button, Input, Modal, Pill } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
4
4
|
import { api, type SessionScope } from './api.ts'
|
|
5
5
|
import { t } from './locales.ts'
|
|
6
6
|
import { IconChevronLeftOutline14, IconChevronRightOutline14, IconRefreshOutline14 } from './icons.tsx'
|
|
@@ -9,6 +9,8 @@ import type { InstalledMarketplacePlugin, MarketplacePlugin, PluginMarketScope }
|
|
|
9
9
|
import css from './PluginMarketView.module.css'
|
|
10
10
|
import { PluginProjectPicker, type PluginProjectRequest } from './PluginProjectPicker.tsx'
|
|
11
11
|
|
|
12
|
+
interface ProjectInstallConfirmation { name: string; path: string; finish: (confirmed: boolean) => void }
|
|
13
|
+
|
|
12
14
|
export function PluginMarketView({ scope, visible, catalog }: { scope: SessionScope; visible: boolean; catalog: PluginMarketCatalog }): JSX.Element {
|
|
13
15
|
const snapshot = useSyncExternalStore(catalog.subscribe, catalog.getSnapshot)
|
|
14
16
|
const [marketId, setMarketId] = useState<string | null>(null)
|
|
@@ -16,7 +18,9 @@ export function PluginMarketView({ scope, visible, catalog }: { scope: SessionSc
|
|
|
16
18
|
const [query, setQuery] = useState('')
|
|
17
19
|
const [recordState, setRecordState] = useState<{ key: string; records: InstalledMarketplacePlugin[]; ready: boolean; error: string }>()
|
|
18
20
|
const [message, setMessage] = useState<{ text: string; error: boolean }>()
|
|
19
|
-
const [working, setWorking] = useState<{ id: string; name: string; scope: PluginMarketScope; uninstall: boolean;
|
|
21
|
+
const [working, setWorking] = useState<{ id: string; name: string; scope: PluginMarketScope; uninstall: boolean; confirming?: boolean }>()
|
|
22
|
+
const [confirmation, setConfirmation] = useState<ProjectInstallConfirmation>()
|
|
23
|
+
const confirmationRef = useRef<ProjectInstallConfirmation>()
|
|
20
24
|
const [selection, setSelection] = useState<{ key: string; path: string }>()
|
|
21
25
|
const [picker, setPicker] = useState<PluginProjectRequest>()
|
|
22
26
|
const pickerRef = useRef<PluginProjectRequest>()
|
|
@@ -37,7 +41,7 @@ export function PluginMarketView({ scope, visible, catalog }: { scope: SessionSc
|
|
|
37
41
|
const market = markets.find(item => item.id === marketId)
|
|
38
42
|
const catalogEntry = tab === 'markets' && marketId ? snapshot.plugins[marketId] : snapshot.markets
|
|
39
43
|
const loading = snapshot.refresh?.status === 'loading' || (tab === 'markets' && (!catalogEntry || catalogEntry.status === 'loading'))
|
|
40
|
-
const operating = !!working && !working.
|
|
44
|
+
const operating = !!working && !working.confirming
|
|
41
45
|
const blocked = loading || snapshot.refresh?.status === 'error' || !!working
|
|
42
46
|
|
|
43
47
|
useEffect(() => {
|
|
@@ -49,9 +53,15 @@ export function PluginMarketView({ scope, visible, catalog }: { scope: SessionSc
|
|
|
49
53
|
useEffect(() => { if (focusBack.current) { breadcrumbNode.current?.querySelector('button')?.focus(); focusBack.current = false } }, [marketId])
|
|
50
54
|
|
|
51
55
|
useEffect(() => { setMessage(undefined) }, [scopeKey])
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
confirmationRef.current?.finish(false)
|
|
58
|
+
confirmationRef.current = undefined
|
|
59
|
+
setConfirmation(undefined)
|
|
60
|
+
}, [scopeKey, visible])
|
|
52
61
|
useEffect(() => () => {
|
|
53
62
|
pickerRef.current?.controller.abort()
|
|
54
63
|
pickerRef.current?.finish(null)
|
|
64
|
+
confirmationRef.current?.finish(false)
|
|
55
65
|
}, [])
|
|
56
66
|
useEffect(() => {
|
|
57
67
|
if (!visible) return
|
|
@@ -88,6 +98,17 @@ export function PluginMarketView({ scope, visible, catalog }: { scope: SessionSc
|
|
|
88
98
|
pickerRef.current = undefined
|
|
89
99
|
setPicker(undefined)
|
|
90
100
|
}
|
|
101
|
+
const confirmProjectInstall = (name: string, path: string): Promise<boolean> => new Promise(finish => {
|
|
102
|
+
const request = { name, path, finish }
|
|
103
|
+
confirmationRef.current = request
|
|
104
|
+
setConfirmation(request)
|
|
105
|
+
})
|
|
106
|
+
const finishConfirmation = (confirmed: boolean): void => {
|
|
107
|
+
const request = confirmationRef.current
|
|
108
|
+
confirmationRef.current = undefined
|
|
109
|
+
setConfirmation(undefined)
|
|
110
|
+
request?.finish(confirmed)
|
|
111
|
+
}
|
|
91
112
|
const chooseProject = async (): Promise<void> => {
|
|
92
113
|
if (pending.current || blocked) return
|
|
93
114
|
pending.current = true
|
|
@@ -106,18 +127,20 @@ export function PluginMarketView({ scope, visible, catalog }: { scope: SessionSc
|
|
|
106
127
|
const key = scopeKey
|
|
107
128
|
const record = installed.find(item => item.id === plugin.id && item.scope === installScope)
|
|
108
129
|
const present = !!record
|
|
130
|
+
if (installScope === 'project' && !present && !projectPath) {
|
|
131
|
+
setMessage({ text: t('pluginMarketProjectHint'), error: true })
|
|
132
|
+
return
|
|
133
|
+
}
|
|
109
134
|
pending.current = true
|
|
110
135
|
mutationVersion.current++
|
|
111
136
|
const operation = { id: plugin.id, name: plugin.name, scope: installScope, uninstall: present }
|
|
112
|
-
setWorking({ ...operation,
|
|
137
|
+
setWorking({ ...operation, confirming: installScope === 'project' && !present })
|
|
113
138
|
setMessage(undefined)
|
|
114
139
|
try {
|
|
115
140
|
let target = record?.scope === 'project' ? record.projectPath : projectPath
|
|
116
141
|
if (installScope === 'project' && !present) {
|
|
117
|
-
const
|
|
118
|
-
if (!
|
|
119
|
-
target = chosen
|
|
120
|
-
setSelection({ key, path: target })
|
|
142
|
+
const confirmed = await confirmProjectInstall(plugin.name, target!)
|
|
143
|
+
if (!confirmed || latestScope.current !== key) return
|
|
121
144
|
setWorking(operation)
|
|
122
145
|
}
|
|
123
146
|
let installedPath = ''
|
|
@@ -203,7 +226,7 @@ export function PluginMarketView({ scope, visible, catalog }: { scope: SessionSc
|
|
|
203
226
|
const busy = working?.id === item.id && working.scope === installScope
|
|
204
227
|
return <Button key={installScope} size="sm" variant={installScope === 'project' && !present ? 'primary' : 'outline'} className={installScope === 'project' && !present ? css.projectInstall : undefined} disabled={blocked || !!working || !recordsReady} onClick={() => { void perform(item, installScope) }}>
|
|
205
228
|
{busy && operating ? <span className={css.spinning} aria-hidden="true"><IconRefreshOutline14 /></span> : null}
|
|
206
|
-
{t(busy ? working?.
|
|
229
|
+
{t(busy ? working?.confirming ? 'pluginMarketAwaitingConfirmation' : working?.uninstall ? 'pluginMarketUninstalling' : 'pluginMarketInstalling' : present ? 'pluginMarketUninstall' : 'pluginMarketInstall')} · {t(installScope === 'project' ? 'pluginMarketProject' : 'pluginMarketUser')}
|
|
207
230
|
</Button>
|
|
208
231
|
})}
|
|
209
232
|
</div>
|
|
@@ -211,6 +234,10 @@ export function PluginMarketView({ scope, visible, catalog }: { scope: SessionSc
|
|
|
211
234
|
})}
|
|
212
235
|
</div>
|
|
213
236
|
{!loading && hasData && (showMarkets ? filteredMarkets : filteredPlugins).length === 0 ? <p className={css.state}>{t('pluginMarketEmpty')}</p> : null}
|
|
237
|
+
{confirmation ? <Modal open title={t('pluginMarketConfirmProjectInstall')} description={t('pluginMarketConfirmProjectDescription', { name: confirmation.name })} closeLabel={t('cancel')} onClose={() => finishConfirmation(false)} className={css.projectPicker}
|
|
238
|
+
footer={<><Button variant="outline" data-modal-autofocus onClick={() => finishConfirmation(false)}>{t('cancel')}</Button><Button variant="primary" onClick={() => finishConfirmation(true)}>{t('pluginMarketConfirmInstall')}</Button></>}>
|
|
239
|
+
<p className={css.confirmPath}>{confirmation.path}</p>
|
|
240
|
+
</Modal> : null}
|
|
214
241
|
{picker ? <PluginProjectPicker request={picker} initialPath={projectPath} onPicked={finishPicker} onCancel={() => finishPicker(null)} onError={text => { setMessage({ text, error: true }); finishPicker(null) }} /> : null}
|
|
215
242
|
</section>
|
|
216
243
|
}
|
package/src/client/locales-ja.ts
CHANGED
|
@@ -34,6 +34,10 @@ export const ja: Record<string, string> = {
|
|
|
34
34
|
pluginMarketChooseProject: 'プロジェクトディレクトリを選択',
|
|
35
35
|
pluginMarketProjectHint: 'プロジェクトにインストールする場合は対象フォルダーを選択してください。',
|
|
36
36
|
pluginMarketChoosingDirectory: 'ディレクトリを選択中…',
|
|
37
|
+
pluginMarketAwaitingConfirmation: '確認待ち…',
|
|
38
|
+
pluginMarketConfirmProjectInstall: 'プロジェクトへのインストールを確認',
|
|
39
|
+
pluginMarketConfirmProjectDescription: '{name} を以下のプロジェクトディレクトリにインストールします。変更する場合はキャンセルし、ページ上部でディレクトリを選択してください。',
|
|
40
|
+
pluginMarketConfirmInstall: 'インストールを確認',
|
|
37
41
|
pluginMarketInstalledAt: 'プラグインのインストール先:{path}',
|
|
38
42
|
pluginMarketInstallPath: '{scope}のインストール先:{path}',
|
|
39
43
|
pluginMarketDirectoryPath: 'ディレクトリパス',
|
package/src/client/locales.ts
CHANGED
|
@@ -41,6 +41,10 @@ export const zh = {
|
|
|
41
41
|
pluginMarketChooseProject: '选择项目目录',
|
|
42
42
|
pluginMarketProjectHint: '安装到项目时,请先选择目标项目文件夹。',
|
|
43
43
|
pluginMarketChoosingDirectory: '正在选择目录…',
|
|
44
|
+
pluginMarketAwaitingConfirmation: '等待确认…',
|
|
45
|
+
pluginMarketConfirmProjectInstall: '确认项目级安装',
|
|
46
|
+
pluginMarketConfirmProjectDescription: '将把 {name} 安装到以下项目目录。如需更改,请取消后在顶部选择项目目录。',
|
|
47
|
+
pluginMarketConfirmInstall: '确认安装',
|
|
44
48
|
pluginMarketInstalledAt: '插件已安装到:{path}',
|
|
45
49
|
pluginMarketInstallPath: '{scope}安装目录:{path}',
|
|
46
50
|
pluginMarketDirectoryPath: '目录路径',
|
|
@@ -547,6 +551,10 @@ export const en: Record<keyof typeof zh, string> = {
|
|
|
547
551
|
pluginMarketChooseProject: 'Choose project directory',
|
|
548
552
|
pluginMarketProjectHint: 'Choose a target project folder when installing for a project.',
|
|
549
553
|
pluginMarketChoosingDirectory: 'Choosing directory…',
|
|
554
|
+
pluginMarketAwaitingConfirmation: 'Awaiting confirmation…',
|
|
555
|
+
pluginMarketConfirmProjectInstall: 'Confirm project installation',
|
|
556
|
+
pluginMarketConfirmProjectDescription: '{name} will be installed in the project directory below. To change it, cancel and choose a project directory at the top of the page.',
|
|
557
|
+
pluginMarketConfirmInstall: 'Confirm installation',
|
|
550
558
|
pluginMarketInstalledAt: 'Plugin installed at: {path}',
|
|
551
559
|
pluginMarketInstallPath: '{scope} installation directory: {path}',
|
|
552
560
|
pluginMarketDirectoryPath: 'Directory path',
|
|
@@ -1083,6 +1091,10 @@ const accountAndModelFallback = {
|
|
|
1083
1091
|
pluginMarketChooseProject: en.pluginMarketChooseProject,
|
|
1084
1092
|
pluginMarketProjectHint: en.pluginMarketProjectHint,
|
|
1085
1093
|
pluginMarketChoosingDirectory: en.pluginMarketChoosingDirectory,
|
|
1094
|
+
pluginMarketAwaitingConfirmation: en.pluginMarketAwaitingConfirmation,
|
|
1095
|
+
pluginMarketConfirmProjectInstall: en.pluginMarketConfirmProjectInstall,
|
|
1096
|
+
pluginMarketConfirmProjectDescription: en.pluginMarketConfirmProjectDescription,
|
|
1097
|
+
pluginMarketConfirmInstall: en.pluginMarketConfirmInstall,
|
|
1086
1098
|
pluginMarketInstalledAt: en.pluginMarketInstalledAt,
|
|
1087
1099
|
pluginMarketInstallPath: en.pluginMarketInstallPath,
|
|
1088
1100
|
pluginMarketDirectoryPath: en.pluginMarketDirectoryPath,
|