跳过主要内容
W

dsh-im-workspace-buttons

whatcannotbesaid/dsh-im-workspace-buttons

在官方「工作区」标题行上,为每个已接入账号的 dsh-im 通道添加一个 28x28 图标按钮。点击后侧栏切到该通道视图,只显示它的会话、隐藏其余所有行;再次点击回到官方列表。只有已接入账号的通道才会出现按钮。

安装

dsh plugin --profile web add github:whatcannotbesaid/dsh-im-workspace-buttons

README

Workspace Channel Buttons

licence: MIT

English | 中文

One 28×28 icon button per @xmanrui/dsh-im channel that holds an account, placed in the official 「工作区」 (Workspaces) header row. Pressing one puts the sidebar into that channel's view: only its conversations are drawn, and the official list (every other conversation, every group header) waits behind it. Pressing the same entry again drops back to the official list, where every dsh-im conversation is hidden again.

This is the single feature extracted from eva-inspired-theme: the buttons and nothing else. No skin, no theme replacement, no dependency on the theme's CSS, locale namespace or build pipeline.

Install

# 1. install the package
npm install dsh-im-workspace-buttons --prefix ~/.dsh/profiles/desktop --registry=https://registry.npmjs.org

# 2. add it to the profile's bundle list (<profileDir> = ~/.dsh/profiles/desktop)
#    append "dsh-im-workspace-buttons" to dsh.profile.bundles in package.json

Restart DSH afterwards. The package ships its own dsh.bundle.patch, so cordis.patch.yml needs no hand edit — but the profile's dsh.profile.bundles must list this package, or the loader never mounts it.

Requires @xmanrui/dsh-im and at least one channel with a connected account; by design no buttons appear otherwise.

Migrating from the theme edition

eva-inspired-theme 1.2.0 no longer contains these buttons. Do not install both:

  • The theme edition uses the data-eva-im-* contract and the eva-im-filter locale namespace; this package uses data-dsh-im-* and dsh-im-workspace-buttons. Installed together they draw two sets of buttons on the same row, and neither stylesheet covers the other's.
  • Upgrading to eva-inspired-theme@1.2.0 and installing this package is a drop-in swap: interaction, icons, order and the visibility rule are identical.

Behaviour

Plugin iddsh-im-workspace-buttons (also the client module id and the locale namespace)
WhereThe header row carrying the official sidebar.workspaces.directoryFlow marker, left of the search slot, in the plugin's own channel order
VisibilityAn entry appears only when the channel has an account: connection.status returns a non-empty botId in bots[], or the older configured: true. An inconclusive probe (RPC not ready, request failed) draws nothing on the first pass and keeps the previous verdict after that
ClickPuts the sidebar into that channel's view: its conversation rows (recognised by data-dsh-im-session-channel or by the row's title belonging to the channel) stay, and every other row — other channels, official conversations, group headers, show-more controls — is hidden. A second click drops back to the official list
Collapsed groupsUnfolded, paged and scrolled to the channel's first conversation only on click, capped at CLICK_LIMIT = 200 steps. The controls are hidden in the view but still clickable, so the rows are drawn before they are hidden
dsh-im hidden by defaultNo dsh-im conversation shows in any of the three views — 工作区, 工作区树, single list — until its entry is pressed. Every dsh-im row carries data-eva-im-tag='off'; only the lit channel's own rows turn 'on'. A row that is not a dsh-im conversation is never tagged and is never hidden by the official list; in a lit view it is 'off' like everything else
Group ownershipA conversation counts as living in exactly one group, decided by home(): the workspace record whose sessionIds names it owns it, and everything else is Ungrouped (the empty workspace: suffix). That is the host's own rule (groupByWorkspace); cwd takes no part in it
Ungrouped bucketUngrouped has no header to press — only its own show-more control, keyed overflow: with nothing after the colon. The plugin clicks that control and never a header for it
Flat listThe host draws both lists through AnimatedRows, so the single list's rows sit inside the same [role='tree'] and need no separate container. tree() still answers with a [data-testid="session-list"] container when a host draws the rows outside that tree. The whole page is one overflow: row; the plugin records it as __ungrouped_session__ and opens that single control, so a channel view can reach conversations past the first page
LabelsThrough the Client locale service; follows the profile language, falls back to Chinese when the registry is absent
UntouchedOfficial grouping, collapsed state, spy counts, conversation order — none of them are rewritten. A lit view hides the official rows along with everything else, and dropping back draws them exactly as the host had them

