@mongoosejs/studio 0.0.130 → 0.0.131

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.
@@ -1639,38 +1639,39 @@ module.exports = app => app.component('document-details', {
1639
1639
  shouldUseDatePicker() {
1640
1640
  return this.fieldData.type === 'Date';
1641
1641
  },
1642
- filteredSchemaPaths() {
1642
+ typeFilteredSchemaPaths() {
1643
1643
  let paths = this.schemaPaths || [];
1644
1644
 
1645
- // Filter by search query
1646
- if (this.searchQuery.trim()) {
1647
- const query = this.searchQuery.toLowerCase();
1648
- paths = paths.filter(path =>
1649
- path.path.toLowerCase().includes(query)
1650
- );
1651
- }
1652
-
1653
- // Filter by data type
1654
1645
  if (this.selectedType) {
1655
- paths = paths.filter(path =>
1656
- path.instance === this.selectedType
1657
- );
1646
+ paths = paths.filter(path => path.instance === this.selectedType);
1658
1647
  }
1659
1648
 
1660
1649
  return paths;
1661
1650
  },
1662
- filteredVirtuals() {
1663
- let virtuals = this.virtuals;
1651
+ filteredSchemaPaths() {
1652
+ const paths = this.typeFilteredSchemaPaths.slice();
1664
1653
 
1665
- // Filter by search query
1666
- if (this.searchQuery.trim()) {
1667
- const query = this.searchQuery.toLowerCase();
1668
- virtuals = virtuals.filter(virtual =>
1669
- virtual.name.toLowerCase().includes(query)
1670
- );
1654
+ if (!this.searchQuery.trim()) {
1655
+ return paths;
1671
1656
  }
1672
1657
 
1673
- // Filter by data type
1658
+ const query = this.searchQuery.toLowerCase();
1659
+ const matches = [];
1660
+ const nonMatches = [];
1661
+
1662
+ paths.forEach(path => {
1663
+ if (path.path.toLowerCase().includes(query)) {
1664
+ matches.push(path);
1665
+ } else {
1666
+ nonMatches.push(path);
1667
+ }
1668
+ });
1669
+
1670
+ return matches.concat(nonMatches);
1671
+ },
1672
+ typeFilteredVirtuals() {
1673
+ let virtuals = this.virtuals;
1674
+
1674
1675
  if (this.selectedType) {
1675
1676
  virtuals = virtuals.filter(virtual => {
1676
1677
  const virtualType = this.getVirtualFieldType(virtual);
@@ -1680,6 +1681,58 @@ module.exports = app => app.component('document-details', {
1680
1681
 
1681
1682
  return virtuals;
1682
1683
  },
1684
+ filteredVirtuals() {
1685
+ const virtuals = this.typeFilteredVirtuals.slice();
1686
+
1687
+ if (!this.searchQuery.trim()) {
1688
+ return virtuals;
1689
+ }
1690
+
1691
+ const query = this.searchQuery.toLowerCase();
1692
+ const matches = [];
1693
+ const nonMatches = [];
1694
+
1695
+ virtuals.forEach(virtual => {
1696
+ if (virtual.name.toLowerCase().includes(query)) {
1697
+ matches.push(virtual);
1698
+ } else {
1699
+ nonMatches.push(virtual);
1700
+ }
1701
+ });
1702
+
1703
+ return matches.concat(nonMatches);
1704
+ },
1705
+ schemaSearchMatchSet() {
1706
+ if (!this.searchQuery.trim()) {
1707
+ return new Set();
1708
+ }
1709
+
1710
+ const query = this.searchQuery.toLowerCase();
1711
+ return new Set(
1712
+ this.typeFilteredSchemaPaths
1713
+ .filter(path => path.path.toLowerCase().includes(query))
1714
+ .map(path => path.path)
1715
+ );
1716
+ },
1717
+ virtualSearchMatchSet() {
1718
+ if (!this.searchQuery.trim()) {
1719
+ return new Set();
1720
+ }
1721
+
1722
+ const query = this.searchQuery.toLowerCase();
1723
+ return new Set(
1724
+ this.typeFilteredVirtuals
1725
+ .filter(virtual => virtual.name.toLowerCase().includes(query))
1726
+ .map(virtual => virtual.name)
1727
+ );
1728
+ },
1729
+ hasSearchMatches() {
1730
+ if (!this.searchQuery.trim()) {
1731
+ return true;
1732
+ }
1733
+
1734
+ return this.schemaSearchMatchSet.size > 0 || this.virtualSearchMatchSet.size > 0;
1735
+ },
1683
1736
  formattedJson() {
1684
1737
  if (!this.document) {
1685
1738
  return '{}';
@@ -1695,6 +1748,20 @@ module.exports = app => app.component('document-details', {
1695
1748
  this.collapsedVirtuals.add(fieldName);
1696
1749
  }
1697
1750
  },
1751
+ isSchemaPathMatched(path) {
1752
+ if (!path) {
1753
+ return false;
1754
+ }
1755
+
1756
+ return this.schemaSearchMatchSet.has(path.path);
1757
+ },
1758
+ isVirtualMatched(virtual) {
1759
+ if (!virtual) {
1760
+ return false;
1761
+ }
1762
+
1763
+ return this.virtualSearchMatchSet.has(virtual.name);
1764
+ },
1698
1765
  isVirtualFieldCollapsed(fieldName) {
1699
1766
  return this.collapsedVirtuals.has(fieldName);
1700
1767
  },
@@ -1909,7 +1976,7 @@ module.exports = app => app.component('document-property', {
1909
1976
  isValueExpanded: false // Track if the value is expanded
1910
1977
  };
1911
1978
  },
1912
- props: ['path', 'document', 'schemaPaths', 'editting', 'changes', 'invalid'],
1979
+ props: ['path', 'document', 'schemaPaths', 'editting', 'changes', 'invalid', 'highlight'],
1913
1980
  computed: {
1914
1981
  valueAsString() {
1915
1982
  const value = this.getValueForPath(this.path.path);
@@ -1989,6 +2056,7 @@ module.exports = app => app.component('document-property', {
1989
2056
  }
1990
2057
  });
1991
2058
 
2059
+
1992
2060
  /***/ }),
1993
2061
 
1994
2062
  /***/ "./frontend/src/document/confirm-changes/confirm-changes.js":
@@ -5376,7 +5444,7 @@ module.exports = ".document-details {\n width: 100%;\n}\n\n.document-details .v
5376
5444
  /***/ ((module) => {
5377
5445
 
5378
5446
  "use strict";
5379
- module.exports = "<div class=\"document-details\">\n <!-- View Toggle and Search/Filter Bar -->\n <div class=\"mb-4 mt-4\">\n\n <!-- Search and Filter Bar (only show in fields view) -->\n <div v-if=\"viewMode === 'fields'\" class=\"flex md:gap-3\">\n <!-- Search Bar -->\n <div class=\"relative flex-1\">\n <input\n ref=\"searchInput\"\n v-model=\"searchQuery\"\n type=\"text\"\n placeholder=\"Search fields...\"\n class=\"w-full px-4 py-2 pl-10 pr-4 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n <div class=\"absolute inset-y-0 left-0 flex items-center pl-3\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"></path>\n </svg>\n </div>\n </div>\n\n <!-- Data Type Filter -->\n <div class=\"relative\">\n <select\n v-model=\"selectedType\"\n class=\"hidden md:block px-4 py-2 pr-8 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white min-w-[140px] appearance-none\"\n >\n <option value=\"\">All Types</option>\n <option v-for=\"type in availableTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <div class=\"absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none\">\n <svg class=\"w-4 h-4 text-gray-400\" 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 </div>\n </div>\n\n <!-- Add Field Button -->\n <button\n @click=\"openAddFieldModal\"\n class=\"hidden md:block px-4 py-2 text-sm font-medium text-white bg-green-600 hover:bg-green-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 flex items-center gap-2\"\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=\"M12 4v16m8-8H4\"></path>\n </svg>\n Add Field\n </button>\n </div>\n </div>\n\n <!-- Fields View -->\n <div v-if=\"viewMode === 'fields'\">\n <!-- Schema Paths -->\n <div v-for=\"path in filteredSchemaPaths\" :key=\"path\" class=\"value\">\n <document-property\n :path=\"path\"\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"></document-property>\n </div>\n\n <!-- Virtual Fields -->\n <div v-for=\"path in filteredVirtuals\" class=\"border border-gray-200 rounded-lg mb-2\">\n <!-- Virtual Field Header (Always Visible) -->\n <div\n @click=\"toggleVirtualField(path.name)\"\n class=\"p-3 bg-slate-50 hover:bg-slate-100 cursor-pointer flex items-center justify-between border-b border-gray-200\"\n >\n <div class=\"flex items-center\">\n <svg\n :class=\"isVirtualFieldCollapsed(path.name) ? '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.name}}</span>\n <span v-if=\"path.isVirtual\" class=\"ml-2 text-sm text-purple-600\">(virtual - {{getVirtualFieldType(path)}})</span>\n <span v-else class=\"ml-2 text-sm text-blue-600\">(user-added - {{getVirtualFieldType(path)}})</span>\n </div>\n </div>\n\n <!-- Virtual Field Content (Collapsible) -->\n <div v-if=\"!isVirtualFieldCollapsed(path.name)\" class=\"p-3\">\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n\n <!-- No Results Message -->\n <div v-if=\"searchQuery && filteredSchemaPaths.length === 0 && filteredVirtuals.length === 0\" class=\"text-center py-8 text-gray-500\">\n <svg class=\"w-12 h-12 mx-auto mb-4 text-gray-300\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.29-1.009-5.824-2.709M15 6.291A7.962 7.962 0 0012 5c-2.34 0-4.29 1.009-5.824 2.709\"></path>\n </svg>\n <p>No fields found matching \"{{searchQuery}}\"</p>\n </div>\n </div>\n\n <!-- JSON View -->\n <div v-if=\"viewMode === 'json'\" class=\"json-view\">\n <div class=\"border border-gray-300 rounded-lg bg-gray-50 p-4 overflow-auto\">\n <pre class=\"text-sm font-mono text-gray-800 whitespace-pre\">{{formattedJson}}</pre>\n </div>\n </div>\n\n <!-- Add Field Modal -->\n <modal v-if=\"showAddFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"closeAddFieldModal\">&times;</div>\n <div class=\"add-field-modal\">\n <div class=\"mb-6\">\n <h2 class=\"text-xl font-semibold text-gray-900 mb-2\">Add New Field</h2>\n <p class=\"text-sm text-gray-600\">Create a new field for this document</p>\n </div>\n\n <form @submit.prevent=\"handleAddFieldSubmit\" class=\"space-y-4\">\n <!-- Field Name -->\n <div>\n <label for=\"fieldName\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Field Name *\n </label>\n <input\n id=\"fieldName\"\n v-model=\"fieldData.name\"\n type=\"text\"\n required\n placeholder=\"Enter field name...\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.name }\"\n />\n <p v-if=\"fieldErrors.name\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.name}}</p>\n <p v-if=\"fieldData.name && getTransformedFieldName() !== fieldData.name.trim()\" class=\"mt-1 text-sm text-blue-600\">\n Field name will be: <code class=\"bg-blue-50 px-1 py-0.5 rounded text-xs\">{{getTransformedFieldName()}}</code>\n </p>\n </div>\n\n <!-- Field Type -->\n <div>\n <label for=\"fieldType\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Field Type *\n </label>\n <select\n id=\"fieldType\"\n v-model=\"fieldData.type\"\n required\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.type }\"\n >\n <option value=\"\">Select field type...</option>\n <option v-for=\"type in allFieldTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <p v-if=\"fieldErrors.type\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.type}}</p>\n </div>\n\n <!-- Field Value -->\n <div>\n <label for=\"fieldValue\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Initial Value\n </label>\n\n <!-- Date picker for Date type -->\n <input\n v-if=\"shouldUseDatePicker\"\n v-model=\"fieldData.value\"\n type=\"date\"\n id=\"fieldValue\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- Simple input for basic types -->\n <input\n v-else-if=\"!shouldUseCodeMirror\"\n v-model=\"fieldData.value\"\n type=\"text\"\n id=\"fieldValue\"\n placeholder=\"Enter initial value (optional)...\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- CodeMirror textarea for complex types -->\n <textarea\n v-else\n ref=\"fieldValueEditor\"\n id=\"fieldValue\"\n rows=\"3\"\n placeholder=\"Enter initial value (optional)...\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n ></textarea>\n\n <p v-if=\"fieldErrors.value\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.value}}</p>\n <p class=\"mt-1 text-xs text-gray-500\">\n <span v-if=\"shouldUseDatePicker\">Select a date or leave empty for null/undefined values.</span>\n <span v-else-if=\"shouldUseCodeMirror\">Leave empty for null/undefined values. Use valid JSON format.</span>\n <span v-else>Leave empty for null/undefined values.</span>\n </p>\n </div>\n\n\n <!-- Action Buttons -->\n <div class=\"flex justify-end gap-3 pt-4 border-t border-gray-200\">\n <button\n type=\"button\"\n @click=\"closeAddFieldModal\"\n class=\"px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n :disabled=\"isSubmittingField\"\n class=\"px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <span v-if=\"isSubmittingField\">Adding...</span>\n <span v-else>Add Field</span>\n </button>\n </div>\n </form>\n </div>\n </template>\n </modal>\n</div>\n";
5447
+ module.exports = "<div class=\"document-details\">\n <!-- View Toggle and Search/Filter Bar -->\n <div class=\"mb-4 mt-4\">\n\n <!-- Search and Filter Bar (only show in fields view) -->\n <div v-if=\"viewMode === 'fields'\" class=\"flex md:gap-3\">\n <!-- Search Bar -->\n <div class=\"relative flex-1\">\n <input\n ref=\"searchInput\"\n v-model=\"searchQuery\"\n type=\"text\"\n placeholder=\"Search fields...\"\n class=\"w-full px-4 py-2 pl-10 pr-4 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n <div class=\"absolute inset-y-0 left-0 flex items-center pl-3\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"></path>\n </svg>\n </div>\n </div>\n\n <!-- Data Type Filter -->\n <div class=\"relative hidden md:block\">\n <select\n v-model=\"selectedType\"\n class=\"px-4 py-2 pr-8 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white min-w-[140px] appearance-none\"\n >\n <option value=\"\">All Types</option>\n <option v-for=\"type in availableTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <div class=\"absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none\">\n <svg class=\"w-4 h-4 text-gray-400\" 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 </div>\n </div>\n\n <!-- Add Field Button -->\n <button\n @click=\"openAddFieldModal\"\n class=\"hidden md:flex px-4 py-2 text-sm font-medium text-white bg-green-600 hover:bg-green-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 items-center gap-2\"\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=\"M12 4v16m8-8H4\"></path>\n </svg> Add Field\n </button>\n </div>\n </div>\n\n <!-- Fields View -->\n <div v-if=\"viewMode === 'fields'\">\n <!-- Schema Paths -->\n <div\n v-for=\"path in filteredSchemaPaths\"\n :key=\"path.path || path\"\n class=\"value\"\n >\n <document-property\n :path=\"path\"\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :highlight=\"isSchemaPathMatched(path)\"\n :invalid=\"invalid\"></document-property>\n </div>\n\n <!-- Virtual Fields -->\n <div\n v-for=\"path in filteredVirtuals\"\n :key=\"path.name\"\n class=\"border rounded-lg mb-2 transition-all duration-200 ease-in-out\"\n :class=\"[\n isVirtualMatched(path)\n ? 'border-amber-400 ring-2 ring-amber-200 ring-offset-1'\n : 'border-gray-200'\n ]\"\n >\n <!-- Virtual Field Header (Always Visible) -->\n <div\n @click=\"toggleVirtualField(path.name)\"\n class=\"p-3 bg-slate-50 hover:bg-slate-100 cursor-pointer flex items-center justify-between border-b border-gray-200\"\n >\n <div class=\"flex items-center\">\n <svg\n :class=\"isVirtualFieldCollapsed(path.name) ? '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.name}}</span>\n <span v-if=\"path.isVirtual\" class=\"ml-2 text-sm text-purple-600\">(virtual - {{getVirtualFieldType(path)}})</span>\n <span v-else class=\"ml-2 text-sm text-blue-600\">(user-added - {{getVirtualFieldType(path)}})</span>\n </div>\n </div>\n\n <!-- Virtual Field Content (Collapsible) -->\n <div v-if=\"!isVirtualFieldCollapsed(path.name)\" class=\"p-3\">\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n\n <!-- No Results Message -->\n <div v-if=\"searchQuery.trim() && !hasSearchMatches\" class=\"text-center py-8 text-gray-500\">\n <svg class=\"w-12 h-12 mx-auto mb-4 text-gray-300\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.29-1.009-5.824-2.709M15 6.291A7.962 7.962 0 0012 5c-2.34 0-4.29 1.009-5.824 2.709\"></path>\n </svg>\n <p>No fields found matching \"{{searchQuery}}\"</p>\n </div>\n </div>\n\n <!-- JSON View -->\n <div v-if=\"viewMode === 'json'\" class=\"json-view\">\n <div class=\"border border-gray-300 rounded-lg bg-gray-50 p-4 overflow-auto\">\n <pre class=\"text-sm font-mono text-gray-800 whitespace-pre\">{{formattedJson}}</pre>\n </div>\n </div>\n\n <!-- Add Field Modal -->\n <modal v-if=\"showAddFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"closeAddFieldModal\">&times;</div>\n <div class=\"add-field-modal\">\n <div class=\"mb-6\">\n <h2 class=\"text-xl font-semibold text-gray-900 mb-2\">Add New Field</h2>\n <p class=\"text-sm text-gray-600\">Create a new field for this document</p>\n </div>\n\n <form @submit.prevent=\"handleAddFieldSubmit\" class=\"space-y-4\">\n <!-- Field Name -->\n <div>\n <label for=\"fieldName\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Field Name *\n </label>\n <input\n id=\"fieldName\"\n v-model=\"fieldData.name\"\n type=\"text\"\n required\n placeholder=\"Enter field name...\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.name }\"\n />\n <p v-if=\"fieldErrors.name\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.name}}</p>\n <p v-if=\"fieldData.name && getTransformedFieldName() !== fieldData.name.trim()\" class=\"mt-1 text-sm text-blue-600\">\n Field name will be: <code class=\"bg-blue-50 px-1 py-0.5 rounded text-xs\">{{getTransformedFieldName()}}</code>\n </p>\n </div>\n\n <!-- Field Type -->\n <div>\n <label for=\"fieldType\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Field Type *\n </label>\n <select\n id=\"fieldType\"\n v-model=\"fieldData.type\"\n required\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.type }\"\n >\n <option value=\"\">Select field type...</option>\n <option v-for=\"type in allFieldTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <p v-if=\"fieldErrors.type\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.type}}</p>\n </div>\n\n <!-- Field Value -->\n <div>\n <label for=\"fieldValue\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Initial Value\n </label>\n\n <!-- Date picker for Date type -->\n <input\n v-if=\"shouldUseDatePicker\"\n v-model=\"fieldData.value\"\n type=\"date\"\n id=\"fieldValue\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- Simple input for basic types -->\n <input\n v-else-if=\"!shouldUseCodeMirror\"\n v-model=\"fieldData.value\"\n type=\"text\"\n id=\"fieldValue\"\n placeholder=\"Enter initial value (optional)...\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- CodeMirror textarea for complex types -->\n <textarea\n v-else\n ref=\"fieldValueEditor\"\n id=\"fieldValue\"\n rows=\"3\"\n placeholder=\"Enter initial value (optional)...\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n ></textarea>\n\n <p v-if=\"fieldErrors.value\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.value}}</p>\n <p class=\"mt-1 text-xs text-gray-500\">\n <span v-if=\"shouldUseDatePicker\">Select a date or leave empty for null/undefined values.</span>\n <span v-else-if=\"shouldUseCodeMirror\">Leave empty for null/undefined values. Use valid JSON format.</span>\n <span v-else>Leave empty for null/undefined values.</span>\n </p>\n </div>\n\n\n <!-- Action Buttons -->\n <div class=\"flex justify-end gap-3 pt-4 border-t border-gray-200\">\n <button\n type=\"button\"\n @click=\"closeAddFieldModal\"\n class=\"px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n :disabled=\"isSubmittingField\"\n class=\"px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <span v-if=\"isSubmittingField\">Adding...</span>\n <span v-else>Add Field</span>\n </button>\n </div>\n </form>\n </div>\n </template>\n </modal>\n</div>\n";
5380
5448
 
5381
5449
  /***/ }),
5382
5450
 
@@ -5398,7 +5466,7 @@ module.exports = ".document-details {\n width: 100%;\n }\n \n .document-de
5398
5466
  /***/ ((module) => {
5399
5467
 
5400
5468
  "use strict";
5401
- module.exports = "<div class=\"border border-gray-200 rounded-lg mb-2\">\n <!-- Collapsible Header -->\n <div \n @click=\"toggleCollapse\"\n class=\"p-3 bg-gray-50 hover:bg-gray-100 cursor-pointer flex items-center justify-between border-b border-gray-200\"\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 <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-3\">\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 @input=\"changes[path.path] = $event; delete invalid[path.path];\"\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 <div v-if=\"needsTruncation && !isValueExpanded\" class=\"truncated-value-container\">\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\"\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 <div v-else-if=\"needsTruncation && isValueExpanded\" class=\"expanded-value-container\">\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\"\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 <div v-else>\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n </div>\n </div>\n </div>\n</div>\n";
5469
+ module.exports = "<div class=\"border border-gray-200 rounded-lg mb-2\">\n <!-- Collapsible Header -->\n <div\n @click=\"toggleCollapse\"\n class=\"p-3 hover:bg-gray-100 cursor-pointer flex items-center justify-between border-b border-gray-200 transition-colors duration-200 ease-in-out\"\n :class=\"{ 'bg-amber-100': highlight, 'bg-gray-50': !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 <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-3\">\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 @input=\"changes[path.path] = $event; delete invalid[path.path];\"\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 <div v-if=\"needsTruncation && !isValueExpanded\" class=\"truncated-value-container\">\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\"\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 <div v-else-if=\"needsTruncation && isValueExpanded\" class=\"expanded-value-container\">\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\"\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 <div v-else>\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n </div>\n </div>\n </div>\n</div>\n";
5402
5470
 
5403
5471
  /***/ }),
5404
5472
 
@@ -5420,7 +5488,7 @@ module.exports = "<div>\n <h2>\n Are you sure you want to save the fol
5420
5488
  /***/ ((module) => {
5421
5489
 
5422
5490
  "use strict";
5423
- module.exports = "<div>\n <h2>\n Are you sure you want to delete the following document?\n </h2>\n <pre class=\"max-h-[50vh] overflow-auto\"><code ref=\"code\" class=\"language-javascript\" v-text=\"displayValue\"></code></pre>\n <div class=\"flex gap-2 mt-2\">\n <async-button\n class=\"rounded-md bg-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 @click=\"startDelete\">\n Confirm\n </async-button>\n <button \n class=\"rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-semibold text-black shadow-sm hover:bg-gray-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-300\"\n @click=\"closeDelete\">\n Cancel\n </button>\n </div>\n</div>";
5491
+ module.exports = "<div>\n <h2>\n Are you sure you want to delete the following document?\n </h2>\n <pre class=\"max-h-[50vh] overflow-auto\"><code ref=\"code\" class=\"language-javascript\" v-text=\"displayValue\"></code></pre>\n <div class=\"flex gap-2 mt-2\">\n <async-button\n class=\"rounded-md bg-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 @click=\"startDelete\">\n Confirm\n </async-button>\n <button\n class=\"rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-semibold text-black shadow-sm hover:bg-gray-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-300\"\n @click=\"closeDelete\">\n Cancel\n </button>\n </div>\n</div>\n";
5424
5492
 
5425
5493
  /***/ }),
5426
5494
 
@@ -5684,7 +5752,7 @@ module.exports = "<div class=\"list-subdocument tooltip\">\n <pre>\n <code r
5684
5752
  /***/ ((module) => {
5685
5753
 
5686
5754
  "use strict";
5687
- module.exports = "/** Vue modal */\n\n.modal-mask {\n position: fixed;\n z-index: 9998;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.5);\n display: table;\n transition: opacity 0.3s ease;\n}\n\n.modal-wrapper {\n display: table-cell;\n vertical-align: middle;\n}\n\n.modal-container {\n width: 600px;\n margin: 0px auto;\n padding: 20px 30px;\n padding-bottom: 40px;\n background-color: #fff;\n border-radius: 2px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);\n transition: all 0.3s ease;\n font-family: Helvetica, Arial, sans-serif;\n position: relative;\n}\n\n.modal-header {\n margin-top: 0;\n font-size: 18px;\n font-weight: bold;\n}\n\n.modal-header-success {\n color: #42b983;\n}\n\n.modal-header-error {\n color: #ff0000;\n}\n\n.modal-body {\n margin: 20px 0;\n max-height: calc(100vh - 40px - 60px - 10px);\n overflow: auto;\n}\n\n.modal__button--default {\n float: right;\n}\n\n/*\n * The following styles are auto-applied to elements with\n * transition=\"modal\" when their visibility is toggled\n * by Vue.js.\n *\n * You can easily play with the modal transition by editing\n * these styles.\n */\n\n.modal-enter {\n opacity: 0;\n}\n\n.modal-leave-active {\n opacity: 0;\n}\n\n.modal-enter .modal-container,\n.modal-leave-active .modal-container {\n -webkit-transform: scale(1.1);\n transform: scale(1.1);\n}\n\n.modal-container .modal-exit {\n position: absolute;\n right: 0.25em;\n top: 0.25em;\n cursor: pointer;\n font-size: 1.25em;\n height: 1.25em;\n width: 1.25em;\n border-radius: 100%;\n border: 1px solid #ddd;\n display: flex;\n align-items: center;\n justify-content: center;\n padding-bottom: 0.25em;\n}\n\n.modal-container .modal-exit:hover {\n background-color: #f1f5ff;\n}\n";
5755
+ module.exports = "/** Vue modal */\n\n.modal-mask {\n position: fixed;\n z-index: 9998;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.5);\n display: table;\n transition: opacity 0.3s ease;\n}\n\n.modal-wrapper {\n display: table-cell;\n vertical-align: middle;\n}\n\n.modal-container {\n width: 600px;\n margin: 0px auto;\n padding: 20px 30px;\n padding-bottom: 40px;\n background-color: #fff;\n border-radius: 2px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);\n transition: all 0.3s ease;\n font-family: Helvetica, Arial, sans-serif;\n position: relative;\n}\n\n.modal-header {\n margin-top: 0;\n font-size: 18px;\n font-weight: bold;\n}\n\n.modal-header-success {\n color: #42b983;\n}\n\n.modal-header-error {\n color: #ff0000;\n}\n\n.modal-body {\n margin: 20px 0;\n max-height: calc(100vh - 40px - 60px - 10px);\n overflow: auto;\n}\n\n.modal__button--default {\n float: right;\n}\n\n/*\n * The following styles are auto-applied to elements with\n * transition=\"modal\" when their visibility is toggled\n * by Vue.js.\n *\n * You can easily play with the modal transition by editing\n * these styles.\n */\n\n.modal-enter {\n opacity: 0;\n}\n\n.modal-leave-active {\n opacity: 0;\n}\n\n.modal-enter .modal-container,\n.modal-leave-active .modal-container {\n -webkit-transform: scale(1.1);\n transform: scale(1.1);\n}\n\n.modal-container .modal-exit {\n position: absolute;\n right: 0.25em;\n top: 0.25em;\n cursor: pointer;\n font-size: 1.25em;\n height: 1.25em;\n width: 1.25em;\n border-radius: 100%;\n border: 1px solid #ddd;\n display: flex;\n align-items: center;\n justify-content: center;\n padding-bottom: 0.25em;\n}\n\n.modal-container .modal-exit:hover {\n background-color: #f1f5ff;\n}\n\n@media (max-width: 767px) {\n .modal-container {\n width: calc(100vw - 10px);\n margin: 0;\n margin-left: 5px;\n }\n}\n";
5688
5756
 
5689
5757
  /***/ }),
5690
5758
 
@@ -15835,7 +15903,7 @@ var bson = /*#__PURE__*/Object.freeze({
15835
15903
  /***/ ((module) => {
15836
15904
 
15837
15905
  "use strict";
15838
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.130","description":"A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.","homepage":"https://studio.mongoosejs.io/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"dependencies":{"archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.1.0","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vanillatoasts":"^1.6.0","vue":"3.x","webpack":"5.x"},"peerDependencies":{"bson":"^5.5.1 || 6.x","express":"4.x","mongoose":"7.x || 8.x"},"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":"8.x"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
15906
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.131","description":"A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.","homepage":"https://studio.mongoosejs.io/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"dependencies":{"archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.1.0","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vanillatoasts":"^1.6.0","vue":"3.x","webpack":"5.x"},"peerDependencies":{"bson":"^5.5.1 || 6.x","express":"4.x","mongoose":"7.x || 8.x"},"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":"8.x"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
15839
15907
 
15840
15908
  /***/ })
15841
15909
 
@@ -1399,6 +1399,11 @@ video {
1399
1399
  border-style: none;
1400
1400
  }
1401
1401
 
1402
+ .border-amber-400 {
1403
+ --tw-border-opacity: 1;
1404
+ border-color: rgb(251 191 36 / var(--tw-border-opacity));
1405
+ }
1406
+
1402
1407
  .border-blue-300 {
1403
1408
  --tw-border-opacity: 1;
1404
1409
  border-color: rgb(147 197 253 / var(--tw-border-opacity));
@@ -1433,6 +1438,11 @@ video {
1433
1438
  border-color: rgb(63 83 255 / var(--tw-border-opacity));
1434
1439
  }
1435
1440
 
1441
+ .bg-amber-100 {
1442
+ --tw-bg-opacity: 1;
1443
+ background-color: rgb(254 243 199 / var(--tw-bg-opacity));
1444
+ }
1445
+
1436
1446
  .bg-black {
1437
1447
  --tw-bg-opacity: 1;
1438
1448
  background-color: rgb(0 0 0 / var(--tw-bg-opacity));
@@ -2044,6 +2054,11 @@ video {
2044
2054
  --tw-ring-inset: inset;
2045
2055
  }
2046
2056
 
2057
+ .ring-amber-200 {
2058
+ --tw-ring-opacity: 1;
2059
+ --tw-ring-color: rgb(253 230 138 / var(--tw-ring-opacity));
2060
+ }
2061
+
2047
2062
  .ring-black {
2048
2063
  --tw-ring-opacity: 1;
2049
2064
  --tw-ring-color: rgb(0 0 0 / var(--tw-ring-opacity));
@@ -2074,6 +2089,10 @@ video {
2074
2089
  --tw-ring-opacity: 0.05;
2075
2090
  }
2076
2091
 
2092
+ .ring-offset-1 {
2093
+ --tw-ring-offset-width: 1px;
2094
+ }
2095
+
2077
2096
  .filter {
2078
2097
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
2079
2098
  }
@@ -1,18 +1,18 @@
1
1
  <div>
2
- <h2>
3
- Are you sure you want to delete the following document?
4
- </h2>
5
- <pre class="max-h-[50vh] overflow-auto"><code ref="code" class="language-javascript" v-text="displayValue"></code></pre>
6
- <div class="flex gap-2 mt-2">
7
- <async-button
8
- class="rounded-md bg-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"
9
- @click="startDelete">
10
- Confirm
11
- </async-button>
12
- <button
13
- class="rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-semibold text-black shadow-sm hover:bg-gray-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-300"
14
- @click="closeDelete">
15
- Cancel
16
- </button>
17
- </div>
18
- </div>
2
+ <h2>
3
+ Are you sure you want to delete the following document?
4
+ </h2>
5
+ <pre class="max-h-[50vh] overflow-auto"><code ref="code" class="language-javascript" v-text="displayValue"></code></pre>
6
+ <div class="flex gap-2 mt-2">
7
+ <async-button
8
+ class="rounded-md bg-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"
9
+ @click="startDelete">
10
+ Confirm
11
+ </async-button>
12
+ <button
13
+ class="rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-semibold text-black shadow-sm hover:bg-gray-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-300"
14
+ @click="closeDelete">
15
+ Cancel
16
+ </button>
17
+ </div>
18
+ </div>
@@ -21,10 +21,10 @@
21
21
  </div>
22
22
 
23
23
  <!-- Data Type Filter -->
24
- <div class="relative">
24
+ <div class="relative hidden md:block">
25
25
  <select
26
26
  v-model="selectedType"
27
- class="hidden md:block px-4 py-2 pr-8 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white min-w-[140px] appearance-none"
27
+ class="px-4 py-2 pr-8 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white min-w-[140px] appearance-none"
28
28
  >
29
29
  <option value="">All Types</option>
30
30
  <option v-for="type in availableTypes" :key="type" :value="type">
@@ -41,12 +41,11 @@
41
41
  <!-- Add Field Button -->
42
42
  <button
43
43
  @click="openAddFieldModal"
44
- class="hidden md:block px-4 py-2 text-sm font-medium text-white bg-green-600 hover:bg-green-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 flex items-center gap-2"
44
+ class="hidden md:flex px-4 py-2 text-sm font-medium text-white bg-green-600 hover:bg-green-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 items-center gap-2"
45
45
  >
46
46
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
47
47
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
48
- </svg>
49
- Add Field
48
+ </svg> Add Field
50
49
  </button>
51
50
  </div>
52
51
  </div>
@@ -54,18 +53,32 @@
54
53
  <!-- Fields View -->
55
54
  <div v-if="viewMode === 'fields'">
56
55
  <!-- Schema Paths -->
57
- <div v-for="path in filteredSchemaPaths" :key="path" class="value">
56
+ <div
57
+ v-for="path in filteredSchemaPaths"
58
+ :key="path.path || path"
59
+ class="value"
60
+ >
58
61
  <document-property
59
62
  :path="path"
60
63
  :document="document"
61
64
  :schemaPaths="schemaPaths"
62
65
  :editting="editting"
63
66
  :changes="changes"
67
+ :highlight="isSchemaPathMatched(path)"
64
68
  :invalid="invalid"></document-property>
65
69
  </div>
66
70
 
67
71
  <!-- Virtual Fields -->
68
- <div v-for="path in filteredVirtuals" class="border border-gray-200 rounded-lg mb-2">
72
+ <div
73
+ v-for="path in filteredVirtuals"
74
+ :key="path.name"
75
+ class="border rounded-lg mb-2 transition-all duration-200 ease-in-out"
76
+ :class="[
77
+ isVirtualMatched(path)
78
+ ? 'border-amber-400 ring-2 ring-amber-200 ring-offset-1'
79
+ : 'border-gray-200'
80
+ ]"
81
+ >
69
82
  <!-- Virtual Field Header (Always Visible) -->
70
83
  <div
71
84
  @click="toggleVirtualField(path.name)"
@@ -99,7 +112,7 @@
99
112
  </div>
100
113
 
101
114
  <!-- No Results Message -->
102
- <div v-if="searchQuery && filteredSchemaPaths.length === 0 && filteredVirtuals.length === 0" class="text-center py-8 text-gray-500">
115
+ <div v-if="searchQuery.trim() && !hasSearchMatches" class="text-center py-8 text-gray-500">
103
116
  <svg class="w-12 h-12 mx-auto mb-4 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
104
117
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.29-1.009-5.824-2.709M15 6.291A7.962 7.962 0 0012 5c-2.34 0-4.29 1.009-5.824 2.709"></path>
105
118
  </svg>
@@ -115,38 +115,39 @@ module.exports = app => app.component('document-details', {
115
115
  shouldUseDatePicker() {
116
116
  return this.fieldData.type === 'Date';
117
117
  },
118
- filteredSchemaPaths() {
118
+ typeFilteredSchemaPaths() {
119
119
  let paths = this.schemaPaths || [];
120
120
 
121
- // Filter by search query
122
- if (this.searchQuery.trim()) {
123
- const query = this.searchQuery.toLowerCase();
124
- paths = paths.filter(path =>
125
- path.path.toLowerCase().includes(query)
126
- );
127
- }
128
-
129
- // Filter by data type
130
121
  if (this.selectedType) {
131
- paths = paths.filter(path =>
132
- path.instance === this.selectedType
133
- );
122
+ paths = paths.filter(path => path.instance === this.selectedType);
134
123
  }
135
124
 
136
125
  return paths;
137
126
  },
138
- filteredVirtuals() {
139
- let virtuals = this.virtuals;
127
+ filteredSchemaPaths() {
128
+ const paths = this.typeFilteredSchemaPaths.slice();
140
129
 
141
- // Filter by search query
142
- if (this.searchQuery.trim()) {
143
- const query = this.searchQuery.toLowerCase();
144
- virtuals = virtuals.filter(virtual =>
145
- virtual.name.toLowerCase().includes(query)
146
- );
130
+ if (!this.searchQuery.trim()) {
131
+ return paths;
147
132
  }
148
133
 
149
- // Filter by data type
134
+ const query = this.searchQuery.toLowerCase();
135
+ const matches = [];
136
+ const nonMatches = [];
137
+
138
+ paths.forEach(path => {
139
+ if (path.path.toLowerCase().includes(query)) {
140
+ matches.push(path);
141
+ } else {
142
+ nonMatches.push(path);
143
+ }
144
+ });
145
+
146
+ return matches.concat(nonMatches);
147
+ },
148
+ typeFilteredVirtuals() {
149
+ let virtuals = this.virtuals;
150
+
150
151
  if (this.selectedType) {
151
152
  virtuals = virtuals.filter(virtual => {
152
153
  const virtualType = this.getVirtualFieldType(virtual);
@@ -156,6 +157,58 @@ module.exports = app => app.component('document-details', {
156
157
 
157
158
  return virtuals;
158
159
  },
160
+ filteredVirtuals() {
161
+ const virtuals = this.typeFilteredVirtuals.slice();
162
+
163
+ if (!this.searchQuery.trim()) {
164
+ return virtuals;
165
+ }
166
+
167
+ const query = this.searchQuery.toLowerCase();
168
+ const matches = [];
169
+ const nonMatches = [];
170
+
171
+ virtuals.forEach(virtual => {
172
+ if (virtual.name.toLowerCase().includes(query)) {
173
+ matches.push(virtual);
174
+ } else {
175
+ nonMatches.push(virtual);
176
+ }
177
+ });
178
+
179
+ return matches.concat(nonMatches);
180
+ },
181
+ schemaSearchMatchSet() {
182
+ if (!this.searchQuery.trim()) {
183
+ return new Set();
184
+ }
185
+
186
+ const query = this.searchQuery.toLowerCase();
187
+ return new Set(
188
+ this.typeFilteredSchemaPaths
189
+ .filter(path => path.path.toLowerCase().includes(query))
190
+ .map(path => path.path)
191
+ );
192
+ },
193
+ virtualSearchMatchSet() {
194
+ if (!this.searchQuery.trim()) {
195
+ return new Set();
196
+ }
197
+
198
+ const query = this.searchQuery.toLowerCase();
199
+ return new Set(
200
+ this.typeFilteredVirtuals
201
+ .filter(virtual => virtual.name.toLowerCase().includes(query))
202
+ .map(virtual => virtual.name)
203
+ );
204
+ },
205
+ hasSearchMatches() {
206
+ if (!this.searchQuery.trim()) {
207
+ return true;
208
+ }
209
+
210
+ return this.schemaSearchMatchSet.size > 0 || this.virtualSearchMatchSet.size > 0;
211
+ },
159
212
  formattedJson() {
160
213
  if (!this.document) {
161
214
  return '{}';
@@ -171,6 +224,20 @@ module.exports = app => app.component('document-details', {
171
224
  this.collapsedVirtuals.add(fieldName);
172
225
  }
173
226
  },
227
+ isSchemaPathMatched(path) {
228
+ if (!path) {
229
+ return false;
230
+ }
231
+
232
+ return this.schemaSearchMatchSet.has(path.path);
233
+ },
234
+ isVirtualMatched(virtual) {
235
+ if (!virtual) {
236
+ return false;
237
+ }
238
+
239
+ return this.virtualSearchMatchSet.has(virtual.name);
240
+ },
174
241
  isVirtualFieldCollapsed(fieldName) {
175
242
  return this.collapsedVirtuals.has(fieldName);
176
243
  },
@@ -1,15 +1,16 @@
1
1
  <div class="border border-gray-200 rounded-lg mb-2">
2
2
  <!-- Collapsible Header -->
3
- <div
3
+ <div
4
4
  @click="toggleCollapse"
5
- class="p-3 bg-gray-50 hover:bg-gray-100 cursor-pointer flex items-center justify-between border-b border-gray-200"
5
+ class="p-3 hover:bg-gray-100 cursor-pointer flex items-center justify-between border-b border-gray-200 transition-colors duration-200 ease-in-out"
6
+ :class="{ 'bg-amber-100': highlight, 'bg-gray-50': !highlight }"
6
7
  >
7
- <div class="flex items-center">
8
- <svg
8
+ <div class="flex items-center" >
9
+ <svg
9
10
  :class="isCollapsed ? 'rotate-0' : 'rotate-90'"
10
11
  class="w-4 h-4 text-gray-500 mr-2 transition-transform duration-200"
11
- fill="none"
12
- stroke="currentColor"
12
+ fill="none"
13
+ stroke="currentColor"
13
14
  viewBox="0 0 24 24"
14
15
  >
15
16
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
@@ -27,7 +28,7 @@
27
28
  </router-link>
28
29
  </div>
29
30
  </div>
30
-
31
+
31
32
  <!-- Collapsible Content -->
32
33
  <div v-if="!isCollapsed" class="p-3">
33
34
  <!-- Date Type Selector (when editing dates) -->
@@ -53,7 +54,7 @@
53
54
  </div>
54
55
  </div>
55
56
  </div>
56
-
57
+
57
58
  <!-- Field Content -->
58
59
  <div v-if="editting && path.path !== '_id'">
59
60
  <component
@@ -69,7 +70,7 @@
69
70
  <!-- Show truncated or full value based on needsTruncation and isValueExpanded -->
70
71
  <div v-if="needsTruncation && !isValueExpanded" class="truncated-value-container">
71
72
  <div class="text-gray-700 whitespace-pre-wrap break-words font-mono text-sm">{{truncatedString}}</div>
72
- <button
73
+ <button
73
74
  @click="toggleValueExpansion"
74
75
  class="mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1"
75
76
  >
@@ -81,7 +82,7 @@
81
82
  </div>
82
83
  <div v-else-if="needsTruncation && isValueExpanded" class="expanded-value-container">
83
84
  <component :is="getComponentForPath(path)" :value="getValueForPath(path.path)"></component>
84
- <button
85
+ <button
85
86
  @click="toggleValueExpansion"
86
87
  class="mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1"
87
88
  >
@@ -16,7 +16,7 @@ module.exports = app => app.component('document-property', {
16
16
  isValueExpanded: false // Track if the value is expanded
17
17
  };
18
18
  },
19
- props: ['path', 'document', 'schemaPaths', 'editting', 'changes', 'invalid'],
19
+ props: ['path', 'document', 'schemaPaths', 'editting', 'changes', 'invalid', 'highlight'],
20
20
  computed: {
21
21
  valueAsString() {
22
22
  const value = this.getValueForPath(this.path.path);
@@ -94,4 +94,4 @@ module.exports = app => app.component('document-property', {
94
94
  this.isValueExpanded = !this.isValueExpanded;
95
95
  }
96
96
  }
97
- });
97
+ });
@@ -96,3 +96,11 @@
96
96
  .modal-container .modal-exit:hover {
97
97
  background-color: #f1f5ff;
98
98
  }
99
+
100
+ @media (max-width: 767px) {
101
+ .modal-container {
102
+ width: calc(100vw - 10px);
103
+ margin: 0;
104
+ margin-left: 5px;
105
+ }
106
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.0.130",
3
+ "version": "0.0.131",
4
4
  "description": "A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.",
5
5
  "homepage": "https://studio.mongoosejs.io/",
6
6
  "repository": {