@abtnode/ux 1.16.28-next-5a717317 → 1.16.28

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.
Files changed (59) hide show
  1. package/lib/actions.js +8 -6
  2. package/lib/analytics/traffic.js +4 -10
  3. package/lib/blocklet/authentication/federated/federated-detail-dialog.js +2 -2
  4. package/lib/blocklet/blocklet-source.js +2 -2
  5. package/lib/blocklet/component/add-component/add-component-core.js +3 -2
  6. package/lib/blocklet/component/component-cell-mount-poin.js +164 -0
  7. package/lib/blocklet/component/component-cell.js +83 -84
  8. package/lib/blocklet/component/configuration.js +52 -36
  9. package/lib/blocklet/component/did-space/connect-to.js +4 -4
  10. package/lib/blocklet/component/index.js +29 -38
  11. package/lib/blocklet/component/navigation/navigation-dialog.js +2 -3
  12. package/lib/blocklet/component/space-connector.js +3 -3
  13. package/lib/blocklet/component/space-selector.js +2 -2
  14. package/lib/blocklet/component/store-blocklet-list.js +3 -3
  15. package/lib/blocklet/logo-uploader.js +2 -2
  16. package/lib/blocklet/notification/push-kit.js +1 -1
  17. package/lib/blocklet/preferences/index.js +3 -3
  18. package/lib/blocklet/publish/create-release/header.js +36 -31
  19. package/lib/blocklet/publish/create-release/index.js +54 -21
  20. package/lib/blocklet/publish/create-release/keep-now-resources.js +15 -0
  21. package/lib/blocklet/publish/create-release/release-stepper.js +4 -4
  22. package/lib/blocklet/publish/create-release/{resources.js → resource-select.js} +8 -7
  23. package/lib/blocklet/publish/create-release/resource-upload.js +105 -0
  24. package/lib/blocklet/publish/project-list.js +6 -0
  25. package/lib/blocklet/publish/release-list.js +4 -4
  26. package/lib/blocklet/publish/utils/validate-params.js +4 -4
  27. package/lib/blocklet/purchase/component-purchase.js +2 -2
  28. package/lib/blocklet/router/action/add-rule.js +31 -18
  29. package/lib/blocklet/router/rule-actions.js +8 -1
  30. package/lib/blocklet/storage/connect-to.js +4 -4
  31. package/lib/blocklet/storage/item.js +3 -3
  32. package/lib/blocklet/util.js +4 -4
  33. package/lib/hooks/url-evaluation.js +2 -2
  34. package/lib/hooks/use-app-logo.js +4 -4
  35. package/lib/hooks/use-bundle-logo.js +4 -4
  36. package/lib/invitation/invitation-receive.js +1 -1
  37. package/lib/locales/ar.js +12 -5
  38. package/lib/locales/de.js +11 -4
  39. package/lib/locales/en.js +11 -4
  40. package/lib/locales/es.js +11 -4
  41. package/lib/locales/fr.js +11 -4
  42. package/lib/locales/hi.js +11 -4
  43. package/lib/locales/i18n.db +0 -0
  44. package/lib/locales/id.js +11 -4
  45. package/lib/locales/ja.js +11 -4
  46. package/lib/locales/ko.js +11 -4
  47. package/lib/locales/pt.js +11 -4
  48. package/lib/locales/ru.js +11 -4
  49. package/lib/locales/th.js +11 -4
  50. package/lib/locales/vi.js +11 -4
  51. package/lib/locales/zh-tw.js +12 -5
  52. package/lib/locales/zh.js +12 -5
  53. package/lib/store/connect-actions.js +2 -2
  54. package/lib/store/item.js +2 -2
  55. package/lib/team/members/util.js +3 -3
  56. package/lib/util/index.js +18 -18
  57. package/lib/util/join-url-keep-search.js +2 -2
  58. package/lib/util/spaces.js +4 -2
  59. package/package.json +19 -20
package/lib/locales/zh.js CHANGED
@@ -355,7 +355,8 @@ export default {
355
355
  upload: '上传',
356
356
  blockletStudio: 'Studio',
357
357
  visitStore: '访问 Store',
358
- reconnect: '重新连接'
358
+ reconnect: '重新连接',
359
+ change: '更改'
359
360
  },
360
361
  blocklet: {
361
362
  external: '外部',
@@ -680,7 +681,7 @@ export default {
680
681
  resourceEmptyTip: '没有可用的资源',
681
682
  releaseEmptyTip: '这里没有任何发布',
682
683
  createReleaseTip: '您可以创建 Blocklet 新版本,在发布中包含说明和资源,以供其他人使用',
683
- createRelease: '创建新的版本',
684
+ createRelease: '创建版本',
684
685
  deleteReleaseTip: '此发布删除后不可恢复,是否继续?',
685
686
  createBlockletDid: '通过 DID 钱包创建',
686
687
  createReleaseSuccess: '发布成功',
@@ -705,7 +706,7 @@ export default {
705
706
  noFirstDid: '请先填写 Blocklet DID',
706
707
  noTitle: '请填写 Blocklet 名称',
707
708
  noVersion: '请填写 Blocklet 版本号',
708
- noResource: '请选择需要关联的资源',
709
+ noResource: '请选择上传的资源以包含在区块中',
709
710
  noDescription: '请填写 Blocklet 描述',
710
711
  noNote: '请填写 发布说明',
711
712
  inValidVersion: '无效的 Blocklet 版本'
@@ -744,7 +745,9 @@ export default {
744
745
  branding: '描述您的区块链应用的基本信息。此信息将在商店向用户展示。'
745
746
  },
746
747
  branding: '品牌',
747
- brandingHelp: '将在商店中显示有关区块链的更多信息'
748
+ brandingHelp: '将在商店中显示有关区块链的更多信息',
749
+ resourceSelect: '从 blocklets 选择资源',
750
+ resourceUpload: '从您的磁盘上传资源'
748
751
  },
749
752
  titleValidationError: '标题长度必须至少为3到40个字符',
750
753
  descriptionValidationError: '描述长度必须在3到160个字符之间'
@@ -874,6 +877,10 @@ export default {
874
877
  welcome: {
875
878
  switchLabel: 'Welcome 页面'
876
879
  },
880
+ security: {
881
+ switchLabel: '文件系统隔离',
882
+ switchTips: '关闭此选项后,Blocklet 的文件系统将不再处于隔离状态,可能会存在恶意软件破坏文件系统的风险。'
883
+ },
877
884
  register: {
878
885
  titleAdded: '当前节点已经添加到应用启动器',
879
886
  title: '添加节点到应用启动器',
@@ -983,7 +990,7 @@ export default {
983
990
  testSend: '测试推送',
984
991
  inputTestReceiver: '请输入测试接收者 DID',
985
992
  receiverRequired: '需要 PushKit 接收者 DID',
986
- pushPath: '推送套件的 API 路径(默认 /api/push)',
993
+ pushPath: 'PushKit API 路径(默认 /api/push)',
987
994
  did: 'PushKit Blocklet DID',
988
995
  customOptions: '自定义选项'
989
996
  }
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
2
2
  import { useState } from 'react';
3
3
  import { Box, Button, Menu, MenuItem } from '@mui/material';
4
4
  import MoreVertIcon from '@mui/icons-material/MoreVert';
5
- import joinUrl from 'url-join';
5
+ import { joinURL } from 'ufo';
6
6
  import LaunchIcon from '@mui/icons-material/Launch';
7
7
  import LinkOutlinedIcon from '@mui/icons-material/LinkOutlined';
8
8
  import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
@@ -26,7 +26,7 @@ function ConnectActions({
26
26
  const [showReconnectConfirm, setShowReconnectConfirm] = useState(false);
27
27
  const handleOpenUrl = e => {
28
28
  e.stopPropagation();
29
- window.open(joinUrl(store.url, 'developer/blocklets'));
29
+ window.open(joinURL(store.url, 'developer/blocklets'));
30
30
  window.open(store.url);
31
31
  };
32
32
  const handleOpen = event => {
package/lib/store/item.js CHANGED
@@ -7,7 +7,7 @@ import AdminPanelSettingsIcon from '@mui/icons-material/AdminPanelSettings';
7
7
  import DoneIcon from '@mui/icons-material/Done';
8
8
  import PropTypes from 'prop-types';
9
9
  import { useEffect, useState } from 'react';
10
- import joinUrl from 'url-join';
10
+ import { joinURL } from 'ufo';
11
11
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
12
12
  import Toast from '@arcblock/ux/lib/Toast';
13
13
  import DeleteStore from './delete';
@@ -117,7 +117,7 @@ export default function StoreItem({
117
117
  const handleOpenUrl = e => {
118
118
  e.stopPropagation();
119
119
  if (isPublish) {
120
- window.open(joinUrl(store.url, 'developer/blocklets'));
120
+ window.open(joinURL(store.url, 'developer/blocklets'));
121
121
  return;
122
122
  }
123
123
  window.open(store.url);
@@ -1,4 +1,4 @@
1
- import joinUrl from 'url-join';
1
+ import { joinURL } from 'ufo';
2
2
  import { USER_AVATAR_URL_PREFIX, USER_AVATAR_PATH_PREFIX } from '@abtnode/constant';
3
3
 
4
4
  // eslint-disable-next-line import/prefer-default-export
@@ -6,9 +6,9 @@ export function parseAvatar(avatar, teamDid, inService) {
6
6
  if (avatar && avatar.startsWith(USER_AVATAR_URL_PREFIX)) {
7
7
  const prefix = window.env.apiPrefix || '/';
8
8
  if (inService) {
9
- return joinUrl(prefix, USER_AVATAR_PATH_PREFIX, avatar.replace(USER_AVATAR_URL_PREFIX, ''));
9
+ return joinURL(prefix, USER_AVATAR_PATH_PREFIX, avatar.replace(USER_AVATAR_URL_PREFIX, ''));
10
10
  }
11
- return joinUrl(prefix, USER_AVATAR_PATH_PREFIX, teamDid, avatar.replace(USER_AVATAR_URL_PREFIX, ''));
11
+ return joinURL(prefix, USER_AVATAR_PATH_PREFIX, teamDid, avatar.replace(USER_AVATAR_URL_PREFIX, ''));
12
12
  }
13
13
  return avatar;
14
14
  }
package/lib/util/index.js CHANGED
@@ -8,7 +8,7 @@ import isUrl from 'is-url';
8
8
  import get from 'lodash/get';
9
9
  import trimEnd from 'lodash/trimEnd';
10
10
  import qs from 'querystring';
11
- import joinUrl from 'url-join';
11
+ import { joinURL } from 'ufo';
12
12
  import axios from '@abtnode/util/lib/axios';
13
13
  import dayjs from '@abtnode/util/lib/dayjs';
14
14
  import tryWithTimeout from '@abtnode/util/lib/try-with-timeout';
@@ -272,7 +272,7 @@ export const getInviteLink = ({
272
272
  return '';
273
273
  }
274
274
  const u = new URL(endpoint);
275
- u.pathname = joinUrl(u.pathname, WELLKNOWN_SERVICE_PATH_PREFIX, 'invite');
275
+ u.pathname = joinURL(u.pathname, WELLKNOWN_SERVICE_PATH_PREFIX, 'invite');
276
276
  u.searchParams.set('inviteId', inviteId);
277
277
  return u.href;
278
278
  };
@@ -284,7 +284,7 @@ export const getTransferAppLink = ({
284
284
  return '';
285
285
  }
286
286
  const u = new URL(endpoint);
287
- u.pathname = joinUrl(u.pathname, WELLKNOWN_SERVICE_PATH_PREFIX, 'transfer');
287
+ u.pathname = joinURL(u.pathname, WELLKNOWN_SERVICE_PATH_PREFIX, 'transfer');
288
288
  u.searchParams.set('transferId', transferId);
289
289
  return u.href;
290
290
  };
@@ -296,7 +296,7 @@ export const getIssuePassportLink = ({
296
296
  return '';
297
297
  }
298
298
  const u = new URL(endpoint);
299
- u.pathname = joinUrl(u.pathname, WELLKNOWN_SERVICE_PATH_PREFIX, 'issue-passport');
299
+ u.pathname = joinURL(u.pathname, WELLKNOWN_SERVICE_PATH_PREFIX, 'issue-passport');
300
300
  u.searchParams.set('id', id);
301
301
  return u.href;
302
302
  };
@@ -366,22 +366,22 @@ export const getBlockletLogoUrl = ({
366
366
  return logoPath;
367
367
  }
368
368
  if (baseUrl.startsWith('http') && logoPath) {
369
- return joinUrl(baseUrl, formatRegistryLogoPath(did, logoPath, version));
369
+ return joinURL(baseUrl, formatRegistryLogoPath(did, logoPath, version));
370
370
  }
371
371
  const prefix = window.env.apiPrefix || '/';
372
372
  let apiPrefix = prefix.replace(/^\/+/, '').replace(/\/+$/, '');
373
373
  if (apiPrefix) {
374
374
  apiPrefix = `/${apiPrefix}`;
375
375
  }
376
- return joinUrl(apiPrefix, version ? `/blocklet/logo/${did}?v=${version}` : `/blocklet/logo/${did}`);
376
+ return joinURL(apiPrefix, version ? `/blocklet/logo/${did}?v=${version}` : `/blocklet/logo/${did}`);
377
377
  };
378
378
  export const getPathPrefix = () => window.env && window.env.apiPrefix ? window.env.apiPrefix : '/';
379
- export const isUrlAccessible = url => checkAccessible(joinUrl(new URL(url).origin, WELLKNOWN_PING_PREFIX));
379
+ export const isUrlAccessible = url => checkAccessible(joinURL(new URL(url).origin, WELLKNOWN_PING_PREFIX));
380
380
  const checkBlockletAccessible = async (url, timeout = 5000) => {
381
381
  try {
382
382
  const urlObj = new URL(url);
383
383
  urlObj.protocol = 'https';
384
- urlObj.pathname = joinUrl(WELLKNOWN_BLOCKLET_HEALTH_PATH);
384
+ urlObj.pathname = joinURL(WELLKNOWN_BLOCKLET_HEALTH_PATH);
385
385
  const response = await axios.get(urlObj.toString(), {
386
386
  timeout,
387
387
  validateStatus: status => {
@@ -501,10 +501,10 @@ export function getBlockletMetaUrl(storeUrl, did, dist) {
501
501
  if (!origin && dist?.tarball) {
502
502
  origin = new URL(dist.tarball).origin;
503
503
  }
504
- return joinUrl(origin, BLOCKLET_STORE_API_PREFIX, `/blocklets/${did}/blocklet.json`);
504
+ return joinURL(origin, BLOCKLET_STORE_API_PREFIX, `/blocklets/${did}/blocklet.json`);
505
505
  }
506
506
  export async function getAsset(chainHost, address) {
507
- const result = await axios.post(joinUrl(chainHost, '/api/gql/'), JSON.stringify({
507
+ const result = await axios.post(joinURL(chainHost, '/api/gql/'), JSON.stringify({
508
508
  query: `{
509
509
  getAssetState(address: "${address}") {
510
510
  state {
@@ -627,12 +627,12 @@ const getLauncherBaseURL = async launcherUrl => {
627
627
  try {
628
628
  const {
629
629
  data: appMeta
630
- } = await axios.get(joinUrl(launcherUrl, '__blocklet__.js?type=json'), {
630
+ } = await axios.get(joinURL(launcherUrl, '__blocklet__.js?type=json'), {
631
631
  timeout: 5000
632
632
  });
633
633
  const mountPoint = appMeta.componentMountPoints?.find(item => item.did === 'z8iZkFBbrVQxZHvcWWB3Sa2TrfGmSeFz9MSU7');
634
634
  const launcherUrlObj = new URL(launcherUrl);
635
- baseUrl = joinUrl(launcherUrlObj.origin, mountPoint?.mountPoint);
635
+ baseUrl = joinURL(launcherUrlObj.origin, mountPoint?.mountPoint);
636
636
  } catch (error) {
637
637
  console.error(error);
638
638
  }
@@ -646,20 +646,20 @@ export const getSubscriptionURL = async ({
646
646
  if (!launcherUrl) {
647
647
  return '';
648
648
  }
649
- let baseUrl = joinUrl(launcherUrl, '/instances');
649
+ let baseUrl = joinURL(launcherUrl, '/instances');
650
650
  try {
651
651
  const {
652
652
  data: appMeta
653
- } = await axios.get(joinUrl(launcherUrl, '__blocklet__.js?type=json'), {
653
+ } = await axios.get(joinURL(launcherUrl, '__blocklet__.js?type=json'), {
654
654
  timeout: 5000
655
655
  });
656
656
  const mountPoint = appMeta.componentMountPoints?.find(item => item.did === 'z8iZy4P83i6AgnNdNUexsh2kBcsDHoqcwPavn');
657
657
  const launcherUrlObj = new URL(launcherUrl);
658
- baseUrl = joinUrl(launcherUrlObj.origin, mountPoint?.mountPoint, '/nfts');
658
+ baseUrl = joinURL(launcherUrlObj.origin, mountPoint?.mountPoint, '/nfts');
659
659
  } catch (error) {
660
660
  console.error(error);
661
661
  }
662
- return joinUrl(baseUrl, `/${nftId}/subscription?locale=${locale}&return-url=${encodeURIComponent(window.location.href)}`);
662
+ return joinURL(baseUrl, `/${nftId}/subscription?locale=${locale}&return-url=${encodeURIComponent(window.location.href)}`);
663
663
  };
664
664
  export const getSubscriptionUrlV2 = async ({
665
665
  launcherUrl = '',
@@ -671,7 +671,7 @@ export const getSubscriptionUrlV2 = async ({
671
671
  return '';
672
672
  }
673
673
  const baseUrl = await getLauncherBaseURL(launcherUrl);
674
- return joinUrl(baseUrl, `/embed/subscription?launcherSessionId=${launcherSessionId}&nftDid=${nftDid}&locale=${locale}`); // nftDid 是兼容字段
674
+ return joinURL(baseUrl, `/embed/subscription?launcherSessionId=${launcherSessionId}&nftDid=${nftDid}&locale=${locale}`); // nftDid 是兼容字段
675
675
  };
676
676
  export const getManageSubscriptionURL = async ({
677
677
  launcherUrl = '',
@@ -682,7 +682,7 @@ export const getManageSubscriptionURL = async ({
682
682
  return '';
683
683
  }
684
684
  const baseUrl = await getLauncherBaseURL(launcherUrl);
685
- return joinUrl(baseUrl, `/embed/manage-subscription?launcherSessionId=${launcherSessionId}&locale=${locale}`);
685
+ return joinURL(baseUrl, `/embed/manage-subscription?launcherSessionId=${launcherSessionId}&locale=${locale}`);
686
686
  };
687
687
  export const nanoid = (length = 16) => [...Array(length)].map(() => Math.random().toString(36)[2]).join('');
688
688
  export const getSystemDomains = domains => domains.filter(x => x.isProtected);
@@ -1,4 +1,4 @@
1
- import joinUrl from 'url-join';
1
+ import { joinURL } from 'ufo';
2
2
  const joinUrlKeepSearch = (originalUrl, path) => {
3
3
  if (!originalUrl) {
4
4
  return '';
@@ -8,7 +8,7 @@ const joinUrlKeepSearch = (originalUrl, path) => {
8
8
  }
9
9
  const url = new URL(originalUrl);
10
10
  const searchParams = new URLSearchParams(url.searchParams);
11
- const newPath = new URL(joinUrl(url.origin, path));
11
+ const newPath = new URL(joinURL(url.origin, path));
12
12
  newPath.search = searchParams.toString();
13
13
  return newPath.toString();
14
14
  };
@@ -1,7 +1,7 @@
1
1
  import isUrl from 'is-url';
2
2
  import { BLOCKLET_CONFIGURABLE_KEY } from '@blocklet/constant';
3
3
  import { isObject, isString } from 'lodash';
4
- import { joinURL } from 'ufo';
4
+ import { joinURL, withQuery } from 'ufo';
5
5
  import { api } from './api';
6
6
 
7
7
  /* eslint-disable import/prefer-default-export */
@@ -230,6 +230,8 @@ export async function getSpaceGatewayUrl(anyUrl, options = {
230
230
  throw new Error(`MountPoint(${mountPoint}) not found`);
231
231
  }
232
232
  const didSpacesCoreUrl = joinURL(u.origin, mountPoint);
233
- const spaceGatewayUrl = options.withSearchParams && u.searchParams.get('spaceDid') ? joinURL(didSpacesCoreUrl, `?spaceDid=${u.searchParams.get('spaceDid')}`) : didSpacesCoreUrl;
233
+ const spaceGatewayUrl = options.withSearchParams && u.searchParams.get('spaceDid') ? withQuery(didSpacesCoreUrl, {
234
+ spaceDid: u.searchParams.get('spaceDid')
235
+ }) : didSpacesCoreUrl;
234
236
  return spaceGatewayUrl;
235
237
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abtnode/ux",
3
- "version": "1.16.28-next-5a717317",
3
+ "version": "1.16.28",
4
4
  "description": "UX components shared across abtnode packages",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -25,24 +25,24 @@
25
25
  "author": "linchen <linchen1987@foxmail.com> (http://github.com/linchen1987)",
26
26
  "license": "Apache-2.0",
27
27
  "dependencies": {
28
- "@abtnode/auth": "1.16.28-next-5a717317",
29
- "@abtnode/constant": "1.16.28-next-5a717317",
30
- "@abtnode/util": "1.16.28-next-5a717317",
28
+ "@abtnode/auth": "1.16.28",
29
+ "@abtnode/constant": "1.16.28",
30
+ "@abtnode/util": "1.16.28",
31
31
  "@ahooksjs/use-url-state": "^3.5.1",
32
32
  "@arcblock/did": "^1.18.123",
33
- "@arcblock/did-connect": "^2.9.90",
33
+ "@arcblock/did-connect": "^2.10.0",
34
34
  "@arcblock/did-motif": "^1.1.13",
35
- "@arcblock/icons": "^2.9.90",
36
- "@arcblock/nft-display": "2.9.90",
37
- "@arcblock/react-hooks": "^2.9.90",
38
- "@arcblock/terminal": "^2.9.90",
39
- "@arcblock/ux": "^2.9.90",
40
- "@blocklet/constant": "1.16.28-next-5a717317",
41
- "@blocklet/launcher-layout": "2.3.21",
42
- "@blocklet/list": "^0.12.111",
43
- "@blocklet/meta": "1.16.28-next-5a717317",
44
- "@blocklet/ui-react": "^2.9.90",
45
- "@blocklet/uploader": "^0.1.6",
35
+ "@arcblock/icons": "^2.10.0",
36
+ "@arcblock/nft-display": "2.10.0",
37
+ "@arcblock/react-hooks": "^2.10.0",
38
+ "@arcblock/terminal": "^2.10.0",
39
+ "@arcblock/ux": "^2.10.0",
40
+ "@blocklet/constant": "1.16.28",
41
+ "@blocklet/launcher-layout": "2.3.22",
42
+ "@blocklet/list": "^0.12.114",
43
+ "@blocklet/meta": "1.16.28",
44
+ "@blocklet/ui-react": "^2.10.0",
45
+ "@blocklet/uploader": "^0.1.10",
46
46
  "@emotion/react": "^11.10.4",
47
47
  "@emotion/styled": "^11.10.4",
48
48
  "@iconify-icons/logos": "^1.2.36",
@@ -58,7 +58,7 @@
58
58
  "@ocap/client": "1.18.123",
59
59
  "@uiw/react-markdown-preview": "^5.1.1",
60
60
  "ahooks": "^3.7.8",
61
- "axios": "^0.27.2",
61
+ "axios": "^1.7.2",
62
62
  "classnames": "^2.3.2",
63
63
  "copy-to-clipboard": "^3.3.3",
64
64
  "date-fns": "^2.30.0",
@@ -93,8 +93,7 @@
93
93
  "rehype-external-links": "^3.0.0",
94
94
  "semver": "^7.3.8",
95
95
  "timeago.js": "^4.0.2",
96
- "ufo": "^1.0.1",
97
- "url-join": "^4.0.1",
96
+ "ufo": "^1.5.3",
98
97
  "validator": "^13.9.0"
99
98
  },
100
99
  "devDependencies": {
@@ -108,5 +107,5 @@
108
107
  "jest": "^29.7.0",
109
108
  "jest-environment-jsdom": "^29.7.0"
110
109
  },
111
- "gitHead": "3624967f9549de3a25a87ed6b20f82519ddb4757"
110
+ "gitHead": "54db076a7e520bbc260f8cbf0af31dd50b86aef1"
112
111
  }