@blocklet/ui-react 3.1.37 → 3.1.39

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.
@@ -1,65 +1,65 @@
1
- import { jsx as t, jsxs as p } from "react/jsx-runtime";
2
- import { useState as L, useMemo as w } from "react";
3
- import { styled as C, Box as d, Typography as x, IconButton as I, Button as k, useTheme as S, FormControl as D, TextField as F } from "@mui/material";
4
- import { Remove as B, Add as T } from "@mui/icons-material";
5
- import { translate as U } from "@arcblock/ux/lib/Locale/util";
6
- import { useMemoizedFn as _ } from "ahooks";
7
- import { useLocaleContext as z } from "@arcblock/ux/lib/Locale/context";
8
- import E from "@arcblock/icons/lib/Link";
9
- import { mergeSx as M } from "@arcblock/ux/lib/Util/style";
10
- import { withoutProtocol as j } from "ufo";
11
- import { isValidUrl as g } from "./utils.js";
12
- import { translations as A } from "../../libs/locales.js";
13
- import { inputFieldStyle as P, commonInputStyle as R } from "../editable-field.js";
14
- const h = 10;
15
- function W(e) {
16
- return j(e);
1
+ import { jsx as t, jsxs as f } from "react/jsx-runtime";
2
+ import { useState as C, useMemo as k } from "react";
3
+ import { styled as I, Box as p, Typography as y, IconButton as D, Button as U, useTheme as S, FormControl as F, TextField as B } from "@mui/material";
4
+ import { Remove as T, Add as _ } from "@mui/icons-material";
5
+ import { translate as z } from "@arcblock/ux/lib/Locale/util";
6
+ import { useMemoizedFn as E } from "ahooks";
7
+ import { useLocaleContext as M } from "@arcblock/ux/lib/Locale/context";
8
+ import R from "@arcblock/icons/lib/Link";
9
+ import { mergeSx as j } from "@arcblock/ux/lib/Util/style";
10
+ import { withoutProtocol as A } from "ufo";
11
+ import { isValidUrl as h, isDuplicateUrl as P } from "./utils.js";
12
+ import { translations as W } from "../../libs/locales.js";
13
+ import { inputFieldStyle as N, commonInputStyle as V } from "../editable-field.js";
14
+ const x = 10;
15
+ function K(e) {
16
+ return A(e);
17
17
  }
18
- function N({
18
+ function O({
19
19
  value: e,
20
- onChange: i,
21
- errorMsg: l
20
+ onChange: a,
21
+ errorMsg: s
22
22
  }) {
23
- return /* @__PURE__ */ t(D, { fullWidth: !0, children: /* @__PURE__ */ t(
24
- F,
23
+ return /* @__PURE__ */ t(F, { fullWidth: !0, children: /* @__PURE__ */ t(
24
+ B,
25
25
  {
26
26
  variant: "outlined",
27
27
  value: e,
28
- onChange: (a) => {
29
- const c = a.target.value;
30
- i(c);
28
+ onChange: (i) => {
29
+ const u = i.target.value;
30
+ a(u);
31
31
  },
32
32
  fullWidth: !0,
33
- error: !!l,
34
- helperText: l,
35
- sx: M(P, l ? {} : R)
33
+ error: !!s,
34
+ helperText: s,
35
+ sx: j(N, s ? {} : V)
36
36
  }
37
37
  ) });
38
38
  }
39
- function V({ links: e = [], onChange: i }) {
40
- const { locale: l } = z(), n = _((o, r = {}) => U(A, o, l, "en", r)), [a, c] = L([!1]), f = w(() => {
41
- const o = e[e.length - 1];
42
- return !g(o) || a.length > 0 && a[a.length - 1];
43
- }, [a, e]), y = () => {
44
- (e.length < h || !f) && i([...e, ""]);
45
- }, b = (o) => {
46
- const r = e.filter((m, u) => u !== o), s = a.filter((m, u) => u !== o);
47
- c(s), i(r);
48
- }, v = (o, r) => {
49
- const s = [...e];
50
- s[o] = r;
51
- const m = [...a];
52
- m[o] = !!(r && !g(r)), c(m), i(s);
39
+ function X({ links: e = [], onChange: a }) {
40
+ const { locale: s } = M(), n = E((r, o = {}) => z(W, r, s, "en", o)), [i, u] = C([""]), g = k(() => {
41
+ const r = e[e.length - 1];
42
+ return !h(r) || i.length > 0 && i[i.length - 1];
43
+ }, [i, e]), L = () => {
44
+ (e.length < x || !g) && a([...e, ""]);
45
+ }, v = (r) => {
46
+ const o = e.filter((c, d) => d !== r), l = i.filter((c, d) => d !== r);
47
+ u(l), a(o);
48
+ }, b = (r, o) => {
49
+ const l = [...e];
50
+ l[r] = o;
51
+ const c = [...i], d = !!(o && !h(o));
52
+ c[r] = !1, d ? c[r] = d : l.filter((m, w) => m && h(m) && w !== r).some((m) => P(m, o)) && (c[r] = "duplicate"), u(c), a(l);
53
53
  };
54
- return /* @__PURE__ */ p(
55
- d,
54
+ return /* @__PURE__ */ f(
55
+ p,
56
56
  {
57
57
  sx: {
58
58
  width: "100%"
59
59
  },
60
60
  children: [
61
61
  /* @__PURE__ */ t(
62
- d,
62
+ p,
63
63
  {
64
64
  sx: {
65
65
  display: "flex",
@@ -67,38 +67,34 @@ function V({ links: e = [], onChange: i }) {
67
67
  alignItems: "center",
68
68
  gap: 1
69
69
  },
70
- children: /* @__PURE__ */ t(x, { variant: "subtitle1", gutterBottom: !0, sx: { mb: 0, fontSize: "12px", color: "text.primary" }, children: n("profile.socialMedia") })
70
+ children: /* @__PURE__ */ t(y, { variant: "subtitle1", gutterBottom: !0, sx: { mb: 0, fontSize: "12px", color: "text.primary" }, children: n("profile.socialMedia") })
71
71
  }
72
72
  ),
73
- e.map((o, r) => /* @__PURE__ */ p(
74
- d,
75
- {
76
- sx: {
77
- display: "flex",
78
- alignItems: "flex-start",
79
- mb: 1
73
+ e.map((r, o) => {
74
+ let l = "";
75
+ return i[o] === "duplicate" ? l = n("profile.duplicateURL") : i[o] === !0 ? l = n("profile.invalidURL") : l = "", /* @__PURE__ */ f(
76
+ p,
77
+ {
78
+ sx: {
79
+ display: "flex",
80
+ alignItems: "flex-start",
81
+ mb: 1
82
+ },
83
+ children: [
84
+ /* @__PURE__ */ t(O, { value: r, onChange: (c) => b(o, c), errorMsg: l }),
85
+ /* @__PURE__ */ t(D, { onClick: () => v(o), children: /* @__PURE__ */ t(T, { sx: { color: "text.secondary" } }) })
86
+ ]
80
87
  },
81
- children: [
82
- /* @__PURE__ */ t(
83
- N,
84
- {
85
- value: o,
86
- onChange: (s) => v(r, s),
87
- errorMsg: a[r] ? n("profile.invalidURL") : ""
88
- }
89
- ),
90
- /* @__PURE__ */ t(I, { onClick: () => b(r), children: /* @__PURE__ */ t(B, { sx: { color: "text.secondary" } }) })
91
- ]
92
- },
93
- r
94
- )),
95
- e.length < h ? /* @__PURE__ */ p(
96
- k,
88
+ o
89
+ );
90
+ }),
91
+ e.length < x ? /* @__PURE__ */ f(
92
+ U,
97
93
  {
98
94
  fullWidth: !0,
99
95
  variant: "outlined",
100
- disabled: f,
101
- onClick: y,
96
+ disabled: !!g,
97
+ onClick: L,
102
98
  size: "small",
103
99
  sx: {
104
100
  height: "40px",
@@ -114,27 +110,27 @@ function V({ links: e = [], onChange: i }) {
114
110
  }
115
111
  },
116
112
  children: [
117
- /* @__PURE__ */ t(T, {}),
113
+ /* @__PURE__ */ t(_, {}),
118
114
  " ",
119
115
  /* @__PURE__ */ t("span", { children: n("profile.addLink") })
120
116
  ]
121
117
  }
122
- ) : /* @__PURE__ */ t(x, { variant: "subtitle1", gutterBottom: !0, sx: { mb: 0, fontSize: "12px", color: "text.secondary" }, children: n("profile.maxLinkCount", { count: h }) })
118
+ ) : /* @__PURE__ */ t(y, { variant: "subtitle1", gutterBottom: !0, sx: { mb: 0, fontSize: "12px", color: "text.secondary" }, children: n("profile.maxLinkCount", { count: x }) })
123
119
  ]
124
120
  }
125
121
  );
126
122
  }
127
- function K({ link: e }) {
128
- const l = S().palette.mode === "dark";
129
- return e ? /* @__PURE__ */ t(E, { width: 20, height: 20, style: { filter: l ? "brightness(0) saturate(100%) invert(1)" : "none" } }) : null;
123
+ function $({ link: e }) {
124
+ const s = S().palette.mode === "dark";
125
+ return e ? /* @__PURE__ */ t(R, { width: 20, height: 20, style: { filter: s ? "brightness(0) saturate(100%) invert(1)" : "none" } }) : null;
130
126
  }
131
- function ne({
127
+ function ce({
132
128
  editable: e = !1,
133
- links: i = [],
134
- onChange: l
129
+ links: a = [],
130
+ onChange: s
135
131
  }) {
136
- return e ? /* @__PURE__ */ t(V, { links: i, onChange: l }) : /* @__PURE__ */ t(
137
- d,
132
+ return e ? /* @__PURE__ */ t(X, { links: a, onChange: s }) : /* @__PURE__ */ t(
133
+ p,
138
134
  {
139
135
  sx: {
140
136
  width: "100%",
@@ -142,8 +138,8 @@ function ne({
142
138
  flexDirection: "column",
143
139
  gap: 2
144
140
  },
145
- children: i.map((n) => /* @__PURE__ */ p(
146
- d,
141
+ children: a.map((n) => /* @__PURE__ */ f(
142
+ p,
147
143
  {
148
144
  sx: {
149
145
  display: "flex",
@@ -153,8 +149,8 @@ function ne({
153
149
  width: "100%"
154
150
  },
155
151
  children: [
156
- /* @__PURE__ */ t(K, { link: n }),
157
- /* @__PURE__ */ t(O, { children: /* @__PURE__ */ t(d, { component: "a", href: n, style: { textDecoration: "none" }, target: "_blank", rel: "noopener noreferrer", children: W(n) }) })
152
+ /* @__PURE__ */ t($, { link: n }),
153
+ /* @__PURE__ */ t(q, { children: /* @__PURE__ */ t(p, { component: "a", href: n, style: { textDecoration: "none" }, target: "_blank", rel: "noopener noreferrer", children: K(n) }) })
158
154
  ]
159
155
  },
160
156
  n
@@ -162,7 +158,7 @@ function ne({
162
158
  }
163
159
  );
164
160
  }
165
- const O = C("span")`
161
+ const q = I("span")`
166
162
  flex: 1;
167
163
  white-space: nowrap;
168
164
  overflow: hidden;
@@ -181,5 +177,5 @@ const O = C("span")`
181
177
  }
182
178
  `;
183
179
  export {
184
- ne as LinkPreviewInput
180
+ ce as LinkPreviewInput
185
181
  };
@@ -1,84 +1,85 @@
1
- import { jsxs as c, jsx as r, Fragment as B } from "react/jsx-runtime";
2
- import { Box as l, Typography as S, Divider as N, IconButton as T, Collapse as F } from "@mui/material";
3
- import K from "@arcblock/ux/lib/Avatar";
4
- import W from "@arcblock/ux/lib/DID";
5
- import { useMemoizedFn as O } from "ahooks";
6
- import { translate as Q } from "@arcblock/ux/lib/Locale/util";
7
- import { useLocaleContext as V } from "@arcblock/ux/lib/Locale/context";
8
- import Y from "lodash/noop";
9
- import { useState as U, useEffect as y } from "react";
10
- import C from "@arcblock/ux/lib/Toast";
11
- import { parseURL as $, joinURL as q } from "ufo";
12
- import { KeyboardArrowUp as G, KeyboardArrowDown as H } from "@mui/icons-material";
13
- import { translations as J } from "../../libs/locales.js";
14
- import { formatAxiosError as z } from "../../libs/utils.js";
15
- import { currentTimezone as X, getStatusDuration as Z, isValidUrl as _ } from "./utils.js";
16
- import P from "./switch-role.js";
17
- import M from "./metadata.js";
18
- import tt from "./user-status.js";
19
- import rt from "./user-info.js";
20
- import { client as j } from "../../../libs/client.js";
21
- import ot from "./social-actions/index.js";
22
- function zt({
1
+ import { jsxs as l, jsx as r, Fragment as T } from "react/jsx-runtime";
2
+ import { Box as c, Typography as y, Divider as F, IconButton as K, Collapse as W } from "@mui/material";
3
+ import O from "@arcblock/ux/lib/Avatar";
4
+ import Q from "@arcblock/ux/lib/DID";
5
+ import { useMemoizedFn as V } from "ahooks";
6
+ import { translate as Y } from "@arcblock/ux/lib/Locale/util";
7
+ import { useLocaleContext as $ } from "@arcblock/ux/lib/Locale/context";
8
+ import q from "lodash/noop";
9
+ import { useState as C, useEffect as z } from "react";
10
+ import j from "@arcblock/ux/lib/Toast";
11
+ import { parseURL as G, joinURL as H } from "ufo";
12
+ import { KeyboardArrowUp as J, KeyboardArrowDown as X } from "@mui/icons-material";
13
+ import { translations as Z } from "../../libs/locales.js";
14
+ import { formatAxiosError as I } from "../../libs/utils.js";
15
+ import { currentTimezone as _, getStatusDuration as P, isValidUrl as M, isDuplicateUrl as tt } from "./utils.js";
16
+ import rt from "./switch-role.js";
17
+ import et from "./metadata.js";
18
+ import ot from "./user-status.js";
19
+ import at from "./user-info.js";
20
+ import { client as R } from "../../../libs/client.js";
21
+ import nt from "./social-actions/index.js";
22
+ function Rt({
23
23
  user: t,
24
- isMyself: e = !0,
24
+ isMyself: o = !0,
25
25
  showFullDid: f = !0,
26
- switchPassport: I,
27
- switchProfile: R,
26
+ switchPassport: A,
27
+ switchProfile: L,
28
28
  isMobile: a = !1,
29
29
  onlyProfile: s = !1,
30
30
  refreshProfile: u,
31
- isShowSocialActions: A = !1,
31
+ isShowSocialActions: k = !1,
32
32
  ...m
33
33
  }) {
34
- const { locale: x } = V(), [h, g] = U(void 0), w = O((n, o = {}) => Q(J, n, x, "en", o)), [d, v] = U(!a || s);
35
- y(() => {
34
+ const { locale: h } = $(), [x, g] = C(void 0), v = V((i, e = {}) => Y(Z, i, h, "en", e)), [d, w] = C(!a || s);
35
+ z(() => {
36
36
  g(t?.metadata?.status);
37
- }, [t]), y(() => {
38
- v(!a || s);
37
+ }, [t]), z(() => {
38
+ w(!a || s);
39
39
  }, [a, s]);
40
- const b = async (n) => {
41
- if (e)
40
+ const D = async (i) => {
41
+ if (o)
42
42
  try {
43
- if (n) {
44
- const o = Z(n);
45
- n.dateRange = o.length > 0 ? o : h?.dateRange ?? [];
43
+ if (i) {
44
+ const e = P(i);
45
+ i.dateRange = e.length > 0 ? e : x?.dateRange ?? [];
46
46
  }
47
- g(n), await j.user.saveProfile({
47
+ g(i), await R.user.saveProfile({
48
48
  // @ts-ignore
49
49
  metadata: {
50
50
  ...t?.metadata ?? { joinedAt: t?.createdAt, email: t?.email, phone: t?.phone },
51
- status: n || {}
51
+ status: i || {}
52
52
  }
53
53
  }), u();
54
- } catch (o) {
55
- console.error(o), C.error(z(o));
54
+ } catch (e) {
55
+ console.error(e), j.error(I(e));
56
56
  }
57
- }, D = () => {
58
- v(!d);
57
+ }, b = () => {
58
+ w(!d);
59
59
  };
60
60
  if (!t)
61
61
  return null;
62
- const L = async (n) => {
63
- if (!e)
62
+ const E = async (i) => {
63
+ if (!o)
64
64
  return;
65
- const { metadata: o, address: k } = n;
65
+ const { metadata: e, address: B } = i;
66
66
  try {
67
- const p = o?.links?.map((i) => {
68
- if (!i.url || !_(i.url)) return null;
67
+ const p = e?.links?.map((n, S) => {
68
+ if (!n.url || !M(n.url) || S > 0 && e.links?.slice(0, S)?.some((N) => tt(N.url, n.url)))
69
+ return null;
69
70
  try {
70
- return $(i.url).protocol || (i.url = q("https://", i.url)), i;
71
+ return G(n.url).protocol || (n.url = H("https://", n.url)), n;
71
72
  } catch {
72
- return console.error("Invalid URL:", i.url), null;
73
+ return console.error("Invalid URL:", n.url), null;
73
74
  }
74
- }).filter((i) => !!i) || [];
75
- o.links = p, await j.user.saveProfile({ metadata: o, address: k }), u();
75
+ }).filter((n) => !!n) || [];
76
+ e.links = p, await R.user.saveProfile({ metadata: e, address: B }), u();
76
77
  } catch (p) {
77
- console.error(p), C.error(z(p));
78
+ console.error(p), j.error(I(p));
78
79
  }
79
80
  };
80
- return /* @__PURE__ */ c(
81
- l,
81
+ return /* @__PURE__ */ l(
82
+ c,
82
83
  {
83
84
  ...m,
84
85
  sx: {
@@ -86,8 +87,8 @@ function zt({
86
87
  ...m.sx ?? {}
87
88
  },
88
89
  children: [
89
- /* @__PURE__ */ c(
90
- l,
90
+ /* @__PURE__ */ l(
91
+ c,
91
92
  {
92
93
  className: "user-info",
93
94
  sx: {
@@ -96,8 +97,8 @@ function zt({
96
97
  gap: 2
97
98
  },
98
99
  children: [
99
- /* @__PURE__ */ c(
100
- l,
100
+ /* @__PURE__ */ l(
101
+ c,
101
102
  {
102
103
  className: "user-avatar",
103
104
  sx: {
@@ -108,7 +109,7 @@ function zt({
108
109
  },
109
110
  children: [
110
111
  /* @__PURE__ */ r(
111
- K,
112
+ O,
112
113
  {
113
114
  src: t?.avatar,
114
115
  did: t?.did,
@@ -121,10 +122,10 @@ function zt({
121
122
  position: "relative",
122
123
  overflow: "hidden",
123
124
  flexShrink: 0,
124
- ...e ? {
125
+ ...o ? {
125
126
  cursor: "pointer",
126
127
  "&::after": {
127
- content: `"${w("switchProfile")}"`,
128
+ content: `"${v("switchProfile")}"`,
128
129
  color: "white",
129
130
  position: "absolute",
130
131
  fontSize: "12px",
@@ -139,33 +140,33 @@ function zt({
139
140
  }
140
141
  } : {}
141
142
  },
142
- onClick: e ? R : Y
143
+ onClick: o ? L : q
143
144
  }
144
145
  ),
145
146
  /* @__PURE__ */ r(
146
- tt,
147
+ ot,
147
148
  {
148
149
  isMobile: a,
149
150
  size: m.size || (a ? 64 : 100),
150
- isMyself: e,
151
- timezone: t?.metadata?.timezone || X,
152
- status: h,
153
- onChange: b
151
+ isMyself: o,
152
+ timezone: t?.metadata?.timezone || _,
153
+ status: x,
154
+ onChange: D
154
155
  }
155
156
  )
156
157
  ]
157
158
  }
158
159
  ),
159
- /* @__PURE__ */ c(
160
- l,
160
+ /* @__PURE__ */ l(
161
+ c,
161
162
  {
162
163
  sx: {
163
164
  flex: 1,
164
165
  overflow: "hidden"
165
166
  },
166
167
  children: [
167
- /* @__PURE__ */ c(
168
- S,
168
+ /* @__PURE__ */ l(
169
+ y,
169
170
  {
170
171
  variant: "h6",
171
172
  component: "div",
@@ -190,23 +191,23 @@ function zt({
190
191
  children: t?.fullName
191
192
  }
192
193
  ),
193
- e ? /* @__PURE__ */ r(P, { user: t, switchPassport: I }) : null
194
+ o ? /* @__PURE__ */ r(rt, { user: t, switchPassport: A }) : null
194
195
  ]
195
196
  }
196
197
  ),
197
- /* @__PURE__ */ r(W, { did: t.did, showQrcode: !0, copyable: !0, compact: !f, responsive: !f, locale: x })
198
+ /* @__PURE__ */ r(Q, { did: t.did, showQrcode: !0, copyable: !0, compact: !f, responsive: !f, locale: h })
198
199
  ]
199
200
  }
200
201
  )
201
202
  ]
202
203
  }
203
204
  ),
204
- !e && A ? /* @__PURE__ */ r(l, { sx: { mt: 2 }, children: /* @__PURE__ */ r(ot, { user: t }) }) : null,
205
- /* @__PURE__ */ r(M, { isMobile: a, isMyself: e, user: t, onSave: L }),
206
- e ? /* @__PURE__ */ c(B, { children: [
207
- /* @__PURE__ */ r(N, { sx: { my: a ? 1 : 3 } }),
205
+ !o && k ? /* @__PURE__ */ r(c, { sx: { mt: 2 }, children: /* @__PURE__ */ r(nt, { user: t }) }) : null,
206
+ /* @__PURE__ */ r(et, { isMobile: a, isMyself: o, user: t, onSave: E }),
207
+ o ? /* @__PURE__ */ l(T, { children: [
208
+ /* @__PURE__ */ r(F, { sx: { my: a ? 1 : 3 } }),
208
209
  a && !s ? /* @__PURE__ */ r(
209
- l,
210
+ c,
210
211
  {
211
212
  sx: {
212
213
  display: "flex",
@@ -214,10 +215,10 @@ function zt({
214
215
  mb: 0
215
216
  },
216
217
  children: /* @__PURE__ */ r(
217
- T,
218
+ K,
218
219
  {
219
220
  size: "small",
220
- onClick: D,
221
+ onClick: b,
221
222
  sx: {
222
223
  backgroundColor: "grey.50",
223
224
  "&:hover": {
@@ -225,14 +226,14 @@ function zt({
225
226
  opacity: 0.8
226
227
  }
227
228
  },
228
- children: d ? /* @__PURE__ */ r(G, {}) : /* @__PURE__ */ r(H, {})
229
+ children: d ? /* @__PURE__ */ r(J, {}) : /* @__PURE__ */ r(X, {})
229
230
  }
230
231
  )
231
232
  }
232
233
  ) : null,
233
- /* @__PURE__ */ c(F, { in: d, timeout: "auto", children: [
234
+ /* @__PURE__ */ l(W, { in: d, timeout: "auto", children: [
234
235
  /* @__PURE__ */ r(
235
- S,
236
+ y,
236
237
  {
237
238
  component: "p",
238
239
  sx: {
@@ -240,10 +241,10 @@ function zt({
240
241
  fontSize: "14px",
241
242
  mb: 2
242
243
  },
243
- children: w("profile.justForYou")
244
+ children: v("profile.justForYou")
244
245
  }
245
246
  ),
246
- /* @__PURE__ */ r(rt, { user: t, isMySelf: e })
247
+ /* @__PURE__ */ r(at, { user: t, isMySelf: o })
247
248
  ] })
248
249
  ] }) : null
249
250
  ]
@@ -251,5 +252,5 @@ function zt({
251
252
  );
252
253
  }
253
254
  export {
254
- zt as default
255
+ Rt as default
255
256
  };
@@ -5,6 +5,7 @@ export declare const getTimezones: () => {
5
5
  value: string;
6
6
  }[];
7
7
  export declare const isValidUrl: (url: string) => any;
8
+ export declare const isDuplicateUrl: (url1: string, url2: string) => boolean;
8
9
  /**
9
10
  * 根据 duration 类型,计算出date range
10
11
  * @param status
@@ -1,12 +1,12 @@
1
- import g from "is-url";
2
- import { withHttps as A } from "ufo";
1
+ import h from "is-url";
2
+ import { withHttps as c } from "ufo";
3
3
  import n from "dayjs";
4
- import h from "dayjs/plugin/timezone";
4
+ import C from "dayjs/plugin/timezone";
5
5
  import k from "dayjs/plugin/utc";
6
- import { DurationEnum as i } from "../../../@types/index.js";
6
+ import { DurationEnum as s } from "../../../@types/index.js";
7
7
  n.extend(k);
8
- n.extend(h);
9
- const u = 3600, l = 1800, m = 600, d = 300, f = 60, p = 1, v = n.tz.guess(), b = [
8
+ n.extend(C);
9
+ const l = 3600, m = 1800, d = 600, f = 300, p = 60, b = 1, _ = n.tz.guess(), y = [
10
10
  "America/New_York",
11
11
  "America/Chicago",
12
12
  "America/Denver",
@@ -28,7 +28,7 @@ const u = 3600, l = 1800, m = 600, d = 300, f = 60, p = 1, v = n.tz.guess(), b =
28
28
  "America/Mexico_City",
29
29
  "Africa/Cairo",
30
30
  "UTC"
31
- ], C = () => {
31
+ ], O = () => {
32
32
  if (typeof Intl < "u" && Intl.supportedValuesOf)
33
33
  try {
34
34
  return Intl.supportedValuesOf("timeZone");
@@ -38,58 +38,63 @@ const u = 3600, l = 1800, m = 600, d = 300, f = 60, p = 1, v = n.tz.guess(), b =
38
38
  if (typeof Intl < "u" && Intl.DateTimeFormat)
39
39
  try {
40
40
  if (new Intl.DateTimeFormat("en", { timeZone: "UTC" }).resolvedOptions().timeZone)
41
- return b;
41
+ return y;
42
42
  } catch {
43
43
  console.warn("Intl.DateTimeFormat timezone support limited");
44
44
  }
45
- return b;
46
- }, w = () => C().map((e) => {
45
+ return y;
46
+ }, v = () => O().map((e) => {
47
47
  try {
48
- const o = n.tz(n(), e).utcOffset() / 60, a = Math.floor(o), s = o % 1 * 60;
49
- return { label: `GMT${a >= 0 ? "+" : ""}${a}:${s === 30 ? "30" : "00"}`, value: e };
48
+ const t = n.tz(n(), e).utcOffset() / 60, a = Math.floor(t), i = t % 1 * 60;
49
+ return { label: `GMT${a >= 0 ? "+" : ""}${a}:${i === 30 ? "30" : "00"}`, value: e };
50
50
  } catch {
51
51
  return console.warn(`Timezone ${e} not supported, skipping`), null;
52
52
  }
53
- }).filter((e) => e !== null).sort((e, o) => {
54
- const [a, s] = e.label.replace("GMT", "").split(":").map(Number), [c, y] = o.label.replace("GMT", "").split(":").map(Number), T = a * 60 + s;
55
- return c * 60 + y - T;
53
+ }).filter((e) => e !== null).sort((e, t) => {
54
+ const [a, i] = e.label.replace("GMT", "").split(":").map(Number), [u, g] = t.label.replace("GMT", "").split(":").map(Number), A = a * 60 + i;
55
+ return u * 60 + g - A;
56
56
  }).map((e) => ({
57
57
  label: `(${e.label}) ${e.value}`,
58
58
  value: e.value
59
- })), z = (t) => g(A(t)), U = (t) => {
60
- let r = t?.dateRange?.map((o) => n(o)) ?? [];
59
+ })), T = (o) => h(c(o)), z = (o, r) => {
60
+ if (!o || !r || !T(o) || !T(r))
61
+ return !1;
62
+ const e = c(o.trim()), t = c(r.trim());
63
+ return e.toLowerCase() === t.toLowerCase();
64
+ }, D = (o) => {
65
+ let r = o?.dateRange?.map((t) => n(t)) ?? [];
61
66
  const e = n();
62
- switch (t?.duration) {
63
- case i.ThirtyMinutes:
67
+ switch (o?.duration) {
68
+ case s.ThirtyMinutes:
64
69
  r = [e, e.add(30, "minutes")];
65
70
  break;
66
- case i.OneHour:
71
+ case s.OneHour:
67
72
  r = [e, e.add(1, "hour")];
68
73
  break;
69
- case i.FourHours:
74
+ case s.FourHours:
70
75
  r = [e, e.add(4, "hours")];
71
76
  break;
72
- case i.Today:
77
+ case s.Today:
73
78
  r = [e, e.endOf("day")];
74
79
  break;
75
- case i.ThisWeek:
80
+ case s.ThisWeek:
76
81
  r = [e, e.endOf("week")];
77
82
  break;
78
- case i.NoClear:
83
+ case s.NoClear:
79
84
  r = [e, e];
80
85
  break;
81
86
  }
82
- return r.map((o) => o.toDate());
83
- }, B = (t) => {
87
+ return r.map((t) => t.toDate());
88
+ }, B = (o) => {
84
89
  const r = n();
85
- return r.isAfter(n(t[0])) && r.isBefore(n(t[1]));
86
- }, D = (t) => {
87
- const { duration: r, dateRange: e } = t ?? {};
88
- return !r || !e ? !1 : r === i.NoClear || n(e?.[0]).isSame(n(e?.[1]));
89
- }, R = (t) => {
90
- const r = n(), o = n(t).diff(r, "seconds"), a = (s) => s * 1e3;
91
- return o >= u ? a(u) : o >= l ? a(l) : o >= m ? a(m) : o >= d ? a(d) : o >= f ? a(f) : o >= p ? a(p) : 0;
92
- }, x = {
90
+ return r.isAfter(n(o[0])) && r.isBefore(n(o[1]));
91
+ }, R = (o) => {
92
+ const { duration: r, dateRange: e } = o ?? {};
93
+ return !r || !e ? !1 : r === s.NoClear || n(e?.[0]).isSame(n(e?.[1]));
94
+ }, x = (o) => {
95
+ const r = n(), t = n(o).diff(r, "seconds"), a = (i) => i * 1e3;
96
+ return t >= l ? a(l) : t >= m ? a(m) : t >= d ? a(d) : t >= f ? a(f) : t >= p ? a(p) : t >= b ? a(b) : 0;
97
+ }, $ = {
93
98
  color: "text.primary",
94
99
  borderColor: "grey.100",
95
100
  backgroundColor: "background.default",
@@ -99,7 +104,7 @@ const u = 3600, l = 1800, m = 600, d = 300, f = 60, p = 1, v = n.tz.guess(), b =
99
104
  },
100
105
  py: 0.5,
101
106
  borderRadius: 1
102
- }, $ = {
107
+ }, H = {
103
108
  color: "primary.contrastText",
104
109
  borderColor: "primary.main",
105
110
  backgroundColor: "primary.main",
@@ -111,13 +116,14 @@ const u = 3600, l = 1800, m = 600, d = 300, f = 60, p = 1, v = n.tz.guess(), b =
111
116
  borderRadius: 1
112
117
  };
113
118
  export {
114
- v as currentTimezone,
115
- x as defaultButtonStyle,
116
- U as getStatusDuration,
117
- R as getTimeRemaining,
118
- w as getTimezones,
119
- D as isNotClear,
120
- z as isValidUrl,
119
+ _ as currentTimezone,
120
+ $ as defaultButtonStyle,
121
+ D as getStatusDuration,
122
+ x as getTimeRemaining,
123
+ v as getTimezones,
124
+ z as isDuplicateUrl,
125
+ R as isNotClear,
126
+ T as isValidUrl,
121
127
  B as isWithinTimeRange,
122
- $ as primaryButtonStyle
128
+ H as primaryButtonStyle
123
129
  };
@@ -142,6 +142,7 @@ export declare const translations: {
142
142
  selectEndTime: string;
143
143
  pleaseSelectTime: string;
144
144
  invalidURL: string;
145
+ duplicateURL: string;
145
146
  socialMedia: string;
146
147
  timezonePhase: {
147
148
  dawn: string;
@@ -325,6 +326,7 @@ export declare const translations: {
325
326
  cleanStatus: string;
326
327
  quickSettings: string;
327
328
  invalidURL: string;
329
+ duplicateURL: string;
328
330
  socialMedia: string;
329
331
  timezonePhase: {
330
332
  dawn: string;
@@ -142,6 +142,7 @@ const e = {
142
142
  selectEndTime: "选择结束时间",
143
143
  pleaseSelectTime: "请选择时间",
144
144
  invalidURL: "无效的 URL",
145
+ duplicateURL: "URL 已存在",
145
146
  socialMedia: "社交链接",
146
147
  timezonePhase: {
147
148
  dawn: "凌晨",
@@ -325,6 +326,7 @@ const e = {
325
326
  cleanStatus: "Clean status",
326
327
  quickSettings: "Quick Settings",
327
328
  invalidURL: "Invalid URL",
329
+ duplicateURL: "URL already exists",
328
330
  socialMedia: "Social Media",
329
331
  timezonePhase: {
330
332
  dawn: "AM",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/ui-react",
3
- "version": "3.1.37",
3
+ "version": "3.1.39",
4
4
  "description": "Some useful front-end web components that can be used in Blocklets.",
5
5
  "keywords": [
6
6
  "react",
@@ -35,9 +35,9 @@
35
35
  "dependencies": {
36
36
  "@abtnode/constant": "^1.16.49-beta-20250823-082650-626c1473",
37
37
  "@abtnode/util": "^1.16.49-beta-20250823-082650-626c1473",
38
- "@arcblock/bridge": "3.1.37",
39
- "@arcblock/icons": "3.1.37",
40
- "@arcblock/react-hooks": "3.1.37",
38
+ "@arcblock/bridge": "3.1.39",
39
+ "@arcblock/icons": "3.1.39",
40
+ "@arcblock/react-hooks": "3.1.39",
41
41
  "@arcblock/ws": "^1.21.3",
42
42
  "@blocklet/constant": "^1.16.49-beta-20250823-082650-626c1473",
43
43
  "@blocklet/did-space-react": "^1.1.16",
@@ -83,7 +83,7 @@
83
83
  "access": "public"
84
84
  },
85
85
  "devDependencies": {
86
- "@arcblock/did-connect-react": "3.1.37",
86
+ "@arcblock/did-connect-react": "3.1.39",
87
87
  "@types/dompurify": "^3.2.0",
88
88
  "@types/ua-parser-js": "^0.7.39",
89
89
  "@types/validator": "^13.15.2",
@@ -91,5 +91,5 @@
91
91
  "jest": "^29.7.0",
92
92
  "unbuild": "^2.0.0"
93
93
  },
94
- "gitHead": "2b0893787889c20893e9ff1f8c7a016f386882b4"
94
+ "gitHead": "39e8ff533f71280af4170866de8055a188c820ba"
95
95
  }
@@ -7,7 +7,7 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
7
7
  import LinkIcon from '@arcblock/icons/lib/Link';
8
8
  import { mergeSx } from '@arcblock/ux/lib/Util/style';
9
9
  import { withoutProtocol } from 'ufo';
10
- import { isValidUrl } from './utils';
10
+ import { isDuplicateUrl, isValidUrl } from './utils';
11
11
  import { translations } from '../../libs/locales';
12
12
  import { commonInputStyle, inputFieldStyle } from '../editable-field';
13
13
 
@@ -61,7 +61,7 @@ function DynamicLinkForm({ links = [], onChange }: { links?: string[]; onChange:
61
61
  const t = useMemoizedFn((key, data = {}) => {
62
62
  return translate(translations, key, locale, 'en', data);
63
63
  });
64
- const [errors, setErrors] = useState([false]); // 记录 URL 输入错误状态
64
+ const [errors, setErrors] = useState<(boolean | string)[]>(['']); // 记录 URL 输入错误状态
65
65
 
66
66
  const isLastError = useMemo(() => {
67
67
  const lastLink = links[links.length - 1];
@@ -89,7 +89,18 @@ function DynamicLinkForm({ links = [], onChange }: { links?: string[]; onChange:
89
89
 
90
90
  // 校验 URL 是否有效
91
91
  const updatedErrors = [...errors];
92
- updatedErrors[index] = !!(value && !isValidUrl(value)); // 如果有输入但格式错误,标记错误
92
+ const isInvalidUrl = !!(value && !isValidUrl(value));
93
+ updatedErrors[index] = false;
94
+ // 如果 URL 有效,则校验是否重复
95
+ if (!isInvalidUrl) {
96
+ const validLinks = updatedLinks.filter((l, i) => l && isValidUrl(l) && i !== index);
97
+ const isDuplicate = validLinks.some((v) => isDuplicateUrl(v, value));
98
+ if (isDuplicate) {
99
+ updatedErrors[index] = 'duplicate';
100
+ }
101
+ } else {
102
+ updatedErrors[index] = isInvalidUrl; // 如果有输入但格式错误,标记错误
103
+ }
93
104
  setErrors(updatedErrors);
94
105
  onChange(updatedLinks);
95
106
  };
@@ -110,30 +121,36 @@ function DynamicLinkForm({ links = [], onChange }: { links?: string[]; onChange:
110
121
  {t('profile.socialMedia')}
111
122
  </Typography>
112
123
  </Box>
113
- {links.map((link, index) => (
114
- <Box
115
- // eslint-disable-next-line react/no-array-index-key
116
- key={index}
117
- sx={{
118
- display: 'flex',
119
- alignItems: 'flex-start',
120
- mb: 1,
121
- }}>
122
- <LinkInput
123
- value={link}
124
- onChange={(value) => handleInputChange(index, value)}
125
- errorMsg={errors[index] ? t('profile.invalidURL') : ''}
126
- />
127
- <IconButton onClick={() => handleRemoveLink(index)}>
128
- <RemoveIcon sx={{ color: 'text.secondary' }} />
129
- </IconButton>
130
- </Box>
131
- ))}
124
+ {links.map((link, index) => {
125
+ let errorMsg = '';
126
+ if (errors[index] === 'duplicate') {
127
+ errorMsg = t('profile.duplicateURL');
128
+ } else if (errors[index] === true) {
129
+ errorMsg = t('profile.invalidURL');
130
+ } else {
131
+ errorMsg = '';
132
+ }
133
+ return (
134
+ <Box
135
+ // eslint-disable-next-line react/no-array-index-key
136
+ key={index}
137
+ sx={{
138
+ display: 'flex',
139
+ alignItems: 'flex-start',
140
+ mb: 1,
141
+ }}>
142
+ <LinkInput value={link} onChange={(value) => handleInputChange(index, value)} errorMsg={errorMsg} />
143
+ <IconButton onClick={() => handleRemoveLink(index)}>
144
+ <RemoveIcon sx={{ color: 'text.secondary' }} />
145
+ </IconButton>
146
+ </Box>
147
+ );
148
+ })}
132
149
  {links.length < MAX_LINK_COUNT ? (
133
150
  <Button
134
151
  fullWidth
135
152
  variant="outlined"
136
- disabled={isLastError}
153
+ disabled={!!isLastError}
137
154
  onClick={handleAddLink}
138
155
  size="small"
139
156
  sx={{
@@ -18,7 +18,7 @@ import {
18
18
  import { translations } from '../../libs/locales';
19
19
  import type { User, UserAddress, UserMetadata } from '../../../@types';
20
20
  import { formatAxiosError } from '../../libs/utils';
21
- import { currentTimezone, getStatusDuration, isValidUrl } from './utils';
21
+ import { currentTimezone, getStatusDuration, isValidUrl, isDuplicateUrl } from './utils';
22
22
  import SwitchRole from './switch-role';
23
23
  import UserMetadataComponent from './metadata';
24
24
  import UserStatus from './user-status';
@@ -106,9 +106,17 @@ export default function UserBasicInfo({
106
106
  try {
107
107
  const newLinks =
108
108
  metadata?.links
109
- ?.map((link) => {
109
+ ?.map((link, index) => {
110
110
  if (!link.url || !isValidUrl(link.url)) return null;
111
111
 
112
+ // 去重:如果 URL 相同则跳过(忽略协议和大 小写)
113
+ if (index > 0) {
114
+ const prevLinks = metadata.links?.slice(0, index);
115
+ if (prevLinks?.some((l) => isDuplicateUrl(l.url, link.url))) {
116
+ return null;
117
+ }
118
+ }
119
+
112
120
  try {
113
121
  const parsedUrl = parseURL(link.url);
114
122
  // 如果没有协议,添加 https
@@ -109,6 +109,19 @@ export const isValidUrl = (url: string) => {
109
109
  return isUrl(withHttps(url)); // 补充协议后在进行验证是否是合法的url
110
110
  };
111
111
 
112
+ export const isDuplicateUrl = (url1: string, url2: string) => {
113
+ if (!url1 || !url2) {
114
+ return false;
115
+ }
116
+ if (!isValidUrl(url1) || !isValidUrl(url2)) {
117
+ return false;
118
+ }
119
+
120
+ const parsedUrl1 = withHttps(url1.trim());
121
+ const parsedUrl2 = withHttps(url2.trim());
122
+ return parsedUrl1.toLowerCase() === parsedUrl2.toLowerCase();
123
+ };
124
+
112
125
  /**
113
126
  * 根据 duration 类型,计算出date range
114
127
  * @param status
@@ -144,6 +144,7 @@ export const translations = {
144
144
  selectEndTime: '选择结束时间',
145
145
  pleaseSelectTime: '请选择时间',
146
146
  invalidURL: '无效的 URL',
147
+ duplicateURL: 'URL 已存在',
147
148
  socialMedia: '社交链接',
148
149
  timezonePhase: {
149
150
  dawn: '凌晨',
@@ -330,6 +331,7 @@ export const translations = {
330
331
  cleanStatus: 'Clean status',
331
332
  quickSettings: 'Quick Settings',
332
333
  invalidURL: 'Invalid URL',
334
+ duplicateURL: 'URL already exists',
333
335
  socialMedia: 'Social Media',
334
336
  timezonePhase: {
335
337
  dawn: 'AM',