@mzzsfy/dsh-cron-board 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/client.js +56 -23
- package/test/switch-guard.test.mjs +12 -0
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
挂载权交给用户,运行期不自动切换:
|
|
22
22
|
|
|
23
23
|
- **默认:主界面**。无论是否安装 dsh-better-sidebar,看板始终在主界面(原生侧栏「定时任务」入口 + 中心列看板,激活时隐藏会话区,面板互斥)
|
|
24
|
-
- **手动移入**:设置>插件
|
|
24
|
+
- **手动移入**:设置>插件 页「定时任务」卡片(官方 PluginCard 形制:折叠卡壳,cb-pc 镜像)提供「看板移入 better-sidebar 侧边栏」开关;开启即注册扩展槽 tab(显式 openTab 落入工作台)并拆除主界面形态
|
|
25
25
|
- **不自动回退**:移入后页签关闭/类型禁用均不再自动返回主界面;把开关关掉即回到主界面形态(轮询感知,约一个 tick 周期内生效)
|
|
26
26
|
- **开关可用性**:已安装 better-sidebar 时可切换;未安装时开关呈禁用态仅展示(保持主界面,无副作用)
|
|
27
27
|
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -136,8 +136,23 @@ body{
|
|
|
136
136
|
.cb-switch input[type="checkbox"]:disabled + .cb-switch__track{opacity:.4;cursor:not-allowed}
|
|
137
137
|
.cb-switch:not(:has(input[type="checkbox"]:disabled)):hover .cb-switch__track{background:var(--cb-accent-deep);opacity:.85}
|
|
138
138
|
.cb-switch input[type="checkbox"]:checked:not(:disabled) + .cb-switch__track:hover{background:var(--cb-accent-deep)}
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
/* 设置>插件页卡片(cb-pc):镜像官方 PluginCard/SubagentModelSelectionCard 形制,数值与令牌直取官方 CSS Module;
|
|
140
|
+
官方壳未导出,slot 契约声明卡片外观归插件自持,同页同 dsw-alias 令牌即同观感(含深色主题) */
|
|
141
|
+
.cb-pc{border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-3);border-radius:16px;list-style:none;transition:border-color .16s,background .16s}
|
|
142
|
+
.cb-pc:hover{border-color:var(--dsw-alias-label-dimmed)}
|
|
143
|
+
.cb-pc--open{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}
|
|
144
|
+
.cb-pc__head{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}
|
|
145
|
+
.cb-pc__head:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}
|
|
146
|
+
.cb-pc__text{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}
|
|
147
|
+
.cb-pc__name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}
|
|
148
|
+
.cb-pc__desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
|
|
149
|
+
.cb-pc__chevron{color:var(--dsw-alias-label-tertiary);flex:none;display:inline-flex;transition:transform .16s}
|
|
150
|
+
.cb-pc--open .cb-pc__chevron{transform:rotate(180deg)}
|
|
151
|
+
.cb-pc__body{border-top:.5px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}
|
|
152
|
+
.cb-pc__row{gap:6px;padding:12px 0;display:grid}
|
|
153
|
+
.cb-pc__rowLine{color:var(--dsw-alias-label-primary);justify-content:space-between;align-items:flex-start;gap:16px;font-size:13px;line-height:1.5;display:flex}
|
|
154
|
+
.cb-pc__rowLabel{flex:1;min-width:0}
|
|
155
|
+
.cb-pc__hint{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
|
|
141
156
|
.cb-table{display:flex;flex-direction:column;gap:var(--cb-space-1);font-size:var(--cb-font-md)}
|
|
142
157
|
.cb-row{display:flex;align-items:center;gap:var(--cb-space-3);border:1px solid var(--cb-border);border-radius:var(--cb-radius-md);padding:var(--cb-space-2) var(--cb-space-4);flex-wrap:wrap;transition:background .15s,border-color .15s}
|
|
143
158
|
.cb-row:hover{background:var(--cb-hover)}
|
|
@@ -147,7 +162,7 @@ body{
|
|
|
147
162
|
.cb-modal{background:var(--cb-bg);color:var(--cb-text);border:1px solid var(--cb-border);border-radius:var(--cb-radius-lg);padding:var(--cb-space-6);max-width:720px;width:min(720px,92vw);max-height:84vh;overflow:auto;display:flex;flex-direction:column;gap:var(--cb-space-4);box-shadow:0 12px 40px rgb(31 35 41 / 18%);animation:cb-pop-in .2s ease-out}
|
|
148
163
|
@keyframes cb-fade-in{from{opacity:0}to{opacity:1}}
|
|
149
164
|
@keyframes cb-pop-in{from{opacity:0;transform:translateY(8px) scale(.98)}to{opacity:1;transform:none}}
|
|
150
|
-
@media (prefers-reduced-motion: reduce){.cb-modal-mask,.cb-modal{animation:none}.cb-card,.cb-row,.cb-button,.cb-pill,.cb-icon,.cb-switch__track{transition:none}}
|
|
165
|
+
@media (prefers-reduced-motion: reduce){.cb-modal-mask,.cb-modal{animation:none}.cb-card,.cb-row,.cb-button,.cb-pill,.cb-icon,.cb-switch__track,.cb-pc,.cb-pc__chevron{transition:none}}
|
|
151
166
|
.cb-modal-head{display:flex;align-items:center;justify-content:space-between;gap:var(--cb-space-3);padding-bottom:var(--cb-space-3);border-bottom:1px solid var(--cb-border)}
|
|
152
167
|
.cb-modal-title{font-size:16px;font-weight:600}
|
|
153
168
|
.cb-modal-body{display:flex;flex-direction:column;gap:var(--cb-space-5)}
|
|
@@ -239,7 +254,8 @@ if (typeof window !== 'undefined' && window.__ModuleLoader__) {
|
|
|
239
254
|
// 规约 switch:input 锚定状态选择器,视觉由 track+thumb 呈现
|
|
240
255
|
function switchToggle(props) {
|
|
241
256
|
return h('label', { className: 'cb-switch' },
|
|
242
|
-
h('input', { type: 'checkbox', checked: props.checked, disabled: props.disabled, onChange: props.onChange
|
|
257
|
+
h('input', { type: 'checkbox', checked: props.checked, disabled: props.disabled, onChange: props.onChange,
|
|
258
|
+
'aria-label': props.ariaLabel }),
|
|
243
259
|
h('span', { className: 'cb-switch__track' }, h('span', { className: 'cb-switch__thumb' })),
|
|
244
260
|
props.label ? h('span', { className: 'cb-meta' }, props.label) : null)
|
|
245
261
|
}
|
|
@@ -755,15 +771,25 @@ if (typeof window !== 'undefined' && window.__ModuleLoader__) {
|
|
|
755
771
|
const OTHER_PANEL_ACTIVE_ATTRS = ['data-dsh-atb-active', 'data-dsh-taskboard-active', 'data-dsh-ssh-active']
|
|
756
772
|
const SIDEBAR_ROW_SELECTOR = '[class*="sessionRow"], [class*="projectRow"], [class*="searchResultRow"], [class*="searchResultWorkspace"], [class*="newSession"]'
|
|
757
773
|
|
|
758
|
-
//
|
|
774
|
+
// 设置>插件页卡片:官方 PluginCard 形制(名称/描述头部 + chevron 折叠 + 行内开关);better-sidebar 在场可切换,否则仅展示禁用态
|
|
775
|
+
const CARD_TITLE = '定时任务'
|
|
776
|
+
const CARD_TOGGLE_LABEL = '看板移入 better-sidebar 侧边栏'
|
|
777
|
+
const readSidebarTabPref = (outcome) => outcome.ok && outcome.data &&
|
|
778
|
+
typeof outcome.data.ui === 'object' && outcome.data.ui !== null && outcome.data.ui.sidebarTab === true
|
|
779
|
+
const CHEVRON_DOWN = () => h('svg', { viewBox: '0 0 14 14', width: 14, height: 14, fill: 'none',
|
|
780
|
+
stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': 'true' },
|
|
781
|
+
h('path', { d: 'm3.5 5.5 3.5 3.5 3.5-3.5' }))
|
|
782
|
+
|
|
759
783
|
function CronBoardPluginCard() {
|
|
760
784
|
const [enabled, setEnabled] = useState(null)
|
|
761
785
|
const [busy, setBusy] = useState(false)
|
|
762
|
-
const [
|
|
786
|
+
const [open, setOpen] = useState(false)
|
|
787
|
+
// 渲染时重探:侧栏面晚于设置页挂载时,enabled 到达的重渲染会自动纠正禁用态
|
|
788
|
+
const sidebarReady = Boolean(document.querySelector(SIDEBAR_ROOT_SELECTOR))
|
|
763
789
|
useEffect(() => {
|
|
764
790
|
let alive = true
|
|
765
791
|
request('GET', 'status').then((outcome) => {
|
|
766
|
-
if (alive) setEnabled(outcome
|
|
792
|
+
if (alive) setEnabled(readSidebarTabPref(outcome))
|
|
767
793
|
})
|
|
768
794
|
return () => { alive = false }
|
|
769
795
|
}, [])
|
|
@@ -774,16 +800,25 @@ if (typeof window !== 'undefined' && window.__ModuleLoader__) {
|
|
|
774
800
|
if (outcome.ok && outcome.data && outcome.data.ui) setEnabled(outcome.data.ui.sidebarTab === true)
|
|
775
801
|
setBusy(false)
|
|
776
802
|
}
|
|
777
|
-
return h('
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
h('div', { className: 'cb-
|
|
785
|
-
|
|
786
|
-
|
|
803
|
+
return h('li', { className: 'cb-pc' + (open ? ' cb-pc--open' : '') },
|
|
804
|
+
h('button', { type: 'button', className: 'cb-pc__head', 'aria-expanded': open,
|
|
805
|
+
'aria-label': (open ? '折叠' : '展开') + ': ' + CARD_TITLE, onClick: () => setOpen(!open) },
|
|
806
|
+
h('span', { className: 'cb-pc__text' },
|
|
807
|
+
h('span', { className: 'cb-pc__name' }, CARD_TITLE),
|
|
808
|
+
h('span', { className: 'cb-pc__desc' }, '计划任务看板、环境变量与执行日志')),
|
|
809
|
+
h('span', { className: 'cb-pc__chevron' }, h(CHEVRON_DOWN))),
|
|
810
|
+
open ? h('div', { className: 'cb-pc__body' },
|
|
811
|
+
h('div', { className: 'cb-pc__row' },
|
|
812
|
+
h('div', { className: 'cb-pc__rowLine' },
|
|
813
|
+
h('span', { className: 'cb-pc__rowLabel' }, CARD_TOGGLE_LABEL),
|
|
814
|
+
switchToggle({
|
|
815
|
+
checked: enabled === true,
|
|
816
|
+
disabled: busy || enabled === null || !sidebarReady,
|
|
817
|
+
onChange: toggle,
|
|
818
|
+
ariaLabel: CARD_TOGGLE_LABEL,
|
|
819
|
+
})),
|
|
820
|
+
h('p', { className: 'cb-pc__hint' },
|
|
821
|
+
sidebarReady ? '开启后看板以侧边栏页签呈现,关闭时始终使用主界面。' : '需安装 better-sidebar 后方可切换;未安装时看板始终使用主界面。'))) : null)
|
|
787
822
|
}
|
|
788
823
|
|
|
789
824
|
// better-sidebar tab:注册即单实例(single);v0.19.0 起注册只入「可打开」目录,
|
|
@@ -958,12 +993,10 @@ if (typeof window !== 'undefined' && window.__ModuleLoader__) {
|
|
|
958
993
|
const prefWatch = setInterval(async () => {
|
|
959
994
|
try {
|
|
960
995
|
const outcome = await request('GET', 'status')
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
applyPref()
|
|
966
|
-
}
|
|
996
|
+
const next = readSidebarTabPref(outcome)
|
|
997
|
+
if (next !== sidebarTabOn) {
|
|
998
|
+
sidebarTabOn = next
|
|
999
|
+
applyPref()
|
|
967
1000
|
}
|
|
968
1001
|
} catch { /* 轮询失败保形态不变,下轮重试 */ }
|
|
969
1002
|
}, 5 * 1000)
|
|
@@ -41,6 +41,18 @@ test('checked/focus-visible/disabled 三态均以 checkbox 锚定', () => {
|
|
|
41
41
|
assert.match(source, /\.cb-switch input\[type="checkbox"\]:disabled \+/)
|
|
42
42
|
})
|
|
43
43
|
|
|
44
|
+
test('插件页卡片为官方 PluginCard 形制:li 卡壳 + 头部折叠 + 令牌同源', () => {
|
|
45
|
+
// Given 官方插件卡片 = ul.cards > li.PluginCard(边框卡壳 + 名称/描述头部 + chevron 折叠)
|
|
46
|
+
// When 本插件卡片注册进同一列表(slot 契约:外观归插件自持,官方壳未导出)
|
|
47
|
+
// Then 静态锁定 cb-pc 镜像形制,防止退化回裸 div 平铺
|
|
48
|
+
assert.match(source, /h\('li', \{ className: 'cb-pc/, '卡片根元素必须是 li(官方列表 ul.cards 的合法子元素)')
|
|
49
|
+
assert.match(source, /\.cb-pc\{border:\.5px solid var\(--dsw-alias-border-l4\)/, '卡壳边框/底色令牌必须与官方 PluginCard 同源')
|
|
50
|
+
assert.match(source, /h\('button', \{ type: 'button', className: 'cb-pc__head'/, '缺少官方形制的头部按钮')
|
|
51
|
+
assert.match(source, /aria-expanded/, '头部必须可折叠并暴露 aria-expanded')
|
|
52
|
+
assert.match(source, /\.cb-pc__hint\{[^}]*var\(--dsw-alias-label-tertiary\)/, '提示文本令牌必须与官方 hint 同源')
|
|
53
|
+
assert.doesNotMatch(source, /'cb-settings'/, '旧裸 div 平铺形态已移除')
|
|
54
|
+
})
|
|
55
|
+
|
|
44
56
|
test('client.js 无顶层词法声明(经典 script 书挡内安全)', () => {
|
|
45
57
|
// IIFE 书挡内允许任意声明;守卫确认整文件被书挡包裹
|
|
46
58
|
const trimmed = source.trim()
|