mushroom-cli 1.2.0-alpha.23 → 1.2.0-alpha.25

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/README.md CHANGED
@@ -132,6 +132,34 @@ npx mushroom-cli parameters --no-check-update
132
132
 
133
133
  ## Version
134
134
 
135
+ ### 1.2.0-alpha.25
136
+
137
+ Improvement:
138
+
139
+ - gen c#-mongo
140
+ - support System.Text.Json on resources
141
+ - gen api docs
142
+ - impove copy function
143
+ - allow collapse object fields
144
+ - display the description of entities
145
+
146
+ Fixed bug:
147
+
148
+ - gen api docs
149
+ - correct route for role \*
150
+
151
+ ### 1.2.0-alpha.24
152
+
153
+ Improvement:
154
+
155
+ - gen-docs
156
+ - Add `JavascriptParamModel`, `JavascriptRequestBodyModel`, `JavascriptResultModel` tab in code snippets of custom methods
157
+ - Correct generate query string and request body at HTTP and cURL tabs in code snippets of custom methods
158
+ - Correct generate param and body at Javascript tab in code snippets of custom methods
159
+ - Correct generate JavascriptModel in code snippets
160
+ - Add docs for `inline` mode of download file and zip file(s), `allowSmallerSize` for thumb
161
+ - Support link for Roles and Code Snippets
162
+
135
163
  ### 1.2.0-alpha.22
136
164
 
137
165
  Improvement:
@@ -74,6 +74,25 @@
74
74
  display: none;
75
75
  }
76
76
 
77
+
78
+ .entity > .description {
79
+ display: none;
80
+ }
81
+ .entity h2:hover, .extension h2:hover {
82
+ background-color: #f5f5f5;
83
+ margin-left: calc(0px - var(--padding));
84
+ margin-right: calc(0px - var(--padding));
85
+ padding-left: var(--padding);
86
+ padding-right: var(--padding);
87
+ }
88
+ .entity h2 .description {
89
+ float: right;
90
+ font-size: 15px;
91
+ }
92
+ .entity .project-entity-actions, .extension .actions {
93
+ margin-top: var(--margin);
94
+ }
95
+
77
96
  .actions {
78
97
  padding-left: var(--padding);
79
98
  display: none;
@@ -88,6 +107,25 @@
88
107
  z-index: 100;
89
108
  }
90
109
 
110
+ .actions .panel-heading .copy, .entity h2 .copy {
111
+ display: none;
112
+ width: 16px;
113
+ height: 16px;
114
+ opacity: 0.8;
115
+ background-size: contain;
116
+ background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIHZpZXdCb3g9IjAgMCA1MCA1MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cmVjdCB4PSIxMCIgeT0iNSIgd2lkdGg9IjI1IiBoZWlnaHQ9IjMwIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjUiIGZpbGw9Im5vbmUiIC8+CiAgPHJlY3QgeD0iMTgiIHk9IjEzIiB3aWR0aD0iMjUiIGhlaWdodD0iMzAiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iNSIgZmlsbD0ibm9uZSIgLz4KPC9zdmc+);
117
+ }
118
+ .entity h2 .copy {
119
+ filter: invert(1);
120
+ vertical-align: super;
121
+ }
122
+ .actions .panel-heading:hover .copy, .entity h2:hover .copy {
123
+ display: inline-block;
124
+ opacity: 1;
125
+ }
126
+ .actions .panel-heading .copy:hover, .entity h2 .copy:hover {
127
+ transform: scale(1.1);
128
+ }
91
129
  .actions .panel-heading .description {
92
130
  float: right;
93
131
  }
@@ -133,6 +171,26 @@
133
171
  font-weight: bold;
134
172
  }
135
173
 
174
+ pre .collapsable-field {
175
+ cursor: pointer;
176
+ }
177
+ pre .collapsable-field:hover {
178
+ text-decoration: underline;
179
+ }
180
+ pre .collapsable-field.collapsed {
181
+ display: inline-block;
182
+ }
183
+ pre .collapsable-field.collapsed::before {
184
+ content: ">";
185
+ text-decoration: none;
186
+ display: inline-block;
187
+ margin-right: 1px;
188
+ margin-left: -9px;
189
+ }
190
+ pre .collapsed {
191
+ display: none;
192
+ }
193
+
136
194
  pre[data-language] {
137
195
  position: relative;
138
196
  }
@@ -167,11 +225,23 @@
167
225
  h2 {
168
226
  font-size: 26px;
169
227
  }
228
+ .entity > .description {
229
+ display: block;
230
+ }
231
+ .entity h2 .description {
232
+ display: none;
233
+ }
170
234
  .actions .panel-heading .description {
171
235
  float: none;
172
236
  display: block;
173
237
  opacity: 0.8;
174
238
  }
239
+ .nav.nav-tabs {
240
+ top: 121px;
241
+ }
242
+ .tab-pages .nav-tabs {
243
+ top: 164px;
244
+ }
175
245
  }
176
246
 
177
247
  #project-rootUrls li label {
@@ -479,7 +549,8 @@
479
549
  <div class="panel panel-info" id="extension-auth-login">
480
550
  <div class="panel-heading" onclick="toggleParentClass.call(this)" data-toggled-class="show" data-hash="$auth.login">
481
551
  <span>POST</span>
482
- <span class="root-url">{{rootUrl}}</span>auth/login
552
+ <span class="url"><span class="root-url">{{rootUrl}}</span>auth/login</span>
553
+ <span class="copy" title="Copy" data-copy=".url" onclick="doCopy.call(this, event)"></span>
483
554
  <span class="description">Đăng nhập</span>
484
555
  </div>
485
556
  <div class="panel-body">
