@arcblock/ux 2.10.73 → 2.10.75

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.
@@ -10,8 +10,8 @@
10
10
  * showCancelButton?: true | false,
11
11
  * showCloseButton?: true | false,
12
12
  * fullScreen?: false | true,
13
- * confirmButton?: {text: string, props?: ButtonProps}
14
- * cancelButton?: {text: string, props?: ButtonProps}
13
+ * confirmButton?: {text: React.ReactNode, props?: ButtonProps}
14
+ * cancelButton?: {text: React.ReactNode, props?: ButtonProps}
15
15
  * PaperProps?: PaperProps
16
16
  * }} ConfirmProps
17
17
  */
@@ -32,11 +32,11 @@ declare namespace Confirm {
32
32
  let showCloseButton: PropTypes.Requireable<boolean>;
33
33
  let fullScreen: PropTypes.Requireable<boolean>;
34
34
  let confirmButton: PropTypes.Requireable<PropTypes.InferProps<{
35
- text: PropTypes.Validator<string>;
35
+ text: PropTypes.Validator<NonNullable<PropTypes.ReactNodeLike>>;
36
36
  props: PropTypes.Requireable<object>;
37
37
  }>>;
38
38
  let cancelButton: PropTypes.Requireable<PropTypes.InferProps<{
39
- text: PropTypes.Validator<string>;
39
+ text: PropTypes.Validator<NonNullable<PropTypes.ReactNodeLike>>;
40
40
  props: PropTypes.Requireable<object>;
41
41
  }>>;
42
42
  let PaperProps: PropTypes.Requireable<object>;
@@ -74,11 +74,11 @@ export type ConfirmProps = {
74
74
  showCloseButton?: true | false;
75
75
  fullScreen?: false | true;
76
76
  confirmButton?: {
77
- text: string;
77
+ text: React.ReactNode;
78
78
  props?: ButtonProps;
79
79
  };
80
80
  cancelButton?: {
81
- text: string;
81
+ text: React.ReactNode;
82
82
  props?: ButtonProps;
83
83
  };
84
84
  PaperProps?: PaperProps;
@@ -16,8 +16,8 @@ import Dialog from './dialog';
16
16
  * showCancelButton?: true | false,
17
17
  * showCloseButton?: true | false,
18
18
  * fullScreen?: false | true,
19
- * confirmButton?: {text: string, props?: ButtonProps}
20
- * cancelButton?: {text: string, props?: ButtonProps}
19
+ * confirmButton?: {text: React.ReactNode, props?: ButtonProps}
20
+ * cancelButton?: {text: React.ReactNode, props?: ButtonProps}
21
21
  * PaperProps?: PaperProps
22
22
  * }} ConfirmProps
23
23
  */