Style contract

The runtime injects its own <style data-dsh-im-workspace-buttons-sheet>; every colour comes from a --dsw-* token:

SelectorEffect
[data-eva-im-button]28×28, no border, no background, --dsw-radius-sm corners, --dsw-alias-label-secondary foreground
[data-eva-im-button]:hoverForeground turns --dsw-alias-label-primary
[data-eva-im-state='on']The lit entry turns --dsw-alias-brand-primary
[data-eva-im-tag='off']Hides that one row — the only hiding rule in the sheet. It is written on dsh-im rows while no entry is lit, and on every row that does not belong to the lit channel

The data-eva-im-* prefix is kept verbatim from the extraction; renaming it would break style compatibility with eva-inspired-theme. The rule is a plain attribute test, so an "off" state must be written explicitly: a row with no tag is a row the host draws as-is. The tag lives on its own attribute rather than on data-eva-im-row, which is in this effect's MutationObserver filter — a per-row tag there would re-trigger every pass. The pump hides rows after it has clicked them, which is why the sheet never hides a control the runtime still has to click.

Dependencies

DependencyKind
@xmanrui/dsh-imRequired at runtime. The channel table and the status probe both read its connection.status; without it no button appears
Host client services sessions / workspaces / locale / connectionTaken through ctx.get only, never imported
eva-inspired-themeNot needed. Styles, locale namespace and build are all self-contained

Development

node tools/build-client.mjs          # rewrite the runtime section of client.js from src/runtime.js
node tools/build-client.mjs --check  # assert client.js and src/runtime.js agree
node --test --test-timeout=15000 test/*.test.mjs   # 14 behaviour tests, fake DOM, no browser
node test/render.mjs                 # real browser: isolated profile + this package + dsh-im

The runtime inside client.js is a copy, not an import: the client module loader evaluates client.js as a classic script, so the file cannot contain a module graph. tools/build-client.mjs keeps the copy honest.

Files

FilePurpose
index.jsHost entry, ESM, export apply(ctx); one log line, no service / route / Config
client.jsBrowser entry, window.__ModuleLoader__.load({ id, factory }), carries the runtime copy
src/runtime.jsRuntime source of truth (export function mountWorkspaceButtons(ctx))
cordis.patch.yml- insert: [{ id, name }]; also the browser roster entry
locale/zh.json · locale/en.jsonmeta.title / meta.description plus the 12 channel labels
tools/build-client.mjsInlines src/runtime.js into client.js, --check verifies it
test/fake-dom.mjs · test/behaviour.test.mjsFake DOM and 14 behaviour assertions

Three deviations from the theme edition

  1. Locale namespace renamed eva-im-filter → dsh-im-workspace-buttons. The registry keeps the first registration for a namespace, so two packages sharing one would overwrite each other's dictionary. The fallback language also moved from DICT.en to DICT.zh, because these strings were written in Chinese — an untranslated language reading Chinese beats reading English.
  2. Styles moved from theme.css into the runtime's own <style> (data-dsh-im-workspace-buttons-sheet). The four rules are copied verbatim; selectors, sizes and colour tokens are unchanged.
  3. The disposer also calls removeSheet(). In the theme the styles live with the skin document, so the rules survived unloading; in a standalone package "uninstalling must stop affecting the document" is a hard rule, so the buttons and the rules leave together.

Beyond these three, behaviour, channel table, icons, probe interval, click limit and selectors are identical to the theme edition.

Licence

MIT — see LICENSE.

相关插件