divhunt 2.0.6 → 2.0.10

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 (56) hide show
  1. package/addons/core/commands/back/functions/expose.js +14 -0
  2. package/addons/core/commands/back/functions/find.js +1 -1
  3. package/addons/core/commands/back/functions/grpc/client.js +1 -1
  4. package/addons/core/commands/back/functions/grpc/server.js +1 -1
  5. package/addons/core/commands/back/functions/hide.js +14 -0
  6. package/addons/core/commands/back/functions/http/server.js +1 -1
  7. package/addons/core/commands/back/items/{many.js → self/many.js} +2 -4
  8. package/addons/core/commands/back/items/{one.js → self/one.js} +2 -4
  9. package/addons/core/commands/back/items/{run.js → self/run.js} +3 -7
  10. package/addons/core/commands/core/functions/run.js +13 -0
  11. package/addons/core/commands/{back → core}/item/functions/run.js +1 -1
  12. package/addons/core/commands/core/load.js +25 -0
  13. package/addons/core/commands/front/directives/run.js +90 -0
  14. package/addons/core/commands/front/directives/submit.js +152 -0
  15. package/addons/core/commands/front/functions/api.js +30 -0
  16. package/addons/render/assets/back/functions/import.js +2 -2
  17. package/addons/render/pages/{front/js/#register → core}/addon.js +4 -0
  18. package/addons/render/pages/core/load.js +3 -0
  19. package/addons/render/pages/front/items/directives/change.js +38 -0
  20. package/examples/basic-front/back/assets.js +12 -0
  21. package/examples/basic-front/back/index.js +13 -0
  22. package/examples/basic-front/back/items/commands/html.js +14 -0
  23. package/examples/basic-front/back/items/commands/test.js +19 -0
  24. package/examples/basic-front/back/items/html/fonts.js +12 -0
  25. package/examples/basic-front/front/pages/home.js +16 -0
  26. package/examples/basic-front/front/styles/main.css +31 -0
  27. package/examples/basic-front/front/test.js +7 -0
  28. package/examples/basic-front/package.json +7 -0
  29. package/lib/src/divhunt.js +2 -0
  30. package/lib/src/mixins/form.js +67 -0
  31. package/package.json +5 -3
  32. package/test/front/test.js +54 -0
  33. package/test.js +53 -0
  34. package/addons/core/commands/LICENSE.txt +0 -40
  35. package/addons/core/commands/README.md +0 -294
  36. package/addons/core/commands/back/load.js +0 -18
  37. package/addons/core/commands/front/#register/addon.js +0 -9
  38. package/addons/core/commands/front/functions/find.js +0 -22
  39. package/addons/core/commands/front/item/functions/run.js +0 -96
  40. package/addons/core/commands/front/items/elements/table/table.css +0 -18
  41. package/addons/core/commands/front/items/elements/table/table.js +0 -254
  42. package/addons/render/pages/front/js/items/commands/change.js +0 -41
  43. package/addons/render/pages/front/js/items/shortcuts/home.js +0 -9
  44. package/addons/render/pages/front/js/items/shortcuts/login.js +0 -9
  45. /package/addons/core/commands/{back → core}/addon.js +0 -0
  46. /package/addons/render/pages/front/{js/events → events}/click.js +0 -0
  47. /package/addons/render/pages/front/{js/events → events}/load.js +0 -0
  48. /package/addons/render/pages/front/{js/functions → functions}/change.js +0 -0
  49. /package/addons/render/pages/front/{js/functions → functions}/match.js +0 -0
  50. /package/addons/render/pages/front/{js/functions → functions}/open.js +0 -0
  51. /package/addons/render/pages/front/{js/item → item}/catch/add.js +0 -0
  52. /package/addons/render/pages/front/{js/item → item}/catch/remove.js +0 -0
  53. /package/addons/render/pages/front/{js/item → item}/functions/enter.js +0 -0
  54. /package/addons/render/pages/front/{js/item → item}/functions/leave.js +0 -0
  55. /package/addons/render/pages/front/{js/item → item}/functions/render.js +0 -0
  56. /package/addons/render/pages/front/{css → styles}/page.css +0 -0
@@ -0,0 +1,14 @@
1
+ import commands from '#commands/core/addon.js';
2
+
3
+ commands.Fn('expose', function(id, endpoint = null)
4
+ {
5
+ const command = commands.ItemGet(id);
6
+
7
+ if(!command)
8
+ {
9
+ return;
10
+ }
11
+
12
+ command.Set('exposed', true);
13
+ command.Set('endpoint', endpoint || '/api/' + id.replace(/:/g, '/'));
14
+ });
@@ -1,4 +1,4 @@
1
- import commands from '../addon.js';
1
+ import commands from '#commands/core/addon.js';
2
2
 
3
3
  commands.Fn('find', function(method, pathname)
4
4
  {
@@ -1,4 +1,4 @@
1
- import commands from '../../addon.js';
1
+ import commands from '#commands/core/addon.js';
2
2
 
3
3
  commands.Fn('grpc.client', async function(host, port, metadata = {}, prefix = 'remote', bidirectional = false, callbacks = {})
4
4
  {
@@ -1,4 +1,4 @@
1
- import commands from '../../addon.js';
1
+ import commands from '#commands/core/addon.js';
2
2
 
3
3
  commands.Fn('grpc.server', async function(port = 50000, callbacks = {})
4
4
  {
@@ -0,0 +1,14 @@
1
+ import commands from '#commands/core/addon.js';
2
+
3
+ commands.Fn('hide', function(id)
4
+ {
5
+ const command = commands.ItemGet(id);
6
+
7
+ if(!command)
8
+ {
9
+ return;
10
+ }
11
+
12
+ command.Set('exposed', false);
13
+ command.Set('endpoint', null);
14
+ });
@@ -1,5 +1,5 @@
1
1
  import divhunt from '#framework/load.js';
2
- import commands from '../../addon.js';
2
+ import commands from '#commands/core/addon.js';
3
3
 
4
4
  commands.Fn('http.server', async function(port = 3000, callbacks = {})
5
5
  {
@@ -1,11 +1,9 @@
1
1
  import divhunt from '#framework/load.js';
2
- import commands from '../addon.js';
2
+ import commands from '#commands/core/addon.js';
3
3
 
4
4
  commands.Item({
5
5
  id: 'commands:get:many',
6
- exposed: true,
7
6
  method: 'GET',
8
- endpoint: '/api/commands',
9
7
  type: 'JSON',
10
8
  out: {
11
9
  commands: {
@@ -44,7 +42,7 @@ commands.Item({
44
42
  {
45
43
  const list = [];
46
44
 
47
- Object.values(commands.Items()).forEach((item) =>
45
+ Object.values(commands.Items()).forEach((item) =>
48
46
  {
49
47
  if(!item.Get('exposed'))
50
48
  {
@@ -1,11 +1,9 @@
1
1
  import divhunt from '#framework/load.js';
2
- import commands from '../addon.js';
2
+ import commands from '#commands/core/addon.js';
3
3
 
4
4
  commands.Item({
5
5
  id: 'commands:get:one',
6
- exposed: true,
7
6
  method: 'GET',
8
- endpoint: '/api/commands/:id',
9
7
  type: 'JSON',
10
8
  in: {
11
9
  id: ['string']
@@ -73,4 +71,4 @@ commands.Item({
73
71
  }
74
72
  });
75
73
  }
76
- });
74
+ });
@@ -1,10 +1,8 @@
1
- import commands from '../addon.js';
1
+ import commands from '#commands/core/addon.js';
2
2
 
3
3
  commands.Item({
4
4
  id: 'commands:run',
5
- exposed: true,
6
5
  method: 'POST',
7
- endpoint: '/api/commands/run',
8
6
  type: 'JSON',
9
7
  in: {
10
8
  id: ['string', null, true],
@@ -17,8 +15,6 @@ commands.Item({
17
15
  },
18
16
  callback: async function(properties, resolve)
19
17
  {
20
- console.log(properties.id);
21
-
22
18
  const command = commands.ItemGet(properties.id);
23
19
 
24
20
  if(!command)
@@ -31,7 +27,7 @@ commands.Item({
31
27
  return resolve(null, 'Command is not exposed.', 403);
32
28
  }
33
29
 
34
- try
30
+ try
35
31
  {
36
32
  const result = await commands.Item(properties.id).Fn('run', (properties.data || {}));
37
33
 
@@ -46,4 +42,4 @@ commands.Item({
46
42
  resolve(null, error.message, typeof error.code === 'number' ? error.code : 500);
47
43
  }
48
44
  }
49
- });
45
+ });
@@ -0,0 +1,13 @@
1
+ import commands from '#commands/core/addon.js';
2
+
3
+ commands.Fn('run', async function(id, data = {})
4
+ {
5
+ const command = commands.ItemGet(id);
6
+
7
+ if(!command)
8
+ {
9
+ throw new Error(`Command '${id}' not found.`);
10
+ }
11
+
12
+ return await command.Fn('run', data);
13
+ });
@@ -1,5 +1,5 @@
1
1
  import divhunt from '#framework/load.js';
2
- import commands from '../../addon.js';
2
+ import commands from '#commands/core/addon.js';
3
3
 
4
4
  commands.Fn('item.run', function(item, properties = {}, onChunk = null, context = {})
5
5
  {
@@ -0,0 +1,25 @@
1
+ import commands from '#commands/core/addon.js';
2
+
3
+ import '#commands/core/item/functions/run.js';
4
+ import '#commands/core/functions/run.js';
5
+
6
+ /* gRPC */
7
+ import '#commands/back/functions/grpc/server.js';
8
+ import '#commands/back/functions/grpc/client.js';
9
+
10
+ /* HTTP */
11
+ import '#commands/back/functions/http/server.js';
12
+
13
+ /* Find */
14
+ import '#commands/back/functions/find.js';
15
+
16
+ /* Expose */
17
+ import '#commands/back/functions/expose.js';
18
+ import '#commands/back/functions/hide.js';
19
+
20
+ /* Items */
21
+ import '#commands/back/items/self/one.js';
22
+ import '#commands/back/items/self/many.js';
23
+ import '#commands/back/items/self/run.js';
24
+
25
+ export default commands;
@@ -0,0 +1,90 @@
1
+ directives.ItemAdd({
2
+ id: 'dh-command',
3
+ icon: 'terminal',
4
+ name: 'Command',
5
+ description: 'Execute a command instantly on render',
6
+ category: 'data',
7
+ trigger: 'node',
8
+ order: 664,
9
+ strict: false,
10
+ tag: 'dh-command',
11
+ attributes: {
12
+ 'command': ['string', null, true],
13
+ 'bind': ['string', 'command'],
14
+ '_success': ['function'],
15
+ '_error': ['function'],
16
+ 'data': ['object', {}],
17
+ 'api': ['boolean', false]
18
+ },
19
+ code: function(data, item, compile, node, identifier)
20
+ {
21
+ if(node.tagName.toLowerCase() !== 'dh-command')
22
+ {
23
+ return;
24
+ }
25
+
26
+ const config = {};
27
+ const methods = {};
28
+
29
+ methods.init = () =>
30
+ {
31
+ methods.config();
32
+
33
+ if(compile.data[config.bind] !== undefined)
34
+ {
35
+ return;
36
+ }
37
+
38
+ compile.data[config.bind] = null;
39
+ methods.run();
40
+ };
41
+
42
+ methods.config = () =>
43
+ {
44
+ config.command = data['command'].value;
45
+ config.bind = data['bind'].value;
46
+ config.onSuccess = data['_success'].value;
47
+ config.onError = data['_error'].value;
48
+ config.data = data['data'].value;
49
+ config.api = data['api'].value;
50
+ };
51
+
52
+ methods.run = async () =>
53
+ {
54
+ const state = {
55
+ response: null,
56
+ error: null,
57
+ loading: true
58
+ };
59
+
60
+ try
61
+ {
62
+ const result = config.api
63
+ ? await commands.Fn('api', config.command, config.data)
64
+ : await commands.Fn('run', config.command, config.data);
65
+
66
+ state.response = result;
67
+ state.error = null;
68
+ state.loading = false;
69
+
70
+ config.onSuccess && config.onSuccess(state);
71
+ }
72
+ catch(error)
73
+ {
74
+ state.response = null;
75
+ state.error = error.message;
76
+ state.loading = false;
77
+
78
+ config.onError && config.onError(state);
79
+ }
80
+ finally
81
+ {
82
+
83
+ compile.data[config.bind] = state;
84
+ compile.data.Update();
85
+ }
86
+ };
87
+
88
+ methods.init();
89
+ }
90
+ });
@@ -0,0 +1,152 @@
1
+ directives.ItemAdd({
2
+ id: 'dh-command-submit',
3
+ icon: 'terminal',
4
+ name: 'Command Submit',
5
+ description: 'Submit form data to a command via commands.Fn',
6
+ category: 'data',
7
+ trigger: 'node',
8
+ order: 665,
9
+ strict: false,
10
+ tag: 'dh-command-submit',
11
+ attributes: {
12
+ 'command': ['string', null, true],
13
+ 'bind': ['string', 'command'],
14
+ '_success': ['function'],
15
+ '_error': ['function'],
16
+ 'reset': ['boolean', false],
17
+ 'stop': ['boolean', false],
18
+ 'data': ['object', {}],
19
+ 'api': ['boolean', false]
20
+ },
21
+ code: function(data, item, compile, node, identifier)
22
+ {
23
+ const config = {};
24
+ const methods = {};
25
+
26
+ methods.init = () =>
27
+ {
28
+ methods.config();
29
+ methods.state();
30
+ methods.element();
31
+ methods.handler();
32
+ };
33
+
34
+ methods.state = () =>
35
+ {
36
+ if(!compile.data[config.bind])
37
+ {
38
+ compile.data[config.bind] = {
39
+ response: null,
40
+ error: null,
41
+ loading: false
42
+ };
43
+ }
44
+ };
45
+
46
+ methods.config = () =>
47
+ {
48
+ config.command = data['command'].value;
49
+ config.bind = data['bind'].value;
50
+ config.onSuccess = data['_success'].value;
51
+ config.onError = data['_error'].value;
52
+ config.reset = data['reset'].value;
53
+ config.stop = data['stop'].value;
54
+ config.data = data['data'].value;
55
+ config.api = data['api'].value;
56
+ };
57
+
58
+ methods.element = () =>
59
+ {
60
+ config.form = document.createElement('form');
61
+ config.form.setAttribute('autocomplete', 'off');
62
+
63
+ while(node.firstChild)
64
+ {
65
+ config.form.appendChild(node.firstChild);
66
+ }
67
+
68
+ node.appendChild(config.form);
69
+ divhunt.FormSet(config.form, config.data);
70
+ };
71
+
72
+ methods.handler = () =>
73
+ {
74
+ config.form.dhCommandSubmit = async (event) =>
75
+ {
76
+ event.preventDefault();
77
+
78
+ if(config.stop)
79
+ {
80
+ event.stopPropagation();
81
+ }
82
+
83
+ await methods.submit();
84
+ };
85
+ };
86
+
87
+ methods.submit = async () =>
88
+ {
89
+ compile.data[config.bind];
90
+
91
+ if(compile.data[config.bind].loading)
92
+ {
93
+ return;
94
+ }
95
+
96
+ compile.data[config.bind].loading = true;
97
+ compile.data[config.bind].error = null;
98
+
99
+ console.log(compile.data[config.bind]);
100
+
101
+ compile.data.Update();
102
+
103
+ try
104
+ {
105
+ const formData = divhunt.FormGet(config.form);
106
+ const result = config.api
107
+ ? await commands.Fn('api', config.command, formData)
108
+ : await commands.Fn('run', config.command, formData);
109
+
110
+ compile.data[config.bind].response = result;
111
+ compile.data[config.bind].error = null;
112
+ compile.data[config.bind].loading = false;
113
+
114
+ config.reset && config.form.reset();
115
+ config.onSuccess && config.onSuccess(compile.data[config.bind]);
116
+ }
117
+ catch(error)
118
+ {
119
+ compile.data[config.bind].response = null;
120
+ compile.data[config.bind].error = error.message;
121
+ compile.data[config.bind].loading = false;
122
+
123
+ config.onError && config.onError(compile.data[config.bind]);
124
+ }
125
+ finally
126
+ {
127
+ compile.data.Update();
128
+ }
129
+ };
130
+
131
+ methods.init();
132
+ }
133
+ });
134
+
135
+ divhunt.AddonReady('directives', function()
136
+ {
137
+ document.addEventListener('submit', function(event)
138
+ {
139
+ let node = event.target;
140
+
141
+ while(node && node !== document)
142
+ {
143
+ if('dhCommandSubmit' in node)
144
+ {
145
+ node.dhCommandSubmit(event);
146
+ break;
147
+ }
148
+
149
+ node = node.parentNode;
150
+ }
151
+ });
152
+ });
@@ -0,0 +1,30 @@
1
+ commands.Fn('api', async function(id, data = {})
2
+ {
3
+ try
4
+ {
5
+ const response = await fetch('/api/commands/run', {
6
+ method: 'POST',
7
+ headers: {
8
+ 'Content-Type': 'application/json'
9
+ },
10
+ body: JSON.stringify({ id, data })
11
+ });
12
+
13
+ const result = await response.json();
14
+
15
+ if(result.code !== 200)
16
+ {
17
+ throw result.data;
18
+ }
19
+
20
+ return { time: result.time, ...result.data };
21
+ }
22
+ catch(error)
23
+ {
24
+ return {
25
+ data: null,
26
+ message: error.message,
27
+ code: 500
28
+ };
29
+ }
30
+ });
@@ -8,7 +8,7 @@ const root = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..',
8
8
  const map =
9
9
  {
10
10
  framework: { js: 'lib', ignore: ['lib/load.js'] },
11
- commands: { js: 'addons/core/commands/front', css: 'addons/core/commands/front' },
11
+ commands: { js: 'addons/core/commands', css: 'addons/core/commands/front', ignore: ['addons/core/commands/back'] },
12
12
  database: { js: 'addons/core/database/front' },
13
13
  actions: { js: 'addons/modules/actions/front' },
14
14
  bugs: { js: 'addons/modules/bugs/front' },
@@ -18,7 +18,7 @@ const map =
18
18
  sources: { js: 'addons/modules/sources/front' },
19
19
  directives: { js: 'addons/render/directives/front' },
20
20
  transforms: { js: 'addons/render/transforms/front' },
21
- pages: { js: 'addons/render/pages/front', css: 'addons/render/pages/front' },
21
+ pages: { js: 'addons/render/pages', css: 'addons/render/pages/front' },
22
22
  elements: { js: 'addons/render/elements/front', css: 'addons/render/elements/front' },
23
23
  float: { js: 'addons/float', css: 'addons/float' }
24
24
  };
@@ -1,3 +1,5 @@
1
+ import divhunt from '#framework/load.js';
2
+
1
3
  const pages = divhunt.Addon('pages', (addon) =>
2
4
  {
3
5
  addon.Field('id', ['string']);
@@ -27,3 +29,5 @@ const pages = divhunt.Addon('pages', (addon) =>
27
29
  addon.Field('element', ['object']);
28
30
  addon.Field('404', ['boolean', false]);
29
31
  });
32
+
33
+ export default pages;
@@ -0,0 +1,3 @@
1
+ import pages from './addon.js';
2
+
3
+ export default pages;
@@ -0,0 +1,38 @@
1
+ directives.ItemAdd({
2
+ id: 'dh-page',
3
+ icon: 'file',
4
+ name: 'Page',
5
+ description: 'Navigate to a page on render',
6
+ category: 'navigation',
7
+ trigger: 'node',
8
+ order: 666,
9
+ strict: false,
10
+ tag: 'dh-page',
11
+ attributes: {
12
+ 'route': ['string', null, true],
13
+ 'parameters': ['object', {}],
14
+ 'history': ['boolean', true],
15
+ 'timeout': ['number', 0]
16
+ },
17
+ code: function(data, item, compile, node, identifier)
18
+ {
19
+ const route = data['route'].value;
20
+ const parameters = data['parameters'].value;
21
+ const push = data['history'].value;
22
+ const timeout = data['timeout'].value;
23
+
24
+ const change = () =>
25
+ {
26
+ pages.Fn('change', route, parameters, { path: true, push });
27
+ };
28
+
29
+ if(timeout > 0)
30
+ {
31
+ setTimeout(change, timeout);
32
+ }
33
+ else
34
+ {
35
+ change();
36
+ }
37
+ }
38
+ });
@@ -0,0 +1,12 @@
1
+ import assets from 'divhunt/assets';
2
+ import 'divhunt/pages';
3
+
4
+ import { dirname, resolve } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
8
+
9
+ assets.Fn('import', ['framework', 'commands', 'pages']);
10
+
11
+ assets.Item({ type: 'js', order: 10, path: resolve(root, 'front') });
12
+ assets.Item({ type: 'css', order: 10, path: resolve(root, 'front') });
@@ -0,0 +1,13 @@
1
+ import './assets.js';
2
+ import './items/html/fonts.js';
3
+ import './items/commands/html.js';
4
+ import './items/commands/test.js';
5
+
6
+ import commands from 'divhunt/commands';
7
+
8
+ commands.Fn('http.server', 3000, {
9
+ onStart: () =>
10
+ {
11
+ console.log('basic-front running on http://localhost:3000');
12
+ }
13
+ });
@@ -0,0 +1,14 @@
1
+ import commands from 'divhunt/commands';
2
+ import html from 'divhunt/html';
3
+
4
+ commands.Item({
5
+ id: 'html',
6
+ exposed: true,
7
+ method: 'GET',
8
+ endpoint: '*',
9
+ type: 'HTML',
10
+ callback: async function(properties, resolve)
11
+ {
12
+ resolve(html.Fn('render'));
13
+ }
14
+ });
@@ -0,0 +1,19 @@
1
+ import commands from 'divhunt/commands';
2
+
3
+ commands.Item({
4
+ id: 'test',
5
+ exposed: true,
6
+ method: 'POST',
7
+ endpoint: '/api/test',
8
+ type: 'JSON',
9
+ in: {
10
+ name: ['string', 'World']
11
+ },
12
+ out: {
13
+ message: ['string']
14
+ },
15
+ callback: async function(properties, resolve)
16
+ {
17
+ resolve({ message: 'Hello, ' + properties.name + '!' });
18
+ }
19
+ });
@@ -0,0 +1,12 @@
1
+ import html from 'divhunt/html';
2
+
3
+ html.Item({
4
+ id: 'fonts',
5
+ tag: 'link',
6
+ position: 'head',
7
+ order: 85,
8
+ attributes: {
9
+ rel: 'stylesheet',
10
+ href: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap'
11
+ }
12
+ });
@@ -0,0 +1,16 @@
1
+ pages.Item({
2
+ id: 'home',
3
+ route: '/',
4
+ title: 'Divhunt Framework',
5
+ areas: {
6
+ main: function()
7
+ {
8
+ return `
9
+ <div class="hero">
10
+ <h1>Hello from Divhunt Framework</h1>
11
+ <p>A minimal full-stack JavaScript framework.</p>
12
+ </div>
13
+ `;
14
+ }
15
+ }
16
+ });
@@ -0,0 +1,31 @@
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
9
+ background: #0f0f0f;
10
+ color: #e0e0e0;
11
+ min-height: 100vh;
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ }
16
+
17
+ .hero {
18
+ text-align: center;
19
+ }
20
+
21
+ .hero h1 {
22
+ font-size: 48px;
23
+ font-weight: 600;
24
+ letter-spacing: -1px;
25
+ margin-bottom: 16px;
26
+ }
27
+
28
+ .hero p {
29
+ font-size: 16px;
30
+ color: #888;
31
+ }
@@ -0,0 +1,7 @@
1
+ const commands = divhunt.Addon('commands');
2
+
3
+ (async () =>
4
+ {
5
+ const result = await commands.Fn('api', 'test', { name: 'Dejan' });
6
+ console.log('api result:', result);
7
+ })();
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "basic-front-example",
3
+ "type": "module",
4
+ "dependencies": {
5
+ "divhunt": "^2.0.6"
6
+ }
7
+ }