dsh-workspace-session-filter 1.0.4 → 1.0.6

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.
Files changed (3) hide show
  1. package/README.md +9 -4
  2. package/client.js +132 -4
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,7 +6,8 @@
6
6
 
7
7
  1. **侧边栏底部的筛选条** —— 设置按钮上方单独一行,`运行中` / `待确认` / `未读` 三个
8
8
  chip(各自带命中数)。点一下向上弹出按工作区分组的会话列表,点行直接进会话,是日常主入口;
9
- 2. 侧边栏面板列表里的「会话筛选」图标 —— 打开中间栏的完整视图,顶部是
9
+ 2. 侧边栏面板列表里的「会话筛选」图标(**默认不显示**)—— 在
10
+ `设置 → 插件 → 会话筛选` 里打开开关后出现,指向中间栏的完整视图,顶部是
10
11
  `全部 / 运行中 / 等待确认 / 未读 / 更多` 筛选器,`更多` 里是 `最近一天 / 最近三天 /
11
12
  最近一周 / 最近一个月`(外加 `不限时间`)。
12
13
 
@@ -80,11 +81,12 @@ dsh plugin --profile desktop remove dsh-workspace-session-filter
80
81
  装好后:
81
82
 
82
83
  - 侧边栏**底部**(设置按钮上方、单独一行)出现 `运行中 N` / `待确认 N` / `未读 N` 筛选条;
83
- - 侧边栏面板列表里出现「会话筛选」图标,指向中间栏的完整视图。
84
+ - 面板列表里**不会**自动多出图标;需要完整视图时,到
85
+ `设置 → 插件 → 会话筛选` 打开「在左侧导航显示」,图标才出现(开关状态存本地,刷新后保留)。
84
86
 
85
87
  卸载:`plugin_manager({ action: "remove_bundle", target: "dsh-workspace-session-filter" })`。
86
88
 
87
- > 包名 `dsh-workspace-session-filter` 与仓库同名。截至 1.0.3,npm registry、
89
+ > 包名 `dsh-workspace-session-filter` 与仓库同名。截至 1.0.6,npm registry、
88
90
  > GitHub、release tarball、本地目录四种来源都能装;registry 上是 `npm publish` 发出的
89
91
  > 正式版本,`repository` 字段指回本仓库。
90
92
 
@@ -92,7 +94,7 @@ dsh plugin --profile desktop remove dsh-workspace-session-filter
92
94
 
93
95
  | 文件 | 作用 |
94
96
  | --- | --- |
95
- | `client.js` | 全部界面逻辑:字典、图标、底部筛选条 + 面板 + 侧边栏图标三个 slot 注册、内联样式 |
97
+ | `client.js` | 全部界面逻辑:字典、图标、底部筛选条 / 面板 / 设置页三处注册 + 按开关动态注册的侧边栏图标、内联样式 |
96
98
  | `index.js` | Host 半边(本插件不需要 Host 能力,留空) |
97
99
  | `cordis.patch.yml` | 把 `session-filter` 条目插入 profile 组合 |
98
100
  | `package.json` | 包元信息 + `dsh.client` / `dsh.bundle` 声明 |
@@ -104,6 +106,9 @@ dsh plugin --profile desktop remove dsh-workspace-session-filter
104
106
  - **面板落位**:`sidebar.panellist` 的 `id` 与 `main` 的 `key` 复用同一个字符串
105
107
  `session-filter`,侧边栏图标因此直接指向本面板;`order: 30` 排在插件、定时任务、
106
108
  上下文之后。
109
+ - **导航图标可关**:注册在 `设置 → 插件` 的 `settings.plugins.tab` 页面只放一个开关;
110
+ 偏好存 `localStorage`(键 `dsh-workspace-session-filter:nav.v1`,默认关),
111
+ 开关变化时**注销/重建** `sidebar.panellist` 注册,因此关掉时不会留下一个空图标位。
107
112
  - **筛选条落位**:注册在 `sidebar.footer.action`(设置按钮上方的一行槽位),整条占满宽度;
