@blocklet/ui-react 2.12.21 → 2.12.22
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/lib/@types/index.d.ts +1 -0
- package/lib/@types/index.js +1 -0
- package/lib/UserCenter/components/editable-field.d.ts +26 -0
- package/lib/UserCenter/components/editable-field.js +13 -4
- package/lib/UserCenter/components/user-info/link-preview-input.js +31 -11
- package/lib/UserCenter/components/user-info/metadata.js +3 -20
- package/lib/UserCenter/components/user-info/user-status.js +17 -6
- package/lib/UserCenter/components/user-info/utils.d.ts +6 -0
- package/lib/UserCenter/components/user-info/utils.js +10 -0
- package/lib/UserCenter/libs/locales.d.ts +4 -0
- package/lib/UserCenter/libs/locales.js +4 -0
- package/package.json +4 -4
- package/src/@types/index.ts +1 -0
- package/src/UserCenter/components/editable-field.tsx +13 -4
- package/src/UserCenter/components/user-info/link-preview-input.tsx +39 -11
- package/src/UserCenter/components/user-info/metadata.tsx +3 -20
- package/src/UserCenter/components/user-info/user-status.tsx +17 -6
- package/src/UserCenter/components/user-info/utils.ts +16 -0
- package/src/UserCenter/libs/locales.ts +4 -0
package/lib/@types/index.d.ts
CHANGED
package/lib/@types/index.js
CHANGED
|
@@ -20,5 +20,31 @@ interface EditableFieldProps {
|
|
|
20
20
|
canEdit?: boolean;
|
|
21
21
|
renderValue?: (value: string) => React.ReactNode;
|
|
22
22
|
}
|
|
23
|
+
export declare const commonInputStyle: {
|
|
24
|
+
'.MuiOutlinedInput-root': {
|
|
25
|
+
'&:hover': {
|
|
26
|
+
fieldset: {
|
|
27
|
+
borderColor: string;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
'&.Mui-focused': {
|
|
31
|
+
fieldset: {
|
|
32
|
+
borderColor: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export declare const inputFieldStyle: {
|
|
38
|
+
width: string;
|
|
39
|
+
'& .MuiFormHelperText-root': {
|
|
40
|
+
position: string;
|
|
41
|
+
bottom: number;
|
|
42
|
+
left: number;
|
|
43
|
+
margin: number;
|
|
44
|
+
};
|
|
45
|
+
fieldset: {
|
|
46
|
+
borderColor: string;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
23
49
|
declare function EditableField({ value, onChange, onValueValidate, errorMsg, editable, component, placeholder, rows, maxLength, icon, label, children, tooltip, inline, style, verified, canEdit, renderValue, }: EditableFieldProps): JSX.Element | null;
|
|
24
50
|
export default EditableField;
|
|
@@ -5,9 +5,9 @@ import { temp as colors } from "@arcblock/ux/lib/Colors";
|
|
|
5
5
|
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
6
6
|
import { translate } from "@arcblock/ux/lib/Locale/util";
|
|
7
7
|
import VerifiedIcon from "@mui/icons-material/Verified";
|
|
8
|
+
import { mergeSx } from "@arcblock/ux/lib/Util/style";
|
|
8
9
|
import { translations } from "../libs/locales.js";
|
|
9
|
-
const
|
|
10
|
-
width: "100%",
|
|
10
|
+
export const commonInputStyle = {
|
|
11
11
|
".MuiOutlinedInput-root": {
|
|
12
12
|
"&:hover": {
|
|
13
13
|
fieldset: {
|
|
@@ -19,6 +19,15 @@ const inputFieldStyle = {
|
|
|
19
19
|
borderColor: colors.dividerColor
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
export const inputFieldStyle = {
|
|
25
|
+
width: "100%",
|
|
26
|
+
"& .MuiFormHelperText-root": {
|
|
27
|
+
position: "relative",
|
|
28
|
+
bottom: 0,
|
|
29
|
+
left: 0,
|
|
30
|
+
margin: 0
|
|
22
31
|
},
|
|
23
32
|
fieldset: {
|
|
24
33
|
borderColor: colors.dividerColor
|
|
@@ -74,7 +83,7 @@ function EditableField({
|
|
|
74
83
|
},
|
|
75
84
|
value,
|
|
76
85
|
onChange: (e) => handleChange(e.target.value),
|
|
77
|
-
sx: inputFieldStyle,
|
|
86
|
+
sx: mergeSx(inputFieldStyle, !errorMsg ? commonInputStyle : {}),
|
|
78
87
|
error: Boolean(errorMsg),
|
|
79
88
|
helperText: errorMsg
|
|
80
89
|
}
|
|
@@ -91,7 +100,7 @@ function EditableField({
|
|
|
91
100
|
minRows: rows,
|
|
92
101
|
placeholder
|
|
93
102
|
},
|
|
94
|
-
sx: inputFieldStyle,
|
|
103
|
+
sx: mergeSx(inputFieldStyle, !errorMsg ? commonInputStyle : {}),
|
|
95
104
|
value,
|
|
96
105
|
onChange: (e) => handleChange(e.target.value),
|
|
97
106
|
error: Boolean(errorMsg),
|
|
@@ -9,10 +9,27 @@ import { translate } from "@arcblock/ux/lib/Locale/util";
|
|
|
9
9
|
import { useMemoizedFn } from "ahooks";
|
|
10
10
|
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
11
11
|
import LinkIcon from "@arcblock/icons/lib/Link";
|
|
12
|
-
import {
|
|
12
|
+
import { mergeSx } from "@arcblock/ux/lib/Util/style";
|
|
13
|
+
import { joinURL, parseURL } from "ufo";
|
|
13
14
|
import { isValidUrl } from "./utils.js";
|
|
14
15
|
import { translations } from "../../libs/locales.js";
|
|
15
|
-
|
|
16
|
+
import { commonInputStyle, inputFieldStyle } from "../editable-field.js";
|
|
17
|
+
function formatLinkDisplay(link) {
|
|
18
|
+
try {
|
|
19
|
+
const url = parseURL(link);
|
|
20
|
+
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
21
|
+
return url.host || link;
|
|
22
|
+
}
|
|
23
|
+
return link;
|
|
24
|
+
} catch {
|
|
25
|
+
return link;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function LinkInput({
|
|
29
|
+
value,
|
|
30
|
+
onChange,
|
|
31
|
+
errorMsg
|
|
32
|
+
}) {
|
|
16
33
|
const handleUrlChange = (event) => {
|
|
17
34
|
const inputValue = event.target.value;
|
|
18
35
|
onChange(inputValue);
|
|
@@ -24,13 +41,9 @@ function LinkInput({ value, onChange, error }) {
|
|
|
24
41
|
value,
|
|
25
42
|
onChange: handleUrlChange,
|
|
26
43
|
fullWidth: true,
|
|
27
|
-
error,
|
|
28
|
-
helperText:
|
|
29
|
-
sx: {
|
|
30
|
-
fieldset: {
|
|
31
|
-
borderColor: colors.dividerColor
|
|
32
|
-
}
|
|
33
|
-
}
|
|
44
|
+
error: !!errorMsg,
|
|
45
|
+
helperText: errorMsg,
|
|
46
|
+
sx: mergeSx(inputFieldStyle, !errorMsg ? commonInputStyle : {})
|
|
34
47
|
}
|
|
35
48
|
) });
|
|
36
49
|
}
|
|
@@ -68,7 +81,14 @@ function DynamicLinkForm({ links = [], onChange }) {
|
|
|
68
81
|
links.map((link, index) => (
|
|
69
82
|
// eslint-disable-next-line react/no-array-index-key
|
|
70
83
|
/* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "flex-start", mb: 1, children: [
|
|
71
|
-
/* @__PURE__ */ jsx(
|
|
84
|
+
/* @__PURE__ */ jsx(
|
|
85
|
+
LinkInput,
|
|
86
|
+
{
|
|
87
|
+
value: link,
|
|
88
|
+
onChange: (value) => handleInputChange(index, value),
|
|
89
|
+
errorMsg: errors[index] ? t("profile.invalidURL") : ""
|
|
90
|
+
}
|
|
91
|
+
),
|
|
72
92
|
/* @__PURE__ */ jsx(IconButton, { sx: { color: colors.foregroundsFgMuted }, onClick: () => handleRemoveLink(index), children: /* @__PURE__ */ jsx(RemoveIcon, {}) })
|
|
73
93
|
] }, index)
|
|
74
94
|
)),
|
|
@@ -155,7 +175,7 @@ export function LinkPreviewInput({
|
|
|
155
175
|
},
|
|
156
176
|
children: [
|
|
157
177
|
/* @__PURE__ */ jsx(FaviconPreview, { link }),
|
|
158
|
-
/* @__PURE__ */ jsx(LinkDiv, { children: /* @__PURE__ */ jsx(Box, { component: "a", href: link, style: { textDecoration: "none" }, target: "_blank", rel: "noopener noreferrer", children: link }) })
|
|
178
|
+
/* @__PURE__ */ jsx(LinkDiv, { children: /* @__PURE__ */ jsx(Box, { component: "a", href: link, style: { textDecoration: "none" }, target: "_blank", rel: "noopener noreferrer", children: formatLinkDisplay(link) }) })
|
|
159
179
|
]
|
|
160
180
|
},
|
|
161
181
|
link
|
|
@@ -9,15 +9,15 @@ import { joinURL } from "ufo";
|
|
|
9
9
|
import Button from "@arcblock/ux/lib/Button";
|
|
10
10
|
import PhoneInput, { validatePhoneNumber } from "@arcblock/ux/lib/PhoneInput";
|
|
11
11
|
import cloneDeep from "lodash/cloneDeep";
|
|
12
|
+
import { mergeSx } from "@arcblock/ux/lib/Util/style";
|
|
12
13
|
import { useCreation, useMemoizedFn, useReactive } from "ahooks";
|
|
13
14
|
import { useMemo, useRef, useState, memo, forwardRef, useEffect, lazy } from "react";
|
|
14
15
|
import { translate } from "@arcblock/ux/lib/Locale/util";
|
|
15
16
|
import isEmail from "validator/lib/isEmail";
|
|
16
|
-
import { temp as colors } from "@arcblock/ux/lib/Colors";
|
|
17
17
|
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
18
18
|
import { useBrowser } from "@arcblock/react-hooks";
|
|
19
19
|
import { translations } from "../../libs/locales.js";
|
|
20
|
-
import EditableField from "../editable-field.js";
|
|
20
|
+
import EditableField, { commonInputStyle, inputFieldStyle } from "../editable-field.js";
|
|
21
21
|
import { LinkPreviewInput } from "./link-preview-input.js";
|
|
22
22
|
import { currentTimezone, defaultButtonStyle, primaryButtonStyle } from "./utils.js";
|
|
23
23
|
import { TimezoneSelect } from "./timezone-select.js";
|
|
@@ -316,24 +316,7 @@ export default function UserMetadataComponent({
|
|
|
316
316
|
value: phoneValue,
|
|
317
317
|
error: !!validateMsg.phone,
|
|
318
318
|
helperText: validateMsg.phone,
|
|
319
|
-
sx: {
|
|
320
|
-
width: "100%",
|
|
321
|
-
".MuiOutlinedInput-root": {
|
|
322
|
-
"&:hover": {
|
|
323
|
-
fieldset: {
|
|
324
|
-
borderColor: colors.dividerColor
|
|
325
|
-
}
|
|
326
|
-
},
|
|
327
|
-
"&.Mui-focused": {
|
|
328
|
-
fieldset: {
|
|
329
|
-
borderColor: colors.dividerColor
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
},
|
|
333
|
-
fieldset: {
|
|
334
|
-
borderColor: colors.dividerColor
|
|
335
|
-
}
|
|
336
|
-
},
|
|
319
|
+
sx: mergeSx(inputFieldStyle, !validateMsg.phone ? commonInputStyle : {}),
|
|
337
320
|
onChange: (value) => {
|
|
338
321
|
const isValid = validatePhoneNumber(value.phone);
|
|
339
322
|
if (!isValid) {
|
|
@@ -11,7 +11,7 @@ import { formatToDatetime } from "@arcblock/ux/lib/Util";
|
|
|
11
11
|
import { DurationEnum, StatusEnum } from "../../../@types/index.js";
|
|
12
12
|
import StatusDialog from "../status-dialog/index.js";
|
|
13
13
|
import { translations } from "../../libs/locales.js";
|
|
14
|
-
import { getTimeRemaining, isWithinTimeRange } from "./utils.js";
|
|
14
|
+
import { getTimeRemaining, isNotClear, isWithinTimeRange } from "./utils.js";
|
|
15
15
|
const MeetingIcon = lazy(() => import("@arcblock/icons/lib/Meeting"));
|
|
16
16
|
const CommunityIcon = lazy(() => import("@arcblock/icons/lib/Community"));
|
|
17
17
|
const HolidayIcon = lazy(() => import("@arcblock/icons/lib/Holiday"));
|
|
@@ -67,6 +67,10 @@ export default function UserStatus({
|
|
|
67
67
|
}, [status]);
|
|
68
68
|
const clear = useInterval(() => {
|
|
69
69
|
if (status?.value && status?.dateRange?.length === 2) {
|
|
70
|
+
const notClear = isNotClear(status);
|
|
71
|
+
if (notClear) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
70
74
|
const isWithin = isWithinTimeRange(status.dateRange);
|
|
71
75
|
if (!isWithin) {
|
|
72
76
|
pauseInterval();
|
|
@@ -107,7 +111,7 @@ export default function UserStatus({
|
|
|
107
111
|
children: durationData
|
|
108
112
|
};
|
|
109
113
|
});
|
|
110
|
-
}, [t, getDurationData]);
|
|
114
|
+
}, [t, getDurationData, locale]);
|
|
111
115
|
const onOpenStatusSelector = (event) => {
|
|
112
116
|
if (!isMyself) {
|
|
113
117
|
return;
|
|
@@ -129,10 +133,17 @@ export default function UserStatus({
|
|
|
129
133
|
const currentStatus = statusData.find((item) => item.id === status?.value);
|
|
130
134
|
if (currentStatus) {
|
|
131
135
|
const localeOption = locale === "zh" ? "zh-cn" : "en-us";
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
let range;
|
|
137
|
+
const notClear = isNotClear(status);
|
|
138
|
+
if (!notClear) {
|
|
139
|
+
range = status?.dateRange?.map((item) => {
|
|
140
|
+
return formatToDatetime(item, { locale: localeOption });
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (range && range.length > 0) {
|
|
144
|
+
return `${currentStatus.name}: ${range.join("~")}`;
|
|
145
|
+
}
|
|
146
|
+
return currentStatus.name;
|
|
136
147
|
}
|
|
137
148
|
return null;
|
|
138
149
|
}, [status, statusData, locale]);
|
|
@@ -14,6 +14,12 @@ export declare const getStatusDuration: (status: UserMetadata["status"]) => any[
|
|
|
14
14
|
* @returns
|
|
15
15
|
*/
|
|
16
16
|
export declare const isWithinTimeRange: (dateRange: [Date, Date]) => any;
|
|
17
|
+
/**
|
|
18
|
+
* 判断状态持续时间是否为不可清除
|
|
19
|
+
* @param status
|
|
20
|
+
* @returns
|
|
21
|
+
*/
|
|
22
|
+
export declare const isNotClear: (status: UserMetadata["status"]) => any;
|
|
17
23
|
/**
|
|
18
24
|
* 获取当前时间距离结束时间还有多久
|
|
19
25
|
*/
|
|
@@ -52,6 +52,9 @@ export const getStatusDuration = (status) => {
|
|
|
52
52
|
case DurationEnum.ThisWeek:
|
|
53
53
|
dateRange = [current, current.endOf("week")];
|
|
54
54
|
break;
|
|
55
|
+
case DurationEnum.NoClear:
|
|
56
|
+
dateRange = [current, current];
|
|
57
|
+
break;
|
|
55
58
|
default:
|
|
56
59
|
break;
|
|
57
60
|
}
|
|
@@ -61,6 +64,13 @@ export const isWithinTimeRange = (dateRange) => {
|
|
|
61
64
|
const current = dayjs();
|
|
62
65
|
return current.isAfter(dayjs(dateRange[0])) && current.isBefore(dayjs(dateRange[1]));
|
|
63
66
|
};
|
|
67
|
+
export const isNotClear = (status) => {
|
|
68
|
+
const { duration, dateRange } = status ?? {};
|
|
69
|
+
if (!duration || !dateRange) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return duration === DurationEnum.NoClear || dayjs(dateRange?.[0]).isSame(dayjs(dateRange?.[1]));
|
|
73
|
+
};
|
|
64
74
|
export const getTimeRemaining = (date) => {
|
|
65
75
|
const now = dayjs();
|
|
66
76
|
const end = dayjs(date);
|
|
@@ -105,6 +105,7 @@ export declare const translations: {
|
|
|
105
105
|
OffSick: string;
|
|
106
106
|
WorkingRemotely: string;
|
|
107
107
|
duration: {
|
|
108
|
+
NoClear: string;
|
|
108
109
|
ThirtyMinutes: string;
|
|
109
110
|
OneHour: string;
|
|
110
111
|
FourHours: string;
|
|
@@ -131,6 +132,7 @@ export declare const translations: {
|
|
|
131
132
|
quickSettings: string;
|
|
132
133
|
selectEndTime: string;
|
|
133
134
|
pleaseSelectTime: string;
|
|
135
|
+
invalidURL: string;
|
|
134
136
|
timezonePhase: {
|
|
135
137
|
dawn: string;
|
|
136
138
|
morning: string;
|
|
@@ -245,6 +247,7 @@ export declare const translations: {
|
|
|
245
247
|
OffSick: string;
|
|
246
248
|
WorkingRemotely: string;
|
|
247
249
|
duration: {
|
|
250
|
+
NoClear: string;
|
|
248
251
|
ThirtyMinutes: string;
|
|
249
252
|
OneHour: string;
|
|
250
253
|
FourHours: string;
|
|
@@ -272,6 +275,7 @@ export declare const translations: {
|
|
|
272
275
|
pleaseSelectTime: string;
|
|
273
276
|
cleanStatus: string;
|
|
274
277
|
quickSettings: string;
|
|
278
|
+
invalidURL: string;
|
|
275
279
|
timezonePhase: {
|
|
276
280
|
dawn: string;
|
|
277
281
|
morning: string;
|
|
@@ -105,6 +105,7 @@ export const translations = {
|
|
|
105
105
|
OffSick: "\u75C5\u5047",
|
|
106
106
|
WorkingRemotely: "\u8FDC\u7A0B\u5DE5\u4F5C",
|
|
107
107
|
duration: {
|
|
108
|
+
NoClear: "\u4E0D\u8981\u6E05\u9664",
|
|
108
109
|
ThirtyMinutes: "30\u5206\u949F",
|
|
109
110
|
OneHour: "1\u5C0F\u65F6",
|
|
110
111
|
FourHours: "4\u5C0F\u65F6",
|
|
@@ -131,6 +132,7 @@ export const translations = {
|
|
|
131
132
|
quickSettings: "\u5FEB\u6377\u8BBE\u7F6E",
|
|
132
133
|
selectEndTime: "\u9009\u62E9\u7ED3\u675F\u65F6\u95F4",
|
|
133
134
|
pleaseSelectTime: "\u8BF7\u9009\u62E9\u65F6\u95F4",
|
|
135
|
+
invalidURL: "\u65E0\u6548\u7684 URL",
|
|
134
136
|
timezonePhase: {
|
|
135
137
|
dawn: "\u51CC\u6668",
|
|
136
138
|
morning: "\u4E0A\u5348",
|
|
@@ -245,6 +247,7 @@ export const translations = {
|
|
|
245
247
|
OffSick: "Off Sick",
|
|
246
248
|
WorkingRemotely: "Working Remotely",
|
|
247
249
|
duration: {
|
|
250
|
+
NoClear: "Don't clear",
|
|
248
251
|
ThirtyMinutes: "30 minutes",
|
|
249
252
|
OneHour: "1 hour",
|
|
250
253
|
FourHours: "4 hours",
|
|
@@ -272,6 +275,7 @@ export const translations = {
|
|
|
272
275
|
pleaseSelectTime: "Please select time",
|
|
273
276
|
cleanStatus: "Clean status",
|
|
274
277
|
quickSettings: "Quick Settings",
|
|
278
|
+
invalidURL: "Invalid URL",
|
|
275
279
|
timezonePhase: {
|
|
276
280
|
dawn: "AM",
|
|
277
281
|
morning: "AM",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/ui-react",
|
|
3
|
-
"version": "2.12.
|
|
3
|
+
"version": "2.12.22",
|
|
4
4
|
"description": "Some useful front-end web components that can be used in Blocklets.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@abtnode/constant": "^1.16.39",
|
|
36
|
-
"@arcblock/bridge": "^2.12.
|
|
37
|
-
"@arcblock/react-hooks": "^2.12.
|
|
36
|
+
"@arcblock/bridge": "^2.12.22",
|
|
37
|
+
"@arcblock/react-hooks": "^2.12.22",
|
|
38
38
|
"@arcblock/ws": "^1.19.15",
|
|
39
39
|
"@blocklet/did-space-react": "^1.0.33",
|
|
40
40
|
"@iconify-icons/logos": "^1.2.36",
|
|
@@ -87,5 +87,5 @@
|
|
|
87
87
|
"jest": "^29.7.0",
|
|
88
88
|
"unbuild": "^2.0.0"
|
|
89
89
|
},
|
|
90
|
-
"gitHead": "
|
|
90
|
+
"gitHead": "f6f7c7949553c8c3e66516f13f768264f480b5d6"
|
|
91
91
|
}
|
package/src/@types/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { temp as colors } from '@arcblock/ux/lib/Colors';
|
|
|
5
5
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
6
6
|
import { translate } from '@arcblock/ux/lib/Locale/util';
|
|
7
7
|
import VerifiedIcon from '@mui/icons-material/Verified';
|
|
8
|
+
import { mergeSx } from '@arcblock/ux/lib/Util/style';
|
|
8
9
|
import { translations } from '../libs/locales';
|
|
9
10
|
|
|
10
11
|
interface EditableFieldProps {
|
|
@@ -28,8 +29,7 @@ interface EditableFieldProps {
|
|
|
28
29
|
renderValue?: (value: string) => React.ReactNode;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
const
|
|
32
|
-
width: '100%',
|
|
32
|
+
export const commonInputStyle = {
|
|
33
33
|
'.MuiOutlinedInput-root': {
|
|
34
34
|
'&:hover': {
|
|
35
35
|
fieldset: {
|
|
@@ -42,7 +42,16 @@ const inputFieldStyle = {
|
|
|
42
42
|
},
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
|
+
};
|
|
45
46
|
|
|
47
|
+
export const inputFieldStyle = {
|
|
48
|
+
width: '100%',
|
|
49
|
+
'& .MuiFormHelperText-root': {
|
|
50
|
+
position: 'relative',
|
|
51
|
+
bottom: 0,
|
|
52
|
+
left: 0,
|
|
53
|
+
margin: 0,
|
|
54
|
+
},
|
|
46
55
|
fieldset: {
|
|
47
56
|
borderColor: colors.dividerColor,
|
|
48
57
|
},
|
|
@@ -97,7 +106,7 @@ function EditableField({
|
|
|
97
106
|
}}
|
|
98
107
|
value={value}
|
|
99
108
|
onChange={(e) => handleChange(e.target.value)}
|
|
100
|
-
sx={inputFieldStyle}
|
|
109
|
+
sx={mergeSx(inputFieldStyle, !errorMsg ? commonInputStyle : {})}
|
|
101
110
|
error={Boolean(errorMsg)}
|
|
102
111
|
helperText={errorMsg}
|
|
103
112
|
/>
|
|
@@ -114,7 +123,7 @@ function EditableField({
|
|
|
114
123
|
minRows: rows,
|
|
115
124
|
placeholder,
|
|
116
125
|
}}
|
|
117
|
-
sx={inputFieldStyle}
|
|
126
|
+
sx={mergeSx(inputFieldStyle, !errorMsg ? commonInputStyle : {})}
|
|
118
127
|
value={value}
|
|
119
128
|
onChange={(e) => handleChange(e.target.value)}
|
|
120
129
|
error={Boolean(errorMsg)}
|
|
@@ -10,14 +10,42 @@ import { useMemoizedFn } from 'ahooks';
|
|
|
10
10
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
11
11
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
12
12
|
import LinkIcon from '@arcblock/icons/lib/Link';
|
|
13
|
-
import {
|
|
13
|
+
import { mergeSx } from '@arcblock/ux/lib/Util/style';
|
|
14
|
+
import { joinURL, parseURL } from 'ufo';
|
|
14
15
|
import { isValidUrl } from './utils';
|
|
15
16
|
import { translations } from '../../libs/locales';
|
|
17
|
+
import { commonInputStyle, inputFieldStyle } from '../editable-field';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 格式化链接显示
|
|
21
|
+
* 对于 http/https 协议只显示域名,其他协议显示完整链接
|
|
22
|
+
*/
|
|
23
|
+
function formatLinkDisplay(link: string): string {
|
|
24
|
+
try {
|
|
25
|
+
const url = parseURL(link);
|
|
26
|
+
// 如果是 http 或 https 协议,只显示域名
|
|
27
|
+
if (url.protocol === 'http:' || url.protocol === 'https:') {
|
|
28
|
+
return url.host || link;
|
|
29
|
+
}
|
|
30
|
+
// 其他协议显示完整链接
|
|
31
|
+
return link;
|
|
32
|
+
} catch {
|
|
33
|
+
return link;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
16
36
|
|
|
17
37
|
/**
|
|
18
38
|
* 链接预览输入框
|
|
19
39
|
*/
|
|
20
|
-
function LinkInput({
|
|
40
|
+
function LinkInput({
|
|
41
|
+
value,
|
|
42
|
+
onChange,
|
|
43
|
+
errorMsg,
|
|
44
|
+
}: {
|
|
45
|
+
value: string;
|
|
46
|
+
onChange: (value: string) => void;
|
|
47
|
+
errorMsg: string;
|
|
48
|
+
}) {
|
|
21
49
|
const handleUrlChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
22
50
|
const inputValue = event.target.value;
|
|
23
51
|
onChange(inputValue);
|
|
@@ -30,13 +58,9 @@ function LinkInput({ value, onChange, error }: { value: string; onChange: (value
|
|
|
30
58
|
value={value}
|
|
31
59
|
onChange={handleUrlChange}
|
|
32
60
|
fullWidth
|
|
33
|
-
error={
|
|
34
|
-
helperText={
|
|
35
|
-
sx={{
|
|
36
|
-
fieldset: {
|
|
37
|
-
borderColor: colors.dividerColor,
|
|
38
|
-
},
|
|
39
|
-
}}
|
|
61
|
+
error={!!errorMsg}
|
|
62
|
+
helperText={errorMsg}
|
|
63
|
+
sx={mergeSx(inputFieldStyle, !errorMsg ? commonInputStyle : {})}
|
|
40
64
|
/>
|
|
41
65
|
</FormControl>
|
|
42
66
|
);
|
|
@@ -93,7 +117,11 @@ function DynamicLinkForm({ links = [], onChange }: { links: string[]; onChange:
|
|
|
93
117
|
{links.map((link, index) => (
|
|
94
118
|
// eslint-disable-next-line react/no-array-index-key
|
|
95
119
|
<Box key={index} display="flex" alignItems="flex-start" mb={1}>
|
|
96
|
-
<LinkInput
|
|
120
|
+
<LinkInput
|
|
121
|
+
value={link}
|
|
122
|
+
onChange={(value) => handleInputChange(index, value)}
|
|
123
|
+
errorMsg={errors[index] ? t('profile.invalidURL') : ''}
|
|
124
|
+
/>
|
|
97
125
|
<IconButton sx={{ color: colors.foregroundsFgMuted }} onClick={() => handleRemoveLink(index)}>
|
|
98
126
|
<RemoveIcon />
|
|
99
127
|
</IconButton>
|
|
@@ -198,7 +226,7 @@ export function LinkPreviewInput({
|
|
|
198
226
|
<FaviconPreview link={link} />
|
|
199
227
|
<LinkDiv>
|
|
200
228
|
<Box component="a" href={link} style={{ textDecoration: 'none' }} target="_blank" rel="noopener noreferrer">
|
|
201
|
-
{link}
|
|
229
|
+
{formatLinkDisplay(link)}
|
|
202
230
|
</Box>
|
|
203
231
|
</LinkDiv>
|
|
204
232
|
</Box>
|
|
@@ -11,17 +11,17 @@ import { joinURL } from 'ufo';
|
|
|
11
11
|
import Button from '@arcblock/ux/lib/Button';
|
|
12
12
|
import PhoneInput, { PhoneValue, validatePhoneNumber } from '@arcblock/ux/lib/PhoneInput';
|
|
13
13
|
import cloneDeep from 'lodash/cloneDeep';
|
|
14
|
+
import { mergeSx } from '@arcblock/ux/lib/Util/style';
|
|
14
15
|
|
|
15
16
|
import { useCreation, useMemoizedFn, useReactive } from 'ahooks';
|
|
16
17
|
import { useMemo, useRef, useState, memo, forwardRef, useEffect, lazy } from 'react';
|
|
17
18
|
import { translate } from '@arcblock/ux/lib/Locale/util';
|
|
18
19
|
import isEmail from 'validator/lib/isEmail';
|
|
19
|
-
import { temp as colors } from '@arcblock/ux/lib/Colors';
|
|
20
20
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
21
21
|
import { useBrowser } from '@arcblock/react-hooks';
|
|
22
22
|
import { translations } from '../../libs/locales';
|
|
23
23
|
import type { User, UserMetadata, UserPhoneProps } from '../../../@types';
|
|
24
|
-
import EditableField from '../editable-field';
|
|
24
|
+
import EditableField, { commonInputStyle, inputFieldStyle } from '../editable-field';
|
|
25
25
|
import { LinkPreviewInput } from './link-preview-input';
|
|
26
26
|
import { currentTimezone, defaultButtonStyle, primaryButtonStyle } from './utils';
|
|
27
27
|
import { TimezoneSelect } from './timezone-select';
|
|
@@ -330,24 +330,7 @@ export default function UserMetadataComponent({
|
|
|
330
330
|
value={phoneValue}
|
|
331
331
|
error={!!validateMsg.phone}
|
|
332
332
|
helperText={validateMsg.phone}
|
|
333
|
-
sx={{
|
|
334
|
-
width: '100%',
|
|
335
|
-
'.MuiOutlinedInput-root': {
|
|
336
|
-
'&:hover': {
|
|
337
|
-
fieldset: {
|
|
338
|
-
borderColor: colors.dividerColor,
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
-
'&.Mui-focused': {
|
|
342
|
-
fieldset: {
|
|
343
|
-
borderColor: colors.dividerColor,
|
|
344
|
-
},
|
|
345
|
-
},
|
|
346
|
-
},
|
|
347
|
-
fieldset: {
|
|
348
|
-
borderColor: colors.dividerColor,
|
|
349
|
-
},
|
|
350
|
-
}}
|
|
333
|
+
sx={mergeSx(inputFieldStyle, !validateMsg.phone ? commonInputStyle : {})}
|
|
351
334
|
onChange={(value: any) => {
|
|
352
335
|
const isValid = validatePhoneNumber(value.phone);
|
|
353
336
|
if (!isValid) {
|
|
@@ -13,7 +13,7 @@ import type { UserMetadata } from '../../../@types';
|
|
|
13
13
|
import { DurationEnum, StatusEnum } from '../../../@types';
|
|
14
14
|
import StatusDialog from '../status-dialog';
|
|
15
15
|
import { translations } from '../../libs/locales';
|
|
16
|
-
import { getTimeRemaining, isWithinTimeRange } from './utils';
|
|
16
|
+
import { getTimeRemaining, isNotClear, isWithinTimeRange } from './utils';
|
|
17
17
|
import { StatusItem } from '../status-selector/menu-item';
|
|
18
18
|
|
|
19
19
|
const MeetingIcon = lazy(() => import('@arcblock/icons/lib/Meeting'));
|
|
@@ -85,6 +85,10 @@ export default function UserStatus({
|
|
|
85
85
|
|
|
86
86
|
const clear = useInterval(() => {
|
|
87
87
|
if (status?.value && status?.dateRange?.length === 2) {
|
|
88
|
+
const notClear = isNotClear(status);
|
|
89
|
+
if (notClear) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
88
92
|
const isWithin = isWithinTimeRange(status.dateRange as [Date, Date]);
|
|
89
93
|
if (!isWithin) {
|
|
90
94
|
pauseInterval();
|
|
@@ -131,7 +135,7 @@ export default function UserStatus({
|
|
|
131
135
|
children: durationData,
|
|
132
136
|
};
|
|
133
137
|
});
|
|
134
|
-
}, [t, getDurationData]);
|
|
138
|
+
}, [t, getDurationData, locale]);
|
|
135
139
|
|
|
136
140
|
const onOpenStatusSelector = (event: React.MouseEvent<HTMLDivElement>) => {
|
|
137
141
|
if (!isMyself) {
|
|
@@ -158,10 +162,17 @@ export default function UserStatus({
|
|
|
158
162
|
const currentStatus = statusData.find((item) => item.id === status?.value);
|
|
159
163
|
if (currentStatus) {
|
|
160
164
|
const localeOption = locale === 'zh' ? 'zh-cn' : 'en-us';
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
+
let range;
|
|
166
|
+
const notClear = isNotClear(status);
|
|
167
|
+
if (!notClear) {
|
|
168
|
+
range = status?.dateRange?.map((item) => {
|
|
169
|
+
return formatToDatetime(item, { locale: localeOption });
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
if (range && range.length > 0) {
|
|
173
|
+
return `${currentStatus.name}: ${range.join('~')}`;
|
|
174
|
+
}
|
|
175
|
+
return currentStatus.name;
|
|
165
176
|
}
|
|
166
177
|
return null;
|
|
167
178
|
}, [status, statusData, locale]);
|
|
@@ -68,6 +68,9 @@ export const getStatusDuration = (status: UserMetadata['status']) => {
|
|
|
68
68
|
case DurationEnum.ThisWeek:
|
|
69
69
|
dateRange = [current, current.endOf('week')];
|
|
70
70
|
break;
|
|
71
|
+
case DurationEnum.NoClear:
|
|
72
|
+
dateRange = [current, current];
|
|
73
|
+
break;
|
|
71
74
|
default:
|
|
72
75
|
break;
|
|
73
76
|
}
|
|
@@ -84,6 +87,19 @@ export const isWithinTimeRange = (dateRange: [Date, Date]) => {
|
|
|
84
87
|
return current.isAfter(dayjs(dateRange[0])) && current.isBefore(dayjs(dateRange[1]));
|
|
85
88
|
};
|
|
86
89
|
|
|
90
|
+
/**
|
|
91
|
+
* 判断状态持续时间是否为不可清除
|
|
92
|
+
* @param status
|
|
93
|
+
* @returns
|
|
94
|
+
*/
|
|
95
|
+
export const isNotClear = (status: UserMetadata['status']) => {
|
|
96
|
+
const { duration, dateRange } = status ?? {};
|
|
97
|
+
if (!duration || !dateRange) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return duration === DurationEnum.NoClear || dayjs(dateRange?.[0]).isSame(dayjs(dateRange?.[1]));
|
|
101
|
+
};
|
|
102
|
+
|
|
87
103
|
/**
|
|
88
104
|
* 获取当前时间距离结束时间还有多久
|
|
89
105
|
*/
|
|
@@ -107,6 +107,7 @@ export const translations = {
|
|
|
107
107
|
OffSick: '病假',
|
|
108
108
|
WorkingRemotely: '远程工作',
|
|
109
109
|
duration: {
|
|
110
|
+
NoClear: '不要清除',
|
|
110
111
|
ThirtyMinutes: '30分钟',
|
|
111
112
|
OneHour: '1小时',
|
|
112
113
|
FourHours: '4小时',
|
|
@@ -133,6 +134,7 @@ export const translations = {
|
|
|
133
134
|
quickSettings: '快捷设置',
|
|
134
135
|
selectEndTime: '选择结束时间',
|
|
135
136
|
pleaseSelectTime: '请选择时间',
|
|
137
|
+
invalidURL: '无效的 URL',
|
|
136
138
|
timezonePhase: {
|
|
137
139
|
dawn: '凌晨',
|
|
138
140
|
morning: '上午',
|
|
@@ -249,6 +251,7 @@ export const translations = {
|
|
|
249
251
|
OffSick: 'Off Sick',
|
|
250
252
|
WorkingRemotely: 'Working Remotely',
|
|
251
253
|
duration: {
|
|
254
|
+
NoClear: "Don't clear",
|
|
252
255
|
ThirtyMinutes: '30 minutes',
|
|
253
256
|
OneHour: '1 hour',
|
|
254
257
|
FourHours: '4 hours',
|
|
@@ -276,6 +279,7 @@ export const translations = {
|
|
|
276
279
|
pleaseSelectTime: 'Please select time',
|
|
277
280
|
cleanStatus: 'Clean status',
|
|
278
281
|
quickSettings: 'Quick Settings',
|
|
282
|
+
invalidURL: 'Invalid URL',
|
|
279
283
|
timezonePhase: {
|
|
280
284
|
dawn: 'AM',
|
|
281
285
|
morning: 'AM',
|