dsh-entry-drawer:用一個開關把側欄入口列表收起來

前言

DSH 的左側欄上半部分是一列「全局面板入口」:插件、插件市場、自動化任務、遠程窗口、任務看板、記憶系統。每裝一個插件,這裏就多一行。列表越長,會話歷史就被擠得越靠下,而會話歷史纔是日常真正在用的那塊。

dsh-entry-drawer 就是針對這個痛點的插件。維護者是 xingzhen199186,分類爲客戶端插件,協議 MIT,npm 上版本 0.1.0,倉庫星標爲 0。它不做任何新功能,只借平臺留給第三方插件的唯一座位 sidebar.panellist,在入口列表最前面放一個開關行,把其餘入口整塊收起,把空間還給會話歷史。

這是什麼

一句話定位:在 DSH 0.2.x 的側欄入口列表最前面加一行開關,點擊後隱藏除自己以外的所有入口,再點一次恢復。

瀏覽器端的能力只有這一條,但做得比較完整:收起狀態會被記住,重載頁面、重啓桌面端都保持上次的選擇;展開/收起都不會改動 DOM,收起通過一條 CSS 規則表達。

平臺側沒有被它修改。座位(sidebar.panellist)是界面上一個固定位置,插件只能往裏放自己的東西,改不了別人放的東西;殼層掌管這些行的盒子、行高和點擊行爲,平臺本身也沒有「把入口收成一組」的能力。

你會看到什麼

展開時(默認狀態),新增的一行位於左側欄入口列表第一行,就在會話歷史上方,內容是一對朝上的雙箭頭圖標加文字「入口收納」(英文界面顯示 Entry drawer),下方是原有的全部入口,一個不少。

點擊這一行:

  • 其餘入口整塊隱藏,直接不佔位置,不是變灰;
  • 會話歷史上移,多出幾行空間;
  • 箭頭翻成朝下,開關行留在原位。

再點一次,全部入口回來,箭頭朝上。

收起狀態存兩份:瀏覽器 cookie(鍵 dsh-entry-drawer,一年有效)和 localStorage(鍵 dsh-entry-drawer:collapsed)。桌面端每次啓動會在 127.0.0.1 上換一個端口,而瀏覽器按「協議 + 主機 + 端口」分區存 localStorage,cookie 只認主機不認端口,所以跨端口重啓後依然記得。

一條已實測的行爲:2026-11-05 在桌面端 0.2.2-rc.1 上驗證過,先收起再展開正常;之後新裝的第三方插件多出來的入口也會被一併收進去,不需要重新配置。

安裝

前提:DSH 0.2.x,本機有可用的 Node.js 與 pnpm(dsh plugin 只是 pnpm 的轉發器)。三種安裝方式按場景選一種。

從 npm 安裝(推薦):

dsh plugin --profile web add dsh-entry-drawer

web 換成你實際在用的 profile 名。

跟最新代碼、或 npm 還沒同步到新版時,從 GitHub 安裝:

dsh plugin --profile web add github:xingzhen199186/dsh-entry-drawer

改代碼時想一邊改一邊看效果,用本地目錄安裝:

dsh plugin --profile web add ./dsh-entry-drawer

也可以在插件管理器裏選「從目錄安裝」並指向該文件夾。注意這種方式 profile 裏記的是目錄路徑,之後移動或刪除這個文件夾,插件就加載不起來了。

裝完必須重載:Web 端刷新頁面即可;桌面端建議直接重啓應用,因爲跑在瀏覽器裏的那一半(client.js)要等窗口重新加載纔會掛上,桌面端默認沒有客戶端熱更新。

另外,桌面端請優先在應用內安裝。桌面端的 profile 由應用自己託管,命令行寫入一般會被拒絕(提示該 profile 只由桌面應用管理),這不是權限問題。在桌面端「插件」頁面填包名、倉庫地址或本機目錄即可。

卸載:

dsh plugin --profile web remove dsh-entry-drawer

然後重載窗口。收起狀態不會跟着刪掉——它存在瀏覽器裏,想清掉見後面的常見問題。

它是怎麼做到的

一共三件事:佔一個座位、攔一次點擊、寫一條樣式規則,外加一個保險面板和一處存偏好的地方。

第一步,佔座位。客戶端半在側欄座位上註冊一行,order: -1000 讓它排在入口列表最前面:

ctx.slots.inject('sidebar.panellist', () =>
  ctx.slots.register(
    { name: 'sidebar.panellist', id: 'entry-drawer', order: -1000, label },
    FoldGlyph,
  ),
)

行裏畫出來的就是開關本身:一個帶 .dsh-entry-drawer-mark 標記的 span,裏面是雙箭頭圖標,文字標籤「入口收納」來自插件自帶的中英詞典。

第二步,攔點擊。行盒子和行點擊都歸殼層,插件改不了,於是在 document 上註冊一個捕獲階段的 click 監聽(捕獲階段比殼層自己的處理更早看到這次點擊)。判據很窄:點到的元素在某個 button 裏,且那個 button 含本插件的標記。命中就 preventDefault() 加 stopPropagation(),殼層「切換到某個面板」的邏輯不再執行,插件改爲翻轉收起狀態並把選擇存起來。整個插件只註冊一個監聽,隨插件一起掛上和回收,點側欄其他位置不受影響。

