@orbit-work/ui 0.1.1 → 0.1.2-preview.0
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/dist/index.cjs +13 -13
- package/dist/index.d.cts +19 -2
- package/dist/index.d.ts +19 -2
- package/dist/index.js +13 -13
- package/dist/orbitwork.css +82 -18
- package/package.json +1 -1
- package/src/components/composite/ContextUsageIndicatorView/index.tsx +1 -1
- package/src/components/composite/Icons/index.tsx +10 -4
- package/src/components/composite/Markdown/MarkdownStyleSource.test.mjs +16 -1
- package/src/components/domain/desktop/DesktopLibraryAuthorizationDialog/DesktopLibraryAuthorizationDialogSource.test.mjs +22 -0
- package/src/components/domain/desktop/DesktopLibraryAuthorizationDialog/index.tsx +72 -57
- package/src/components/domain/desktop/DesktopLibraryPageView/DesktopLibraryPageView.stories.tsx +7 -0
- package/src/components/domain/desktop/DesktopLibraryPageView/index.tsx +14 -0
- package/src/components/domain/desktop/DesktopLoginView/DesktopLoginViewSource.test.mjs +8 -1
- package/src/components/domain/desktop/DesktopLoginView/index.tsx +28 -14
- package/src/components/domain/desktop/DesktopSidebarView/DesktopSidebarViewSource.test.mjs +4 -2
- package/src/components/domain/desktop/DesktopSidebarView/index.tsx +43 -7
- package/src/components/domain/desktop/DesktopSitesListView/index.tsx +2 -2
- package/src/components/primitives/Button/index.tsx +4 -1
- package/src/components/primitives/Menu/index.tsx +2 -1
- package/src/components/primitives/Select/index.tsx +103 -61
package/dist/orbitwork.css
CHANGED
|
@@ -244,6 +244,9 @@
|
|
|
244
244
|
.collapse {
|
|
245
245
|
visibility: collapse;
|
|
246
246
|
}
|
|
247
|
+
.invisible {
|
|
248
|
+
visibility: hidden;
|
|
249
|
+
}
|
|
247
250
|
.visible {
|
|
248
251
|
visibility: visible;
|
|
249
252
|
}
|
|
@@ -6178,23 +6181,30 @@
|
|
|
6178
6181
|
max-width: 100%;
|
|
6179
6182
|
margin: var(--ow-space-2, 8px) 0 var(--ow-space-3, 12px);
|
|
6180
6183
|
overflow-x: auto;
|
|
6184
|
+
overscroll-behavior-inline: contain;
|
|
6181
6185
|
border: var(--ow-border-width-thin, 1px) solid var(--ow-border);
|
|
6182
6186
|
border-radius: var(--ow-radius-card);
|
|
6183
6187
|
background: var(--ow-surface);
|
|
6184
6188
|
}
|
|
6185
6189
|
.md table {
|
|
6186
|
-
width:
|
|
6187
|
-
min-width: 100
|
|
6190
|
+
width: 100%;
|
|
6191
|
+
min-width: min(480px, 100%);
|
|
6188
6192
|
border-collapse: collapse;
|
|
6189
|
-
|
|
6193
|
+
table-layout: auto;
|
|
6194
|
+
font: 400 var(--ow-fs-ui)/1.55 var(--ow-font-sans);
|
|
6190
6195
|
}
|
|
6191
6196
|
.md th, .md td {
|
|
6197
|
+
min-width: 88px;
|
|
6198
|
+
max-width: 360px;
|
|
6192
6199
|
padding: var(--ow-space-2, 8px) var(--ow-space-3, 12px);
|
|
6193
6200
|
border-bottom: var(--ow-border-width-thin, 1px) solid var(--ow-border);
|
|
6194
6201
|
color: var(--ow-text-2);
|
|
6195
6202
|
text-align: left;
|
|
6196
6203
|
vertical-align: top;
|
|
6197
|
-
white-space:
|
|
6204
|
+
white-space: normal;
|
|
6205
|
+
overflow-wrap: anywhere;
|
|
6206
|
+
word-break: break-word;
|
|
6207
|
+
text-wrap: pretty;
|
|
6198
6208
|
}
|
|
6199
6209
|
.md tr:last-child td {
|
|
6200
6210
|
border-bottom: 0;
|
|
@@ -6203,9 +6213,11 @@
|
|
|
6203
6213
|
background: var(--ow-surface-2);
|
|
6204
6214
|
font-weight: 500;
|
|
6205
6215
|
color: var(--ow-text);
|
|
6216
|
+
text-wrap: balance;
|
|
6206
6217
|
}
|
|
6207
6218
|
.md td code, .md th code {
|
|
6208
|
-
white-space:
|
|
6219
|
+
white-space: normal;
|
|
6220
|
+
overflow-wrap: anywhere;
|
|
6209
6221
|
}
|
|
6210
6222
|
.md .al-right {
|
|
6211
6223
|
text-align: right;
|
|
@@ -6825,6 +6837,24 @@
|
|
|
6825
6837
|
border-radius: 0;
|
|
6826
6838
|
box-shadow: none;
|
|
6827
6839
|
}
|
|
6840
|
+
.auth-restore-splash {
|
|
6841
|
+
position: absolute;
|
|
6842
|
+
inset: 0;
|
|
6843
|
+
display: flex;
|
|
6844
|
+
flex-direction: column;
|
|
6845
|
+
align-items: center;
|
|
6846
|
+
justify-content: center;
|
|
6847
|
+
gap: var(--ow-space-4, 16px);
|
|
6848
|
+
-webkit-app-region: drag;
|
|
6849
|
+
}
|
|
6850
|
+
.auth-restore-splash img {
|
|
6851
|
+
opacity: 0.9;
|
|
6852
|
+
}
|
|
6853
|
+
.auth-restore-splash p {
|
|
6854
|
+
margin: 0;
|
|
6855
|
+
color: var(--ow-text-3);
|
|
6856
|
+
font-size: var(--ow-text-sm, 13px);
|
|
6857
|
+
}
|
|
6828
6858
|
.window-chrome {
|
|
6829
6859
|
position: absolute;
|
|
6830
6860
|
left: 12px;
|
|
@@ -7122,6 +7152,16 @@
|
|
|
7122
7152
|
gap: var(--ow-space-1, 4px);
|
|
7123
7153
|
padding-top: 2px;
|
|
7124
7154
|
}
|
|
7155
|
+
.owprojects-boot {
|
|
7156
|
+
flex: 1;
|
|
7157
|
+
min-height: 120px;
|
|
7158
|
+
display: flex;
|
|
7159
|
+
flex-direction: column;
|
|
7160
|
+
align-items: center;
|
|
7161
|
+
justify-content: center;
|
|
7162
|
+
gap: var(--ow-space-3, 12px);
|
|
7163
|
+
color: var(--ow-text-3);
|
|
7164
|
+
}
|
|
7125
7165
|
.owprojects::-webkit-scrollbar {
|
|
7126
7166
|
width: 0;
|
|
7127
7167
|
}
|
|
@@ -8066,7 +8106,7 @@
|
|
|
8066
8106
|
.desktop-library-page__state strong {
|
|
8067
8107
|
color: var(--ow-error);
|
|
8068
8108
|
}
|
|
8069
|
-
.desktop-library-page__state span, .desktop-library-page__demo p {
|
|
8109
|
+
.desktop-library-page__state > span, .desktop-library-page__demo p {
|
|
8070
8110
|
margin: 0;
|
|
8071
8111
|
color: var(--ow-text-3);
|
|
8072
8112
|
font: 400 var(--ow-fs-ui)/1.6 var(--ow-font-sans);
|
|
@@ -8088,6 +8128,8 @@
|
|
|
8088
8128
|
border-bottom: var(--ow-border-width-thin, 1px) solid var(--ow-border);
|
|
8089
8129
|
}
|
|
8090
8130
|
.desktop-library-authorization-dialog [data-slot="dialog-body"] {
|
|
8131
|
+
display: flex;
|
|
8132
|
+
min-height: 0;
|
|
8091
8133
|
padding: 0;
|
|
8092
8134
|
}
|
|
8093
8135
|
.desktop-library-consent {
|
|
@@ -8099,6 +8141,13 @@
|
|
|
8099
8141
|
padding: var(--ow-space-5, 20px) var(--ow-space-6, 24px) var(--ow-space-6, 24px);
|
|
8100
8142
|
color: var(--ow-text-2);
|
|
8101
8143
|
}
|
|
8144
|
+
.desktop-library-consent--authorizing {
|
|
8145
|
+
width: 100%;
|
|
8146
|
+
height: min(680px, 72vh);
|
|
8147
|
+
min-height: 0;
|
|
8148
|
+
box-sizing: border-box;
|
|
8149
|
+
overflow: hidden;
|
|
8150
|
+
}
|
|
8102
8151
|
.desktop-library-consent__flow {
|
|
8103
8152
|
display: flex;
|
|
8104
8153
|
align-items: center;
|
|
@@ -8237,21 +8286,24 @@
|
|
|
8237
8286
|
font: 400 var(--ow-fs-body-sm)/1.5 var(--ow-font-sans);
|
|
8238
8287
|
}
|
|
8239
8288
|
.desktop-library-consent__webview {
|
|
8240
|
-
|
|
8241
|
-
width:
|
|
8242
|
-
height:
|
|
8243
|
-
|
|
8289
|
+
display: flex;
|
|
8290
|
+
min-width: 0;
|
|
8291
|
+
min-height: 360px;
|
|
8292
|
+
flex: 1;
|
|
8244
8293
|
overflow: hidden;
|
|
8245
|
-
border:
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
clip: rect(0 0 0 0);
|
|
8294
|
+
border: var(--ow-border-width-thin, 1px) solid var(--ow-border);
|
|
8295
|
+
border-radius: var(--ow-radius-lg);
|
|
8296
|
+
background: var(--ow-surface-2);
|
|
8249
8297
|
}
|
|
8250
8298
|
.desktop-library-consent__webview webview {
|
|
8251
|
-
display:
|
|
8252
|
-
width:
|
|
8253
|
-
height:
|
|
8299
|
+
display: flex;
|
|
8300
|
+
width: 100%;
|
|
8301
|
+
height: 100%;
|
|
8302
|
+
min-width: 0;
|
|
8303
|
+
min-height: 0;
|
|
8304
|
+
flex: 1;
|
|
8254
8305
|
border: 0;
|
|
8306
|
+
background: var(--ow-bg);
|
|
8255
8307
|
}
|
|
8256
8308
|
.desktop-library-consent__error {
|
|
8257
8309
|
margin: 0;
|
|
@@ -8277,6 +8329,12 @@
|
|
|
8277
8329
|
max-height: 82vh;
|
|
8278
8330
|
padding: var(--ow-space-4, 16px);
|
|
8279
8331
|
}
|
|
8332
|
+
.desktop-library-consent--authorizing {
|
|
8333
|
+
height: 74vh;
|
|
8334
|
+
}
|
|
8335
|
+
.desktop-library-consent__webview {
|
|
8336
|
+
min-height: 280px;
|
|
8337
|
+
}
|
|
8280
8338
|
.desktop-library-consent__actions {
|
|
8281
8339
|
position: sticky;
|
|
8282
8340
|
bottom: calc(var(--ow-space-2, 8px) * -1);
|
|
@@ -9493,6 +9551,12 @@
|
|
|
9493
9551
|
.history-status {
|
|
9494
9552
|
margin-bottom: 18px;
|
|
9495
9553
|
}
|
|
9554
|
+
.history-status:has([data-ow-part="spinner"]),
|
|
9555
|
+
.history-top-status:has([data-ow-part="spinner"]) {
|
|
9556
|
+
display: inline-flex;
|
|
9557
|
+
align-items: center;
|
|
9558
|
+
gap: var(--ow-space-15, 6px);
|
|
9559
|
+
}
|
|
9496
9560
|
.history-top-status.error,
|
|
9497
9561
|
.history-status.error {
|
|
9498
9562
|
border-color: var(--ow-error-tint-border);
|
|
@@ -18089,7 +18153,7 @@ img.g {
|
|
|
18089
18153
|
word-break: break-all;
|
|
18090
18154
|
}
|
|
18091
18155
|
.browser-sidebar {
|
|
18092
|
-
width: min(
|
|
18156
|
+
width: min(400px, 38vw);
|
|
18093
18157
|
max-width: calc(100% - clamp(0px, calc(100% - 300px), 400px));
|
|
18094
18158
|
flex: 0 0 auto;
|
|
18095
18159
|
display: flex;
|
package/package.json
CHANGED
|
@@ -110,7 +110,7 @@ export function ContextUsageIndicatorView(props: ContextUsageIndicatorViewProps)
|
|
|
110
110
|
<TotalRow label="总可用" value={props.totalLabel} strong />
|
|
111
111
|
{props.totalLabel === "—" && (
|
|
112
112
|
<p className="context-usage-card__unavailable">
|
|
113
|
-
{props.modelLoading ? "正在获取模型上下文上限。" : "
|
|
113
|
+
{props.modelLoading ? "正在获取模型上下文上限。" : "Platform 暂未提供此模型的上下文上限。"}
|
|
114
114
|
</p>
|
|
115
115
|
)}
|
|
116
116
|
</Card>
|
|
@@ -1157,12 +1157,17 @@ export const PanelFold = (p: IconProps) => (
|
|
|
1157
1157
|
)
|
|
1158
1158
|
|
|
1159
1159
|
// Product integration icons are intentionally outside the Figma 05 Icons catalog.
|
|
1160
|
+
const dingTalkPath =
|
|
1161
|
+
"M849.92 51.2H174.08c-67.8656 0-122.88 55.0144-122.88 122.88v675.84c0 67.8656 55.0144 122.88 122.88 122.88h675.84c67.8656 0 122.88-55.0144 122.88-122.88V174.08c0-67.8656-55.0144-122.88-122.88-122.88z m-97.17248 383.4112c-1.00352 4.61824-3.61472 10.84928-7.22944 19.18976l-0.50688 0.50176c-21.69856 46.01344-77.66016 136.13568-77.66016 136.13568l-0.50688-0.50176-16.57344 28.43136h79.17056L578.33472 819.2l34.16064-136.63744h-62.09024l21.69856-90.624c-17.58208 4.1216-38.28224 9.85088-62.592 18.08384 0 0-33.15712 19.18976-95.24736-37.26848 0 0-41.89696-37.2736-17.58208-46.01344 10.34752-4.1216 50.23232-8.84736 81.78176-12.95872 42.3936-5.72928 68.81792-8.84736 68.81792-8.84736s-130.90816 2.10944-161.95584-3.10784c-31.04256-4.62336-70.4256-56.97024-78.6688-102.48192 0 0-12.95872-24.81664 27.9296-12.95872 40.39168 11.8528 210.18112 46.01344 210.18112 46.01344S324.2496 365.2864 309.78048 348.70784c-14.46912-16.57856-42.99776-90.01984-39.38304-135.13216 0 0 1.50528-11.35104 12.95872-8.23808 0 0 162.55488 74.5472 273.77664 114.93376 111.8208 41.40032 208.57344 62.09536 195.61472 114.33984z"
|
|
1160
1162
|
export const DingTalk = (p: IconProps) => (
|
|
1161
1163
|
<svg viewBox="0 0 1024 1024" {...p}>
|
|
1162
|
-
<path
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1164
|
+
<path d={dingTalkPath} fill="#3296FA" />
|
|
1165
|
+
</svg>
|
|
1166
|
+
)
|
|
1167
|
+
/** 钉钉 AI 伙伴:同 DingTalk 几何,fill 换 #1677FF 作同族第二色区分(登录页双钉钉入口用)。 */
|
|
1168
|
+
export const DingTalkAi = (p: IconProps) => (
|
|
1169
|
+
<svg viewBox="0 0 1024 1024" {...p}>
|
|
1170
|
+
<path d={dingTalkPath} fill="#1677FF" />
|
|
1166
1171
|
</svg>
|
|
1167
1172
|
)
|
|
1168
1173
|
export const Feishu = (p: IconProps) => (
|
|
@@ -1314,6 +1319,7 @@ export const Icons = {
|
|
|
1314
1319
|
Skills,
|
|
1315
1320
|
AlertCircle,
|
|
1316
1321
|
DingTalk,
|
|
1322
|
+
DingTalkAi,
|
|
1317
1323
|
Feishu,
|
|
1318
1324
|
WeCom,
|
|
1319
1325
|
CommentPlus,
|
|
@@ -98,7 +98,7 @@ test("markdown tables reuse the DataTable facade in one theme-agnostic block", (
|
|
|
98
98
|
)
|
|
99
99
|
assert.match(
|
|
100
100
|
styleSource,
|
|
101
|
-
/\.md th \{ background: var\(--ow-surface-2\); font-weight: 500; color: var\(--ow-text\); \}/,
|
|
101
|
+
/\.md th \{ background: var\(--ow-surface-2\); font-weight: 500; color: var\(--ow-text\); text-wrap: balance; \}/,
|
|
102
102
|
)
|
|
103
103
|
assert.doesNotMatch(styleSource, /var\(--ow-gray-00\)/)
|
|
104
104
|
assert.doesNotMatch(styleSource, /\[data-theme="dark"\] \.md/)
|
|
@@ -120,6 +120,21 @@ test("markdown tables draw row rules only — no vertical cell borders, header a
|
|
|
120
120
|
assert.doesNotMatch(styleSource, /\.md th:last-child, \.md td:last-child \{ border-right: 0; \}/)
|
|
121
121
|
})
|
|
122
122
|
|
|
123
|
+
// 可读性修复:nowrap + width:max-content 会把长内容顶成强制横滚,
|
|
124
|
+
// 改为列宽 88–360px、任意断词、容器内滚不链到页面(overscroll-behavior-inline)。
|
|
125
|
+
// 斑马纹(tbody tr:nth-child(even))上游设计新增,DS2 立面没有——不迁。
|
|
126
|
+
test("markdown tables wrap long content while preserving readable column widths", () => {
|
|
127
|
+
assert.match(styleSource, /\.md table \{[^}]*width: 100%; min-width: min\(480px, 100%\)/)
|
|
128
|
+
assert.match(styleSource, /\.md th, \.md td \{[^}]*min-width: 88px; max-width: 360px/)
|
|
129
|
+
assert.match(
|
|
130
|
+
styleSource,
|
|
131
|
+
/\.md th, \.md td \{[^}]*white-space: normal; overflow-wrap: anywhere; word-break: break-word/,
|
|
132
|
+
)
|
|
133
|
+
assert.match(styleSource, /\.md__table-wrap \{[^}]*overscroll-behavior-inline: contain/)
|
|
134
|
+
assert.match(styleSource, /\.md td code, \.md th code \{ white-space: normal; overflow-wrap: anywhere; \}/)
|
|
135
|
+
assert.doesNotMatch(styleSource, /nth-child\(even\)/)
|
|
136
|
+
})
|
|
137
|
+
|
|
123
138
|
// 列表(§8):全局 reset 会把 ol/ul 的 list-style 清成 none,.md 族必须自己补回,
|
|
124
139
|
// 否则无序列表没圆点、有序列表没序号(W0 断链)。
|
|
125
140
|
test("markdown lists restore their bullets and numbering after the global reset", () => {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import assert from "node:assert/strict"
|
|
2
|
+
import { readFile } from "node:fs/promises"
|
|
3
|
+
import test from "node:test"
|
|
4
|
+
|
|
5
|
+
const source = await readFile(new URL("./index.tsx", import.meta.url), "utf8")
|
|
6
|
+
const styles = await readFile(new URL("../../../../../styles/desktop-surfaces.css", import.meta.url), "utf8")
|
|
7
|
+
|
|
8
|
+
test("embedded authorization shows an interactive webview after consent", () => {
|
|
9
|
+
assert.ok(source.includes("width={authorizationUrl ? 920 : 520}"))
|
|
10
|
+
assert.ok(source.includes('className="desktop-library-consent__webview"'))
|
|
11
|
+
assert.ok(source.includes('role="region"'))
|
|
12
|
+
assert.ok(source.includes('authorizationUrl ? "取消授权" : "拒绝"'))
|
|
13
|
+
|
|
14
|
+
const hostRule = styles.match(/\.desktop-library-consent__webview \{([^}]*)\}/)?.[1] ?? ""
|
|
15
|
+
const webviewRule = styles.match(/\.desktop-library-consent__webview webview \{([^}]*)\}/)?.[1] ?? ""
|
|
16
|
+
assert.match(hostRule, /display: flex/)
|
|
17
|
+
assert.match(hostRule, /min-height: 360px/)
|
|
18
|
+
assert.match(hostRule, /flex: 1/)
|
|
19
|
+
assert.match(webviewRule, /width: 100%; height: 100%/)
|
|
20
|
+
assert.match(webviewRule, /flex: 1/)
|
|
21
|
+
assert.doesNotMatch(hostRule, /opacity: 0|pointer-events: none|clip:/)
|
|
22
|
+
})
|
|
@@ -56,6 +56,7 @@ export function DesktopLibraryAuthorizationDialog({
|
|
|
56
56
|
if (!host || !authorizationUrl) return
|
|
57
57
|
host.replaceChildren()
|
|
58
58
|
const webview = document.createElement("webview")
|
|
59
|
+
webview.setAttribute("aria-label", `${appName} 授权页面`)
|
|
59
60
|
webview.setAttribute("partition", "persist:library-authorization")
|
|
60
61
|
webview.setAttribute("webpreferences", "contextIsolation=yes, nodeIntegration=no, sandbox=yes")
|
|
61
62
|
webview.setAttribute("src", authorizationUrl)
|
|
@@ -63,7 +64,7 @@ export function DesktopLibraryAuthorizationDialog({
|
|
|
63
64
|
return () => {
|
|
64
65
|
webview.remove()
|
|
65
66
|
}
|
|
66
|
-
}, [authorizationUrl])
|
|
67
|
+
}, [appName, authorizationUrl])
|
|
67
68
|
|
|
68
69
|
return (
|
|
69
70
|
<Dialog
|
|
@@ -71,63 +72,75 @@ export function DesktopLibraryAuthorizationDialog({
|
|
|
71
72
|
title="授权访问"
|
|
72
73
|
subtitle={`${appName} · Orbitwork 安全授权`}
|
|
73
74
|
className="desktop-library-authorization-dialog"
|
|
74
|
-
width={520}
|
|
75
|
+
width={authorizationUrl ? 920 : 520}
|
|
75
76
|
padded={false}
|
|
76
77
|
onClose={submitting === "deny" ? undefined : onClose}
|
|
77
78
|
closable={submitting !== "deny"}
|
|
78
79
|
>
|
|
79
|
-
<div
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
{scope.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
80
|
+
<div
|
|
81
|
+
className={`desktop-library-consent${authorizationUrl ? " desktop-library-consent--authorizing" : ""}`}
|
|
82
|
+
aria-busy={loading || submitting !== null}
|
|
83
|
+
>
|
|
84
|
+
{!authorizationUrl && (
|
|
85
|
+
<>
|
|
86
|
+
<div className="desktop-library-consent__flow" aria-hidden="true">
|
|
87
|
+
<span className="desktop-library-consent__mark desktop-library-consent__mark--orbitwork">
|
|
88
|
+
<OrbitworkMark size={40} />
|
|
89
|
+
</span>
|
|
90
|
+
<span className="desktop-library-consent__arrow">→</span>
|
|
91
|
+
<span
|
|
92
|
+
className={`desktop-library-consent__mark desktop-library-consent__mark--accent${appIconSource ? " desktop-library-consent__mark--image" : ""}`}
|
|
93
|
+
>
|
|
94
|
+
{appIconSource ? (
|
|
95
|
+
<img src={appIconSource} alt="" onError={() => setAppIconFailed(true)} />
|
|
96
|
+
) : (
|
|
97
|
+
initial(appName)
|
|
98
|
+
)}
|
|
99
|
+
</span>
|
|
100
|
+
</div>
|
|
101
|
+
<h2>
|
|
102
|
+
<strong>{appName}</strong> 请求访问你的账号
|
|
103
|
+
</h2>
|
|
104
|
+
<p className="desktop-library-consent__description">{appDescription}</p>
|
|
105
|
+
<div className="desktop-library-consent__facts">
|
|
106
|
+
<span>
|
|
107
|
+
<Lock aria-hidden="true" /> 使用 Orbitwork 账号凭证
|
|
108
|
+
</span>
|
|
109
|
+
<span>
|
|
110
|
+
<Shield aria-hidden="true" /> 仅在资料库请求中生效
|
|
111
|
+
</span>
|
|
112
|
+
</div>
|
|
113
|
+
<section className="desktop-library-consent__scopes" aria-labelledby="desktop-library-consent-scopes">
|
|
114
|
+
<h3 id="desktop-library-consent-scopes">将授予以下权限</h3>
|
|
115
|
+
{loading ? (
|
|
116
|
+
<p className="desktop-library-consent__loading">正在检查登录状态…</p>
|
|
117
|
+
) : scopes.length > 0 ? (
|
|
118
|
+
<ul>
|
|
119
|
+
{scopes.map((scope) => (
|
|
120
|
+
<li key={`${scope.owner ?? ""}:${scope.title}`}>
|
|
121
|
+
<CheckCircle aria-hidden="true" />
|
|
122
|
+
<span>
|
|
123
|
+
<strong>{scope.title}</strong>
|
|
124
|
+
{scope.owner && <small>{scope.owner}</small>}
|
|
125
|
+
{scope.description && <em>{scope.description}</em>}
|
|
126
|
+
</span>
|
|
127
|
+
{scope.required && <b>必需</b>}
|
|
128
|
+
</li>
|
|
129
|
+
))}
|
|
130
|
+
</ul>
|
|
131
|
+
) : (
|
|
132
|
+
<p className="desktop-library-consent__loading">访问资料库内容</p>
|
|
133
|
+
)}
|
|
134
|
+
</section>
|
|
135
|
+
</>
|
|
136
|
+
)}
|
|
129
137
|
{authorizationUrl && (
|
|
130
|
-
<div
|
|
138
|
+
<div
|
|
139
|
+
ref={webviewHostRef}
|
|
140
|
+
className="desktop-library-consent__webview"
|
|
141
|
+
role="region"
|
|
142
|
+
aria-label={`${appName} 授权页面`}
|
|
143
|
+
/>
|
|
131
144
|
)}
|
|
132
145
|
{error && (
|
|
133
146
|
<p className="desktop-library-consent__error" role="alert">
|
|
@@ -139,11 +152,13 @@ export function DesktopLibraryAuthorizationDialog({
|
|
|
139
152
|
</p>
|
|
140
153
|
<div className="desktop-library-consent__actions">
|
|
141
154
|
<Button variant="ghost" disabled={submitting === "deny"} onClick={onDeny}>
|
|
142
|
-
{submitting === "deny" ? "处理中…" : "拒绝"}
|
|
143
|
-
</Button>
|
|
144
|
-
<Button variant="primary" disabled={loading || submitting !== null} onClick={onApprove}>
|
|
145
|
-
{submitting === "approve" ? "授权中…" : "同意授权"}
|
|
155
|
+
{submitting === "deny" ? "处理中…" : authorizationUrl ? "取消授权" : "拒绝"}
|
|
146
156
|
</Button>
|
|
157
|
+
{!authorizationUrl && (
|
|
158
|
+
<Button variant="primary" disabled={loading || submitting !== null} onClick={onApprove}>
|
|
159
|
+
{submitting === "approve" ? "授权中…" : "同意授权"}
|
|
160
|
+
</Button>
|
|
161
|
+
)}
|
|
147
162
|
</div>
|
|
148
163
|
</div>
|
|
149
164
|
</Dialog>
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import type { Ref } from "react"
|
|
4
|
+
import { Button } from "../../../primitives"
|
|
4
5
|
|
|
5
6
|
export interface DesktopLibraryPageViewProps {
|
|
6
7
|
loading?: boolean
|
|
7
8
|
error?: string | null
|
|
9
|
+
authorizationRequired?: boolean
|
|
10
|
+
onRequestAuthorization?: () => void
|
|
8
11
|
webviewHostRef?: Ref<HTMLDivElement>
|
|
9
12
|
}
|
|
10
13
|
|
|
@@ -12,6 +15,8 @@ export interface DesktopLibraryPageViewProps {
|
|
|
12
15
|
export function DesktopLibraryPageView({
|
|
13
16
|
loading = false,
|
|
14
17
|
error = null,
|
|
18
|
+
authorizationRequired = false,
|
|
19
|
+
onRequestAuthorization,
|
|
15
20
|
webviewHostRef,
|
|
16
21
|
}: DesktopLibraryPageViewProps): React.ReactElement {
|
|
17
22
|
return (
|
|
@@ -22,6 +27,15 @@ export function DesktopLibraryPageView({
|
|
|
22
27
|
<strong>资料库页面加载失败</strong>
|
|
23
28
|
<span>{error}</span>
|
|
24
29
|
</div>
|
|
30
|
+
) : authorizationRequired ? (
|
|
31
|
+
<div className="desktop-library-page__state" role="status">
|
|
32
|
+
<span>需要授权后才能访问资料库。</span>
|
|
33
|
+
{onRequestAuthorization && (
|
|
34
|
+
<Button variant="primary" onClick={onRequestAuthorization}>
|
|
35
|
+
立即授权
|
|
36
|
+
</Button>
|
|
37
|
+
)}
|
|
38
|
+
</div>
|
|
25
39
|
) : webviewHostRef ? (
|
|
26
40
|
<div ref={webviewHostRef} className="desktop-library-page__webview-host" aria-busy={loading} />
|
|
27
41
|
) : (
|
|
@@ -115,7 +115,7 @@ test("登录段控件补齐 §6 状态:focus-visible 到位,禁用不用统
|
|
|
115
115
|
test("§7 图标:手搓件退役,归口 composite/Icons;QR 面渲染主进程编码的真图", () => {
|
|
116
116
|
assert.match(
|
|
117
117
|
source,
|
|
118
|
-
/import
|
|
118
|
+
/import { ChevLeft, DingTalk, DingTalkAi, Feishu, Globe, WeCom } from "\.\.\/\.\.\/\.\.\/composite\/Icons"/,
|
|
119
119
|
)
|
|
120
120
|
assert.doesNotMatch(source, /function (?:BackIcon|CheckIcon)\(/)
|
|
121
121
|
assert.doesNotMatch(source, /<Check \/>/)
|
|
@@ -131,6 +131,13 @@ test("§7 图标:手搓件退役,归口 composite/Icons;QR 面渲染主进
|
|
|
131
131
|
assert.match(source, /case "IDENTITY_PROVIDER_KIND_FEISHU": return Feishu/)
|
|
132
132
|
assert.match(source, /case "IDENTITY_PROVIDER_KIND_WECOM": return WeCom/)
|
|
133
133
|
assert.match(source, /default: return Globe/)
|
|
134
|
+
// 「钉钉 AI 伙伴」临时按 displayName 精确匹配出同族蓝色标(kind 契约无独立枚举、
|
|
135
|
+
// 连接 id 为随机 UUID);proto 加 DINGTALK_AI 后此处应翻成 kind 分派断言。
|
|
136
|
+
assert.match(
|
|
137
|
+
source,
|
|
138
|
+
/provider\.kind === "IDENTITY_PROVIDER_KIND_DINGTALK" && provider\.displayName === "钉钉(AI伙伴)"[\s\S]*return DingTalkAi/,
|
|
139
|
+
)
|
|
140
|
+
assert.match(source, /TODO\(kind-dingtalk-ai\)/)
|
|
134
141
|
// 未知 kind 的通用入口:Globe 标+displayName 文本,不再隐藏连接。
|
|
135
142
|
assert.match(source, /login-provider--generic/)
|
|
136
143
|
assert.match(source, /Icon === Globe \? <span>\{provider\.displayName\}<\/span>/)
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
import { useState } from "react"
|
|
4
4
|
import "./DesktopLoginView.css"
|
|
5
5
|
import { BeaconSpinner } from "../../../composite/BeaconSpinner"
|
|
6
|
-
import { ChevLeft, DingTalk, Feishu, Globe, WeCom } from "../../../composite/Icons"
|
|
6
|
+
import { ChevLeft, DingTalk, DingTalkAi, Feishu, Globe, WeCom } from "../../../composite/Icons"
|
|
7
7
|
import { OrbitworkMark } from "../../../composite/OrbitworkLogo"
|
|
8
|
+
import { Tooltip } from "../../../composite/Tooltip"
|
|
8
9
|
import { Button } from "../../../primitives"
|
|
9
10
|
import { Field } from "../../../primitives/Field"
|
|
10
11
|
import { InlineMessage } from "../../../primitives/InlineMessage"
|
|
@@ -158,7 +159,7 @@ export function DesktopLoginView({
|
|
|
158
159
|
const failed = stage === "expired" || stage === "error"
|
|
159
160
|
const passwordEnabled = Boolean(onPasswordLogin)
|
|
160
161
|
const canSubmitPassword = identifier.trim().length > 0 && password.length > 0 && !passwordSubmitting
|
|
161
|
-
const visibleProviders = providers.map((provider) => ({ provider, Icon: providerBrandIcon(provider
|
|
162
|
+
const visibleProviders = providers.map((provider) => ({ provider, Icon: providerBrandIcon(provider) }))
|
|
162
163
|
const providersEnabled = Boolean(onQrLogin) && visibleProviders.length > 0
|
|
163
164
|
|
|
164
165
|
const submitPassword = () => {
|
|
@@ -250,23 +251,30 @@ export function DesktopLoginView({
|
|
|
250
251
|
未知 kind 不渲染该入口——连接列表以后端 identity-providers
|
|
251
252
|
为准,不硬编码三家。裸图标点击件:品牌标自足辨识,不套按钮
|
|
252
253
|
边框底(hover/focus 态在本 CSS 的 .login-provider 一族),
|
|
253
|
-
可访问名取 displayName
|
|
254
|
+
可访问名取 displayName;悬浮提示走 Tooltip(键盘 focus 即出,
|
|
255
|
+
不用原生 title——与自定气泡同现会双重弹层);发起中换小转子。 */}
|
|
254
256
|
<div className="login-provider-row">
|
|
255
257
|
{visibleProviders.map(({ provider, Icon }) => {
|
|
256
258
|
const loading = qrRequesting === provider.id
|
|
257
259
|
return (
|
|
258
|
-
<
|
|
260
|
+
<Tooltip
|
|
259
261
|
key={provider.id}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
+
content={`使用${provider.displayName}扫码登录`}
|
|
263
|
+
side="top"
|
|
262
264
|
disabled={loading}
|
|
263
|
-
aria-label={`使用${provider.displayName}扫码登录`}
|
|
264
|
-
aria-busy={loading || undefined}
|
|
265
|
-
onClick={() => onQrLogin?.(provider.id)}
|
|
266
265
|
>
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
266
|
+
<button
|
|
267
|
+
type="button"
|
|
268
|
+
className={`login-provider${Icon === Globe ? " login-provider--generic" : ""}`}
|
|
269
|
+
disabled={loading}
|
|
270
|
+
aria-label={`使用${provider.displayName}扫码登录`}
|
|
271
|
+
aria-busy={loading || undefined}
|
|
272
|
+
onClick={() => onQrLogin?.(provider.id)}
|
|
273
|
+
>
|
|
274
|
+
{loading ? <Spinner className="w-5 h-5" /> : <Icon />}
|
|
275
|
+
{Icon === Globe ? <span>{provider.displayName}</span> : null}
|
|
276
|
+
</button>
|
|
277
|
+
</Tooltip>
|
|
270
278
|
)
|
|
271
279
|
})}
|
|
272
280
|
</div>
|
|
@@ -408,8 +416,14 @@ export function DesktopLoginView({
|
|
|
408
416
|
}
|
|
409
417
|
|
|
410
418
|
// Unknown categories use a generic entry instead of hiding a usable connection.
|
|
411
|
-
function providerBrandIcon(
|
|
412
|
-
|
|
419
|
+
function providerBrandIcon(provider: LoginProviderOption) {
|
|
420
|
+
// TODO(kind-dingtalk-ai):「钉钉 AI 伙伴」与钉钉同为 DINGTALK kind(契约无独立
|
|
421
|
+
// 枚举、连接 id 是随机 UUID),临时按 displayName 精确匹配出同族蓝色标区分。
|
|
422
|
+
// proto 增加 IDENTITY_PROVIDER_KIND_DINGTALK_AI 后改为按 kind 映射并删除本分支。
|
|
423
|
+
if (provider.kind === "IDENTITY_PROVIDER_KIND_DINGTALK" && provider.displayName === "钉钉(AI伙伴)") {
|
|
424
|
+
return DingTalkAi
|
|
425
|
+
}
|
|
426
|
+
switch (provider.kind) {
|
|
413
427
|
case "IDENTITY_PROVIDER_KIND_DINGTALK": return DingTalk
|
|
414
428
|
case "IDENTITY_PROVIDER_KIND_FEISHU": return Feishu
|
|
415
429
|
case "IDENTITY_PROVIDER_KIND_WECOM": return WeCom
|
|
@@ -380,7 +380,9 @@ test("R2 · T4:账户菜单的新数据口全部可选,缺省即该块不出
|
|
|
380
380
|
}
|
|
381
381
|
})
|
|
382
382
|
|
|
383
|
-
test("library
|
|
383
|
+
test("library is available after my space in primary navigation", () => {
|
|
384
384
|
assert.match(source, /<NavItem\s+id="files"\s+label="我的空间"/)
|
|
385
|
-
assert.
|
|
385
|
+
assert.match(source, /<NavItem\s+id="files"\s+label="我的空间"[\s\S]*<NavItem\s+id="library"\s+label="资料库"/)
|
|
386
|
+
assert.ok(source.includes('active={props.activeNav === "library"}'))
|
|
387
|
+
assert.ok(source.includes('onClick={() => props.onNavigate("library")}'))
|
|
386
388
|
})
|