@dsh-tom/dsh-css 0.1.3 → 0.1.5
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 +3 -3
- package/dist/client.js +290 -3
- package/package.json +4 -3
- package/theme.css +295 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
```powershell
|
|
8
8
|
cd D:\product\dsh-css
|
|
9
|
-
npm
|
|
9
|
+
npm run check
|
|
10
10
|
npm login --registry=https://registry.npmjs.org/
|
|
11
11
|
npm publish --access public --registry=https://registry.npmjs.org/
|
|
12
12
|
```
|
|
@@ -21,7 +21,7 @@ npm publish --access public --registry=https://registry.npmjs.org/
|
|
|
21
21
|
打开 **插件 → 添加插件 → 安装第三方插件**,在“包名或地址”输入:
|
|
22
22
|
|
|
23
23
|
```text
|
|
24
|
-
D:\product\dsh-css\dsh-tom-dsh-css-0.1.
|
|
24
|
+
D:\product\dsh-css\dsh-tom-dsh-css-0.1.5.tgz
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
安装并启用,然后完全退出并重新打开 dsh。
|
|
@@ -31,7 +31,7 @@ D:\product\dsh-css\dsh-tom-dsh-css-0.1.3.tgz
|
|
|
31
31
|
包发布到 npmjs 后,先卸载已安装的旧版,再打开 **插件 → 添加插件 → 安装第三方插件**,输入:
|
|
32
32
|
|
|
33
33
|
```text
|
|
34
|
-
@dsh-tom/dsh-css@0.1.
|
|
34
|
+
@dsh-tom/dsh-css@0.1.5
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
选择 **安装源 → npm 官方源**,安装并启用,然后完全退出并重新打开 dsh。
|
package/dist/client.js
CHANGED
|
@@ -2,9 +2,295 @@
|
|
|
2
2
|
window.__ModuleLoader__.load({
|
|
3
3
|
id: "@dsh-tom/dsh-css",
|
|
4
4
|
factory: (require) => {
|
|
5
|
-
const { createElement } = require('react');
|
|
6
|
-
const
|
|
7
|
-
const inject = ['slots', 'sessions', 'workspaces', 'layout'];
|
|
5
|
+
const { createElement, useState, useEffect, useRef } = require('react');
|
|
6
|
+
const { Menu, Modal } = require('@deepseek-ai/dsh-client-ui-primitives');
|
|
7
|
+
const css = "/* 深色模式配色,保留内置文字、边框和语法高亮颜色。 */\nhtml body[data-ds-dark-theme] {\n /* 区域 1:左侧会话列表;Windows 标题栏在下方单独设置。原 RGB(27, 27, 28)。 */\n --dsw-specific-sidebar-fill: rgb(36, 37, 33) !important;\n\n /* 区域 2:会话标题、正文以及应用基础背景。原 RGB(21, 21, 23)。 */\n --dsw-alias-bg-base: rgb(39, 40, 34) !important;\n\n /* 区域 3:主输入框。原 RGB(44, 44, 46)。 */\n --dsw-specific-input-major: rgb(66, 67, 61) !important;\n\n /* 会话内代码块及使用同一语义变量的输出区域。原 RGB(27, 27, 28)。 */\n --dsw-alias-markdown-code-block: rgb(55, 56, 50) !important;\n}\n\n/* 标题栏及原生窗口按钮共享此颜色,会话列表继续使用原来的背景。 */\nhtml[data-windows-titlebar] body[data-ds-dark-theme] {\n --dsw-specific-sidebar-fill: rgb(32, 32, 32) !important;\n\n & .BynINW_sidebarCol {\n --dsw-specific-sidebar-fill: rgb(36, 37, 33) !important;\n }\n\n /* 主内容面板左上角改为直角。 */\n & .BynINW_centerCol {\n border-top-left-radius: 0;\n }\n}\n\n/* Windows 桌面版展开侧栏:左侧图标栏 + 右侧标题、会话列表。\n * 类名对应 DSH 0.2.1-rc.2;折叠侧栏继续使用宿主布局。\n * 复用品牌、新会话、面板和账户按钮,不改变原有点击事件。\n */\nhtml[data-windows-titlebar] body[data-ds-dark-theme] ._2H3hWW_root:not(._2H3hWW_collapsed) {\n --dsh-css-rail-width: 52px;\n display: grid;\n grid-template-columns: var(--dsh-css-rail-width) minmax(0, 1fr);\n grid-template-rows: 52px auto auto minmax(0, 1fr);\n padding: 0;\n position: relative;\n\n &::before {\n content: \"\";\n position: absolute;\n inset: 0 auto 0 0;\n width: var(--dsh-css-rail-width);\n background: rgb(32, 32, 32);\n pointer-events: none;\n }\n\n & ._2H3hWW_logoRow {\n grid-area: 1 / 2;\n height: 52px;\n margin: 0;\n padding: 0 16px;\n justify-content: flex-start;\n }\n\n & ._2H3hWW_logoRow::after {\n content: \"DeepSeek\";\n color: var(--dsw-alias-label-primary);\n font: 600 18px / 24px var(--dsw-font-family);\n letter-spacing: 0;\n }\n\n /* 原品牌按钮仍打开新会话,以首页图标呈现。 */\n & ._2H3hWW_brand {\n position: absolute;\n top: 8px;\n left: 8px;\n width: 36px;\n height: 36px;\n padding: 0;\n border-radius: 8px;\n justify-content: center;\n background: var(--dsw-alias-interactive-bg-hover);\n overflow: visible;\n }\n\n & ._2H3hWW_brandIdentity {\n display: none;\n }\n\n & ._2H3hWW_brand::before {\n content: \"\";\n width: 18px;\n height: 18px;\n background: currentColor;\n mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2 2 10v3h2v7a2 2 0 0 0 2 2h4v-7h4v7h4a2 2 0 0 0 2-2v-7h2v-3Z'/%3E%3C/svg%3E\") center / contain no-repeat;\n }\n\n &:has(._2H3hWW_panelActive) ._2H3hWW_brand {\n background: transparent;\n }\n\n & ._2H3hWW_newSession {\n grid-area: 2 / 2;\n align-self: start;\n z-index: 1;\n height: 36px;\n margin: 0 8px;\n padding: 0 8px;\n border: none;\n border-radius: 8px;\n background: transparent;\n font-weight: 400;\n justify-content: flex-start;\n }\n\n & ._2H3hWW_newSessionContent {\n width: 100%;\n justify-content: flex-start;\n gap: 8px;\n }\n\n & ._2H3hWW_newSessionContent > svg {\n display: none;\n }\n\n & ._2H3hWW_newSessionContent::before {\n content: \"\";\n flex: none;\n width: 14px;\n height: 14px;\n background: currentColor;\n mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 4H5a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h13a2 2 0 0 0 2-2v-7M16 3a2.1 2.1 0 0 1 3 3L9 16l-4 1 1-4Z'/%3E%3C/svg%3E\") center / contain no-repeat;\n }\n\n & ._2H3hWW_panelList {\n grid-area: 2 / 1;\n position: relative;\n align-self: start;\n gap: 8px;\n margin: 0;\n padding: 0 8px;\n }\n\n & ._2H3hWW_panelRow {\n position: relative;\n justify-content: center;\n width: 36px;\n height: 36px;\n margin: 0;\n padding: 0;\n border-radius: 8px;\n color: var(--dsw-alias-label-tertiary);\n }\n\n & ._2H3hWW_panelTitle {\n display: none;\n }\n\n & ._2H3hWW_panelGlyph > svg {\n width: 18px;\n height: 18px;\n }\n\n & ._2H3hWW_panelActive {\n color: var(--dsw-alias-label-primary);\n }\n\n /* 列表与导航共用网格行,顶部留出新会话按钮的位置。 */\n & ._2H3hWW_regionArea {\n grid-area: 2 / 2 / -1 / 3;\n margin: 0;\n padding: 44px 0 0 12px;\n }\n\n & ._2H3hWW_footArea {\n grid-area: 4 / 1;\n position: relative;\n align-self: end;\n gap: 8px;\n padding: 8px;\n }\n\n & ._2H3hWW_footerActions:empty {\n display: none;\n }\n\n /* 左下角设置按钮沿用账户菜单,设置、登录等入口仍由宿主提供。 */\n & .ET65mq_trigger {\n position: relative;\n justify-content: center;\n width: 36px;\n height: 36px;\n padding: 0;\n border-radius: 8px;\n color: var(--dsw-alias-label-tertiary);\n }\n\n & .ET65mq_trigger > * {\n display: none;\n }\n\n & .ET65mq_trigger::before {\n content: \"\";\n width: 18px;\n height: 18px;\n background: currentColor;\n mask: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9.5 3-.5 2-2 1-2-.5-2 3.5L4.5 10v3L3 14l2 3.5 2-.5 2 1 .5 3h5l.5-3 2-1 2 .5 2-3.5-1.5-1v-3L21 9l-2-3.5-2 .5-2-1-.5-2Z'/%3E%3Ccircle cx='12' cy='12' r='3'/%3E%3C/svg%3E\") center / contain no-repeat;\n }\n\n /* 同一元素也显示等待状态,只隐藏普通的相对时间。 */\n & .hIlkoa_sessionRow > .hIlkoa_time:not([aria-hidden=\"true\"]) {\n display: none;\n }\n\n & :is(._2H3hWW_brand, ._2H3hWW_newSession, .ET65mq_trigger):hover {\n background: var(--dsw-alias-interactive-bg-hover);\n }\n\n & :is(._2H3hWW_brand, ._2H3hWW_newSession, .ET65mq_trigger):focus-visible {\n outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));\n outline-offset: -2px;\n }\n\n /* 展开状态下宿主禁用了面板 Tooltip,为纯图标入口补上名称。 */\n & :is(._2H3hWW_panelRow, .ET65mq_trigger):is(:hover, :focus-visible)::after {\n content: attr(aria-label);\n position: absolute;\n z-index: 40;\n left: 44px;\n top: 50%;\n transform: translateY(-50%);\n padding: 4px 8px;\n border: 1px solid var(--dsw-alias-border-l3);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font: 12px / 20px var(--dsw-font-family);\n white-space: nowrap;\n pointer-events: none;\n }\n\n & .ET65mq_trigger:is(:hover, :focus-visible)::after {\n content: \"设置\";\n }\n}\n\n/* 悬浮卡片由宿主通过 portal 挂在侧栏外。 */\n.dsh-css-project {\n display: none;\n}\n\nhtml[data-windows-titlebar] body[data-ds-dark-theme] .dsh-css-project {\n display: flex;\n align-items: center;\n gap: 6px;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n line-height: 20px;\n\n & svg {\n flex: none;\n }\n\n & span {\n overflow-wrap: anywhere;\n }\n}\n\n/* 项目页通过宿主 main 插槽挂载,菜单和对话框沿用宿主组件。 */\n.dsh-css-more-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.dsh-css-more-open {\n background: var(--dsw-alias-interactive-bg-hover) !important;\n}\n\nhtml[data-windows-titlebar] body[data-ds-dark-theme] ._2H3hWW_panelRow.dsh-css-more-open::after {\n display: none;\n}\n\n.dsh-css-projects-page {\n flex: 1;\n min-height: 0;\n overflow: auto;\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-base);\n font: 14px / 1.5 var(--dsw-font-family);\n}\n\n.dsh-css-projects-content {\n box-sizing: border-box;\n width: min(100%, 1408px);\n margin: 0 auto;\n padding: 32px;\n}\n\n.dsh-css-projects-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 20px;\n margin-bottom: 24px;\n\n & h1 {\n margin: 0;\n font-size: 24px;\n font-weight: 600;\n }\n}\n\n.dsh-css-projects-tools,\n.dsh-css-project-dialog-actions {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.dsh-css-projects-page button,\n.dsh-css-project-dialog-actions button {\n cursor: pointer;\n font: inherit;\n\n &:disabled { cursor: default; opacity: .45; }\n}\n\n.dsh-css-project-search {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: 8px;\n width: 240px;\n height: 34px;\n padding: 0 12px;\n border: 1px solid var(--dsw-alias-border-l3);\n border-radius: 20px;\n color: var(--dsw-alias-label-tertiary);\n\n & input {\n width: 100%;\n min-width: 0;\n border: 0;\n outline: none;\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font: inherit;\n }\n\n &:focus-within { border-color: var(--dsw-alias-label-secondary); }\n}\n\n.dsh-css-project-create,\n.dsh-css-project-secondary {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n min-height: 34px;\n padding: 6px 16px;\n border: 1px solid transparent;\n border-radius: 20px;\n white-space: nowrap;\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.dsh-css-project-secondary {\n background: transparent;\n color: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-border-l3);\n}\n\n.dsh-css-project-columns,\n.dsh-css-project-row {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 100px 80px;\n align-items: center;\n gap: 16px;\n}\n\n.dsh-css-project-columns {\n min-height: 32px;\n border-bottom: 1px solid var(--dsw-alias-border-l3);\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n\n & button { border: 0; padding: 0; color: inherit; background: transparent; }\n}\n\n.dsh-css-project-row {\n position: relative;\n min-height: 70px;\n border-bottom: 1px solid var(--dsw-alias-border-l3);\n\n &::before {\n content: '';\n position: absolute;\n inset: 0 -12px;\n border-radius: 12px;\n pointer-events: none;\n }\n\n &:is(:hover, :focus-within, .is-open)::before { background: var(--dsw-alias-interactive-bg-hover); }\n & > * { position: relative; }\n &:is(:hover, :focus-within, .is-open) .dsh-css-project-chevron { opacity: 1; }\n}\n\n.dsh-css-project-name-cell { min-width: 0; }\n\n.dsh-css-project-name {\n display: flex;\n align-items: center;\n gap: 20px;\n width: 100%;\n min-height: 70px;\n padding: 8px;\n border: 0;\n background: transparent;\n color: inherit;\n text-align: left;\n\n & svg { flex: none; }\n & span { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }\n}\n\n.dsh-css-project-chevron { opacity: 0; }\n.dsh-css-project-time { color: var(--dsw-alias-label-secondary); white-space: nowrap; }\n.dsh-css-project-actions { display: flex; gap: 4px; }\n\n.dsh-css-icon-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: 32px;\n height: 32px;\n padding: 0;\n border: 0;\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n\n &:is(:hover, [aria-expanded='true']) { background: var(--dsw-alias-interactive-bg-hover); }\n}\n\n.dsh-css-projects-page button:focus-visible,\n.dsh-css-project-dialog-actions button:focus-visible {\n outline: 2px solid var(--dsw-focus-ring-color, var(--dsw-alias-label-secondary));\n outline-offset: 2px;\n}\n\n.dsh-css-project-empty { padding: 60px 0; text-align: center; color: var(--dsw-alias-label-tertiary); }\n.dsh-css-project-error { color: var(--dsw-alias-state-error-primary, #dc6767); overflow-wrap: anywhere; }\n.dsh-css-project-notice { color: var(--dsw-alias-label-secondary); }\n.dsh-css-project-field { display: flex; flex-direction: column; gap: 10px; }\n.dsh-css-project-field input {\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l3);\n border-radius: 8px;\n background: var(--dsw-alias-bg-base);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n}\n.dsh-css-project-path { color: var(--dsw-alias-label-tertiary); font-size: 12px; overflow-wrap: anywhere; }\n\n/* 项目弹窗使用 Codex 的深灰底、圆角和按钮配色。 */\n.dsh-css-project-remove-dialog,\n.dsh-css-project-edit-dialog {\n gap: 12px;\n padding-bottom: 20px;\n border-radius: 22px;\n box-shadow: none;\n\n & > :first-child > :first-child { padding: 20px 14px 4px 20px; }\n & h2 { font-size: 20px; line-height: 26px; font-weight: 600; overflow-wrap: anywhere; }\n & > :last-child { padding: 0 20px; }\n & .dsh-css-project-dialog-actions { gap: 12px; }\n & .dsh-css-project-dialog-actions button { min-height: 32px; padding: 5px 16px; border-radius: 10px; }\n & .dsh-css-project-secondary { border-color: transparent; }\n}\n\n.dsh-css-project-remove-dialog {\n width: min(420px, 100%);\n & .dsh-css-project-remove-content > p { padding: 0 20px; }\n & .dsh-css-project-remove-content > :last-child { margin-top: 0; padding: 0 20px; }\n}\n\n.dsh-css-project-edit-dialog {\n width: min(520px, 100%);\n gap: 20px;\n\n & .dsh-css-project-edit-content > :first-child { padding-bottom: 12px; }\n & .dsh-css-project-edit-content > :last-child { margin-top: 0; padding: 0 20px; }\n & .dsh-css-project-field { gap: 12px; }\n & .dsh-css-project-name-input {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 0 12px;\n border: 1px solid var(--dsw-alias-border-l3);\n border-radius: 12px;\n }\n & .dsh-css-project-name-input:focus-within { border-color: var(--dsw-alias-label-secondary); }\n & .dsh-css-project-name-input input {\n min-width: 0;\n width: 100%;\n height: 38px;\n padding: 0;\n border: 0;\n outline: 0;\n background: transparent;\n }\n & svg { flex: none; }\n & .dsh-css-project-source { display: flex; flex-direction: column; gap: 8px; }\n & .dsh-css-project-source-label { font-size: 13px; font-weight: 600; }\n & .dsh-css-project-path {\n box-sizing: border-box;\n display: flex;\n align-items: center;\n gap: 10px;\n min-height: 48px;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l3);\n border-radius: 10px;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n }\n}\n\nbody[data-ds-dark-theme] :is(.dsh-css-project-remove-dialog, .dsh-css-project-edit-dialog) {\n background: rgb(55, 56, 51);\n\n & h2 { color: rgb(238, 238, 233); }\n & .dsh-css-project-remove-content > p,\n & .dsh-css-project-secondary { color: rgb(142, 143, 137); }\n & .dsh-css-project-secondary:hover:not(:disabled) { background: rgb(255 255 255 / 5%); }\n & .dsh-css-project-remove { color: rgb(240, 100, 96); background: rgb(76, 61, 56); }\n & .dsh-css-project-remove:hover:not(:disabled) { background: rgb(88, 65, 60); }\n & .dsh-css-project-name-input { background: rgb(58, 59, 53); border-color: rgb(80, 81, 74); }\n & .dsh-css-project-name-input:focus-within { border-color: rgb(148, 149, 138); }\n & .dsh-css-project-path { background: rgb(58, 59, 53); border-color: rgb(70, 71, 64); }\n & .dsh-css-project-path svg { color: rgb(142, 143, 137); }\n}\n\nbody[data-ds-dark-theme] .dsh-css-project-edit-dialog .dsh-css-project-create {\n background: rgb(248, 248, 243);\n color: rgb(55, 56, 51);\n}\n\nbody[data-ds-dark-theme] [role='presentation']:has(> :is(.dsh-css-project-remove-dialog, .dsh-css-project-edit-dialog)) {\n --dsw-alias-bg-mask-1: rgb(0 0 0 / 15%);\n}\n\n@media (max-width: 760px) {\n .dsh-css-projects-content { padding: 24px 20px; }\n .dsh-css-projects-header { align-items: stretch; flex-direction: column; }\n .dsh-css-project-search { flex: 1; width: auto; }\n .dsh-css-project-columns, .dsh-css-project-row { grid-template-columns: minmax(0, 1fr) 64px 68px; gap: 8px; }\n .dsh-css-project-name { gap: 10px; }\n}\n";
|
|
8
|
+
// React 和宿主的菜单、对话框由构建入口注入。
|
|
9
|
+
const PROJECTS_PANEL = 'dsh-css-projects';
|
|
10
|
+
|
|
11
|
+
function ProjectIcon({ name = 'folder', size = 16, ...props }) {
|
|
12
|
+
const paths = {
|
|
13
|
+
folder: 'M3 7V5a2 2 0 0 1 2-2h5l2 3h7a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7Zm0 2h18',
|
|
14
|
+
more: 'M5 12h.01M12 12h.01M19 12h.01',
|
|
15
|
+
search: 'M21 21l-5-5M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0',
|
|
16
|
+
plus: 'M12 5v14M5 12h14',
|
|
17
|
+
edit: 'M12 4H5a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h13a2 2 0 0 0 2-2v-7M16 3a2.1 2.1 0 0 1 3 3L9 16l-4 1 1-4Z',
|
|
18
|
+
archive: 'M3 3h18v4H3ZM5 7v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7M9 11h6',
|
|
19
|
+
close: 'm6 6 12 12M6 18 18 6',
|
|
20
|
+
chevron: 'm9 5 7 7-7 7',
|
|
21
|
+
};
|
|
22
|
+
return createElement('svg', {
|
|
23
|
+
width: size, height: size, viewBox: '0 0 24 24', fill: 'none',
|
|
24
|
+
stroke: 'currentColor', strokeWidth: name === 'more' ? 3.5 : 1.6,
|
|
25
|
+
strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true, ...props,
|
|
26
|
+
}, createElement('path', { d: paths[name] }));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ProjectsNav({ size, openProjects }) {
|
|
30
|
+
const icon = useRef(null);
|
|
31
|
+
const anchor = useRef(null);
|
|
32
|
+
const [open, setOpen] = useState(false);
|
|
33
|
+
const opened = useRef(false);
|
|
34
|
+
opened.current = open;
|
|
35
|
+
// panellist 只提供图标插槽,外层按钮由宿主创建;接管点击以先打开菜单。
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const button = icon.current.closest('button');
|
|
38
|
+
anchor.current = button;
|
|
39
|
+
const toggle = event => {
|
|
40
|
+
event.stopPropagation();
|
|
41
|
+
setOpen(value => !value);
|
|
42
|
+
};
|
|
43
|
+
const keepAnchor = event => { if (opened.current) event.stopPropagation(); };
|
|
44
|
+
button.addEventListener('click', toggle);
|
|
45
|
+
button.addEventListener('pointerdown', keepAnchor);
|
|
46
|
+
button.setAttribute('aria-haspopup', 'menu');
|
|
47
|
+
return () => {
|
|
48
|
+
button.removeEventListener('click', toggle);
|
|
49
|
+
button.removeEventListener('pointerdown', keepAnchor);
|
|
50
|
+
button.removeAttribute('aria-haspopup');
|
|
51
|
+
button.removeAttribute('aria-expanded');
|
|
52
|
+
button.classList.remove('dsh-css-more-open');
|
|
53
|
+
};
|
|
54
|
+
}, []);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
anchor.current.setAttribute('aria-expanded', String(open));
|
|
57
|
+
anchor.current.classList.toggle('dsh-css-more-open', open);
|
|
58
|
+
}, [open]);
|
|
59
|
+
return createElement('span', { ref: icon, className: 'dsh-css-more-icon', onClick: event => event.stopPropagation() },
|
|
60
|
+
createElement(ProjectIcon, { name: 'more', size }),
|
|
61
|
+
createElement(Menu, {
|
|
62
|
+
open, anchor: null, portal: true, side: 'right', autoFocus: true,
|
|
63
|
+
getAnchorRect: () => anchor.current?.getBoundingClientRect() ?? null,
|
|
64
|
+
items: [{ id: 'projects', label: '项目', icon: createElement(ProjectIcon) }],
|
|
65
|
+
onClose: () => {
|
|
66
|
+
setOpen(false);
|
|
67
|
+
if (document.activeElement?.closest('[role="menu"]')) anchor.current?.focus();
|
|
68
|
+
},
|
|
69
|
+
onSelect: () => { setOpen(false); openProjects(); },
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function projectName(workspace) {
|
|
74
|
+
return workspace.title || workspace.path.split(/[\\/]/).filter(Boolean).at(-1) || workspace.path;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function projectUpdatedAt(workspace, sessions) {
|
|
78
|
+
return workspace.sessionIds.reduce((latest, id) => Math.max(latest, Number(sessions[id]?.updatedAt) || 0),
|
|
79
|
+
Date.parse(workspace.updatedAt || workspace.createdAt) || 0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function projectTime(time, now) {
|
|
83
|
+
if (!time) return '—';
|
|
84
|
+
const minutes = Math.max(0, Math.floor((now - time) / 60000));
|
|
85
|
+
if (minutes < 1) return '刚刚';
|
|
86
|
+
if (minutes < 60) return `${minutes} 分`;
|
|
87
|
+
if (minutes < 1440) return `${Math.floor(minutes / 60)} 小时`;
|
|
88
|
+
if (minutes < 10080) return `${Math.floor(minutes / 1440)} 天`;
|
|
89
|
+
return `${Math.floor(minutes / 10080)} 周`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function ProjectListRow({ workspace, updatedAt, now, busy, onOpen, onCreate, onAction }) {
|
|
93
|
+
const [open, setOpen] = useState(false);
|
|
94
|
+
const title = projectName(workspace);
|
|
95
|
+
return createElement('div', { className: `dsh-css-project-row${open ? ' is-open' : ''}`, role: 'row' },
|
|
96
|
+
createElement('div', { role: 'cell', className: 'dsh-css-project-name-cell' },
|
|
97
|
+
createElement('button', {
|
|
98
|
+
type: 'button', className: 'dsh-css-project-name', title: workspace.path,
|
|
99
|
+
disabled: busy, onClick: () => onOpen(workspace),
|
|
100
|
+
}, createElement(ProjectIcon), createElement('span', null, title),
|
|
101
|
+
createElement(ProjectIcon, { name: 'chevron', size: 12, className: 'dsh-css-project-chevron' }))),
|
|
102
|
+
createElement('div', { role: 'cell', className: 'dsh-css-project-time', title: updatedAt ? new Date(updatedAt).toLocaleString('zh-CN') : '' }, projectTime(updatedAt, now)),
|
|
103
|
+
createElement('div', { role: 'cell', className: 'dsh-css-project-actions' },
|
|
104
|
+
createElement(Menu, {
|
|
105
|
+
open, portal: true, side: 'bottom', align: 'end', autoFocus: true,
|
|
106
|
+
anchor: createElement('button', {
|
|
107
|
+
type: 'button', className: 'dsh-css-icon-button', disabled: busy,
|
|
108
|
+
'aria-label': `${title}:更多操作`, 'aria-haspopup': 'menu', 'aria-expanded': open,
|
|
109
|
+
onClick: () => setOpen(value => !value),
|
|
110
|
+
}, createElement(ProjectIcon, { name: 'more' })),
|
|
111
|
+
items: [
|
|
112
|
+
{ id: 'rename', label: '编辑项目', icon: createElement(ProjectIcon, { name: 'edit' }) },
|
|
113
|
+
{ id: 'archive', label: '归档聊天', icon: createElement(ProjectIcon, { name: 'archive' }) },
|
|
114
|
+
{ id: 'remove', label: '移除项目', icon: createElement(ProjectIcon, { name: 'close' }) },
|
|
115
|
+
],
|
|
116
|
+
onClose: () => setOpen(false),
|
|
117
|
+
onSelect: action => { setOpen(false); onAction(action, workspace); },
|
|
118
|
+
}),
|
|
119
|
+
createElement('button', {
|
|
120
|
+
type: 'button', className: 'dsh-css-icon-button', disabled: busy,
|
|
121
|
+
'aria-label': `${title}:新建聊天`, title: '新建聊天', onClick: () => onCreate(workspace),
|
|
122
|
+
}, createElement(ProjectIcon, { name: 'edit' }))));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function ProjectsPage({ useWorkspaces, useSessions, actions }) {
|
|
126
|
+
const workspaces = useWorkspaces(state => state);
|
|
127
|
+
const sessions = useSessions(state => state);
|
|
128
|
+
const [query, setQuery] = useState('');
|
|
129
|
+
const [ascending, setAscending] = useState(false);
|
|
130
|
+
const [now, setNow] = useState(Date.now);
|
|
131
|
+
const [busy, setBusy] = useState(false);
|
|
132
|
+
const pending = useRef(false);
|
|
133
|
+
const [error, setError] = useState('');
|
|
134
|
+
const [notice, setNotice] = useState('');
|
|
135
|
+
const [dialog, setDialog] = useState(null);
|
|
136
|
+
const [draft, setDraft] = useState('');
|
|
137
|
+
const ready = workspaces.phase === 'ready' && workspaces.state !== 'loading' && sessions.phase === 'ready';
|
|
138
|
+
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
const timer = window.setInterval(() => setNow(Date.now()), 60000);
|
|
141
|
+
return () => window.clearInterval(timer);
|
|
142
|
+
}, []);
|
|
143
|
+
|
|
144
|
+
const run = async operation => {
|
|
145
|
+
if (pending.current) return;
|
|
146
|
+
pending.current = true;
|
|
147
|
+
setBusy(true);
|
|
148
|
+
setError('');
|
|
149
|
+
setNotice('');
|
|
150
|
+
try { await operation(); }
|
|
151
|
+
catch (reason) { setError(reason instanceof Error ? reason.message : String(reason)); }
|
|
152
|
+
finally { pending.current = false; setBusy(false); }
|
|
153
|
+
};
|
|
154
|
+
const requestAction = (kind, workspace) => {
|
|
155
|
+
setError('');
|
|
156
|
+
setNotice('');
|
|
157
|
+
setDraft(projectName(workspace));
|
|
158
|
+
setDialog({ kind, workspace });
|
|
159
|
+
};
|
|
160
|
+
const closeDialog = () => { if (!pending.current) { setDialog(null); setError(''); } };
|
|
161
|
+
const confirm = () => run(async () => {
|
|
162
|
+
const { kind, workspace } = dialog;
|
|
163
|
+
if (kind === 'rename') {
|
|
164
|
+
if (!draft.trim()) return;
|
|
165
|
+
await actions.rename(workspace.workspaceId, draft.trim());
|
|
166
|
+
} else if (kind === 'remove') {
|
|
167
|
+
await actions.remove(workspace.workspaceId);
|
|
168
|
+
} else {
|
|
169
|
+
const result = await actions.archive(workspace.workspaceId);
|
|
170
|
+
setNotice(result);
|
|
171
|
+
}
|
|
172
|
+
setDialog(null);
|
|
173
|
+
});
|
|
174
|
+
const needle = query.trim().toLocaleLowerCase();
|
|
175
|
+
const rows = workspaces.items.filter(workspace =>
|
|
176
|
+
`${projectName(workspace)}\n${workspace.path}`.toLocaleLowerCase().includes(needle))
|
|
177
|
+
.map(workspace => ({ workspace, updatedAt: projectUpdatedAt(workspace, sessions.byId) }))
|
|
178
|
+
.sort((a, b) => (ascending ? a.updatedAt - b.updatedAt : b.updatedAt - a.updatedAt) ||
|
|
179
|
+
projectName(a.workspace).localeCompare(projectName(b.workspace), 'zh-CN'));
|
|
180
|
+
const dialogTitle = dialog?.kind === 'rename' ? '编辑项目' : dialog?.kind === 'archive' ? '归档聊天' : '移除项目';
|
|
181
|
+
const removing = dialog?.kind === 'remove';
|
|
182
|
+
const editing = dialog?.kind === 'rename';
|
|
183
|
+
|
|
184
|
+
return createElement('section', { className: 'dsh-css-projects-page', 'aria-label': '项目', 'aria-busy': busy },
|
|
185
|
+
createElement('div', { className: 'dsh-css-projects-content' },
|
|
186
|
+
createElement('header', { className: 'dsh-css-projects-header' },
|
|
187
|
+
createElement('h1', null, '项目'),
|
|
188
|
+
createElement('div', { className: 'dsh-css-projects-tools' },
|
|
189
|
+
createElement('label', { className: 'dsh-css-project-search' },
|
|
190
|
+
createElement(ProjectIcon, { name: 'search', size: 14 }),
|
|
191
|
+
createElement('input', { type: 'search', value: query, placeholder: '搜索项目', 'aria-label': '搜索项目', onChange: event => setQuery(event.target.value) })),
|
|
192
|
+
createElement('button', {
|
|
193
|
+
type: 'button', className: 'dsh-css-project-create', disabled: busy || !ready,
|
|
194
|
+
onClick: () => run(async () => { if (await actions.create()) setNotice('项目已添加'); }),
|
|
195
|
+
}, createElement(ProjectIcon, { name: 'plus', size: 14 }), '创建'))),
|
|
196
|
+
error && !dialog && createElement('p', { className: 'dsh-css-project-error', role: 'alert' }, error),
|
|
197
|
+
notice && createElement('p', { className: 'dsh-css-project-notice', role: 'status' }, notice),
|
|
198
|
+
createElement('div', { role: 'table', 'aria-label': '项目列表', className: 'dsh-css-project-table' },
|
|
199
|
+
createElement('div', { role: 'row', className: 'dsh-css-project-columns' },
|
|
200
|
+
createElement('div', { role: 'columnheader' }, '名称'),
|
|
201
|
+
createElement('div', { role: 'columnheader', 'aria-sort': ascending ? 'ascending' : 'descending' },
|
|
202
|
+
createElement('button', { type: 'button', onClick: () => setAscending(value => !value) }, '已更新 ', ascending ? '↑' : '↓')),
|
|
203
|
+
createElement('div', { role: 'columnheader', 'aria-label': '操作' })),
|
|
204
|
+
...rows.map(({ workspace, updatedAt }) => createElement(ProjectListRow, {
|
|
205
|
+
key: workspace.workspaceId, workspace, updatedAt, now, busy: busy || !ready,
|
|
206
|
+
onOpen: target => run(() => actions.open(target.workspaceId)),
|
|
207
|
+
onCreate: target => actions.startSession(target.workspaceId), onAction: requestAction,
|
|
208
|
+
}))),
|
|
209
|
+
rows.length === 0 && createElement('p', { className: 'dsh-css-project-empty', role: 'status' },
|
|
210
|
+
!ready ? '正在加载项目…' : needle ? '没有找到匹配的项目' : '还没有项目,点击“创建”选择项目文件夹。')),
|
|
211
|
+
createElement(Modal, {
|
|
212
|
+
open: dialog !== null, title: removing ? `移除 ${projectName(dialog.workspace)}?` : dialogTitle,
|
|
213
|
+
closeLabel: '关闭', onClose: closeDialog,
|
|
214
|
+
className: removing ? 'dsh-css-project-remove-dialog' : editing ? 'dsh-css-project-edit-dialog' : undefined,
|
|
215
|
+
contentClassName: removing ? 'dsh-css-project-remove-content' : editing ? 'dsh-css-project-edit-content' : undefined,
|
|
216
|
+
backdropBlur: !(removing || editing),
|
|
217
|
+
description: removing ? '这只会从应用中移除该项目。你电脑上的文件和现有聊天不会被删除。' : undefined,
|
|
218
|
+
footer: createElement('div', { className: 'dsh-css-project-dialog-actions' },
|
|
219
|
+
createElement('button', { type: 'button', className: 'dsh-css-project-secondary', disabled: busy, onClick: closeDialog }, '取消'),
|
|
220
|
+
createElement('button', {
|
|
221
|
+
type: 'button', className: `dsh-css-project-create${removing ? ' dsh-css-project-remove' : ''}`,
|
|
222
|
+
disabled: busy || (dialog?.kind === 'rename' && !draft.trim()), onClick: confirm,
|
|
223
|
+
}, busy ? '处理中…' : dialog?.kind === 'rename' ? '保存' : dialogTitle)),
|
|
224
|
+
}, editing
|
|
225
|
+
? createElement('div', { className: 'dsh-css-project-field' },
|
|
226
|
+
createElement('label', { className: 'dsh-css-project-name-input' },
|
|
227
|
+
createElement(ProjectIcon), createElement('input', {
|
|
228
|
+
value: draft, disabled: busy, 'data-modal-autofocus': true,
|
|
229
|
+
'aria-label': '项目名称',
|
|
230
|
+
onChange: event => setDraft(event.target.value),
|
|
231
|
+
onKeyDown: event => {
|
|
232
|
+
if (event.key === 'Enter' && !event.nativeEvent.isComposing) { event.preventDefault(); confirm(); }
|
|
233
|
+
},
|
|
234
|
+
})),
|
|
235
|
+
createElement('div', { className: 'dsh-css-project-source' },
|
|
236
|
+
createElement('span', { className: 'dsh-css-project-source-label' }, '源文件夹'),
|
|
237
|
+
createElement('div', { className: 'dsh-css-project-path', title: dialog.workspace.path },
|
|
238
|
+
createElement(ProjectIcon), createElement('span', null, dialog.workspace.path))))
|
|
239
|
+
: dialog?.kind === 'archive' ? createElement('p', null,
|
|
240
|
+
`归档“${projectName(dialog.workspace)}”中的聊天?运行中的聊天会保留,聊天记录不会删除。`) : null,
|
|
241
|
+
error && createElement('p', { className: 'dsh-css-project-error', role: 'alert' }, error)));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function registerProjects(ctx) {
|
|
245
|
+
const actions = {
|
|
246
|
+
async create() {
|
|
247
|
+
const picker = globalThis.__DSH_DIRECTORY_PICKER__;
|
|
248
|
+
const path = await (picker ? picker.pick() : ctx.uiWorkspace.pickDirectory());
|
|
249
|
+
if (path === null) return false;
|
|
250
|
+
await ctx.workspaces.create({ path });
|
|
251
|
+
return true;
|
|
252
|
+
},
|
|
253
|
+
rename: (id, title) => ctx.workspaces.rename(id, title),
|
|
254
|
+
remove: id => ctx.workspaces.delete(id),
|
|
255
|
+
startSession: id => ctx.uiWorkspace.startSession(id),
|
|
256
|
+
async open(id) {
|
|
257
|
+
const snapshot = ctx.workspaces.list.getSnapshot();
|
|
258
|
+
const workspace = snapshot.items.find(item => item.workspaceId === id);
|
|
259
|
+
if (!workspace) throw new Error('项目已被移除,请刷新后重试。');
|
|
260
|
+
const sessions = ctx.sessions.list.getSnapshot().byId;
|
|
261
|
+
const recent = workspace.sessionIds.filter(sessionId => sessions[sessionId] && !snapshot.archivedSessionIds.includes(sessionId))
|
|
262
|
+
.sort((a, b) => sessions[b].updatedAt - sessions[a].updatedAt)[0];
|
|
263
|
+
if (recent) ctx.uiWorkspace.openSession(recent);
|
|
264
|
+
else await ctx.uiWorkspace.openWorkspace(id);
|
|
265
|
+
},
|
|
266
|
+
async archive(id) {
|
|
267
|
+
const snapshot = ctx.workspaces.list.getSnapshot();
|
|
268
|
+
const workspace = snapshot.items.find(item => item.workspaceId === id);
|
|
269
|
+
if (!workspace) throw new Error('项目已被移除,请刷新后重试。');
|
|
270
|
+
const ids = workspace.sessionIds.filter(sessionId => !snapshot.archivedSessionIds.includes(sessionId));
|
|
271
|
+
let archived = 0;
|
|
272
|
+
const failures = [];
|
|
273
|
+
for (const sessionId of ids) {
|
|
274
|
+
try { await ctx.uiWorkspace.archiveSession(sessionId); archived++; }
|
|
275
|
+
catch (reason) {
|
|
276
|
+
failures.push(reason instanceof Error ? reason.message : String(reason));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (failures.length) throw new Error(`已归档 ${archived} 个聊天,${failures.length} 个未归档:${failures[0]}`);
|
|
280
|
+
return ids.length ? `已归档 ${archived} 个聊天` : '没有需要归档的聊天';
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
ctx.slots.inject('main', () => ctx.slots.register({
|
|
284
|
+
name: 'main', key: PROJECTS_PANEL,
|
|
285
|
+
inject: () => ({ actions, hooks: { workspaces: ctx.workspaces.list, sessions: ctx.sessions.list } }),
|
|
286
|
+
}, ProjectsPage));
|
|
287
|
+
ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({
|
|
288
|
+
name: 'sidebar.panellist', id: PROJECTS_PANEL, label: '更多', order: 100,
|
|
289
|
+
inject: () => ({ openProjects: () => ctx.layout.selectPanel(PROJECTS_PANEL) }),
|
|
290
|
+
}, ProjectsNav));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const inject = ['slots', 'sessions', 'workspaces', 'layout', 'uiWorkspace'];
|
|
8
294
|
|
|
9
295
|
// createElement 和 css 由构建入口注入,React 复用宿主提供的模块。
|
|
10
296
|
function ProjectHover({ sessionId, useWorkspaces, useSessions }) {
|
|
@@ -74,6 +360,7 @@ function apply(ctx) {
|
|
|
74
360
|
});
|
|
75
361
|
|
|
76
362
|
ctx.effect(() => applySidebarWidth(ctx.layout));
|
|
363
|
+
registerProjects(ctx);
|
|
77
364
|
ctx.slots.inject('sidebar.session.row.hover', () => ctx.slots.register({
|
|
78
365
|
name: 'sidebar.session.row.hover',
|
|
79
366
|
id: 'dsh-css-project',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-tom/dsh-css",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "为 DeepSeek Harness
|
|
3
|
+
"version": "0.1.5",
|
|
4
|
+
"description": "为 DeepSeek Harness 调整深色界面,并增加项目管理列表。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
7
7
|
"exports": {
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "node scripts/build.mjs",
|
|
26
|
-
"
|
|
26
|
+
"test": "node --test test/*.test.mjs",
|
|
27
|
+
"check": "node --check index.js && node --check src/client.js && node --check src/projects.js && node --check scripts/build.mjs && npm run build && node --check dist/client.js && npm test",
|
|
27
28
|
"prepack": "npm run check"
|
|
28
29
|
}
|
|
29
30
|
}
|
package/theme.css
CHANGED
|
@@ -259,3 +259,298 @@ html[data-windows-titlebar] body[data-ds-dark-theme] .dsh-css-project {
|
|
|
259
259
|
overflow-wrap: anywhere;
|
|
260
260
|
}
|
|
261
261
|
}
|
|
262
|
+
|
|
263
|
+
/* 项目页通过宿主 main 插槽挂载,菜单和对话框沿用宿主组件。 */
|
|
264
|
+
.dsh-css-more-icon {
|
|
265
|
+
display: inline-flex;
|
|
266
|
+
align-items: center;
|
|
267
|
+
justify-content: center;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.dsh-css-more-open {
|
|
271
|
+
background: var(--dsw-alias-interactive-bg-hover) !important;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
html[data-windows-titlebar] body[data-ds-dark-theme] ._2H3hWW_panelRow.dsh-css-more-open::after {
|
|
275
|
+
display: none;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.dsh-css-projects-page {
|
|
279
|
+
flex: 1;
|
|
280
|
+
min-height: 0;
|
|
281
|
+
overflow: auto;
|
|
282
|
+
color: var(--dsw-alias-label-primary);
|
|
283
|
+
background: var(--dsw-alias-bg-base);
|
|
284
|
+
font: 14px / 1.5 var(--dsw-font-family);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.dsh-css-projects-content {
|
|
288
|
+
box-sizing: border-box;
|
|
289
|
+
width: min(100%, 1408px);
|
|
290
|
+
margin: 0 auto;
|
|
291
|
+
padding: 32px;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.dsh-css-projects-header {
|
|
295
|
+
display: flex;
|
|
296
|
+
align-items: center;
|
|
297
|
+
justify-content: space-between;
|
|
298
|
+
gap: 20px;
|
|
299
|
+
margin-bottom: 24px;
|
|
300
|
+
|
|
301
|
+
& h1 {
|
|
302
|
+
margin: 0;
|
|
303
|
+
font-size: 24px;
|
|
304
|
+
font-weight: 600;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.dsh-css-projects-tools,
|
|
309
|
+
.dsh-css-project-dialog-actions {
|
|
310
|
+
display: flex;
|
|
311
|
+
align-items: center;
|
|
312
|
+
gap: 12px;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.dsh-css-projects-page button,
|
|
316
|
+
.dsh-css-project-dialog-actions button {
|
|
317
|
+
cursor: pointer;
|
|
318
|
+
font: inherit;
|
|
319
|
+
|
|
320
|
+
&:disabled { cursor: default; opacity: .45; }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.dsh-css-project-search {
|
|
324
|
+
box-sizing: border-box;
|
|
325
|
+
display: flex;
|
|
326
|
+
align-items: center;
|
|
327
|
+
gap: 8px;
|
|
328
|
+
width: 240px;
|
|
329
|
+
height: 34px;
|
|
330
|
+
padding: 0 12px;
|
|
331
|
+
border: 1px solid var(--dsw-alias-border-l3);
|
|
332
|
+
border-radius: 20px;
|
|
333
|
+
color: var(--dsw-alias-label-tertiary);
|
|
334
|
+
|
|
335
|
+
& input {
|
|
336
|
+
width: 100%;
|
|
337
|
+
min-width: 0;
|
|
338
|
+
border: 0;
|
|
339
|
+
outline: none;
|
|
340
|
+
background: transparent;
|
|
341
|
+
color: var(--dsw-alias-label-primary);
|
|
342
|
+
font: inherit;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
&:focus-within { border-color: var(--dsw-alias-label-secondary); }
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.dsh-css-project-create,
|
|
349
|
+
.dsh-css-project-secondary {
|
|
350
|
+
display: inline-flex;
|
|
351
|
+
align-items: center;
|
|
352
|
+
justify-content: center;
|
|
353
|
+
gap: 6px;
|
|
354
|
+
min-height: 34px;
|
|
355
|
+
padding: 6px 16px;
|
|
356
|
+
border: 1px solid transparent;
|
|
357
|
+
border-radius: 20px;
|
|
358
|
+
white-space: nowrap;
|
|
359
|
+
background: var(--dsw-alias-label-primary);
|
|
360
|
+
color: var(--dsw-alias-bg-base);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.dsh-css-project-secondary {
|
|
364
|
+
background: transparent;
|
|
365
|
+
color: var(--dsw-alias-label-primary);
|
|
366
|
+
border-color: var(--dsw-alias-border-l3);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.dsh-css-project-columns,
|
|
370
|
+
.dsh-css-project-row {
|
|
371
|
+
display: grid;
|
|
372
|
+
grid-template-columns: minmax(0, 1fr) 100px 80px;
|
|
373
|
+
align-items: center;
|
|
374
|
+
gap: 16px;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.dsh-css-project-columns {
|
|
378
|
+
min-height: 32px;
|
|
379
|
+
border-bottom: 1px solid var(--dsw-alias-border-l3);
|
|
380
|
+
color: var(--dsw-alias-label-tertiary);
|
|
381
|
+
font-size: 12px;
|
|
382
|
+
|
|
383
|
+
& button { border: 0; padding: 0; color: inherit; background: transparent; }
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.dsh-css-project-row {
|
|
387
|
+
position: relative;
|
|
388
|
+
min-height: 70px;
|
|
389
|
+
border-bottom: 1px solid var(--dsw-alias-border-l3);
|
|
390
|
+
|
|
391
|
+
&::before {
|
|
392
|
+
content: '';
|
|
393
|
+
position: absolute;
|
|
394
|
+
inset: 0 -12px;
|
|
395
|
+
border-radius: 12px;
|
|
396
|
+
pointer-events: none;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
&:is(:hover, :focus-within, .is-open)::before { background: var(--dsw-alias-interactive-bg-hover); }
|
|
400
|
+
& > * { position: relative; }
|
|
401
|
+
&:is(:hover, :focus-within, .is-open) .dsh-css-project-chevron { opacity: 1; }
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.dsh-css-project-name-cell { min-width: 0; }
|
|
405
|
+
|
|
406
|
+
.dsh-css-project-name {
|
|
407
|
+
display: flex;
|
|
408
|
+
align-items: center;
|
|
409
|
+
gap: 20px;
|
|
410
|
+
width: 100%;
|
|
411
|
+
min-height: 70px;
|
|
412
|
+
padding: 8px;
|
|
413
|
+
border: 0;
|
|
414
|
+
background: transparent;
|
|
415
|
+
color: inherit;
|
|
416
|
+
text-align: left;
|
|
417
|
+
|
|
418
|
+
& svg { flex: none; }
|
|
419
|
+
& span { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.dsh-css-project-chevron { opacity: 0; }
|
|
423
|
+
.dsh-css-project-time { color: var(--dsw-alias-label-secondary); white-space: nowrap; }
|
|
424
|
+
.dsh-css-project-actions { display: flex; gap: 4px; }
|
|
425
|
+
|
|
426
|
+
.dsh-css-icon-button {
|
|
427
|
+
display: inline-flex;
|
|
428
|
+
align-items: center;
|
|
429
|
+
justify-content: center;
|
|
430
|
+
flex: none;
|
|
431
|
+
width: 32px;
|
|
432
|
+
height: 32px;
|
|
433
|
+
padding: 0;
|
|
434
|
+
border: 0;
|
|
435
|
+
border-radius: 8px;
|
|
436
|
+
background: transparent;
|
|
437
|
+
color: var(--dsw-alias-label-secondary);
|
|
438
|
+
|
|
439
|
+
&:is(:hover, [aria-expanded='true']) { background: var(--dsw-alias-interactive-bg-hover); }
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.dsh-css-projects-page button:focus-visible,
|
|
443
|
+
.dsh-css-project-dialog-actions button:focus-visible {
|
|
444
|
+
outline: 2px solid var(--dsw-focus-ring-color, var(--dsw-alias-label-secondary));
|
|
445
|
+
outline-offset: 2px;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.dsh-css-project-empty { padding: 60px 0; text-align: center; color: var(--dsw-alias-label-tertiary); }
|
|
449
|
+
.dsh-css-project-error { color: var(--dsw-alias-state-error-primary, #dc6767); overflow-wrap: anywhere; }
|
|
450
|
+
.dsh-css-project-notice { color: var(--dsw-alias-label-secondary); }
|
|
451
|
+
.dsh-css-project-field { display: flex; flex-direction: column; gap: 10px; }
|
|
452
|
+
.dsh-css-project-field input {
|
|
453
|
+
padding: 10px 12px;
|
|
454
|
+
border: 1px solid var(--dsw-alias-border-l3);
|
|
455
|
+
border-radius: 8px;
|
|
456
|
+
background: var(--dsw-alias-bg-base);
|
|
457
|
+
color: var(--dsw-alias-label-primary);
|
|
458
|
+
font: inherit;
|
|
459
|
+
}
|
|
460
|
+
.dsh-css-project-path { color: var(--dsw-alias-label-tertiary); font-size: 12px; overflow-wrap: anywhere; }
|
|
461
|
+
|
|
462
|
+
/* 项目弹窗使用 Codex 的深灰底、圆角和按钮配色。 */
|
|
463
|
+
.dsh-css-project-remove-dialog,
|
|
464
|
+
.dsh-css-project-edit-dialog {
|
|
465
|
+
gap: 12px;
|
|
466
|
+
padding-bottom: 20px;
|
|
467
|
+
border-radius: 22px;
|
|
468
|
+
box-shadow: none;
|
|
469
|
+
|
|
470
|
+
& > :first-child > :first-child { padding: 20px 14px 4px 20px; }
|
|
471
|
+
& h2 { font-size: 20px; line-height: 26px; font-weight: 600; overflow-wrap: anywhere; }
|
|
472
|
+
& > :last-child { padding: 0 20px; }
|
|
473
|
+
& .dsh-css-project-dialog-actions { gap: 12px; }
|
|
474
|
+
& .dsh-css-project-dialog-actions button { min-height: 32px; padding: 5px 16px; border-radius: 10px; }
|
|
475
|
+
& .dsh-css-project-secondary { border-color: transparent; }
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.dsh-css-project-remove-dialog {
|
|
479
|
+
width: min(420px, 100%);
|
|
480
|
+
& .dsh-css-project-remove-content > p { padding: 0 20px; }
|
|
481
|
+
& .dsh-css-project-remove-content > :last-child { margin-top: 0; padding: 0 20px; }
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.dsh-css-project-edit-dialog {
|
|
485
|
+
width: min(520px, 100%);
|
|
486
|
+
gap: 20px;
|
|
487
|
+
|
|
488
|
+
& .dsh-css-project-edit-content > :first-child { padding-bottom: 12px; }
|
|
489
|
+
& .dsh-css-project-edit-content > :last-child { margin-top: 0; padding: 0 20px; }
|
|
490
|
+
& .dsh-css-project-field { gap: 12px; }
|
|
491
|
+
& .dsh-css-project-name-input {
|
|
492
|
+
display: flex;
|
|
493
|
+
align-items: center;
|
|
494
|
+
gap: 12px;
|
|
495
|
+
padding: 0 12px;
|
|
496
|
+
border: 1px solid var(--dsw-alias-border-l3);
|
|
497
|
+
border-radius: 12px;
|
|
498
|
+
}
|
|
499
|
+
& .dsh-css-project-name-input:focus-within { border-color: var(--dsw-alias-label-secondary); }
|
|
500
|
+
& .dsh-css-project-name-input input {
|
|
501
|
+
min-width: 0;
|
|
502
|
+
width: 100%;
|
|
503
|
+
height: 38px;
|
|
504
|
+
padding: 0;
|
|
505
|
+
border: 0;
|
|
506
|
+
outline: 0;
|
|
507
|
+
background: transparent;
|
|
508
|
+
}
|
|
509
|
+
& svg { flex: none; }
|
|
510
|
+
& .dsh-css-project-source { display: flex; flex-direction: column; gap: 8px; }
|
|
511
|
+
& .dsh-css-project-source-label { font-size: 13px; font-weight: 600; }
|
|
512
|
+
& .dsh-css-project-path {
|
|
513
|
+
box-sizing: border-box;
|
|
514
|
+
display: flex;
|
|
515
|
+
align-items: center;
|
|
516
|
+
gap: 10px;
|
|
517
|
+
min-height: 48px;
|
|
518
|
+
padding: 10px 12px;
|
|
519
|
+
border: 1px solid var(--dsw-alias-border-l3);
|
|
520
|
+
border-radius: 10px;
|
|
521
|
+
color: var(--dsw-alias-label-primary);
|
|
522
|
+
font-size: 13px;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
body[data-ds-dark-theme] :is(.dsh-css-project-remove-dialog, .dsh-css-project-edit-dialog) {
|
|
527
|
+
background: rgb(55, 56, 51);
|
|
528
|
+
|
|
529
|
+
& h2 { color: rgb(238, 238, 233); }
|
|
530
|
+
& .dsh-css-project-remove-content > p,
|
|
531
|
+
& .dsh-css-project-secondary { color: rgb(142, 143, 137); }
|
|
532
|
+
& .dsh-css-project-secondary:hover:not(:disabled) { background: rgb(255 255 255 / 5%); }
|
|
533
|
+
& .dsh-css-project-remove { color: rgb(240, 100, 96); background: rgb(76, 61, 56); }
|
|
534
|
+
& .dsh-css-project-remove:hover:not(:disabled) { background: rgb(88, 65, 60); }
|
|
535
|
+
& .dsh-css-project-name-input { background: rgb(58, 59, 53); border-color: rgb(80, 81, 74); }
|
|
536
|
+
& .dsh-css-project-name-input:focus-within { border-color: rgb(148, 149, 138); }
|
|
537
|
+
& .dsh-css-project-path { background: rgb(58, 59, 53); border-color: rgb(70, 71, 64); }
|
|
538
|
+
& .dsh-css-project-path svg { color: rgb(142, 143, 137); }
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
body[data-ds-dark-theme] .dsh-css-project-edit-dialog .dsh-css-project-create {
|
|
542
|
+
background: rgb(248, 248, 243);
|
|
543
|
+
color: rgb(55, 56, 51);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
body[data-ds-dark-theme] [role='presentation']:has(> :is(.dsh-css-project-remove-dialog, .dsh-css-project-edit-dialog)) {
|
|
547
|
+
--dsw-alias-bg-mask-1: rgb(0 0 0 / 15%);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
@media (max-width: 760px) {
|
|
551
|
+
.dsh-css-projects-content { padding: 24px 20px; }
|
|
552
|
+
.dsh-css-projects-header { align-items: stretch; flex-direction: column; }
|
|
553
|
+
.dsh-css-project-search { flex: 1; width: auto; }
|
|
554
|
+
.dsh-css-project-columns, .dsh-css-project-row { grid-template-columns: minmax(0, 1fr) 64px 68px; gap: 8px; }
|
|
555
|
+
.dsh-css-project-name { gap: 10px; }
|
|
556
|
+
}
|