@mongoosejs/studio 0.2.2 → 0.2.4
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/backend/actions/Model/updateDocument.js +7 -9
- package/frontend/public/app.js +274 -70
- package/frontend/public/tw.css +4 -0
- package/frontend/src/_util/baseComponent.js +19 -0
- package/frontend/src/_util/deepEqual.js +53 -0
- package/frontend/src/array-utils.js +1 -0
- package/frontend/src/dashboard/dashboard.js +22 -17
- package/frontend/src/document/confirm-changes/confirm-changes.js +11 -1
- package/frontend/src/document/document.html +1 -1
- package/frontend/src/document/document.js +16 -1
- package/frontend/src/document-details/document-property/document-property.html +1 -1
- package/frontend/src/document-details/document-property/document-property.js +15 -1
- package/frontend/src/edit-boolean/edit-boolean.js +1 -9
- package/frontend/src/index.js +6 -2
- package/package.json +5 -3
|
@@ -16,13 +16,16 @@ const UpdateDocumentsParams = new Archetype({
|
|
|
16
16
|
$type: Object,
|
|
17
17
|
$required: true
|
|
18
18
|
},
|
|
19
|
+
unset: {
|
|
20
|
+
$type: Object
|
|
21
|
+
},
|
|
19
22
|
roles: {
|
|
20
23
|
$type: ['string']
|
|
21
24
|
}
|
|
22
25
|
}).compile('UpdateDocumentsParams');
|
|
23
26
|
|
|
24
27
|
module.exports = ({ db }) => async function updateDocument(params) {
|
|
25
|
-
const { model, _id, update, roles } = new UpdateDocumentsParams(params);
|
|
28
|
+
const { model, _id, update, unset, roles } = new UpdateDocumentsParams(params);
|
|
26
29
|
|
|
27
30
|
await authorize('Model.updateDocument', roles);
|
|
28
31
|
|
|
@@ -32,16 +35,11 @@ module.exports = ({ db }) => async function updateDocument(params) {
|
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
const setFields = {};
|
|
35
|
-
const unsetFields = {};
|
|
36
|
-
|
|
38
|
+
const unsetFields = unset || {};
|
|
39
|
+
|
|
37
40
|
if (Object.keys(update).length > 0) {
|
|
38
41
|
Object.entries(update).forEach(([key, value]) => {
|
|
39
|
-
if (value === '
|
|
40
|
-
setFields[key] = null;
|
|
41
|
-
} else if (value === 'undefined') {
|
|
42
|
-
// Use $unset to remove the field for undefined values
|
|
43
|
-
unsetFields[key] = 1;
|
|
44
|
-
} else if (value === '') {
|
|
42
|
+
if (value === '') {
|
|
45
43
|
// Treat empty strings as undefined - unset the field
|
|
46
44
|
unsetFields[key] = 1;
|
|
47
45
|
} else {
|
package/frontend/public/app.js
CHANGED
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
var map = {
|
|
11
11
|
"./": "./frontend/src/index.js",
|
|
12
|
+
"./_util/baseComponent": "./frontend/src/_util/baseComponent.js",
|
|
13
|
+
"./_util/baseComponent.js": "./frontend/src/_util/baseComponent.js",
|
|
14
|
+
"./_util/deepEqual": "./frontend/src/_util/deepEqual.js",
|
|
15
|
+
"./_util/deepEqual.js": "./frontend/src/_util/deepEqual.js",
|
|
12
16
|
"./_util/document-search-autocomplete": "./frontend/src/_util/document-search-autocomplete.js",
|
|
13
17
|
"./_util/document-search-autocomplete.js": "./frontend/src/_util/document-search-autocomplete.js",
|
|
14
18
|
"./api": "./frontend/src/api.js",
|
|
@@ -208,6 +212,100 @@ webpackContext.resolve = webpackContextResolve;
|
|
|
208
212
|
module.exports = webpackContext;
|
|
209
213
|
webpackContext.id = "./frontend/src sync recursive ^\\.\\/.*$";
|
|
210
214
|
|
|
215
|
+
/***/ },
|
|
216
|
+
|
|
217
|
+
/***/ "./frontend/src/_util/baseComponent.js"
|
|
218
|
+
/*!*********************************************!*\
|
|
219
|
+
!*** ./frontend/src/_util/baseComponent.js ***!
|
|
220
|
+
\*********************************************/
|
|
221
|
+
(module) {
|
|
222
|
+
|
|
223
|
+
"use strict";
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
module.exports = {
|
|
227
|
+
destroyed() {
|
|
228
|
+
this.$parent.$options.$children = this.$parent.$options.$children.filter(
|
|
229
|
+
(el) => el !== this
|
|
230
|
+
);
|
|
231
|
+
},
|
|
232
|
+
created() {
|
|
233
|
+
this.$parent.$options.$children = this.$parent.$options.$children || [];
|
|
234
|
+
this.$parent.$options.$children.push(this);
|
|
235
|
+
},
|
|
236
|
+
mounted() {
|
|
237
|
+
this.isMounted = true;
|
|
238
|
+
},
|
|
239
|
+
unmounted() {
|
|
240
|
+
this.isMounted = false;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
/***/ },
|
|
246
|
+
|
|
247
|
+
/***/ "./frontend/src/_util/deepEqual.js"
|
|
248
|
+
/*!*****************************************!*\
|
|
249
|
+
!*** ./frontend/src/_util/deepEqual.js ***!
|
|
250
|
+
\*****************************************/
|
|
251
|
+
(module) {
|
|
252
|
+
|
|
253
|
+
"use strict";
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Deep equality check for values (handles primitives, arrays, objects, dates, etc.)
|
|
258
|
+
* @param {*} a - First value to compare
|
|
259
|
+
* @param {*} b - Second value to compare
|
|
260
|
+
* @returns {boolean} - True if values are deeply equal
|
|
261
|
+
*/
|
|
262
|
+
function deepEqual(a, b) {
|
|
263
|
+
// Handle primitives and same reference
|
|
264
|
+
if (a === b) return true;
|
|
265
|
+
|
|
266
|
+
// Handle null and undefined
|
|
267
|
+
if (a == null || b == null) return a === b;
|
|
268
|
+
|
|
269
|
+
// Handle different types
|
|
270
|
+
if (typeof a !== typeof b) return false;
|
|
271
|
+
|
|
272
|
+
// Handle dates
|
|
273
|
+
if (a instanceof Date && b instanceof Date) {
|
|
274
|
+
return a.getTime() === b.getTime();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Handle arrays - must both be arrays
|
|
278
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
279
|
+
if (!Array.isArray(a) || !Array.isArray(b)) return false;
|
|
280
|
+
if (a.length !== b.length) return false;
|
|
281
|
+
for (let i = 0; i < a.length; i++) {
|
|
282
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
283
|
+
}
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Handle objects (non-arrays)
|
|
288
|
+
if (typeof a === 'object' && typeof b === 'object') {
|
|
289
|
+
const keysA = Object.keys(a);
|
|
290
|
+
const keysB = Object.keys(b);
|
|
291
|
+
|
|
292
|
+
if (keysA.length !== keysB.length) return false;
|
|
293
|
+
|
|
294
|
+
for (const key of keysA) {
|
|
295
|
+
if (!keysB.includes(key)) return false;
|
|
296
|
+
if (!deepEqual(a[key], b[key])) return false;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Fallback for primitives (strings, numbers, booleans)
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
module.exports = deepEqual;
|
|
307
|
+
|
|
308
|
+
|
|
211
309
|
/***/ },
|
|
212
310
|
|
|
213
311
|
/***/ "./frontend/src/_util/document-search-autocomplete.js"
|
|
@@ -855,6 +953,7 @@ module.exports = function appendCSS(css) {
|
|
|
855
953
|
|
|
856
954
|
|
|
857
955
|
const { inspect } = __webpack_require__(/*! node-inspect-extracted */ "./node_modules/node-inspect-extracted/dist/inspect.js");
|
|
956
|
+
const deepEqual = __webpack_require__(/*! ./_util/deepEqual */ "./frontend/src/_util/deepEqual.js");
|
|
858
957
|
|
|
859
958
|
/**
|
|
860
959
|
* Format a value for display in array views
|
|
@@ -2254,10 +2353,12 @@ module.exports = "<div class=\"dashboard px-1\">\n <div v-if=\"status === 'load
|
|
|
2254
2353
|
|
|
2255
2354
|
|
|
2256
2355
|
const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
|
|
2356
|
+
const baseComponent = __webpack_require__(/*! ../_util/baseComponent */ "./frontend/src/_util/baseComponent.js");
|
|
2257
2357
|
const template = __webpack_require__(/*! ./dashboard.html */ "./frontend/src/dashboard/dashboard.html");
|
|
2258
2358
|
|
|
2259
|
-
module.exports =
|
|
2359
|
+
module.exports = {
|
|
2260
2360
|
template: template,
|
|
2361
|
+
extends: baseComponent,
|
|
2261
2362
|
props: ['dashboardId'],
|
|
2262
2363
|
data: function() {
|
|
2263
2364
|
return {
|
|
@@ -2301,7 +2402,7 @@ module.exports = app => app.component('dashboard', {
|
|
|
2301
2402
|
}
|
|
2302
2403
|
},
|
|
2303
2404
|
shouldEvaluateDashboard() {
|
|
2304
|
-
if (this.dashboardResults.length === 0) {
|
|
2405
|
+
if (!this.dashboardResults || this.dashboardResults.length === 0) {
|
|
2305
2406
|
return true;
|
|
2306
2407
|
}
|
|
2307
2408
|
|
|
@@ -2368,6 +2469,22 @@ module.exports = app => app.component('dashboard', {
|
|
|
2368
2469
|
this.startingChat = false;
|
|
2369
2470
|
this.showActionsMenu = false;
|
|
2370
2471
|
}
|
|
2472
|
+
},
|
|
2473
|
+
async loadInitial() {
|
|
2474
|
+
const { dashboard, dashboardResults, error } = await api.Dashboard.getDashboard({ dashboardId: this.dashboardId, evaluate: false });
|
|
2475
|
+
if (!dashboard) {
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2478
|
+
this.dashboard = dashboard;
|
|
2479
|
+
this.code = this.dashboard.code;
|
|
2480
|
+
this.title = this.dashboard.title;
|
|
2481
|
+
this.description = this.dashboard.description ?? '';
|
|
2482
|
+
this.dashboardResults = dashboardResults;
|
|
2483
|
+
if (this.shouldEvaluateDashboard()) {
|
|
2484
|
+
await this.evaluateDashboard();
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
this.status = 'loaded';
|
|
2371
2488
|
}
|
|
2372
2489
|
},
|
|
2373
2490
|
computed: {
|
|
@@ -2378,25 +2495,12 @@ module.exports = app => app.component('dashboard', {
|
|
|
2378
2495
|
mounted: async function() {
|
|
2379
2496
|
document.addEventListener('click', this.handleDocumentClick);
|
|
2380
2497
|
this.showEditor = this.$route.query.edit;
|
|
2381
|
-
|
|
2382
|
-
if (!dashboard) {
|
|
2383
|
-
return;
|
|
2384
|
-
}
|
|
2385
|
-
this.dashboard = dashboard;
|
|
2386
|
-
this.code = this.dashboard.code;
|
|
2387
|
-
this.title = this.dashboard.title;
|
|
2388
|
-
this.description = this.dashboard.description ?? '';
|
|
2389
|
-
this.dashboardResults = dashboardResults;
|
|
2390
|
-
if (this.shouldEvaluateDashboard()) {
|
|
2391
|
-
await this.evaluateDashboard();
|
|
2392
|
-
return;
|
|
2393
|
-
}
|
|
2394
|
-
this.status = 'loaded';
|
|
2498
|
+
await this.loadInitial();
|
|
2395
2499
|
},
|
|
2396
2500
|
beforeDestroy() {
|
|
2397
2501
|
document.removeEventListener('click', this.handleDocumentClick);
|
|
2398
2502
|
}
|
|
2399
|
-
}
|
|
2503
|
+
};
|
|
2400
2504
|
|
|
2401
2505
|
|
|
2402
2506
|
/***/ },
|
|
@@ -3181,7 +3285,7 @@ module.exports = ".document-details {\n width: 100%;\n }\n \n .document-de
|
|
|
3181
3285
|
(module) {
|
|
3182
3286
|
|
|
3183
3287
|
"use strict";
|
|
3184
|
-
module.exports = "<div class=\"border border-gray-200 bg-white rounded-lg mb-2\">\n <!-- Collapsible Header -->\n <div\n @click=\"toggleCollapse\"\n class=\"p-1 cursor-pointer flex items-center justify-between border-b border-gray-200 transition-colors duration-200 ease-in-out\"\n :class=\"{ 'bg-amber-100 hover:bg-amber-200': highlight, 'bg-slate-100 hover:bg-gray-100': !highlight }\"\n >\n <div class=\"flex items-center\" >\n <svg\n :class=\"isCollapsed ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-gray-500 mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-gray-900\">{{path.path}}</span>\n <span class=\"ml-2 text-sm text-gray-500\">({{(path.instance || 'unknown').toLowerCase()}})</span>\n </div>\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800 px-2 py-1 rounded-md border border-transparent hover:border-gray-300 bg-white\"\n @click.stop.prevent=\"copyPropertyValue\"\n title=\"Copy value\"\n aria-label=\"Copy property value\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7h8m-8 4h8m-8 4h5m-7-9a2 2 0 012-2h7a2 2 0 012 2v10a2 2 0 01-2 2H8l-4-4V7a2 2 0 012-2z\" />\n </svg>\n {{copyButtonLabel}}\n </button>\n <router-link\n v-if=\"path.ref && getValueForPath(path.path)\"\n :to=\"`/model/${path.ref}/document/${getValueForPath(path.path)}`\"\n class=\"bg-ultramarine-600 hover:bg-ultramarine-500 text-white px-2 py-1 text-sm rounded-md\"\n @click.stop\n >View Document\n </router-link>\n </div>\n </div>\n\n <!-- Collapsible Content -->\n <div v-if=\"!isCollapsed\" class=\"p-2\">\n <!-- Date Type Selector (when editing dates) -->\n <div v-if=\"editting && path.instance === 'Date'\" class=\"mb-3 flex gap-1.5\">\n <div\n @click=\"dateType = 'picker'\"\n :class=\"dateType === 'picker' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'picker' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n Date Picker\n </div>\n </div>\n <div\n @click=\"dateType = 'iso'\"\n :class=\"dateType === 'iso' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'iso' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n ISO String\n </div>\n </div>\n </div>\n\n <!-- Field Content -->\n <div v-if=\"editting && path.path !== '_id'\">\n <component\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n v-bind=\"getEditComponentProps(path)\"\n @input=\"
|
|
3288
|
+
module.exports = "<div class=\"border border-gray-200 bg-white rounded-lg mb-2\">\n <!-- Collapsible Header -->\n <div\n @click=\"toggleCollapse\"\n class=\"p-1 cursor-pointer flex items-center justify-between border-b border-gray-200 transition-colors duration-200 ease-in-out\"\n :class=\"{ 'bg-amber-100 hover:bg-amber-200': highlight, 'bg-slate-100 hover:bg-gray-100': !highlight }\"\n >\n <div class=\"flex items-center\" >\n <svg\n :class=\"isCollapsed ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-gray-500 mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-gray-900\">{{path.path}}</span>\n <span class=\"ml-2 text-sm text-gray-500\">({{(path.instance || 'unknown').toLowerCase()}})</span>\n </div>\n <div class=\"flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"flex items-center gap-1 text-sm text-gray-600 hover:text-gray-800 px-2 py-1 rounded-md border border-transparent hover:border-gray-300 bg-white\"\n @click.stop.prevent=\"copyPropertyValue\"\n title=\"Copy value\"\n aria-label=\"Copy property value\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7h8m-8 4h8m-8 4h5m-7-9a2 2 0 012-2h7a2 2 0 012 2v10a2 2 0 01-2 2H8l-4-4V7a2 2 0 012-2z\" />\n </svg>\n {{copyButtonLabel}}\n </button>\n <router-link\n v-if=\"path.ref && getValueForPath(path.path)\"\n :to=\"`/model/${path.ref}/document/${getValueForPath(path.path)}`\"\n class=\"bg-ultramarine-600 hover:bg-ultramarine-500 text-white px-2 py-1 text-sm rounded-md\"\n @click.stop\n >View Document\n </router-link>\n </div>\n </div>\n\n <!-- Collapsible Content -->\n <div v-if=\"!isCollapsed\" class=\"p-2\">\n <!-- Date Type Selector (when editing dates) -->\n <div v-if=\"editting && path.instance === 'Date'\" class=\"mb-3 flex gap-1.5\">\n <div\n @click=\"dateType = 'picker'\"\n :class=\"dateType === 'picker' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'picker' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n Date Picker\n </div>\n </div>\n <div\n @click=\"dateType = 'iso'\"\n :class=\"dateType === 'iso' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'iso' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n ISO String\n </div>\n </div>\n </div>\n\n <!-- Field Content -->\n <div v-if=\"editting && path.path !== '_id'\">\n <component\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n v-bind=\"getEditComponentProps(path)\"\n @input=\"handleInputChange($event)\"\n @error=\"invalid[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else>\n <!-- Show truncated or full value based on needsTruncation and isValueExpanded -->\n <!-- Special handling for truncated arrays -->\n <div v-if=\"isArray && shouldShowTruncated\" class=\"w-full\">\n <div class=\"mt-2\">\n <div\n v-for=\"(item, index) in truncatedArrayItems\"\n :key=\"index\"\n class=\"mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-l-[3px] border-l-blue-500 rounded-none transition-all duration-200 cursor-pointer relative hover:bg-slate-50 hover:border-l-blue-600\">\n <div class=\"absolute -left-2 top-1/2 -translate-y-1/2 w-5 h-5 bg-blue-500 text-white rounded-full flex items-center justify-center text-[10px] font-semibold font-mono z-10 hover:bg-blue-600\">{{ index }}</div>\n <div v-if=\"arrayUtils.isObjectItem(item)\" class=\"flex flex-col gap-1 mt-1 px-2\">\n <div\n v-for=\"key in arrayUtils.getItemKeys(item)\"\n :key=\"key\"\n class=\"flex items-start gap-2 text-xs font-mono\">\n <span class=\"font-semibold text-gray-600 flex-shrink-0 min-w-[80px]\">{{ key }}:</span>\n <span class=\"text-gray-800 break-words whitespace-pre-wrap flex-1\">{{ arrayUtils.formatItemValue(item, key) }}</span>\n </div>\n </div>\n <div v-else class=\"text-xs py-1.5 px-2 font-mono text-gray-800 break-words whitespace-pre-wrap mt-1\">{{ arrayUtils.formatValue(item) }}</div>\n </div>\n <div class=\"mb-1.5 py-2.5 px-3 pl-4 bg-transparent border-none border-l-[3px] border-l-blue-500 rounded-none transition-all duration-200 cursor-pointer relative opacity-70 hover:opacity-100\">\n <div class=\"text-xs py-1.5 px-2 font-mono text-gray-500 italic break-words whitespace-pre-wrap mt-1\">\n ... and {{ remainingArrayCount }} more item{{ remainingArrayCount !== 1 ? 's' : '' }}\n </div>\n </div>\n </div>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show all {{ arrayValue.length }} items\n </button>\n </div>\n <!-- Non-array truncated view -->\n <div v-else-if=\"shouldShowTruncated && !isArray\" class=\"relative\">\n <div class=\"text-gray-700 whitespace-pre-wrap break-words font-mono text-sm\">{{truncatedString}}</div>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show more ({{valueAsString.length}} characters)\n </button>\n </div>\n <!-- Expanded view -->\n <div v-else-if=\"needsTruncation && isValueExpanded\" class=\"relative\">\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n <button\n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5\"\n >\n <svg class=\"w-4 h-4 rotate-180\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show less\n </button>\n </div>\n <!-- Full view (no truncation needed) -->\n <div v-else>\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n </div>\n </div>\n </div>\n</div>\n";
|
|
3185
3289
|
|
|
3186
3290
|
/***/ },
|
|
3187
3291
|
|
|
@@ -3197,7 +3301,7 @@ module.exports = "<div class=\"border border-gray-200 bg-white rounded-lg mb-2\"
|
|
|
3197
3301
|
|
|
3198
3302
|
|
|
3199
3303
|
const mpath = __webpack_require__(/*! mpath */ "./node_modules/mpath/index.js");
|
|
3200
|
-
const
|
|
3304
|
+
const deepEqual = __webpack_require__(/*! ../../_util/deepEqual */ "./frontend/src/_util/deepEqual.js");
|
|
3201
3305
|
const template = __webpack_require__(/*! ./document-property.html */ "./frontend/src/document-details/document-property/document-property.html");
|
|
3202
3306
|
|
|
3203
3307
|
const appendCSS = __webpack_require__(/*! ../../appendCSS */ "./frontend/src/appendCSS.js");
|
|
@@ -3289,6 +3393,20 @@ module.exports = app => app.component('document-property', {
|
|
|
3289
3393
|
}
|
|
3290
3394
|
},
|
|
3291
3395
|
methods: {
|
|
3396
|
+
handleInputChange(newValue) {
|
|
3397
|
+
const currentValue = this.getValueForPath(this.path.path);
|
|
3398
|
+
|
|
3399
|
+
// Only record as a change if the value is actually different
|
|
3400
|
+
if (!deepEqual(currentValue, newValue)) {
|
|
3401
|
+
this.changes[this.path.path] = newValue;
|
|
3402
|
+
} else {
|
|
3403
|
+
// If the value is the same as the original, remove it from changes
|
|
3404
|
+
delete this.changes[this.path.path];
|
|
3405
|
+
}
|
|
3406
|
+
|
|
3407
|
+
// Always clear invalid state on input
|
|
3408
|
+
delete this.invalid[this.path.path];
|
|
3409
|
+
},
|
|
3292
3410
|
getComponentForPath(schemaPath) {
|
|
3293
3411
|
if (schemaPath.instance === 'Array') {
|
|
3294
3412
|
return 'detail-array';
|
|
@@ -3434,6 +3552,16 @@ module.exports = app => app.component('confirm-changes', {
|
|
|
3434
3552
|
props: ['value'],
|
|
3435
3553
|
computed: {
|
|
3436
3554
|
displayValue() {
|
|
3555
|
+
const hasUnsetFields = Object.keys(this.value).some(key => this.value[key] === undefined);
|
|
3556
|
+
if (hasUnsetFields) {
|
|
3557
|
+
const unsetFields = Object.keys(this.value)
|
|
3558
|
+
.filter(key => this.value[key] === undefined)
|
|
3559
|
+
.reduce((obj, key) => Object.assign(obj, { [key]: 1 }), {});
|
|
3560
|
+
const setFields = Object.keys(this.value)
|
|
3561
|
+
.filter(key => this.value[key] !== undefined)
|
|
3562
|
+
.reduce((obj, key) => Object.assign(obj, { [key]: this.value[key] }), {});
|
|
3563
|
+
return JSON.stringify({ $set: setFields, $unset: unsetFields }, null, ' ').trim();
|
|
3564
|
+
}
|
|
3437
3565
|
return JSON.stringify(this.value, null, ' ').trim();
|
|
3438
3566
|
}
|
|
3439
3567
|
},
|
|
@@ -3450,6 +3578,7 @@ module.exports = app => app.component('confirm-changes', {
|
|
|
3450
3578
|
}
|
|
3451
3579
|
});
|
|
3452
3580
|
|
|
3581
|
+
|
|
3453
3582
|
/***/ },
|
|
3454
3583
|
|
|
3455
3584
|
/***/ "./frontend/src/document/confirm-delete/confirm-delete.html"
|
|
@@ -3515,7 +3644,7 @@ module.exports = ".document .document-menu {\n display: flex;\n position: stic
|
|
|
3515
3644
|
(module) {
|
|
3516
3645
|
|
|
3517
3646
|
"use strict";
|
|
3518
|
-
module.exports = "<div class=\"document px-1 pt-4 md:px-0 bg-slate-50 w-full\">\n <div class=\"max-w-7xl mx-auto\">\n <div class=\"flex gap-4 items-center sticky top-0 z-50 bg-white p-4 border-b border-gray-200 shadow-sm\">\n <div class=\"font-bold overflow-hidden text-ellipsis\">{{model}}: {{documentId}}</div>\n <div class=\"flex grow\">\n <button\n @click=\"viewMode = 'fields'\"\n :class=\"viewMode === 'fields'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 border-r-0 rounded-l-lg rounded-r-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 6h16M4 10h16M4 14h16M4 18h16\"></path>\n </svg>\n Fields\n </button>\n <button\n @click=\"viewMode = 'json'\"\n :class=\"viewMode === 'json'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 rounded-r-lg rounded-l-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\"></path>\n </svg>\n JSON\n </button>\n </div>\n\n <div class=\"gap-2 hidden md:flex items-center\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n :disabled=\"!canEdit\"\n :class=\"{'cursor-not-allowed opacity-50': !canEdit}\"\n type=\"button\"\n class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n × Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n @click=\"shouldShowConfirmModal=true;\"\n type=\"button\"\n class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n\n <!-- 3-dot menu -->\n <div class=\"relative\">\n <button\n @click=\"desktopMenuOpen = !desktopMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"desktopMenuOpen\"\n aria-label=\"More options\"\n >\n <svg class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"5\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"12\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"19\" r=\"2\"></circle>\n </svg>\n </button>\n <div\n v-show=\"desktopMenuOpen\"\n @click.away=\"desktopMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n @click=\"addField(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n <div class=\"md:hidden flex items-center\">\n <div class=\"relative\">\n <button\n @click=\"mobileMenuOpen = !mobileMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"mobileMenuOpen\"\n aria-label=\"Open menu\"\n >\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\n d=\"M4 6h16M4 12h16M4 18h16\"></path>\n </svg>\n </button>\n <div\n v-show=\"mobileMenuOpen\"\n @click.away=\"mobileMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-52 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true; mobileMenuOpen = false\"\n :disabled=\"!canEdit\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-ultramarine-100']\"\n type=\"button\"\n >\n Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false; mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-slate-100\"\n >\n Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']\"\n @click=\"shouldShowConfirmModal=true; mobileMenuOpen = false\"\n type=\"button\"\n >\n Save\n </button>\n <button\n @click=\"addField(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <document-details\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :virtualPaths=\"virtualPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"\n :viewMode=\"viewMode\"\n :model=\"model\"\n @add-field=\"addField\"\n @view-mode-change=\"updateViewMode\"></document-details>\n <modal v-if=\"shouldShowConfirmModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">×</div>\n <confirm-changes @close=\"shouldShowConfirmModal = false;\" @save=\"save\" :value=\"changes\"></confirm-changes>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteModal = false;\">×</div>\n <confirm-delete @close=\"shouldShowDeleteModal = false;\" @remove=\"remove\" :value=\"document\"></confirm-delete>\n </template>\n </modal>\n <modal v-if=\"shouldShowCloneModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCloneModal = false;\">×</div>\n <clone-document :currentModel=\"model\" :doc=\"document\" :schemaPaths=\"schemaPaths\" @close=\"showClonedDocument\"></clone-document>\n </template>\n </modal>\n </div>\n </div>\n</div>\n";
|
|
3647
|
+
module.exports = "<div class=\"document px-1 pt-4 pb-16 md:px-0 bg-slate-50 w-full\">\n <div class=\"max-w-7xl mx-auto\">\n <div class=\"flex gap-4 items-center sticky top-0 z-50 bg-white p-4 border-b border-gray-200 shadow-sm\">\n <div class=\"font-bold overflow-hidden text-ellipsis\">{{model}}: {{documentId}}</div>\n <div class=\"flex grow\">\n <button\n @click=\"viewMode = 'fields'\"\n :class=\"viewMode === 'fields'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 border-r-0 rounded-l-lg rounded-r-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 6h16M4 10h16M4 14h16M4 18h16\"></path>\n </svg>\n Fields\n </button>\n <button\n @click=\"viewMode = 'json'\"\n :class=\"viewMode === 'json'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-2 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 rounded-r-lg rounded-l-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\"></path>\n </svg>\n JSON\n </button>\n </div>\n\n <div class=\"gap-2 hidden md:flex items-center\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n :disabled=\"!canEdit\"\n :class=\"{'cursor-not-allowed opacity-50': !canEdit}\"\n type=\"button\"\n class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n × Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n @click=\"shouldShowConfirmModal=true;\"\n type=\"button\"\n class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n\n <!-- 3-dot menu -->\n <div class=\"relative\">\n <button\n @click=\"desktopMenuOpen = !desktopMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"desktopMenuOpen\"\n aria-label=\"More options\"\n >\n <svg class=\"w-5 h-5\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <circle cx=\"12\" cy=\"5\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"12\" r=\"2\"></circle>\n <circle cx=\"12\" cy=\"19\" r=\"2\"></circle>\n </svg>\n </button>\n <div\n v-show=\"desktopMenuOpen\"\n @click.away=\"desktopMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n @click=\"addField(); desktopMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; desktopMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n <div class=\"md:hidden flex items-center\">\n <div class=\"relative\">\n <button\n @click=\"mobileMenuOpen = !mobileMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"mobileMenuOpen\"\n aria-label=\"Open menu\"\n >\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\n d=\"M4 6h16M4 12h16M4 18h16\"></path>\n </svg>\n </button>\n <div\n v-show=\"mobileMenuOpen\"\n @click.away=\"mobileMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-52 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true; mobileMenuOpen = false\"\n :disabled=\"!canEdit\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-ultramarine-100']\"\n type=\"button\"\n >\n Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false; mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-slate-100\"\n >\n Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']\"\n @click=\"shouldShowConfirmModal=true; mobileMenuOpen = false\"\n type=\"button\"\n >\n Save\n </button>\n <button\n @click=\"addField(); mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-green-100\"\n >\n Add Field\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <document-details\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :virtualPaths=\"virtualPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"\n :viewMode=\"viewMode\"\n :model=\"model\"\n @add-field=\"addField\"\n @view-mode-change=\"updateViewMode\"></document-details>\n <modal v-if=\"shouldShowConfirmModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">×</div>\n <confirm-changes @close=\"shouldShowConfirmModal = false;\" @save=\"save\" :value=\"changes\"></confirm-changes>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteModal = false;\">×</div>\n <confirm-delete @close=\"shouldShowDeleteModal = false;\" @remove=\"remove\" :value=\"document\"></confirm-delete>\n </template>\n </modal>\n <modal v-if=\"shouldShowCloneModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCloneModal = false;\">×</div>\n <clone-document :currentModel=\"model\" :doc=\"document\" :schemaPaths=\"schemaPaths\" @close=\"showClonedDocument\"></clone-document>\n </template>\n </modal>\n </div>\n </div>\n</div>\n";
|
|
3519
3648
|
|
|
3520
3649
|
/***/ },
|
|
3521
3650
|
|
|
@@ -3602,10 +3731,25 @@ module.exports = app => app.component('document', {
|
|
|
3602
3731
|
if (Object.keys(this.invalid).length > 0) {
|
|
3603
3732
|
throw new Error('Invalid paths: ' + Object.keys(this.invalid).join(', '));
|
|
3604
3733
|
}
|
|
3734
|
+
|
|
3735
|
+
let update = this.changes;
|
|
3736
|
+
let unset = {};
|
|
3737
|
+
const hasUnsetFields = Object.keys(this.changes)
|
|
3738
|
+
.some(key => this.changes[key] === undefined);
|
|
3739
|
+
if (hasUnsetFields) {
|
|
3740
|
+
unset = Object.keys(this.changes)
|
|
3741
|
+
.filter(key => this.changes[key] === undefined)
|
|
3742
|
+
.reduce((obj, key) => Object.assign(obj, { [key]: 1 }), {});
|
|
3743
|
+
update = Object.keys(this.changes)
|
|
3744
|
+
.filter(key => this.changes[key] !== undefined)
|
|
3745
|
+
.reduce((obj, key) => Object.assign(obj, { [key]: this.changes[key] }), {});
|
|
3746
|
+
}
|
|
3747
|
+
|
|
3605
3748
|
const { doc } = await api.Model.updateDocument({
|
|
3606
3749
|
model: this.model,
|
|
3607
3750
|
_id: this.document._id,
|
|
3608
|
-
update
|
|
3751
|
+
update,
|
|
3752
|
+
unset
|
|
3609
3753
|
});
|
|
3610
3754
|
this.document = doc;
|
|
3611
3755
|
this.changes = {};
|
|
@@ -3833,20 +3977,12 @@ module.exports = app => app.component('edit-boolean', {
|
|
|
3833
3977
|
this.selectedValue = newValue;
|
|
3834
3978
|
},
|
|
3835
3979
|
selectedValue(newValue) {
|
|
3836
|
-
|
|
3837
|
-
const emitValue = this.convertValueToString(newValue);
|
|
3838
|
-
this.$emit('input', emitValue);
|
|
3980
|
+
this.$emit('input', newValue);
|
|
3839
3981
|
}
|
|
3840
3982
|
},
|
|
3841
3983
|
methods: {
|
|
3842
3984
|
selectValue(value) {
|
|
3843
3985
|
this.selectedValue = value;
|
|
3844
|
-
},
|
|
3845
|
-
convertValueToString(value) {
|
|
3846
|
-
// Convert null/undefined to strings for proper backend serialization
|
|
3847
|
-
if (value === null) return 'null';
|
|
3848
|
-
if (typeof value === 'undefined') return 'undefined';
|
|
3849
|
-
return value;
|
|
3850
3986
|
}
|
|
3851
3987
|
}
|
|
3852
3988
|
});
|
|
@@ -4398,7 +4534,11 @@ requireComponents.keys().forEach((filePath) => {
|
|
|
4398
4534
|
// Check if the file name matches the directory name
|
|
4399
4535
|
if (directoryName === fileName) {
|
|
4400
4536
|
components[directoryName] = requireComponents(filePath);
|
|
4401
|
-
components[directoryName]
|
|
4537
|
+
if (typeof components[directoryName] === 'function') {
|
|
4538
|
+
components[directoryName](app);
|
|
4539
|
+
} else {
|
|
4540
|
+
app.component(directoryName, components[directoryName]);
|
|
4541
|
+
}
|
|
4402
4542
|
}
|
|
4403
4543
|
});
|
|
4404
4544
|
|
|
@@ -4470,7 +4610,7 @@ app.component('app-component', {
|
|
|
4470
4610
|
|
|
4471
4611
|
this.user = user;
|
|
4472
4612
|
this.roles = roles;
|
|
4473
|
-
|
|
4613
|
+
|
|
4474
4614
|
setTimeout(() => {
|
|
4475
4615
|
this.$router.replace(this.$router.currentRoute.value.path);
|
|
4476
4616
|
}, 0);
|
|
@@ -6949,7 +7089,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
6949
7089
|
/* harmony export */ });
|
|
6950
7090
|
/* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
|
|
6951
7091
|
/**
|
|
6952
|
-
* @vue/reactivity v3.5.
|
|
7092
|
+
* @vue/reactivity v3.5.27
|
|
6953
7093
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
6954
7094
|
* @license MIT
|
|
6955
7095
|
**/
|
|
@@ -8078,20 +8218,20 @@ function createIterableMethod(method, isReadonly2, isShallow2) {
|
|
|
8078
8218
|
"iterate",
|
|
8079
8219
|
isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY
|
|
8080
8220
|
);
|
|
8081
|
-
return
|
|
8082
|
-
// iterator
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
done
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8221
|
+
return (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.extend)(
|
|
8222
|
+
// inheriting all iterator properties
|
|
8223
|
+
Object.create(innerIterator),
|
|
8224
|
+
{
|
|
8225
|
+
// iterator protocol
|
|
8226
|
+
next() {
|
|
8227
|
+
const { value, done } = innerIterator.next();
|
|
8228
|
+
return done ? { value, done } : {
|
|
8229
|
+
value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
|
|
8230
|
+
done
|
|
8231
|
+
};
|
|
8232
|
+
}
|
|
8093
8233
|
}
|
|
8094
|
-
|
|
8234
|
+
);
|
|
8095
8235
|
};
|
|
8096
8236
|
}
|
|
8097
8237
|
function createReadonlyMethod(type) {
|
|
@@ -8305,8 +8445,9 @@ function targetTypeMap(rawType) {
|
|
|
8305
8445
|
function getTargetType(value) {
|
|
8306
8446
|
return value["__v_skip"] || !Object.isExtensible(value) ? 0 /* INVALID */ : targetTypeMap((0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.toRawType)(value));
|
|
8307
8447
|
}
|
|
8448
|
+
// @__NO_SIDE_EFFECTS__
|
|
8308
8449
|
function reactive(target) {
|
|
8309
|
-
if (isReadonly(target)) {
|
|
8450
|
+
if (/* @__PURE__ */ isReadonly(target)) {
|
|
8310
8451
|
return target;
|
|
8311
8452
|
}
|
|
8312
8453
|
return createReactiveObject(
|
|
@@ -8317,6 +8458,7 @@ function reactive(target) {
|
|
|
8317
8458
|
reactiveMap
|
|
8318
8459
|
);
|
|
8319
8460
|
}
|
|
8461
|
+
// @__NO_SIDE_EFFECTS__
|
|
8320
8462
|
function shallowReactive(target) {
|
|
8321
8463
|
return createReactiveObject(
|
|
8322
8464
|
target,
|
|
@@ -8326,6 +8468,7 @@ function shallowReactive(target) {
|
|
|
8326
8468
|
shallowReactiveMap
|
|
8327
8469
|
);
|
|
8328
8470
|
}
|
|
8471
|
+
// @__NO_SIDE_EFFECTS__
|
|
8329
8472
|
function readonly(target) {
|
|
8330
8473
|
return createReactiveObject(
|
|
8331
8474
|
target,
|
|
@@ -8335,6 +8478,7 @@ function readonly(target) {
|
|
|
8335
8478
|
readonlyMap
|
|
8336
8479
|
);
|
|
8337
8480
|
}
|
|
8481
|
+
// @__NO_SIDE_EFFECTS__
|
|
8338
8482
|
function shallowReadonly(target) {
|
|
8339
8483
|
return createReactiveObject(
|
|
8340
8484
|
target,
|
|
@@ -8373,24 +8517,29 @@ function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandl
|
|
|
8373
8517
|
proxyMap.set(target, proxy);
|
|
8374
8518
|
return proxy;
|
|
8375
8519
|
}
|
|
8520
|
+
// @__NO_SIDE_EFFECTS__
|
|
8376
8521
|
function isReactive(value) {
|
|
8377
|
-
if (isReadonly(value)) {
|
|
8378
|
-
return isReactive(value["__v_raw"]);
|
|
8522
|
+
if (/* @__PURE__ */ isReadonly(value)) {
|
|
8523
|
+
return /* @__PURE__ */ isReactive(value["__v_raw"]);
|
|
8379
8524
|
}
|
|
8380
8525
|
return !!(value && value["__v_isReactive"]);
|
|
8381
8526
|
}
|
|
8527
|
+
// @__NO_SIDE_EFFECTS__
|
|
8382
8528
|
function isReadonly(value) {
|
|
8383
8529
|
return !!(value && value["__v_isReadonly"]);
|
|
8384
8530
|
}
|
|
8531
|
+
// @__NO_SIDE_EFFECTS__
|
|
8385
8532
|
function isShallow(value) {
|
|
8386
8533
|
return !!(value && value["__v_isShallow"]);
|
|
8387
8534
|
}
|
|
8535
|
+
// @__NO_SIDE_EFFECTS__
|
|
8388
8536
|
function isProxy(value) {
|
|
8389
8537
|
return value ? !!value["__v_raw"] : false;
|
|
8390
8538
|
}
|
|
8539
|
+
// @__NO_SIDE_EFFECTS__
|
|
8391
8540
|
function toRaw(observed) {
|
|
8392
8541
|
const raw = observed && observed["__v_raw"];
|
|
8393
|
-
return raw ? toRaw(raw) : observed;
|
|
8542
|
+
return raw ? /* @__PURE__ */ toRaw(raw) : observed;
|
|
8394
8543
|
}
|
|
8395
8544
|
function markRaw(value) {
|
|
8396
8545
|
if (!(0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.hasOwn)(value, "__v_skip") && Object.isExtensible(value)) {
|
|
@@ -8398,20 +8547,23 @@ function markRaw(value) {
|
|
|
8398
8547
|
}
|
|
8399
8548
|
return value;
|
|
8400
8549
|
}
|
|
8401
|
-
const toReactive = (value) => (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isObject)(value) ? reactive(value) : value;
|
|
8402
|
-
const toReadonly = (value) => (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isObject)(value) ? readonly(value) : value;
|
|
8550
|
+
const toReactive = (value) => (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isObject)(value) ? /* @__PURE__ */ reactive(value) : value;
|
|
8551
|
+
const toReadonly = (value) => (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isObject)(value) ? /* @__PURE__ */ readonly(value) : value;
|
|
8403
8552
|
|
|
8553
|
+
// @__NO_SIDE_EFFECTS__
|
|
8404
8554
|
function isRef(r) {
|
|
8405
8555
|
return r ? r["__v_isRef"] === true : false;
|
|
8406
8556
|
}
|
|
8557
|
+
// @__NO_SIDE_EFFECTS__
|
|
8407
8558
|
function ref(value) {
|
|
8408
8559
|
return createRef(value, false);
|
|
8409
8560
|
}
|
|
8561
|
+
// @__NO_SIDE_EFFECTS__
|
|
8410
8562
|
function shallowRef(value) {
|
|
8411
8563
|
return createRef(value, true);
|
|
8412
8564
|
}
|
|
8413
8565
|
function createRef(rawValue, shallow) {
|
|
8414
|
-
if (isRef(rawValue)) {
|
|
8566
|
+
if (/* @__PURE__ */ isRef(rawValue)) {
|
|
8415
8567
|
return rawValue;
|
|
8416
8568
|
}
|
|
8417
8569
|
return new RefImpl(rawValue, shallow);
|
|
@@ -8470,7 +8622,7 @@ function triggerRef(ref2) {
|
|
|
8470
8622
|
}
|
|
8471
8623
|
}
|
|
8472
8624
|
function unref(ref2) {
|
|
8473
|
-
return isRef(ref2) ? ref2.value : ref2;
|
|
8625
|
+
return /* @__PURE__ */ isRef(ref2) ? ref2.value : ref2;
|
|
8474
8626
|
}
|
|
8475
8627
|
function toValue(source) {
|
|
8476
8628
|
return (0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isFunction)(source) ? source() : unref(source);
|
|
@@ -8479,7 +8631,7 @@ const shallowUnwrapHandlers = {
|
|
|
8479
8631
|
get: (target, key, receiver) => key === "__v_raw" ? target : unref(Reflect.get(target, key, receiver)),
|
|
8480
8632
|
set: (target, key, value, receiver) => {
|
|
8481
8633
|
const oldValue = target[key];
|
|
8482
|
-
if (isRef(oldValue) &&
|
|
8634
|
+
if (/* @__PURE__ */ isRef(oldValue) && !/* @__PURE__ */ isRef(value)) {
|
|
8483
8635
|
oldValue.value = value;
|
|
8484
8636
|
return true;
|
|
8485
8637
|
} else {
|
|
@@ -8509,6 +8661,7 @@ class CustomRefImpl {
|
|
|
8509
8661
|
function customRef(factory) {
|
|
8510
8662
|
return new CustomRefImpl(factory);
|
|
8511
8663
|
}
|
|
8664
|
+
// @__NO_SIDE_EFFECTS__
|
|
8512
8665
|
function toRefs(object) {
|
|
8513
8666
|
if ( true && !isProxy(object)) {
|
|
8514
8667
|
warn(`toRefs() expects a reactive object but received a plain one.`);
|
|
@@ -8544,9 +8697,9 @@ class ObjectRefImpl {
|
|
|
8544
8697
|
return this._value = val === void 0 ? this._defaultValue : val;
|
|
8545
8698
|
}
|
|
8546
8699
|
set value(newVal) {
|
|
8547
|
-
if (this._shallow && isRef(this._raw[this._key])) {
|
|
8700
|
+
if (this._shallow && /* @__PURE__ */ isRef(this._raw[this._key])) {
|
|
8548
8701
|
const nestedRef = this._object[this._key];
|
|
8549
|
-
if (isRef(nestedRef)) {
|
|
8702
|
+
if (/* @__PURE__ */ isRef(nestedRef)) {
|
|
8550
8703
|
nestedRef.value = newVal;
|
|
8551
8704
|
return;
|
|
8552
8705
|
}
|
|
@@ -8568,15 +8721,16 @@ class GetterRefImpl {
|
|
|
8568
8721
|
return this._value = this._getter();
|
|
8569
8722
|
}
|
|
8570
8723
|
}
|
|
8724
|
+
// @__NO_SIDE_EFFECTS__
|
|
8571
8725
|
function toRef(source, key, defaultValue) {
|
|
8572
|
-
if (isRef(source)) {
|
|
8726
|
+
if (/* @__PURE__ */ isRef(source)) {
|
|
8573
8727
|
return source;
|
|
8574
8728
|
} else if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isFunction)(source)) {
|
|
8575
8729
|
return new GetterRefImpl(source);
|
|
8576
8730
|
} else if ((0,_vue_shared__WEBPACK_IMPORTED_MODULE_0__.isObject)(source) && arguments.length > 1) {
|
|
8577
8731
|
return propertyToRef(source, key, defaultValue);
|
|
8578
8732
|
} else {
|
|
8579
|
-
return ref(source);
|
|
8733
|
+
return /* @__PURE__ */ ref(source);
|
|
8580
8734
|
}
|
|
8581
8735
|
}
|
|
8582
8736
|
function propertyToRef(source, key, defaultValue) {
|
|
@@ -8657,6 +8811,7 @@ class ComputedRefImpl {
|
|
|
8657
8811
|
}
|
|
8658
8812
|
}
|
|
8659
8813
|
}
|
|
8814
|
+
// @__NO_SIDE_EFFECTS__
|
|
8660
8815
|
function computed(getterOrOptions, debugOptions, isSSR = false) {
|
|
8661
8816
|
let getter;
|
|
8662
8817
|
let setter;
|
|
@@ -9072,7 +9227,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
9072
9227
|
/* harmony import */ var _vue_reactivity__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/reactivity */ "./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js");
|
|
9073
9228
|
/* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
|
|
9074
9229
|
/**
|
|
9075
|
-
* @vue/runtime-core v3.5.
|
|
9230
|
+
* @vue/runtime-core v3.5.27
|
|
9076
9231
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
9077
9232
|
* @license MIT
|
|
9078
9233
|
**/
|
|
@@ -11223,7 +11378,7 @@ Server rendered element contains more child nodes than client vdom.`
|
|
|
11223
11378
|
logMismatchError();
|
|
11224
11379
|
}
|
|
11225
11380
|
if (forcePatch && (key.endsWith("value") || key === "indeterminate") || (0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.isOn)(key) && !(0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.isReservedProp)(key) || // force hydrate v-bind with .prop modifiers
|
|
11226
|
-
key[0] === "." || isCustomElement) {
|
|
11381
|
+
key[0] === "." || isCustomElement && !(0,_vue_shared__WEBPACK_IMPORTED_MODULE_1__.isReservedProp)(key)) {
|
|
11227
11382
|
patchProp(el, key, null, props[key], void 0, parentComponent);
|
|
11228
11383
|
}
|
|
11229
11384
|
}
|
|
@@ -17571,7 +17726,7 @@ function isMemoSame(cached, memo) {
|
|
|
17571
17726
|
return true;
|
|
17572
17727
|
}
|
|
17573
17728
|
|
|
17574
|
-
const version = "3.5.
|
|
17729
|
+
const version = "3.5.27";
|
|
17575
17730
|
const warn = true ? warn$1 : 0;
|
|
17576
17731
|
const ErrorTypeStrings = ErrorTypeStrings$1 ;
|
|
17577
17732
|
const devtools = true ? devtools$1 : 0;
|
|
@@ -17782,7 +17937,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
17782
17937
|
/* harmony import */ var _vue_runtime_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/runtime-core */ "./node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js");
|
|
17783
17938
|
/* harmony import */ var _vue_runtime_core__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js");
|
|
17784
17939
|
/**
|
|
17785
|
-
* @vue/runtime-dom v3.5.
|
|
17940
|
+
* @vue/runtime-dom v3.5.27
|
|
17786
17941
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
17787
17942
|
* @license MIT
|
|
17788
17943
|
**/
|
|
@@ -19788,7 +19943,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
19788
19943
|
/* harmony export */ toTypeString: () => (/* binding */ toTypeString)
|
|
19789
19944
|
/* harmony export */ });
|
|
19790
19945
|
/**
|
|
19791
|
-
* @vue/shared v3.5.
|
|
19946
|
+
* @vue/shared v3.5.27
|
|
19792
19947
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
19793
19948
|
* @license MIT
|
|
19794
19949
|
**/
|
|
@@ -23512,6 +23667,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
23512
23667
|
/* harmony export */ BSONValue: () => (/* binding */ BSONValue),
|
|
23513
23668
|
/* harmony export */ BSONVersionError: () => (/* binding */ BSONVersionError),
|
|
23514
23669
|
/* harmony export */ Binary: () => (/* binding */ Binary),
|
|
23670
|
+
/* harmony export */ ByteUtils: () => (/* binding */ ByteUtils),
|
|
23515
23671
|
/* harmony export */ Code: () => (/* binding */ Code),
|
|
23516
23672
|
/* harmony export */ DBRef: () => (/* binding */ DBRef),
|
|
23517
23673
|
/* harmony export */ Decimal128: () => (/* binding */ Decimal128),
|
|
@@ -23521,6 +23677,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
23521
23677
|
/* harmony export */ Long: () => (/* binding */ Long),
|
|
23522
23678
|
/* harmony export */ MaxKey: () => (/* binding */ MaxKey),
|
|
23523
23679
|
/* harmony export */ MinKey: () => (/* binding */ MinKey),
|
|
23680
|
+
/* harmony export */ NumberUtils: () => (/* binding */ NumberUtils),
|
|
23524
23681
|
/* harmony export */ ObjectId: () => (/* binding */ ObjectId),
|
|
23525
23682
|
/* harmony export */ Timestamp: () => (/* binding */ Timestamp),
|
|
23526
23683
|
/* harmony export */ UUID: () => (/* binding */ UUID),
|
|
@@ -23765,6 +23922,7 @@ const nodejsRandomBytes = (() => {
|
|
|
23765
23922
|
}
|
|
23766
23923
|
})();
|
|
23767
23924
|
const nodeJsByteUtils = {
|
|
23925
|
+
isUint8Array: isUint8Array,
|
|
23768
23926
|
toLocalBufferType(potentialBuffer) {
|
|
23769
23927
|
if (Buffer.isBuffer(potentialBuffer)) {
|
|
23770
23928
|
return potentialBuffer;
|
|
@@ -23787,6 +23945,12 @@ const nodeJsByteUtils = {
|
|
|
23787
23945
|
allocateUnsafe(size) {
|
|
23788
23946
|
return Buffer.allocUnsafe(size);
|
|
23789
23947
|
},
|
|
23948
|
+
compare(a, b) {
|
|
23949
|
+
return nodeJsByteUtils.toLocalBufferType(a).compare(b);
|
|
23950
|
+
},
|
|
23951
|
+
concat(list) {
|
|
23952
|
+
return Buffer.concat(list);
|
|
23953
|
+
},
|
|
23790
23954
|
equals(a, b) {
|
|
23791
23955
|
return nodeJsByteUtils.toLocalBufferType(a).equals(b);
|
|
23792
23956
|
},
|
|
@@ -23796,6 +23960,9 @@ const nodeJsByteUtils = {
|
|
|
23796
23960
|
fromBase64(base64) {
|
|
23797
23961
|
return Buffer.from(base64, 'base64');
|
|
23798
23962
|
},
|
|
23963
|
+
fromUTF8(utf8) {
|
|
23964
|
+
return Buffer.from(utf8, 'utf8');
|
|
23965
|
+
},
|
|
23799
23966
|
toBase64(buffer) {
|
|
23800
23967
|
return nodeJsByteUtils.toLocalBufferType(buffer).toString('base64');
|
|
23801
23968
|
},
|
|
@@ -23870,6 +24037,7 @@ const webRandomBytes = (() => {
|
|
|
23870
24037
|
})();
|
|
23871
24038
|
const HEX_DIGIT = /(\d|[a-f])/i;
|
|
23872
24039
|
const webByteUtils = {
|
|
24040
|
+
isUint8Array: isUint8Array,
|
|
23873
24041
|
toLocalBufferType(potentialUint8array) {
|
|
23874
24042
|
const stringTag = potentialUint8array?.[Symbol.toStringTag] ??
|
|
23875
24043
|
Object.prototype.toString.call(potentialUint8array);
|
|
@@ -23896,12 +24064,43 @@ const webByteUtils = {
|
|
|
23896
24064
|
allocateUnsafe(size) {
|
|
23897
24065
|
return webByteUtils.allocate(size);
|
|
23898
24066
|
},
|
|
23899
|
-
|
|
23900
|
-
if (
|
|
24067
|
+
compare(uint8Array, otherUint8Array) {
|
|
24068
|
+
if (uint8Array === otherUint8Array)
|
|
24069
|
+
return 0;
|
|
24070
|
+
const len = Math.min(uint8Array.length, otherUint8Array.length);
|
|
24071
|
+
for (let i = 0; i < len; i++) {
|
|
24072
|
+
if (uint8Array[i] < otherUint8Array[i])
|
|
24073
|
+
return -1;
|
|
24074
|
+
if (uint8Array[i] > otherUint8Array[i])
|
|
24075
|
+
return 1;
|
|
24076
|
+
}
|
|
24077
|
+
if (uint8Array.length < otherUint8Array.length)
|
|
24078
|
+
return -1;
|
|
24079
|
+
if (uint8Array.length > otherUint8Array.length)
|
|
24080
|
+
return 1;
|
|
24081
|
+
return 0;
|
|
24082
|
+
},
|
|
24083
|
+
concat(uint8Arrays) {
|
|
24084
|
+
if (uint8Arrays.length === 0)
|
|
24085
|
+
return webByteUtils.allocate(0);
|
|
24086
|
+
let totalLength = 0;
|
|
24087
|
+
for (const uint8Array of uint8Arrays) {
|
|
24088
|
+
totalLength += uint8Array.length;
|
|
24089
|
+
}
|
|
24090
|
+
const result = webByteUtils.allocate(totalLength);
|
|
24091
|
+
let offset = 0;
|
|
24092
|
+
for (const uint8Array of uint8Arrays) {
|
|
24093
|
+
result.set(uint8Array, offset);
|
|
24094
|
+
offset += uint8Array.length;
|
|
24095
|
+
}
|
|
24096
|
+
return result;
|
|
24097
|
+
},
|
|
24098
|
+
equals(uint8Array, otherUint8Array) {
|
|
24099
|
+
if (uint8Array.byteLength !== otherUint8Array.byteLength) {
|
|
23901
24100
|
return false;
|
|
23902
24101
|
}
|
|
23903
|
-
for (let i = 0; i <
|
|
23904
|
-
if (
|
|
24102
|
+
for (let i = 0; i < uint8Array.byteLength; i++) {
|
|
24103
|
+
if (uint8Array[i] !== otherUint8Array[i]) {
|
|
23905
24104
|
return false;
|
|
23906
24105
|
}
|
|
23907
24106
|
}
|
|
@@ -23913,6 +24112,9 @@ const webByteUtils = {
|
|
|
23913
24112
|
fromBase64(base64) {
|
|
23914
24113
|
return Uint8Array.from(atob(base64), c => c.charCodeAt(0));
|
|
23915
24114
|
},
|
|
24115
|
+
fromUTF8(utf8) {
|
|
24116
|
+
return new TextEncoder().encode(utf8);
|
|
24117
|
+
},
|
|
23916
24118
|
toBase64(uint8array) {
|
|
23917
24119
|
return btoa(webByteUtils.toISO88591(uint8array));
|
|
23918
24120
|
},
|
|
@@ -28148,6 +28350,7 @@ var bson = /*#__PURE__*/Object.freeze({
|
|
|
28148
28350
|
BSONValue: BSONValue,
|
|
28149
28351
|
BSONVersionError: BSONVersionError,
|
|
28150
28352
|
Binary: Binary,
|
|
28353
|
+
ByteUtils: ByteUtils,
|
|
28151
28354
|
Code: Code,
|
|
28152
28355
|
DBRef: DBRef,
|
|
28153
28356
|
Decimal128: Decimal128,
|
|
@@ -28157,6 +28360,7 @@ var bson = /*#__PURE__*/Object.freeze({
|
|
|
28157
28360
|
Long: Long,
|
|
28158
28361
|
MaxKey: MaxKey,
|
|
28159
28362
|
MinKey: MinKey,
|
|
28363
|
+
NumberUtils: NumberUtils,
|
|
28160
28364
|
ObjectId: ObjectId,
|
|
28161
28365
|
Timestamp: Timestamp,
|
|
28162
28366
|
UUID: UUID,
|
|
@@ -32210,7 +32414,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
32210
32414
|
/* harmony import */ var _vue_runtime_dom__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/runtime-dom */ "./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js");
|
|
32211
32415
|
/* harmony import */ var _vue_runtime_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @vue/runtime-dom */ "./node_modules/@vue/runtime-dom/dist/runtime-dom.esm-bundler.js");
|
|
32212
32416
|
/**
|
|
32213
|
-
* vue v3.5.
|
|
32417
|
+
* vue v3.5.27
|
|
32214
32418
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
32215
32419
|
* @license MIT
|
|
32216
32420
|
**/
|
|
@@ -32246,7 +32450,7 @@ const compile = () => {
|
|
|
32246
32450
|
(module) {
|
|
32247
32451
|
|
|
32248
32452
|
"use strict";
|
|
32249
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.2.
|
|
32453
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.2.4","description":"A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js","test:frontend":"mocha test/frontend/*.test.js"}}');
|
|
32250
32454
|
|
|
32251
32455
|
/***/ }
|
|
32252
32456
|
|
package/frontend/public/tw.css
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
destroyed() {
|
|
5
|
+
this.$parent.$options.$children = this.$parent.$options.$children.filter(
|
|
6
|
+
(el) => el !== this
|
|
7
|
+
);
|
|
8
|
+
},
|
|
9
|
+
created() {
|
|
10
|
+
this.$parent.$options.$children = this.$parent.$options.$children || [];
|
|
11
|
+
this.$parent.$options.$children.push(this);
|
|
12
|
+
},
|
|
13
|
+
mounted() {
|
|
14
|
+
this.isMounted = true;
|
|
15
|
+
},
|
|
16
|
+
unmounted() {
|
|
17
|
+
this.isMounted = false;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Deep equality check for values (handles primitives, arrays, objects, dates, etc.)
|
|
5
|
+
* @param {*} a - First value to compare
|
|
6
|
+
* @param {*} b - Second value to compare
|
|
7
|
+
* @returns {boolean} - True if values are deeply equal
|
|
8
|
+
*/
|
|
9
|
+
function deepEqual(a, b) {
|
|
10
|
+
// Handle primitives and same reference
|
|
11
|
+
if (a === b) return true;
|
|
12
|
+
|
|
13
|
+
// Handle null and undefined
|
|
14
|
+
if (a == null || b == null) return a === b;
|
|
15
|
+
|
|
16
|
+
// Handle different types
|
|
17
|
+
if (typeof a !== typeof b) return false;
|
|
18
|
+
|
|
19
|
+
// Handle dates
|
|
20
|
+
if (a instanceof Date && b instanceof Date) {
|
|
21
|
+
return a.getTime() === b.getTime();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Handle arrays - must both be arrays
|
|
25
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
26
|
+
if (!Array.isArray(a) || !Array.isArray(b)) return false;
|
|
27
|
+
if (a.length !== b.length) return false;
|
|
28
|
+
for (let i = 0; i < a.length; i++) {
|
|
29
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Handle objects (non-arrays)
|
|
35
|
+
if (typeof a === 'object' && typeof b === 'object') {
|
|
36
|
+
const keysA = Object.keys(a);
|
|
37
|
+
const keysB = Object.keys(b);
|
|
38
|
+
|
|
39
|
+
if (keysA.length !== keysB.length) return false;
|
|
40
|
+
|
|
41
|
+
for (const key of keysA) {
|
|
42
|
+
if (!keysB.includes(key)) return false;
|
|
43
|
+
if (!deepEqual(a[key], b[key])) return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Fallback for primitives (strings, numbers, booleans)
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = deepEqual;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const api = require('../api');
|
|
4
|
+
const baseComponent = require('../_util/baseComponent');
|
|
4
5
|
const template = require('./dashboard.html');
|
|
5
6
|
|
|
6
|
-
module.exports =
|
|
7
|
+
module.exports = {
|
|
7
8
|
template: template,
|
|
9
|
+
extends: baseComponent,
|
|
8
10
|
props: ['dashboardId'],
|
|
9
11
|
data: function() {
|
|
10
12
|
return {
|
|
@@ -48,7 +50,7 @@ module.exports = app => app.component('dashboard', {
|
|
|
48
50
|
}
|
|
49
51
|
},
|
|
50
52
|
shouldEvaluateDashboard() {
|
|
51
|
-
if (this.dashboardResults.length === 0) {
|
|
53
|
+
if (!this.dashboardResults || this.dashboardResults.length === 0) {
|
|
52
54
|
return true;
|
|
53
55
|
}
|
|
54
56
|
|
|
@@ -115,6 +117,22 @@ module.exports = app => app.component('dashboard', {
|
|
|
115
117
|
this.startingChat = false;
|
|
116
118
|
this.showActionsMenu = false;
|
|
117
119
|
}
|
|
120
|
+
},
|
|
121
|
+
async loadInitial() {
|
|
122
|
+
const { dashboard, dashboardResults, error } = await api.Dashboard.getDashboard({ dashboardId: this.dashboardId, evaluate: false });
|
|
123
|
+
if (!dashboard) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
this.dashboard = dashboard;
|
|
127
|
+
this.code = this.dashboard.code;
|
|
128
|
+
this.title = this.dashboard.title;
|
|
129
|
+
this.description = this.dashboard.description ?? '';
|
|
130
|
+
this.dashboardResults = dashboardResults;
|
|
131
|
+
if (this.shouldEvaluateDashboard()) {
|
|
132
|
+
await this.evaluateDashboard();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.status = 'loaded';
|
|
118
136
|
}
|
|
119
137
|
},
|
|
120
138
|
computed: {
|
|
@@ -125,22 +143,9 @@ module.exports = app => app.component('dashboard', {
|
|
|
125
143
|
mounted: async function() {
|
|
126
144
|
document.addEventListener('click', this.handleDocumentClick);
|
|
127
145
|
this.showEditor = this.$route.query.edit;
|
|
128
|
-
|
|
129
|
-
if (!dashboard) {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
this.dashboard = dashboard;
|
|
133
|
-
this.code = this.dashboard.code;
|
|
134
|
-
this.title = this.dashboard.title;
|
|
135
|
-
this.description = this.dashboard.description ?? '';
|
|
136
|
-
this.dashboardResults = dashboardResults;
|
|
137
|
-
if (this.shouldEvaluateDashboard()) {
|
|
138
|
-
await this.evaluateDashboard();
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
this.status = 'loaded';
|
|
146
|
+
await this.loadInitial();
|
|
142
147
|
},
|
|
143
148
|
beforeDestroy() {
|
|
144
149
|
document.removeEventListener('click', this.handleDocumentClick);
|
|
145
150
|
}
|
|
146
|
-
}
|
|
151
|
+
};
|
|
@@ -7,6 +7,16 @@ module.exports = app => app.component('confirm-changes', {
|
|
|
7
7
|
props: ['value'],
|
|
8
8
|
computed: {
|
|
9
9
|
displayValue() {
|
|
10
|
+
const hasUnsetFields = Object.keys(this.value).some(key => this.value[key] === undefined);
|
|
11
|
+
if (hasUnsetFields) {
|
|
12
|
+
const unsetFields = Object.keys(this.value)
|
|
13
|
+
.filter(key => this.value[key] === undefined)
|
|
14
|
+
.reduce((obj, key) => Object.assign(obj, { [key]: 1 }), {});
|
|
15
|
+
const setFields = Object.keys(this.value)
|
|
16
|
+
.filter(key => this.value[key] !== undefined)
|
|
17
|
+
.reduce((obj, key) => Object.assign(obj, { [key]: this.value[key] }), {});
|
|
18
|
+
return JSON.stringify({ $set: setFields, $unset: unsetFields }, null, ' ').trim();
|
|
19
|
+
}
|
|
10
20
|
return JSON.stringify(this.value, null, ' ').trim();
|
|
11
21
|
}
|
|
12
22
|
},
|
|
@@ -21,4 +31,4 @@ module.exports = app => app.component('confirm-changes', {
|
|
|
21
31
|
mounted() {
|
|
22
32
|
Prism.highlightElement(this.$refs.code);
|
|
23
33
|
}
|
|
24
|
-
});
|
|
34
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<div class="document px-1 pt-4 md:px-0 bg-slate-50 w-full">
|
|
1
|
+
<div class="document px-1 pt-4 pb-16 md:px-0 bg-slate-50 w-full">
|
|
2
2
|
<div class="max-w-7xl mx-auto">
|
|
3
3
|
<div class="flex gap-4 items-center sticky top-0 z-50 bg-white p-4 border-b border-gray-200 shadow-sm">
|
|
4
4
|
<div class="font-bold overflow-hidden text-ellipsis">{{model}}: {{documentId}}</div>
|
|
@@ -74,10 +74,25 @@ module.exports = app => app.component('document', {
|
|
|
74
74
|
if (Object.keys(this.invalid).length > 0) {
|
|
75
75
|
throw new Error('Invalid paths: ' + Object.keys(this.invalid).join(', '));
|
|
76
76
|
}
|
|
77
|
+
|
|
78
|
+
let update = this.changes;
|
|
79
|
+
let unset = {};
|
|
80
|
+
const hasUnsetFields = Object.keys(this.changes)
|
|
81
|
+
.some(key => this.changes[key] === undefined);
|
|
82
|
+
if (hasUnsetFields) {
|
|
83
|
+
unset = Object.keys(this.changes)
|
|
84
|
+
.filter(key => this.changes[key] === undefined)
|
|
85
|
+
.reduce((obj, key) => Object.assign(obj, { [key]: 1 }), {});
|
|
86
|
+
update = Object.keys(this.changes)
|
|
87
|
+
.filter(key => this.changes[key] !== undefined)
|
|
88
|
+
.reduce((obj, key) => Object.assign(obj, { [key]: this.changes[key] }), {});
|
|
89
|
+
}
|
|
90
|
+
|
|
77
91
|
const { doc } = await api.Model.updateDocument({
|
|
78
92
|
model: this.model,
|
|
79
93
|
_id: this.document._id,
|
|
80
|
-
update
|
|
94
|
+
update,
|
|
95
|
+
unset
|
|
81
96
|
});
|
|
82
97
|
this.document = doc;
|
|
83
98
|
this.changes = {};
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
:value="getEditValueForPath(path)"
|
|
75
75
|
:format="dateType"
|
|
76
76
|
v-bind="getEditComponentProps(path)"
|
|
77
|
-
@input="
|
|
77
|
+
@input="handleInputChange($event)"
|
|
78
78
|
@error="invalid[path.path] = $event;"
|
|
79
79
|
>
|
|
80
80
|
</component>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
5
|
const mpath = require('mpath');
|
|
6
|
-
const
|
|
6
|
+
const deepEqual = require('../../_util/deepEqual');
|
|
7
7
|
const template = require('./document-property.html');
|
|
8
8
|
|
|
9
9
|
const appendCSS = require('../../appendCSS');
|
|
@@ -95,6 +95,20 @@ module.exports = app => app.component('document-property', {
|
|
|
95
95
|
}
|
|
96
96
|
},
|
|
97
97
|
methods: {
|
|
98
|
+
handleInputChange(newValue) {
|
|
99
|
+
const currentValue = this.getValueForPath(this.path.path);
|
|
100
|
+
|
|
101
|
+
// Only record as a change if the value is actually different
|
|
102
|
+
if (!deepEqual(currentValue, newValue)) {
|
|
103
|
+
this.changes[this.path.path] = newValue;
|
|
104
|
+
} else {
|
|
105
|
+
// If the value is the same as the original, remove it from changes
|
|
106
|
+
delete this.changes[this.path.path];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Always clear invalid state on input
|
|
110
|
+
delete this.invalid[this.path.path];
|
|
111
|
+
},
|
|
98
112
|
getComponentForPath(schemaPath) {
|
|
99
113
|
if (schemaPath.instance === 'Array') {
|
|
100
114
|
return 'detail-array';
|
|
@@ -19,20 +19,12 @@ module.exports = app => app.component('edit-boolean', {
|
|
|
19
19
|
this.selectedValue = newValue;
|
|
20
20
|
},
|
|
21
21
|
selectedValue(newValue) {
|
|
22
|
-
|
|
23
|
-
const emitValue = this.convertValueToString(newValue);
|
|
24
|
-
this.$emit('input', emitValue);
|
|
22
|
+
this.$emit('input', newValue);
|
|
25
23
|
}
|
|
26
24
|
},
|
|
27
25
|
methods: {
|
|
28
26
|
selectValue(value) {
|
|
29
27
|
this.selectedValue = value;
|
|
30
|
-
},
|
|
31
|
-
convertValueToString(value) {
|
|
32
|
-
// Convert null/undefined to strings for proper backend serialization
|
|
33
|
-
if (value === null) return 'null';
|
|
34
|
-
if (typeof value === 'undefined') return 'undefined';
|
|
35
|
-
return value;
|
|
36
28
|
}
|
|
37
29
|
}
|
|
38
30
|
});
|
package/frontend/src/index.js
CHANGED
|
@@ -44,7 +44,11 @@ requireComponents.keys().forEach((filePath) => {
|
|
|
44
44
|
// Check if the file name matches the directory name
|
|
45
45
|
if (directoryName === fileName) {
|
|
46
46
|
components[directoryName] = requireComponents(filePath);
|
|
47
|
-
components[directoryName]
|
|
47
|
+
if (typeof components[directoryName] === 'function') {
|
|
48
|
+
components[directoryName](app);
|
|
49
|
+
} else {
|
|
50
|
+
app.component(directoryName, components[directoryName]);
|
|
51
|
+
}
|
|
48
52
|
}
|
|
49
53
|
});
|
|
50
54
|
|
|
@@ -116,7 +120,7 @@ app.component('app-component', {
|
|
|
116
120
|
|
|
117
121
|
this.user = user;
|
|
118
122
|
this.roles = roles;
|
|
119
|
-
|
|
123
|
+
|
|
120
124
|
setTimeout(() => {
|
|
121
125
|
this.$router.replace(this.$router.currentRoute.value.path);
|
|
122
126
|
}, 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mongoosejs/studio",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.",
|
|
5
5
|
"homepage": "https://mongoosestudio.app/",
|
|
6
6
|
"repository": {
|
|
@@ -34,12 +34,14 @@
|
|
|
34
34
|
"eslint": "9.30.0",
|
|
35
35
|
"express": "4.x",
|
|
36
36
|
"mocha": "10.2.0",
|
|
37
|
-
"mongoose": "9.x"
|
|
37
|
+
"mongoose": "9.x",
|
|
38
|
+
"sinon": "^21.0.1"
|
|
38
39
|
},
|
|
39
40
|
"scripts": {
|
|
40
41
|
"lint": "eslint .",
|
|
41
42
|
"tailwind": "tailwindcss -o ./frontend/public/tw.css",
|
|
42
43
|
"tailwind:watch": "tailwindcss -o ./frontend/public/tw.css --watch",
|
|
43
|
-
"test": "mocha test/*.test.js"
|
|
44
|
+
"test": "mocha test/*.test.js",
|
|
45
|
+
"test:frontend": "mocha test/frontend/*.test.js"
|
|
44
46
|
}
|
|
45
47
|
}
|