Replace IM channel rail with a top dropdown (4.9.1-ops.13).

Give the settings panel the full right-pane width instead of a left channel list.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-05 17:57:40 +08:00
parent ec96b4d6eb
commit 43f082d2e3
6 changed files with 91 additions and 106 deletions

View file

@ -571,7 +571,7 @@ var React24 = __toESM(require("react"), 1);
// package.json // package.json
var package_default = { var package_default = {
name: "dsh-im-ops", name: "dsh-im-ops",
version: "4.9.1-ops.12", version: "4.9.1-ops.13",
description: "Ops fork of dsh-im (all channels kept). Based on @xmanrui/dsh-im@4.9.1 \u2014 access/session policies owned here.", description: "Ops fork of dsh-im (all channels kept). Based on @xmanrui/dsh-im@4.9.1 \u2014 access/session policies owned here.",
keywords: [ keywords: [
"deepseek-harness", "deepseek-harness",
@ -14282,14 +14282,13 @@ var CSS11 = String.raw`
.dim-githubArrow { font-size: 13px; line-height: 1; } .dim-githubArrow { font-size: 13px; line-height: 1; }
.dim-githubTooltip { position: absolute; top: calc(100% + 8px); right: 0; z-index: 20; width: max-content; max-width: min(220px, 80vw); padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; } .dim-githubTooltip { position: absolute; top: calc(100% + 8px); right: 0; z-index: 20; width: max-content; max-width: min(220px, 80vw); padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
.dim-githubAction:hover .dim-githubTooltip, .dim-githubAction:focus-within .dim-githubTooltip { opacity: 1; visibility: visible; transform: translateY(0); } .dim-githubAction:hover .dim-githubTooltip, .dim-githubAction:focus-within .dim-githubTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
.dim-layout { display: grid; grid-template-columns: 174px 1px minmax(0, 1fr); gap: 24px; align-items: start; } .dim-layout { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; align-items: start; }
.dim-rail { max-height: 520px; display: grid; align-content: start; gap: 8px; overflow-y: auto; padding: 0 4px 1px 1px; scrollbar-width: thin; scrollbar-color: var(--dsw-alias-border-l2, #dfe1e5) transparent; } .dim-channelPicker { min-width: 0; display: flex; align-items: center; gap: 10px; }
.dim-rail::-webkit-scrollbar { width: 4px; } .dim-channelPickerLabel { flex: none; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; font-weight: 560; white-space: nowrap; }
.dim-rail::-webkit-scrollbar-thumb { border-radius: 99px; background: var(--dsw-alias-border-l2, #dfe1e5); } .dim-channelPickerControl { min-width: 0; flex: 1 1 auto; display: grid; grid-template-columns: 30px minmax(0, 1fr); align-items: center; gap: 10px; max-width: 420px; padding: 4px 10px 4px 6px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 10px; background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 4%); }
.dim-channel { width: 100%; min-height: 48px; display: grid; grid-template-columns: 30px minmax(0, 1fr); align-items: center; gap: 10px; padding: 8px 12px; border: 1px solid var(--dsw-alias-border-l2, #eef0f3); border-radius: 14px; color: inherit; background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 2px 8px rgb(31 35 41 / 3%); font: inherit; text-align: left; cursor: pointer; transition: border-color .16s ease, background .16s ease, box-shadow .16s ease; } .dim-channelPickerSelect { min-width: 0; width: 100%; height: 34px; padding: 0 4px 0 0; border: 0; border-radius: 6px; color: var(--dsw-alias-label-primary, #1f2329); background: transparent; font: inherit; font-size: 14px; font-weight: 650; cursor: pointer; }
.dim-channel:hover { border-color: color-mix(in srgb, var(--dim-blue) 25%, var(--dsw-alias-border-l2, #eef0f3)); background: color-mix(in srgb, var(--dim-blue) 2%, var(--dsw-alias-bg-layer-3, #fff)); box-shadow: 0 5px 16px rgb(31 35 41 / 5%); } .dim-channelPickerSelect:focus { outline: none; }
.dim-channel[aria-selected="true"] { border-color: color-mix(in srgb, var(--dim-blue) 43%, var(--dsw-alias-border-l2, #dfe1e5)); color: var(--dim-blue); background: color-mix(in srgb, var(--dim-blue) 12%, var(--dsw-alias-bg-layer-3, #fff)); box-shadow: 0 3px 12px rgb(51 112 255 / 7%); } .dim-channelPickerControl:focus-within { border-color: color-mix(in srgb, var(--dim-blue) 55%, var(--dsw-alias-border-l2, #dfe1e5)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--dim-blue) 16%, transparent); }
.dim-channel:focus-visible { outline: none; border-color: color-mix(in srgb, var(--dim-blue) 72%, var(--dsw-alias-border-l2, #dfe1e5)); box-shadow: 0 0 0 1px color-mix(in srgb, var(--dim-blue) 24%, transparent) inset, 0 3px 12px rgb(51 112 255 / 7%); }
.dim-logo { width: 30px; height: 30px; display: grid; place-items: center; border-radius: 9px; box-shadow: 0 1px 3px rgb(31 35 41 / 7%); } .dim-logo { width: 30px; height: 30px; display: grid; place-items: center; border-radius: 9px; box-shadow: 0 1px 3px rgb(31 35 41 / 7%); }
.dim-logo svg { display: block; width: 20px; height: 20px; } .dim-logo svg { display: block; width: 20px; height: 20px; }
.dim-logoWeixin { color: white; background: #07c160; } .dim-logoWeixin { color: white; background: #07c160; }
@ -14312,10 +14311,6 @@ var CSS11 = String.raw`
.dim-logoSlack svg { width: 21px; height: 21px; } .dim-logoSlack svg { width: 21px; height: 21px; }
.dim-logoWhatsapp { color: white; background: #25d366; } .dim-logoWhatsapp { color: white; background: #25d366; }
.dim-logoWhatsapp svg { width: 21px; height: 21px; } .dim-logoWhatsapp svg { width: 21px; height: 21px; }
.dim-channelCopy { min-width: 0; display: grid; }
.dim-channelCopy strong { overflow: hidden; color: inherit; font-size: 14px; line-height: 20px; font-weight: 680; text-overflow: ellipsis; white-space: nowrap; }
.dim-channelNote { overflow: hidden; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 10px; line-height: 13px; font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
.dim-divider { width: 1px; min-height: 520px; background: var(--dsw-alias-border-l1, #eef0f3); }
.dim-panel { min-width: 0; container-type: inline-size; } .dim-panel { min-width: 0; container-type: inline-size; }
.dim-loopbackRecovery { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 0 0 14px; padding: 14px 16px; border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary, #d97706) 30%, var(--dsw-alias-border-l2, #dfe1e5)); border-radius: 12px; color: var(--dsw-alias-label-primary, #1f2329); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #d97706) 8%, var(--dsw-alias-bg-layer-1, #fff)); } .dim-loopbackRecovery { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 0 0 14px; padding: 14px 16px; border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary, #d97706) 30%, var(--dsw-alias-border-l2, #dfe1e5)); border-radius: 12px; color: var(--dsw-alias-label-primary, #1f2329); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #d97706) 8%, var(--dsw-alias-bg-layer-1, #fff)); }
.dim-loopbackRecoveryCopy { min-width: 0; } .dim-loopbackRecoveryCopy { min-width: 0; }
@ -14746,11 +14741,7 @@ var CSS11 = String.raw`
} }
@media (max-width: 840px) { @media (max-width: 840px) {
.dim-title { align-items: flex-start; } .dim-title { align-items: flex-start; }
.dim-layout { grid-template-columns: minmax(0, 1fr); gap: 18px; } .dim-channelPickerControl { max-width: none; }
.dim-rail { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.dim-divider { display: none; }
.dim-rail { max-height: none; overflow: visible; padding-right: 1px; }
.dim-channel { min-height: 48px; }
} }
@media (max-width: 560px) { @media (max-width: 560px) {
.dim-title { flex-direction: column; gap: 10px; } .dim-title { flex-direction: column; gap: 10px; }
@ -14763,7 +14754,8 @@ var CSS11 = String.raw`
.dim-updateBody { padding: 16px 18px; } .dim-updateBody { padding: 16px 18px; }
.dim-updateFooter { padding: 12px 18px; } .dim-updateFooter { padding: 12px 18px; }
.dim-githubTooltip { right: auto; left: 0; } .dim-githubTooltip { right: auto; left: 0; }
.dim-rail { grid-template-columns: minmax(0, 1fr); } .dim-channelPicker { align-items: stretch; flex-direction: column; gap: 6px; }
.dim-channelPickerControl { max-width: none; }
.dim-loopbackRecovery { align-items: stretch; flex-direction: column; gap: 12px; } .dim-loopbackRecovery { align-items: stretch; flex-direction: column; gap: 12px; }
.dim-loopbackRecoveryAction { width: 100%; } .dim-loopbackRecoveryAction { width: 100%; }
.dim-deliverySectionHeading { align-items: stretch; flex-direction: column; } .dim-deliverySectionHeading { align-items: stretch; flex-direction: column; }
@ -15439,6 +15431,7 @@ function IMSettingsTab({
const [runningVersion, setRunningVersion] = React24.useState(IM_PLUGIN_VERSION); const [runningVersion, setRunningVersion] = React24.useState(IM_PLUGIN_VERSION);
const [deliverySettings, setDeliverySettings] = React24.useState(null); const [deliverySettings, setDeliverySettings] = React24.useState(null);
const githubTooltipId = React24.useId(); const githubTooltipId = React24.useId();
const channelSelectId = React24.useId();
const active = CHANNELS.find((channel4) => channel4.id === selected) ?? CHANNELS[0]; const active = CHANNELS.find((channel4) => channel4.id === selected) ?? CHANNELS[0];
const reportLoopbackRecovery = React24.useCallback((recovery) => { const reportLoopbackRecovery = React24.useCallback((recovery) => {
setLoopbackRecovery((current) => current?.url === recovery.url ? current : recovery); setLoopbackRecovery((current) => current?.url === recovery.url ? current : recovery);
@ -15537,40 +15530,37 @@ function IMSettingsTab({
"div", "div",
{ className: "dim-layout" }, { className: "dim-layout" },
h2( h2(
"nav", "div",
{ className: "dim-rail", role: "tablist", "aria-label": "IM \u6E20\u9053" }, { className: "dim-channelPicker" },
CHANNELS.map((channel4) => h2( h2("label", {
"button", className: "dim-channelPickerLabel",
{ htmlFor: channelSelectId
key: channel4.id, }, "IM \u6E20\u9053"),
type: "button", h2(
role: "tab", "div",
id: `dim-tab-${channel4.id}`, { className: "dim-channelPickerControl" },
className: "dim-channel", h2(ChannelLogo, { channel: active.id }),
"aria-selected": channel4.id === active.id, h2("select", {
"aria-controls": `dim-panel-${channel4.id}`, id: channelSelectId,
onClick: () => { className: "dim-channelPickerSelect",
setSelected(channel4.id); value: active.id,
"aria-label": "IM \u6E20\u9053",
onChange: (event) => {
setSelected(event.target.value);
setDeliverySettings(null); setDeliverySettings(null);
} }
}, }, CHANNELS.map((channel4) => h2("option", {
h2(ChannelLogo, { channel: channel4.id }), key: channel4.id,
h2( value: channel4.id
"span", }, channel4.label, channel4.note || null)))
{ className: "dim-channelCopy" }, )
h2("strong", null, channel4.label),
channel4.note ? h2("small", { className: "dim-channelNote" }, channel4.note) : null
)
))
), ),
h2("div", { className: "dim-divider", "aria-hidden": "true" }),
h2( h2(
"main", "main",
{ {
className: "dim-panel", className: "dim-panel",
role: "tabpanel",
id: `dim-panel-${active.id}`, id: `dim-panel-${active.id}`,
"aria-labelledby": `dim-tab-${active.id}` "aria-label": active.label
}, },
loopbackRecovery ? h2(LoopbackRecoveryNotice, { loopbackRecovery ? h2(LoopbackRecoveryNotice, {
recovery: loopbackRecovery, recovery: loopbackRecovery,

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,6 @@
{ {
"name": "dsh-im-ops", "name": "dsh-im-ops",
"version": "4.9.1-ops.12", "version": "4.9.1-ops.13",
"description": "Ops fork of dsh-im (all channels kept). Based on @xmanrui/dsh-im@4.9.1 — access/session policies owned here.", "description": "Ops fork of dsh-im (all channels kept). Based on @xmanrui/dsh-im@4.9.1 — access/session policies owned here.",
"keywords": [ "keywords": [
"deepseek-harness", "deepseek-harness",

View file

@ -182,6 +182,7 @@ export function IMSettingsTab({
const [runningVersion, setRunningVersion] = React.useState(IM_PLUGIN_VERSION); const [runningVersion, setRunningVersion] = React.useState(IM_PLUGIN_VERSION);
const [deliverySettings, setDeliverySettings] = React.useState(null); const [deliverySettings, setDeliverySettings] = React.useState(null);
const githubTooltipId = React.useId(); const githubTooltipId = React.useId();
const channelSelectId = React.useId();
const active = CHANNELS.find((channel) => channel.id === selected) ?? CHANNELS[0]; const active = CHANNELS.find((channel) => channel.id === selected) ?? CHANNELS[0];
const reportLoopbackRecovery = React.useCallback((recovery) => { const reportLoopbackRecovery = React.useCallback((recovery) => {
setLoopbackRecovery((current) => current?.url === recovery.url ? current : recovery); setLoopbackRecovery((current) => current?.url === recovery.url ? current : recovery);
@ -256,31 +257,30 @@ export function IMSettingsTab({
}, '帮助与反馈 · 前往 GitHub'))), }, '帮助与反馈 · 前往 GitHub'))),
), ),
h('div', { className: 'dim-layout' }, h('div', { className: 'dim-layout' },
h('nav', { className: 'dim-rail', role: 'tablist', 'aria-label': 'IM 渠道' }, h('div', { className: 'dim-channelPicker' },
CHANNELS.map((channel) => h('button', { h('label', {
key: channel.id, className: 'dim-channelPickerLabel',
type: 'button', htmlFor: channelSelectId,
role: 'tab', }, 'IM 渠道'),
id: `dim-tab-${channel.id}`, h('div', { className: 'dim-channelPickerControl' },
className: 'dim-channel', h(ChannelLogo, { channel: active.id }),
'aria-selected': channel.id === active.id, h('select', {
'aria-controls': `dim-panel-${channel.id}`, id: channelSelectId,
onClick: () => { className: 'dim-channelPickerSelect',
setSelected(channel.id); value: active.id,
setDeliverySettings(null); 'aria-label': 'IM 渠道',
}, onChange: (event) => {
}, setSelected(event.target.value);
h(ChannelLogo, { channel: channel.id }), setDeliverySettings(null);
h('span', { className: 'dim-channelCopy' }, },
h('strong', null, channel.label), }, CHANNELS.map((channel) => h('option', {
channel.note ? h('small', { className: 'dim-channelNote' }, channel.note) : null, key: channel.id,
)))), value: channel.id,
h('div', { className: 'dim-divider', 'aria-hidden': 'true' }), }, channel.label, channel.note || null))))),
h('main', { h('main', {
className: 'dim-panel', className: 'dim-panel',
role: 'tabpanel',
id: `dim-panel-${active.id}`, id: `dim-panel-${active.id}`,
'aria-labelledby': `dim-tab-${active.id}`, 'aria-label': active.label,
}, },
loopbackRecovery loopbackRecovery
? h(LoopbackRecoveryNotice, { ? h(LoopbackRecoveryNotice, {

View file

@ -61,14 +61,13 @@ const CSS = String.raw`
.dim-githubArrow { font-size: 13px; line-height: 1; } .dim-githubArrow { font-size: 13px; line-height: 1; }
.dim-githubTooltip { position: absolute; top: calc(100% + 8px); right: 0; z-index: 20; width: max-content; max-width: min(220px, 80vw); padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; } .dim-githubTooltip { position: absolute; top: calc(100% + 8px); right: 0; z-index: 20; width: max-content; max-width: min(220px, 80vw); padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
.dim-githubAction:hover .dim-githubTooltip, .dim-githubAction:focus-within .dim-githubTooltip { opacity: 1; visibility: visible; transform: translateY(0); } .dim-githubAction:hover .dim-githubTooltip, .dim-githubAction:focus-within .dim-githubTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
.dim-layout { display: grid; grid-template-columns: 174px 1px minmax(0, 1fr); gap: 24px; align-items: start; } .dim-layout { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; align-items: start; }
.dim-rail { max-height: 520px; display: grid; align-content: start; gap: 8px; overflow-y: auto; padding: 0 4px 1px 1px; scrollbar-width: thin; scrollbar-color: var(--dsw-alias-border-l2, #dfe1e5) transparent; } .dim-channelPicker { min-width: 0; display: flex; align-items: center; gap: 10px; }
.dim-rail::-webkit-scrollbar { width: 4px; } .dim-channelPickerLabel { flex: none; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; font-weight: 560; white-space: nowrap; }
.dim-rail::-webkit-scrollbar-thumb { border-radius: 99px; background: var(--dsw-alias-border-l2, #dfe1e5); } .dim-channelPickerControl { min-width: 0; flex: 1 1 auto; display: grid; grid-template-columns: 30px minmax(0, 1fr); align-items: center; gap: 10px; max-width: 420px; padding: 4px 10px 4px 6px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 10px; background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 4%); }
.dim-channel { width: 100%; min-height: 48px; display: grid; grid-template-columns: 30px minmax(0, 1fr); align-items: center; gap: 10px; padding: 8px 12px; border: 1px solid var(--dsw-alias-border-l2, #eef0f3); border-radius: 14px; color: inherit; background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 2px 8px rgb(31 35 41 / 3%); font: inherit; text-align: left; cursor: pointer; transition: border-color .16s ease, background .16s ease, box-shadow .16s ease; } .dim-channelPickerSelect { min-width: 0; width: 100%; height: 34px; padding: 0 4px 0 0; border: 0; border-radius: 6px; color: var(--dsw-alias-label-primary, #1f2329); background: transparent; font: inherit; font-size: 14px; font-weight: 650; cursor: pointer; }
.dim-channel:hover { border-color: color-mix(in srgb, var(--dim-blue) 25%, var(--dsw-alias-border-l2, #eef0f3)); background: color-mix(in srgb, var(--dim-blue) 2%, var(--dsw-alias-bg-layer-3, #fff)); box-shadow: 0 5px 16px rgb(31 35 41 / 5%); } .dim-channelPickerSelect:focus { outline: none; }
.dim-channel[aria-selected="true"] { border-color: color-mix(in srgb, var(--dim-blue) 43%, var(--dsw-alias-border-l2, #dfe1e5)); color: var(--dim-blue); background: color-mix(in srgb, var(--dim-blue) 12%, var(--dsw-alias-bg-layer-3, #fff)); box-shadow: 0 3px 12px rgb(51 112 255 / 7%); } .dim-channelPickerControl:focus-within { border-color: color-mix(in srgb, var(--dim-blue) 55%, var(--dsw-alias-border-l2, #dfe1e5)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--dim-blue) 16%, transparent); }
.dim-channel:focus-visible { outline: none; border-color: color-mix(in srgb, var(--dim-blue) 72%, var(--dsw-alias-border-l2, #dfe1e5)); box-shadow: 0 0 0 1px color-mix(in srgb, var(--dim-blue) 24%, transparent) inset, 0 3px 12px rgb(51 112 255 / 7%); }
.dim-logo { width: 30px; height: 30px; display: grid; place-items: center; border-radius: 9px; box-shadow: 0 1px 3px rgb(31 35 41 / 7%); } .dim-logo { width: 30px; height: 30px; display: grid; place-items: center; border-radius: 9px; box-shadow: 0 1px 3px rgb(31 35 41 / 7%); }
.dim-logo svg { display: block; width: 20px; height: 20px; } .dim-logo svg { display: block; width: 20px; height: 20px; }
.dim-logoWeixin { color: white; background: #07c160; } .dim-logoWeixin { color: white; background: #07c160; }
@ -91,10 +90,6 @@ const CSS = String.raw`
.dim-logoSlack svg { width: 21px; height: 21px; } .dim-logoSlack svg { width: 21px; height: 21px; }
.dim-logoWhatsapp { color: white; background: #25d366; } .dim-logoWhatsapp { color: white; background: #25d366; }
.dim-logoWhatsapp svg { width: 21px; height: 21px; } .dim-logoWhatsapp svg { width: 21px; height: 21px; }
.dim-channelCopy { min-width: 0; display: grid; }
.dim-channelCopy strong { overflow: hidden; color: inherit; font-size: 14px; line-height: 20px; font-weight: 680; text-overflow: ellipsis; white-space: nowrap; }
.dim-channelNote { overflow: hidden; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 10px; line-height: 13px; font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
.dim-divider { width: 1px; min-height: 520px; background: var(--dsw-alias-border-l1, #eef0f3); }
.dim-panel { min-width: 0; container-type: inline-size; } .dim-panel { min-width: 0; container-type: inline-size; }
.dim-loopbackRecovery { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 0 0 14px; padding: 14px 16px; border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary, #d97706) 30%, var(--dsw-alias-border-l2, #dfe1e5)); border-radius: 12px; color: var(--dsw-alias-label-primary, #1f2329); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #d97706) 8%, var(--dsw-alias-bg-layer-1, #fff)); } .dim-loopbackRecovery { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 0 0 14px; padding: 14px 16px; border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary, #d97706) 30%, var(--dsw-alias-border-l2, #dfe1e5)); border-radius: 12px; color: var(--dsw-alias-label-primary, #1f2329); background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #d97706) 8%, var(--dsw-alias-bg-layer-1, #fff)); }
.dim-loopbackRecoveryCopy { min-width: 0; } .dim-loopbackRecoveryCopy { min-width: 0; }
@ -525,11 +520,7 @@ const CSS = String.raw`
} }
@media (max-width: 840px) { @media (max-width: 840px) {
.dim-title { align-items: flex-start; } .dim-title { align-items: flex-start; }
.dim-layout { grid-template-columns: minmax(0, 1fr); gap: 18px; } .dim-channelPickerControl { max-width: none; }
.dim-rail { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.dim-divider { display: none; }
.dim-rail { max-height: none; overflow: visible; padding-right: 1px; }
.dim-channel { min-height: 48px; }
} }
@media (max-width: 560px) { @media (max-width: 560px) {
.dim-title { flex-direction: column; gap: 10px; } .dim-title { flex-direction: column; gap: 10px; }
@ -542,7 +533,8 @@ const CSS = String.raw`
.dim-updateBody { padding: 16px 18px; } .dim-updateBody { padding: 16px 18px; }
.dim-updateFooter { padding: 12px 18px; } .dim-updateFooter { padding: 12px 18px; }
.dim-githubTooltip { right: auto; left: 0; } .dim-githubTooltip { right: auto; left: 0; }
.dim-rail { grid-template-columns: minmax(0, 1fr); } .dim-channelPicker { align-items: stretch; flex-direction: column; gap: 6px; }
.dim-channelPickerControl { max-width: none; }
.dim-loopbackRecovery { align-items: stretch; flex-direction: column; gap: 12px; } .dim-loopbackRecovery { align-items: stretch; flex-direction: column; gap: 12px; }
.dim-loopbackRecoveryAction { width: 100%; } .dim-loopbackRecoveryAction { width: 100%; }
.dim-deliverySectionHeading { align-items: stretch; flex-direction: column; } .dim-deliverySectionHeading { align-items: stretch; flex-direction: column; }

View file

@ -147,6 +147,8 @@ test('IM settings renders nine IM channels plus the AI Office connector', async
assert.match(styles, /\.dim-githubTooltip \{[^}]*top: calc\(100% \+ 8px\);[^}]*transform: translateY\(-3px\);/); assert.match(styles, /\.dim-githubTooltip \{[^}]*top: calc\(100% \+ 8px\);[^}]*transform: translateY\(-3px\);/);
assert.match(styles, /\.dim-githubAction:hover \.dim-githubTooltip, \.dim-githubAction:focus-within \.dim-githubTooltip \{[^}]*opacity: 1;[^}]*visibility: visible;/); assert.match(styles, /\.dim-githubAction:hover \.dim-githubTooltip, \.dim-githubAction:focus-within \.dim-githubTooltip \{[^}]*opacity: 1;[^}]*visibility: visible;/);
assert.doesNotMatch(markup, /\d+ 个渠道|dim-channelCount/); assert.doesNotMatch(markup, /\d+ 个渠道|dim-channelCount/);
assert.match(markup, /dim-channelPickerSelect/);
assert.match(markup, /aria-label="IM 渠道"/);
assert.match(markup, />微信</); assert.match(markup, />微信</);
assert.match(markup, />飞书</); assert.match(markup, />飞书</);
assert.match(markup, />钉钉</); assert.match(markup, />钉钉</);
@ -156,20 +158,22 @@ test('IM settings renders nine IM channels plus the AI Office connector', async
assert.match(markup, />Telegram</); assert.match(markup, />Telegram</);
assert.match(markup, />Discord</); assert.match(markup, />Discord</);
assert.match(markup, />WhatsApp</); assert.match(markup, />WhatsApp</);
assert.match(markup, />AI Office<\/strong><small class="dim-channelNote">(实验功能)<\/small>/); assert.match(markup, />AI Office(实验功能)</);
assert.match(markup, /dim-logoWeixin/); assert.match(markup, /dim-logoWeixin/);
assert.match(markup, /dim-logoFeishu/); assert.match(markup, /data-im-channel-logo="weixin"/);
assert.match(markup, /dim-logoDingtalk/);
assert.match(markup, /dim-logoWecom/);
assert.match(markup, /dim-logoQq/);
assert.match(markup, /dim-logoSlack/);
assert.match(markup, /dim-logoTelegram/);
assert.match(markup, /dim-logoDiscord/);
assert.match(markup, /dim-logoWhatsapp/);
assert.match(markup, /dim-logoOffice/);
assert.match(styles, /\.dim-logoFeishu svg \{ width: 28px; height: 28px; \}/); assert.match(styles, /\.dim-logoFeishu svg \{ width: 28px; height: 28px; \}/);
assert.equal((markup.match(/role="tab"/g) ?? []).length, 10); assert.match(styles, /\.dim-logoWeixin \{/);
assert.equal((markup.match(/aria-selected="true"/g) ?? []).length, 1); assert.match(styles, /\.dim-logoDingtalk \{/);
assert.match(styles, /\.dim-logoWecom \{/);
assert.match(styles, /\.dim-logoQq \{/);
assert.match(styles, /\.dim-logoSlack \{/);
assert.match(styles, /\.dim-logoTelegram \{/);
assert.match(styles, /\.dim-logoDiscord \{/);
assert.match(styles, /\.dim-logoWhatsapp \{/);
assert.match(styles, /\.dim-logoOffice \{/);
assert.equal((markup.match(/<option /g) ?? []).length, 10);
assert.match(markup, /<option[^>]*value="weixin"[^>]*selected=""/);
assert.doesNotMatch(markup, /role="tab"|role="tablist"|role="tabpanel"/);
assert.doesNotMatch(markup, /role="switch"|type="checkbox"/); assert.doesNotMatch(markup, /role="switch"|type="checkbox"/);
assert.doesNotMatch(markup, /dim-chevron|扫码绑定<\/small>|扫码接入<\/small>/); assert.doesNotMatch(markup, /dim-chevron|扫码绑定<\/small>|扫码接入<\/small>/);
assert.doesNotMatch(markup, />INSTANT MESSAGING<|>Channel<|>微信设置</); assert.doesNotMatch(markup, />INSTANT MESSAGING<|>Channel<|>微信设置</);
@ -196,7 +200,7 @@ test('all channel styles use the current Harness theme tokens', async () => {
assert.match(styles, /--dim-blue: var\(--dsw-alias-state-business-primary, #3370ff\)/); assert.match(styles, /--dim-blue: var\(--dsw-alias-state-business-primary, #3370ff\)/);
assert.match( assert.match(
styles, styles,
/\.dim-channel\[aria-selected="true"\][^}]*var\(--dsw-alias-bg-layer-3/, /\.dim-channelPickerControl:focus-within[^}]*var\(--dim-blue\)/,
); );
assert.match( assert.match(
styles, styles,
@ -450,8 +454,8 @@ test('all channel settings states use the DingTalk page treatment', async () =>
assert.match(styles, /\.dim-panel \.dim-confirm \{[^}]*padding: 18px 24px;[^}]*border-top: 1px solid/); assert.match(styles, /\.dim-panel \.dim-confirm \{[^}]*padding: 18px 24px;[^}]*border-top: 1px solid/);
}); });
test('bot cards reuse the same channel brand logos as the channel rail', () => { test('bot cards reuse the same channel brand logos as the channel picker', () => {
const railMarkup = renderToStaticMarkup(React.createElement(IMSettingsTab, { const pickerMarkup = renderToStaticMarkup(React.createElement(IMSettingsTab, {
feishuRpcCall: async () => ({ ok: true, value: {} }), feishuRpcCall: async () => ({ ok: true, value: {} }),
weixinRpcCall: async () => ({ ok: true, value: {} }), weixinRpcCall: async () => ({ ok: true, value: {} }),
dingtalkRpcCall: async () => ({ ok: true, value: {} }), dingtalkRpcCall: async () => ({ ok: true, value: {} }),
@ -472,9 +476,8 @@ test('bot cards reuse the same channel brand logos as the channel rail', () => {
onCancelRemove() {}, onCancelRemove() {},
})); }));
assert.match(railMarkup, /data-im-channel-logo="weixin"/); assert.match(pickerMarkup, /dim-channelPickerSelect/);
assert.match(railMarkup, /data-im-channel-logo="feishu"/); assert.match(pickerMarkup, /data-im-channel-logo="weixin"/);
assert.match(railMarkup, /data-im-channel-logo="wecom"/);
assert.match(accountMarkup, /class="dxw-card dim-botCard"/); assert.match(accountMarkup, /class="dxw-card dim-botCard"/);
assert.match(accountMarkup, /class="dxw-avatar dim-botAvatar"[^]*data-im-channel-logo="weixin"/); assert.match(accountMarkup, /class="dxw-avatar dim-botAvatar"[^]*data-im-channel-logo="weixin"/);
assert.match(accountMarkup, /class="dxw-health dim-botHealth"/); assert.match(accountMarkup, /class="dxw-health dim-botHealth"/);
@ -813,7 +816,7 @@ test('client registers one top-level bilingual IM settings section with a direct
assert.match(markup, /Help &amp; feedback · Open GitHub/); assert.match(markup, /Help &amp; feedback · Open GitHub/);
assert.match(markup, />WeChat<|>Feishu<|>DingTalk<|>WeCom</); assert.match(markup, />WeChat<|>Feishu<|>DingTalk<|>WeCom</);
assert.match(markup, />QQ<[^]*>Slack<[^]*>Telegram<[^]*>Discord<[^]*>WhatsApp</); assert.match(markup, />QQ<[^]*>Slack<[^]*>Telegram<[^]*>Discord<[^]*>WhatsApp</);
assert.match(markup, />AI Office<\/strong><small class="dim-channelNote">\(Experimental\)<\/small>/); assert.match(markup, />AI Office\(Experimental\)</);
assert.doesNotMatch(markup, /[\p{Script=Han}]/u); assert.doesNotMatch(markup, /[\p{Script=Han}]/u);
} finally { } finally {
setImTranslator(null); setImTranslator(null);