@@ -685,7 +756,8 @@ API.getAuthentication().loginAsync("your account", "your password", loginResult
685
756
  <div class="panel panel-primary" id="extension-auth-logout">
686
757
  <div class="panel-heading" onclick="toggleParentClass.call(this)" data-toggled-class="show" data-hash="$auth.logout">
687
758
  <span>GET</span>
688
- <span class="root-url">{{rootUrl}}</span>auth/logout
759
+ <span class="url"><span class="root-url">{{rootUrl}}</span>auth/logout</span>
760
+ <span class="copy" title="Copy" data-copy=".url" onclick="doCopy.call(this, event)"></span>
689
761
  <span class="description">Đăng xuất</span>
690
762
  </div>
691
763
  <div class="panel-body">
@@ -849,7 +921,8 @@ API.getAuthentication().logoutAsync(logoutMode, () -> {
849
921
  <div class="panel panel-primary" id="extension-auth-status">
850
922
  <div class="panel-heading" onclick="toggleParentClass.call(this)" data-toggled-class="show" data-hash="$auth.status">
851
923
  <span>GET</span>
852
- <span class="root-url">{{rootUrl}}</span>auth/status
924
+ <span class="url"><span class="root-url">{{rootUrl}}</span>auth/status
925
+ <span class="copy" title="Copy" data-copy=".url" onclick="doCopy.call(this, event)"></span>
853
926
  <span class="description">Truy vấn trạng thái token</span>
854
927
  </div>
855
928
  <div class="panel-body">
@@ -995,7 +1068,8 @@ API.getAuthentication().statusAsync(loginStatus -> {
995
1068
  <div class="panel panel-primary" id="extension-auth-me">
996
1069
  <div class="panel-heading" onclick="toggleParentClass.call(this)" data-toggled-class="show" data-hash="$auth.me">
997
1070
  <span>GET</span>
998
- <span class="root-url">{{rootUrl}}</span>auth/me
1071
+ <span class="url"><span class="root-url">{{rootUrl}}</span>auth/me</span>
1072
+ <span class="copy" title="Copy" data-copy=".url" onclick="doCopy.call(this, event)"></span>
999
1073
  <span class="description">Lấy về thông tin người dùng hiện tại</span>
1000
1074
  </div>
1001
1075
  <div class="panel-body">
@@ -1144,7 +1218,8 @@ API.getAuthentication().meAsync(user -> {
1144
1218
  <div class="panel panel-info" id="extension-auth-register">
1145
1219
  <div class="panel-heading" onclick="toggleParentClass.call(this)" data-toggled-class="show" data-hash="$auth.register">
1146
1220
  <span>POST</span>
1147
- <span class="root-url">{{rootUrl}}</span>auth/register
1221
+ <span class="url"><span class="root-url">{{rootUrl}}</span>auth/register</span>
1222
+ <span class="copy" title="Copy" data-copy=".url" onclick="doCopy.call(this, event)"></span>
1148
1223
  <span class="description">Đăng ký tài khoản</span>
1149
1224
  </div>
1150
1225
  <div class="panel-body">
@@ -1324,7 +1399,8 @@ API.getAuthentication().registerAsync("your account", "your password", registerR
1324
1399
  <div class="panel panel-info" id="extension-auth-activate">
1325
1400
  <div class="panel-heading" onclick="toggleParentClass.call(this)" data-toggled-class="show" data-hash="$auth.activate">
1326
1401
  <span>POST</span>
1327
- <span class="root-url">{{rootUrl}}</span>auth/activate
1402
+ <span class="url"><span class="root-url">{{rootUrl}}</span>auth/activate</span>
1403
+ <span class="copy" title="Copy" data-copy=".url" onclick="doCopy.call(this, event)"></span>
1328
1404
  <span class="description">Kích hoạt tài khoản</span>
1329
1405
  </div>
1330
1406
  <div class="panel-body">
@@ -1483,7 +1559,8 @@ API.getAuthentication().activateAsync("your account", "activation code", id -> {
1483
1559
  <div class="panel panel-info" id="extension-auth-recover_password">
1484
1560
  <div class="panel-heading" onclick="toggleParentClass.call(this)" data-toggled-class="show" data-hash="$auth.recover_password">
1485
1561
  <span>POST</span>
1486
- <span class="root-url">{{rootUrl}}</span>auth/recover-password
1562
+ <span class="url"><span class="root-url">{{rootUrl}}</span>auth/recover-password</span>
1563
+ <span class="copy" title="Copy" data-copy=".url" onclick="doCopy.call(this, event)"></span>
1487
1564
  <span class="description">Lấy lại mật khẩu</span>
1488
1565
  </div>
1489
1566
  <div class="panel-body">
@@ -1636,7 +1713,8 @@ API.getAuthentication().recoverPasswordAsync("your account", id -> {
1636
1713
  <div class="panel panel-info" id="extension-auth-reset_password">
1637
1714
  <div class="panel-heading" onclick="toggleParentClass.call(this)" data-toggled-class="show" data-hash="$auth.reset_password">
1638
1715
  <span>POST</span>
1639
- <span class="root-url">{{rootUrl}}</span>auth/reset-password
1716
+ <span class="url"><span class="root-url">{{rootUrl}}</span>auth/reset-password</span>
1717
+ <span class="copy" title="Copy" data-copy=".url" onclick="doCopy.call(this, event)"></span>
1640
1718
  <span class="description">Khôi phục mật khẩu / Đổi mật khẩu</span>
1641
1719
  </div>
1642
1720
  <div class="panel-body">
@@ -1905,7 +1983,8 @@ API.getAuthentication().changePasswordAsync("your account", "your old password",
1905
1983
  <div class="panel panel-info" id="extension-file-upload">
1906
1984
  <div class="panel-heading" onclick="toggleParentClass.call(this)" data-toggled-class="show" data-hash="$file.upload">
1907
1985
  <span>POST</span>
1908
- <span class="root-url">{{rootUrl}}</span>file/upload
1986
+ <span class="url"><span class="root-url">{{rootUrl}}</span>file/upload</span>
1987
+ <span class="copy" title="Copy" data-copy=".url" onclick="doCopy.call(this, event)"></span>
1909
1988
  <span class="description">Upload file</span>
1910
1989
  </div>
1911
1990
  <div class="panel-body">
@@ -2093,7 +2172,8 @@ API.fileExtension().uploadAsync(file, uploadResult -> {
2093
2172
  <div class="panel panel-primary" id="extension-file-download">
2094
2173
  <div class="panel-heading" onclick="toggleParentClass.call(this)" data-toggled-class="show" data-hash="$file.download">
2095
2174
  <span>GET</span>
2096
- <span class="root-url">{{rootUrl}}</span>file/download
2175
+ <span class="url"><span class="root-url">{{rootUrl}}</span>file/download</span>
2176
+ <span class="copy" title="Copy" data-copy=".url" onclick="doCopy.call(this, event)"></span>
2097
2177
  <span class="description">Download file</span>
2098
2178
  </div>
2099
2179
  <div class="panel-body">
@@ -2101,6 +2181,7 @@ API.fileExtension().uploadAsync(file, uploadResult -> {
2101
2181
  <h4>Query string</h4>
2102
2182
  <ul>
2103
2183
  <li>id: string, required - Mã của file cần download</li>
2184
+ <li>inline: boolean, optional - Cho biết có download ở chế độ inline hay không. Xem thêm tại <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition" target="_blank">Content-Disposition</a></li>
2104
2185
  </ul>
2105
2186
  </div>
2106
2187
  <div>
@@ -2128,6 +2209,11 @@ Content-Range: byte {realStartIndex}-{realEndIndex}/{totalLength}
2128
2209
  </div>
2129
2210
  <div class="js-driver-snippet">
2130
2211
  <h4>Mẫu javascript</h4>
2212
+ Lấy về link download file
2213
+ <pre>mushroom.$file.getDownloadLink("your file id");</pre>
2214
+ Tùy biến lấy về link download inline mode
2215
+ <pre>mushroom.$file.linkBuilder.download.id("your file id").inline().build();</pre>
2216
+ Download file trên trình duyệt
2131
2217
  <pre>mushroom.$file.download("your file id");</pre>
2132
2218
  </div>
2133
2219
  </div>
@@ -2135,7 +2221,8 @@ Content-Range: byte {realStartIndex}-{realEndIndex}/{totalLength}
2135
2221
  <div class="panel panel-primary" id="extension-file-thumb">
2136
2222
  <div class="panel-heading" onclick="toggleParentClass.call(this)" data-toggled-class="show" data-hash="$file.thumb">
2137
2223
  <span>GET</span>
2138
- <span class="root-url">{{rootUrl}}</span>file/thumb
2224
+ <span class="url"><span class="root-url">{{rootUrl}}</span>file/thumb</span>
2225
+ <span class="copy" title="Copy" data-copy=".url" onclick="doCopy.call(this, event)"></span>
2139
2226
  <span class="description">Download image thumbnail</span>
2140
2227
  </div>
2141
2228
  <div class="panel-body">
@@ -2209,6 +2296,13 @@ Content-Range: byte {realStartIndex}-{realEndIndex}/{totalLength}
2209
2296
  <li>rotate270 - xoay ảnh 270 độ</li>
2210
2297
  </ul>
2211
2298
  </li>
2299
+ <li>
2300
+ o: string, optional, chỉ định các tùy chọn, nhận một trong các giá trị sau, có thể áp dụng nhiều tùy chọn, phân cách nhau bởi dấu phải
2301
+ <ul>
2302
+ <li>allow-smaller-size - cho phép sinh ảnh bé hơn kích thước quy định nếu ảnh gốc bé</li>
2303
+ <li>disable-cache - không sử dụng cache server mà yêu cầu sinh ảnh thumb mới</li>
2304
+ </ul>
2305
+ </li>
2212
2306
  </ul>
2213
2307
  </div>
2214
2308
  <div>
@@ -2233,23 +2327,28 @@ Last-Modified: {datetime}</pre>
2233
2327
  <div class="js-driver-snippet">
2234
2328
  <pre data-language="javascript">const imgSrc = mushroom.$file.linkBuilder.thumb
2235
2329
  .id(fileId) <span class="code-comment">// required
2236
- // .stretch(width, height)
2237
- // .square(size)
2238
- // .zoom(width, height, backgroundColor) // backgroundColor: optional, default: transparent, valid values: transparent | [0-9a-fA-F]{6}
2239
- // .x(width)
2240
- // .y(height)
2241
- // .clip(150, 75, "top", "left")
2242
- // // .clip(width, height, verticalAlign, horizontalAlign)
2243
- // // verticalAlign: optional, default: middle, valid values: top | middle | bottom
2244
- // // horizontalAlign: optional, default: center, valid values: left | center | right
2330
+ // .stretch(width, height) // width: gt 0; height: gt 0
2331
+ // .square(size) // size: gt 0
2332
+ // .zoom(width, height, backgroundColor)
2333
+ // width: gt: 0
2334
+ // height: gt 0
2335
+ // backgroundColor: optional, default: transparent, valid values: transparent | [0-9a-fA-F]{6}
2336
+ // .x(width) // width: gt 0
2337
+ // .y(height) // height: gt 0
2338
+ // .clip(width, height, verticalAlign, horizontalAlign)
2339
+ // width: gt 0
2340
+ // height: gt 0
2341
+ // verticalAlign: optional, default: middle, valid values: top | middle | bottom
2342
+ // horizontalAlign: optional, default: center, valid values: left | center | right
2245
2343
  // .rotate90()
2246
2344
  // .rotate180()
2247
2345
  // .rotate270()
2248
2346
  // .flipVertical()
2249
2347
  // .flipHorizontal()
2250
- // .transparent(50) // value: from 0 to 255
2348
+ // .transparent(value) // value: from 0 to 255
2251
2349
  // .grayscale()
2252
- // .format("png") // outputFormat: source | jpg | png | gif | webp | auto</span>
2350
+ // .allowSmallerSize()
2351
+ // .format(outputFormat) // outputFormat: source | jpg | png | gif | webp | auto</span>
2253
2352
  .build();</pre>
2254
2353
  </div>
2255
2354
  </div>
@@ -2321,7 +2420,8 @@ Tạo hiệu ứng đen trắng: &lt;img src="<span class="root-url">{{rootUrl}}
2321
2420
  <div class="panel panel-primary" id="extension-file-zip">
2322
2421
  <div class="panel-heading" onclick="toggleParentClass.call(this)" data-toggled-class="show" data-hash="$file.zip">
2323
2422
  <span>GET</span>
2324
- <span class="root-url">{{rootUrl}}</span>file/zip
2423
+ <span class="url"><span class="root-url">{{rootUrl}}</span>file/zip</span>
2424
+ <span class="copy" title="Copy" data-copy=".url" onclick="doCopy.call(this, event)"></span>
2325
2425
  <span class="description">Download zip file</span>
2326
2426
  </div>
2327
2427
  <div class="panel-body">
@@ -2330,6 +2430,7 @@ Tạo hiệu ứng đen trắng: &lt;img src="<span class="root-url">{{rootUrl}}
2330
2430
  <ul>
2331
2431
  <li>id: string, required, danh sách mã các file cần zip, các mã file được phân cách bởi dấu phảy (,)</li>
2332
2432
  <li>name: string, optional, tên file zip khi download. Mặc định là "No name.zip"</li>
2433
+ <li>inline: boolean, optional - Cho biết có download ở chế độ inline hay không. Xem thêm tại <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition" target="_blank">Content-Disposition</a></li>
2333
2434
  </ul>
2334
2435
  </div>
2335
2436
  <div>
@@ -2343,7 +2444,14 @@ Content-Disposition: attachment; filename="{name}"</pre>
2343
2444
  </div>
2344
2445
  <div class="js-driver-snippet">
2345
2446
  <h4>Mẫu javascript</h4>
2346
- <pre>mushroom.$file.zip(["your file id 1", "your file id 2"]);</pre>
2447
+ Lấy về link download zip file
2448
+ <pre>mushroom.$file.getZipLink("your file id 1", "your file id 2");</pre>
2449
+ Tùy biến lấy về link download zip file với chế độ inline
2450
+ <pre>mushroom.$file.linkBuilder.zip.add("your file id 1", "your file id 2").inline().build();</pre>
2451
+ Tùy biến lấy về link download zip file chỉ định tên file
2452
+ <pre>mushroom.$file.linkBuilder.zip.add("your file id 1", "your file id 2").attachment("file-name.zip").build();</pre>
2453
+ Download zip file trên trình duyệt
2454
+ <pre>mushroom.$file.zip("your file id 1", "your file id 2");</pre>
2347
2455
  </div>
2348
2456
  </div>
2349
2457
  </div>
@@ -2355,7 +2463,8 @@ Content-Disposition: attachment; filename="{name}"</pre>
2355
2463
  <div class="panel panel-info" id="extension-mailing-sent">
2356
2464
  <div class="panel-heading" onclick="toggleParentClass.call(this)" data-toggled-class="show" data-hash="$mailing.sent">
2357
2465
  <span>POST</span>
2358
- <span class="root-url">{{rootUrl}}</span>mailing/sent
2466
+ <span class="url"><span class="root-url">{{rootUrl}}</span>mailing/sent</span>
2467
+ <span class="copy" title="Copy" data-copy=".url" onclick="doCopy.call(this, event)"></span>
2359
2468
  <span class="description">Tạo lệnh gửi mail</span>
2360
2469
  </div>
2361
2470
  <div class="panel-body">
@@ -2480,7 +2589,8 @@ try {
2480
2589
  <div class="panel panel-primary" id="extension-mailing-get">
2481
2590
  <div class="panel-heading" onclick="toggleParentClass.call(this)" data-toggled-class="show" data-hash="$mailing.get">
2482
2591
  <span>GET</span>
2483
- <span class="root-url">{{rootUrl}}</span>mailing/get
2592
+ <span class="url"><span class="root-url">{{rootUrl}}</span>mailing/get</span>
2593
+ <span class="copy" title="Copy" data-copy=".url" onclick="doCopy.call(this, event)"></span>
2484
2594
  <span class="description">Lấy thông tin lệnh gửi mail</span>
2485
2595
  </div>
2486
2596
  <div class="panel-body">
@@ -2563,7 +2673,8 @@ catch (error) {
2563
2673
  <div class="panel panel-danger" id="extension-mailing-discard">
2564
2674
  <div class="panel-heading" onclick="toggleParentClass.call(this)" data-toggled-class="show" data-hash="$mailing.discard">
2565
2675
  <span>DELETE</span>
2566
- <span class="root-url">{{rootUrl}}</span>mailing/discard
2676
+ <span class="url"><span class="root-url">{{rootUrl}}</span>mailing/discard</span>
2677
+ <span class="copy" title="Copy" data-copy=".url" onclick="doCopy.call(this, event)"></span>
2567
2678
  <span class="description">Hủy lệnh gửi mail</span>
2568
2679
  </div>
2569
2680
  <div class="panel-body">
@@ -2668,7 +2779,12 @@ catch (error) {
2668
2779
 
2669
2780
  <div class="mushroom-docs-template" id="template-project-entity">
2670
2781
  <div class="entity" id="entity-{{name}}">
2671
- <h2 data-hash="{{name}}" onclick="renderEntity.call(this, '{{name}}'); toggleParentClass.call(this)" data-toggled-class="expand">{{name}}</h2>
2782
+ <h2 data-hash="{{name}}" onclick="renderEntity.call(this, '{{name}}'); toggleParentClass.call(this)" data-toggled-class="expand">
2783
+ <span class="entity-name">{{name}}</span>
2784
+ <span class="copy" title="Copy" data-copy=".entity-name" onclick="doCopy.call(this, event)"></span>
2785
+ <div class="description">{{description}}</div>
2786
+ </h2>
2787
+ <div class="description">{{description}}</div>
2672
2788
  <div class="project-entity-actions"></div>
2673
2789
  <div class="project-entity-views"></div>
2674
2790
  </div>
@@ -2679,6 +2795,7 @@ catch (error) {
2679
2795
  <div class="panel-heading" onclick="renderEntityAction.call(this, '{{entityName}}', '{{actionName}}'); toggleParentClass.call(this)" data-toggled-class="show" data-hash="{{entityName}}.{{actionName}}">
2680
2796
  {{actionHttpMethod}}
2681
2797
  <span class="contain-root-url">{{actionApiUrl}}</span>
2798
+ <span class="copy" title="Copy" data-copy=".contain-root-url" onclick="doCopy.call(this, event)"></span>
2682
2799
  <span class="description">{{actionDescription}}</span>
2683
2800
  </div>
2684
2801
  <div class="panel-body">
@@ -2689,7 +2806,7 @@ catch (error) {
2689
2806
  </div>
2690
2807
  </div>
2691
2808
  <div class="mushroom-docs-template" id="template-project-entity-action-role">
2692
- <li role="presentation"><a onclick="renderEntityActionRolePage.call(this, '{{entityName}}', '{{actionName}}', '{{grant}}'); activeRoleTab.call(this)" data-role="{{grant}}">{{explainedRole}}</a></li>
2809
+ <li role="presentation"><a onclick="renderEntityActionRolePage.call(this, '{{entityName}}', '{{actionName}}', '{{grant}}'); activeRoleTab.call(this)" data-hash="{{entityName}}.{{actionName}}/{{grant}}" data-role="{{grant}}">{{explainedRole}}</a></li>
2693
2810
  </div>
2694
2811
  <div class="mushroom-docs-template" id="template-project-entity-action-page">
2695
2812
  <div class="tab-page" data-role="{{grant}}"></div>
@@ -2733,9 +2850,9 @@ catch (e) {
2733
2850
  Tùy biến tham số
2734
2851
  <pre data-language="javascript">try {
2735
2852
  const response = await mushroom.{{entityName}}.listAsync({
2736
- fields: "<span class="separated-items">{{fields:html}}</span>",
2737
- filters: ["your filter 1", "your filter 2"], // <i>or</i> "your filters" ,<span style="display: {{allowSortingsDisplay}}">
2738
- sort: "<span class="separated-items">{{sortFields:html}}</span>",</span>
2853
+ fields: "<span class="separated-items">{{fields:html}}</span>", <span class="code-comment">// nên sử dụng builder dạng Project.include('a', 'b').build()</span>
2854
+ filters: ["your filter 1", "your filter 2"], <span class="code-comment">// nên sử dụng builder dạng Filter.eq('a', 1).gte('b', 2).build()</span><span style="display: {{allowSortingsDisplay}}">
2855
+ sort: "<span class="separated-items">{{sortFields:html}}</span>", <span class="code-comment">// nên sử dụng builder dạng Sort.ascending('a').thenByAscending('b').build()</span></span>
2739
2856
  <span style="display: {{limitDisplay}}"> limit: 25,
2740
2857
  </span><span style="display: {{offsetDisplay}}"> offset: 0,
2741
2858
  </span><span style="display: {{pageDisplay}}"> page: 1,
@@ -4153,8 +4270,7 @@ catch (Exception ex)
4153
4270
  <div class="http-snippet">
4154
4271
  Xóa nhật một {{entityName}}
4155
4272
  <pre data-language="http">DELETE {{rootUrlPath}}{{entityName}}s/<span class="place-holder">id</span> HTTP/1.1
4156
- Host: <span class="domain">{{domain}}</span>
4157
- Content-Type: application/json<span style="display: {{tokenDisplay}}">
4273
+ Host: <span class="domain">{{domain}}</span><span style="display: {{tokenDisplay}}">
4158
4274
  Token: your token</span></pre>
4159
4275
  </div>
4160
4276
  </script>
@@ -4162,8 +4278,7 @@ Token: your token</span></pre>
4162
4278
  <div class="curl-snippet">
4163
4279
  cURL
4164
4280
  <pre data-language="cURL">curl -X DELETE \
4165
- '<span class="root-url">{{rootUrl}}</span>{{entityName}}s/<span class="place-holder">id</span>' \
4166
- -H 'Content-Type: application/json' \ <span style="display: {{tokenDisplay}}">
4281
+ '<span class="root-url">{{rootUrl}}</span>{{entityName}}s/<span class="place-holder">id</span>' \<span style="display: {{tokenDisplay}}">
4167
4282
  -H 'Token: {<span>{</span>TOKEN}}' \</span></pre>
4168
4283
  Shell
4169
4284
  <pre data-language="cURL">curl --request DELETE \
@@ -4389,12 +4504,8 @@ API.get{{EntityName}}().deleteAsync(arg, count -> {
4389
4504
  Code tham khảo:
4390
4505
  <pre data-language="javascript">try {
4391
4506
  const response = await mushroom.{{entityName}}.{{httpMethod}}Async({<span style="display: {{paramDisplay}}">
4392
- params: {
4393
- <span class="code-comment">// parameters here</span>
4394
- },</span><span style="display: {{bodyDisplay}}">
4395
- body: {
4396
- <span class="code-comment">// request body here</span>
4397
- }</span>
4507
+ params: {{paramModel:html}},</span><span style="display: {{bodyDisplay}}">
4508
+ body: {{bodyModel:html}}</span>
4398
4509
  });
4399
4510
  console.log("Kết quả trả về, response: %o", response);
4400
4511
  } catch (error) {
@@ -4402,6 +4513,16 @@ API.get{{EntityName}}().deleteAsync(arg, count -> {
4402
4513
  }</pre>
4403
4514
  </div>
4404
4515
  </script>
4516
+ <script type="text/mushroom-docs-template" class="mushroom-docs-template" id="template-codeSnippets-generalAction-JavascriptModel">
4517
+ <div class="js-model-snippet">
4518
+ Javascript UNDEFINED model
4519
+ <pre data-language="json">{{undefinedModel:html}}</pre>
4520
+ Javascript VALUE model
4521
+ <pre data-language="json">{{valueModel:html}}</pre>
4522
+ Javascript NULL model
4523
+ <pre data-language="json">{{nullModel:html}}</pre>
4524
+ </div>
4525
+ </script>
4405
4526
  <div class="mushroom-docs-template" id="template-codeSnippets-generalAction-CSharp">
4406
4527
  <div class="csharp-driver-snippet">
4407
4528
  <p>Hỗ trợ: DotNet Framework 4.5+, DotNet Core 3.1+, Unity3D, Mono</p>
@@ -4424,31 +4545,30 @@ catch (Exception ex)
4424
4545
  <script type="text/mushroom-docs-template" class="mushroom-docs-template" id="template-codeSnippets-generalAction-HTTP">
4425
4546
  <div class="http-snippet">
4426
4547
  Code tham khảo:
4427
- <pre data-language="http">POST {{rootUrlPath}}{{entityName}}s/{{httpMethod}}<span class="place-holder">?param1=value1&amp;param2=value2</span> HTTP/1.1
4548
+ <pre data-language="http">POST {{rootUrlPath}}{{entityName}}s/{{httpMethod}}{{queryString}} HTTP/1.1
4428
4549
  Host: <span class="domain">{{domain}}</span>
4429
4550
  Content-Type: application/json
4430
4551
  X-HTTP-Method-Override: {{httpMethod}}<span style="display: {{tokenDisplay}}">
4431
4552
  Token: your token</span>
4432
4553
 
4433
- <span class="place-holder">BODY DATA IF ANY</span></pre>
4554
+ {{bodyModel:html}}</pre>
4434
4555
  </div>
4435
4556
  </script>
4436
4557
  <script type="text/mushroom-docs-template" class="mushroom-docs-template" id="template-codeSnippets-generalAction-cURL">
4437
4558
  <div class="curl-snippet">
4438
4559
  cURL
4439
4560
  <pre data-language="cURL">curl -X POST \
4440
- '<span class="root-url">{{rootUrl}}</span>{{entityName}}s/{{httpMethod}}<span class="place-holder">?param1=value1&amp;param2=value2</span>' \
4561
+ '<span class="root-url">{{rootUrl}}</span>{{entityName}}s/{{httpMethod}}{{queryString}}' \
4441
4562
  -H 'Content-Type: application/json' \
4442
4563
  -H 'X-HTTP-Method-Override: {{httpMethod}}' \<span style="display: {{tokenDisplay}}">
4443
4564
  -H 'Token: {<span>{</span>TOKEN}}' \</span>
4444
- -d 'BODY DATA IF ANY'
4445
- </pre>
4565
+ -d '{{bodyModel:html}}'</pre>
4446
4566
  Shell
4447
4567
  <pre data-language="cURL">curl --request POST \
4448
- --url '<span class="root-url">{{rootUrl}}</span>{{entityName}}s/{{httpMethod}}<span class="place-holder">?param1=value1&amp;param2=value2</span>' \
4568
+ --url '<span class="root-url">{{rootUrl}}</span>{{entityName}}s/{{httpMethod}}{{queryString}}' \
4449
4569
  --header 'X-HTTP-Method-Override: {{httpMethod}}' \<span style="display: {{tokenDisplay}}">
4450
4570
  --header 'Token: {<span>{</span>TOKEN}}' \</span>
4451
- --data 'BODY DATA IF ANY'</pre>
4571
+ --data '{{bodyModel:html}}'</pre>
4452
4572
  </div>
4453
4573
  </script>
4454
4574
  <div class="mushroom-docs-template" id="template-codeSnippets-generalAction-Java">
@@ -4989,7 +5109,7 @@ API.get{{EntityName}}().getViews().{{viewName}}Async(arg, result -> {
4989
5109
  </div>
4990
5110
  <div class="query-string">
4991
5111
  <h4>Query string</h4>
4992
- <pre class="query-string-model"></pre>
5112
+ <ul class="parameters-list"></ul>
4993
5113
  </div>
4994
5114
  <div class="request-body">
4995
5115
  <h4>Request body</h4>
@@ -5025,7 +5145,7 @@ API.get{{EntityName}}().getViews().{{viewName}}Async(arg, result -> {
5025
5145
  </div>
5026
5146
  <div class="mushroom-docs-template" id="template-view-parameters">
5027
5147
  <li>
5028
- <b>{{field}}</b>: {{type}}, {{required}}{{defaultValue}} - {{description}}
5148
+ <b>{{field}}</b>: {{type}}, {{required}}{{defaultValue}}, {{description}}
5029
5149
  </li>
5030
5150
  </div>
5031
5151
  <div class="mushroom-docs-template" id="template-view">
@@ -182,6 +182,7 @@ function getModelOutput(arrFields, fields, options) {
182
182
 
183
183
  return formatModel(obj, '', options).trim();
184
184
  }
185
+ let __field_idx = 0;
185
186
  function formatModel(obj, strIndent, options) {
186
187
  if (!obj) return '';
187
188
 
@@ -270,7 +271,7 @@ function formatModel(obj, strIndent, options) {
270
271
  ' ' +
271
272
  comment +
272
273
  `\r\n${nextIndent}`;
273
- result += `]`;
274
+ result += '],';
274
275
  } else if (child.type === 'datetime')
275
276
  result +=
276
277
  `\r\n${nextIndent}"${m}": ` + JSON.stringify(child.defaultValue || 'ISODate_ISO_8601') + ', ' + comment;
@@ -281,7 +282,7 @@ function formatModel(obj, strIndent, options) {
281
282
  else result += `\r\n${nextIndent}"${m}": ` + JSON.stringify(child.defaultValue || child.type) + ', ' + comment;
282
283
  } else if (options.mode === 'model_null') {
283
284
  if (child.type === 'object')
284
- result += '\r\n' + nextIndent + '"' + m + '": ' + comment + formatModel(child.fields, nextIndent, options);
285
+ result += '\r\n' + nextIndent + '"' + m + '": ' + comment + formatModel(child.fields, nextIndent, options) + ',';
285
286
  else if (child.type === 'array') {
286
287
  result += `\r\n${nextIndent}"${m}": [`;
287
288
  if (child.items.type === 'object')
@@ -290,7 +291,7 @@ function formatModel(obj, strIndent, options) {
290
291
  } else result += `\r\n${nextIndent}"${m}": null, ${comment}`;
291
292
  } else if (options.mode === 'model_undefined') {
292
293
  if (child.type === 'object')
293
- result += '\r\n' + nextIndent + '"' + m + '": ' + comment + formatModel(child.fields, nextIndent, options);
294
+ result += '\r\n' + nextIndent + '"' + m + '": ' + comment + formatModel(child.fields, nextIndent, options) + ',';
294
295
  else if (child.type === 'array') {
295
296
  result += `\r\n${nextIndent}"${m}": [`;
296
297
  if (child.items.type === 'object')
@@ -298,11 +299,13 @@ function formatModel(obj, strIndent, options) {
298
299
  else result += '], ' + comment;
299
300
  } else result += `\r\n${nextIndent}"${m}": undefined, ${comment}`;
300
301
  } else {
301
- result += '\r\n' + strIndent + encodeHtml(m) + ': ' + encodeHtml(child.type);
302
+ const fieldIdx = 'f_' + __field_idx.toString(36);
303
+ __field_idx++;
304
+ result += '\r\n' + strIndent + (child.type === 'object' || child.type === 'array' && child.items.type === 'object' ? '<span class="collapsable-field" data-field-idx="' + fieldIdx + '" onclick="collapseThisField(this)">' + encodeHtml(m) + '</span>' : encodeHtml(m)) + ': ' + encodeHtml(child.type);
302
305
  if (child.type === 'array') result += child.items.type === 'object' ? ' of object' : ' of ' + child.items.type;
303
- if (child.type === 'object') result += ' ' + comment + formatModel(child.fields, strIndent + ' ', options);
306
+ if (child.type === 'object') result += ' ' + comment + '<span id="' + fieldIdx + '">' + formatModel(child.fields, strIndent + ' ', options) + '</span>';
304
307
  else if (child.type === 'array' && child.items.type === 'object')
305
- result += ' ' + comment + formatModel(child.items.fields, strIndent + ' ', options);
308
+ result += ' ' + comment + '<span id="' + fieldIdx + '">' + formatModel(child.items.fields, strIndent + ' ', options) + '</span>';
306
309
  else result += ', ' + comment;
307
310
  }
308
311
  }
@@ -313,6 +316,10 @@ function formatModel(obj, strIndent, options) {
313
316
 
314
317
  return result;
315
318
  }
319
+ function collapseThisField(dom) {
320
+ dom.classList.toggle('collapsed');
321
+ document.getElementById(dom.dataset.fieldIdx).classList.toggle('collapsed');
322
+ }
316
323
  function toCachePolicyString(cachePolicy) {
317
324
  if (cachePolicy === undefined || cachePolicy === null || cachePolicy === 'unset') return 'Không thiết lập cấu hình';
318
325
  if (cachePolicy === 'required') return 'Bắt buộc phải cache';
@@ -387,7 +394,7 @@ function formatHintModel(obj, strIndent, options) {
387
394
  validation += ', enum(' + encodeHtml(child.items.validValues.join(', ')) + ')';
388
395
  }
389
396
 
390
- var comment = `<span class="code-comment">// ${validation} ${typeof child.description === 'string' && child.description !== '' ? '- ' + encodeHtml(child.description).replace(/\n/g, ' ') : ''}</span>`;
397
+ var comment = options.hideComment ? '' : `<span class="code-comment">// ${validation} ${typeof child.description === 'string' && child.description !== '' ? '- ' + encodeHtml(child.description).replace(/\n/g, ' ') : ''}</span>`;
391
398
 
392
399
  if (options.mode === 'model') {
393
400
  if (child.type === 'object')
@@ -415,7 +422,7 @@ function formatHintModel(obj, strIndent, options) {
415
422
  ' ' +
416
423
  comment +
417
424
  `\r\n${nextIndent}`;
418
- result += `]`;
425
+ result += '],';
419
426
  } else if (child.type === 'datetime')
420
427
  result +=
421
428
  `\r\n${nextIndent}"${m}": ` + JSON.stringify(child.defaultValue || 'ISODate_ISO_8601') + ', ' + comment;
@@ -426,7 +433,7 @@ function formatHintModel(obj, strIndent, options) {
426
433
  else result += `\r\n${nextIndent}"${m}": ` + JSON.stringify(child.defaultValue || child.type) + ', ' + comment;
427
434
  } else if (options.mode === 'model_null') {
428
435
  if (child.type === 'object')
429
- result += '\r\n' + nextIndent + '"' + m + '": ' + comment + formatHintModel(child.fields, nextIndent, options);
436
+ result += '\r\n' + nextIndent + '"' + m + '": ' + comment + formatHintModel(child.fields, nextIndent, options) + ',';
430
437
  else if (child.type === 'array') {
431
438
  result += `\r\n${nextIndent}"${m}": [`;
432
439
  if (child.items.type === 'object')
@@ -435,7 +442,7 @@ function formatHintModel(obj, strIndent, options) {
435
442
  } else result += `\r\n${nextIndent}"${m}": null, ${comment}`;
436
443
  } else if (options.mode === 'model_undefined') {
437
444
  if (child.type === 'object')
438
- result += '\r\n' + nextIndent + '"' + m + '": ' + comment + formatHintModel(child.fields, nextIndent, options);
445
+ result += '\r\n' + nextIndent + '"' + m + '": ' + comment + formatHintModel(child.fields, nextIndent, options) + ",";
439
446
  else if (child.type === 'array') {
440
447
  result += `\r\n${nextIndent}"${m}": [`;
441
448
  if (child.items.type === 'object')
@@ -490,7 +497,8 @@ function renderInfo() {
490
497
  let selected = localStorage.selected_root_url;
491
498
  if (!selected) selected = localStorage.selected_root_url = window.docs.info.rootUrl;
492
499
  const onspanclick = function () {
493
- this.parentNode.parentNode.querySelector('.selected').className = '';
500
+ const domSelected = this.parentNode.parentNode.querySelector('.selected');
501
+ if (domSelected) domSelected.className = '';
494
502
  selected = this.innerText;
495
503
  this.className = 'selected';
496
504
  const old_root_url = localStorage.selected_root_url;
@@ -745,20 +753,28 @@ function renderEntityActionRolePage(entityName, actionName, grant) {
745
753
  domain: getApiDomain(localStorage.selected_root_url),
746
754
  rootUrlPath: getApiPath(localStorage.selected_root_url),
747
755
  };
748
- const snippet = window.codeSnippets[codeSnippetName] ?? {};
756
+ const snippets = window.codeSnippets[codeSnippetName]
757
+ ? Object.keys(window.codeSnippets[codeSnippetName]).map((lang) => {
758
+ return {
759
+ lang: lang,
760
+ html: window.codeSnippets[codeSnippetName][lang]?.call(null, args)
761
+ }
762
+ }).filter(item => item.html)
763
+ : [];
764
+
749
765
  const data = {
750
766
  explainedDenyRoles: explainRoles(action.deny),
751
767
  entityName,
752
- codeSnippetTabHeaders: Object.keys(snippet)
768
+ codeSnippetTabHeaders: snippets
753
769
  .map(
754
- (lang, idx) =>
755
- `<li role="presentation" ${idx ? '' : 'class="active"'}><a data-role="${lang}" onclick="activeRoleTab.call(this)">${lang}</a></li>`,
770
+ (item, idx) =>
771
+ `<li role="presentation" ${idx ? '' : 'class="active"'}><a data-role="${item.lang}" onclick="activeRoleTab.call(this)" data-hash="${entityName}.${actionName}/${grant}/${item.lang}">${item.lang}</a></li>`,
756
772
  )
757
773
  .join(''),
758
- codeSnippetTabPages: Object.keys(snippet)
774
+ codeSnippetTabPages: snippets
759
775
  .map(
760
- (lang, idx) =>
761
- `<div class="tab-page ${lang} ${idx ? '' : 'active'}" data-role="${lang}">${snippet[lang]?.call(null, args) ?? ''}</div>`,
776
+ (item, idx) =>
777
+ `<div class="tab-page ${item.lang} ${idx ? '' : 'active'}" data-role="${item.lang}">${item.html}</div>`,
762
778
  )
763
779
  .join(''),
764
780
  EntityName: args.EntityName,
@@ -1005,9 +1021,16 @@ function renderDeleteMany(args) {}
1005
1021
 
1006
1022
  function renderGeneralAction(args) {
1007
1023
  if (args.action?.codeHint?.delegator?.param?.fields) {
1008
- args.tabPage.querySelector('.query-string-model').innerHTML = formatHintModel(
1009
- args.action.codeHint.delegator.param.fields,
1010
- ).trim();
1024
+ args.tabPage.querySelector('.parameters-list').innerHTML = Object.keys(args.action.codeHint.delegator.param.fields)
1025
+ .map((m) => {
1026
+ const field = args.action.codeHint.delegator.param.fields[m];
1027
+ return renderTemplate('template-view-parameters', {
1028
+ field: m,
1029
+ type: (field.type === 'array' ? 'array of ' : '') + (field.items?.type ?? 'string'),
1030
+ description: field.description ?? '',
1031
+ required: field.required ? 'required' : 'optional',
1032
+ })
1033
+ }).join('');
1011
1034
  } else {
1012
1035
  args.tabPage.querySelector('.query-string')?.remove();
1013
1036
  }
@@ -1022,7 +1045,6 @@ function renderGeneralAction(args) {
1022
1045
  } else {
1023
1046
  args.tabPage.querySelector('.request-body')?.remove();
1024
1047
  }
1025
- console.log('args.action.codeHint.delegator', args.action.codeHint.delegator);
1026
1048
  if (args.action?.codeHint?.delegator?.result?.fields) {
1027
1049
  args.tabPage.querySelector('.response-body-model').innerHTML = formatHintModel(
1028
1050
  args.action.codeHint.delegator.result.fields,
@@ -1036,6 +1058,17 @@ function renderGeneralAction(args) {
1036
1058
  }
1037
1059
  }
1038
1060
 
1061
+ function genQueryString(param) {
1062
+ if (!param) return '';
1063
+ if (!param.fields) return '';
1064
+
1065
+ const queryString = Object.keys(param.fields).map(key => {
1066
+ const type = param.fields[key].type === 'array' ? 'array_of_' + (param.fields[key].items?.type ?? 'value') : param.fields[key].type;
1067
+ return `${key}=${type}`;
1068
+ }).join("&");
1069
+ return queryString ? '?' + queryString : '';
1070
+ }
1071
+
1039
1072
  function renderEntityView(entityName, viewName) {
1040
1073
  if (this.dataset.rendered) return;
1041
1074
  this.dataset.rendered = true;
@@ -1597,7 +1630,33 @@ window.codeSnippets = {
1597
1630
  httpMethod: getHttpMethod(args.actionName),
1598
1631
  paramDisplay: args.action?.codeHint?.delegator?.param?.fields ? 'inline' : 'none',
1599
1632
  bodyDisplay: args.action?.codeHint?.delegator?.requestBody?.fields ? 'inline' : 'none',
1600
- }),
1633
+ paramModel: formatHintModel(args.action.codeHint?.delegator?.param?.fields, ' ', { mode: 'model' }).trim(),
1634
+ bodyModel: formatHintModel(args.action.codeHint?.delegator?.requestBody?.fields, ' ', { mode: 'model' }).trim(),
1635
+ }),
1636
+ JavascriptParamModel: (args) =>
1637
+ args.action?.codeHint?.delegator?.param?.fields
1638
+ ? renderTemplate('template-codeSnippets-generalAction-JavascriptModel', {
1639
+ undefinedModel: formatHintModel(args.action.codeHint.delegator.param.fields, undefined, { mode: 'model_undefined' }),
1640
+ valueModel: formatHintModel(args.action.codeHint.delegator.param.fields, undefined, { mode: 'model' }),
1641
+ nullModel: formatHintModel(args.action.codeHint.delegator.param.fields, undefined, { mode: 'model_null' }),
1642
+ })
1643
+ : '',
1644
+ JavascriptRequestBodyModel: (args) =>
1645
+ args.action?.codeHint?.delegator?.requestBody?.fields
1646
+ ? renderTemplate('template-codeSnippets-generalAction-JavascriptModel', {
1647
+ undefinedModel: formatHintModel(args.action.codeHint.delegator.requestBody.fields, undefined, { mode: 'model_undefined' }),
1648
+ valueModel: formatHintModel(args.action.codeHint.delegator.requestBody.fields, undefined, { mode: 'model' }),
1649
+ nullModel: formatHintModel(args.action.codeHint.delegator.requestBody.fields, undefined, { mode: 'model_null' }),
1650
+ })
1651
+ : '',
1652
+ JavascriptResultModel: (args) =>
1653
+ args.action?.codeHint?.delegator?.result?.fields
1654
+ ? renderTemplate('template-codeSnippets-generalAction-JavascriptModel', {
1655
+ undefinedModel: formatHintModel(args.action.codeHint.delegator.result.fields, undefined, { mode: 'model_undefined' }),
1656
+ valueModel: formatHintModel(args.action.codeHint.delegator.result.fields, undefined, { mode: 'model' }),
1657
+ nullModel: formatHintModel(args.action.codeHint.delegator.result.fields, undefined, { mode: 'model_null' }),
1658
+ })
1659
+ : '',
1601
1660
  CSharp: (args) =>
1602
1661
  renderTemplate('template-codeSnippets-generalAction-CSharp', {
1603
1662
  entityName: args.entityName,
@@ -1609,14 +1668,18 @@ window.codeSnippets = {
1609
1668
  domain: args.domain,
1610
1669
  rootUrlPath: args.rootUrlPath,
1611
1670
  entityName: args.entityName,
1671
+ queryString: genQueryString(args.action.codeHint?.delegator?.param),
1612
1672
  httpMethod: getHttpMethod(args.actionName),
1673
+ bodyModel: formatHintModel(args.action.codeHint?.delegator?.requestBody?.fields, undefined, { mode: 'model', hideComment: true }),
1613
1674
  tokenDisplay: args.action.grant !== '*' ? 'inline' : 'none',
1614
1675
  }),
1615
1676
  cURL: (args) =>
1616
1677
  renderTemplate('template-codeSnippets-generalAction-cURL', {
1617
1678
  rootUrl: localStorage.selected_root_url,
1618
1679
  entityName: args.entityName,
1680
+ queryString: genQueryString(args.action.codeHint?.delegator?.param),
1619
1681
  httpMethod: getHttpMethod(args.actionName),
1682
+ bodyModel: formatHintModel(args.action.codeHint?.delegator?.requestBody?.fields, undefined, { mode: 'model', hideComment: true }),
1620
1683
  tokenDisplay: args.action.grant !== '*' ? 'inline' : 'none',
1621
1684
  }),
1622
1685
  Java: (args) =>
@@ -1767,6 +1830,10 @@ function activeRoleTab() {
1767
1830
  return $(this).data('role') === role;
1768
1831
  })
1769
1832
  .addClass('active');
1833
+ var hash = $(this).data('hash');
1834
+ if (hash) {
1835
+ location.hash = hash;
1836
+ }
1770
1837
  }
1771
1838
 
1772
1839
  function activeFirstChild() {
@@ -1781,7 +1848,7 @@ function activeFirstChild() {
1781
1848
  function gotoHash() {
1782
1849
  if (!location.hash) return;
1783
1850
 
1784
- const m = location.hash.match(/^#(\$[\w-]+|\w+)(\.((views\.)?\w+))?$/);
1851
+ const m = location.hash.match(/^#(\$[\w-]+|\w+)(\.((views\.)?\w+)(\/\*?[\/\w\+\|]*)?)?$/);
1785
1852
  if (!m) return;
1786
1853
 
1787
1854
  const cardSelector = m[1].charAt(0) === '$' ? '#extension-' + m[1].substring(1) : '#entity-' + m[1];
@@ -1820,6 +1887,55 @@ function gotoHash() {
1820
1887
  if (isNaN(stickyTop)) stickyTop = 0;
1821
1888
  }
1822
1889
  window.scrollTo(0, top - stickyTop);
1890
+
1891
+ if (m[5]) {
1892
+ const segs = m[5].substring(1).split('/');
1893
+ const role = segs[0];
1894
+ if (!role) return;
1895
+ const snippet = segs[1];
1896
+
1897
+ const aRole = document.querySelector(`${panelSelector} .panel-body .nav-tabs a[data-role="${role}"]`);
1898
+ if (aRole && aRole.onclick) aRole.onclick();
1899
+
1900
+ if (!snippet) return;
1901
+
1902
+ const aSnippet = document.querySelector(`${panelSelector} .panel-body .tab-pages .tab-page[data-role="${role}"] .nav-tabs a[data-role="${snippet}"]`);
1903
+ if (aSnippet && aSnippet.onclick) aSnippet.onclick();
1904
+ }
1905
+ }
1906
+
1907
+ function doCopy(e) {
1908
+ if (!this.dataset.copy) return;
1909
+
1910
+ e.stopPropagation();
1911
+ if (location.protocol !== 'https:' && location.protocol !== 'file:') {
1912
+ alert('Can not copy, please use https protocol!');
1913
+ return;
1914
+ }
1915
+
1916
+ const copyDom = this.parentNode.querySelector(this.dataset.copy);
1917
+
1918
+ if (window.ClipboardItem) {
1919
+ const content = copyDom.innerText;
1920
+ const blob = new Blob([content], { type: 'text/plain' });
1921
+ const text = new ClipboardItem({ 'text/plain': blob });
1922
+ navigator.clipboard.write([text]);
1923
+ } else {
1924
+ if (document.body.createTextRange) {
1925
+ const range = document.body.createTextRange();
1926
+ range.moveToElementText(copyDom);
1927
+ range.select();
1928
+ } else if (window.getSelection) {
1929
+ const selection = window.getSelection();
1930
+ const range = document.createRange();
1931
+ range.selectNodeContents(copyDom);
1932
+ selection.removeAllRanges();
1933
+ selection.addRange(range);
1934
+ }
1935
+ document.execCommand('copy');
1936
+ }
1937
+
1938
+ copied();
1823
1939
  }
1824
1940
 
1825
1941
  function copied() {
@@ -16,9 +16,16 @@ function reserveWhitelist(obj, whitelist) {
16
16
  return obj;
17
17
  }
18
18
  const environmentVariableWhitelist = ['type', 'description'];
19
- function filterEnvironmentVariables(environmentVariables) {
19
+ function filterEnvironmentVariables(environmentVariables, allowRoles) {
20
20
  if (!environmentVariables)
21
21
  return;
22
+ if (!allowRoles.includes('*')) {
23
+ Object.keys(environmentVariables).forEach((envName) => {
24
+ if (environmentVariables[envName].codeHint?.roles?.every((role) => !allowRoles.includes(role))) {
25
+ delete environmentVariables[envName];
26
+ }
27
+ });
28
+ }
22
29
  Object.values(environmentVariables).forEach((env) => reserveWhitelist(env, environmentVariableWhitelist));
23
30
  }
24
31
  const entityWhitelist = ['name', 'description', 'actions', 'views', 'features', 'fields'];
@@ -145,7 +152,9 @@ function markIncludedFields(excludedFields, dict) {
145
152
  if (!Array.isArray(excludedFields))
146
153
  return;
147
154
  for (const m in dict) {
148
- if (!dict[m] && !excludedFields.includes(m))
155
+ if (!dict[m] &&
156
+ !excludedFields.includes(m) &&
157
+ excludedFields.every((excludedField) => !m.startsWith(excludedField + '.')))
149
158
  dict[m] = true;
150
159
  }
151
160
  }
@@ -198,7 +207,18 @@ function filterEntityFields(entity) {
198
207
  case 'updateOne':
199
208
  case 'updateMany':
200
209
  case 'updatePartially':
201
- actions.forEach((action) => (action.exclude = action.exclude?.filter((field) => allowFields.includes(field))));
210
+ actions.forEach((action) => {
211
+ action.exclude = action.exclude?.filter((field) => {
212
+ let idx = 0;
213
+ do {
214
+ idx = field.indexOf('.', idx + 1);
215
+ const prefix = idx === -1 ? field : field.substring(0, idx);
216
+ if (allowFields.includes(prefix))
217
+ return true;
218
+ } while (idx !== -1);
219
+ return false;
220
+ });
221
+ });
202
222
  break;
203
223
  }
204
224
  });
@@ -250,7 +270,8 @@ export default async function genDocs(args, projectName, project, alias, source)
250
270
  throw new Error(`Not found gen docs configuration. Please run 'npx mushroom-cli config ${projectName} gen docs OUTPUT_FILE.html${alias === 'default' ? '' : ' alias ' + alias}' first`);
251
271
  }
252
272
  common.logInfo('Use GenDocEngine v1.2');
253
- const entities = common.getEntities(source, aliasProject.allowRoles);
273
+ const allowRoles = aliasProject.allowRoles ?? ['*'];
274
+ const entities = common.getEntities(source, allowRoles);
254
275
  if (Array.isArray(aliasProject.entities)) {
255
276
  for (const entityName in entities) {
256
277
  if (Object.hasOwnProperty.call(entities, entityName)) {
@@ -260,7 +281,7 @@ export default async function genDocs(args, projectName, project, alias, source)
260
281
  }
261
282
  }
262
283
  }
263
- filterEnvironmentVariables(source.environmentVariables);
284
+ filterEnvironmentVariables(source.environmentVariables, allowRoles);
264
285
  filterEntities(entities);
265
286
  const data = JSON.stringify({
266
287
  info: {
@@ -275,7 +296,7 @@ export default async function genDocs(args, projectName, project, alias, source)
275
296
  },
276
297
  },
277
298
  features: aliasProject.features,
278
- roles: common.filterRoles(source.roles ?? [], aliasProject.allowRoles ?? ['*']),
299
+ roles: common.filterRoles(source.roles ?? [], allowRoles),
279
300
  environmentVariables: source.environmentVariables,
280
301
  entities: entities,
281
302
  });
@@ -213,6 +213,8 @@ function generateCSharpClassMongoDb(source, className, fields, isRoot, descripti
213
213
  content += ` [MongoDB.Bson.Serialization.Attributes.BsonElement("${isRoot && fieldName === 'id' ? '_id' : fieldName}")]
214
214
  [MongoDB.Bson.Serialization.Attributes.BsonIgnoreIfNull]
215
215
  [Newtonsoft.Json.JsonProperty(PropertyName = "${fieldName}", NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore${type === 'System.DateTime' ? ', DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Ignore' : ''})]
216
+ [System.Text.Json.Serialization.JsonPropertyName("${fieldName}")]
217
+ [System.Text.Json.Serialization.JsonIgnore(Condition = ${type === 'System.DateTime' ? 'JsonIgnoreCondition.WhenWritingDefault' : 'JsonIgnoreCondition.WhenWritingNull'})]
216
218
  `;
217
219
  if ((field.type === 'AutoId' || field.type === 'IdRef') && source.idType === 'ObjectId') {
218
220
  content += ' [MongoDB.Bson.Serialization.Attributes.BsonRepresentation(MongoDB.Bson.BsonType.ObjectId)]\n';
@@ -558,6 +560,8 @@ function generateCSharpClassMongoDbToFolder(source, className, fields, isRoot, d
558
560
  content += ` [MongoDB.Bson.Serialization.Attributes.BsonElement("${isRoot && fieldName === 'id' ? '_id' : fieldName}")]
559
561
  [MongoDB.Bson.Serialization.Attributes.BsonIgnoreIfNull]
560
562
  [Newtonsoft.Json.JsonProperty(PropertyName = "${fieldName}", NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore${type === 'System.DateTime' ? ', DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Ignore' : ''})]
563
+ [System.Text.Json.Serialization.JsonPropertyName("${fieldName}")]
564
+ [System.Text.Json.Serialization.JsonIgnore(Condition = ${type === 'System.DateTime' ? 'JsonIgnoreCondition.WhenWritingDefault' : 'JsonIgnoreCondition.WhenWritingNull'})]
561
565
  `;
562
566
  if ((field.type === 'AutoId' || field.type === 'IdRef') && source.idType === 'ObjectId') {
563
567
  content += ' [MongoDB.Bson.Serialization.Attributes.BsonRepresentation(MongoDB.Bson.BsonType.ObjectId)]\n';
@@ -838,6 +842,8 @@ namespace ${namespace}
838
842
  private void RegisterCustomVnTextBuilderMethod(System.Func<System.Collections.Generic.IEnumerable<string>, System.Collections.Generic.IEnumerable<string>> fn) { }
839
843
  private void RegisterEvent(System.Func<object, Mushroom.ActionArguments, System.Threading.Tasks.Task> fn) { }
840
844
  private void RegisterEvent(System.Action<object, Mushroom.ActionArguments> fn) { }
845
+ private void RegisterRespondingEvent(System.Func<object, Mushroom.ActionArguments, Mushroom.ActionResult, System.Threading.Tasks.Task> fn) { }
846
+ private void RegisterRespondingEvent(System.Action<object, Mushroom.ActionArguments, Mushroom.ActionResult> fn) { }
841
847
  private void RegisterJob<T>() where T : Mushroom.Job.JobBase { }
842
848
  private void Help()
843
849
  {
@@ -900,6 +906,8 @@ function generateCSharpClassDriver(source, className, fields, description, out)
900
906
  /// ${singleLine(field.description)}
901
907
  /// </summary>
902
908
  [Newtonsoft.Json.JsonProperty(PropertyName ="${fieldName}", NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore${type === 'System.Datetime' ? ', DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Ignore' : ''})]
909
+ [System.Text.Json.Serialization.JsonPropertyName("${fieldName}")]
910
+ [System.Text.Json.Serialization.JsonIgnore(Condition = ${type === 'System.DateTime' ? 'JsonIgnoreCondition.WhenWritingDefault' : 'JsonIgnoreCondition.WhenWritingNull'})]
903
911
  public ${type} ${propertyName} { get; set; }
904
912
 
905
913
  `;
@@ -1135,7 +1143,9 @@ export function genCSharpDriver(args, projectName, project, alias, source) {
1135
1143
  let type = getCSharpTypeFromMushroomType(source, p.type, p.validation && p.validation.required);
1136
1144
  if (p.isArray)
1137
1145
  type = `System.Collections.Generic.List<${type}>`;
1138
- viewExtraClass += ` [Newtonsoft.Json.JsonProperty(PropertyName = "${p.field}")]
1146
+ viewExtraClass += ` [Newtonsoft.Json.JsonProperty(PropertyName = "${p.field}", NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore${type === 'System.Datetime' ? ', DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Ignore' : ''})]
1147
+ [System.Text.Json.Serialization.JsonPropertyName("${p.field}")]
1148
+ [System.Text.Json.Serialization.JsonIgnore(Condition = ${type === 'System.DateTime' ? 'JsonIgnoreCondition.WhenWritingDefault' : 'JsonIgnoreCondition.WhenWritingNull'})]
1139
1149
  public ${type} ${toCSharpName(p.field)} { get; set; }
1140
1150
 
1141
1151
  `;
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import targz from 'targz';
3
+ import { compress } from 'targz';
4
4
  import * as common from '../common.js';
5
5
  import * as javascriptDriverGenerator from './gen-js-driver.js';
6
6
  import * as typescriptDriverGenerator from './gen-ts-driver.js';
@@ -21,7 +21,7 @@ function compressAsync(src, dest, folderName) {
21
21
  },
22
22
  };
23
23
  }
24
- targz.compress(options, function (err) {
24
+ compress(options, function (err) {
25
25
  if (err) {
26
26
  reject(err);
27
27
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mushroom-cli",
3
- "version": "1.2.0-alpha.23",
3
+ "version": "1.2.0-alpha.25",
4
4
  "description": "Mushroom CLI",
5
5
  "main": "dist/mr.js",
6
6
  "type": "module",
@@ -33,30 +33,30 @@
33
33
  "author": "suco2007",
34
34
  "license": "ISC",
35
35
  "dependencies": {
36
- "jsdom": "^25.0.1",
36
+ "jsdom": "^26.1.0",
37
37
  "json5": "^2.2.3",
38
- "minify": "^11.4.1",
38
+ "minify": "^14.0.0",
39
39
  "node-fetch": "^3.3.2",
40
- "open": "^10.1.0",
40
+ "open": "^10.1.2",
41
41
  "readline-sync": "^1.4.10",
42
42
  "targz": "^1.0.1"
43
43
  },
44
44
  "devDependencies": {
45
- "@eslint/js": "^9.11.1",
45
+ "@eslint/js": "^9.27.0",
46
46
  "@types/jsdom": "^21.1.7",
47
47
  "@types/minify": "^9.1.4",
48
- "@types/node": "^22.7.4",
49
- "@types/node-fetch": "^2.6.11",
48
+ "@types/node": "^22.15.26",
49
+ "@types/node-fetch": "^2.6.12",
50
50
  "@types/readline-sync": "^1.4.8",
51
- "@types/targz": "^1.0.4",
51
+ "@types/targz": "^1.0.5",
52
52
  "copyfiles": "^2.4.1",
53
- "eslint": "^9.11.1",
54
- "husky": "^9.1.6",
55
- "lint-staged": "^15.2.10",
53
+ "eslint": "^9.27.0",
54
+ "husky": "^9.1.7",
55
+ "lint-staged": "^16.1.0",
56
56
  "only-allow": "^1.2.1",
57
- "prettier": "^3.3.3",
58
- "typescript": "^5.6.2",
59
- "typescript-eslint": "^8.8.0"
57
+ "prettier": "^3.5.3",
58
+ "typescript": "^5.8.3",
59
+ "typescript-eslint": "^8.33.0"
60
60
  },
61
61
  "engines": {
62
62
  "node": ">=14.0.0"