dsh-opencode-palette 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,6 +5,7 @@
5
5
  **The complete opencode palette for DeepSeek Harness — 34 official themes, one click.**
6
6
 
7
7
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE)
8
+ [![npm](https://img.shields.io/npm/v/dsh-opencode-palette)](https://www.npmjs.com/package/dsh-opencode-palette)
8
9
  [![opencode](https://img.shields.io/badge/themes-opencode%20v1.18.12-orange)](https://github.com/anomalyco/opencode)
9
10
  [![tests](https://img.shields.io/badge/tests-20%2F20-green)]()
10
11
 
package/lib/client.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * dsh-opencode-palette v1.5.0 — 浏览器半(构建产物,勿手改)
2
+ * dsh-opencode-palette v1.6.0 — 浏览器半(构建产物,勿手改)
3
3
  * 数据驱动管线:opencode v1.18.12 官方主题 JSON → 颜色解析 → DSH 适配注入
4
4
  * 源:src/engine/* + runtime/client.mjs(npm run build 重新生成)
5
5
  */
@@ -363,6 +363,25 @@ function generateTheme(colors, typography, themeName) {
363
363
  __mods["generate"] = { buildTokens, buildTypographyCss, buildColorCss, generateTheme }
364
364
  })();
365
365
  (function () {
366
+ // zh-names.mjs — 主题中文名表(单一来源)
367
+ // 供运行时面板(中文界面显示中文主题名)与 assets 生成器(矩阵/故事卡中文名)共用。
368
+ // 无中文译名者(opencode / One Dark / Monokai / Cursor / GitHub / Vercel)保留原名。
369
+
370
+ const THEME_ZH = {
371
+ opencode: "opencode", tokyonight: "东京之夜", dracula: "德古拉", gruvbox: "复古凹槽",
372
+ matrix: "黑客帝国", "rose-pine": "玫瑰松林", catppuccin: "卡布奇诺", "catppuccin-frappe": "卡布奇诺·冰沙",
373
+ "catppuccin-macchiato": "卡布奇诺·玛奇朵", solarized: "日光浴", synthwave84: "合成波 84",
374
+ everforest: "常青森林", nord: "北极", kanagawa: "神奈川", nightowl: "夜猫子",
375
+ "one-dark": "One Dark", monokai: "Monokai", palenight: "苍白之夜", material: "材料设计",
376
+ ayu: "鮎", carbonfox: "碳狐", cobalt2: "钴蓝", cursor: "Cursor", aura: "光环",
377
+ flexoki: "纸墨", github: "GitHub", zenburn: "禅燃", mercury: "水星",
378
+ "osaka-jade": "大阪翡翠", vesper: "黄昏星", vercel: "Vercel", "lucent-orng": "透光橙",
379
+ orng: "纯橙", system: "跟随系统",
380
+ }
381
+
382
+ __mods["zh-names"] = { THEME_ZH }
383
+ })();
384
+ (function () {
366
385
  // registry.mjs — 主题注册表:33 个静态主题(vendored JSON)+ system 特殊主题
367
386
  // 数据由 scripts/sync-themes.mjs 从 opencode v1.18.12 官方 tag 同步(见 MANIFEST.json 指纹)
368
387
  const aura = {
@@ -7962,6 +7981,7 @@ __mods["index"] = { renderTheme, previewColors, themeNames, themeGroups, GROUP_O
7962
7981
  // 依赖注入:theme(dsh-client-ui-theme)、slots(settings.plugins.tab / tool.view.cordis)
7963
7982
  const { renderTheme, previewColors, themeNames, themeGroups } = __mods["index"]
7964
7983
  const { FONTS, SANS_STACK } = __mods["map-dsh"]
7984
+ const { THEME_ZH } = __mods["zh-names"]
7965
7985
 
7966
7986
  const STORAGE_KEY = 'dsh.opencode-palette.v2'
7967
7987
  // 兼容迁移:旧插件(dsh-opencode-tui-theme)的本地设置键,读到即迁移到新键
@@ -8008,8 +8028,8 @@ const I18N = {
8008
8028
  enableTitle: { zh: '点击启用主题', en: 'Click to enable' },
8009
8029
  typography: { zh: '字体字号', en: 'Typography' },
8010
8030
  bodyStyle: { zh: '正文样式', en: 'Body style' },
8011
- mono: { zh: '等宽字体(终端风)', en: 'Monospace (terminal)' },
8012
- sans: { zh: '常规字体(界面风)', en: 'Regular (UI)' },
8031
+ mono: { zh: '等宽(终端风)', en: 'Monospace (terminal)' },
8032
+ sans: { zh: '常规(界面风)', en: 'Regular (UI)' },
8013
8033
  fontSize: { zh: '字号', en: 'Font size' },
8014
8034
  codeFont: { zh: '代码字体', en: 'Code font' },
8015
8035
  fontPreview: { zh: '等宽', en: 'mono' },
@@ -8146,20 +8166,23 @@ function createClient(slotTarget) {
8146
8166
  const h = react.createElement
8147
8167
  const [query, setQuery] = react.useState('')
8148
8168
  const [fontOpen, setFontOpen] = react.useState(false)
8169
+ const [sizeOpen, setSizeOpen] = react.useState(false)
8149
8170
  const fontRef = react.useRef(null)
8171
+ const sizeRef = react.useRef(null)
8150
8172
  // UI 快照:所有引擎动作后 setUi(props.getState()) 重同步,避免受控控件显示值漂移
8151
8173
  const [ui, setUi] = react.useState(props.getState())
8152
8174
  const st = ui
8153
8175
 
8154
8176
  // 字体下拉:点击外部关闭
8155
8177
  react.useEffect(function () {
8156
- if (!fontOpen) return
8178
+ if (!fontOpen && !sizeOpen) return
8157
8179
  function onDoc(e) {
8158
8180
  if (fontRef.current && !fontRef.current.contains(e.target)) setFontOpen(false)
8181
+ if (sizeRef.current && !sizeRef.current.contains(e.target)) setSizeOpen(false)
8159
8182
  }
8160
8183
  document.addEventListener('mousedown', onDoc)
8161
8184
  return function () { document.removeEventListener('mousedown', onDoc) }
8162
- }, [fontOpen])
8185
+ }, [fontOpen, sizeOpen])
8163
8186
 
8164
8187
  // 语言切换:DSH 界面语言变化时重渲染(文案跟随)
8165
8188
  react.useEffect(function () {
@@ -8171,11 +8194,15 @@ function createClient(slotTarget) {
8171
8194
  const shown = q === ''
8172
8195
  ? props.groups()
8173
8196
  : props.groups()
8174
- .map(function (g) { return { name: g.name, color: g.color, themes: g.themes.filter(function (t) { return t.name.indexOf(q) >= 0 }) } })
8197
+ .map(function (g) { return { name: g.name, color: g.color, themes: g.themes.filter(function (t) { return (t.name + ' ' + (THEME_ZH[t.name] || '')).toLowerCase().indexOf(q) >= 0 }) } })
8175
8198
  .filter(function (g) { return g.themes.length > 0 })
8176
8199
 
8177
8200
  const muted = 'var(--dsw-alias-label-secondary)'
8178
8201
  const base = 'var(--dsw-alias-label-primary)'
8202
+ // 主题显示名:中文界面用中文名(system 走 i18n 文案)
8203
+ const themeLabel = function (name) {
8204
+ return currentLang === 'zh' && name !== 'system' ? (THEME_ZH[name] || name) : name
8205
+ }
8179
8206
  const fieldLabel = { fontSize: 11, color: muted }
8180
8207
  const secTitle = { fontSize: 11, color: muted, letterSpacing: '.08em', marginBottom: 8, display: 'flex', alignItems: 'baseline', gap: 8 }
8181
8208
  const countStyle = { color: 'var(--dsw-alias-label-dimmed)', fontSize: 11, letterSpacing: 0 }
@@ -8197,9 +8224,26 @@ function createClient(slotTarget) {
8197
8224
  }, opt.label)
8198
8225
  }))
8199
8226
  }
8200
- const field = function (labelText, node) {
8201
- return h('div', { style: { display: 'flex', flexDirection: 'column', gap: 5 } },
8202
- h('span', { style: fieldLabel }, labelText), node)
8227
+ // 通用下拉(字号 / 代码字体):紧凑按钮 + 弹出菜单,对齐 setup-panel 样例
8228
+ const dd = function (open, setOpen, ref, labelNode, items) {
8229
+ return h('div', { ref: ref, style: { position: 'relative' } }, [
8230
+ h('button', {
8231
+ onClick: function () { setOpen(!open) },
8232
+ style: {
8233
+ display: 'flex', alignItems: 'center', gap: 8,
8234
+ background: 'var(--dsw-alias-bg-layer-2)', border: '1px solid var(--dsw-alias-border-l1)',
8235
+ borderRadius: 6, padding: '5px 10px', fontSize: 12, cursor: 'pointer',
8236
+ color: base, fontFamily: 'var(--dsw-font-family)',
8237
+ },
8238
+ }, [labelNode, h('span', { style: { color: muted } }, '▾')]),
8239
+ open ? h('div', {
8240
+ style: {
8241
+ position: 'absolute', top: 'calc(100% + 4px)', left: 0, zIndex: 20,
8242
+ background: 'var(--dsw-alias-bg-overlay)', border: '1px solid var(--dsw-alias-border-l1)',
8243
+ borderRadius: 8, minWidth: 200, padding: 4, boxShadow: '0 8px 24px rgba(0,0,0,0.5)',
8244
+ },
8245
+ }, items) : null,
8246
+ ])
8203
8247
  }
8204
8248
  const dot = function (color, size) {
8205
8249
  return h('span', { style: { width: size, height: size, borderRadius: '50%', background: color || '#555', display: 'inline-block', flex: 'none' } })
@@ -8223,48 +8267,11 @@ function createClient(slotTarget) {
8223
8267
  },
8224
8268
  }, [
8225
8269
  dot(c && c.primary, 9),
8226
- t.name === 'system' ? tr('systemDefault') : t.name,
8270
+ t.name === 'system' ? tr('systemDefault') : themeLabel(t.name),
8227
8271
  isCur ? h('span', { style: { color: 'var(--dsw-alias-brand-primary)', fontWeight: 700 } }, '✓') : null,
8228
8272
  ])
8229
8273
  }
8230
8274
 
8231
- // 自定义字体下拉(每个选项用其字体预览)
8232
- const fontDropdown = h('div', { ref: fontRef, style: { position: 'relative' } }, [
8233
- h('button', {
8234
- onClick: function () { setFontOpen(!fontOpen) },
8235
- style: {
8236
- display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10,
8237
- background: 'var(--dsw-alias-bg-layer-2)', border: '1px solid var(--dsw-alias-border-l1)',
8238
- borderRadius: 6, padding: '6px 12px', fontSize: 12, cursor: 'pointer', minWidth: 200,
8239
- color: base, fontFamily: 'var(--dsw-font-family)',
8240
- },
8241
- }, [
8242
- h('span', { style: { fontFamily: FONTS[st.fontKey] || FONTS['JetBrains Mono'] } }, st.fontKey),
8243
- h('span', { style: { color: muted } }, '▾'),
8244
- ]),
8245
- fontOpen ? h('div', {
8246
- style: {
8247
- position: 'absolute', top: 'calc(100% + 4px)', left: 0, zIndex: 20,
8248
- background: 'var(--dsw-alias-bg-overlay)', border: '1px solid var(--dsw-alias-border-l1)',
8249
- borderRadius: 8, minWidth: 230, padding: 4, boxShadow: '0 8px 24px rgba(0,0,0,0.5)',
8250
- },
8251
- }, Object.keys(FONTS).map(function (k) {
8252
- const on = k === st.fontKey
8253
- return h('div', {
8254
- key: k,
8255
- onClick: function () {
8256
- props.refresh(st.mode, st.size, k)
8257
- setUi(props.getState())
8258
- setFontOpen(false)
8259
- },
8260
- style: {
8261
- padding: '6px 10px', fontSize: 12, borderRadius: 5, cursor: 'pointer',
8262
- background: on ? 'rgba(255,255,255,0.1)' : 'transparent',
8263
- color: on ? base : muted,
8264
- },
8265
- }, h('span', { style: { fontFamily: FONTS[k] } }, k + ' — Aa ' + tr('fontPreview')))
8266
- })) : null,
8267
- ])
8268
8275
 
8269
8276
 
8270
8277
  return h('div', { style: { display: 'flex', flexDirection: 'column', gap: 14, maxWidth: 920 } }, [
@@ -8299,14 +8306,43 @@ function createClient(slotTarget) {
8299
8306
  h('div', { style: secTitle }, [
8300
8307
  h('span', null, tr('typography')),
8301
8308
  ]),
8302
- h('div', { style: { display: 'flex', alignItems: 'flex-end', gap: 20, flexWrap: 'wrap' } }, [
8303
- field(tr('bodyStyle'), seg(st.mode, [
8309
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' } }, [
8310
+ seg(st.mode, [
8304
8311
  { value: 'mono', label: tr('mono') },
8305
8312
  { value: 'tui', label: tr('sans') },
8306
- ], function (v) { props.refresh(v, st.size, st.fontKey); setUi(props.getState()) })),
8307
- field(tr('fontSize'), seg(st.size, [11, 12, 13, 14, 15, 16, 17, 18].map(function (s) { return { value: s, label: s + 'px' } }),
8308
- function (v) { props.refresh(st.mode, Number(v), st.fontKey); setUi(props.getState()) })),
8309
- field(tr('codeFont'), fontDropdown),
8313
+ ], function (v) { props.refresh(v, st.size, st.fontKey); setUi(props.getState()) }),
8314
+ dd(sizeOpen, setSizeOpen, sizeRef,
8315
+ h('span', null, tr('fontSize') + ' ' + st.size + 'px'),
8316
+ [11, 12, 13, 14, 15, 16, 17, 18].map(function (s) {
8317
+ const on = s === st.size
8318
+ return h('div', {
8319
+ key: String(s),
8320
+ onClick: function () { props.refresh(st.mode, s, st.fontKey); setUi(props.getState()); setSizeOpen(false) },
8321
+ style: {
8322
+ padding: '6px 10px', fontSize: 12, borderRadius: 5, cursor: 'pointer',
8323
+ background: on ? 'rgba(255,255,255,0.1)' : 'transparent',
8324
+ color: on ? base : muted,
8325
+ },
8326
+ }, String(s) + 'px')
8327
+ })),
8328
+ dd(fontOpen, setFontOpen, fontRef,
8329
+ h('span', { style: { fontFamily: FONTS[st.fontKey] || FONTS['JetBrains Mono'] } }, st.fontKey),
8330
+ Object.keys(FONTS).map(function (k) {
8331
+ const on = k === st.fontKey
8332
+ return h('div', {
8333
+ key: k,
8334
+ onClick: function () {
8335
+ props.refresh(st.mode, st.size, k)
8336
+ setUi(props.getState())
8337
+ setFontOpen(false)
8338
+ },
8339
+ style: {
8340
+ padding: '6px 10px', fontSize: 12, borderRadius: 5, cursor: 'pointer',
8341
+ background: on ? 'rgba(255,255,255,0.1)' : 'transparent',
8342
+ color: on ? base : muted,
8343
+ },
8344
+ }, h('span', { style: { fontFamily: FONTS[k] } }, k + ' — Aa ' + tr('fontPreview')))
8345
+ })),
8310
8346
  ]),
8311
8347
  // ── 主题选择(色系分组标签 + mini 芯片)──
8312
8348
  h('div', { style: secTitle }, [
@@ -8338,32 +8374,49 @@ function createClient(slotTarget) {
8338
8374
  }),
8339
8375
  ])
8340
8376
  }
8377
+ // 面板 API(settings.plugins.tab 与 settings.section 两个入口共享同一份 state)
8378
+ const paletteApi = function () {
8379
+ return {
8380
+ getState: getState,
8381
+ toggle: toggle,
8382
+ refresh: refresh,
8383
+ setTheme: setTheme,
8384
+ themeNames: themeNames,
8385
+ groups: function () { return themeGroups() },
8386
+ subscribeLocale: function (fn) {
8387
+ localeListeners.push(fn)
8388
+ return function () {
8389
+ const i = localeListeners.indexOf(fn)
8390
+ if (i >= 0) localeListeners.splice(i, 1)
8391
+ }
8392
+ },
8393
+ previews: function () { return themeNames().map(function (n) { return { name: n, colors: previewColors(n) } }) },
8394
+ }
8395
+ }
8341
8396
  disposePanel = slots.inject(slotTarget, function () {
8342
8397
  return slots.register({
8343
8398
  name: slotTarget,
8344
8399
  id: 'opencode-palette',
8345
8400
  order: 30,
8346
8401
  label: function () { return tr('panelName') },
8347
- inject: function () {
8348
- return {
8349
- getState: getState,
8350
- toggle: toggle,
8351
- refresh: refresh,
8352
- setTheme: setTheme,
8353
- themeNames: themeNames,
8354
- groups: function () { return themeGroups() },
8355
- subscribeLocale: function (fn) {
8356
- localeListeners.push(fn)
8357
- return function () {
8358
- const i = localeListeners.indexOf(fn)
8359
- if (i >= 0) localeListeners.splice(i, 1)
8360
- }
8361
- },
8362
- previews: function () { return themeNames().map(function (n) { return { name: n, colors: previewColors(n) } }) },
8363
- }
8364
- },
8402
+ inject: paletteApi,
8365
8403
  }, Panel)
8366
8404
  })
8405
+ // 设置页左侧导航直达入口(保留「设置 → 插件」内的原入口)
8406
+ // settings.section = 设置页左侧 section 列表(general=0 / models=10 / plugins=15 / agent-presets=20)
8407
+ let disposeSection = null
8408
+ if (slotTarget === 'settings.plugins.tab') {
8409
+ disposeSection = slots.inject('settings.section', function () {
8410
+ return slots.register({
8411
+ name: 'settings.section',
8412
+ id: 'opencode-palette',
8413
+ order: 16,
8414
+ label: function () { return tr('panelName') },
8415
+ inject: paletteApi,
8416
+ }, Panel)
8417
+ })
8418
+ }
8419
+ disposeSection = disposeSection || null
8367
8420
  }
8368
8421
 
8369
8422
  // 卸载清理(cordis 语义:effect fn 立即执行,返回值才是清理器)
@@ -8371,6 +8424,7 @@ function createClient(slotTarget) {
8371
8424
  return function () {
8372
8425
  try { clearStyle() } catch (e) { /* 忽略 */ }
8373
8426
  try { if (disposePanel) disposePanel() } catch (e) { /* 忽略 */ }
8427
+ try { if (disposeSection) disposeSection() } catch (e) { /* 忽略 */ }
8374
8428
  try { if (globalThis.__opencodePalette) delete globalThis.__opencodePalette } catch (e) { /* 忽略 */ }
8375
8429
  try { if (localeObserver) localeObserver.disconnect() } catch (e) { /* 忽略 */ }
8376
8430
  }
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * dsh-opencode-palette v1.5.0 — 宿主半(no-op)
2
+ * dsh-opencode-palette v1.6.0 — 宿主半(no-op)
3
3
  *
4
4
  * 主题的全部工作在浏览器端(./client.js)完成:
5
5
  * - 34 个 opencode 主题(33 内置 JSON + system)经数据驱动管线解析
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-opencode-palette",
3
- "version": "1.5.0",
4
- "description": "DSH Web 多主题引擎:完整支持 opencode TUI 全部 34 个主题(33 内置 + system)。数据驱动管线:主题 JSON → 颜色解析 → DSH 适配注入;设置面板即时切换、持久化。",
3
+ "version": "1.6.0",
4
+ "description": "让 DeepSeek Harness 穿上 34 款经典皮肤——东京的霓虹夜色、德古拉的暗红月光、复古工坊的暖黄灯火、黑客帝国的数字雨、玫瑰松林间的风……一键换肤,即点即换,重启不丢。34 legendary skins for DeepSeek Harness — tokyonight's neon dusk, dracula's crimson moon, gruvbox's retro glow, the matrix's digital rain, rose-pine's rosewood calm… one click, instant, persisted.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {