@antv/dumi-theme-antv 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +9 -0
  2. package/es/antv/404/index.js +20 -0
  3. package/es/antv/Banner/Banner.module.less +412 -0
  4. package/es/antv/Banner/Notification.js +44 -0
  5. package/es/antv/Banner/Notification.module.less +108 -0
  6. package/es/antv/Banner/index.js +115 -0
  7. package/es/antv/Cases/Cases.js +124 -0
  8. package/es/antv/Cases/Cases.module.less +203 -0
  9. package/es/antv/Features/FeatureCard.js +25 -0
  10. package/es/antv/Features/FeatureCard.module.less +51 -0
  11. package/es/antv/Features/Features.module.less +169 -0
  12. package/es/antv/Features/index.js +102 -0
  13. package/es/antv/Footer/Footer.module.less +36 -0
  14. package/es/antv/Footer/index.js +232 -0
  15. package/es/antv/Header/Logo.js +130 -0
  16. package/es/antv/Products/Product.js +61 -0
  17. package/es/antv/Products/Product.module.less +146 -0
  18. package/es/antv/Products/getNewProducts.js +41 -0
  19. package/es/antv/Products/getProducts.js +466 -0
  20. package/es/antv/Products/index.js +81 -0
  21. package/es/antv/hooks.js +81 -0
  22. package/es/antv/mixins.less +21 -0
  23. package/es/antv/utils.js +49 -0
  24. package/es/builtins/API.js +37 -0
  25. package/es/builtins/Alert.js +9 -0
  26. package/es/builtins/Alert.less +62 -0
  27. package/es/builtins/Badge.js +9 -0
  28. package/es/builtins/Badge.less +31 -0
  29. package/es/builtins/Example.js +48 -0
  30. package/es/builtins/Example.less +47 -0
  31. package/es/builtins/Previewer.js +225 -0
  32. package/es/builtins/Previewer.less +406 -0
  33. package/es/builtins/SourceCode.js +72 -0
  34. package/es/builtins/SourceCode.less +103 -0
  35. package/es/builtins/Table.js +56 -0
  36. package/es/builtins/Table.less +43 -0
  37. package/es/builtins/Tree.js +219 -0
  38. package/es/builtins/Tree.less +159 -0
  39. package/es/components/Dark.js +125 -0
  40. package/es/components/Dark.less +121 -0
  41. package/es/components/LocaleSelect.js +53 -0
  42. package/es/components/LocaleSelect.less +59 -0
  43. package/es/components/Navbar.js +155 -0
  44. package/es/components/Navbar.less +180 -0
  45. package/es/components/SearchBar.js +83 -0
  46. package/es/components/SearchBar.less +165 -0
  47. package/es/components/SideMenu.js +99 -0
  48. package/es/components/SideMenu.less +379 -0
  49. package/es/components/SlugList.js +33 -0
  50. package/es/components/SlugList.less +18 -0
  51. package/es/layout.js +276 -0
  52. package/es/style/layout.less +402 -0
  53. package/es/style/markdown.less +240 -0
  54. package/es/style/variables.less +37 -0
  55. package/package.json +58 -0
  56. package/src/antv/404/index.tsx +25 -0
  57. package/src/antv/Banner/Banner.module.less +412 -0
  58. package/src/antv/Banner/Notification.module.less +108 -0
  59. package/src/antv/Banner/Notification.tsx +45 -0
  60. package/src/antv/Banner/index.tsx +121 -0
  61. package/src/antv/Cases/Cases.module.less +203 -0
  62. package/src/antv/Cases/Cases.tsx +116 -0
  63. package/src/antv/Features/FeatureCard.module.less +51 -0
  64. package/src/antv/Features/FeatureCard.tsx +24 -0
  65. package/src/antv/Features/Features.module.less +169 -0
  66. package/src/antv/Features/index.tsx +86 -0
  67. package/src/antv/Footer/Footer.module.less +36 -0
  68. package/src/antv/Footer/index.tsx +272 -0
  69. package/src/antv/Header/Logo.tsx +85 -0
  70. package/src/antv/Products/Product.module.less +146 -0
  71. package/src/antv/Products/Product.tsx +80 -0
  72. package/src/antv/Products/getNewProducts.tsx +53 -0
  73. package/src/antv/Products/getProducts.tsx +626 -0
  74. package/src/antv/Products/index.tsx +70 -0
  75. package/src/antv/hooks.ts +87 -0
  76. package/src/antv/mixins.less +21 -0
  77. package/src/antv/utils.ts +44 -0
  78. package/src/builtins/API.tsx +57 -0
  79. package/src/builtins/Alert.less +62 -0
  80. package/src/builtins/Alert.tsx +4 -0
  81. package/src/builtins/Badge.less +31 -0
  82. package/src/builtins/Badge.tsx +4 -0
  83. package/src/builtins/Example.less +47 -0
  84. package/src/builtins/Example.tsx +34 -0
  85. package/src/builtins/Previewer.less +406 -0
  86. package/src/builtins/Previewer.tsx +264 -0
  87. package/src/builtins/SourceCode.less +103 -0
  88. package/src/builtins/SourceCode.tsx +55 -0
  89. package/src/builtins/Table.less +43 -0
  90. package/src/builtins/Table.tsx +42 -0
  91. package/src/builtins/Tree.less +159 -0
  92. package/src/builtins/Tree.tsx +130 -0
  93. package/src/components/Dark.less +121 -0
  94. package/src/components/Dark.tsx +78 -0
  95. package/src/components/LocaleSelect.less +59 -0
  96. package/src/components/LocaleSelect.tsx +53 -0
  97. package/src/components/Navbar.less +180 -0
  98. package/src/components/Navbar.tsx +152 -0
  99. package/src/components/SearchBar.less +165 -0
  100. package/src/components/SearchBar.tsx +68 -0
  101. package/src/components/SideMenu.less +379 -0
  102. package/src/components/SideMenu.tsx +148 -0
  103. package/src/components/SlugList.less +18 -0
  104. package/src/components/SlugList.tsx +20 -0
  105. package/src/layout.tsx +225 -0
  106. package/src/style/layout.less +402 -0
  107. package/src/style/markdown.less +240 -0
  108. package/src/style/variables.less +37 -0
  109. package/src/test/SearchBar.test.ts +32 -0
  110. package/src/test/Table.test.tsx +41 -0
  111. package/src/test/index.test.tsx +377 -0