@@ -108,11 +108,11 @@ Confirm.propTypes = {
108
108
  fullScreen: PropTypes.bool,
109
109
  // 可以传入 {text: ..., props: ...}
110
110
  confirmButton: PropTypes.shape({
111
- text: PropTypes.string.isRequired,
111
+ text: PropTypes.node.isRequired,
112
112
  props: PropTypes.object
113
113
  }),
114
114
  cancelButton: PropTypes.shape({
115
- text: PropTypes.string.isRequired,
115
+ text: PropTypes.node.isRequired,
116
116
  props: PropTypes.object
117
117
  }),
118
118
  PaperProps: PropTypes.object
@@ -20,7 +20,7 @@ interface LocaleProviderProps {
20
20
  export interface LocaleContextType {
21
21
  locale: Locale;
22
22
  changeLocale: (locale: Locale) => void;
23
- t: (key: string, data?: Record<string, string | number>) => string;
23
+ t: (key: string, data?: Record<string, any>) => string;
24
24
  languages: {
25
25
  code: string;
26
26
  name: string;
@@ -16,6 +16,7 @@ import DefaultLoading from './loading';
16
16
  import DefaultBrokenImage from './broken';
17
17
  import { styled } from '../Theme';
18
18
  import displayApi from './displayApi';
19
+ import RenderSvg from './render-svg';
19
20
 
20
21
  /**
21
22
  * 从 assetState 中获取 nft data, 兼容新旧两种类型的数据结构, 建议将该方法的返回值传入 NFTDisplay 组件的 data prop
@@ -236,6 +237,11 @@ function NFTDisplay({
236
237
  if (imageExtensions.some(ext => pathname.endsWith(ext))) {
237
238
  return renderImg();
238
239
  }
240
+
241
+ // related: https://github.com/ArcBlock/did-spaces/issues/1367
242
+ if (objectType.includes('text/html;')) {
243
+ return /*#__PURE__*/_jsx(DefaultBrokenImage, {});
244
+ }
239
245
  return (
240
246
  /*#__PURE__*/
241
247
  // eslint-disable-next-line jsx-a11y/alt-text
@@ -260,9 +266,15 @@ function NFTDisplay({
260
266
  if (state.loadingUrlType) {
261
267
  return null;
262
268
  }
269
+ if (state.urlType?.startsWith('image/svg+xml')) {
270
+ return /*#__PURE__*/_jsx(RenderSvg, {
271
+ src: getFullContentUrl(),
272
+ onLoad: onLoad
273
+ });
274
+ }
263
275
 
264
276
  // render image
265
- if (state.urlType?.startsWith('image') && !state.urlType.startsWith('image/svg')) {
277
+ if (state.urlType?.startsWith('image')) {
266
278
  return renderImg();
267
279
  }
268
280
  return renderObject();
@@ -0,0 +1,5 @@
1
+ declare function RenderSvg({ src, onLoad }: {
2
+ src: string;
3
+ onLoad?: () => void;
4
+ }): import("react/jsx-runtime").JSX.Element;
5
+ export default RenderSvg;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import DOMPurify from 'dompurify';
3
+ import { useAsyncEffect } from 'ahooks';
4
+ import axios from 'axios';
5
+ import { useState } from 'react';
6
+ import InlineSvg from './svg-embedder/inline-svg';
7
+ function RenderSvg({
8
+ src,
9
+ onLoad
10
+ }) {
11
+ const [svgContent, setSvgContent] = useState('');
12
+ useAsyncEffect(async () => {
13
+ // @note: 注意源站点需要允许跨域
14
+ const response = await axios.get(src);
15
+ const cleanSVG = DOMPurify.sanitize(response.data);
16
+ setSvgContent(cleanSVG);
17
+ onLoad?.();
18
+ }, []);
19
+ return /*#__PURE__*/_jsx(InlineSvg, {
20
+ svg: svgContent
21
+ });
22
+ }
23
+ export default RenderSvg;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcblock/ux",
3
- "version": "2.10.73",
3
+ "version": "2.10.75",
4
4
  "description": "Common used react components for arcblock products",
5
5
  "keywords": [
6
6
  "react",
@@ -67,12 +67,12 @@
67
67
  "react": ">=18.2.0",
68
68
  "react-router-dom": ">=6.22.3"
69
69
  },
70
- "gitHead": "ab1e258ff3ddcabdef0512d52baa75a5ad43a5ec",
70
+ "gitHead": "9e8d12bef56d3cd933ef1cb3a7209068de448dbb",
71
71
  "dependencies": {
72
72
  "@arcblock/did-motif": "^1.1.13",
73
- "@arcblock/icons": "^2.10.73",
74
- "@arcblock/nft-display": "^2.10.73",
75
- "@arcblock/react-hooks": "^2.10.73",
73
+ "@arcblock/icons": "^2.10.75",
74
+ "@arcblock/nft-display": "^2.10.75",
75
+ "@arcblock/react-hooks": "^2.10.75",
76
76
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
77
77
  "@fontsource/inter": "^5.0.16",
78
78
  "@fontsource/ubuntu-mono": "^5.0.18",
@@ -83,6 +83,7 @@
83
83
  "@iconify/react": "^4.1.1",
84
84
  "@solana/qr-code-styling": "^1.6.0-beta.0",
85
85
  "@testing-library/react": "^14.0.0",
86
+ "@types/dompurify": "^3.2.0",
86
87
  "@types/mui-datatables": "^4.3.12",
87
88
  "ahooks": "^3.7.10",
88
89
  "axios": "^1.7.5",
@@ -92,6 +93,7 @@
92
93
  "d3-geo": "^1.12.1",
93
94
  "dayjs": "^1.11.5",
94
95
  "devices.css": "^0.1.15",
96
+ "dompurify": "^3.2.1",
95
97
  "highlight.js": "^11.6.0",
96
98
  "iconify-icon": "^1.0.8",
97
99
  "iconify-icons-material-symbols-400": "^0.0.1",
@@ -16,8 +16,8 @@ import Dialog from './dialog';
16
16
  * showCancelButton?: true | false,
17
17
  * showCloseButton?: true | false,
18
18
  * fullScreen?: false | true,
19
- * confirmButton?: {text: string, props?: ButtonProps}
20
- * cancelButton?: {text: string, props?: ButtonProps}
19
+ * confirmButton?: {text: React.ReactNode, props?: ButtonProps}
20
+ * cancelButton?: {text: React.ReactNode, props?: ButtonProps}
21
21
  * PaperProps?: PaperProps
22
22
  * }} ConfirmProps
23
23
  */
@@ -121,11 +121,11 @@ Confirm.propTypes = {
121
121
  fullScreen: PropTypes.bool,
122
122
  // 可以传入 {text: ..., props: ...}
123
123
  confirmButton: PropTypes.shape({
124
- text: PropTypes.string.isRequired,
124
+ text: PropTypes.node.isRequired,
125
125
  props: PropTypes.object,
126
126
  }),
127
127
  cancelButton: PropTypes.shape({
128
- text: PropTypes.string.isRequired,
128
+ text: PropTypes.node.isRequired,
129
129
  props: PropTypes.object,
130
130
  }),
131
131
  PaperProps: PropTypes.object,
@@ -74,7 +74,7 @@ interface LocaleProviderProps {
74
74
  export interface LocaleContextType {
75
75
  locale: Locale;
76
76
  changeLocale: (locale: Locale) => void;
77
- t: (key: string, data?: Record<string, string | number>) => string;
77
+ t: (key: string, data?: Record<string, any>) => string;
78
78
  languages: { code: string; name: string }[];
79
79
  }
80
80
 
@@ -130,7 +130,7 @@ function LocaleProvider({
130
130
  }
131
131
 
132
132
  const t = useCallback(
133
- (key: string, data?: any) => translate(translations, key, currentLocale, fallbackLocale, data),
133
+ (key: string, data?: Record<string, any>) => translate(translations, key, currentLocale, fallbackLocale, data),
134
134
  [translations, currentLocale, fallbackLocale]
135
135
  );
136
136
 
@@ -31,7 +31,7 @@ export default function Metric({
31
31
  <div>
32
32
  <div
33
33
  className={`metric__number ${animated ? 'metric__number--animated' : ''}`}
34
- dangerouslySetInnerHTML={{ __html: value }}
34
+ dangerouslySetInnerHTML={{ __html: value as string }}
35
35
  />
36
36
  <div className="metric__name">{name}</div>
37
37
  </div>
@@ -16,6 +16,7 @@ import DefaultLoading from './loading';
16
16
  import DefaultBrokenImage from './broken';
17
17
  import { styled } from '../Theme';
18
18
  import displayApi from './displayApi';
19
+ import RenderSvg from './render-svg';
19
20
 
20
21
  /**
21
22
  * 从 assetState 中获取 nft data, 兼容新旧两种类型的数据结构, 建议将该方法的返回值传入 NFTDisplay 组件的 data prop
@@ -113,7 +114,7 @@ function NFTDisplay({
113
114
  const display = parsed.current.credentialSubject
114
115
  ? get(parsed.current, 'credentialSubject.display')
115
116
  : parsed.current;
116
- const { content, type } = display;
117
+ const { content, type } = display as { content: string; type: string };
117
118
  const isUrlType = type === 'url';
118
119
 
119
120
  // 首次加载, 对于 url type 的情况, loading 为 true
@@ -256,6 +257,11 @@ function NFTDisplay({
256
257
  return renderImg();
257
258
  }
258
259
 
260
+ // related: https://github.com/ArcBlock/did-spaces/issues/1367
261
+ if (objectType.includes('text/html;')) {
262
+ return <DefaultBrokenImage />;
263
+ }
264
+
259
265
  return (
260
266
  // eslint-disable-next-line jsx-a11y/alt-text
261
267
  <object
@@ -277,8 +283,12 @@ function NFTDisplay({
277
283
  return null;
278
284
  }
279
285
 
286
+ if (state.urlType?.startsWith('image/svg+xml')) {
287
+ return <RenderSvg src={getFullContentUrl()} onLoad={onLoad} />;
288
+ }
289
+
280
290
  // render image
281
- if (state.urlType?.startsWith('image') && !state.urlType.startsWith('image/svg')) {
291
+ if (state.urlType?.startsWith('image')) {
282
292
  return renderImg();
283
293
  }
284
294
 
@@ -0,0 +1,21 @@
1
+ import DOMPurify from 'dompurify';
2
+ import { useAsyncEffect } from 'ahooks';
3
+ import axios from 'axios';
4
+ import { useState } from 'react';
5
+ import InlineSvg from './svg-embedder/inline-svg';
6
+
7
+ function RenderSvg({ src, onLoad }: { src: string; onLoad?: () => void }) {
8
+ const [svgContent, setSvgContent] = useState('');
9
+
10
+ useAsyncEffect(async () => {
11
+ // @note: 注意源站点需要允许跨域
12
+ const response = await axios.get(src);
13
+ const cleanSVG = DOMPurify.sanitize(response.data);
14
+ setSvgContent(cleanSVG);
15
+ onLoad?.();
16
+ }, []);
17
+
18
+ return <InlineSvg svg={svgContent} />;
19
+ }
20
+
21
+ export default RenderSvg;