108
113
  弹层用 `position: fixed` 贴在 chip 上方(`bottom: innerHeight - rect.top + 8`),因为侧边栏
109
114
  容器 `overflow` 会裁掉绝对定位的浮层。宽版渲染三个 chip,窄条(`wide: false`)渲染一个
package/client.js CHANGED
@@ -31,6 +31,8 @@ window.__ModuleLoader__.load({
31
31
  'filter.awaiting': '等待确认',
32
32
  'filter.awaitingShort': '待确认',
33
33
  'filter.unread': '未读',
34
+ 'settings.nav': '在左侧导航显示',
35
+ 'settings.navHint': '打开后左侧导航多一个「会话筛选」入口,可打开完整筛选面板;关闭时侧边栏底部的筛选条照常可用。',
34
36
  'filter.more': '更多',
35
37
  'filter.reset': '清除筛选',
36
38
  'filter.aria': '会话筛选条件',
@@ -70,6 +72,8 @@ window.__ModuleLoader__.load({
70
72
  'filter.awaiting': 'Awaiting confirmation',
71
73
  'filter.awaitingShort': 'Awaiting',
72
74
  'filter.unread': 'Unread',
75
+ 'settings.nav': 'Show in the left navigation',
76
+ 'settings.navHint': 'Adds a Session filter entry to the left navigation that opens the full panel. The filter bar at the foot of the sidebar keeps working either way.',
73
77
  'filter.more': 'More',
74
78
  'filter.reset': 'Clear filters',
75
79
  'filter.aria': 'Session filters',
@@ -232,6 +236,55 @@ window.__ModuleLoader__.load({
232
236
  }
233
237
  }
234
238
 
239
+ /* ---------------------------- navigation toggle --------------------------- *
240
+ * The left-nav icon is opt-in: the sidebar already carries four panel cells and
241
+ * most visits start at the sidebar-foot bar. The preference shares the same
242
+ * localStorage store as the seen stamps, and the registration follows it, so a
243
+ * hidden entry leaves no empty cell behind the panel list.
244
+ * ------------------------------------------------------------------ */
245
+ const NAV_KEY = 'dsh-workspace-session-filter:nav.v1'
246
+ const navState = { value: null, listeners: new Set() }
247
+
248
+ function navStorage() {
249
+ try {
250
+ return typeof localStorage === 'undefined' ? null : localStorage
251
+ } catch (error) {
252
+ return null
253
+ }
254
+ }
255
+
256
+ function navVisible() {
257
+ if (navState.value !== null) return navState.value
258
+ const store = navStorage()
259
+ let value = false
260
+ if (store) {
261
+ try { value = store.getItem(NAV_KEY) === '1' } catch (error) { value = false }
262
+ }
263
+ navState.value = value
264
+ return value
265
+ }
266
+
267
+ function setNavVisible(next) {
268
+ const value = next === true
269
+ if (navVisible() === value) return
270
+ navState.value = value
271
+ const store = navStorage()
272
+ if (store) {
273
+ try {
274
+ if (value) store.setItem(NAV_KEY, '1')
275
+ else store.removeItem(NAV_KEY)
276
+ } catch (error) { /* storage may be full or blocked */ }
277
+ }
278
+ for (const listener of Array.from(navState.listeners)) {
279
+ try { listener(value) } catch (error) { console.error('[workspace-session-filter] nav listener failed', error) }
280
+ }
281
+ }
282
+
283
+ function subscribeNav(listener) {
284
+ navState.listeners.add(listener)
285
+ return () => { navState.listeners.delete(listener) }
286
+ }
287
+
235
288
  function loadSeen() {
236
289
  if (seenCache.loaded) return seenCache.map
237
290
  seenCache.loaded = true
@@ -811,6 +864,39 @@ window.__ModuleLoader__.load({
811
864
  return h('div', { ref: rootRef, className: 'dwsf-foot' }, popover, filters)
812
865
  }
813
866
 
867
+ /**
868
+ * The plugin's own page inside Settings → Plugins. One switch owns the
869
+ * optional left-nav entry; the sidebar-foot filter bar is unaffected.
870
+ */
871
+ function SettingsPage(props) {
872
+ const t = props.t
873
+ const [on, setOn] = useState(navVisible)
874
+
875
+ useEffect(() => subscribeNav((value) => setOn(value)), [])
876
+
877
+ return h('div', { className: 'dwsf-settings' },
878
+ h('div', { className: 'dwsf-settingRow' },
879
+ h('div', { className: 'dwsf-settingCopy' },
880
+ h('span', { className: 'dwsf-settingLabel' }, t('settings.nav')),
881
+ h('span', { className: 'dwsf-settingHint' }, t('settings.navHint'))
882
+ ),
883
+ h('button', {
884
+ type: 'button',
885
+ role: 'switch',
886
+ className: 'dwsf-switch',
887
+ 'aria-checked': on ? 'true' : 'false',
888
+ 'aria-label': t('settings.nav'),
889
+ 'data-on': on ? 'true' : 'false',
890
+ onClick: () => setNavVisible(!on),
891
+ },
892
+ h('span', { className: 'dwsf-switchTrack', 'data-on': on ? 'true' : 'false', 'aria-hidden': 'true' },
893
+ h('span', { className: 'dwsf-switchThumb' })
894
+ )
895
+ )
896
+ )
897
+ )
898
+ }
899
+
814
900
  /** Sidebar entry: guards the standard hooks and keeps a crash out of the sidebar. */
815
901
  function FooterEntry(props) {
816
902
  const ready = typeof props.useSessions === 'function'
@@ -906,7 +992,7 @@ window.__ModuleLoader__.load({
906
992
  .dwsf-emptyHint{color:var(--dsw-alias-label-caption);font-size:13px;line-height:20px}
907
993
  [data-slot="sidebar.footer.action"]{display:flex !important;flex-wrap:wrap;width:100%;min-width:0;align-items:flex-start;column-gap:0;row-gap:0}
908
994
  .dwsf-foot{flex:1 0 100%;display:flex;align-items:center;min-width:0;max-width:100%;margin:8px 0 2px;padding:0 2px}
909
- .dwsf-footChips{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:4px 2px;min-width:0;width:100%}
995
+ .dwsf-footChips{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-start;gap:4px 2px;min-width:0;width:100%}
910
996
  .dwsf-footChip{flex:0 1 auto;min-width:0;max-width:100%;height:28px;display:inline-flex;align-items:center;justify-content:center;gap:3px;padding:0 7px;border:0;border-radius:999px;background:0 0;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:12px;line-height:18px;white-space:nowrap;cursor:pointer;overflow:hidden}
911
997
  .dwsf-footChip:hover{background:var(--dsw-alias-interactive-bg-hover)}
912
998
  .dwsf-footChip[data-active="true"]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
@@ -919,6 +1005,17 @@ window.__ModuleLoader__.load({
919
1005
  .dwsf-footRailButton:hover,.dwsf-footRailButton[data-active="true"]{background:var(--dsw-alias-interactive-bg-hover)}
920
1006
  .dwsf-footRailButton:focus-visible{outline:var(--dsw-focus-ring-width,2px) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:-2px}
921
1007
  .dwsf-footBadge{position:absolute;top:4px;right:2px;min-width:15px;height:15px;padding:0 3px;border-radius:999px;background:var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-label-primary-inverted);font-size:9px;line-height:15px;text-align:center;font-variant-numeric:tabular-nums}
1008
+ .dwsf-settings{display:flex;flex-direction:column;gap:8px;color:var(--dsw-alias-label-primary)}
1009
+ .dwsf-settingRow{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:12px 14px;border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1)}
1010
+ .dwsf-settingCopy{display:flex;flex-direction:column;gap:2px;min-width:0}
1011
+ .dwsf-settingLabel{font-size:13px;line-height:20px}
1012
+ .dwsf-settingHint{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
1013
+ .dwsf-switch{flex:none;margin-top:1px;padding:0;border:0;background:0 0;cursor:pointer;border-radius:999px;line-height:0}
1014
+ .dwsf-switch:focus-visible{outline:var(--dsw-focus-ring-width,2px) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
1015
+ .dwsf-switchTrack{display:inline-flex;align-items:center;width:34px;height:20px;padding:2px;border-radius:999px;background:var(--dsw-alias-bg-layer-2);box-shadow:inset 0 0 0 .5px var(--dsw-alias-border-l2);transition:background .16s ease,box-shadow .16s ease}
1016
+ .dwsf-switchTrack[data-on="true"]{background:var(--dsw-alias-brand-primary);box-shadow:none}
1017
+ .dwsf-switchThumb{width:16px;height:16px;border-radius:999px;background:var(--dsw-alias-bg-base);transition:transform .16s ease}
1018
+ .dwsf-switchTrack[data-on="true"] .dwsf-switchThumb{transform:translateX(14px)}
922
1019
  .dwsf-pop{position:fixed;z-index:60;display:flex;flex-direction:column;width:min(340px,calc(100vw - 24px));max-height:60vh;border-radius:12px;background:var(--dsw-specific-menu);backdrop-filter:var(--dsw-menu-backdrop-filter);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);box-shadow:var(--dsw-elevation-prominent);overflow:hidden}
923
1020
  .dwsf-popHead{flex:none;display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:44px;padding:10px 12px 4px}
924
1021
  .dwsf-popTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:20px}
@@ -986,12 +1083,43 @@ window.__ModuleLoader__.load({
986
1083
  inject: () => ({ t, onOpen: openSession }),
987
1084
  }, FooterEntry)), 'workspace-session-filter: sidebar footer')
988
1085
 
989
- ctx.effect(() => ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({
990
- name: 'sidebar.panellist',
1086
+ ctx.effect(() => ctx.slots.inject('settings.plugins.tab', () => ctx.slots.register({
1087
+ name: 'settings.plugins.tab',
991
1088
  id: PANEL_ID,
992
1089
  order: 30,
993
1090
  label: () => t('panel.label'),
994
- }, PanelGlyph)), 'workspace-session-filter: icon')
1091
+ inject: () => ({ t }),
1092
+ }, SettingsPage)), 'workspace-session-filter: settings page')
1093
+
1094
+ /* The nav entry is opt-in, so its registration is created and dropped while
1095
+ * the page runs instead of being decided once at apply time. */
1096
+ ctx.effect(() => {
1097
+ let dispose = null
1098
+ const sync = () => {
1099
+ if (navVisible() && !dispose) {
1100
+ dispose = ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({
1101
+ name: 'sidebar.panellist',
1102
+ id: PANEL_ID,
1103
+ order: 30,
1104
+ label: () => t('panel.label'),
1105
+ }, PanelGlyph))
1106
+ return
1107
+ }
1108
+ if (!navVisible() && dispose) {
1109
+ dispose()
1110
+ dispose = null
1111
+ }
1112
+ }
1113
+ const off = subscribeNav(sync)
1114
+ sync()
1115
+ return () => {
1116
+ off()
1117
+ if (dispose) {
1118
+ dispose()
1119
+ dispose = null
1120
+ }
1121
+ }
1122
+ }, 'workspace-session-filter: nav icon')
995
1123
  },
996
1124
  }
997
1125
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-workspace-session-filter",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Workspace session filter panel with status and recency filters.",
5
5
  "type": "module",
6
6
  "exports": {