@saltcorn/markup 0.6.1-beta.0 → 0.6.1
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.
- package/builder.js +28 -1
- package/emergency_layout.js +22 -0
- package/form.js +92 -1
- package/helpers.js +42 -0
- package/index.js +64 -4
- package/layout.js +46 -7
- package/layout.test.js +1 -1
- package/layout_utils.js +97 -0
- package/mktag.js +41 -0
- package/package.json +1 -1
- package/table.js +22 -0
- package/tabs.js +13 -0
- package/tags.js +28 -0
package/builder.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @category saltcorn-markup
|
|
3
|
+
* @module builder
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
const {
|
|
2
7
|
p,
|
|
3
8
|
div,
|
|
@@ -16,14 +21,36 @@ const {
|
|
|
16
21
|
} = require("./tags");
|
|
17
22
|
const { contract, is } = require("contractis");
|
|
18
23
|
|
|
24
|
+
/**
|
|
25
|
+
* @param {object} rec
|
|
26
|
+
* @param {object} csrf
|
|
27
|
+
* @returns {object}
|
|
28
|
+
*/
|
|
19
29
|
const addCsrf = (rec, csrf) => {
|
|
20
30
|
rec.csrfToken = csrf;
|
|
21
31
|
return rec;
|
|
22
32
|
};
|
|
23
33
|
|
|
34
|
+
/**
|
|
35
|
+
* @param {object} x
|
|
36
|
+
* @returns {string}
|
|
37
|
+
*/
|
|
24
38
|
const encode = (x) => encodeURIComponent(JSON.stringify(x));
|
|
25
39
|
|
|
26
|
-
module.exports =
|
|
40
|
+
module.exports =
|
|
41
|
+
/**
|
|
42
|
+
* @param {object} opts
|
|
43
|
+
* @param {object} opts.options
|
|
44
|
+
* @param {object} opts.context
|
|
45
|
+
* @param {object} opts.action
|
|
46
|
+
* @param {string} opts.stepName
|
|
47
|
+
* @param {object} opts.layout
|
|
48
|
+
* @param {string} [opts.mode = "show"]
|
|
49
|
+
* @param {object} opts
|
|
50
|
+
* @param {object} csrfToken
|
|
51
|
+
* @returns {div}
|
|
52
|
+
*/
|
|
53
|
+
(
|
|
27
54
|
{ options, context, action, stepName, layout, mode = "show", version_tag },
|
|
28
55
|
csrfToken
|
|
29
56
|
) =>
|
package/emergency_layout.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @category saltcorn-markup
|
|
3
|
+
* @module emergency_layout
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
const {
|
|
2
7
|
ul,
|
|
3
8
|
li,
|
|
@@ -18,6 +23,12 @@ const renderLayout = require("./layout");
|
|
|
18
23
|
const { renderForm, link } = require(".");
|
|
19
24
|
const { navbar, alert } = require("./layout_utils");
|
|
20
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} title
|
|
28
|
+
* @param {string|object} body
|
|
29
|
+
* @param {object[]} alerts
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
21
32
|
const renderBody = (title, body, alerts) =>
|
|
22
33
|
renderLayout({
|
|
23
34
|
blockDispatch: {},
|
|
@@ -26,6 +37,17 @@ const renderBody = (title, body, alerts) =>
|
|
|
26
37
|
alerts,
|
|
27
38
|
});
|
|
28
39
|
|
|
40
|
+
/**
|
|
41
|
+
* @param {object} opts
|
|
42
|
+
* @param {string} opts.title
|
|
43
|
+
* @param {object} opts.menu
|
|
44
|
+
* @param {object} opts.brand
|
|
45
|
+
* @param {object[]} opts.alerts
|
|
46
|
+
* @param {string} opts.currentUrl
|
|
47
|
+
* @param {string|object} opts.body
|
|
48
|
+
* @param {object[]} opts.headers
|
|
49
|
+
* @returns {string}
|
|
50
|
+
*/
|
|
29
51
|
const wrap = ({ title, menu, brand, alerts, currentUrl, body, headers }) =>
|
|
30
52
|
navbar(brand, menu, currentUrl) + renderBody(title, body, alerts);
|
|
31
53
|
|
package/form.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @category saltcorn-markup
|
|
3
|
+
* @module form
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
const {
|
|
2
7
|
p,
|
|
3
8
|
div,
|
|
@@ -13,7 +18,17 @@ const {
|
|
|
13
18
|
const { contract, is } = require("contractis");
|
|
14
19
|
const renderLayout = require("./layout");
|
|
15
20
|
const { isdef, select_options, search_bar } = require("./helpers");
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} s
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
16
26
|
const rmInitialDot = (s) => (s && s[0] === "." ? s.replace(".", "") : s);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {object} sIf
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
17
32
|
const mkShowIf = (sIf) =>
|
|
18
33
|
Object.entries(sIf)
|
|
19
34
|
.map(([target, value]) =>
|
|
@@ -33,7 +48,20 @@ const mkShowIf = (sIf) =>
|
|
|
33
48
|
)
|
|
34
49
|
.join(" && ");
|
|
35
50
|
|
|
51
|
+
/**
|
|
52
|
+
* @param {string} formStyle
|
|
53
|
+
* @returns {boolean}
|
|
54
|
+
*/
|
|
36
55
|
const isHoriz = (formStyle) => formStyle === "horiz";
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {object} hdr
|
|
59
|
+
* @param {object} inner
|
|
60
|
+
* @param {string} [error = ""]
|
|
61
|
+
* @param {string} fStyle
|
|
62
|
+
* @param {string} labelCols
|
|
63
|
+
* @returns {div}
|
|
64
|
+
*/
|
|
37
65
|
const formRowWrap = (hdr, inner, error = "", fStyle, labelCols) =>
|
|
38
66
|
div(
|
|
39
67
|
{
|
|
@@ -67,6 +95,12 @@ const formRowWrap = (hdr, inner, error = "", fStyle, labelCols) =>
|
|
|
67
95
|
]
|
|
68
96
|
);
|
|
69
97
|
|
|
98
|
+
/**
|
|
99
|
+
* @param {object[]} v
|
|
100
|
+
* @param {object[]} errors
|
|
101
|
+
* @param {string} [nameAdd = ""]
|
|
102
|
+
* @returns {function}
|
|
103
|
+
*/
|
|
70
104
|
const innerField = (v, errors, nameAdd = "") => (hdr) => {
|
|
71
105
|
const name = hdr.form_name + nameAdd;
|
|
72
106
|
const validClass = errors[name] ? "is-invalid" : "";
|
|
@@ -163,11 +197,26 @@ const innerField = (v, errors, nameAdd = "") => (hdr) => {
|
|
|
163
197
|
}
|
|
164
198
|
};
|
|
165
199
|
|
|
200
|
+
/**
|
|
201
|
+
* @param {object[]} v
|
|
202
|
+
* @param {object[]} errors
|
|
203
|
+
* @param {string} formStyle
|
|
204
|
+
* @param {object[]} labelCols
|
|
205
|
+
* @returns {function}
|
|
206
|
+
*/
|
|
166
207
|
const mkFormRow = (v, errors, formStyle, labelCols) => (hdr) =>
|
|
167
208
|
hdr.isRepeat
|
|
168
209
|
? mkFormRowForRepeat(v, errors, formStyle, labelCols, hdr)
|
|
169
210
|
: mkFormRowForField(v, errors, formStyle, labelCols)(hdr);
|
|
170
211
|
|
|
212
|
+
/**
|
|
213
|
+
* @param {object[]} v
|
|
214
|
+
* @param {object[]} errors
|
|
215
|
+
* @param {string} formStyle
|
|
216
|
+
* @param {object[]} labelCols
|
|
217
|
+
* @param {object} hdr
|
|
218
|
+
* @returns {div}
|
|
219
|
+
*/
|
|
171
220
|
const mkFormRowForRepeat = (v, errors, formStyle, labelCols, hdr) => {
|
|
172
221
|
const adder = a(
|
|
173
222
|
{
|
|
@@ -228,6 +277,13 @@ const mkFormRowForRepeat = (v, errors, formStyle, labelCols, hdr) => {
|
|
|
228
277
|
}
|
|
229
278
|
};
|
|
230
279
|
|
|
280
|
+
/**
|
|
281
|
+
* @param {object} hdr
|
|
282
|
+
* @param {string} name
|
|
283
|
+
* @param {object} v
|
|
284
|
+
* @param {string} extracls
|
|
285
|
+
* @returns {*}
|
|
286
|
+
*/
|
|
231
287
|
const displayEdit = (hdr, name, v, extracls) => {
|
|
232
288
|
var fieldview;
|
|
233
289
|
var attributes = hdr.attributes;
|
|
@@ -259,6 +315,14 @@ const displayEdit = (hdr, name, v, extracls) => {
|
|
|
259
315
|
else return fieldview.run(v, undefined, attributes);
|
|
260
316
|
};
|
|
261
317
|
|
|
318
|
+
/**
|
|
319
|
+
* @param {object[]} v
|
|
320
|
+
* @param {object[]} errors
|
|
321
|
+
* @param {string} formStyle
|
|
322
|
+
* @param {string} labelCols
|
|
323
|
+
* @param {string} [nameAdd = ""]
|
|
324
|
+
* @returns {function}
|
|
325
|
+
*/
|
|
262
326
|
const mkFormRowForField = (v, errors, formStyle, labelCols, nameAdd = "") => (
|
|
263
327
|
hdr
|
|
264
328
|
) => {
|
|
@@ -278,6 +342,10 @@ const mkFormRowForField = (v, errors, formStyle, labelCols, nameAdd = "") => (
|
|
|
278
342
|
);
|
|
279
343
|
};
|
|
280
344
|
|
|
345
|
+
/**
|
|
346
|
+
* @param {object} form
|
|
347
|
+
* @returns {string}
|
|
348
|
+
*/
|
|
281
349
|
const renderFormLayout = (form) => {
|
|
282
350
|
const blockDispatch = {
|
|
283
351
|
field(segment) {
|
|
@@ -358,6 +426,11 @@ const renderFormLayout = (form) => {
|
|
|
358
426
|
return renderLayout({ blockDispatch, layout: form.layout });
|
|
359
427
|
};
|
|
360
428
|
|
|
429
|
+
/**
|
|
430
|
+
* @param {string|object} form
|
|
431
|
+
* @param {string|false} csrfToken0
|
|
432
|
+
* @returns {string}
|
|
433
|
+
*/
|
|
361
434
|
const renderForm = (form, csrfToken0) => {
|
|
362
435
|
const csrfToken =
|
|
363
436
|
csrfToken0 === false || csrfToken0 === ""
|
|
@@ -401,6 +474,11 @@ const renderForm = (form, csrfToken0) => {
|
|
|
401
474
|
else return mkForm(form, csrfToken, form.errors);
|
|
402
475
|
};
|
|
403
476
|
|
|
477
|
+
/**
|
|
478
|
+
* @param {object} form
|
|
479
|
+
* @param {string} csrfToken
|
|
480
|
+
* @returns {string}
|
|
481
|
+
*/
|
|
404
482
|
const mkFormWithLayout = (form, csrfToken) => {
|
|
405
483
|
const hasFile = form.fields.some((f) => f.input_type === "file");
|
|
406
484
|
const csrfField = `<input type="hidden" name="_csrf" value="${csrfToken}">`;
|
|
@@ -436,6 +514,11 @@ const mkFormWithLayout = (form, csrfToken) => {
|
|
|
436
514
|
"</form>"
|
|
437
515
|
);
|
|
438
516
|
};
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* @param {object[]} additionalButtons
|
|
520
|
+
* @returns {string}
|
|
521
|
+
*/
|
|
439
522
|
const displayAdditionalButtons = (additionalButtons) =>
|
|
440
523
|
additionalButtons
|
|
441
524
|
.map(
|
|
@@ -445,6 +528,13 @@ const displayAdditionalButtons = (additionalButtons) =>
|
|
|
445
528
|
}>${btn.label}</button> `
|
|
446
529
|
)
|
|
447
530
|
.join("");
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* @param {object} form
|
|
534
|
+
* @param {string} csrfToken
|
|
535
|
+
* @param {object} [errors = {}]
|
|
536
|
+
* @returns {string}
|
|
537
|
+
*/
|
|
448
538
|
const mkForm = (form, csrfToken, errors = {}) => {
|
|
449
539
|
const hasFile = form.fields.some((f) => f.input_type === "file");
|
|
450
540
|
const csrfField =
|
|
@@ -503,7 +593,8 @@ const mkForm = (form, csrfToken, errors = {}) => {
|
|
|
503
593
|
return blurbp + top + csrfField + flds + fullFormError + bot + "</form>";
|
|
504
594
|
};
|
|
505
595
|
|
|
506
|
-
module.exports =
|
|
596
|
+
module.exports =
|
|
597
|
+
contract(
|
|
507
598
|
is.fun(
|
|
508
599
|
[is.or(is.str, is.class("Form")), is.maybe(is.or(is.str, is.eq(false)))],
|
|
509
600
|
is.str
|
package/helpers.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @category saltcorn-markup
|
|
3
|
+
* @module helpers
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
const {
|
|
2
7
|
a,
|
|
3
8
|
text,
|
|
@@ -10,8 +15,20 @@ const {
|
|
|
10
15
|
label,
|
|
11
16
|
} = require("./tags");
|
|
12
17
|
|
|
18
|
+
/**
|
|
19
|
+
* checks if x is defined
|
|
20
|
+
* @param {*} x
|
|
21
|
+
* @returns {boolean}
|
|
22
|
+
*/
|
|
13
23
|
const isdef = (x) => typeof x !== "undefined";
|
|
14
24
|
|
|
25
|
+
/**
|
|
26
|
+
* @param {object|string} v
|
|
27
|
+
* @param {object} hdr
|
|
28
|
+
* @param {boolean} force_required
|
|
29
|
+
* @param {string} neutral_label
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
15
32
|
const select_options = (v, hdr, force_required, neutral_label = "") => {
|
|
16
33
|
const options0 = hdr.options || [];
|
|
17
34
|
const options1 = force_required
|
|
@@ -38,6 +55,17 @@ const select_options = (v, hdr, force_required, neutral_label = "") => {
|
|
|
38
55
|
.join("");
|
|
39
56
|
};
|
|
40
57
|
|
|
58
|
+
/**
|
|
59
|
+
*
|
|
60
|
+
* @param {object} opts
|
|
61
|
+
* @param {string} opts.name
|
|
62
|
+
* @param {object} [opts.options]
|
|
63
|
+
* @param {string} opts.value
|
|
64
|
+
* @param {object} opts.inline
|
|
65
|
+
* @param {string} opts.form_name
|
|
66
|
+
* @param {...*} opts.rest
|
|
67
|
+
* @returns {string}
|
|
68
|
+
*/
|
|
41
69
|
const radio_group = ({ name, options, value, inline, form_name, ...rest }) =>
|
|
42
70
|
div(
|
|
43
71
|
(options || [])
|
|
@@ -65,6 +93,14 @@ const radio_group = ({ name, options, value, inline, form_name, ...rest }) =>
|
|
|
65
93
|
.join("")
|
|
66
94
|
);
|
|
67
95
|
|
|
96
|
+
/**
|
|
97
|
+
* @param {object} opts
|
|
98
|
+
* @param {number} opts.current_page
|
|
99
|
+
* @param {number} opts.pages
|
|
100
|
+
* @param {function} opts.get_page_link
|
|
101
|
+
* @param {boolean} opts.trailing_ellipsis
|
|
102
|
+
* @returns {string}
|
|
103
|
+
*/
|
|
68
104
|
const pagination = ({
|
|
69
105
|
current_page,
|
|
70
106
|
pages,
|
|
@@ -106,6 +142,12 @@ const pagination = ({
|
|
|
106
142
|
return ul({ class: "pagination" }, lis);
|
|
107
143
|
};
|
|
108
144
|
|
|
145
|
+
/**
|
|
146
|
+
* @param {string} name
|
|
147
|
+
* @param {object} v
|
|
148
|
+
* @param {object} param2
|
|
149
|
+
* @returns {string}
|
|
150
|
+
*/
|
|
109
151
|
const search_bar = (
|
|
110
152
|
name,
|
|
111
153
|
v,
|
package/index.js
CHANGED
|
@@ -1,11 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @category saltcorn-markup
|
|
3
|
+
* @module saltcorn-markup/index
|
|
4
|
+
*/
|
|
1
5
|
const renderForm = require("./form");
|
|
2
6
|
const renderBuilder = require("./builder");
|
|
3
7
|
const mkTable = require("./table");
|
|
4
8
|
const tabs = require("./tabs");
|
|
5
9
|
const { a, text, div, button, time } = require("./tags");
|
|
6
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} href
|
|
13
|
+
* @param {string} s
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
7
16
|
const link = (href, s) => a({ href: text(href) }, text(s));
|
|
8
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} href
|
|
20
|
+
* @param {string} s
|
|
21
|
+
* @param {string} csrfToken
|
|
22
|
+
* @param {object} opts
|
|
23
|
+
* @param {string} [opts.btnClass = "btn-primary"]
|
|
24
|
+
* @param {string} [opts.onClick]
|
|
25
|
+
* @param {string} [opts.small]
|
|
26
|
+
* @param {string} [opts.style]
|
|
27
|
+
* @param {*} opts.ajax
|
|
28
|
+
* @param {string} opts.reload_on_done
|
|
29
|
+
* @param {string} opts.reload_delay
|
|
30
|
+
* @param {string} [opts.klass = "btn-primary"]
|
|
31
|
+
* @param {string} [opts.formClass]
|
|
32
|
+
* @param {string} opts.spinner
|
|
33
|
+
* @param {object} opts.req
|
|
34
|
+
* @param {boolean} opts.confirm
|
|
35
|
+
* @param {string} opts.icon
|
|
36
|
+
* @returns {string}
|
|
37
|
+
*/
|
|
9
38
|
const post_btn = (
|
|
10
39
|
href,
|
|
11
40
|
s,
|
|
@@ -47,11 +76,12 @@ const post_btn = (
|
|
|
47
76
|
} class="${klass} btn ${small ? "btn-sm" : ""} ${btnClass}"${
|
|
48
77
|
style ? ` style="${style}"` : ""
|
|
49
78
|
}>${icon ? `<i class="${icon}"></i> ` : ""}${s}</button></form>`;
|
|
50
|
-
|
|
79
|
+
|
|
80
|
+
/**
|
|
51
81
|
* UI Form for Delete Item confirmation
|
|
52
|
-
* @param href - href
|
|
53
|
-
* @param req - Request
|
|
54
|
-
* @param what - Item
|
|
82
|
+
* @param {string} href - href
|
|
83
|
+
* @param {string} req - Request
|
|
84
|
+
* @param {string} what - Item
|
|
55
85
|
* @returns {string} return html form
|
|
56
86
|
*/
|
|
57
87
|
const post_delete_btn = (href, req, what) =>
|
|
@@ -67,6 +97,14 @@ const post_delete_btn = (href, req, what) =>
|
|
|
67
97
|
</button>
|
|
68
98
|
</form>`;
|
|
69
99
|
|
|
100
|
+
/**
|
|
101
|
+
* @param {string} href
|
|
102
|
+
* @param {string} s
|
|
103
|
+
* @param {object} req
|
|
104
|
+
* @param {boolean} confirm
|
|
105
|
+
* @param {string} what
|
|
106
|
+
* @returns {string}
|
|
107
|
+
*/
|
|
70
108
|
const post_dropdown_item = (href, s, req, confirm, what) => {
|
|
71
109
|
const id = href.split("/").join("");
|
|
72
110
|
return `<a class="dropdown-item" onclick="${
|
|
@@ -83,6 +121,11 @@ const post_dropdown_item = (href, s, req, confirm, what) => {
|
|
|
83
121
|
</form>`;
|
|
84
122
|
};
|
|
85
123
|
|
|
124
|
+
/**
|
|
125
|
+
* @param {string} id
|
|
126
|
+
* @param {*} elems
|
|
127
|
+
* @returns {div}
|
|
128
|
+
*/
|
|
86
129
|
const settingsDropdown = (id, elems) =>
|
|
87
130
|
div(
|
|
88
131
|
{ class: "dropdown" },
|
|
@@ -107,6 +150,13 @@ const settingsDropdown = (id, elems) =>
|
|
|
107
150
|
)
|
|
108
151
|
);
|
|
109
152
|
|
|
153
|
+
/**
|
|
154
|
+
* @param {Date} date
|
|
155
|
+
* @param {object} opts
|
|
156
|
+
* @param {string} [opts.hour = "2-digit"]
|
|
157
|
+
* @param {string} [opts.minute = "2-digit"]
|
|
158
|
+
* @returns {string}
|
|
159
|
+
*/
|
|
110
160
|
const localeTime = (date, options = { hour: "2-digit", minute: "2-digit" }) =>
|
|
111
161
|
time(
|
|
112
162
|
{
|
|
@@ -116,6 +166,11 @@ const localeTime = (date, options = { hour: "2-digit", minute: "2-digit" }) =>
|
|
|
116
166
|
date.toLocaleTimeString("en", options)
|
|
117
167
|
);
|
|
118
168
|
|
|
169
|
+
/**
|
|
170
|
+
* @param {Date} date
|
|
171
|
+
* @param {object} [options ={}]
|
|
172
|
+
* @returns {string}
|
|
173
|
+
*/
|
|
119
174
|
const localeDateTime = (date, options = {}) =>
|
|
120
175
|
time(
|
|
121
176
|
{
|
|
@@ -125,6 +180,11 @@ const localeDateTime = (date, options = {}) =>
|
|
|
125
180
|
date.toLocaleString("en", options)
|
|
126
181
|
);
|
|
127
182
|
|
|
183
|
+
/**
|
|
184
|
+
* @param {Date} date
|
|
185
|
+
* @param {object} [options = {}]
|
|
186
|
+
* @returns {string}
|
|
187
|
+
*/
|
|
128
188
|
const localeDate = (date, options = {}) =>
|
|
129
189
|
time(
|
|
130
190
|
{
|
package/layout.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @category saltcorn-markup
|
|
3
|
+
* @module layout
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
const { contract, is } = require("contractis");
|
|
2
7
|
const {
|
|
3
8
|
div,
|
|
@@ -22,8 +27,17 @@ const {
|
|
|
22
27
|
const { alert, breadcrumbs } = require("./layout_utils");
|
|
23
28
|
const { search_bar_form, search_bar } = require("./helpers");
|
|
24
29
|
|
|
30
|
+
/**
|
|
31
|
+
* @param {object[]} [alerts]
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
*/
|
|
25
34
|
const couldHaveAlerts = (alerts) => alerts || Array.isArray(alerts);
|
|
26
35
|
|
|
36
|
+
/**
|
|
37
|
+
* @param {string|object} body
|
|
38
|
+
* @param {object[]} [alerts]
|
|
39
|
+
* @returns {object}
|
|
40
|
+
*/
|
|
27
41
|
const makeSegments = (body, alerts) => {
|
|
28
42
|
const alertsSegments = couldHaveAlerts(alerts)
|
|
29
43
|
? [
|
|
@@ -46,6 +60,13 @@ const makeSegments = (body, alerts) => {
|
|
|
46
60
|
return body;
|
|
47
61
|
} else return { above: [...alertsSegments, body] };
|
|
48
62
|
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
*
|
|
66
|
+
* @param {object} segment
|
|
67
|
+
* @param {string} inner
|
|
68
|
+
* @returns {div|span|string}
|
|
69
|
+
*/
|
|
49
70
|
const applyTextStyle = (segment, inner) => {
|
|
50
71
|
let style = segment.font ? { fontFamily: segment.font } : {};
|
|
51
72
|
switch (segment.textStyle) {
|
|
@@ -70,6 +91,15 @@ const applyTextStyle = (segment, inner) => {
|
|
|
70
91
|
}
|
|
71
92
|
};
|
|
72
93
|
|
|
94
|
+
/**
|
|
95
|
+
* @param {object} opts
|
|
96
|
+
* @param {object[]} opts.contents
|
|
97
|
+
* @param {string[]} opts.titles
|
|
98
|
+
* @param {string} opts.tabsStyle
|
|
99
|
+
* @param {*} opts.ntabs
|
|
100
|
+
* @param {function} go
|
|
101
|
+
* @returns {ul_div}
|
|
102
|
+
*/
|
|
73
103
|
const renderTabs = ({ contents, titles, tabsStyle, ntabs }, go) => {
|
|
74
104
|
const rndid = `tab${Math.floor(Math.random() * 16777215).toString(16)}`;
|
|
75
105
|
if (tabsStyle === "Accordion")
|
|
@@ -149,6 +179,16 @@ const renderTabs = ({ contents, titles, tabsStyle, ntabs }, go) => {
|
|
|
149
179
|
)
|
|
150
180
|
);
|
|
151
181
|
};
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @param {object} opts
|
|
185
|
+
* @param {object} opts.blockDispatch
|
|
186
|
+
* @param {object|string} opts.layout
|
|
187
|
+
* @param {object} [opts.role]
|
|
188
|
+
* @param {object[]} [opts.alerts]
|
|
189
|
+
* @param {boolean} opts.is_owner missing in contract
|
|
190
|
+
* @returns {string}
|
|
191
|
+
*/
|
|
152
192
|
const render = ({ blockDispatch, layout, role, alerts, is_owner }) => {
|
|
153
193
|
//console.log(JSON.stringify(layout, null, 2));
|
|
154
194
|
function wrap(segment, isTop, ix, inner) {
|
|
@@ -157,12 +197,11 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner }) => {
|
|
|
157
197
|
return blockDispatch.wrapTop(segment, ix, inner);
|
|
158
198
|
else
|
|
159
199
|
return segment.labelFor
|
|
160
|
-
?
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
: iconTag + applyTextStyle(segment, inner);
|
|
200
|
+
? label(
|
|
201
|
+
{ for: `input${text(segment.labelFor)}` },
|
|
202
|
+
applyTextStyle(segment, iconTag + inner)
|
|
203
|
+
)
|
|
204
|
+
: applyTextStyle(segment, iconTag + inner);
|
|
166
205
|
}
|
|
167
206
|
function go(segment, isTop, ix) {
|
|
168
207
|
if (!segment) return "";
|
|
@@ -503,7 +542,7 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner }) => {
|
|
|
503
542
|
);
|
|
504
543
|
else
|
|
505
544
|
markup = div(
|
|
506
|
-
{ class: "row" },
|
|
545
|
+
{ class: "row w-100" },
|
|
507
546
|
segment.besides.map((t, ixb) =>
|
|
508
547
|
div(
|
|
509
548
|
{
|
package/layout.test.js
CHANGED
|
@@ -33,7 +33,7 @@ describe("layout", () => {
|
|
|
33
33
|
],
|
|
34
34
|
};
|
|
35
35
|
expect(render({ blockDispatch, layout: markup })).toBe(
|
|
36
|
-
'<div class="row"><div class="col-6">hello</div><div class="col-6">world</div></div><div class="row"><div class="col-6">bar</div><div class="col-6">foo</div></div>'
|
|
36
|
+
'<div class="row w-100"><div class="col-6">hello</div><div class="col-6">world</div></div><div class="row w-100"><div class="col-6">bar</div><div class="col-6">foo</div></div>'
|
|
37
37
|
);
|
|
38
38
|
});
|
|
39
39
|
});
|
package/layout_utils.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @category saltcorn-markup
|
|
3
|
+
* @module layout_utils
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
const {
|
|
2
7
|
ul,
|
|
3
8
|
li,
|
|
@@ -18,13 +23,26 @@ const {
|
|
|
18
23
|
br,
|
|
19
24
|
} = require("./tags");
|
|
20
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} item
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
21
30
|
const labelToId = (item) => text(item.replace(" ", ""));
|
|
22
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @param {string} currentUrl
|
|
34
|
+
* @param {object} item
|
|
35
|
+
* @returns {boolean}
|
|
36
|
+
*/
|
|
23
37
|
const active = (currentUrl, item) =>
|
|
24
38
|
(item.link && currentUrl.startsWith(item.link)) ||
|
|
25
39
|
(item.subitems &&
|
|
26
40
|
item.subitems.some((si) => si.link && currentUrl.startsWith(si.link)));
|
|
27
41
|
|
|
42
|
+
/**
|
|
43
|
+
* @param {object[]} sections
|
|
44
|
+
* @returns {object[]}
|
|
45
|
+
*/
|
|
28
46
|
const innerSections = (sections) => {
|
|
29
47
|
var items = [];
|
|
30
48
|
(sections || []).forEach((section) => {
|
|
@@ -35,6 +53,14 @@ const innerSections = (sections) => {
|
|
|
35
53
|
return items;
|
|
36
54
|
};
|
|
37
55
|
|
|
56
|
+
/**
|
|
57
|
+
* @param {object} opts
|
|
58
|
+
* @param {string} opts.label
|
|
59
|
+
* @param {object[]} opts.subitems
|
|
60
|
+
* @param {string} [opts.icon]
|
|
61
|
+
* @param {boolean} opts.isUser
|
|
62
|
+
* @returns {li}
|
|
63
|
+
*/
|
|
38
64
|
const navSubitems = ({ label, subitems, icon, isUser }) =>
|
|
39
65
|
li(
|
|
40
66
|
{ class: "nav-item dropdown" },
|
|
@@ -65,6 +91,12 @@ const navSubitems = ({ label, subitems, icon, isUser }) =>
|
|
|
65
91
|
)
|
|
66
92
|
)
|
|
67
93
|
);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @param {string} currentUrl
|
|
97
|
+
* @param {object[]} sections
|
|
98
|
+
* @returns {div}
|
|
99
|
+
*/
|
|
68
100
|
const rightNavBar = (currentUrl, sections) =>
|
|
69
101
|
div(
|
|
70
102
|
{ class: "collapse navbar-collapse", id: "navbarResponsive" },
|
|
@@ -95,9 +127,20 @@ const rightNavBar = (currentUrl, sections) =>
|
|
|
95
127
|
)
|
|
96
128
|
);
|
|
97
129
|
|
|
130
|
+
/**
|
|
131
|
+
* @param {object[]} sections
|
|
132
|
+
* @returns {boolean}
|
|
133
|
+
*/
|
|
98
134
|
const hasMobileItems = (sections) =>
|
|
99
135
|
innerSections(sections).some((s) => s.location === "Mobile Bottom");
|
|
100
136
|
|
|
137
|
+
/**
|
|
138
|
+
* @param {string} currentUrl
|
|
139
|
+
* @param {object[]} sections
|
|
140
|
+
* @param {string} [cls = ""]
|
|
141
|
+
* @param {string} [clsLink = ""]
|
|
142
|
+
* @returns {footer|string}
|
|
143
|
+
*/
|
|
101
144
|
const mobileBottomNavBar = (currentUrl, sections, cls = "", clsLink = "") =>
|
|
102
145
|
hasMobileItems(sections)
|
|
103
146
|
? footer(
|
|
@@ -134,6 +177,12 @@ const mobileBottomNavBar = (currentUrl, sections, cls = "", clsLink = "") =>
|
|
|
134
177
|
)
|
|
135
178
|
: "";
|
|
136
179
|
|
|
180
|
+
/**
|
|
181
|
+
* @param {object} opts
|
|
182
|
+
* @param {string} opts.name
|
|
183
|
+
* @param {string} opts.logo
|
|
184
|
+
* @returns {string[]}
|
|
185
|
+
*/
|
|
137
186
|
const leftNavBar = ({ name, logo }) => [
|
|
138
187
|
a(
|
|
139
188
|
{ class: "navbar-brand js-scroll-trigger", href: "/" },
|
|
@@ -162,6 +211,14 @@ const leftNavBar = ({ name, logo }) => [
|
|
|
162
211
|
),
|
|
163
212
|
];
|
|
164
213
|
|
|
214
|
+
/**
|
|
215
|
+
* @param {object} brand
|
|
216
|
+
* @param {object[]} sections
|
|
217
|
+
* @param {string} currentUrl
|
|
218
|
+
* @param {object} opts
|
|
219
|
+
* @param {boolean} [opts.fixedTop = true]
|
|
220
|
+
* @returns {string}
|
|
221
|
+
*/
|
|
165
222
|
const navbar = (brand, sections, currentUrl, opts = { fixedTop: true }) =>
|
|
166
223
|
nav(
|
|
167
224
|
{
|
|
@@ -177,6 +234,11 @@ const navbar = (brand, sections, currentUrl, opts = { fixedTop: true }) =>
|
|
|
177
234
|
)
|
|
178
235
|
);
|
|
179
236
|
|
|
237
|
+
/**
|
|
238
|
+
* @param {string} type
|
|
239
|
+
* @param {string} s
|
|
240
|
+
* @returns {string}
|
|
241
|
+
*/
|
|
180
242
|
const alert = (type, s) => {
|
|
181
243
|
//console.log("alert", type, s,s.length)
|
|
182
244
|
const realtype = type === "error" ? "danger" : type;
|
|
@@ -189,6 +251,10 @@ const alert = (type, s) => {
|
|
|
189
251
|
</div>`
|
|
190
252
|
: "";
|
|
191
253
|
};
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* @returns {string}
|
|
257
|
+
*/
|
|
192
258
|
const navbarSolidOnScroll = script(
|
|
193
259
|
domReady(`$(window).scroll(function () {
|
|
194
260
|
if ($(window).scrollTop() >= 10) {
|
|
@@ -199,12 +265,21 @@ const navbarSolidOnScroll = script(
|
|
|
199
265
|
});`)
|
|
200
266
|
);
|
|
201
267
|
|
|
268
|
+
/**
|
|
269
|
+
* @param {object} x
|
|
270
|
+
* @param {object} s
|
|
271
|
+
* @returns {object}
|
|
272
|
+
*/
|
|
202
273
|
const logit = (x, s) => {
|
|
203
274
|
if (s) console.log(s, x);
|
|
204
275
|
else console.log(x);
|
|
205
276
|
return x;
|
|
206
277
|
};
|
|
207
278
|
|
|
279
|
+
/**
|
|
280
|
+
* @param {number} len
|
|
281
|
+
* @returns {function}
|
|
282
|
+
*/
|
|
208
283
|
const standardBreadcrumbItem = (len) => ({ href, text }, ix) =>
|
|
209
284
|
li(
|
|
210
285
|
{
|
|
@@ -214,6 +289,12 @@ const standardBreadcrumbItem = (len) => ({ href, text }, ix) =>
|
|
|
214
289
|
href ? a({ href }, text) : text
|
|
215
290
|
);
|
|
216
291
|
|
|
292
|
+
/**
|
|
293
|
+
* @param {object} opts
|
|
294
|
+
* @param {Workflow} opts.workflow
|
|
295
|
+
* @param {object} opts.step
|
|
296
|
+
* @returns {string}
|
|
297
|
+
*/
|
|
217
298
|
const workflowBreadcrumbItem = ({ workflow, step }) =>
|
|
218
299
|
workflow.steps
|
|
219
300
|
.map((wfstep, ix) =>
|
|
@@ -229,6 +310,10 @@ const workflowBreadcrumbItem = ({ workflow, step }) =>
|
|
|
229
310
|
)
|
|
230
311
|
.join("");
|
|
231
312
|
|
|
313
|
+
/**
|
|
314
|
+
* @param {object[]} crumbs
|
|
315
|
+
* @returns {string}
|
|
316
|
+
*/
|
|
232
317
|
const breadcrumbs = (crumbs) =>
|
|
233
318
|
nav(
|
|
234
319
|
{ "aria-label": "breadcrumb" },
|
|
@@ -242,6 +327,10 @@ const breadcrumbs = (crumbs) =>
|
|
|
242
327
|
)
|
|
243
328
|
);
|
|
244
329
|
|
|
330
|
+
/**
|
|
331
|
+
* @param {object[]} headers
|
|
332
|
+
* @returns {string}
|
|
333
|
+
*/
|
|
245
334
|
const headersInHead = (headers) =>
|
|
246
335
|
headers
|
|
247
336
|
.filter((h) => h.css)
|
|
@@ -256,6 +345,10 @@ const headersInHead = (headers) =>
|
|
|
256
345
|
.map((h) => h.headerTag)
|
|
257
346
|
.join("");
|
|
258
347
|
|
|
348
|
+
/**
|
|
349
|
+
* @param {object[]} headers
|
|
350
|
+
* @returns {string}
|
|
351
|
+
*/
|
|
259
352
|
const headersInBody = (headers) =>
|
|
260
353
|
headers
|
|
261
354
|
.filter((h) => h.script)
|
|
@@ -273,6 +366,10 @@ const headersInBody = (headers) =>
|
|
|
273
366
|
.map((h) => `<script>${h.scriptBody}</script>`)
|
|
274
367
|
.join("");
|
|
275
368
|
|
|
369
|
+
/**
|
|
370
|
+
* @param {object[]} tabList
|
|
371
|
+
* @returns {ul}
|
|
372
|
+
*/
|
|
276
373
|
const cardHeaderTabs = (tabList) =>
|
|
277
374
|
ul(
|
|
278
375
|
{ class: "nav nav-tabs card-header-tabs" },
|
package/mktag.js
CHANGED
|
@@ -1,13 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @category saltcorn-markup
|
|
3
|
+
* @module mktag
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
//https://stackoverflow.com/a/54246501
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} str
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
2
11
|
const camelToCssCase = (str) =>
|
|
3
12
|
str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
4
13
|
|
|
14
|
+
/**
|
|
15
|
+
* @param {string|object} cs
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
5
18
|
const ppClasses = (cs) =>
|
|
6
19
|
typeof cs === "string" ? cs : !cs ? "" : cs.filter((c) => c).join(" ");
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {string|object} c
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
7
25
|
const ppClass = (c) => {
|
|
8
26
|
const clss = ppClasses(c);
|
|
9
27
|
return clss ? `class="${clss}"` : "";
|
|
10
28
|
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {string|string[]|object} [cs]
|
|
32
|
+
* @returns {string}
|
|
33
|
+
*/
|
|
11
34
|
const ppStyles = (cs) =>
|
|
12
35
|
typeof cs === "string"
|
|
13
36
|
? cs
|
|
@@ -20,10 +43,22 @@ const ppStyles = (cs) =>
|
|
|
20
43
|
.map(([k, v]) => `${camelToCssCase(k)}:${v}`)
|
|
21
44
|
.join(";")
|
|
22
45
|
: "";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {string|string[]|object} [cs]
|
|
49
|
+
* @returns {string}
|
|
50
|
+
*/
|
|
23
51
|
const ppStyle = (c) => {
|
|
24
52
|
const clss = ppStyles(c);
|
|
25
53
|
return clss ? `style="${clss}"` : "";
|
|
26
54
|
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {object[]} opts
|
|
58
|
+
* @param {string} opts.k
|
|
59
|
+
* @param {boolean} [opts.v]
|
|
60
|
+
* @returns {string}
|
|
61
|
+
*/
|
|
27
62
|
const ppAttrib = ([k, v]) =>
|
|
28
63
|
typeof v === "boolean"
|
|
29
64
|
? v
|
|
@@ -36,6 +71,12 @@ const ppAttrib = ([k, v]) =>
|
|
|
36
71
|
: k === "style"
|
|
37
72
|
? ppStyle(v)
|
|
38
73
|
: `${k}="${v}"`;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {string} tnm
|
|
77
|
+
* @param {boolean} voidTag
|
|
78
|
+
* @returns {function}
|
|
79
|
+
*/
|
|
39
80
|
const mkTag = (tnm, voidTag) => (...args) => {
|
|
40
81
|
var body = "";
|
|
41
82
|
var attribs = " ";
|
package/package.json
CHANGED
package/table.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @category saltcorn-markup
|
|
3
|
+
* @module table
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
const { contract, is } = require("contractis");
|
|
2
7
|
|
|
3
8
|
const {
|
|
@@ -15,6 +20,11 @@ const {
|
|
|
15
20
|
span,
|
|
16
21
|
} = require("./tags");
|
|
17
22
|
const { pagination } = require("./helpers");
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {object} hdr
|
|
26
|
+
* @returns {th}
|
|
27
|
+
*/
|
|
18
28
|
const headerCell = (hdr) =>
|
|
19
29
|
th(
|
|
20
30
|
(hdr.align || hdr.width) && {
|
|
@@ -25,6 +35,13 @@ const headerCell = (hdr) =>
|
|
|
25
35
|
hdr.sortlink ? a({ href: hdr.sortlink }, hdr.label) : hdr.label
|
|
26
36
|
);
|
|
27
37
|
|
|
38
|
+
/**
|
|
39
|
+
* @function
|
|
40
|
+
* @param {object[]} hdrs
|
|
41
|
+
* @param {object[]} vs
|
|
42
|
+
* @param {object} [opts]
|
|
43
|
+
* @returns {string}
|
|
44
|
+
*/
|
|
28
45
|
const mkTable = contract(
|
|
29
46
|
is.fun(
|
|
30
47
|
[
|
|
@@ -79,6 +96,11 @@ const mkTable = contract(
|
|
|
79
96
|
)
|
|
80
97
|
);
|
|
81
98
|
|
|
99
|
+
/**
|
|
100
|
+
* @param {object} opts
|
|
101
|
+
* @param {object} v
|
|
102
|
+
* @returns {object}
|
|
103
|
+
*/
|
|
82
104
|
const mkClickHandler = (opts, v) => {
|
|
83
105
|
var attrs = {};
|
|
84
106
|
if (opts.onRowSelect)
|
package/tabs.js
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @category saltcorn-markup
|
|
3
|
+
* @module tabs
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
const { a, text, div, ul, li } = require("./tags");
|
|
2
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} str
|
|
10
|
+
* @returns {string}
|
|
11
|
+
*/
|
|
3
12
|
const mkId = (str) => text(str.split(" ").join("_"));
|
|
4
13
|
|
|
14
|
+
/**
|
|
15
|
+
* @param {object} obj
|
|
16
|
+
* @returns {object}
|
|
17
|
+
*/
|
|
5
18
|
const tabs = (obj) => {
|
|
6
19
|
const entries = Array.isArray(obj) ? obj : Object.entries(obj);
|
|
7
20
|
const lis = entries.map((e, ix) =>
|
package/tags.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @category saltcorn-markup
|
|
3
|
+
* @module tags
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
const xss = require("xss");
|
|
2
7
|
const escape = require("escape-html");
|
|
3
8
|
const htmlTags = require("html-tags");
|
|
@@ -5,24 +10,47 @@ const voidHtmlTags = new Set(require("html-tags/void"));
|
|
|
5
10
|
const mkTag = require("./mktag");
|
|
6
11
|
|
|
7
12
|
//https://stackoverflow.com/a/59220393
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} js
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
8
17
|
const domReady = (js) =>
|
|
9
18
|
`(function(f){if (document.readyState === "complete") f(); else document.addEventListener('DOMContentLoaded',f,false)})(function(){${js}});`;
|
|
10
19
|
|
|
11
20
|
xss.whiteList.kbd = [];
|
|
12
21
|
|
|
22
|
+
/**
|
|
23
|
+
* @param {string|number} t
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
13
26
|
const text = (t) => (t === 0 ? "0" : xss(t));
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string|number} t
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
14
32
|
const text_attr = (t) => (t === 0 ? "0" : escape(t));
|
|
15
33
|
|
|
34
|
+
/**
|
|
35
|
+
* @return {string[]}
|
|
36
|
+
*/
|
|
16
37
|
const allTags = Object.fromEntries(
|
|
17
38
|
htmlTags.map((tag) => [tag, mkTag(tag, voidHtmlTags.has(tag))])
|
|
18
39
|
);
|
|
19
40
|
|
|
20
41
|
module.exports = {
|
|
21
42
|
...allTags,
|
|
43
|
+
/**
|
|
44
|
+
* @param {string} tagName
|
|
45
|
+
* @param {...*} rest
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
22
48
|
genericElement: (tagName, ...rest) => mkTag(tagName, false)(...rest),
|
|
23
49
|
domReady,
|
|
24
50
|
text,
|
|
25
51
|
text_attr,
|
|
52
|
+
/** @type {string} */
|
|
26
53
|
nbsp: " ",
|
|
54
|
+
/** @type {module:mktag} */
|
|
27
55
|
mkTag,
|
|
28
56
|
};
|