@mongoosejs/studio 0.1.6 → 0.1.7
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/getDocuments.js +17 -4
- package/backend/actions/Model/getDocumentsStream.js +17 -4
- package/frontend/public/app.js +300 -197
- package/frontend/src/models/document-search/document-search.html +23 -0
- package/frontend/src/models/document-search/document-search.js +227 -0
- package/frontend/src/models/models.html +8 -7
- package/frontend/src/models/models.js +7 -182
- package/frontend/src/models/trie.js +44 -18
- package/package.json +1 -1
package/frontend/public/app.js
CHANGED
|
@@ -148,6 +148,9 @@ var map = {
|
|
|
148
148
|
"./modal/modal.css": "./frontend/src/modal/modal.css",
|
|
149
149
|
"./modal/modal.html": "./frontend/src/modal/modal.html",
|
|
150
150
|
"./modal/modal.js": "./frontend/src/modal/modal.js",
|
|
151
|
+
"./models/document-search/document-search": "./frontend/src/models/document-search/document-search.js",
|
|
152
|
+
"./models/document-search/document-search.html": "./frontend/src/models/document-search/document-search.html",
|
|
153
|
+
"./models/document-search/document-search.js": "./frontend/src/models/document-search/document-search.js",
|
|
151
154
|
"./models/models": "./frontend/src/models/models.js",
|
|
152
155
|
"./models/models.css": "./frontend/src/models/models.css",
|
|
153
156
|
"./models/models.html": "./frontend/src/models/models.html",
|
|
@@ -4524,43 +4527,28 @@ module.exports = app => app.component('modal', {
|
|
|
4524
4527
|
|
|
4525
4528
|
/***/ }),
|
|
4526
4529
|
|
|
4527
|
-
/***/ "./frontend/src/models/
|
|
4528
|
-
|
|
4529
|
-
!*** ./frontend/src/models/
|
|
4530
|
-
|
|
4531
|
-
/***/ ((module) => {
|
|
4532
|
-
|
|
4533
|
-
"use strict";
|
|
4534
|
-
module.exports = ".models {\n position: relative;\n display: flex;\n flex-direction: row;\n min-height: calc(100% - 56px);\n}\n\n.models button.gray {\n color: black;\n background-color: #eee;\n}\n\n.models .model-selector {\n background-color: #eee;\n flex-grow: 0;\n padding: 15px;\n padding-top: 0px;\n}\n\n.models h1 {\n margin-top: 0px;\n}\n\n.models .documents {\n flex-grow: 1;\n overflow: scroll;\n max-height: calc(100vh - 56px);\n}\n\n.models .documents table {\n /* max-width: -moz-fit-content;\n max-width: fit-content; */\n width: 100%;\n table-layout: auto;\n font-size: small;\n padding: 0;\n margin-right: 1em;\n white-space: nowrap;\n z-index: -1;\n border-collapse: collapse;\n line-height: 1.5em;\n}\n\n.models .documents table th {\n position: sticky;\n top: 42px;\n background-color: white;\n z-index: 1;\n}\n\n.models .documents table th:after {\n content: \"\";\n position: absolute;\n left: 0;\n width: 100%;\n bottom: -1px;\n border-bottom: thin solid rgba(0, 0, 0, 0.12);\n}\n\n.models .documents table tr {\n color: black;\n border-spacing: 0px 0px;\n background-color: white;\n cursor: pointer;\n}\n\n.models .documents table tr:nth-child(even) {\n background-color: #f5f5f5;\n}\n\n.models .documents table tr:hover {\n background-color: #a7b9ff;\n}\n\n.models .documents table th,\ntd {\n border-bottom: thin solid rgba(0, 0, 0, 0.12);\n text-align: left;\n padding: 0 16px;\n height: 48px;\n}\n\n.models textarea {\n font-size: 1.2em;\n}\n\n.models .path-type {\n color: rgba(0, 0, 0, 0.36);\n font-size: 0.8em;\n}\n\n.models .documents-menu {\n position: fixed;\n background-color: white;\n z-index: 1;\n padding: 4px;\n display: flex;\n width: 100vw;\n}\n\n@media (min-width: 1024px) {\n .models .documents-menu {\n width: calc(100vw - 12rem);\n }\n}\n\n.models .documents-menu .search-input {\n flex-grow: 1;\n align-items: center;\n}\n\n.models .search-input input {\n padding: 0.25em 0.5em;\n font-size: 1.1em;\n border: 1px solid #ddd;\n border-radius: 3px;\n width: calc(100% - 1em);\n}\n\n.models .sort-arrow {\n padding-left: 10px;\n padding-right: 10px;\n}\n\n.models .loader {\n width: 100%;\n text-align: center;\n}\n\n.models .loader img {\n height: 4em;\n}\n\n.models .documents .buttons {\n display: inline-flex;\n justify-content: space-around;\n align-items: center;\n}\n\n";
|
|
4535
|
-
|
|
4536
|
-
/***/ }),
|
|
4537
|
-
|
|
4538
|
-
/***/ "./frontend/src/models/models.html":
|
|
4539
|
-
/*!*****************************************!*\
|
|
4540
|
-
!*** ./frontend/src/models/models.html ***!
|
|
4541
|
-
\*****************************************/
|
|
4530
|
+
/***/ "./frontend/src/models/document-search/document-search.html":
|
|
4531
|
+
/*!******************************************************************!*\
|
|
4532
|
+
!*** ./frontend/src/models/document-search/document-search.html ***!
|
|
4533
|
+
\******************************************************************/
|
|
4542
4534
|
/***/ ((module) => {
|
|
4543
4535
|
|
|
4544
4536
|
"use strict";
|
|
4545
|
-
module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div class=\"fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10\" @click=\"hideSidebar = false\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"#5f6368\"><path d=\"M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z\"/></svg>\n </div>\n <aside class=\"bg-white border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-48 fixed lg:relative shrink-0\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-48' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-48 overflow-x-hidden\">\n <div class=\"p-4 font-bold text-lg\">Models</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"currentColor\"><path d=\"M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z\"/></svg>\n </button>\n </div>\n <nav class=\"flex flex-1 flex-col\">\n <ul role=\"list\" class=\"flex flex-1 flex-col gap-y-7\">\n <li>\n <ul role=\"list\">\n <li v-for=\"model in models\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"block truncate rounded-md py-2 pr-2 pl-2 text-sm font-semibold text-gray-700\"\n :class=\"model === currentModel ? 'bg-ultramarine-100 font-bold' : 'hover:bg-ultramarine-100'\">\n {{model}}\n </router-link>\n </li>\n </ul>\n </li>\n </ul>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100\">\n No models found\n </div>\n </nav>\n </aside>\n <div class=\"documents\" ref=\"documentsList\">\n <div class=\"relative h-[42px] z-10\">\n <div class=\"documents-menu\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <form @submit.prevent=\"search\" class=\"relative flex-grow m-0\">\n <input ref=\"searchInput\" class=\"w-full font-mono rounded-md p-1 border border-gray-300 outline-gray-300 text-lg focus:ring-1 focus:ring-ultramarine-200 focus:ring-offset-0 focus:outline-none\" type=\"text\" placeholder=\"Filter\" v-model=\"searchText\" @click=\"initFilter\" @input=\"updateAutocomplete\" @keydown=\"handleKeyDown\" />\n <ul v-if=\"autocompleteSuggestions.length\" class=\"absolute z-[9999] bg-white border border-gray-300 rounded mt-1 w-full max-h-40 overflow-y-auto shadow\">\n <li v-for=\"(suggestion, index) in autocompleteSuggestions\" :key=\"suggestion\" class=\"px-2 py-1 cursor-pointer\" :class=\"{ 'bg-ultramarine-100': index === autocompleteIndex }\" @mousedown.prevent=\"applySuggestion(index)\">{{ suggestion }}</li>\n </ul>\n </form>\n <div>\n <span v-if=\"numDocuments == null\">Loading ...</span>\n <span v-else-if=\"typeof numDocuments === 'number'\">{{numDocuments === 1 ? numDocuments+ ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"shouldShowExportModal = true\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 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-ultramarine-600\">\n Export\n </button>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{ 'bg-gray-500 ring-inset ring-2 ring-gray-300 hover:bg-gray-600': selectMultiple, 'bg-ultramarine-600 hover:bg-ultramarine-500' : !selectMultiple }\"\n class=\"rounded px-2 py-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n >\n {{ selectMultiple ? 'Cancel' : 'Select' }}\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 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 >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 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-ultramarine-600\">\n Indexes\n </button>\n <button\n @click=\"shouldShowCreateModal = true;\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 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-ultramarine-600\">\n Create\n </button>\n <button\n @click=\"openFieldSelection\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 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-ultramarine-600\">\n Fields\n </button>\n <span class=\"isolate inline-flex rounded-md shadow-sm\">\n <button\n @click=\"setOutputType('table')\"\n type=\"button\"\n class=\"relative inline-flex items-center rounded-none rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"setOutputType('json')\"\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n </span>\n </div>\n </div>\n </div>\n <div class=\"documents-container relative\">\n <div v-if=\"error\">\n <div class=\"bg-red-100 border border-red-400 text-red-700 px-4 py-3 relative m-4 rounded-md\" role=\"alert\">\n <span class=\"block font-bold\">Error</span>\n <span class=\"block\">{{ error }}</span>\n </div>\n </div>\n <table v-else-if=\"outputType === 'table'\">\n <thead>\n <th v-for=\"path in filteredPaths\" @click=\"clickFilter(path.path)\" class=\"cursor-pointer\">\n {{path.path}}\n <span class=\"path-type\">\n ({{(path.instance || 'unknown')}})\n </span>\n <span class=\"sort-arrow\" @click=\"sortDocs(1, path.path)\">{{sortBy[path.path] == 1 ? 'X' : '↑'}}</span>\n <span class=\"sort-arrow\" @click=\"sortDocs(-1, path.path)\">{{sortBy[path.path] == -1 ? 'X' : '↓'}}</span>\n </th>\n </thead>\n <tbody>\n <tr v-for=\"document in documents\" @click=\"handleDocumentClick(document, $event)\" :key=\"document._id\">\n <td v-for=\"schemaPath in filteredPaths\" :class=\"{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\">\n </component>\n </td>\n </tr>\n </tbody>\n </table>\n <div v-else-if=\"outputType === 'json'\" class=\"flex flex-col space-y-6\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative transition-colors',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:bg-slate-100'\n ]\"\n >\n <button\n type=\"button\"\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-ultramarine-600 px-2 py-1 text-xs font-semibold text-white shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n @click.stop=\"openDocument(document)\"\n >\n Open this Document\n </button>\n <list-json :value=\"filterDocument(document)\" :references=\"referenceMap\">\n </list-json>\n </div>\n </div>\n <div v-if=\"status === 'loading'\" class=\"loader\">\n <img src=\"images/loader.gif\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">×</div>\n <export-query-results\n :schemaPaths=\"schemaPaths\"\n :search-text=\"searchText\"\n :currentModel=\"currentModel\"\n @done=\"shouldShowExportModal = false\">\n </export-query-results>\n </template>\n </modal>\n <modal v-if=\"shouldShowIndexModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowIndexModal = false\">×</div>\n <div class=\"text-xl font-bold mb-2\">Indexes</div>\n <div v-for=\"index in mongoDBIndexes\" class=\"w-full flex items-center\">\n <div class=\"grow shrink text-left flex justify-between items-center\" v-if=\"index.name != '_id_'\">\n <div>\n <div class=\"font-bold\">{{ index.name }}</div>\n <div class=\"text-sm font-mono\">{{ JSON.stringify(index.key) }}</div>\n </div>\n <div>\n <async-button\n type=\"button\"\n @click=\"dropIndex(index.name)\"\n class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 disabled:bg-gray-400 disabled:cursor-not-allowed\">\n Drop\n </async-button>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowFieldModal = false; selectedPaths = [...filteredPaths];\">×</div>\n <div v-for=\"(path, index) in schemaPaths\" :key=\"index\" class=\"w-5 flex items-center\">\n <input class=\"mt-0 h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-600 accent-sky-600\" type=\"checkbox\" :id=\"'path.path'+index\" @change=\"addOrRemove(path)\" :value=\"path.path\" :checked=\"isSelected(path.path)\" />\n <div class=\"ml-2 text-gray-700 grow shrink text-left\">\n <label :for=\"'path.path' + index\">{{path.path}}</label>\n </div>\n </div>\n <div class=\"mt-4 flex gap-2\">\n <button type=\"button\" @click=\"filterDocuments()\" 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\">Filter Selection</button>\n <button type=\"button\" @click=\"selectAll()\" 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\">Select All</button>\n <button type=\"button\" @click=\"deselectAll()\" class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">Deselect All</button>\n <button type=\"button\" @click=\"resetDocuments()\" class=\"rounded-md bg-gray-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\" >Cancel</button>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">×</div>\n <create-document :currentModel=\"currentModel\" :paths=\"schemaPaths\" @close=\"closeCreationModal\"></create-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowUpdateMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowUpdateMultipleModal = false;\">×</div>\n <update-document :currentModel=\"currentModel\" :document=\"selectedDocuments\" :multiple=\"true\" @update=\"updateDocuments\" @close=\"shouldShowUpdateMultipleModal=false;\"></update-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteMultipleModal = false;\">×</div>\n <h2>Are you sure you want to delete {{selectedDocuments.length}} documents?</h2>\n <div>\n <list-json :value=\"selectedDocuments\"></list-json>\n </div>\n <div class=\"flex gap-4\">\n <async-button @click=\"deleteDocuments\" class=\"rounded bg-red-500 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n Confirm\n </async-button>\n <button @click=\"shouldShowDeleteMultipleModal = false;\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
4537
|
+
module.exports = "<form @submit.prevent=\"emitSearch\" class=\"relative flex-grow m-0\">\n <input\n ref=\"searchInput\"\n class=\"w-full font-mono rounded-md p-1 border border-gray-300 outline-gray-300 text-lg focus:ring-1 focus:ring-ultramarine-200 focus:ring-offset-0 focus:outline-none\"\n type=\"text\"\n placeholder=\"Filter\"\n v-model=\"searchText\"\n @click=\"initFilter\"\n @input=\"updateAutocomplete\"\n @keydown=\"handleKeyDown\"\n />\n <ul v-if=\"autocompleteSuggestions.length\" class=\"absolute z-[9999] bg-white border border-gray-300 rounded mt-1 w-full max-h-40 overflow-y-auto shadow\">\n <li\n v-for=\"(suggestion, index) in autocompleteSuggestions\"\n :key=\"suggestion\"\n class=\"px-2 py-1 cursor-pointer\"\n :class=\"{ 'bg-ultramarine-100': index === autocompleteIndex }\"\n @mousedown.prevent=\"applySuggestion(index)\"\n >\n {{ suggestion }}\n </li>\n </ul>\n</form>\n";
|
|
4546
4538
|
|
|
4547
4539
|
/***/ }),
|
|
4548
4540
|
|
|
4549
|
-
/***/ "./frontend/src/models/
|
|
4550
|
-
|
|
4551
|
-
!*** ./frontend/src/models/
|
|
4552
|
-
|
|
4541
|
+
/***/ "./frontend/src/models/document-search/document-search.js":
|
|
4542
|
+
/*!****************************************************************!*\
|
|
4543
|
+
!*** ./frontend/src/models/document-search/document-search.js ***!
|
|
4544
|
+
\****************************************************************/
|
|
4553
4545
|
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
4554
4546
|
|
|
4555
4547
|
"use strict";
|
|
4556
4548
|
|
|
4557
4549
|
|
|
4558
|
-
const
|
|
4559
|
-
const
|
|
4560
|
-
const mpath = __webpack_require__(/*! mpath */ "./node_modules/mpath/index.js");
|
|
4561
|
-
|
|
4562
|
-
const appendCSS = __webpack_require__(/*! ../appendCSS */ "./frontend/src/appendCSS.js");
|
|
4563
|
-
const { Trie } = __webpack_require__(/*! ./trie */ "./frontend/src/models/trie.js");
|
|
4550
|
+
const template = __webpack_require__(/*! ./document-search.html */ "./frontend/src/models/document-search/document-search.html");
|
|
4551
|
+
const { Trie } = __webpack_require__(/*! ../trie */ "./frontend/src/models/trie.js");
|
|
4564
4552
|
|
|
4565
4553
|
const QUERY_SELECTORS = [
|
|
4566
4554
|
'$eq',
|
|
@@ -4589,7 +4577,241 @@ const QUERY_SELECTORS = [
|
|
|
4589
4577
|
'$mod'
|
|
4590
4578
|
];
|
|
4591
4579
|
|
|
4580
|
+
module.exports = app => app.component('document-search', {
|
|
4581
|
+
template,
|
|
4582
|
+
props: {
|
|
4583
|
+
value: {
|
|
4584
|
+
type: String,
|
|
4585
|
+
default: ''
|
|
4586
|
+
},
|
|
4587
|
+
schemaPaths: {
|
|
4588
|
+
type: Array,
|
|
4589
|
+
default: () => []
|
|
4590
|
+
}
|
|
4591
|
+
},
|
|
4592
|
+
data() {
|
|
4593
|
+
return {
|
|
4594
|
+
autocompleteSuggestions: [],
|
|
4595
|
+
autocompleteIndex: 0,
|
|
4596
|
+
autocompleteTrie: null,
|
|
4597
|
+
searchText: this.value || ''
|
|
4598
|
+
};
|
|
4599
|
+
},
|
|
4600
|
+
watch: {
|
|
4601
|
+
value(val) {
|
|
4602
|
+
this.searchText = val || '';
|
|
4603
|
+
},
|
|
4604
|
+
schemaPaths: {
|
|
4605
|
+
handler() {
|
|
4606
|
+
this.buildAutocompleteTrie();
|
|
4607
|
+
},
|
|
4608
|
+
deep: true
|
|
4609
|
+
}
|
|
4610
|
+
},
|
|
4611
|
+
created() {
|
|
4612
|
+
this.buildAutocompleteTrie();
|
|
4613
|
+
},
|
|
4614
|
+
methods: {
|
|
4615
|
+
emitSearch() {
|
|
4616
|
+
this.$emit('input', this.searchText);
|
|
4617
|
+
this.$emit('search', this.searchText);
|
|
4618
|
+
},
|
|
4619
|
+
buildAutocompleteTrie() {
|
|
4620
|
+
this.autocompleteTrie = new Trie();
|
|
4621
|
+
this.autocompleteTrie.bulkInsert(QUERY_SELECTORS, 5, 'operator');
|
|
4622
|
+
if (Array.isArray(this.schemaPaths) && this.schemaPaths.length > 0) {
|
|
4623
|
+
const paths = this.schemaPaths
|
|
4624
|
+
.map(path => path?.path)
|
|
4625
|
+
.filter(path => typeof path === 'string' && path.length > 0);
|
|
4626
|
+
for (const path of this.schemaPaths) {
|
|
4627
|
+
if (path.schema) {
|
|
4628
|
+
paths.push(...Object.keys(path.schema).map(subpath => `${path.path}.${subpath}`));
|
|
4629
|
+
}
|
|
4630
|
+
}
|
|
4631
|
+
this.autocompleteTrie.bulkInsert(paths, 10, 'fieldName');
|
|
4632
|
+
}
|
|
4633
|
+
},
|
|
4634
|
+
initFilter(ev) {
|
|
4635
|
+
if (!this.searchText) {
|
|
4636
|
+
this.searchText = '{}';
|
|
4637
|
+
this.$nextTick(() => {
|
|
4638
|
+
ev.target.setSelectionRange(1, 1);
|
|
4639
|
+
});
|
|
4640
|
+
}
|
|
4641
|
+
},
|
|
4642
|
+
updateAutocomplete() {
|
|
4643
|
+
const input = this.$refs.searchInput;
|
|
4644
|
+
const cursorPos = input ? input.selectionStart : 0;
|
|
4645
|
+
const before = this.searchText.slice(0, cursorPos);
|
|
4646
|
+
const match = before.match(/(?:\{|,)\s*([^:\s]*)$/);
|
|
4647
|
+
if (match && match[1]) {
|
|
4648
|
+
const token = match[1];
|
|
4649
|
+
const leadingQuoteMatch = token.match(/^["']/);
|
|
4650
|
+
const trailingQuoteMatch = token.length > 1 && /["']$/.test(token)
|
|
4651
|
+
? token[token.length - 1]
|
|
4652
|
+
: '';
|
|
4653
|
+
const term = token
|
|
4654
|
+
.replace(/^["']/, '')
|
|
4655
|
+
.replace(trailingQuoteMatch ? new RegExp(`[${trailingQuoteMatch}]$`) : '', '')
|
|
4656
|
+
.trim();
|
|
4657
|
+
if (!term) {
|
|
4658
|
+
this.autocompleteSuggestions = [];
|
|
4659
|
+
return;
|
|
4660
|
+
}
|
|
4661
|
+
|
|
4662
|
+
const colonMatch = before.match(/:\s*([^,\}\]]*)$/);
|
|
4663
|
+
const role = colonMatch ? 'operator' : 'fieldName';
|
|
4664
|
+
|
|
4665
|
+
if (this.autocompleteTrie) {
|
|
4666
|
+
const primarySuggestions = this.autocompleteTrie.getSuggestions(term, 10, role);
|
|
4667
|
+
const suggestionsSet = new Set(primarySuggestions);
|
|
4668
|
+
if (Array.isArray(this.schemaPaths) && this.schemaPaths.length > 0) {
|
|
4669
|
+
for (const schemaPath of this.schemaPaths) {
|
|
4670
|
+
const path = schemaPath?.path;
|
|
4671
|
+
if (
|
|
4672
|
+
typeof path === 'string' &&
|
|
4673
|
+
path.startsWith(`${term}.`) &&
|
|
4674
|
+
!suggestionsSet.has(path)
|
|
4675
|
+
) {
|
|
4676
|
+
suggestionsSet.add(path);
|
|
4677
|
+
if (suggestionsSet.size >= 10) {
|
|
4678
|
+
break;
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
let suggestions = Array.from(suggestionsSet);
|
|
4684
|
+
if (leadingQuoteMatch) {
|
|
4685
|
+
const leadingQuote = leadingQuoteMatch[0];
|
|
4686
|
+
suggestions = suggestions.map(suggestion => `${leadingQuote}${suggestion}`);
|
|
4687
|
+
}
|
|
4688
|
+
if (trailingQuoteMatch) {
|
|
4689
|
+
suggestions = suggestions.map(suggestion =>
|
|
4690
|
+
suggestion.endsWith(trailingQuoteMatch) ? suggestion : `${suggestion}${trailingQuoteMatch}`
|
|
4691
|
+
);
|
|
4692
|
+
}
|
|
4693
|
+
this.autocompleteSuggestions = suggestions;
|
|
4694
|
+
this.autocompleteIndex = 0;
|
|
4695
|
+
return;
|
|
4696
|
+
}
|
|
4697
|
+
}
|
|
4698
|
+
this.autocompleteSuggestions = [];
|
|
4699
|
+
},
|
|
4700
|
+
handleKeyDown(ev) {
|
|
4701
|
+
if (this.autocompleteSuggestions.length === 0) {
|
|
4702
|
+
return;
|
|
4703
|
+
}
|
|
4704
|
+
if (ev.key === 'Tab' || ev.key === 'Enter') {
|
|
4705
|
+
ev.preventDefault();
|
|
4706
|
+
this.applySuggestion(this.autocompleteIndex);
|
|
4707
|
+
} else if (ev.key === 'ArrowDown') {
|
|
4708
|
+
ev.preventDefault();
|
|
4709
|
+
this.autocompleteIndex = (this.autocompleteIndex + 1) % this.autocompleteSuggestions.length;
|
|
4710
|
+
} else if (ev.key === 'ArrowUp') {
|
|
4711
|
+
ev.preventDefault();
|
|
4712
|
+
this.autocompleteIndex = (this.autocompleteIndex + this.autocompleteSuggestions.length - 1) % this.autocompleteSuggestions.length;
|
|
4713
|
+
}
|
|
4714
|
+
},
|
|
4715
|
+
applySuggestion(index) {
|
|
4716
|
+
const suggestion = this.autocompleteSuggestions[index];
|
|
4717
|
+
if (!suggestion) {
|
|
4718
|
+
return;
|
|
4719
|
+
}
|
|
4720
|
+
const input = this.$refs.searchInput;
|
|
4721
|
+
const cursorPos = input.selectionStart;
|
|
4722
|
+
const before = this.searchText.slice(0, cursorPos);
|
|
4723
|
+
const after = this.searchText.slice(cursorPos);
|
|
4724
|
+
const match = before.match(/(?:\{|,)\s*([^:\s]*)$/);
|
|
4725
|
+
const colonNeeded = !/^\s*:/.test(after);
|
|
4726
|
+
if (!match) {
|
|
4727
|
+
return;
|
|
4728
|
+
}
|
|
4729
|
+
const token = match[1];
|
|
4730
|
+
const start = cursorPos - token.length;
|
|
4731
|
+
let replacement = suggestion;
|
|
4732
|
+
const leadingQuote = token.startsWith('"') || token.startsWith('\'') ? token[0] : '';
|
|
4733
|
+
const trailingQuote = token.length > 1 && (token.endsWith('"') || token.endsWith('\'')) ? token[token.length - 1] : '';
|
|
4734
|
+
if (leadingQuote && !replacement.startsWith(leadingQuote)) {
|
|
4735
|
+
replacement = `${leadingQuote}${replacement}`;
|
|
4736
|
+
}
|
|
4737
|
+
if (trailingQuote && !replacement.endsWith(trailingQuote)) {
|
|
4738
|
+
replacement = `${replacement}${trailingQuote}`;
|
|
4739
|
+
}
|
|
4740
|
+
// Only insert : if we know the user isn't entering in a nested path
|
|
4741
|
+
if (colonNeeded && (!leadingQuote || trailingQuote)) {
|
|
4742
|
+
replacement = `${replacement}:`;
|
|
4743
|
+
}
|
|
4744
|
+
this.searchText = this.searchText.slice(0, start) + replacement + after;
|
|
4745
|
+
this.$nextTick(() => {
|
|
4746
|
+
const pos = start + replacement.length;
|
|
4747
|
+
input.setSelectionRange(pos, pos);
|
|
4748
|
+
});
|
|
4749
|
+
this.autocompleteSuggestions = [];
|
|
4750
|
+
},
|
|
4751
|
+
addPathFilter(path) {
|
|
4752
|
+
if (this.searchText) {
|
|
4753
|
+
if (this.searchText.endsWith('}')) {
|
|
4754
|
+
this.searchText = this.searchText.slice(0, -1) + `, ${path}: }`;
|
|
4755
|
+
} else {
|
|
4756
|
+
this.searchText += `, ${path}: }`;
|
|
4757
|
+
}
|
|
4758
|
+
|
|
4759
|
+
} else {
|
|
4760
|
+
// If this.searchText is empty or undefined, initialize it with a new object
|
|
4761
|
+
this.searchText = `{ ${path}: }`;
|
|
4762
|
+
}
|
|
4763
|
+
|
|
4764
|
+
|
|
4765
|
+
this.$nextTick(() => {
|
|
4766
|
+
const input = this.$refs.searchInput;
|
|
4767
|
+
const cursorIndex = this.searchText.lastIndexOf(':') + 2; // Move cursor after ": "
|
|
4768
|
+
|
|
4769
|
+
input.focus();
|
|
4770
|
+
input.setSelectionRange(cursorIndex, cursorIndex);
|
|
4771
|
+
});
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
4774
|
+
});
|
|
4592
4775
|
|
|
4776
|
+
|
|
4777
|
+
/***/ }),
|
|
4778
|
+
|
|
4779
|
+
/***/ "./frontend/src/models/models.css":
|
|
4780
|
+
/*!****************************************!*\
|
|
4781
|
+
!*** ./frontend/src/models/models.css ***!
|
|
4782
|
+
\****************************************/
|
|
4783
|
+
/***/ ((module) => {
|
|
4784
|
+
|
|
4785
|
+
"use strict";
|
|
4786
|
+
module.exports = ".models {\n position: relative;\n display: flex;\n flex-direction: row;\n min-height: calc(100% - 56px);\n}\n\n.models button.gray {\n color: black;\n background-color: #eee;\n}\n\n.models .model-selector {\n background-color: #eee;\n flex-grow: 0;\n padding: 15px;\n padding-top: 0px;\n}\n\n.models h1 {\n margin-top: 0px;\n}\n\n.models .documents {\n flex-grow: 1;\n overflow: scroll;\n max-height: calc(100vh - 56px);\n}\n\n.models .documents table {\n /* max-width: -moz-fit-content;\n max-width: fit-content; */\n width: 100%;\n table-layout: auto;\n font-size: small;\n padding: 0;\n margin-right: 1em;\n white-space: nowrap;\n z-index: -1;\n border-collapse: collapse;\n line-height: 1.5em;\n}\n\n.models .documents table th {\n position: sticky;\n top: 42px;\n background-color: white;\n z-index: 1;\n}\n\n.models .documents table th:after {\n content: \"\";\n position: absolute;\n left: 0;\n width: 100%;\n bottom: -1px;\n border-bottom: thin solid rgba(0, 0, 0, 0.12);\n}\n\n.models .documents table tr {\n color: black;\n border-spacing: 0px 0px;\n background-color: white;\n cursor: pointer;\n}\n\n.models .documents table tr:nth-child(even) {\n background-color: #f5f5f5;\n}\n\n.models .documents table tr:hover {\n background-color: #a7b9ff;\n}\n\n.models .documents table th,\ntd {\n border-bottom: thin solid rgba(0, 0, 0, 0.12);\n text-align: left;\n padding: 0 16px;\n height: 48px;\n}\n\n.models textarea {\n font-size: 1.2em;\n}\n\n.models .path-type {\n color: rgba(0, 0, 0, 0.36);\n font-size: 0.8em;\n}\n\n.models .documents-menu {\n position: fixed;\n background-color: white;\n z-index: 1;\n padding: 4px;\n display: flex;\n width: 100vw;\n}\n\n@media (min-width: 1024px) {\n .models .documents-menu {\n width: calc(100vw - 12rem);\n }\n}\n\n.models .documents-menu .search-input {\n flex-grow: 1;\n align-items: center;\n}\n\n.models .search-input input {\n padding: 0.25em 0.5em;\n font-size: 1.1em;\n border: 1px solid #ddd;\n border-radius: 3px;\n width: calc(100% - 1em);\n}\n\n.models .sort-arrow {\n padding-left: 10px;\n padding-right: 10px;\n}\n\n.models .loader {\n width: 100%;\n text-align: center;\n}\n\n.models .loader img {\n height: 4em;\n}\n\n.models .documents .buttons {\n display: inline-flex;\n justify-content: space-around;\n align-items: center;\n}\n\n";
|
|
4787
|
+
|
|
4788
|
+
/***/ }),
|
|
4789
|
+
|
|
4790
|
+
/***/ "./frontend/src/models/models.html":
|
|
4791
|
+
/*!*****************************************!*\
|
|
4792
|
+
!*** ./frontend/src/models/models.html ***!
|
|
4793
|
+
\*****************************************/
|
|
4794
|
+
/***/ ((module) => {
|
|
4795
|
+
|
|
4796
|
+
"use strict";
|
|
4797
|
+
module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px); height: calc(100dvh - 55px)\">\n <div class=\"fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10\" @click=\"hideSidebar = false\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"#5f6368\"><path d=\"M360-120v-720h80v720h-80Zm160-160v-400l200 200-200 200Z\"/></svg>\n </div>\n <aside class=\"bg-white border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-48 fixed lg:relative shrink-0\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-48' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-48 overflow-x-hidden\">\n <div class=\"p-4 font-bold text-lg\">Models</div>\n <button\n @click=\"hideSidebar = true\"\n class=\"ml-auto mr-2 p-2 rounded hover:bg-gray-200 focus:outline-none\"\n aria-label=\"Close sidebar\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" style=\"h-5 w-5\" viewBox=\"0 -960 960 960\" class=\"w-5\" fill=\"currentColor\"><path d=\"M660-320v-320L500-480l160 160ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm120-80v-560H200v560h120Zm80 0h360v-560H400v560Zm-80 0H200h120Z\"/></svg>\n </button>\n </div>\n <nav class=\"flex flex-1 flex-col\">\n <ul role=\"list\" class=\"flex flex-1 flex-col gap-y-7\">\n <li>\n <ul role=\"list\">\n <li v-for=\"model in models\">\n <router-link\n :to=\"'/model/' + model\"\n class=\"block truncate rounded-md py-2 pr-2 pl-2 text-sm font-semibold text-gray-700\"\n :class=\"model === currentModel ? 'bg-ultramarine-100 font-bold' : 'hover:bg-ultramarine-100'\">\n {{model}}\n </router-link>\n </li>\n </ul>\n </li>\n </ul>\n <div v-if=\"models.length === 0 && status === 'loaded'\" class=\"p-2 bg-red-100\">\n No models found\n </div>\n </nav>\n </aside>\n <div class=\"documents\" ref=\"documentsList\">\n <div class=\"relative h-[42px] z-10\">\n <div class=\"documents-menu\">\n <div class=\"flex flex-row items-center w-full gap-2\">\n <document-search\n ref=\"documentSearch\"\n :value=\"searchText\"\n :schema-paths=\"schemaPaths\"\n @search=\"search\"\n >\n </document-search>\n <div>\n <span v-if=\"numDocuments == null\">Loading ...</span>\n <span v-else-if=\"typeof numDocuments === 'number'\">{{numDocuments === 1 ? numDocuments+ ' document' : numDocuments + ' documents'}}</span>\n </div>\n <button\n @click=\"shouldShowExportModal = true\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 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-ultramarine-600\">\n Export\n </button>\n <button\n @click=\"stagingSelect\"\n type=\"button\"\n :class=\"{ 'bg-gray-500 ring-inset ring-2 ring-gray-300 hover:bg-gray-600': selectMultiple, 'bg-ultramarine-600 hover:bg-ultramarine-500' : !selectMultiple }\"\n class=\"rounded px-2 py-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n >\n {{ selectMultiple ? 'Cancel' : 'Select' }}\n </button>\n <button\n v-show=\"selectMultiple\"\n @click=\"shouldShowUpdateMultipleModal=true;\"\n type=\"button\"\n class=\"rounded bg-green-600 px-2 py-2 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 >\n Update\n </button>\n <button\n @click=\"shouldShowDeleteMultipleModal=true;\"\n type=\"button\"\n v-show=\"selectMultiple\"\n class=\"rounded bg-red-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500\"\n >\n Delete\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 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-ultramarine-600\">\n Indexes\n </button>\n <button\n @click=\"shouldShowCreateModal = true;\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 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-ultramarine-600\">\n Create\n </button>\n <button\n @click=\"openFieldSelection\"\n type=\"button\"\n v-show=\"!selectMultiple\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 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-ultramarine-600\">\n Fields\n </button>\n <span class=\"isolate inline-flex rounded-md shadow-sm\">\n <button\n @click=\"setOutputType('table')\"\n type=\"button\"\n class=\"relative inline-flex items-center rounded-none rounded-l-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'table' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/table.svg\">\n </button>\n <button\n @click=\"setOutputType('json')\"\n type=\"button\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10\"\n :class=\"outputType === 'json' ? 'bg-gray-200' : 'bg-white'\">\n <img class=\"h-5 w-5\" src=\"images/json.svg\">\n </button>\n </span>\n </div>\n </div>\n </div>\n <div class=\"documents-container relative\">\n <div v-if=\"error\">\n <div class=\"bg-red-100 border border-red-400 text-red-700 px-4 py-3 relative m-4 rounded-md\" role=\"alert\">\n <span class=\"block font-bold\">Error</span>\n <span class=\"block\">{{ error }}</span>\n </div>\n </div>\n <table v-else-if=\"outputType === 'table'\">\n <thead>\n <th v-for=\"path in filteredPaths\" @click=\"addPathFilter(path.path)\" class=\"cursor-pointer\">\n {{path.path}}\n <span class=\"path-type\">\n ({{(path.instance || 'unknown')}})\n </span>\n <span class=\"sort-arrow\" @click=\"sortDocs(1, path.path)\">{{sortBy[path.path] == 1 ? 'X' : '↑'}}</span>\n <span class=\"sort-arrow\" @click=\"sortDocs(-1, path.path)\">{{sortBy[path.path] == -1 ? 'X' : '↓'}}</span>\n </th>\n </thead>\n <tbody>\n <tr v-for=\"document in documents\" @click=\"handleDocumentClick(document, $event)\" :key=\"document._id\">\n <td v-for=\"schemaPath in filteredPaths\" :class=\"{ 'bg-blue-200': selectedDocuments.some(x => x._id.toString() === document._id.toString()) }\">\n <component\n :is=\"getComponentForPath(schemaPath)\"\n :value=\"getValueForPath(document, schemaPath.path)\"\n :allude=\"getReferenceModel(schemaPath)\">\n </component>\n </td>\n </tr>\n </tbody>\n </table>\n <div v-else-if=\"outputType === 'json'\" class=\"flex flex-col space-y-6\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative transition-colors',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:bg-slate-100'\n ]\"\n >\n <button\n type=\"button\"\n class=\"absolute top-2 right-2 z-10 inline-flex items-center rounded bg-ultramarine-600 px-2 py-1 text-xs font-semibold text-white shadow-sm transition-opacity duration-150 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n @click.stop=\"openDocument(document)\"\n >\n Open this Document\n </button>\n <list-json :value=\"filterDocument(document)\" :references=\"referenceMap\">\n </list-json>\n </div>\n </div>\n <div v-if=\"status === 'loading'\" class=\"loader\">\n <img src=\"images/loader.gif\">\n </div>\n </div>\n </div>\n <modal v-if=\"shouldShowExportModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowExportModal = false\">×</div>\n <export-query-results\n :schemaPaths=\"schemaPaths\"\n :search-text=\"searchText\"\n :currentModel=\"currentModel\"\n @done=\"shouldShowExportModal = false\">\n </export-query-results>\n </template>\n </modal>\n <modal v-if=\"shouldShowIndexModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowIndexModal = false\">×</div>\n <div class=\"text-xl font-bold mb-2\">Indexes</div>\n <div v-for=\"index in mongoDBIndexes\" class=\"w-full flex items-center\">\n <div class=\"grow shrink text-left flex justify-between items-center\" v-if=\"index.name != '_id_'\">\n <div>\n <div class=\"font-bold\">{{ index.name }}</div>\n <div class=\"text-sm font-mono\">{{ JSON.stringify(index.key) }}</div>\n </div>\n <div>\n <async-button\n type=\"button\"\n @click=\"dropIndex(index.name)\"\n class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 disabled:bg-gray-400 disabled:cursor-not-allowed\">\n Drop\n </async-button>\n </div>\n </div>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowFieldModal = false; selectedPaths = [...filteredPaths];\">×</div>\n <div v-for=\"(path, index) in schemaPaths\" :key=\"index\" class=\"w-5 flex items-center\">\n <input class=\"mt-0 h-4 w-4 rounded border-gray-300 text-sky-600 focus:ring-sky-600 accent-sky-600\" type=\"checkbox\" :id=\"'path.path'+index\" @change=\"addOrRemove(path)\" :value=\"path.path\" :checked=\"isSelected(path.path)\" />\n <div class=\"ml-2 text-gray-700 grow shrink text-left\">\n <label :for=\"'path.path' + index\">{{path.path}}</label>\n </div>\n </div>\n <div class=\"mt-4 flex gap-2\">\n <button type=\"button\" @click=\"filterDocuments()\" 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\">Filter Selection</button>\n <button type=\"button\" @click=\"selectAll()\" 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\">Select All</button>\n <button type=\"button\" @click=\"deselectAll()\" class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">Deselect All</button>\n <button type=\"button\" @click=\"resetDocuments()\" class=\"rounded-md bg-gray-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\" >Cancel</button>\n </div>\n </template>\n </modal>\n <modal v-if=\"shouldShowCreateModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCreateModal = false;\">×</div>\n <create-document :currentModel=\"currentModel\" :paths=\"schemaPaths\" @close=\"closeCreationModal\"></create-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowUpdateMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowUpdateMultipleModal = false;\">×</div>\n <update-document :currentModel=\"currentModel\" :document=\"selectedDocuments\" :multiple=\"true\" @update=\"updateDocuments\" @close=\"shouldShowUpdateMultipleModal=false;\"></update-document>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteMultipleModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteMultipleModal = false;\">×</div>\n <h2>Are you sure you want to delete {{selectedDocuments.length}} documents?</h2>\n <div>\n <list-json :value=\"selectedDocuments\"></list-json>\n </div>\n <div class=\"flex gap-4\">\n <async-button @click=\"deleteDocuments\" class=\"rounded bg-red-500 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n Confirm\n </async-button>\n <button @click=\"shouldShowDeleteMultipleModal = false;\" class=\"rounded bg-gray-400 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500\">\n Cancel\n </button>\n </div>\n </template>\n </modal>\n</div>\n";
|
|
4798
|
+
|
|
4799
|
+
/***/ }),
|
|
4800
|
+
|
|
4801
|
+
/***/ "./frontend/src/models/models.js":
|
|
4802
|
+
/*!***************************************!*\
|
|
4803
|
+
!*** ./frontend/src/models/models.js ***!
|
|
4804
|
+
\***************************************/
|
|
4805
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
4806
|
+
|
|
4807
|
+
"use strict";
|
|
4808
|
+
|
|
4809
|
+
|
|
4810
|
+
const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
|
|
4811
|
+
const template = __webpack_require__(/*! ./models.html */ "./frontend/src/models/models.html");
|
|
4812
|
+
const mpath = __webpack_require__(/*! mpath */ "./node_modules/mpath/index.js");
|
|
4813
|
+
|
|
4814
|
+
const appendCSS = __webpack_require__(/*! ../appendCSS */ "./frontend/src/appendCSS.js");
|
|
4593
4815
|
appendCSS(__webpack_require__(/*! ./models.css */ "./frontend/src/models/models.css"));
|
|
4594
4816
|
|
|
4595
4817
|
const limit = 20;
|
|
@@ -4615,9 +4837,6 @@ module.exports = app => app.component('models', {
|
|
|
4615
4837
|
selectMultiple: false,
|
|
4616
4838
|
selectedDocuments: [],
|
|
4617
4839
|
searchText: '',
|
|
4618
|
-
autocompleteSuggestions: [],
|
|
4619
|
-
autocompleteIndex: 0,
|
|
4620
|
-
autocompleteTrie: null,
|
|
4621
4840
|
shouldShowExportModal: false,
|
|
4622
4841
|
shouldShowCreateModal: false,
|
|
4623
4842
|
shouldShowFieldModal: false,
|
|
@@ -4636,7 +4855,6 @@ module.exports = app => app.component('models', {
|
|
|
4636
4855
|
}),
|
|
4637
4856
|
created() {
|
|
4638
4857
|
this.currentModel = this.model;
|
|
4639
|
-
this.buildAutocompleteTrie();
|
|
4640
4858
|
this.loadOutputPreference();
|
|
4641
4859
|
},
|
|
4642
4860
|
beforeDestroy() {
|
|
@@ -4675,16 +4893,6 @@ module.exports = app => app.component('models', {
|
|
|
4675
4893
|
}
|
|
4676
4894
|
},
|
|
4677
4895
|
methods: {
|
|
4678
|
-
buildAutocompleteTrie() {
|
|
4679
|
-
this.autocompleteTrie = new Trie();
|
|
4680
|
-
this.autocompleteTrie.bulkInsert(QUERY_SELECTORS, 5);
|
|
4681
|
-
if (Array.isArray(this.schemaPaths) && this.schemaPaths.length > 0) {
|
|
4682
|
-
const paths = this.schemaPaths
|
|
4683
|
-
.map(path => path?.path)
|
|
4684
|
-
.filter(path => typeof path === 'string' && path.length > 0);
|
|
4685
|
-
this.autocompleteTrie.bulkInsert(paths, 10);
|
|
4686
|
-
}
|
|
4687
|
-
},
|
|
4688
4896
|
loadOutputPreference() {
|
|
4689
4897
|
if (typeof window === 'undefined' || !window.localStorage) {
|
|
4690
4898
|
return;
|
|
@@ -4760,141 +4968,6 @@ module.exports = app => app.component('models', {
|
|
|
4760
4968
|
const { mongoDBIndexes } = await api.Model.dropIndex({ model: this.currentModel, name });
|
|
4761
4969
|
this.mongoDBIndexes = mongoDBIndexes;
|
|
4762
4970
|
},
|
|
4763
|
-
initFilter(ev) {
|
|
4764
|
-
if (!this.searchText) {
|
|
4765
|
-
this.searchText = '{}';
|
|
4766
|
-
this.$nextTick(() => {
|
|
4767
|
-
ev.target.setSelectionRange(1, 1);
|
|
4768
|
-
});
|
|
4769
|
-
}
|
|
4770
|
-
},
|
|
4771
|
-
updateAutocomplete() {
|
|
4772
|
-
const input = this.$refs.searchInput;
|
|
4773
|
-
const cursorPos = input ? input.selectionStart : 0;
|
|
4774
|
-
const before = this.searchText.slice(0, cursorPos);
|
|
4775
|
-
const match = before.match(/(?:\{|,)\s*([^:\s]*)$/);
|
|
4776
|
-
if (match && match[1]) {
|
|
4777
|
-
const token = match[1];
|
|
4778
|
-
const leadingQuoteMatch = token.match(/^["']/);
|
|
4779
|
-
const trailingQuoteMatch = token.length > 1 && /["']$/.test(token)
|
|
4780
|
-
? token[token.length - 1]
|
|
4781
|
-
: '';
|
|
4782
|
-
const term = token
|
|
4783
|
-
.replace(/^["']/, '')
|
|
4784
|
-
.replace(trailingQuoteMatch ? new RegExp(`[${trailingQuoteMatch}]$`) : '', '')
|
|
4785
|
-
.trim();
|
|
4786
|
-
if (!term) {
|
|
4787
|
-
this.autocompleteSuggestions = [];
|
|
4788
|
-
return;
|
|
4789
|
-
}
|
|
4790
|
-
if (this.autocompleteTrie) {
|
|
4791
|
-
const primarySuggestions = this.autocompleteTrie.getSuggestions(term, 10);
|
|
4792
|
-
const suggestionsSet = new Set(primarySuggestions);
|
|
4793
|
-
if (Array.isArray(this.schemaPaths) && this.schemaPaths.length > 0) {
|
|
4794
|
-
for (const schemaPath of this.schemaPaths) {
|
|
4795
|
-
const path = schemaPath?.path;
|
|
4796
|
-
if (
|
|
4797
|
-
typeof path === 'string' &&
|
|
4798
|
-
path.startsWith(`${term}.`) &&
|
|
4799
|
-
!suggestionsSet.has(path)
|
|
4800
|
-
) {
|
|
4801
|
-
suggestionsSet.add(path);
|
|
4802
|
-
if (suggestionsSet.size >= 10) {
|
|
4803
|
-
break;
|
|
4804
|
-
}
|
|
4805
|
-
}
|
|
4806
|
-
}
|
|
4807
|
-
}
|
|
4808
|
-
let suggestions = Array.from(suggestionsSet);
|
|
4809
|
-
if (leadingQuoteMatch) {
|
|
4810
|
-
const leadingQuote = leadingQuoteMatch[0];
|
|
4811
|
-
suggestions = suggestions.map(suggestion => `${leadingQuote}${suggestion}`);
|
|
4812
|
-
}
|
|
4813
|
-
if (trailingQuoteMatch) {
|
|
4814
|
-
suggestions = suggestions.map(suggestion =>
|
|
4815
|
-
suggestion.endsWith(trailingQuoteMatch) ? suggestion : `${suggestion}${trailingQuoteMatch}`
|
|
4816
|
-
);
|
|
4817
|
-
}
|
|
4818
|
-
this.autocompleteSuggestions = suggestions;
|
|
4819
|
-
this.autocompleteIndex = 0;
|
|
4820
|
-
return;
|
|
4821
|
-
}
|
|
4822
|
-
}
|
|
4823
|
-
this.autocompleteSuggestions = [];
|
|
4824
|
-
},
|
|
4825
|
-
handleKeyDown(ev) {
|
|
4826
|
-
if (this.autocompleteSuggestions.length === 0) {
|
|
4827
|
-
return;
|
|
4828
|
-
}
|
|
4829
|
-
if (ev.key === 'Tab' || ev.key === 'Enter') {
|
|
4830
|
-
ev.preventDefault();
|
|
4831
|
-
this.applySuggestion(this.autocompleteIndex);
|
|
4832
|
-
} else if (ev.key === 'ArrowDown') {
|
|
4833
|
-
ev.preventDefault();
|
|
4834
|
-
this.autocompleteIndex = (this.autocompleteIndex + 1) % this.autocompleteSuggestions.length;
|
|
4835
|
-
} else if (ev.key === 'ArrowUp') {
|
|
4836
|
-
ev.preventDefault();
|
|
4837
|
-
this.autocompleteIndex = (this.autocompleteIndex + this.autocompleteSuggestions.length - 1) % this.autocompleteSuggestions.length;
|
|
4838
|
-
}
|
|
4839
|
-
},
|
|
4840
|
-
applySuggestion(index) {
|
|
4841
|
-
const suggestion = this.autocompleteSuggestions[index];
|
|
4842
|
-
if (!suggestion) {
|
|
4843
|
-
return;
|
|
4844
|
-
}
|
|
4845
|
-
const input = this.$refs.searchInput;
|
|
4846
|
-
const cursorPos = input.selectionStart;
|
|
4847
|
-
const before = this.searchText.slice(0, cursorPos);
|
|
4848
|
-
const after = this.searchText.slice(cursorPos);
|
|
4849
|
-
const match = before.match(/(?:\{|,)\s*([^:\s]*)$/);
|
|
4850
|
-
const colonNeeded = !/^\s*:/.test(after);
|
|
4851
|
-
if (!match) {
|
|
4852
|
-
return;
|
|
4853
|
-
}
|
|
4854
|
-
const token = match[1];
|
|
4855
|
-
const start = cursorPos - token.length;
|
|
4856
|
-
let replacement = suggestion;
|
|
4857
|
-
const leadingQuote = token.startsWith('"') || token.startsWith('\'') ? token[0] : '';
|
|
4858
|
-
const trailingQuote = token.length > 1 && (token.endsWith('"') || token.endsWith('\'')) ? token[token.length - 1] : '';
|
|
4859
|
-
if (leadingQuote && !replacement.startsWith(leadingQuote)) {
|
|
4860
|
-
replacement = `${leadingQuote}${replacement}`;
|
|
4861
|
-
}
|
|
4862
|
-
if (trailingQuote && !replacement.endsWith(trailingQuote)) {
|
|
4863
|
-
replacement = `${replacement}${trailingQuote}`;
|
|
4864
|
-
}
|
|
4865
|
-
// Only insert : if we know the user isn't entering in a nested path
|
|
4866
|
-
if (colonNeeded && (!leadingQuote || trailingQuote)) {
|
|
4867
|
-
replacement = `${replacement}:`;
|
|
4868
|
-
}
|
|
4869
|
-
this.searchText = this.searchText.slice(0, start) + replacement + after;
|
|
4870
|
-
this.$nextTick(() => {
|
|
4871
|
-
const pos = start + replacement.length;
|
|
4872
|
-
input.setSelectionRange(pos, pos);
|
|
4873
|
-
});
|
|
4874
|
-
this.autocompleteSuggestions = [];
|
|
4875
|
-
},
|
|
4876
|
-
clickFilter(path) {
|
|
4877
|
-
if (this.searchText) {
|
|
4878
|
-
if (this.searchText.endsWith('}')) {
|
|
4879
|
-
this.searchText = this.searchText.slice(0, -1) + `, ${path}: }`;
|
|
4880
|
-
} else {
|
|
4881
|
-
this.searchText += `, ${path}: }`;
|
|
4882
|
-
}
|
|
4883
|
-
|
|
4884
|
-
} else {
|
|
4885
|
-
// If this.searchText is empty or undefined, initialize it with a new object
|
|
4886
|
-
this.searchText = `{ ${path}: }`;
|
|
4887
|
-
}
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
this.$nextTick(() => {
|
|
4891
|
-
const input = this.$refs.searchInput;
|
|
4892
|
-
const cursorIndex = this.searchText.lastIndexOf(':') + 2; // Move cursor after ": "
|
|
4893
|
-
|
|
4894
|
-
input.focus();
|
|
4895
|
-
input.setSelectionRange(cursorIndex, cursorIndex);
|
|
4896
|
-
});
|
|
4897
|
-
},
|
|
4898
4971
|
async closeCreationModal() {
|
|
4899
4972
|
this.shouldShowCreateModal = false;
|
|
4900
4973
|
await this.getDocuments();
|
|
@@ -4944,7 +5017,8 @@ module.exports = app => app.component('models', {
|
|
|
4944
5017
|
}
|
|
4945
5018
|
await this.loadMoreDocuments();
|
|
4946
5019
|
},
|
|
4947
|
-
async search() {
|
|
5020
|
+
async search(searchText) {
|
|
5021
|
+
this.searchText = searchText;
|
|
4948
5022
|
const hasSearch = typeof this.searchText === 'string' && this.searchText.trim().length > 0;
|
|
4949
5023
|
if (hasSearch) {
|
|
4950
5024
|
this.query.search = this.searchText;
|
|
@@ -4959,6 +5033,11 @@ module.exports = app => app.component('models', {
|
|
|
4959
5033
|
await this.loadMoreDocuments();
|
|
4960
5034
|
this.status = 'loaded';
|
|
4961
5035
|
},
|
|
5036
|
+
addPathFilter(path) {
|
|
5037
|
+
if (this.$refs.documentSearch?.addPathFilter) {
|
|
5038
|
+
this.$refs.documentSearch.addPathFilter(path);
|
|
5039
|
+
}
|
|
5040
|
+
},
|
|
4962
5041
|
async openIndexModal() {
|
|
4963
5042
|
this.shouldShowIndexModal = true;
|
|
4964
5043
|
const { mongoDBIndexes, schemaIndexes } = await api.Model.getIndexes({ model: this.currentModel });
|
|
@@ -4978,7 +5057,6 @@ module.exports = app => app.component('models', {
|
|
|
4978
5057
|
// Clear previous data
|
|
4979
5058
|
this.documents = [];
|
|
4980
5059
|
this.schemaPaths = [];
|
|
4981
|
-
this.buildAutocompleteTrie();
|
|
4982
5060
|
this.numDocuments = null;
|
|
4983
5061
|
this.loadedAllDocs = false;
|
|
4984
5062
|
this.lastSelectedIndex = null;
|
|
@@ -5006,7 +5084,6 @@ module.exports = app => app.component('models', {
|
|
|
5006
5084
|
}
|
|
5007
5085
|
this.filteredPaths = [...this.schemaPaths];
|
|
5008
5086
|
this.selectedPaths = [...this.schemaPaths];
|
|
5009
|
-
this.buildAutocompleteTrie();
|
|
5010
5087
|
schemaPathsReceived = true;
|
|
5011
5088
|
}
|
|
5012
5089
|
if (event.numDocs !== undefined) {
|
|
@@ -5236,6 +5313,7 @@ class TrieNode {
|
|
|
5236
5313
|
this.children = Object.create(null);
|
|
5237
5314
|
this.isEnd = false;
|
|
5238
5315
|
this.freq = 0;
|
|
5316
|
+
this.roles = new Set(); // semantic roles like 'fieldName', 'operator'
|
|
5239
5317
|
}
|
|
5240
5318
|
}
|
|
5241
5319
|
|
|
@@ -5244,7 +5322,7 @@ class Trie {
|
|
|
5244
5322
|
this.root = new TrieNode();
|
|
5245
5323
|
}
|
|
5246
5324
|
|
|
5247
|
-
insert(word, freq = 1) {
|
|
5325
|
+
insert(word, freq = 1, role = null) {
|
|
5248
5326
|
if (!word) {
|
|
5249
5327
|
return;
|
|
5250
5328
|
}
|
|
@@ -5257,27 +5335,25 @@ class Trie {
|
|
|
5257
5335
|
}
|
|
5258
5336
|
node.isEnd = true;
|
|
5259
5337
|
node.freq += freq;
|
|
5338
|
+
if (role) {
|
|
5339
|
+
node.roles.add(role);
|
|
5340
|
+
}
|
|
5260
5341
|
}
|
|
5261
5342
|
|
|
5262
|
-
bulkInsert(words, freq = 1) {
|
|
5263
|
-
|
|
5264
|
-
return;
|
|
5265
|
-
}
|
|
5266
|
-
for (const word of words) {
|
|
5267
|
-
this.insert(word, freq);
|
|
5268
|
-
}
|
|
5343
|
+
bulkInsert(words, freq = 1, role = null) {
|
|
5344
|
+
for (const word of words) this.insert(word, freq, role);
|
|
5269
5345
|
}
|
|
5270
5346
|
|
|
5271
|
-
collect(node, prefix, out) {
|
|
5272
|
-
if (node.isEnd) {
|
|
5347
|
+
collect(node, prefix, out, role) {
|
|
5348
|
+
if (node.isEnd && (role == null || node.roles.has(role))) {
|
|
5273
5349
|
out.push([prefix, node.freq]);
|
|
5274
5350
|
}
|
|
5275
5351
|
for (const [ch, child] of Object.entries(node.children)) {
|
|
5276
|
-
this.collect(child, prefix + ch, out);
|
|
5352
|
+
this.collect(child, prefix + ch, out, role);
|
|
5277
5353
|
}
|
|
5278
5354
|
}
|
|
5279
5355
|
|
|
5280
|
-
suggest(prefix, limit = 10) {
|
|
5356
|
+
suggest(prefix, limit = 10, role = null) {
|
|
5281
5357
|
let node = this.root;
|
|
5282
5358
|
for (const ch of prefix) {
|
|
5283
5359
|
if (!node.children[ch]) {
|
|
@@ -5286,19 +5362,19 @@ class Trie {
|
|
|
5286
5362
|
node = node.children[ch];
|
|
5287
5363
|
}
|
|
5288
5364
|
const results = [];
|
|
5289
|
-
this.collect(node, prefix, results);
|
|
5365
|
+
this.collect(node, prefix, results, role);
|
|
5290
5366
|
results.sort((a, b) => b[1] - a[1]);
|
|
5291
5367
|
return results.slice(0, limit).map(([word]) => word);
|
|
5292
5368
|
}
|
|
5293
5369
|
|
|
5294
|
-
fuzzySuggest(prefix, limit = 10) {
|
|
5370
|
+
fuzzySuggest(prefix, limit = 10, role = null) {
|
|
5295
5371
|
const results = new Set();
|
|
5296
5372
|
|
|
5297
5373
|
const dfs = (node, path, edits) => {
|
|
5298
5374
|
if (edits > 1) {
|
|
5299
5375
|
return;
|
|
5300
5376
|
}
|
|
5301
|
-
if (node.isEnd && Math.abs(path.length - prefix.length) <= 1) {
|
|
5377
|
+
if (node.isEnd && Math.abs(path.length - prefix.length) <= 1 && (role == null || node.roles.has(role))) {
|
|
5302
5378
|
const dist = levenshtein(prefix, path);
|
|
5303
5379
|
if (dist <= 1) {
|
|
5304
5380
|
results.add(path);
|
|
@@ -5314,17 +5390,44 @@ class Trie {
|
|
|
5314
5390
|
return Array.from(results).slice(0, limit);
|
|
5315
5391
|
}
|
|
5316
5392
|
|
|
5317
|
-
getSuggestions(prefix, limit = 10) {
|
|
5393
|
+
getSuggestions(prefix, limit = 10, role = null) {
|
|
5318
5394
|
if (!prefix) {
|
|
5319
5395
|
return [];
|
|
5320
5396
|
}
|
|
5321
|
-
const exact = this.suggest(prefix, limit);
|
|
5397
|
+
const exact = this.suggest(prefix, limit, role);
|
|
5322
5398
|
if (exact.length >= limit) {
|
|
5323
5399
|
return exact;
|
|
5324
5400
|
}
|
|
5325
|
-
const fuzzy = this.fuzzySuggest(prefix, limit - exact.length);
|
|
5401
|
+
const fuzzy = this.fuzzySuggest(prefix, limit - exact.length, role);
|
|
5326
5402
|
return [...exact, ...fuzzy];
|
|
5327
5403
|
}
|
|
5404
|
+
|
|
5405
|
+
toString() {
|
|
5406
|
+
const lines = [];
|
|
5407
|
+
function dfs(node, prefix, depth) {
|
|
5408
|
+
let line = ' '.repeat(depth);
|
|
5409
|
+
if (prefix.length > 0) {
|
|
5410
|
+
line += prefix[prefix.length - 1];
|
|
5411
|
+
} else {
|
|
5412
|
+
line += '(root)';
|
|
5413
|
+
}
|
|
5414
|
+
if (node.isEnd) {
|
|
5415
|
+
line += ' *';
|
|
5416
|
+
}
|
|
5417
|
+
if (node.roles.size > 0) {
|
|
5418
|
+
line += ' [' + Array.from(node.roles).join(',') + ']';
|
|
5419
|
+
}
|
|
5420
|
+
if (node.freq > 0) {
|
|
5421
|
+
line += ` {freq:${node.freq}}`;
|
|
5422
|
+
}
|
|
5423
|
+
lines.push(line);
|
|
5424
|
+
for (const ch of Object.keys(node.children).sort()) {
|
|
5425
|
+
dfs(node.children[ch], prefix + ch, depth + 1);
|
|
5426
|
+
}
|
|
5427
|
+
}
|
|
5428
|
+
dfs(this.root, '', 0);
|
|
5429
|
+
return lines.join('\n');
|
|
5430
|
+
}
|
|
5328
5431
|
}
|
|
5329
5432
|
|
|
5330
5433
|
function levenshtein(a, b) {
|
|
@@ -16606,7 +16709,7 @@ module.exports = function stringToParts(str) {
|
|
|
16606
16709
|
/***/ ((module) => {
|
|
16607
16710
|
|
|
16608
16711
|
"use strict";
|
|
16609
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.1.
|
|
16712
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.1.7","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"},"license":"Apache-2.0","dependencies":{"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","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 || ^9.0.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"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
|
|
16610
16713
|
|
|
16611
16714
|
/***/ })
|
|
16612
16715
|
|