@linxin666/dsh-client-ui-community-plugins 0.1.18
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/LICENSE +29 -0
- package/README.i18n.yaml +6 -0
- package/README.md +43 -0
- package/README.zh.md +43 -0
- package/cordis.patch.yml +13 -0
- package/lib/client.js +920 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +29 -0
- package/package.json +67 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":["css","css"],"sources":["../../../packages/dsh-community-plugins/src/client/PluginSettingsCard.tsx","../../../packages/dsh-community-plugins/src/client/settings-form.ts","../../../packages/dsh-community-plugins/src/client/generated/community.ts","../../../packages/dsh-community-plugins/src/client/community-guard.ts","../../../packages/dsh-community-plugins/src/client/CommunityPluginsCard.tsx","../../../packages/dsh-community-plugins/src/client/locales.ts","../../../packages/dsh-community-plugins/src/client/index.ts"],"sourcesContent":["// Generated by scripts/sync-shared.mjs from shared/client/settings/PluginSettingsCard.tsx. Do not edit this copy; edit the shared source and run \"node scripts/sync-shared.mjs\".\n/**\n * Family-shared chrome for plugin settings cards: a disclosure header naming\n * the plugin and what its settings govern, the controls inside, and the save\n * that writes them. Renders nothing while the namespace is unavailable — a\n * deployment that does not compose the owning plugin should show no trace of\n * it. Inlined into each consumer's client bundle; mirrors the official\n * ui-plugin-config PluginCard in a self-contained slice.\n */\n\nimport { useState, type ReactNode } from 'react'\nimport type { CardShell } from './settings-form.ts'\nimport css from './settings-card.module.css'\n\n/** Copy keys the card chrome itself reads; every consumer locale carries this shared vocabulary. */\nexport const CARD_COPY_KEYS = [\n 'settings.collapse',\n 'settings.expand',\n 'settings.notExposed',\n 'settings.unsaved',\n 'settings.readOnly',\n 'settings.saveFailed',\n 'settings.discard',\n 'settings.save',\n 'settings.saving',\n] as const\n\n/** Copy key the card chrome itself reads. */\nexport type CardCopyKey = (typeof CARD_COPY_KEYS)[number]\n\n/** Key domain for the plugin's own copy (inferred per consumer). */\nexport type SettingsCardKey<TKey extends string = string> = TKey | CardCopyKey\n\n/** Card chrome shared by every plugin settings card. */\nexport interface PluginSettingsCardProps<TKey extends string = string> {\n /** Locale reader for this card's copy. */\n t: (key: SettingsCardKey<TKey>, params?: Record<string, string | number>) => string\n /** Locale key of the plugin's name. */\n titleKey: TKey\n /** Locale key of the line describing what this plugin's settings govern. */\n descriptionKey: TKey\n /** The card's form state: availability, writability, and what a save would do. */\n state: CardShell\n /** Write every staged edit. */\n onSave: () => void\n /** Drop every staged edit. */\n onDiscard: () => void\n /**\n * Render the controls expanded on arrival (still collapsible). Defaults to\n * true: promoted first-level sections show their settings immediately.\n */\n defaultOpen?: boolean\n /**\n * Render without the collapse affordance: a static header and an always\n * visible body. Used by first-level settings sections whose nav entry\n * already provides the selection.\n */\n alwaysOpen?: boolean\n /** The plugin's controls. */\n children: ReactNode\n}\n\n/**\n * Render one plugin settings card.\n * @param props - the plugin's copy keys, its form state, and its controls.\n * @returns the card, or nothing while the namespace is still loading.\n */\nexport function PluginSettingsCard<TKey extends string = string>(props: PluginSettingsCardProps<TKey>) {\n const [open, setOpen] = useState(props.defaultOpen ?? true)\n const { state, alwaysOpen } = props\n if (!state.available) return null\n const title = props.t(props.titleKey)\n const description = props.t(props.descriptionKey)\n const blocked = !state.dirty || state.invalid || state.saving\n const expanded = alwaysOpen === true || open\n const cardClass = expanded ? `${css.cardOpen} ${css.card}` : css.card\n // With alwaysOpen the nav entry already provides the selection, so the\n // header is a static title row instead of a disclosure button.\n const header = alwaysOpen === true\n ? (\n <div className={css.headerStatic}>\n <span className={css.headText}>\n <span className={css.name} title={title}>{title}</span>\n <span className={css.description} title={description}>{description}</span>\n </span>\n {state.dirty ? <span className={css.pending} title={props.t('settings.unsaved')}>{props.t('settings.unsaved')}</span> : null}\n </div>\n )\n : (\n <button\n type=\"button\"\n className={css.header}\n aria-expanded={open}\n aria-label={`${props.t(open ? 'settings.collapse' : 'settings.expand')}: ${title}`}\n onClick={() => { setOpen(!open) }}\n >\n <span className={css.headText}>\n <span className={css.name} title={title}>{title}</span>\n <span className={css.description} title={description}>{description}</span>\n </span>\n {state.dirty ? <span className={css.pending} title={props.t('settings.unsaved')}>{props.t('settings.unsaved')}</span> : null}\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 14 14\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={open ? `${css.chevron} ${css.chevronOpen}` : css.chevron}\n >\n <path\n d=\"M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z\"\n fill=\"currentColor\"\n />\n </svg>\n </button>\n )\n // The namespace exists but the Host does not serve it to this client (the\n // official settings allowlist omits third-party namespaces): show a card\n // that explains the gap instead of vanishing, so a missing card never\n // reads as a missing plugin.\n if (!state.exposed) {\n return (\n <li className={cardClass}>\n {header}\n {expanded\n ? (\n <div className={css.body}>\n <p className={css.notExposed} role=\"status\">{props.t('settings.notExposed')}</p>\n </div>\n )\n : null}\n </li>\n )\n }\n return (\n <li className={cardClass}>\n {header}\n {expanded\n ? (\n <div className={css.body}>\n {!state.writable ? <p className={css.readOnly} role=\"status\">{props.t('settings.readOnly')}</p> : null}\n {props.children}\n <div className={css.footer}>\n {state.failed\n ? (\n <p className={css.failed} role=\"status\">\n {props.t('settings.saveFailed')}{state.failedReason ? ' - ' + state.failedReason : ''}\n </p>\n )\n : null}\n <button\n type=\"button\"\n className={css.discard}\n disabled={!state.dirty || state.saving}\n onClick={props.onDiscard}\n >\n {props.t('settings.discard')}\n </button>\n <button\n type=\"button\"\n className={css.save}\n disabled={blocked}\n onClick={props.onSave}\n >\n {props.t(!state.saving ? 'settings.save' : 'settings.saving')}\n </button>\n </div>\n </div>\n )\n : null}\n </li>\n )\n}\n\n/** Props every field control needs regardless of its value type. */\nexport interface FieldProps {\n /** Stable id associating the label with its control. */\n id: string\n /** Visible label. */\n label: string\n /** One-line explanation rendered under the control. */\n hint: string\n /** Draft text this control renders. */\n text: string\n /** True when saving would leave a user-layer entry for this field. */\n overridden: boolean\n /** True when the draft is not a value this field accepts. */\n invalid: boolean\n /** Copy for the overridden badge. */\n overriddenLabel: string\n /** Copy for the reset control. */\n resetLabel: string\n /** Copy shown in place of the hint while the draft is invalid. */\n invalidLabel: string\n /** Disables every control (read-only document, or an unavailable namespace). */\n disabled: boolean\n /** Stage draft text. */\n onEdit: (text: string) => void\n /** Stage a clear so the field re-inherits the composition layer. */\n onReset: () => void\n}\n\n/** A staged value field. `numeric` only hints the keypad: which drafts a field accepts is decided by its spec. */\nexport function ValueField(props: FieldProps & {\n /** Hints a numeric keypad without narrowing what the control accepts. */\n numeric?: boolean\n /** Placeholder shown while the draft is empty. */\n placeholder?: string\n}) {\n return (\n <div className={css.field}>\n <div className={css.head}>\n <label className={css.label} htmlFor={props.id}>{props.label}</label>\n {props.overridden\n ? (\n <span className={css.badges}>\n <span className={css.badge}>{props.overriddenLabel}</span>\n <button\n type=\"button\"\n className={css.reset}\n disabled={props.disabled}\n onClick={props.onReset}\n >\n {props.resetLabel}\n </button>\n </span>\n )\n : null}\n </div>\n <input\n id={props.id}\n className={props.invalid ? css.inputInvalid : css.input}\n type=\"text\"\n {...props.numeric === true ? { inputMode: 'numeric' as const } : {}}\n {...props.invalid ? { 'aria-invalid': true } : {}}\n value={props.text}\n placeholder={props.placeholder ?? ''}\n disabled={props.disabled}\n onChange={(event) => { props.onEdit(event.target.value) }}\n />\n <p className={props.invalid ? css.invalid : css.hint}>\n {props.invalid ? props.invalidLabel : props.hint}\n </p>\n </div>\n )\n}\n\n/** A staged boolean field: 继承 / 开 / 关. */\nexport function BooleanField(props: FieldProps & {\n /** Copy for the inherit option. */\n inheritLabel: string\n /** Copy for the on option. */\n onLabel: string\n /** Copy for the off option. */\n offLabel: string\n}) {\n return (\n <div className={css.field}>\n <div className={css.head}>\n <label className={css.label} htmlFor={props.id}>{props.label}</label>\n {props.overridden\n ? (\n <span className={css.badges}>\n <span className={css.badge}>{props.overriddenLabel}</span>\n <button\n type=\"button\"\n className={css.reset}\n disabled={props.disabled}\n onClick={props.onReset}\n >\n {props.resetLabel}\n </button>\n </span>\n )\n : null}\n </div>\n <select\n id={props.id}\n className={css.select}\n value={props.text}\n disabled={props.disabled}\n onChange={(event) => { props.onEdit(event.target.value) }}\n >\n <option value=\"\">{props.inheritLabel}</option>\n <option value=\"true\">{props.onLabel}</option>\n <option value=\"false\">{props.offLabel}</option>\n </select>\n <p className={css.hint}>{props.hint}</p>\n </div>\n )\n}\n\n/** A staged enumerated field rendered as a select. */\nexport function ChoiceField(props: FieldProps & {\n /** Copy for the inherit option (draft text is the empty string). */\n inheritLabel: string\n /** Choices rendered in order; `value` is the draft/stored text. */\n choices: ReadonlyArray<{ value: string; label: string }>\n}) {\n return (\n <div className={css.field}>\n <div className={css.head}>\n <label className={css.label} htmlFor={props.id}>{props.label}</label>\n {props.overridden\n ? (\n <span className={css.badges}>\n <span className={css.badge}>{props.overriddenLabel}</span>\n <button\n type=\"button\"\n className={css.reset}\n disabled={props.disabled}\n onClick={props.onReset}\n >\n {props.resetLabel}\n </button>\n </span>\n )\n : null}\n </div>\n <select\n id={props.id}\n className={css.select}\n value={props.text}\n disabled={props.disabled}\n onChange={(event) => { props.onEdit(event.target.value) }}\n >\n <option value=\"\">{props.inheritLabel}</option>\n {props.choices.map(choice => (\n <option key={choice.value} value={choice.value}>{choice.label}</option>\n ))}\n </select>\n <p className={props.invalid ? css.invalid : css.hint}>\n {props.invalid ? props.invalidLabel : props.hint}\n </p>\n </div>\n )\n}\n","// Generated by scripts/sync-shared.mjs from shared/client/settings/settings-form.ts. Do not edit this copy; edit the shared source and run \"node scripts/sync-shared.mjs\".\n/**\n * Staged form model behind the plugin settings card. A card stages what the\n * user types and writes it only when they save — the settings write is a\n * durable, revision-fenced document mutation, so staging keeps what is on\n * screen exactly what a save would store. Family-shared slice inlined into\n * each plugin's client bundle; mirrors the official ui-plugin-config\n * card-store pattern.\n */\n\nimport type { SettingsScope, SettingsScopeSnapshot, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'\nimport { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'\n\n/** The write one field's staged text performs when the card is saved. */\nexport type FieldWrite =\n | { kind: 'set'; value: unknown }\n | { kind: 'clear' }\n\n/** How one field converts between its stored value and its draft text. */\nexport interface FieldSpec {\n /** Field name inside the namespace section. */\n field: string\n /**\n * Whether the Host treats this field as a secret and redacts its value from\n * the read-back (role('secret') in the section schema). Redacted secrets are\n * never compared against the draft on save; the field lands when the scope\n * reports the write succeeded (its secret-set marker under the bridge), so\n * a successful secret save is not misreported as failed.\n */\n secret?: boolean\n /** Render a stored value as draft text; the empty string when the section carries none. */\n format: (value: unknown) => string\n /**\n * The write this draft text stages, or undefined when the text is not a\n * value this field accepts — which blocks the save rather than discarding it.\n */\n parse: (text: string) => FieldWrite | undefined\n}\n\n/** One field as the card renders it. */\nexport interface FieldState {\n /** Draft text the control renders. */\n text: string\n /** Whether saving would leave a user-layer entry for this field. */\n overridden: boolean\n /** Whether the draft is not a value this field accepts, which blocks saving. */\n invalid: boolean\n}\n\n/** Form state every plugin settings card shares. */\nexport interface CardShell {\n /** False while the namespace is still loading; the card renders nothing. */\n available: boolean\n /**\n * Whether the namespace is actually served to this client. False when the\n * Host deployment does not expose it (e.g. the official apiproxy settings\n * allowlist omits third-party namespaces): the card renders an explanation\n * instead of its form, so a missing namespace never looks like a missing\n * plugin.\n */\n exposed: boolean\n /** Whether the Host document accepts writes. */\n writable: boolean\n /** Whether the form holds edits that a save would write. */\n dirty: boolean\n /** Whether any staged draft is invalid, which blocks the save. */\n invalid: boolean\n /** Whether a save is crossing the wire. */\n saving: boolean\n /** Whether the last save did not land as staged; cleared by the next edit or save. */\n failed: boolean\n /**\n * The rejection code/message the Host returned for the last failed save,\n * surfaced next to the generic failure text. Undefined while no save has\n * failed (or the failure carried no server reason).\n */\n failedReason?: string\n}\n\n/** The write actions the card's slot entry injects. */\nexport interface CardActions {\n /** Stage draft text for one field. */\n edit: (field: string, text: string) => void\n /** Stage a clear, so saving lets the field re-inherit the composition layer. */\n resetField: (field: string) => void\n /** Write every staged edit, then re-seed from what the Host accepted. */\n save: () => void\n /** Drop every staged edit. */\n discard: () => void\n}\n\n/** One field's staged edit. */\ninterface StagedEdit {\n /** Draft text the control renders. */\n text: string\n /** True when this edit clears the field whatever text it shows. */\n clear: boolean\n}\n\n/** One staged edit resolved into the write a save performs. */\ninterface PlannedWrite {\n /** Field this entry writes. */\n field: string\n /** The durable write this entry performs, described for a batched scope. */\n op: BatchedWrite\n /** Perform the write and report whether the Host holds the staged value afterwards. */\n run: (() => Promise<boolean>) | undefined\n}\n\n/** One durable write a batched settings scope performs. */\nexport interface BatchedWrite {\n /** Field this entry writes. */\n field: string\n /** set stores a value; unset drops the leaf. */\n op: 'set' | 'unset'\n /** Value for op set (absent for unset). */\n value?: unknown\n}\n\n/** Per-field outcome of one batched scope write. */\nexport interface BatchedFieldResult {\n /** Field this entry writes. */\n field: string\n /** Whether the Host accepted this field's write (per the read-back view). */\n landed: boolean\n}\n\n/**\n * Result of a batched scope write. The bridge scope posts every planned write\n * in one /mutate so the Host validate hook judges baseURL+model together; a\n * batched refusal fails the whole save rather than per-field.\n */\nexport interface BatchResult {\n /** Whether the whole mutate was accepted. */\n ok: boolean\n /** Per-field success, in the request order (always present when ok). */\n fields: BatchedFieldResult[]\n /** Host rejection code (mutate refused). */\n code?: string\n /** Host rejection message (mutate refused). */\n message?: string\n}\n\n/** The optional batch surface the bridge scope adds over the SettingsScope contract. */\ninterface BatchedSettingsScope {\n /** Write every operation in one scope mutation, reporting per-field success. */\n mutate: (writes: BatchedWrite[]) => Promise<BatchResult>\n}\n\n/** Constraints a numeric field's accepted drafts must satisfy, mirroring the host schema. */\nexport interface NumberConstraints {\n /** The accepted value must be a whole number. */\n integer?: boolean\n /** The accepted value must be at least this. */\n min?: number\n}\n\n/** A whole- or decimal-number field. An empty draft clears the field; any other draft that is not a finite number within the constraints blocks the save. */\nexport function numberField(field: string, constraints: NumberConstraints = {}): FieldSpec {\n const { integer = false, min } = constraints\n return {\n field,\n format: value => typeof value === 'number' ? String(value) : '',\n parse: (text) => {\n const trimmed = text.trim()\n if (trimmed === '') return { kind: 'clear' }\n const parsed = Number(trimmed)\n if (!Number.isFinite(parsed)) return undefined\n if (integer && !Number.isInteger(parsed)) return undefined\n if (min !== undefined && parsed < min) return undefined\n return { kind: 'set', value: parsed }\n },\n }\n}\n\n/** A free-text field. An empty draft clears the field. */\nexport function textField(field: string): FieldSpec {\n return {\n field,\n format: value => typeof value === 'string' ? value : '',\n parse: (text) => {\n const trimmed = text.trim()\n return trimmed === '' ? { kind: 'clear' } : { kind: 'set', value: trimmed }\n },\n }\n}\n\n/**\n * A free-text field the Host treats as a secret and redacts from the read-back\n * (role('secret') in the section schema). The card still edits it like text,\n * but a save never compares the redacted value back and relies on the scope\n * reporting the write landed.\n */\nexport function secretField(field: string): FieldSpec {\n return { ...textField(field), secret: true }\n}\n\n/** A boolean field, edited through true/false draft text. */\nexport function booleanField(field: string): FieldSpec {\n return {\n field,\n format: value => typeof value === 'boolean' ? String(value) : '',\n parse: (text) => {\n const trimmed = text.trim()\n if (trimmed === '') return { kind: 'clear' }\n if (trimmed === 'true') return { kind: 'set', value: true }\n if (trimmed === 'false') return { kind: 'set', value: false }\n return undefined\n },\n }\n}\n\n/** An enumerated string field; only the listed choices are accepted. An empty draft clears the field. */\nexport function choiceField(field: string, choices: readonly string[]): FieldSpec {\n return {\n field,\n format: value => typeof value === 'string' && choices.includes(value) ? value : '',\n parse: (text) => {\n if (text === '') return { kind: 'clear' }\n return choices.includes(text) ? { kind: 'set', value: text } : undefined\n },\n }\n}\n\n/**\n * Stages one card's edits over one settings namespace and writes them on save.\n *\n * The Host is the only authority on whether a value was accepted — its\n * validators own the constraints no schema can express — so the outcome is\n * read back from the section rather than predicted here. A save that did not\n * land keeps its drafts, so the user can correct them instead of retyping.\n */\nexport class CardForm<T> {\n private readonly specs: Map<string, FieldSpec>\n private readonly staged = new Map<string, StagedEdit>()\n private readonly listeners = new Set<() => void>()\n private saving = false\n private failed = false\n private failedReason: string | undefined\n\n /** @param scope - the bound settings scope for this card's namespace. */\n constructor(\n private readonly scope: SettingsScope<T>,\n specs: FieldSpec[],\n ) {\n this.specs = new Map(specs.map(spec => [spec.field, spec]))\n scope.subscribe(() => { this.publish() })\n }\n\n /** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */\n bind<S>(project: () => S): SnapshotStore<S> {\n const store = createSnapshotStore(project())\n this.listeners.add(() => { store.set(project()) })\n return store\n }\n\n /** Read the card-level state: what the Host serves, and what a save would do. */\n shell(): CardShell {\n const snapshot = this.scope.getSnapshot()\n const plan = this.plan()\n return {\n available: snapshot.status !== 'loading',\n exposed: snapshot.status === 'ready',\n writable: snapshot.writable,\n dirty: plan.length > 0,\n invalid: plan.some(item => item.run === undefined),\n saving: this.saving,\n failed: this.failed,\n ...this.failedReason === undefined ? {} : { failedReason: this.failedReason },\n }\n }\n\n /** Read one field's state from the effective section and its staged draft. */\n field(field: string): FieldState {\n const spec = this.specOf(field)\n const staged = this.staged.get(field)\n if (staged === undefined) {\n return { text: spec.format(this.sectionValue(field)), overridden: this.stored(field), invalid: false }\n }\n const write = staged.clear ? { kind: 'clear' as const } : spec.parse(staged.text)\n return {\n text: staged.text,\n overridden: write?.kind === 'set',\n invalid: write === undefined,\n }\n }\n\n /** The actions the card's slot registration injects. */\n actions(): CardActions {\n return {\n edit: (field, text) => { this.stage(field, { text, clear: false }) },\n resetField: (field) => {\n this.stage(field, { text: this.specOf(field).format(this.baseValue(field)), clear: true })\n },\n save: () => { void this.save() },\n discard: () => {\n if (this.staged.size === 0 && !this.failed) return\n this.staged.clear()\n this.failed = false\n this.failedReason = undefined\n this.publish()\n },\n }\n }\n\n /**\n * Write every staged edit, then re-seed from what the Host accepted.\n *\n * When the scope carries the optional batch surface (the dsh-web-ui\n * bridge scope), every planned write rides one mutation so cross-field\n * validate hooks (baseURL+model) judge the batch as a unit instead of\n * deadlocking on per-field writes. Otherwise the per-field loop runs.\n * A field lands only when the Host reports it held the staged value; a\n * landed field's draft is dropped, a failed one stays staged for the user.\n * @returns settlement after every write and the read-back.\n */\n async save(): Promise<void> {\n const plan = this.plan()\n const valid = plan.filter(item => item.run !== undefined)\n if (plan.length === 0 || this.saving || valid.length !== plan.length) return\n const plannedWrites = valid.map(item => item.op)\n // Snapshot the fields this save writes, so edits staged while it is in\n // flight survive: only the staged keys this save actually wrote are cleared.\n const fields = new Set(plan.map(item => item.field))\n this.saving = true\n this.failed = false\n this.failedReason = undefined\n this.publish()\n const landed = new Set<string>()\n const batch = this.batchedScope()\n if (batch !== undefined) {\n const result = await batch.mutate(plannedWrites)\n if (result.ok) {\n for (const field of result.fields) {\n if (field.landed) landed.add(field.field)\n }\n } else {\n this.failedReason = result.message\n }\n } else {\n for (const item of valid) {\n if (await item.run!()) landed.add(item.field)\n }\n }\n for (const field of fields) {\n if (landed.has(field)) this.staged.delete(field)\n }\n this.saving = false\n this.failed = landed.size !== fields.size\n this.publish()\n }\n\n /** The scope's batch surface when it supports one; undefined conservatively otherwise. */\n private batchedScope(): BatchedSettingsScope | undefined {\n const candidate = this.scope as unknown as BatchedSettingsScope | undefined\n return typeof candidate?.mutate === 'function' ? candidate : undefined\n }\n\n /**\n * Every staged edit a save would write. An entry whose draft is not a value\n * its field accepts carries no write: the form is still dirty, and the save\n * refuses rather than dropping the edit. A staged edit that matches the\n * effective section is not a write at all.\n * @returns the planned writes, in the order the fields were staged.\n */\n private plan(): PlannedWrite[] {\n const plan: PlannedWrite[] = []\n for (const [field, staged] of this.staged) {\n const spec = this.specOf(field)\n if (staged.clear) {\n if (this.stored(field)) plan.push({ field, op: { field, op: 'unset' }, run: () => this.clear(field) })\n continue\n }\n if (staged.text === spec.format(this.sectionValue(field))) continue\n const write = spec.parse(staged.text)\n if (write === undefined) plan.push({ field, op: { field, op: 'unset' }, run: undefined })\n else if (write.kind === 'clear') plan.push({ field, op: { field, op: 'unset' }, run: () => this.clear(field) })\n else plan.push({ field, op: { field, op: 'set', value: write.value }, run: () => this.store(field, write.value) })\n }\n return plan\n }\n\n private async clear(field: string): Promise<boolean> {\n await this.scope.unset(field)\n return !this.stored(field)\n }\n\n private async store(field: string, value: unknown): Promise<boolean> {\n await this.scope.set(field, value)\n // A redacted secret never appears in the user layer read-back; judging it\n // by value would misreport a successful secret save as failed. The bridge\n // reports secret writes through its secret-set markers (batch path); on\n // the per-field path the scope resolved, so the write is landed.\n if (this.specOf(field).secret) return true\n return this.userLayer()?.[field] === value\n }\n\n private stage(field: string, edit: StagedEdit): void {\n this.staged.set(field, edit)\n this.failed = false\n this.failedReason = undefined\n this.publish()\n }\n\n private specOf(field: string): FieldSpec {\n const spec = this.specs.get(field)\n // Every call site names a field this card declared; a missing one is a\n // wiring mistake that must not degrade into a silently inert control.\n if (spec === undefined) throw new Error(`settings card has no field ${field}`)\n return spec\n }\n\n private snapshotOf(): SettingsScopeSnapshot<T> {\n return this.scope.getSnapshot()\n }\n\n private sectionValue(field: string): unknown {\n return (this.snapshotOf().value as Record<string, unknown> | undefined)?.[field]\n }\n\n private baseValue(field: string): unknown {\n return (this.snapshotOf().base as Record<string, unknown> | undefined)?.[field]\n }\n\n private userLayer(): Record<string, unknown> | undefined {\n return this.snapshotOf().user as Record<string, unknown> | undefined\n }\n\n private stored(field: string): boolean {\n const user = this.userLayer()\n return user !== undefined && Object.hasOwn(user, field)\n }\n\n private publish(): void {\n for (const listener of this.listeners) listener()\n }\n}\n","/**\n * AUTO-GENERATED by scripts/community-index — do not edit by hand.\n * Community plugin index for the settings page: every entry links to the\n * contributor's own repository. This repository never vendors the listed\n * plugins — it only points users at them.\n * Regenerate with `node scripts/community-index`.\n */\n\n/** One community plugin entry: contributor metadata plus the repository link. */\nexport interface CommunityPluginEntry {\n /** Stable entry id (unique in community.json). */\n id: string\n /** Display name (zh). */\n name: string\n /** Display name (en). */\n nameEn: string\n /** Contributor (GitHub user or organization). */\n author: string\n /** One-line description (zh). */\n description?: string\n /** One-line description (en). */\n descriptionEn?: string\n /** The contributor's own repository URL. */\n repo: string\n /** npm package name, when the plugin is published there. */\n npm?: string\n}\n\n/** Every community plugin, in community.json order. */\nexport const COMMUNITY_PLUGINS: readonly CommunityPluginEntry[] = [\n {\"id\":\"dsh-data-agent\",\"name\":\"Data Agent\",\"nameEn\":\"Data Agent\",\"author\":\"omdsh-dev\",\"repo\":\"https://github.com/omdsh-dev/dsh-data-agent\",\"description\":\"为 DSH 定义专用 Data Agent 预设,让 AI 帮你查询、更新、分析数据。\",\"descriptionEn\":\"Defines a dedicated Data Agent preset for DSH so the AI can query, update and analyze data.\"},\n {\"id\":\"dsh-tui\",\"name\":\"dsh-TUI\",\"nameEn\":\"dsh-TUI\",\"author\":\"ccch1mneyyy\",\"repo\":\"https://github.com/ccch1mneyyy/dsh-TUI\",\"description\":\"Claude Code 风格全屏交互终端插件:像素鲸鱼顶栏、实时工作状态行、思考流式展开、双击 Esc 回滚、上下文进度条与 TPS 仪表。\",\"descriptionEn\":\"A Claude Code style fullscreen interactive terminal plugin: pixel-whale header, live working-state line, streaming reasoning expansion, double-Esc rollback, context progress bar and TPS gauges.\"},\n {\"id\":\"dsh-tianshu-tui\",\"name\":\"天书 TUI\",\"nameEn\":\"Tianshu TUI\",\"author\":\"huiliyi37\",\"repo\":\"https://github.com/huiliyi37/dsh-tianshu-tui\",\"description\":\"基于官方 DeepSeek Harness 的交互式终端 UI 插件,在官方基础上增加 TDD 与证据门等工作流。\",\"descriptionEn\":\"An interactive terminal UI plugin for DeepSeek Harness that adds TDD and evidence-gate workflows on top of the official base.\"},\n {\"id\":\"dsh-chat-summary\",\"name\":\"Chat Summary\",\"nameEn\":\"Chat Summary\",\"author\":\"v833\",\"repo\":\"https://github.com/v833/dsh-chat-summary\",\"description\":\"总结当前对话并导出为 Markdown / DOCX / PDF,可选 LLM 智能总结(用户自配 API Key)。\",\"descriptionEn\":\"Summarize the current conversation and export it as Markdown / DOCX / PDF, with optional LLM summarization using your own API key.\",\"npm\":\"@linxin666/dsh-client-ui-chat-summary\"},\n {\"id\":\"dsh-builtin-toggles\",\"name\":\"内置能力检查器\",\"nameEn\":\"Built-in Capability Inspector\",\"author\":\"Starfie1d1272\",\"repo\":\"https://github.com/Starfie1d1272/dsh-builtin-toggles\",\"description\":\"Evidence-backed 内置 capability Inspector:展示 DSH Web built-in capability 的 provenance、compatibility 与 structural drift;仅对 9 个经过审阅的 UI leaves 提供 fail-closed 开关。\",\"descriptionEn\":\"Evidence-backed built-in capability Inspector: surfaces provenance, compatibility and structural drift of DSH Web built-ins, with fail-closed toggles for only the nine reviewed UI leaves.\",\"npm\":\"dsh-builtin-toggles\"},\n {\"id\":\"dsh-pilot\",\"name\":\"Pilot 浏览器驾驶舱\",\"nameEn\":\"Pilot Browser Cockpit\",\"author\":\"guo6x\",\"repo\":\"https://github.com/guo6x/dsh-pilot\",\"description\":\"给 agent 一双会开车的手:零依赖 CDP 浏览器操控(8 个 pilot_* 工具:导航/点击/输入/按键/JS/截图)+ Web GUI 可拖拽驾驶舱面板,无需 Playwright、无需 API key。\",\"descriptionEn\":\"Give your agent hands: zero-dependency CDP browser control (8 pilot_* tools: navigate/click/type/keys/eval/screenshot) plus a draggable cockpit panel in the Web GUI - no Playwright, no API key.\"},\n {\"id\":\"dsh-housekeeper\",\"name\":\"环境管家\",\"nameEn\":\"Environment Housekeeper\",\"author\":\"guo6x\",\"repo\":\"https://github.com/guo6x/dsh-housekeeper\",\"description\":\"管住 agent 的脏手:工具链台账(node/pnpm/git/gh/ffmpeg 等自动探测)、缓存与临时目录扫描 + 白名单安全一键清理、机器规则 AGENTS.md 查看编辑,全在设置面板完成。\",\"descriptionEn\":\"Keep your agent's hands clean: toolchain inventory, scratch/cache scan with whitelist-guarded one-click cleanup, and the machine rules file (AGENTS.md) view/edit - all in the settings panel.\"},\n {\"id\":\"dsh-deepread\",\"name\":\"DeepRead 精读助手\",\"nameEn\":\"DeepRead Assistant\",\"author\":\"xiehuan123\",\"repo\":\"https://github.com/xiehuan123/dsh-deepread\",\"description\":\"五种模式精读插件(quick / deep / map / feynman / book),支持公众号链接与文件输入,导出 md / mm / html,Web UI 提供工具结果卡片与精读面板。\",\"descriptionEn\":\"A five-mode deep reading plugin (quick / deep / map / feynman / book) for links and files, with md / mm / html exports and Web UI tool-result cards plus a reading panel.\",\"npm\":\"dsh-deepread\"},\n {\"id\":\"dsh-mnemon\",\"name\":\"Mnemon 记忆系统\",\"nameEn\":\"Mnemon Memory\",\"author\":\"omdsh-dev\",\"repo\":\"https://github.com/omdsh-dev/dsh-mnemon\",\"description\":\"与 Mnemon CLI 集成的跨 Agent、本地优先持久记忆插件:用户画像 / 工作记忆 / 项目档案与长期 Memory Spaces,支持导入导出。\",\"descriptionEn\":\"A cross-agent, local-first persistent memory plugin integrating the Mnemon CLI: profiles, working memory, project documents and long-term Memory Spaces, with import and export.\",\"npm\":\"dsh-mnemon\"},\n]\n","/**\n * Runtime guard for the generated community index entries. The index is\n * build-time generated data, but the card renders whatever the module\n * carries; a hand-written narrowing keeps one malformed entry from breaking\n * the whole list at render time.\n */\n\nimport type { CommunityPluginEntry } from './generated/community.ts'\n\n/** True when the value is a well-formed community plugin entry. */\nexport function isCommunityPluginEntry(value: unknown): value is CommunityPluginEntry {\n if (typeof value !== 'object' || value === null) return false\n const entry = value as Record<string, unknown>\n if (typeof entry.id !== 'string' || entry.id === '') return false\n if (typeof entry.name !== 'string' || typeof entry.nameEn !== 'string') return false\n if (typeof entry.author !== 'string' || entry.author === '') return false\n if (typeof entry.repo !== 'string' || !entry.repo.startsWith('https://')) return false\n if (entry.description !== undefined && typeof entry.description !== 'string') return false\n if (entry.descriptionEn !== undefined && typeof entry.descriptionEn !== 'string') return false\n if (entry.npm !== undefined && typeof entry.npm !== 'string') return false\n return true\n}\n","/**\n * The community plugin index card: a first-level settings section that is\n * always open (a static header with the index list directly visible). Its own\n * enable switch (backed by the community-plugins settings namespace) gates\n * the entry list; the list itself points at contributors' own repositories —\n * this package only indexes them, it never vendors their code.\n */\n\nimport { useState, type ReactNode } from 'react'\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\n// Type-only: pulls the settings-surface SlotMap merge (the 'settings.section' entry).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type { SettingsScope, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'\nimport { PluginSettingsCard, BooleanField } from './PluginSettingsCard.tsx'\nimport { CardForm, booleanField, type CardActions, type CardShell, type FieldState as CardFieldState } from './settings-form.ts'\nimport type { CommunityPluginKey } from './locales.ts'\nimport { COMMUNITY_PLUGINS, type CommunityPluginEntry } from './generated/community.ts'\nimport { isCommunityPluginEntry } from './community-guard.ts'\nimport css from './community.module.css'\n\n/** The settings fields this card edits (the namespace's full schema). */\nexport interface CommunityPluginsSettings {\n /** Master switch for the index card. */\n enabled?: boolean\n}\n\n/** What the community plugin card renders. */\nexport interface CommunityPluginsCardState extends CardShell {\n /** Master switch. */\n enabled: CardFieldState\n}\n\n/** The registration-side face the card's slot entry injects. */\nexport interface CommunityPluginsCardFace extends CardActions {\n hooks: {\n /** Card snapshot bound by the renderer as useCommunityPluginsCard. */\n communityPluginsCard: SnapshotStore<CommunityPluginsCardState>\n }\n}\n\n/** Bridges the community-plugins scope onto the card's staged form. */\nexport class CommunityPluginsCardController {\n private readonly form: CardForm<CommunityPluginsSettings>\n private readonly store: SnapshotStore<CommunityPluginsCardState>\n\n /** @param scope - the bound settings scope for the community-plugins namespace. */\n constructor(scope: SettingsScope<CommunityPluginsSettings>) {\n this.form = new CardForm(scope, [\n booleanField('enabled'),\n ])\n this.store = this.form.bind(() => this.projection())\n }\n\n private projection(): CommunityPluginsCardState {\n return {\n ...this.form.shell(),\n enabled: this.form.field('enabled'),\n }\n }\n\n /**\n * Build the face the card's slot registration injects.\n * @returns the card's snapshot and its form actions.\n */\n inject(): CommunityPluginsCardFace {\n return { hooks: { communityPluginsCard: this.store }, ...this.form.actions() }\n }\n}\n\n/** The one-line install command for an entry: npm package when published, else the contributor repository URL. */\nfunction installCommand(entry: CommunityPluginEntry): string {\n return `dsh plugin --profile web add ${entry.npm ?? entry.repo}`\n}\n\n/** Props the renderer binds for the community plugin card. */\nexport type CommunityPluginsCardProps =\n PropsLocale<'community-plugins'>\n & InjectFace<CommunityPluginsCardFace>\n & {\n /** Index entries; defaults to the generated registry (injected for tests). */\n plugins?: readonly CommunityPluginEntry[]\n }\n\n/**\n * Render the community plugin index card.\n * @param props - locale copy, the card snapshot, its form actions, and the\n * (default-generated) entry list.\n * @returns the card.\n */\nexport function CommunityPluginsCard(props: CommunityPluginsCardProps): ReactNode {\n const { t } = props\n const state = props.useCommunityPluginsCard(snapshot => snapshot)\n const plugins = (props.plugins ?? COMMUNITY_PLUGINS).filter(isCommunityPluginEntry)\n const [copiedId, setCopiedId] = useState<string | null>(null)\n const copyCommand = (id: string, command: string): void => {\n const mark = (): void => { setCopiedId(id) }\n const clipboard = typeof navigator !== 'undefined' ? navigator.clipboard : undefined\n if (clipboard?.writeText !== undefined) {\n void clipboard.writeText(command).then(mark, mark)\n return\n }\n // Fallback for browsers without the async clipboard API (or tests).\n try {\n const area = document.createElement('textarea')\n area.value = command\n area.setAttribute('readonly', '')\n area.style.position = 'fixed'\n area.style.opacity = '0'\n document.body.append(area)\n area.select()\n document.execCommand('copy')\n area.remove()\n } catch { /* the command stays visible to select by hand */ }\n mark()\n }\n const disabled = !state.writable\n // The draft text drives the list: '' (inherit) and 'true' keep it visible,\n // 'false' hides it until the switch is turned back on.\n const visible = state.enabled.text !== 'false'\n const fieldProps = {\n overriddenLabel: t('settings.overridden'),\n resetLabel: t('settings.reset'),\n invalidLabel: t('settings.invalidNumber'),\n disabled,\n }\n return (\n <PluginSettingsCard\n t={t}\n titleKey=\"settings.title\"\n descriptionKey=\"settings.description\"\n state={state}\n alwaysOpen\n onSave={props.save}\n onDiscard={props.discard}\n >\n <BooleanField\n id=\"settings-community-enabled\"\n label={t('settings.enabled')}\n hint={t('settings.enabledHint')}\n inheritLabel={t('settings.inherit')}\n onLabel={t('settings.on')}\n offLabel={t('settings.off')}\n {...fieldProps}\n {...state.enabled}\n onEdit={(text) => { props.edit('enabled', text) }}\n onReset={() => { props.resetField('enabled') }}\n />\n {visible\n ? (\n <ul className={css.entries}>\n {plugins.length === 0\n ? <li className={css.empty} role=\"status\">{t('empty')}</li>\n : plugins.map((plugin) => (\n <li key={plugin.id} className={css.entry}>\n <span className={css.entryHead}>\n <span className={css.entryName} title={plugin.name}>{plugin.name}</span>\n <span className={css.entryAuthor} title={plugin.author}>{t('author')}: {plugin.author}</span>\n </span>\n {plugin.description ? <p className={css.entryDescription}>{plugin.description}</p> : null}\n {plugin.descriptionEn ? <p className={css.entryDescriptionEn}>{plugin.descriptionEn}</p> : null}\n <span className={css.entryLinks}>\n <a className={css.entryLink} href={plugin.repo} target=\"_blank\" rel=\"noreferrer\">{t('repository')}</a>\n {plugin.npm ? <code className={css.entryNpm}>{plugin.npm}</code> : null}\n </span>\n <span className={css.entryInstall}>\n <code className={css.entryCommand}>{installCommand(plugin)}</code>\n <button\n type=\"button\"\n className={css.copyButton}\n onClick={() => { copyCommand(plugin.id, installCommand(plugin)) }}\n >\n {copiedId === plugin.id ? t('copied') : t('copy')}\n </button>\n </span>\n </li>\n ))}\n </ul>\n )\n : <p className={css.off} role=\"status\">{t('off')}</p>}\n <p className={css.installNote} role=\"note\">{t('installHint')}</p>\n <p className={css.notice} role=\"note\">{t('notice')}</p>\n </PluginSettingsCard>\n )\n}\n\n/** Props the settings section binds for the community plugin page. */\nexport type CommunityPluginsSectionProps =\n PropsRuntime<'settings.section'>\n & PropsLocale<'community-plugins'>\n & InjectFace<CommunityPluginsCardFace>\n & {\n /** Index entries; defaults to the generated registry (injected for tests). */\n plugins?: readonly CommunityPluginEntry[]\n }\n\n/** Render the community plugin index as a first-level settings page. */\nexport function CommunityPluginsSection(props: CommunityPluginsSectionProps): ReactNode {\n const { t, useCommunityPluginsCard, save, discard, edit, resetField, plugins } = props\n return (\n <ul className={css.sectionList}>\n <CommunityPluginsCard t={t} useCommunityPluginsCard={useCommunityPluginsCard} save={save} discard={discard} edit={edit} resetField={resetField} plugins={plugins} />\n </ul>\n )\n}\n","/**\n * The community-plugins locale dictionaries for the index card.\n */\n\n/** Simplified Chinese dictionary (the key-set source of truth). */\nexport const zh = {\n 'settings.title': '社区插件',\n 'settings.description': '社区贡献者开发与维护的插件,链接指向作者自己的仓库。',\n 'settings.enabled': '启用社区插件索引',\n 'settings.enabledHint': '此开关只控制索引列表的显示与否,关闭后在这里重新打开。',\n 'settings.inherit': '继承',\n 'settings.on': '开',\n 'settings.off': '关',\n 'settings.overridden': '已覆盖',\n 'settings.reset': '恢复默认',\n 'settings.notExposed': '当前 DSH 版本未向设置页暴露本插件的配置命名空间,表单不可用。可编辑 ~/.dsh/settings.yaml 直接配置,或为 dsh-host-apiproxy 的 WEB_SETTINGS_NAMESPACES 白名单补充本命名空间后重启。',\n 'settings.readOnly': '当前部署的设置只读。',\n 'settings.expand': '展开设置',\n 'settings.collapse': '收起设置',\n 'settings.save': '保存',\n 'settings.saving': '保存中…',\n 'settings.discard': '放弃',\n 'settings.unsaved': '未保存',\n 'settings.saveFailed': '部署未接受这些值,已保留供你修改。',\n 'settings.invalidNumber': '请输入数字,留空则使用默认值。',\n 'author': '作者',\n 'repository': '仓库',\n 'copy': '复制',\n 'copied': '已复制',\n 'installHint': '索引只登记、不安装代码。要运行某个插件,复制它的安装命令到终端执行;安装后,插件自带的开关与配置(若有)会出现在插件配置里。',\n 'empty': '暂无社区插件登记。',\n 'off': '社区插件索引已关闭。',\n 'notice': '条目由贡献者自行登记,与 dsh-web-ui 的发布内容无关;使用前请自行评估。',\n} satisfies Record<string, string>\n\n/** Key union for this namespace. */\nexport type CommunityPluginKey = keyof typeof zh\n\n/** English dictionary, checked complete against the zh key set. */\nexport const en = {\n 'settings.title': 'Community Plugins',\n 'settings.description': \"Plugins developed and maintained by community contributors, linking to each author's own repository.\",\n 'settings.enabled': 'Enable the community plugin index',\n 'settings.enabledHint': 'This switch only controls whether the index list is shown; turn it back on here.',\n 'settings.inherit': 'Inherit',\n 'settings.on': 'On',\n 'settings.off': 'Off',\n 'settings.overridden': 'Overridden',\n 'settings.reset': 'Reset to default',\n 'settings.notExposed': \"This DSH version does not expose this plugin's settings namespace to the configuration page, so the form is unavailable. Edit ~/.dsh/settings.yaml directly, or add the namespace to dsh-host-apiproxy's WEB_SETTINGS_NAMESPACES allowlist and restart.\",\n 'settings.readOnly': 'This deployment stores settings read-only.',\n 'settings.expand': 'Show settings',\n 'settings.collapse': 'Hide settings',\n 'settings.save': 'Save',\n 'settings.saving': 'Saving…',\n 'settings.discard': 'Discard',\n 'settings.unsaved': 'Unsaved',\n 'settings.saveFailed': 'The deployment did not accept these values; they were left for you to correct.',\n 'settings.invalidNumber': 'Enter a number, or leave blank to use the default.',\n 'author': 'Author',\n 'repository': 'Repository',\n 'copy': 'Copy',\n 'copied': 'Copied',\n 'installHint': 'The index only registers entries, it never installs code. To run a plugin, copy its install command into a terminal; once installed, the plugin provides its own switch and config (if any) in the plugin configuration section.',\n 'empty': 'No community plugins registered yet.',\n 'off': 'The community plugin index is turned off.',\n 'notice': 'Entries are contributed by their authors and are separate from dsh-web-ui releases; evaluate before use.',\n} satisfies Record<CommunityPluginKey, string>\n","/**\n * Community plugin index, browser half. Registers the community-plugins\n * dictionaries and one section into the settings panel's first-level nav\n * (settings.section; the promoted family sections sit beside the built-in\n * general / models / plugins / agent-presets entries). The card carries its\n * own enable switch (backed by the community-plugins settings namespace) and\n * lists community plugins with links to each contributor's own repository.\n * @module @linxin666/dsh-client-ui-community-plugins/client\n */\n\nimport type { ClientContext, SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-runtime/client'\n// Type-only: pulls the locale plugin's Context merge (ctx.locale).\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the settings-surface Context merge (ctx.settingsScope).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\n// Type-only: pulls the slot-surface types (the settings.section seat).\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport { CommunityPluginsCardController, CommunityPluginsSection, type CommunityPluginsSettings } from './CommunityPluginsCard.tsx'\nimport { en, zh, type CommunityPluginKey } from './locales.ts'\n\nexport type { CommunityPluginsCardProps, CommunityPluginsSectionProps } from './CommunityPluginsCard.tsx'\n\n/** Settings namespace the card's enable switch edits (the Host plugin registers it). */\nconst COMMUNITY_PLUGINS_NS = 'community-plugins'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Community plugin index card copy. */\n 'community-plugins': CommunityPluginKey\n }\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n /**\n * Optional rc.6 compatibility binder provided by dsh-web-ui-settings;\n * absent when that group plugin is not installed, so callers fall back to\n * the official settings scope.\n */\n webUiSettings?: { bind<S>(spec: SettingsScopeSpec<S>): SettingsScope<S> }\n }\n}\n\n/** Required services. */\nexport const inject = ['slots', 'locale', 'connection', 'settingsScope', 'remote']\n\n/**\n * Register the community plugin index as a first-level settings section, with\n * its own enable switch over the community-plugins settings namespace.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register('community-plugins', { zh, en }), 'community-plugins: dictionaries')\n\n const binder = ctx.get('webUiSettings') ?? ctx.settingsScope\n const settingsScope = binder.bind<CommunityPluginsSettings>({ namespace: COMMUNITY_PLUGINS_NS })\n const controller = new CommunityPluginsCardController(settingsScope)\n\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'community-plugins',\n order: 140,\n label: () => ctx.locale.bind('community-plugins')('settings.title'),\n locale: 'community-plugins',\n inject: () => controller.inject(),\n }, CommunityPluginsSection))\n}\n"],"mappings":"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"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
2
|
+
import z from "schemastery";
|
|
3
|
+
//#region src/index.ts
|
|
4
|
+
/** Stable cordis plugin name (matches cordis.patch.yml insert id). */
|
|
5
|
+
const name = "ui-community-plugins";
|
|
6
|
+
/** Services the settings registration needs (the settings seam is optional). */
|
|
7
|
+
const inject = [];
|
|
8
|
+
/**
|
|
9
|
+
* Settings namespace of the card's enable switch — the section the web
|
|
10
|
+
* settings surface edits. Spelled here rather than imported: the browser half
|
|
11
|
+
* spells the same value and must not depend on a Host package.
|
|
12
|
+
*/
|
|
13
|
+
const COMMUNITY_PLUGINS_SETTINGS_NAMESPACE = settingsNamespace("community-plugins");
|
|
14
|
+
const Config = z.object({ enabled: z.boolean().default(true) });
|
|
15
|
+
/**
|
|
16
|
+
* Register the community-plugins settings namespace. The application of the
|
|
17
|
+
* value is browser-side (the card hides its list while off), so the hooks
|
|
18
|
+
* only keep the source reachable; installSettingsSection is a no-op when no
|
|
19
|
+
* settings service is mounted (pure community-card installs skip it).
|
|
20
|
+
* @param ctx - cordis context.
|
|
21
|
+
*/
|
|
22
|
+
function apply(ctx) {
|
|
23
|
+
installSettingsSection(ctx, COMMUNITY_PLUGINS_SETTINGS_NAMESPACE, Config, {}, {
|
|
24
|
+
setSource: () => {},
|
|
25
|
+
onChange: () => {}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { COMMUNITY_PLUGINS_SETTINGS_NAMESPACE, Config, apply, inject, name };
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@linxin666/dsh-client-ui-community-plugins",
|
|
3
|
+
"description": "Community plugin index card for the dsh web GUI settings page: lists community-contributed plugins with links to each contributor's own repository, gated by its own enable switch in the plugin configuration section.",
|
|
4
|
+
"version": "0.1.18",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./src/*": "./src/*",
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"dsh": {
|
|
14
|
+
"bundle": {
|
|
15
|
+
"patch": "./cordis.patch.yml"
|
|
16
|
+
},
|
|
17
|
+
"client": {
|
|
18
|
+
"inject": [
|
|
19
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
20
|
+
"@deepseek-ai/dsh-client-connection",
|
|
21
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
22
|
+
],
|
|
23
|
+
"platform": "web"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"license": "BSD-3-Clause",
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"react": "^18.2.0"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"schemastery": "^3.18.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
35
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6",
|
|
36
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
|
|
37
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
|
|
38
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
|
|
39
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
|
|
40
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
|
|
41
|
+
"@testing-library/react": "^16.1.0",
|
|
42
|
+
"@types/node": "^22.20.0",
|
|
43
|
+
"@types/react": "~18.3.1",
|
|
44
|
+
"@types/react-dom": "^18.3.5",
|
|
45
|
+
"jsdom": "^26.0.0",
|
|
46
|
+
"lightningcss": "1.32.0",
|
|
47
|
+
"react": "^18.3.1",
|
|
48
|
+
"react-dom": "^18.3.1",
|
|
49
|
+
"tsdown": "0.22.2",
|
|
50
|
+
"typescript": "~5.7.2",
|
|
51
|
+
"vitest": "^4.1.1"
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"lib",
|
|
55
|
+
"cordis.patch.yml"
|
|
56
|
+
],
|
|
57
|
+
"repository": {
|
|
58
|
+
"type": "git",
|
|
59
|
+
"url": "https://github.com/zhu1090093659/dsh-web-ui.git"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "tsdown",
|
|
63
|
+
"watch": "tsdown --watch",
|
|
64
|
+
"typecheck": "tsc --noEmit",
|
|
65
|
+
"test": "vitest run"
|
|
66
|
+
}
|
|
67
|
+
}
|