第三步,寫樣式。收起不改 DOM,由插件自己的一條 <style> 表達,收起時追加:

nav[aria-label="全局面板"] > *:not(:has(.dsh-entry-drawer-mark)) { display: none !important }

讀法:在無障礙名爲「全局面板」的那個 nav 裏,隱藏所有不帶本插件標記的直接子項。導航名優先取活的語言詞典(sidebar 命名空間的 panels.label),取不到時用已知的兩個名字「全局面板」和「Global panels」各生成一條規則,所以中英文界面都能命中;名字拼進選擇器前做過程序內轉義。每個名字都要單獨生成一條規則並寫全子項部分,不能合成一個逗號列表——否則前半段會把整個導航(連開關一起)隱藏掉。切換界面語言時樣式重算。

保險面板和狀態存儲:插件還在主內容區(main)註冊了一個同 id 的佔位面板,渲染結果爲空。正常路徑用不到它,因爲點擊已被攔下;若某條旁路仍然選中了這個面板,它會在掛載時翻轉收起狀態,然後跳回上一個看過的面板,而不是因爲沒有內容報錯。宿主半(index.js)是空實現,apply() 裏什麼都不做,留着只爲讓這個包在插件樹裏有一個可選中、可檢查、可卸載的入口——所有邏輯都在客戶端。

測試

沒有構建步驟,純 JS,客戶端半是 window.__ModuleLoader__.load({ id, factory }) 形式的模塊。測試不需要瀏覽器也不需要裝依賴,用 Node 內置的 assert、一個極小的假 React 與假 DOM 直接驅動 client.js:

node test/client.test.mjs

通過時輸出 client.test.mjs: all checks passed。覆蓋範圍:兩個座位的註冊契約、展開與收起兩種樣式表、點擊攔截(擋住殼層、翻轉狀態、寫入偏好)、點別處不受影響、活詞典優先、標籤轉義、無詞典與無存儲時的降級、樣式表和監聽器的歸屬與回收。

改完 client.js 要重載窗口才生效;桌面端請直接重啓,它默認不跟隨源碼熱更新。

已知限制

以下是實際情況,不是免責聲明。

  1. 依賴導航的無障礙名。樣式規則靠 nav[aria-label=...] 定位,上游哪天改掉這個名字或不再用 nav 元素,插件會安靜地失效(收起不生效,也不誤藏別的東西)。修法是改 client.js 裏的 KNOWN_NAV_LABELS。
  2. 點擊攔截在 React 之外。它靠「button 裏含本插件標記」這個結構判斷,上游若改了行結構,攔截會退化:點擊落回殼層並選中這一個面板,保險面板會跳回原面板,體感是「點了沒反應」。
  3. 收起是整組隱藏。官方的「插件」「自動化任務」「更多」會一起消失,因爲它們和第三方入口在同一個列表裏,做不到只藏第三方、留下官方。展開即恢復。
  4. 窄側欄下沒有可收的東西。Windows 桌面端把側欄收成窄條時,整塊入口列表本來就是隱藏的,插件不改變任何東西。
  5. 本地目錄安裝有脆弱點。用 add ./目錄 裝時,profile 裏記的是目錄路徑,移動或刪除該文件夾客戶端半會加載失敗,長期用請改用 git 安裝,或別動這個目錄。
  6. 狀態按瀏覽器存。換瀏覽器、換主機名(例如從 127.0.0.1 換成 localhost)、清站點數據,都會回到展開。多個窗口共用同一份存儲,但插件不監聽 storage 事件,一個窗口切換後另一個窗口要重載纔跟上。

常見問題

點了這一行入口整塊不見了,怎麼找回來?再點同一行,這一行始終留着,它就是開關。

收起之後點不回來了?通常是上游改了行結構,這一下點擊落回了殼層。收起狀態記在 cookie 裏,刷新不會把它變回來,可在控制台執行 document.cookie = 'dsh-entry-drawer=0; path=/' 再刷新,或清掉這個站點的數據。

會影響別的側欄插件嗎?不會改它們的代碼或狀態。裝在同一列表裏的入口收起時會跟着一起藏起來,展開就回來;側欄其他區域和主內容區不受影響。

能只收第三方入口、留下官方入口嗎?不能。它們在同一個列表裏,從這個座位只能整組隱藏。

適用場景與注意

適合 DSH 0.2.x 的 Web 端與桌面端用戶:側欄裝了較多插件、會話歷史被入口列表擠得要靠滾動找的人。形態爲客戶端插件(dsh.client 聲明 platform: "web"),依賴 @deepseek-ai/dsh-client-ui-sidebar;沒有側欄的形態(例如 headless 命令行)不加載。

狀態存放在瀏覽器本地,不寫 profile、不碰任何配置文件——但插件是以當前 dsh 進程權限運行的。安裝前應檢查源碼與許可證(MIT,見倉庫 LICENSE)。

目錄頁與源碼:

  • 目錄頁:https://www.skillhub.cn/plugins/xingzhen199186/dsh-entry-drawer
  • GitHub:https://github.com/xingzhen199186/dsh-entry-drawer

生態背景:DSH 的理念是「一切皆插件」,插件生態由社區維護,目錄站點是獨立站點,與 DeepSeek / 幻方無官方從屬關係。

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