dsh-workspace-session-filter 1.0.5 → 1.0.7

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 +10 -4
  2. package/client.js +142 -3
  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.7,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,10 @@ 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
+ - **导航图标可关**:开关出现在两处 —— `设置 → 内置插件 → 会话筛选` 标签页,以及底部筛选条
110
+ 弹出的列表页脚(不打开设置也能拨);
111
+ 偏好存 `localStorage`(键 `dsh-workspace-session-filter:nav.v1`,默认关),
112
+ 开关变化时**注销/重建** `sidebar.panellist` 注册,因此关掉时不会留下一个空图标位。
107
113
  - **筛选条落位**:注册在 `sidebar.footer.action`(设置按钮上方的一行槽位),整条占满宽度;
108
114
  弹层用 `position: fixed` 贴在 chip 上方(`bottom: innerHeight - rect.top + 8`),因为侧边栏
109
115
  容器 `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
@@ -785,6 +838,10 @@ window.__ModuleLoader__.load({
785
838
  view.loading
786
839
  ? h('div', { className: 'dwsf-empty' }, h('div', null, t('empty.loading')))
787
840
  : h(Groups, { view, t, now, onOpen })
841
+ ),
842
+ h('div', { className: 'dwsf-popFoot' },
843
+ h('span', { className: 'dwsf-popFootLabel' }, t('settings.nav')),
844
+ h(NavSwitch, { t })
788
845
  )
789
846
  )
790
847
  : null
@@ -811,6 +868,44 @@ window.__ModuleLoader__.load({
811
868
  return h('div', { ref: rootRef, className: 'dwsf-foot' }, popover, filters)
812
869
  }
813
870
 
871
+ /**
872
+ * The plugin's own page inside Settings → Plugins. One switch owns the
873
+ * optional left-nav entry; the sidebar-foot filter bar is unaffected.
874
+ */
875
+ function NavSwitch(props) {
876
+ const [on, setOn] = useState(navVisible)
877
+
878
+ useEffect(() => subscribeNav((value) => setOn(value)), [])
879
+
880
+ return h('button', {
881
+ type: 'button',
882
+ role: 'switch',
883
+ className: 'dwsf-switch',
884
+ 'aria-checked': on ? 'true' : 'false',
885
+ 'aria-label': props.t('settings.nav'),
886
+ 'data-on': on ? 'true' : 'false',
887
+ onClick: () => setNavVisible(!on),
888
+ },
889
+ h('span', { className: 'dwsf-switchTrack', 'data-on': on ? 'true' : 'false', 'aria-hidden': 'true' },
890
+ h('span', { className: 'dwsf-switchThumb' })
891
+ )
892
+ )
893
+ }
894
+
895
+ function SettingsPage(props) {
896
+ const t = props.t
897
+
898
+ return h('div', { className: 'dwsf-settings' },
899
+ h('div', { className: 'dwsf-settingRow' },
900
+ h('div', { className: 'dwsf-settingCopy' },
901
+ h('span', { className: 'dwsf-settingLabel' }, t('settings.nav')),
902
+ h('span', { className: 'dwsf-settingHint' }, t('settings.navHint'))
903
+ ),
904
+ h(NavSwitch, { t })
905
+ )
906
+ )
907
+ }
908
+
814
909
  /** Sidebar entry: guards the standard hooks and keeps a crash out of the sidebar. */
815
910
  function FooterEntry(props) {
816
911
  const ready = typeof props.useSessions === 'function'
@@ -919,7 +1014,20 @@ window.__ModuleLoader__.load({
919
1014
  .dwsf-footRailButton:hover,.dwsf-footRailButton[data-active="true"]{background:var(--dsw-alias-interactive-bg-hover)}
920
1015
  .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
1016
  .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}
1017
+ .dwsf-settings{display:flex;flex-direction:column;gap:8px;color:var(--dsw-alias-label-primary)}
1018
+ .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)}
1019
+ .dwsf-settingCopy{display:flex;flex-direction:column;gap:2px;min-width:0}
1020
+ .dwsf-settingLabel{font-size:13px;line-height:20px}
1021
+ .dwsf-settingHint{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
1022
+ .dwsf-switch{flex:none;margin-top:1px;padding:0;border:0;background:0 0;cursor:pointer;border-radius:999px;line-height:0}
1023
+ .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}
1024
+ .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}
1025
+ .dwsf-switchTrack[data-on="true"]{background:var(--dsw-alias-brand-primary);box-shadow:none}
1026
+ .dwsf-switchThumb{width:16px;height:16px;border-radius:999px;background:var(--dsw-alias-bg-base);transition:transform .16s ease}
1027
+ .dwsf-switchTrack[data-on="true"] .dwsf-switchThumb{transform:translateX(14px)}
922
1028
  .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}
1029
+ .dwsf-popFoot{flex:none;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 12px;border-top:.5px solid var(--dsw-alias-border-l2);font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
1030
+ .dwsf-popFootLabel{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
923
1031
  .dwsf-popHead{flex:none;display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:44px;padding:10px 12px 4px}
924
1032
  .dwsf-popTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:20px}
925
1033
  .dwsf-popCount{flex:none;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:16px;font-variant-numeric:tabular-nums}
@@ -986,12 +1094,43 @@ window.__ModuleLoader__.load({
986
1094
  inject: () => ({ t, onOpen: openSession }),
987
1095
  }, FooterEntry)), 'workspace-session-filter: sidebar footer')
988
1096
 
989
- ctx.effect(() => ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({
990
- name: 'sidebar.panellist',
1097
+ ctx.effect(() => ctx.slots.inject('settings.plugins.tab', () => ctx.slots.register({
1098
+ name: 'settings.plugins.tab',
991
1099
  id: PANEL_ID,
992
1100
  order: 30,
993
1101
  label: () => t('panel.label'),
994
- }, PanelGlyph)), 'workspace-session-filter: icon')
1102
+ inject: () => ({ t }),
1103
+ }, SettingsPage)), 'workspace-session-filter: settings page')
1104
+
1105
+ /* The nav entry is opt-in, so its registration is created and dropped while
1106
+ * the page runs instead of being decided once at apply time. */
1107
+ ctx.effect(() => {
1108
+ let dispose = null
1109
+ const sync = () => {
1110
+ if (navVisible() && !dispose) {
1111
+ dispose = ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({
1112
+ name: 'sidebar.panellist',
1113
+ id: PANEL_ID,
1114
+ order: 30,
1115
+ label: () => t('panel.label'),
1116
+ }, PanelGlyph))
1117
+ return
1118
+ }
1119
+ if (!navVisible() && dispose) {
1120
+ dispose()
1121
+ dispose = null
1122
+ }
1123
+ }
1124
+ const off = subscribeNav(sync)
1125
+ sync()
1126
+ return () => {
1127
+ off()
1128
+ if (dispose) {
1129
+ dispose()
1130
+ dispose = null
1131
+ }
1132
+ }
1133
+ }, 'workspace-session-filter: nav icon')
995
1134
  },
996
1135
  }
997
1136
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-workspace-session-filter",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Workspace session filter panel with status and recency filters.",
5
5
  "type": "module",
6
6
  "exports": {