@mongoosejs/studio 0.0.105 → 0.0.107

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.
@@ -2400,41 +2400,47 @@ module.exports = app => app.component('models', {
2400
2400
  this.currentModel = this.model;
2401
2401
  },
2402
2402
  beforeDestroy() {
2403
- document.removeEventListener('scroll', () => this.onScroll(), true);
2403
+ document.removeEventListener('scroll', this.onScroll, true);
2404
+ window.removeEventListener('popstate', this.onPopState, true);
2404
2405
  },
2405
2406
  async mounted() {
2406
- document.addEventListener('scroll', () => this.onScroll(), true);
2407
+ this.onScroll = () => this.checkIfScrolledToBottom();
2408
+ document.addEventListener('scroll', this.onScroll, true);
2409
+ this.onPopState = () => this.initSearchFromUrl();
2410
+ window.addEventListener('popstate', this.onPopState, true);
2407
2411
  this.models = await api.Model.listModels().then(res => res.models);
2408
2412
  if (this.currentModel == null && this.models.length > 0) {
2409
2413
  this.currentModel = this.models[0];
2410
2414
  }
2411
2415
 
2412
- this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
2413
- if (this.$route.query?.search) {
2414
- this.searchText = this.$route.query.search;
2415
- this.filter = eval(`(${this.$route.query.search})`);
2416
- this.filter = EJSON.stringify(this.filter);
2417
- }
2418
- if (this.$route.query?.sort) {
2419
- const sort = eval(`(${this.$route.query.sort})`);
2420
- const path = Object.keys(sort)[0];
2421
- const num = Object.values(sort)[0];
2422
- this.sortDocs(num, path);
2423
- }
2424
-
2425
-
2426
- if (this.currentModel != null) {
2427
- await this.getDocuments();
2428
- }
2429
- if (this.$route.query?.fields) {
2430
- const filter = this.$route.query.fields.split(',');
2431
- this.filteredPaths = this.filteredPaths.filter(x => filter.includes(x.path));
2432
- }
2433
-
2434
-
2435
- this.status = 'loaded';
2416
+ await this.initSearchFromUrl();
2436
2417
  },
2437
2418
  methods: {
2419
+ async initSearchFromUrl() {
2420
+ this.status = 'loading';
2421
+ this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
2422
+ if (this.$route.query?.search) {
2423
+ this.searchText = this.$route.query.search;
2424
+ this.filter = eval(`(${this.$route.query.search})`);
2425
+ this.filter = EJSON.stringify(this.filter);
2426
+ }
2427
+ if (this.$route.query?.sort) {
2428
+ const sort = eval(`(${this.$route.query.sort})`);
2429
+ const path = Object.keys(sort)[0];
2430
+ const num = Object.values(sort)[0];
2431
+ this.sortDocs(num, path);
2432
+ }
2433
+
2434
+
2435
+ if (this.currentModel != null) {
2436
+ await this.getDocuments();
2437
+ }
2438
+ if (this.$route.query?.fields) {
2439
+ const filter = this.$route.query.fields.split(',');
2440
+ this.filteredPaths = this.filteredPaths.filter(x => filter.includes(x.path));
2441
+ }
2442
+ this.status = 'loaded';
2443
+ },
2438
2444
  async dropIndex(name) {
2439
2445
  const { mongoDBIndexes } = await api.Model.dropIndex({ model: this.currentModel, name });
2440
2446
  this.mongoDBIndexes = mongoDBIndexes;
@@ -2484,7 +2490,7 @@ module.exports = app => app.component('models', {
2484
2490
  }
2485
2491
  return filteredDoc;
2486
2492
  },
2487
- async onScroll() {
2493
+ async checkIfScrolledToBottom() {
2488
2494
  if (this.status === 'loading' || this.loadedAllDocs) {
2489
2495
  return;
2490
2496
  }
@@ -2527,13 +2533,20 @@ module.exports = app => app.component('models', {
2527
2533
  this.filter = eval(`(${this.searchText})`);
2528
2534
  this.filter = EJSON.stringify(this.filter);
2529
2535
  this.query.search = this.searchText;
2530
- this.$router.push({ query: this.query });
2536
+ const query = this.query;
2537
+ const newUrl = this.$router.resolve({ query }).href;
2538
+ window.history.pushState(null, '', newUrl);
2531
2539
  } else {
2532
2540
  this.filter = {};
2533
2541
  delete this.query.search;
2534
- this.$router.push({ query: this.query });
2542
+ const query = this.query;
2543
+ const newUrl = this.$router.resolve({ query }).href;
2544
+ window.history.pushState(null, '', newUrl);
2535
2545
  }
2546
+ this.documents = [];
2547
+ this.status = 'loading';
2536
2548
  await this.loadMoreDocuments();
2549
+ this.status = 'loaded';
2537
2550
  },
2538
2551
  async openIndexModal() {
2539
2552
  this.shouldShowIndexModal = true;
@@ -4038,7 +4051,7 @@ module.exports = "<div class=\"relative flex items-start\" :class=\"{'justify-en
4038
4051
  /***/ ((module) => {
4039
4052
 
4040
4053
  "use strict";
4041
- module.exports = "<div class=\"flex\" style=\"height: calc(100vh - 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 <!-- Sidebar: Chat Threads -->\n <aside class=\"bg-gray-50 border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-64 overflow-x-hidden\">\n <div class=\"p-4 font-bold text-lg\">Chat Threads</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 <div class=\"p-4 w-64\">\n <async-button\n @click=\"createNewThread\"\n class=\"w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700\"\n >\n Create New Thread\n </async-button>\n </div>\n <div v-if=\"status === 'loaded' && chatThreads.length === 0\" class=\"p-4 text-sm text-gray-700\">\n No threads yet\n </div>\n <ul v-if=\"status === 'loaded'\" class=\"w-64\">\n <li\n v-for=\"thread in chatThreads\"\n :key=\"thread._id\"\n @click=\"selectThread(thread._id)\"\n class=\"p-4 hover:bg-gray-200 cursor-pointer w-64\"\n :class=\"{ 'bg-gray-300': thread._id === chatThreadId }\"\n >\n {{ thread.title || 'Untitled Thread' }}\n </li>\n </ul>\n </aside>\n\n <!-- Main Chat Area -->\n <main class=\"flex-1 flex flex-col\">\n <div class=\"flex-1 overflow-y-auto p-6 space-y-4\" ref=\"messagesContainer\">\n <ul role=\"list\" class=\"space-y-4\">\n <div v-if=\"true\">\n <div class=\"flex items-center justify-center py-3 mb-4\">\n <div class=\"bg-gray-300 h-px flex-grow max-w-xs\"></div>\n <p class=\"mx-4 text-sm font-medium text-gray-500\">This is the beginning of the message thread</p>\n <div class=\"bg-gray-300 h-px flex-grow max-w-xs\"></div>\n </div>\n </div>\n <li v-for=\"message in chatMessages\" :key=\"message._id\">\n <chat-message :message=\"message\"></chat-message>\n </li>\n </ul>\n </div>\n\n\n <!-- Input Area -->\n <div class=\"border-t p-4\">\n <form @submit.prevent=\"sendMessage\" :disabled=\"sendingMessage\" class=\"flex gap-2 items-end justify-end\">\n <textarea\n v-model=\"newMessage\"\n placeholder=\"Ask something...\"\n class=\"flex-1 border rounded px-4 py-2 resize-none overflow-y-auto\"\n rows=\"1\"\n ref=\"messageInput\"\n @input=\"adjustTextareaHeight\"\n @keydown.enter.exact.prevent=\"handleEnter\"\n ></textarea>\n <button class=\"bg-blue-600 text-white px-4 h-[42px] rounded disabled:bg-gray-600\" :disabled=\"sendingMessage\">\n <svg v-if=\"sendingMessage\" style=\"height: 1em\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <g>\n <circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" opacity=\"0.3\" />\n <path d=\"M12 2a10 10 0 0 1 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 12 12\" to=\"360 12 12\" dur=\"1s\" repeatCount=\"indefinite\" />\n </path>\n </g>\n </svg>\n <span v-else>Send</span>\n </button>\n </form>\n </div>\n </main>\n</div>\n";
4054
+ module.exports = "<div class=\"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 <!-- Sidebar: Chat Threads -->\n <aside class=\"bg-gray-50 border-r overflow-y-auto overflow-x-hidden h-full transition-all duration-300 ease-in-out z-20 w-0 lg:w-64 fixed lg:relative\" :class=\"hideSidebar === true ? '!w-0' : hideSidebar === false ? '!w-64' : ''\">\n <div class=\"flex items-center border-b border-gray-100 w-64 overflow-x-hidden\">\n <div class=\"p-4 font-bold text-lg\">Chat Threads</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 <div class=\"p-4 w-64\">\n <async-button\n @click=\"createNewThread\"\n class=\"w-full bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700\"\n >\n Create New Thread\n </async-button>\n </div>\n <div v-if=\"status === 'loaded' && chatThreads.length === 0\" class=\"p-4 text-sm text-gray-700\">\n No threads yet\n </div>\n <ul v-if=\"status === 'loaded'\" class=\"w-64\">\n <li\n v-for=\"thread in chatThreads\"\n :key=\"thread._id\"\n @click=\"selectThread(thread._id)\"\n class=\"p-4 hover:bg-gray-200 cursor-pointer w-64\"\n :class=\"{ 'bg-gray-300': thread._id === chatThreadId }\"\n >\n {{ thread.title || 'Untitled Thread' }}\n </li>\n </ul>\n </aside>\n\n <!-- Main Chat Area -->\n <main class=\"flex-1 flex flex-col\">\n <div class=\"flex-1 overflow-y-auto p-6 space-y-4\" ref=\"messagesContainer\">\n <ul role=\"list\" class=\"space-y-4\">\n <div v-if=\"true\">\n <div class=\"flex items-center justify-center py-3 mb-4\">\n <div class=\"bg-gray-300 h-px flex-grow max-w-xs\"></div>\n <p class=\"mx-4 text-sm font-medium text-gray-500\">This is the beginning of the message thread</p>\n <div class=\"bg-gray-300 h-px flex-grow max-w-xs\"></div>\n </div>\n </div>\n <li v-for=\"message in chatMessages\" :key=\"message._id\">\n <chat-message :message=\"message\"></chat-message>\n </li>\n </ul>\n </div>\n\n\n <!-- Input Area -->\n <div class=\"border-t p-4\">\n <form @submit.prevent=\"sendMessage\" :disabled=\"sendingMessage\" class=\"flex gap-2 items-end justify-end\">\n <textarea\n v-model=\"newMessage\"\n placeholder=\"Ask something...\"\n class=\"flex-1 border rounded px-4 py-2 resize-none overflow-y-auto\"\n rows=\"1\"\n ref=\"messageInput\"\n @input=\"adjustTextareaHeight\"\n @keydown.enter.exact.prevent=\"handleEnter\"\n ></textarea>\n <button class=\"bg-blue-600 text-white px-4 h-[42px] rounded disabled:bg-gray-600\" :disabled=\"sendingMessage\">\n <svg v-if=\"sendingMessage\" style=\"height: 1em\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n <g>\n <circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" opacity=\"0.3\" />\n <path d=\"M12 2a10 10 0 0 1 10 10\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\">\n <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 12 12\" to=\"360 12 12\" dur=\"1s\" repeatCount=\"indefinite\" />\n </path>\n </g>\n </svg>\n <span v-else>Send</span>\n </button>\n </form>\n </div>\n </main>\n</div>\n";
4042
4055
 
4043
4056
  /***/ }),
4044
4057
 
@@ -14673,7 +14686,7 @@ var bson = /*#__PURE__*/Object.freeze({
14673
14686
  /***/ ((module) => {
14674
14687
 
14675
14688
  "use strict";
14676
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.105","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.0.26","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","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"}}');
14689
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.107","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.0.26","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","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"}}');
14677
14690
 
14678
14691
  /***/ })
14679
14692
 
@@ -1,4 +1,4 @@
1
- <div class="flex" style="height: calc(100vh - 55px)">
1
+ <div class="flex" style="height: calc(100vh - 55px); height: calc(100dvh - 55px)">
2
2
  <div class="fixed top-[65px] cursor-pointer bg-gray-100 rounded-r-md z-10" @click="hideSidebar = false">
3
3
  <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>
4
4
  </div>
@@ -58,41 +58,47 @@ module.exports = app => app.component('models', {
58
58
  this.currentModel = this.model;
59
59
  },
60
60
  beforeDestroy() {
61
- document.removeEventListener('scroll', () => this.onScroll(), true);
61
+ document.removeEventListener('scroll', this.onScroll, true);
62
+ window.removeEventListener('popstate', this.onPopState, true);
62
63
  },
63
64
  async mounted() {
64
- document.addEventListener('scroll', () => this.onScroll(), true);
65
+ this.onScroll = () => this.checkIfScrolledToBottom();
66
+ document.addEventListener('scroll', this.onScroll, true);
67
+ this.onPopState = () => this.initSearchFromUrl();
68
+ window.addEventListener('popstate', this.onPopState, true);
65
69
  this.models = await api.Model.listModels().then(res => res.models);
66
70
  if (this.currentModel == null && this.models.length > 0) {
67
71
  this.currentModel = this.models[0];
68
72
  }
69
73
 
70
- this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
71
- if (this.$route.query?.search) {
72
- this.searchText = this.$route.query.search;
73
- this.filter = eval(`(${this.$route.query.search})`);
74
- this.filter = EJSON.stringify(this.filter);
75
- }
76
- if (this.$route.query?.sort) {
77
- const sort = eval(`(${this.$route.query.sort})`);
78
- const path = Object.keys(sort)[0];
79
- const num = Object.values(sort)[0];
80
- this.sortDocs(num, path);
81
- }
82
-
83
-
84
- if (this.currentModel != null) {
85
- await this.getDocuments();
86
- }
87
- if (this.$route.query?.fields) {
88
- const filter = this.$route.query.fields.split(',');
89
- this.filteredPaths = this.filteredPaths.filter(x => filter.includes(x.path));
90
- }
91
-
92
-
93
- this.status = 'loaded';
74
+ await this.initSearchFromUrl();
94
75
  },
95
76
  methods: {
77
+ async initSearchFromUrl() {
78
+ this.status = 'loading';
79
+ this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
80
+ if (this.$route.query?.search) {
81
+ this.searchText = this.$route.query.search;
82
+ this.filter = eval(`(${this.$route.query.search})`);
83
+ this.filter = EJSON.stringify(this.filter);
84
+ }
85
+ if (this.$route.query?.sort) {
86
+ const sort = eval(`(${this.$route.query.sort})`);
87
+ const path = Object.keys(sort)[0];
88
+ const num = Object.values(sort)[0];
89
+ this.sortDocs(num, path);
90
+ }
91
+
92
+
93
+ if (this.currentModel != null) {
94
+ await this.getDocuments();
95
+ }
96
+ if (this.$route.query?.fields) {
97
+ const filter = this.$route.query.fields.split(',');
98
+ this.filteredPaths = this.filteredPaths.filter(x => filter.includes(x.path));
99
+ }
100
+ this.status = 'loaded';
101
+ },
96
102
  async dropIndex(name) {
97
103
  const { mongoDBIndexes } = await api.Model.dropIndex({ model: this.currentModel, name });
98
104
  this.mongoDBIndexes = mongoDBIndexes;
@@ -142,7 +148,7 @@ module.exports = app => app.component('models', {
142
148
  }
143
149
  return filteredDoc;
144
150
  },
145
- async onScroll() {
151
+ async checkIfScrolledToBottom() {
146
152
  if (this.status === 'loading' || this.loadedAllDocs) {
147
153
  return;
148
154
  }
@@ -185,13 +191,20 @@ module.exports = app => app.component('models', {
185
191
  this.filter = eval(`(${this.searchText})`);
186
192
  this.filter = EJSON.stringify(this.filter);
187
193
  this.query.search = this.searchText;
188
- this.$router.push({ query: this.query });
194
+ const query = this.query;
195
+ const newUrl = this.$router.resolve({ query }).href;
196
+ window.history.pushState(null, '', newUrl);
189
197
  } else {
190
198
  this.filter = {};
191
199
  delete this.query.search;
192
- this.$router.push({ query: this.query });
200
+ const query = this.query;
201
+ const newUrl = this.$router.resolve({ query }).href;
202
+ window.history.pushState(null, '', newUrl);
193
203
  }
204
+ this.documents = [];
205
+ this.status = 'loading';
194
206
  await this.loadMoreDocuments();
207
+ this.status = 'loaded';
195
208
  },
196
209
  async openIndexModal() {
197
210
  this.shouldShowIndexModal = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.0.105",
3
+ "version": "0.0.107",
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": {