@jskit-ai/crud-ui-generator 0.1.40 → 0.1.41
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/package.descriptor.mjs +3 -3
- package/package.json +4 -4
- package/src/server/buildTemplateContext.js +2 -0
- package/templates/src/pages/admin/ui-generator/EditElement.vue +0 -7
- package/templates/src/pages/admin/ui-generator/EditWrapperElement.vue +0 -7
- package/templates/src/pages/admin/ui-generator/ListElement.vue +0 -6
- package/templates/src/pages/admin/ui-generator/NewElement.vue +0 -7
- package/templates/src/pages/admin/ui-generator/NewWrapperElement.vue +0 -7
- package/templates/src/pages/admin/ui-generator/ViewElement.vue +2 -6
- package/test/buildTemplateContext.test.js +22 -18
- package/test/packageDescriptor.test.js +7 -0
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/crud-ui-generator",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.41",
|
|
5
5
|
kind: "generator",
|
|
6
6
|
description: "Generate CRUD route trees from resource validators at an explicit route root relative to src/pages/.",
|
|
7
7
|
options: {
|
|
@@ -168,7 +168,7 @@ export default Object.freeze({
|
|
|
168
168
|
mutations: {
|
|
169
169
|
dependencies: {
|
|
170
170
|
runtime: {
|
|
171
|
-
"@jskit-ai/users-web": "0.1.
|
|
171
|
+
"@jskit-ai/users-web": "0.1.73"
|
|
172
172
|
},
|
|
173
173
|
dev: {}
|
|
174
174
|
},
|
|
@@ -353,7 +353,7 @@ export default Object.freeze({
|
|
|
353
353
|
position: "bottom",
|
|
354
354
|
skipIfContains: "__JSKIT_UI_MENU_MARKER__",
|
|
355
355
|
value:
|
|
356
|
-
"\n// __JSKIT_UI_MENU_MARKER__\n{\n addPlacement({\n id: \"__JSKIT_UI_MENU_PLACEMENT_ID__\",\n target: \"__JSKIT_UI_MENU_PLACEMENT_TARGET__\",\n surfaces: [\"__JSKIT_UI_SURFACE_ID__\"],\n order: 155,\n componentToken: \"__JSKIT_UI_MENU_COMPONENT_TOKEN__\",\n props: {\n label: \"__JSKIT_UI_MENU_LABEL__\",\n surface: \"__JSKIT_UI_SURFACE_ID__\",\n scopedSuffix: \"__JSKIT_UI_MENU_WORKSPACE_SUFFIX__\",\n unscopedSuffix: \"__JSKIT_UI_MENU_NON_WORKSPACE_SUFFIX__\",\n__JSKIT_UI_MENU_TO_PROP_LINE__ },\n__JSKIT_UI_MENU_WHEN_LINE__ });\n}\n",
|
|
356
|
+
"\n// __JSKIT_UI_MENU_MARKER__\n{\n addPlacement({\n id: \"__JSKIT_UI_MENU_PLACEMENT_ID__\",\n target: \"__JSKIT_UI_MENU_PLACEMENT_TARGET__\",\n surfaces: [\"__JSKIT_UI_SURFACE_ID__\"],\n order: 155,\n componentToken: \"__JSKIT_UI_MENU_COMPONENT_TOKEN__\",\n props: {\n label: \"__JSKIT_UI_MENU_LABEL__\",\n icon: \"__JSKIT_UI_MENU_ICON__\",\n surface: \"__JSKIT_UI_SURFACE_ID__\",\n scopedSuffix: \"__JSKIT_UI_MENU_WORKSPACE_SUFFIX__\",\n unscopedSuffix: \"__JSKIT_UI_MENU_NON_WORKSPACE_SUFFIX__\",\n__JSKIT_UI_MENU_TO_PROP_LINE__ },\n__JSKIT_UI_MENU_WHEN_LINE__ });\n}\n",
|
|
357
357
|
reason: "Append generated CRUD list-page placement.",
|
|
358
358
|
category: "crud-ui-generator",
|
|
359
359
|
id: "crud-ui-placement-menu",
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/crud-ui-generator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.41",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@jskit-ai/crud-core": "0.1.
|
|
10
|
-
"@jskit-ai/kernel": "0.1.
|
|
11
|
-
"@jskit-ai/resource-crud-core": "0.1.
|
|
9
|
+
"@jskit-ai/crud-core": "0.1.66",
|
|
10
|
+
"@jskit-ai/kernel": "0.1.58",
|
|
11
|
+
"@jskit-ai/resource-crud-core": "0.1.3"
|
|
12
12
|
},
|
|
13
13
|
"exports": {
|
|
14
14
|
"./server/buildTemplateContext": "./src/server/buildTemplateContext.js"
|
|
@@ -59,6 +59,7 @@ const DEFAULT_OPERATIONS = normalizeText(descriptor?.options?.operations?.defaul
|
|
|
59
59
|
const DEFAULT_LIST_HIDDEN_FIELD_KEYS = new Set(["createdAt", "updatedAt"]);
|
|
60
60
|
const DEFAULT_FORM_COMPONENT_FILE = "CrudAddEditForm.vue";
|
|
61
61
|
const DEFAULT_FORM_FIELDS_FILE = "CrudAddEditFormFields.js";
|
|
62
|
+
const DEFAULT_GENERATED_LINK_ICON = "mdi-view-list-outline";
|
|
62
63
|
|
|
63
64
|
function splitTextIntoWords(value = "") {
|
|
64
65
|
const normalized = String(value || "")
|
|
@@ -659,6 +660,7 @@ async function buildUiTemplateContext({ appRoot, options } = {}) {
|
|
|
659
660
|
__JSKIT_UI_MENU_PLACEMENT_ID__: String(pageLinkTarget?.pageTarget?.placementId || ""),
|
|
660
661
|
__JSKIT_UI_MENU_PLACEMENT_TARGET__: String(pageLinkTarget?.placementTarget?.id || ""),
|
|
661
662
|
__JSKIT_UI_MENU_COMPONENT_TOKEN__: String(pageLinkTarget?.componentToken || ""),
|
|
663
|
+
__JSKIT_UI_MENU_ICON__: DEFAULT_GENERATED_LINK_ICON,
|
|
662
664
|
__JSKIT_UI_MENU_WORKSPACE_SUFFIX__: String(pageLinkTarget?.pageTarget?.routeUrlSuffix || ""),
|
|
663
665
|
__JSKIT_UI_MENU_NON_WORKSPACE_SUFFIX__: String(pageLinkTarget?.pageTarget?.routeUrlSuffix || ""),
|
|
664
666
|
__JSKIT_UI_MENU_WHEN_LINE__: String(pageLinkTarget?.whenLine || ""),
|
|
@@ -63,12 +63,6 @@ const UI_VIEW_URL = __JSKIT_UI_EDIT_PAGE_VIEW_URL__;
|
|
|
63
63
|
const UI_CANCEL_URL = UI_VIEW_URL || UI_LIST_URL;
|
|
64
64
|
const UI_RECORD_CHANGED_EVENT = __JSKIT_UI_RECORD_CHANGED_EVENT__;
|
|
65
65
|
const UI_EDIT_FORM_FIELDS = [];
|
|
66
|
-
const UI_EDIT_TRANSPORT = Object.freeze({
|
|
67
|
-
kind: "jsonapi-resource",
|
|
68
|
-
requestType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
69
|
-
responseType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
70
|
-
responseKind: "record"
|
|
71
|
-
});
|
|
72
66
|
|
|
73
67
|
// @jskit-contract crud.ui.form-fields.__JSKIT_UI_RESOURCE_NAMESPACE__.edit.v1
|
|
74
68
|
void UI_EDIT_FORM_FIELDS;
|
|
@@ -105,7 +99,6 @@ const formRuntime = useCrudAddEdit({
|
|
|
105
99
|
routeRecordId.value
|
|
106
100
|
],
|
|
107
101
|
placementSource: "ui-generator.__JSKIT_UI_RESOURCE_NAMESPACE__.edit",
|
|
108
|
-
transport: UI_EDIT_TRANSPORT,
|
|
109
102
|
writeMethod: "PATCH",
|
|
110
103
|
fallbackLoadError: "Unable to load record.",
|
|
111
104
|
fallbackSaveError: "Unable to save record.",
|
|
@@ -26,12 +26,6 @@ const UI_LIST_URL = __JSKIT_UI_EDIT_PAGE_LIST_URL__;
|
|
|
26
26
|
const UI_VIEW_URL = __JSKIT_UI_EDIT_PAGE_VIEW_URL__;
|
|
27
27
|
const UI_CANCEL_URL = UI_VIEW_URL || UI_LIST_URL;
|
|
28
28
|
const UI_RECORD_CHANGED_EVENT = __JSKIT_UI_RECORD_CHANGED_EVENT__;
|
|
29
|
-
const UI_EDIT_TRANSPORT = Object.freeze({
|
|
30
|
-
kind: "jsonapi-resource",
|
|
31
|
-
requestType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
32
|
-
responseType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
33
|
-
responseKind: "record"
|
|
34
|
-
});
|
|
35
29
|
const route = useRoute();
|
|
36
30
|
|
|
37
31
|
// jskit:crud-ui-fields-target ../_components/__JSKIT_UI_FORM_COMPONENT_FILE__
|
|
@@ -64,7 +58,6 @@ const formRuntime = useCrudAddEdit({
|
|
|
64
58
|
routeRecordId.value
|
|
65
59
|
],
|
|
66
60
|
placementSource: "ui-generator.__JSKIT_UI_RESOURCE_NAMESPACE__.edit",
|
|
67
|
-
transport: UI_EDIT_TRANSPORT,
|
|
68
61
|
writeMethod: "PATCH",
|
|
69
62
|
fallbackLoadError: "Unable to load record.",
|
|
70
63
|
fallbackSaveError: "Unable to save record.",
|
|
@@ -101,11 +101,6 @@ const UI_EDIT_URL = __JSKIT_UI_LIST_PAGE_EDIT_URL__;
|
|
|
101
101
|
const UI_NEW_URL = __JSKIT_UI_LIST_PAGE_NEW_URL__;
|
|
102
102
|
const UI_RECORD_CHANGED_EVENTS = __JSKIT_UI_LIST_REALTIME_EVENTS__;
|
|
103
103
|
const UI_ROUTE_QUERY_BLACKLIST = Object.freeze(["include", "cursor", "limit"]);
|
|
104
|
-
const UI_LIST_TRANSPORT = Object.freeze({
|
|
105
|
-
kind: "jsonapi-resource",
|
|
106
|
-
responseType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
107
|
-
responseKind: "collection"
|
|
108
|
-
});
|
|
109
104
|
|
|
110
105
|
const records = useCrudList({
|
|
111
106
|
adapter: UI_OPERATION_ADAPTER || undefined,
|
|
@@ -131,7 +126,6 @@ const records = useCrudList({
|
|
|
131
126
|
},
|
|
132
127
|
placementSource: "ui-generator.__JSKIT_UI_RESOURCE_NAMESPACE__.list",
|
|
133
128
|
fallbackLoadError: "Unable to load records.",
|
|
134
|
-
transport: UI_LIST_TRANSPORT,
|
|
135
129
|
recordIdParam: UI_RECORD_ID_PARAM,
|
|
136
130
|
recordIdSelector: (item = {}) => __JSKIT_UI_LIST_RECORD_ID_EXPR__,
|
|
137
131
|
viewUrlTemplate: UI_VIEW_URL,
|
|
@@ -50,12 +50,6 @@ const UI_LIST_URL = __JSKIT_UI_NEW_PAGE_LIST_URL__;
|
|
|
50
50
|
const UI_VIEW_URL = __JSKIT_UI_NEW_PAGE_VIEW_URL__;
|
|
51
51
|
const UI_RECORD_CHANGED_EVENT = __JSKIT_UI_RECORD_CHANGED_EVENT__;
|
|
52
52
|
const UI_CREATE_FORM_FIELDS = [];
|
|
53
|
-
const UI_CREATE_TRANSPORT = Object.freeze({
|
|
54
|
-
kind: "jsonapi-resource",
|
|
55
|
-
requestType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
56
|
-
responseType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
57
|
-
responseKind: "record"
|
|
58
|
-
});
|
|
59
53
|
|
|
60
54
|
// @jskit-contract crud.ui.form-fields.__JSKIT_UI_RESOURCE_NAMESPACE__.new.v1
|
|
61
55
|
void UI_CREATE_FORM_FIELDS;
|
|
@@ -81,7 +75,6 @@ const formRuntime = useCrudAddEdit({
|
|
|
81
75
|
],
|
|
82
76
|
placementSource: "ui-generator.__JSKIT_UI_RESOURCE_NAMESPACE__.new",
|
|
83
77
|
readEnabled: false,
|
|
84
|
-
transport: UI_CREATE_TRANSPORT,
|
|
85
78
|
writeMethod: "POST",
|
|
86
79
|
fallbackSaveError: "Unable to save record.",
|
|
87
80
|
recordIdParam: UI_RECORD_ID_PARAM,
|
|
@@ -23,12 +23,6 @@ const UI_LIST_URL = __JSKIT_UI_NEW_PAGE_LIST_URL__;
|
|
|
23
23
|
const UI_VIEW_URL = __JSKIT_UI_NEW_PAGE_VIEW_URL__;
|
|
24
24
|
const UI_CANCEL_URL = UI_LIST_URL;
|
|
25
25
|
const UI_RECORD_CHANGED_EVENT = __JSKIT_UI_RECORD_CHANGED_EVENT__;
|
|
26
|
-
const UI_CREATE_TRANSPORT = Object.freeze({
|
|
27
|
-
kind: "jsonapi-resource",
|
|
28
|
-
requestType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
29
|
-
responseType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
30
|
-
responseKind: "record"
|
|
31
|
-
});
|
|
32
26
|
|
|
33
27
|
// jskit:crud-ui-fields-target ./_components/__JSKIT_UI_FORM_COMPONENT_FILE__
|
|
34
28
|
// jskit:crud-ui-form-fields-target ./_components/__JSKIT_UI_FORM_FIELDS_FILE__
|
|
@@ -51,7 +45,6 @@ const formRuntime = useCrudAddEdit({
|
|
|
51
45
|
],
|
|
52
46
|
placementSource: "ui-generator.__JSKIT_UI_RESOURCE_NAMESPACE__.new",
|
|
53
47
|
readEnabled: false,
|
|
54
|
-
transport: UI_CREATE_TRANSPORT,
|
|
55
48
|
writeMethod: "POST",
|
|
56
49
|
fallbackSaveError: "Unable to save record.",
|
|
57
50
|
recordIdParam: UI_RECORD_ID_PARAM,
|
|
@@ -57,6 +57,7 @@ __JSKIT_UI_VIEW_COLUMNS__
|
|
|
57
57
|
|
|
58
58
|
<script setup>
|
|
59
59
|
import { useCrudView } from "@jskit-ai/users-web/client/composables/useCrudView";
|
|
60
|
+
import { resource as uiResource } from "__JSKIT_UI_RESOURCE_IMPORT_PATH__";
|
|
60
61
|
|
|
61
62
|
const UI_OPERATION_ADAPTER = null;
|
|
62
63
|
const UI_RECORD_ID_PARAM = "__JSKIT_UI_RECORD_ID_PARAM__";
|
|
@@ -66,14 +67,10 @@ const UI_LIST_URL = __JSKIT_UI_VIEW_PAGE_LIST_URL__;
|
|
|
66
67
|
const UI_EDIT_URL = __JSKIT_UI_VIEW_PAGE_EDIT_URL__;
|
|
67
68
|
const UI_VIEW_TITLE_FALLBACK_FIELD_KEY = __JSKIT_UI_VIEW_TITLE_FALLBACK_FIELD_KEY__;
|
|
68
69
|
const UI_RECORD_CHANGED_EVENT = __JSKIT_UI_RECORD_CHANGED_EVENT__;
|
|
69
|
-
const UI_VIEW_TRANSPORT = Object.freeze({
|
|
70
|
-
kind: "jsonapi-resource",
|
|
71
|
-
responseType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
72
|
-
responseKind: "record"
|
|
73
|
-
});
|
|
74
70
|
|
|
75
71
|
const view = useCrudView({
|
|
76
72
|
adapter: UI_OPERATION_ADAPTER || undefined,
|
|
73
|
+
resource: uiResource,
|
|
77
74
|
apiUrlTemplate: UI_VIEW_API_URL,
|
|
78
75
|
recordIdParam: UI_RECORD_ID_PARAM,
|
|
79
76
|
includeRecordIdInQueryKey: true,
|
|
@@ -85,7 +82,6 @@ const view = useCrudView({
|
|
|
85
82
|
String(workspaceSlug || "")
|
|
86
83
|
],
|
|
87
84
|
placementSource: "ui-generator.__JSKIT_UI_RESOURCE_NAMESPACE__.view",
|
|
88
|
-
transport: UI_VIEW_TRANSPORT,
|
|
89
85
|
fallbackLoadError: "Unable to load record.",
|
|
90
86
|
notFoundMessage: "Record not found.",
|
|
91
87
|
listUrlTemplate: UI_LIST_URL,
|
|
@@ -628,7 +628,8 @@ test("buildUiTemplateContext infers tab placement and relative link-to from the
|
|
|
628
628
|
});
|
|
629
629
|
|
|
630
630
|
assert.equal(context.__JSKIT_UI_MENU_PLACEMENT_TARGET__, "catalog:sub-pages");
|
|
631
|
-
assert.equal(context.__JSKIT_UI_MENU_COMPONENT_TOKEN__, "local.main.ui.
|
|
631
|
+
assert.equal(context.__JSKIT_UI_MENU_COMPONENT_TOKEN__, "local.main.ui.surface-aware-menu-link-item");
|
|
632
|
+
assert.equal(context.__JSKIT_UI_MENU_ICON__, "mdi-view-list-outline");
|
|
632
633
|
assert.equal(context.__JSKIT_UI_MENU_TO_PROP_LINE__, " to: \"./products\",\n");
|
|
633
634
|
assert.equal(context.__JSKIT_UI_MENU_WORKSPACE_SUFFIX__, "/catalog/products");
|
|
634
635
|
});
|
|
@@ -714,7 +715,7 @@ test("buildUiTemplateContext validates operations against the supported CRUD set
|
|
|
714
715
|
});
|
|
715
716
|
});
|
|
716
717
|
|
|
717
|
-
test("crud ui templates
|
|
718
|
+
test("crud ui templates derive JSON:API transport from the shared CRUD resource", async () => {
|
|
718
719
|
const testDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
719
720
|
const templateRoot = path.resolve(testDirectory, "..", "templates", "src", "pages", "admin", "ui-generator");
|
|
720
721
|
|
|
@@ -725,25 +726,28 @@ test("crud ui templates opt into shared JSON:API client transport", async () =>
|
|
|
725
726
|
const newWrapperTemplateSource = await readFile(path.join(templateRoot, "NewWrapperElement.vue"), "utf8");
|
|
726
727
|
const editWrapperTemplateSource = await readFile(path.join(templateRoot, "EditWrapperElement.vue"), "utf8");
|
|
727
728
|
|
|
728
|
-
assert.match(listTemplateSource, /
|
|
729
|
-
assert.
|
|
730
|
-
assert.
|
|
729
|
+
assert.match(listTemplateSource, /resource: uiResource,/);
|
|
730
|
+
assert.doesNotMatch(listTemplateSource, /const UI_LIST_TRANSPORT = Object\.freeze\(\{/);
|
|
731
|
+
assert.doesNotMatch(listTemplateSource, /transport:\s*UI_LIST_TRANSPORT,/);
|
|
731
732
|
|
|
732
|
-
assert.match(viewTemplateSource, /
|
|
733
|
-
assert.match(viewTemplateSource, /
|
|
734
|
-
assert.
|
|
733
|
+
assert.match(viewTemplateSource, /import \{ resource as uiResource \} from/);
|
|
734
|
+
assert.match(viewTemplateSource, /resource: uiResource,/);
|
|
735
|
+
assert.doesNotMatch(viewTemplateSource, /const UI_VIEW_TRANSPORT = Object\.freeze\(\{/);
|
|
736
|
+
assert.doesNotMatch(viewTemplateSource, /transport:\s*UI_VIEW_TRANSPORT,/);
|
|
735
737
|
|
|
736
|
-
assert.match(newTemplateSource, /
|
|
737
|
-
assert.
|
|
738
|
-
assert.
|
|
738
|
+
assert.match(newTemplateSource, /resource: uiResource,/);
|
|
739
|
+
assert.doesNotMatch(newTemplateSource, /const UI_CREATE_TRANSPORT = Object\.freeze\(\{/);
|
|
740
|
+
assert.doesNotMatch(newTemplateSource, /transport:\s*UI_CREATE_TRANSPORT,/);
|
|
739
741
|
|
|
740
|
-
assert.match(editTemplateSource, /
|
|
741
|
-
assert.
|
|
742
|
-
assert.
|
|
742
|
+
assert.match(editTemplateSource, /resource: uiResource,/);
|
|
743
|
+
assert.doesNotMatch(editTemplateSource, /const UI_EDIT_TRANSPORT = Object\.freeze\(\{/);
|
|
744
|
+
assert.doesNotMatch(editTemplateSource, /transport:\s*UI_EDIT_TRANSPORT,/);
|
|
743
745
|
|
|
744
|
-
assert.match(newWrapperTemplateSource, /
|
|
745
|
-
assert.
|
|
746
|
+
assert.match(newWrapperTemplateSource, /resource: uiResource,/);
|
|
747
|
+
assert.doesNotMatch(newWrapperTemplateSource, /const UI_CREATE_TRANSPORT = Object\.freeze\(\{/);
|
|
748
|
+
assert.doesNotMatch(newWrapperTemplateSource, /transport:\s*UI_CREATE_TRANSPORT,/);
|
|
746
749
|
|
|
747
|
-
assert.match(editWrapperTemplateSource, /
|
|
748
|
-
assert.
|
|
750
|
+
assert.match(editWrapperTemplateSource, /resource: uiResource,/);
|
|
751
|
+
assert.doesNotMatch(editWrapperTemplateSource, /const UI_EDIT_TRANSPORT = Object\.freeze\(\{/);
|
|
752
|
+
assert.doesNotMatch(editWrapperTemplateSource, /transport:\s*UI_EDIT_TRANSPORT,/);
|
|
749
753
|
});
|
|
@@ -12,3 +12,10 @@ test("crud-ui-generator operations option exposes structured csv-enum metadata",
|
|
|
12
12
|
assert.equal(descriptor.options?.operations?.defaultValue, "list,view,new,edit");
|
|
13
13
|
assert.equal(descriptor.metadata?.generatorSubcommands?.crud?.optionNames?.includes("operations"), true);
|
|
14
14
|
});
|
|
15
|
+
|
|
16
|
+
test("crud-ui-generator placement scaffold includes an explicit stock icon prop", () => {
|
|
17
|
+
const placementMutation = descriptor?.mutations?.text?.find(
|
|
18
|
+
(entry) => String(entry?.id || "").trim() === "crud-ui-placement-menu"
|
|
19
|
+
);
|
|
20
|
+
assert.match(String(placementMutation?.value || ""), /icon: "__JSKIT_UI_MENU_ICON__"/);
|
|
21
|
+
});
|