@@ -0,0 +1,99 @@
1
+ import React, { useContext } from 'react';
2
+ import { context, Link, NavLink } from 'dumi/theme';
3
+ import LocaleSelect from './LocaleSelect';
4
+ import SlugList from './SlugList';
5
+ import './SideMenu.less';
6
+
7
+ var SideMenu = function SideMenu(_ref) {
8
+ var mobileMenuCollapsed = _ref.mobileMenuCollapsed,
9
+ location = _ref.location,
10
+ darkPrefix = _ref.darkPrefix;
11
+
12
+ var _useContext = useContext(context),
13
+ _useContext$config = _useContext.config,
14
+ logo = _useContext$config.logo,
15
+ title = _useContext$config.title,
16
+ description = _useContext$config.description,
17
+ mode = _useContext$config.mode,
18
+ repoUrl = _useContext$config.repository.url,
19
+ menu = _useContext.menu,
20
+ navItems = _useContext.nav,
21
+ base = _useContext.base,
22
+ meta = _useContext.meta;
23
+
24
+ var isHiddenMenus = Boolean((meta.hero || meta.features || meta.gapless) && mode === 'site') || meta.sidemenu === false || undefined;
25
+ return /*#__PURE__*/React.createElement("div", {
26
+ className: "__dumi-default-menu",
27
+ "data-mode": mode,
28
+ "data-hidden": isHiddenMenus,
29
+ "data-mobile-show": !mobileMenuCollapsed || undefined
30
+ }, /*#__PURE__*/React.createElement("div", {
31
+ className: "__dumi-default-menu-inner"
32
+ }, /*#__PURE__*/React.createElement("div", {
33
+ className: "__dumi-default-menu-header"
34
+ }, /*#__PURE__*/React.createElement(Link, {
35
+ to: base,
36
+ className: "__dumi-default-menu-logo",
37
+ style: {
38
+ backgroundImage: logo && "url('".concat(logo, "')")
39
+ }
40
+ }), /*#__PURE__*/React.createElement("h1", null, title), /*#__PURE__*/React.createElement("p", null, description), /github\.com/.test(repoUrl) && mode === 'doc' && /*#__PURE__*/React.createElement("p", null, /*#__PURE__*/React.createElement("object", {
41
+ type: "image/svg+xml",
42
+ data: "https://img.shields.io/github/stars".concat(repoUrl.match(/((\/[^\/]+){2})$/)[1], "?style=social")
43
+ }))), /*#__PURE__*/React.createElement("div", {
44
+ className: "__dumi-default-menu-mobile-area"
45
+ }, !!navItems.length && /*#__PURE__*/React.createElement("ul", {
46
+ className: "__dumi-default-menu-nav-list"
47
+ }, navItems.map(function (nav) {
48
+ var _nav$children;
49
+
50
+ var child = Boolean((_nav$children = nav.children) === null || _nav$children === void 0 ? void 0 : _nav$children.length) && /*#__PURE__*/React.createElement("ul", null, nav.children.map(function (item) {
51
+ return /*#__PURE__*/React.createElement("li", {
52
+ key: item.path || item.title
53
+ }, /*#__PURE__*/React.createElement(NavLink, {
54
+ to: item.path
55
+ }, item.title));
56
+ }));
57
+ return /*#__PURE__*/React.createElement("li", {
58
+ key: nav.path || nav.title
59
+ }, nav.path ? /*#__PURE__*/React.createElement(NavLink, {
60
+ to: nav.path
61
+ }, nav.title) : nav.title, child);
62
+ })), /*#__PURE__*/React.createElement(LocaleSelect, {
63
+ location: location
64
+ }), darkPrefix), /*#__PURE__*/React.createElement("ul", {
65
+ className: "__dumi-default-menu-list"
66
+ }, !isHiddenMenus && menu.map(function (item) {
67
+ var _meta$slugs;
68
+
69
+ // always use meta from routes to reduce menu data size
70
+ var hasSlugs = Boolean((_meta$slugs = meta.slugs) === null || _meta$slugs === void 0 ? void 0 : _meta$slugs.length);
71
+ var hasChildren = item.children && Boolean(item.children.length);
72
+ var show1LevelSlugs = meta.toc === 'menu' && !hasChildren && hasSlugs && item.path === location.pathname.replace(/([^^])\/$/, '$1');
73
+ var menuPaths = hasChildren ? item.children.map(function (i) {
74
+ return i.path;
75
+ }) : [item.path, // handle menu group which has no index route and no valid children
76
+ location.pathname.startsWith("".concat(item.path, "/")) && meta.title === item.title ? location.pathname : null];
77
+ return /*#__PURE__*/React.createElement("li", {
78
+ key: item.path || item.title
79
+ }, /*#__PURE__*/React.createElement(NavLink, {
80
+ to: item.path,
81
+ isActive: function isActive() {
82
+ return menuPaths.includes(location.pathname);
83
+ }
84
+ }, item.title), Boolean(item.children && item.children.length) && /*#__PURE__*/React.createElement("ul", null, item.children.map(function (child) {
85
+ return /*#__PURE__*/React.createElement("li", {
86
+ key: child.path
87
+ }, /*#__PURE__*/React.createElement(NavLink, {
88
+ to: child.path,
89
+ exact: true
90
+ }, /*#__PURE__*/React.createElement("span", null, child.title)), Boolean(meta.toc === 'menu' && typeof window !== 'undefined' && child.path === location.pathname && hasSlugs) && /*#__PURE__*/React.createElement(SlugList, {
91
+ slugs: meta.slugs
92
+ }));
93
+ })), show1LevelSlugs && /*#__PURE__*/React.createElement(SlugList, {
94
+ slugs: meta.slugs
95
+ }));
96
+ }))));
97
+ };
98
+
99
+ export default SideMenu;
@@ -0,0 +1,379 @@
1
+ @import (reference) '../style/variables.less';
2
+
3
+ .@{prefix}-menu {
4
+ position: fixed;
5
+ z-index: 100;
6
+ top: 0;
7
+ left: 0;
8
+ bottom: 0;
9
+ width: @s-menu-width;
10
+ background-color: #f2f5fa;
11
+ box-sizing: border-box;
12
+ transition: left 0.3s;
13
+
14
+ &[data-hidden] {
15
+ display: none;
16
+ }
17
+
18
+ @media @mobile {
19
+ left: -@s-menu-mobile-width;
20
+ top: @s-mobile-nav-height;
21
+ display: block !important;
22
+ width: @s-menu-mobile-width;
23
+ background-color: #fff;
24
+
25
+ &[data-mobile-show] {
26
+ left: 0;
27
+ }
28
+
29
+ [data-prefers-color=dark] & {
30
+ background-color: @c-bg-dark;
31
+ }
32
+ }
33
+
34
+ // shadow
35
+ &::after {
36
+ content: '';
37
+ position: absolute;
38
+ top: 0;
39
+ right: 0;
40
+ bottom: 0;
41
+ display: block;
42
+ width: 20px;
43
+ background: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.03));
44
+ pointer-events: none;
45
+
46
+ // use border on mobile devices
47
+ @media @mobile {
48
+ width: 1px;
49
+ background: @c-border;
50
+ }
51
+ }
52
+
53
+ &-header {
54
+ position: relative;
55
+ padding-top: 40px;
56
+ text-align: center;
57
+ border-bottom: 1px solid @c-border;
58
+
59
+ [data-prefers-color=dark] & {
60
+ border-color: @c-border-dark;
61
+ }
62
+
63
+ @media @mobile {
64
+ display: none;
65
+ }
66
+
67
+ .@{prefix}-menu-logo {
68
+ display: inline-block;
69
+ width: 66px;
70
+ height: 65px;
71
+ background: url(@img-logo) no-repeat 0 / contain;
72
+ }
73
+
74
+ h1 {
75
+ margin: 10px 0 0;
76
+ color: @c-heading;
77
+ font-weight: 500;
78
+ line-height: 1.40625;
79
+ }
80
+
81
+ p {
82
+ margin: 0 0 5px;
83
+ color: lighten(@c-secondary, 10%);
84
+
85
+ // badges
86
+ > object[data^='https://img.shields.io'] {
87
+ max-height: 20px;
88
+ }
89
+
90
+ + p {
91
+ margin-bottom: 10px;
92
+ }
93
+ }
94
+ }
95
+
96
+ &-doc-locale {
97
+ padding: 16px 0;
98
+ text-align: center;
99
+ border-bottom: 1px solid @c-border;
100
+ display: flex;
101
+ justify-content: space-evenly;
102
+
103
+ [data-prefers-color=dark] & {
104
+ border-color: @c-border-dark;
105
+ }
106
+
107
+ [data-mode=doc][data-mobile-show=true] & {
108
+ display: grid;
109
+ }
110
+
111
+ &:empty {
112
+ display: none;
113
+ }
114
+ }
115
+
116
+ &-inner {
117
+ width: 100%;
118
+ height: 100%;
119
+ overflow: auto;
120
+ overscroll-behavior: contain;
121
+
122
+ [data-prefers-color=dark] & {
123
+ background-color: #262626;
124
+ }
125
+
126
+ // common list styles
127
+ ul {
128
+ list-style: none;
129
+ margin: 0;
130
+ padding: 0;
131
+ font-size: 16px;
132
+
133
+ li {
134
+ color: @c-text;
135
+ a,
136
+ > span {
137
+ position: relative;
138
+ display: block;
139
+ padding-right: 24px;
140
+ color: @c-heading;
141
+ line-height: 2.4;
142
+ text-decoration: none;
143
+ outline: none;
144
+ transition: color 0.3s, background 0.3s;
145
+
146
+ [data-prefers-color=dark] & {
147
+ color: @c-heading-dark;
148
+ }
149
+
150
+ span {
151
+ display: block;
152
+ overflow: hidden;
153
+ white-space: nowrap;
154
+ text-overflow: ellipsis;
155
+ }
156
+
157
+ &:hover,
158
+ &.active {
159
+ color: @c-primary;
160
+
161
+ [data-prefers-color=dark] & {
162
+ color: @c-primary-dark;
163
+ }
164
+ }
165
+
166
+ &::before {
167
+ content: '';
168
+ position: absolute;
169
+ top: 50%;
170
+ left: -10px;
171
+ margin-top: -2.5px;
172
+ display: inline-block;
173
+ width: 5px;
174
+ height: 5px;
175
+ background-color: @c-primary;
176
+ border-radius: 50%;
177
+ opacity: 0;
178
+ transition: transform 0.2s, opacity 0.2s;
179
+ transform: scale(0) translateX(-10px);
180
+ }
181
+ }
182
+
183
+ &.active a,
184
+ a.active {
185
+ &::before {
186
+ opacity: 1;
187
+ transform: scale(1) translateX(0);
188
+ }
189
+ }
190
+
191
+ // level larger, offset larger, font size smaller
192
+ ul {
193
+ font-size: 0.9em;
194
+ padding-left: 1em;
195
+ }
196
+ }
197
+ }
198
+
199
+ // 1-level list styles
200
+ > ul {
201
+ > li > a {
202
+ line-height: 2.875;
203
+
204
+ &:not([href]) {
205
+ padding-top: 24px;
206
+ line-height: 1;
207
+ font-weight: 500;
208
+ color: @c-heading !important;
209
+ background: transparent !important;
210
+ cursor: default;
211
+
212
+ [data-prefers-color=dark] & {
213
+ color: @c-heading-dark !important;
214
+ }
215
+ }
216
+ }
217
+
218
+ > li:first-child > a:not([href]) {
219
+ padding-top: 0;
220
+ }
221
+ }
222
+
223
+ // n-level list styles
224
+ > ul ul {
225
+ a {
226
+ color: @c-secondary;
227
+ [data-prefers-color=dark] & {
228
+ color: @c-secondary-dark;
229
+ }
230
+ &.active {
231
+ color: @c-primary;
232
+ [data-prefers-color=dark] & {
233
+ color: @c-primary-dark;
234
+ }
235
+ }
236
+ }
237
+ }
238
+
239
+ .@{prefix}-menu-mobile-area {
240
+ display: none;
241
+ padding-bottom: 16px;
242
+ margin-bottom: 16px;
243
+ text-align: center;
244
+ border-bottom: 1px solid @c-border;
245
+
246
+ [data-prefers-color=dark] & {
247
+ border-color: @c-border-dark;
248
+ }
249
+
250
+ @media @mobile {
251
+ display: block;
252
+ }
253
+ }
254
+
255
+ // mobile nav list
256
+ .@{prefix}-menu-nav-list {
257
+ padding: 16px 0 0 0;
258
+
259
+ > li,
260
+ > li > a {
261
+ padding-right: 0;
262
+ line-height: 2.4;
263
+
264
+ ul {
265
+ padding-left: 0;
266
+
267
+ a {
268
+ padding-right: 0;
269
+ font-size: 90%;
270
+ }
271
+ }
272
+ }
273
+ }
274
+
275
+ // menu list
276
+ .@{prefix}-menu-list {
277
+ padding: 8px 0;
278
+ margin-bottom: 40px;
279
+
280
+ > li > a {
281
+ @c-active-bg: #e8ecf4;
282
+
283
+ padding-left: 28px;
284
+
285
+ &.active {
286
+ background: linear-gradient(to left, #e8ecf4, rgba(232, 236, 244, 0));
287
+
288
+ [data-prefers-color=dark] & {
289
+ background: linear-gradient(to left, #3d3d3e, rgba(255, 255, 255, 0.06));
290
+ }
291
+ }
292
+
293
+ ~ ul {
294
+ margin-top: 8px;
295
+ margin-left: 28px;
296
+ }
297
+
298
+ @media @mobile {
299
+ padding-left: 16px;
300
+
301
+ ~ ul {
302
+ margin-left: 16px;
303
+ }
304
+ }
305
+ }
306
+ }
307
+ }
308
+
309
+ &[data-mode='site'] {
310
+ &::after {
311
+ width: 1px;
312
+ background: @c-border;
313
+
314
+ [data-prefers-color=dark] & {
315
+ background: @c-border-dark;
316
+ }
317
+ }
318
+
319
+ .@{prefix}-menu-list {
320
+ padding: 0;
321
+
322
+ > li > a {
323
+ position: relative;
324
+
325
+ &::after {
326
+ content: '';
327
+ position: absolute;
328
+ top: 0;
329
+ bottom: 0;
330
+ right: 0;
331
+ display: block;
332
+ width: 3px;
333
+ background-color: @c-primary;
334
+ visibility: hidden;
335
+ opacity: 0;
336
+ transition: all 0.3s;
337
+ border-radius: 1px;
338
+ }
339
+
340
+ &.active {
341
+ z-index: 1;
342
+ background: linear-gradient(to left, #f8faff, rgba(248, 250, 255, 0));
343
+
344
+ [data-prefers-color=dark] & {
345
+ background: linear-gradient(to left, #3d3d3e, rgba(255, 255, 255, 0.06));
346
+ }
347
+ &::after {
348
+ opacity: 1;
349
+ visibility: visible;
350
+ }
351
+ }
352
+ }
353
+ }
354
+
355
+ @media @desktop {
356
+ top: @s-nav-height;
357
+ width: @s-site-menu-width;
358
+ padding-top: 50px;
359
+ background: transparent;
360
+
361
+ [data-prefers-color=dark] & {
362
+ background: @c-light-bg-dark;
363
+ }
364
+
365
+ .@{prefix}-menu-nav,
366
+ .@{prefix}-menu-header {
367
+ display: none;
368
+ }
369
+
370
+ .@{prefix}-menu-list > li > a {
371
+ padding-left: 58px;
372
+
373
+ ~ ul {
374
+ margin-left: 58px;
375
+ }
376
+ }
377
+ }
378
+ }
379
+ }
@@ -0,0 +1,33 @@
1
+ var _excluded = ["slugs"];
2
+
3
+ function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
4
+
5
+ function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
6
+
7
+ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
8
+
9
+ import React from 'react';
10
+ import { AnchorLink } from 'dumi/theme';
11
+ import './SlugList.less';
12
+
13
+ var SlugsList = function SlugsList(_ref) {
14
+ var slugs = _ref.slugs,
15
+ props = _objectWithoutProperties(_ref, _excluded);
16
+
17
+ return /*#__PURE__*/React.createElement("ul", _extends({
18
+ role: "slug-list"
19
+ }, props), slugs.filter(function (_ref2) {
20
+ var depth = _ref2.depth;
21
+ return depth > 1 && depth < 4;
22
+ }).map(function (slug) {
23
+ return /*#__PURE__*/React.createElement("li", {
24
+ key: slug.heading,
25
+ title: slug.value,
26
+ "data-depth": slug.depth
27
+ }, /*#__PURE__*/React.createElement(AnchorLink, {
28
+ to: "#".concat(slug.heading)
29
+ }, /*#__PURE__*/React.createElement("span", null, slug.value)));
30
+ }));
31
+ };
32
+
33
+ export default SlugsList;
@@ -0,0 +1,18 @@
1
+ @import (reference) '../style/variables.less';
2
+
3
+ ul[role='slug-list'] {
4
+ &:empty {
5
+ margin: 0 !important;
6
+ padding: 0 !important;
7
+ }
8
+
9
+ li {
10
+ > a.active {
11
+ color: darken(@c-primary, 2%);
12
+ }
13
+
14
+ &[data-depth='3'] {
15
+ padding-left: 12px;
16
+ }
17
+ }
18
+ }