@mongoosejs/studio 0.2.6 → 0.2.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.
@@ -704,6 +704,14 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
704
704
  updateDocument: function updateDocument(params) {
705
705
  return client.post('', { action: 'Model.updateDocument', ...params }).then(res => res.data);
706
706
  },
707
+ createChatMessage(params) {
708
+ return client.post('', { action: 'Model.createChatMessage', ...params }).then(res => res.data);
709
+ },
710
+ streamChatMessage: async function* streamChatMessage(params) {
711
+ // Don't stream on Next.js or Netlify for now.
712
+ const data = await client.post('', { action: 'Model.createChatMessage', ...params }).then(res => res.data);
713
+ yield { textPart: data.text };
714
+ },
707
715
  updateDocuments: function updateDocuments(params) {
708
716
  return client.post('', { action: 'Model.updateDocuments', ...params }).then(res => res.data);
709
717
  }
@@ -807,6 +815,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
807
815
  createChart: function(params) {
808
816
  return client.post('/Model/createChart', params).then(res => res.data);
809
817
  },
818
+ createChatMessage: function(params) {
819
+ return client.post('/Model/createChatMessage', params).then(res => res.data);
820
+ },
810
821
  createDocument: function(params) {
811
822
  return client.post('/Model/createDocument', params).then(res => res.data);
812
823
  },
@@ -913,6 +924,55 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
913
924
  updateDocument: function updateDocument(params) {
914
925
  return client.post('/Model/updateDocument', params).then(res => res.data);
915
926
  },
927
+ streamChatMessage: async function* streamChatMessage(params) {
928
+ const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
929
+ const url = window.MONGOOSE_STUDIO_CONFIG.baseURL + '/Model/streamChatMessage?' + new URLSearchParams(params).toString();
930
+
931
+ const response = await fetch(url, {
932
+ method: 'GET',
933
+ headers: {
934
+ Authorization: `${accessToken}`,
935
+ Accept: 'text/event-stream'
936
+ }
937
+ });
938
+
939
+ if (!response.ok) {
940
+ throw new Error(`HTTP error! Status: ${response.status}`);
941
+ }
942
+
943
+ const reader = response.body.getReader();
944
+ const decoder = new TextDecoder('utf-8');
945
+ let buffer = '';
946
+
947
+ while (true) {
948
+ const { done, value } = await reader.read();
949
+ if (done) break;
950
+ buffer += decoder.decode(value, { stream: true });
951
+
952
+ let eventEnd;
953
+ while ((eventEnd = buffer.indexOf('\n\n')) !== -1) {
954
+ const eventStr = buffer.slice(0, eventEnd);
955
+ buffer = buffer.slice(eventEnd + 2);
956
+
957
+ // Parse SSE event
958
+ const lines = eventStr.split('\n');
959
+ let data = '';
960
+ for (const line of lines) {
961
+ if (line.startsWith('data:')) {
962
+ data += line.slice(5).trim();
963
+ }
964
+ }
965
+ if (data) {
966
+ try {
967
+ yield JSON.parse(data);
968
+ } catch (err) {
969
+ // If not JSON, yield as string
970
+ yield data;
971
+ }
972
+ }
973
+ }
974
+ }
975
+ },
916
976
  updateDocuments: function updateDocument(params) {
917
977
  return client.post('/Model/updateDocuments', params).then(res => res.data);
918
978
  }
@@ -1869,7 +1929,7 @@ module.exports = "";
1869
1929
  (module) {
1870
1930
 
1871
1931
  "use strict";
1872
- module.exports = "<div>\n <div class=\"mb-2\">\n <textarea class=\"border border-gray-200 p-2 h-[300px] w-full\" ref=\"codeEditor\"></textarea>\n </div>\n <button @click=\"createDocument()\" 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\">Submit</button>\n <div v-if=\"errors.length > 0\" class=\"rounded-md bg-red-50 p-4 mt-1\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">There were {{errors.length}} errors with your submission</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n <ul role=\"list\" class=\"list-disc space-y-1 pl-5\">\n <li v-for=\"error in errors\">\n {{error}}\n </li>\n </ul>\n </div>\n </div>\n </div>\n </div>\n</div>\n";
1932
+ module.exports = "<div>\n <div class=\"mb-4\">\n <label class=\"block text-sm font-bold text-gray-900\">AI Mode</label>\n <div class=\"mt-2 flex flex-col gap-2 sm:flex-row sm:items-center\">\n <input\n v-model=\"aiPrompt\"\n type=\"text\"\n placeholder=\"Describe the document you'd like to create...\"\n @keydown.enter.prevent=\"requestAiSuggestion()\"\n class=\"w-full rounded-md border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-ultramarine-500 focus:outline-none focus:ring-1 focus:ring-ultramarine-500\"\n />\n <button\n @click=\"requestAiSuggestion()\"\n :disabled=\"aiStreaming || !aiPrompt.trim()\"\n class=\"inline-flex items-center justify-center rounded-md bg-ultramarine-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {{ aiStreaming ? 'Generating...' : 'Generate' }}\n </button>\n </div>\n <p class=\"mt-2 text-xs text-gray-500\">Use AI to draft the document. You can accept or reject the suggestion once it finishes.</p>\n <div v-if=\"aiSuggestionReady\" class=\"mt-3 flex flex-wrap gap-2\">\n <button\n @click=\"acceptAiSuggestion()\"\n class=\"rounded-md bg-emerald-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-emerald-500\"\n >\n Accept suggestion\n </button>\n <button\n @click=\"rejectAiSuggestion()\"\n class=\"rounded-md bg-gray-100 px-2.5 py-1.5 text-sm font-semibold text-gray-700 shadow-sm hover:bg-gray-200\"\n >\n Reject suggestion\n </button>\n </div>\n </div>\n <div class=\"mb-2\">\n <label class=\"block text-sm font-bold text-gray-900\">Document to Create</label>\n <textarea class=\"border border-gray-200 p-2 h-[300px] w-full mt-2\" ref=\"codeEditor\"></textarea>\n </div>\n <button @click=\"createDocument()\" 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\">Submit</button>\n <div v-if=\"errors.length > 0\" class=\"rounded-md bg-red-50 p-4 mt-1\">\n <div class=\"flex\">\n <div class=\"flex-shrink-0\">\n <svg class=\"h-5 w-5 text-red-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div class=\"ml-3\">\n <h3 class=\"text-sm font-medium text-red-800\">There were {{errors.length}} errors with your submission</h3>\n <div class=\"mt-2 text-sm text-red-700\">\n <ul role=\"list\" class=\"list-disc space-y-1 pl-5\">\n <li v-for=\"error in errors\">\n {{error}}\n </li>\n </ul>\n </div>\n </div>\n </div>\n </div>\n</div>\n";
1873
1933
 
1874
1934
  /***/ },
1875
1935
 
@@ -1905,10 +1965,60 @@ module.exports = app => app.component('create-document', {
1905
1965
  return {
1906
1966
  documentData: '',
1907
1967
  editor: null,
1908
- errors: []
1968
+ errors: [],
1969
+ aiPrompt: '',
1970
+ aiSuggestion: '',
1971
+ aiOriginalDocument: '',
1972
+ aiStreaming: false,
1973
+ aiSuggestionReady: false
1909
1974
  };
1910
1975
  },
1911
1976
  methods: {
1977
+ async requestAiSuggestion() {
1978
+ if (this.aiStreaming) {
1979
+ return;
1980
+ }
1981
+ const prompt = this.aiPrompt.trim();
1982
+ if (!prompt) {
1983
+ return;
1984
+ }
1985
+
1986
+ this.aiOriginalDocument = this.editor.getValue();
1987
+ this.aiSuggestion = '';
1988
+ this.aiSuggestionReady = false;
1989
+ this.aiStreaming = true;
1990
+
1991
+ try {
1992
+ for await (const event of api.Model.streamChatMessage({
1993
+ model: this.currentModel,
1994
+ content: prompt,
1995
+ documentData: this.aiOriginalDocument
1996
+ })) {
1997
+ if (event?.textPart) {
1998
+ this.aiSuggestion += event.textPart;
1999
+ this.editor.setValue(this.aiSuggestion);
2000
+ }
2001
+ }
2002
+ this.aiSuggestionReady = true;
2003
+ } catch (err) {
2004
+ this.editor.setValue(this.aiOriginalDocument);
2005
+ this.$toast.error('Failed to generate a document suggestion.');
2006
+ throw err;
2007
+ } finally {
2008
+ this.aiStreaming = false;
2009
+ }
2010
+ },
2011
+ acceptAiSuggestion() {
2012
+ this.aiSuggestionReady = false;
2013
+ this.aiSuggestion = '';
2014
+ this.aiOriginalDocument = '';
2015
+ },
2016
+ rejectAiSuggestion() {
2017
+ this.editor.setValue(this.aiOriginalDocument);
2018
+ this.aiSuggestionReady = false;
2019
+ this.aiSuggestion = '';
2020
+ this.aiOriginalDocument = '';
2021
+ },
1912
2022
  async createDocument() {
1913
2023
  const data = EJSON.serialize(eval(`(${this.editor.getValue()})`));
1914
2024
  try {
@@ -6781,7 +6891,7 @@ module.exports = ".models {\n position: relative;\n display: flex;\n flex-dir
6781
6891
  (module) {
6782
6892
 
6783
6893
  "use strict";
6784
- 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-1 ml-2 font-bold\">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 p-1\">\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 text-gray-700\"\n :class=\"model === currentModel ? 'bg-ultramarine-100 font-bold text-gray-900' : 'hover:bg-ultramarine-50'\">\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 bg-slate-50\" ref=\"documentsList\">\n <div class=\"relative h-[42px] z-10\">\n <div class=\"documents-menu bg-slate-50\">\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=\"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 <div class=\"relative\" v-show=\"!selectMultiple\" ref=\"actionsMenuContainer\" @keyup.esc.prevent=\"closeActionsMenu\">\n <button\n @click=\"toggleActionsMenu\"\n type=\"button\"\n aria-label=\"More actions\"\n class=\"rounded bg-white px-2 py-2 text-sm font-semibold text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Z\" />\n </svg>\n </button>\n <div\n v-if=\"showActionsMenu\"\n class=\"absolute right-0 mt-2 w-48 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none z-20\"\n >\n <div class=\"py-1\">\n <button\n @click=\"shouldShowExportModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100\"\n >\n Export\n </button>\n <button\n @click=\"shouldShowCreateModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100\"\n >\n Create\n </button>\n <button\n @click=\"openFieldSelection(); showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100\"\n >\n Projection\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100\"\n >\n Indexes\n </button>\n <button\n @click=\"openCollectionInfo\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100\"\n >\n Collection Info\n </button>\n <button\n @click=\"findOldestDocument\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100\"\n >\n Find oldest document\n </button>\n </div>\n </div>\n </div>\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 class=\"bg-slate-50\">\n <th v-for=\"path in filteredPaths\" @click=\"addPathFilter(path.path)\" class=\"cursor-pointer p-3\">\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\" class=\"bg-white hover:bg-slate-50\">\n <td v-for=\"schemaPath in filteredPaths\" class=\"p-3 cursor-pointer\" :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-2 p-1 mt-1\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative transition-colors rounded-md border border-slate-100',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:shadow-sm hover:border-slate-300 bg-white'\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\">&times;</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\">&times;</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 flex items-center gap-2\">\n <div>{{ index.name }}</div>\n <div v-if=\"isTTLIndex(index)\" class=\"rounded-full bg-ultramarine-100 px-2 py-0.5 text-xs font-semibold text-ultramarine-700\">\n TTL: {{ formatTTL(index.expireAfterSeconds) }}\n </div>\n </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=\"shouldShowCollectionInfoModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCollectionInfoModal = false\">&times;</div>\n <div class=\"text-xl font-bold mb-2\">Collection Info</div>\n <div v-if=\"!collectionInfo\" class=\"text-gray-600\">Loading collection details...</div>\n <div v-else class=\"space-y-3\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Documents</div>\n <div class=\"text-gray-900\">{{ formatNumber(collectionInfo.documentCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Indexes</div>\n <div class=\"text-gray-900\">{{ formatNumber(collectionInfo.indexCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Total Index Size</div>\n <div class=\"text-gray-900\">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Total Storage Size</div>\n <div class=\"text-gray-900\">{{ formatCollectionSize(collectionInfo.size) }}</div>\n </div>\n <div class=\"flex flex-col gap-1\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Collation</div>\n <div class=\"text-gray-900\">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>\n </div>\n <div v-if=\"collectionInfo.hasCollation\" class=\"rounded bg-gray-100 p-3 text-sm text-gray-800 overflow-x-auto\">\n <pre class=\"whitespace-pre-wrap\">{{ JSON.stringify(collectionInfo.collation, null, 2) }}</pre>\n </div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Capped</div>\n <div class=\"text-gray-900\">{{ collectionInfo.capped ? 'Yes' : 'No' }}</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];\">&times;</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;\">&times;</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;\">&times;</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;\">&times;</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";
6894
+ 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-1 ml-2 font-bold\">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 p-1\">\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 text-gray-700\"\n :class=\"model === currentModel ? 'bg-ultramarine-100 font-bold text-gray-900' : 'hover:bg-ultramarine-50'\">\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 bg-slate-50\" ref=\"documentsList\">\n <div class=\"relative h-[42px]\" style=\"z-index: 1000\">\n <div class=\"documents-menu bg-slate-50\">\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'\">{{documents.length}}/{{numDocuments === 1 ? numDocuments + ' document' : numDocuments + ' documents'}}</span>\n </div>\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 <div class=\"relative\" v-show=\"!selectMultiple\" ref=\"actionsMenuContainer\" @keyup.esc.prevent=\"closeActionsMenu\">\n <button\n @click=\"toggleActionsMenu\"\n type=\"button\"\n aria-label=\"More actions\"\n class=\"rounded bg-white px-2 py-2 text-sm font-semibold text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-5 h-5\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 6.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Zm0 6a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Z\" />\n </svg>\n </button>\n <div\n v-if=\"showActionsMenu\"\n class=\"absolute right-0 mt-2 w-48 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none z-20\"\n >\n <div class=\"py-1\">\n <button\n @click=\"shouldShowExportModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100\"\n >\n Export\n </button>\n <button\n @click=\"shouldShowCreateModal = true; showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100\"\n >\n Create\n </button>\n <button\n @click=\"openFieldSelection(); showActionsMenu = false\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100\"\n >\n Projection\n </button>\n <button\n @click=\"openIndexModal\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100\"\n >\n Indexes\n </button>\n <button\n @click=\"openCollectionInfo\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100\"\n >\n Collection Info\n </button>\n <button\n @click=\"findOldestDocument\"\n type=\"button\"\n class=\"block w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100\"\n >\n Find oldest document\n </button>\n </div>\n </div>\n </div>\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 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 <button\n @click=\"setOutputType('map')\"\n :disabled=\"geoJsonFields.length === 0\"\n type=\"button\"\n :title=\"geoJsonFields.length > 0 ? 'Map view' : 'No GeoJSON fields detected'\"\n class=\"relative -ml-px inline-flex items-center rounded-none rounded-r-md px-2 py-2 ring-1 ring-inset ring-gray-300 focus:z-10\"\n :class=\"[\n geoJsonFields.length === 0 ? 'text-gray-300 cursor-not-allowed bg-gray-100' : 'text-gray-400 hover:bg-gray-50',\n outputType === 'map' ? 'bg-gray-200' : (geoJsonFields.length > 0 ? 'bg-white' : '')\n ]\">\n <svg class=\"h-5 w-5\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 6.75V15m6-6v8.25m.503 3.498 4.875-2.437c.381-.19.622-.58.622-1.006V4.82c0-.836-.88-1.38-1.628-1.006l-3.869 1.934c-.317.159-.69.159-1.006 0L9.503 3.252a1.125 1.125 0 0 0-1.006 0L3.622 5.689C3.24 5.88 3 6.27 3 6.695V19.18c0 .836.88 1.38 1.628 1.006l3.869-1.934c.317-.159.69-.159 1.006 0l4.994 2.497c.317.158.69.158 1.006 0Z\" />\n </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 class=\"bg-slate-50\">\n <th v-for=\"path in filteredPaths\" @click=\"addPathFilter(path.path)\" class=\"cursor-pointer p-3\">\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\" class=\"bg-white hover:bg-slate-50\">\n <td v-for=\"schemaPath in filteredPaths\" class=\"p-3 cursor-pointer\" :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-2 p-1 mt-1\">\n <div\n v-for=\"document in documents\"\n :key=\"document._id\"\n @click=\"handleDocumentContainerClick(document, $event)\"\n :class=\"[\n 'group relative transition-colors rounded-md border border-slate-100',\n selectedDocuments.some(x => x._id.toString() === document._id.toString()) ? 'bg-blue-200' : 'hover:shadow-sm hover:border-slate-300 bg-white'\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-else-if=\"outputType === 'map'\" class=\"flex flex-col h-full\">\n <div class=\"p-2 bg-white border-b flex items-center gap-2\">\n <label class=\"text-sm font-medium text-gray-700\">GeoJSON Field:</label>\n <select\n :value=\"selectedGeoField\"\n @change=\"setSelectedGeoField($event.target.value)\"\n class=\"rounded-md border border-gray-300 py-1 px-2 text-sm focus:border-ultramarine-500 focus:ring-ultramarine-500\"\n >\n <option v-for=\"field in geoJsonFields\" :key=\"field.path\" :value=\"field.path\">\n {{ field.label }}\n </option>\n </select>\n <async-button\n @click=\"loadMoreDocuments\"\n :disabled=\"loadedAllDocs\"\n type=\"button\"\n class=\"rounded px-2 py-1 text-xs font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\"\n :class=\"loadedAllDocs ? 'bg-gray-400 cursor-not-allowed' : 'bg-ultramarine-600 hover:bg-ultramarine-500'\"\n >\n Load more\n </async-button>\n </div>\n <div class=\"flex-1 min-h-[400px]\" ref=\"modelsMap\"></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\">&times;</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\">&times;</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 flex items-center gap-2\">\n <div>{{ index.name }}</div>\n <div v-if=\"isTTLIndex(index)\" class=\"rounded-full bg-ultramarine-100 px-2 py-0.5 text-xs font-semibold text-ultramarine-700\">\n TTL: {{ formatTTL(index.expireAfterSeconds) }}\n </div>\n </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=\"shouldShowCollectionInfoModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCollectionInfoModal = false\">&times;</div>\n <div class=\"text-xl font-bold mb-2\">Collection Info</div>\n <div v-if=\"!collectionInfo\" class=\"text-gray-600\">Loading collection details...</div>\n <div v-else class=\"space-y-3\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Documents</div>\n <div class=\"text-gray-900\">{{ formatNumber(collectionInfo.documentCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Indexes</div>\n <div class=\"text-gray-900\">{{ formatNumber(collectionInfo.indexCount) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Total Index Size</div>\n <div class=\"text-gray-900\">{{ formatCollectionSize(collectionInfo.totalIndexSize) }}</div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Total Storage Size</div>\n <div class=\"text-gray-900\">{{ formatCollectionSize(collectionInfo.size) }}</div>\n </div>\n <div class=\"flex flex-col gap-1\">\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Collation</div>\n <div class=\"text-gray-900\">{{ collectionInfo.hasCollation ? 'Yes' : 'No' }}</div>\n </div>\n <div v-if=\"collectionInfo.hasCollation\" class=\"rounded bg-gray-100 p-3 text-sm text-gray-800 overflow-x-auto\">\n <pre class=\"whitespace-pre-wrap\">{{ JSON.stringify(collectionInfo.collation, null, 2) }}</pre>\n </div>\n </div>\n <div class=\"flex justify-between gap-4\">\n <div class=\"font-semibold text-gray-700\">Capped</div>\n <div class=\"text-gray-900\">{{ collectionInfo.capped ? 'Yes' : 'No' }}</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];\">&times;</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;\">&times;</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;\">&times;</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;\">&times;</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";
6785
6895
 
6786
6896
  /***/ },
6787
6897
 
@@ -6794,15 +6904,19 @@ module.exports = "<div class=\"models flex\" style=\"height: calc(100vh - 55px);
6794
6904
  "use strict";
6795
6905
 
6796
6906
 
6907
+ /* global L */
6908
+
6797
6909
  const api = __webpack_require__(/*! ../api */ "./frontend/src/api.js");
6798
6910
  const template = __webpack_require__(/*! ./models.html */ "./frontend/src/models/models.html");
6799
6911
  const mpath = __webpack_require__(/*! mpath */ "./node_modules/mpath/index.js");
6912
+ const xss = __webpack_require__(/*! xss */ "./node_modules/xss/lib/index.js");
6800
6913
 
6801
6914
  const appendCSS = __webpack_require__(/*! ../appendCSS */ "./frontend/src/appendCSS.js");
6802
6915
  appendCSS(__webpack_require__(/*! ./models.css */ "./frontend/src/models/models.css"));
6803
6916
 
6804
6917
  const limit = 20;
6805
6918
  const OUTPUT_TYPE_STORAGE_KEY = 'studio:model-output-type';
6919
+ const SELECTED_GEO_FIELD_STORAGE_KEY = 'studio:model-selected-geo-field';
6806
6920
 
6807
6921
  module.exports = app => app.component('models', {
6808
6922
  template: template,
@@ -6836,7 +6950,10 @@ module.exports = app => app.component('models', {
6836
6950
  query: {},
6837
6951
  scrollHeight: 0,
6838
6952
  interval: null,
6839
- outputType: 'table', // json, table
6953
+ outputType: 'table', // json, table, map
6954
+ selectedGeoField: null,
6955
+ mapInstance: null,
6956
+ mapLayer: null,
6840
6957
  hideSidebar: null,
6841
6958
  lastSelectedIndex: null,
6842
6959
  error: null,
@@ -6846,11 +6963,13 @@ module.exports = app => app.component('models', {
6846
6963
  created() {
6847
6964
  this.currentModel = this.model;
6848
6965
  this.loadOutputPreference();
6966
+ this.loadSelectedGeoField();
6849
6967
  },
6850
6968
  beforeDestroy() {
6851
6969
  document.removeEventListener('scroll', this.onScroll, true);
6852
6970
  window.removeEventListener('popstate', this.onPopState, true);
6853
6971
  document.removeEventListener('click', this.onOutsideActionsMenuClick, true);
6972
+ this.destroyMap();
6854
6973
  },
6855
6974
  async mounted() {
6856
6975
  this.onScroll = () => this.checkIfScrolledToBottom();
@@ -6882,6 +7001,37 @@ module.exports = app => app.component('models', {
6882
7001
 
6883
7002
  await this.initSearchFromUrl();
6884
7003
  },
7004
+ watch: {
7005
+ documents: {
7006
+ handler() {
7007
+ if (this.outputType === 'map' && this.mapInstance) {
7008
+ this.$nextTick(() => {
7009
+ this.updateMapFeatures();
7010
+ });
7011
+ }
7012
+ },
7013
+ deep: true
7014
+ },
7015
+ geoJsonFields: {
7016
+ handler(newFields) {
7017
+ // Switch off map view if map is selected but no GeoJSON fields available
7018
+ if (this.outputType === 'map' && newFields.length === 0) {
7019
+ this.setOutputType('json');
7020
+ return;
7021
+ }
7022
+ // Auto-select first field if current selection is not valid
7023
+ if (this.outputType === 'map' && newFields.length > 0) {
7024
+ const isCurrentValid = newFields.some(f => f.path === this.selectedGeoField);
7025
+ if (!isCurrentValid) {
7026
+ this.selectedGeoField = newFields[0].path;
7027
+ this.$nextTick(() => {
7028
+ this.updateMapFeatures();
7029
+ });
7030
+ }
7031
+ }
7032
+ }
7033
+ }
7034
+ },
6885
7035
  computed: {
6886
7036
  referenceMap() {
6887
7037
  const map = {};
@@ -6891,6 +7041,56 @@ module.exports = app => app.component('models', {
6891
7041
  }
6892
7042
  }
6893
7043
  return map;
7044
+ },
7045
+ geoJsonFields() {
7046
+ // Find schema paths that look like GeoJSON fields
7047
+ // GeoJSON fields have nested 'type' and 'coordinates' properties
7048
+ const geoFields = [];
7049
+ const pathsByPrefix = {};
7050
+
7051
+ // Group paths by their parent prefix
7052
+ for (const schemaPath of this.schemaPaths) {
7053
+ const path = schemaPath.path;
7054
+ const parts = path.split('.');
7055
+ if (parts.length >= 2) {
7056
+ const parent = parts.slice(0, -1).join('.');
7057
+ const child = parts[parts.length - 1];
7058
+ if (!pathsByPrefix[parent]) {
7059
+ pathsByPrefix[parent] = {};
7060
+ }
7061
+ pathsByPrefix[parent][child] = schemaPath;
7062
+ }
7063
+ }
7064
+
7065
+ // Check which parents have both 'type' and 'coordinates' children
7066
+ for (const [parent, children] of Object.entries(pathsByPrefix)) {
7067
+ if (children.type && children.coordinates) {
7068
+ geoFields.push({
7069
+ path: parent,
7070
+ label: parent
7071
+ });
7072
+ }
7073
+ }
7074
+
7075
+ // Also check for Embedded/Mixed fields that might contain GeoJSON
7076
+ // by looking at actual document data
7077
+ for (const schemaPath of this.schemaPaths) {
7078
+ if (schemaPath.instance === 'Embedded' || schemaPath.instance === 'Mixed') {
7079
+ // Check if any document has this field with GeoJSON structure
7080
+ const hasGeoJsonData = this.documents.some(doc => {
7081
+ const value = mpath.get(schemaPath.path, doc);
7082
+ return this.isGeoJsonValue(value);
7083
+ });
7084
+ if (hasGeoJsonData && !geoFields.find(f => f.path === schemaPath.path)) {
7085
+ geoFields.push({
7086
+ path: schemaPath.path,
7087
+ label: schemaPath.path
7088
+ });
7089
+ }
7090
+ }
7091
+ }
7092
+
7093
+ return geoFields;
6894
7094
  }
6895
7095
  },
6896
7096
  methods: {
@@ -6899,18 +7099,170 @@ module.exports = app => app.component('models', {
6899
7099
  return;
6900
7100
  }
6901
7101
  const storedPreference = window.localStorage.getItem(OUTPUT_TYPE_STORAGE_KEY);
6902
- if (storedPreference === 'json' || storedPreference === 'table') {
7102
+ if (storedPreference === 'json' || storedPreference === 'table' || storedPreference === 'map') {
6903
7103
  this.outputType = storedPreference;
6904
7104
  }
6905
7105
  },
7106
+ loadSelectedGeoField() {
7107
+ if (typeof window === 'undefined' || !window.localStorage) {
7108
+ return;
7109
+ }
7110
+ const storedField = window.localStorage.getItem(SELECTED_GEO_FIELD_STORAGE_KEY);
7111
+ if (storedField) {
7112
+ this.selectedGeoField = storedField;
7113
+ }
7114
+ },
6906
7115
  setOutputType(type) {
6907
- if (type !== 'json' && type !== 'table') {
7116
+ if (type !== 'json' && type !== 'table' && type !== 'map') {
6908
7117
  return;
6909
7118
  }
6910
7119
  this.outputType = type;
6911
7120
  if (typeof window !== 'undefined' && window.localStorage) {
6912
7121
  window.localStorage.setItem(OUTPUT_TYPE_STORAGE_KEY, type);
6913
7122
  }
7123
+ if (type === 'map') {
7124
+ this.$nextTick(() => {
7125
+ this.initMap();
7126
+ });
7127
+ } else {
7128
+ this.destroyMap();
7129
+ }
7130
+ },
7131
+ setSelectedGeoField(field) {
7132
+ this.selectedGeoField = field;
7133
+ if (typeof window !== 'undefined' && window.localStorage) {
7134
+ window.localStorage.setItem(SELECTED_GEO_FIELD_STORAGE_KEY, field);
7135
+ }
7136
+ if (this.outputType === 'map') {
7137
+ this.$nextTick(() => {
7138
+ this.updateMapFeatures();
7139
+ });
7140
+ }
7141
+ },
7142
+ isGeoJsonValue(value) {
7143
+ return value != null &&
7144
+ typeof value === 'object' &&
7145
+ !Array.isArray(value) &&
7146
+ Object.prototype.hasOwnProperty.call(value, 'type') &&
7147
+ typeof value.type === 'string' &&
7148
+ Object.prototype.hasOwnProperty.call(value, 'coordinates') &&
7149
+ Array.isArray(value.coordinates);
7150
+ },
7151
+ initMap() {
7152
+ if (typeof L === 'undefined') {
7153
+ console.error('Leaflet (L) is not defined');
7154
+ return;
7155
+ }
7156
+ if (!this.$refs.modelsMap) {
7157
+ return;
7158
+ }
7159
+ if (this.mapInstance) {
7160
+ this.updateMapFeatures();
7161
+ return;
7162
+ }
7163
+
7164
+ const mapElement = this.$refs.modelsMap;
7165
+ mapElement.style.setProperty('height', '100%', 'important');
7166
+ mapElement.style.setProperty('min-height', '400px', 'important');
7167
+ mapElement.style.setProperty('width', '100%', 'important');
7168
+
7169
+ this.mapInstance = L.map(this.$refs.modelsMap).setView([0, 0], 2);
7170
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
7171
+ attribution: '&copy; OpenStreetMap contributors'
7172
+ }).addTo(this.mapInstance);
7173
+
7174
+ this.$nextTick(() => {
7175
+ if (this.mapInstance) {
7176
+ this.mapInstance.invalidateSize();
7177
+ this.updateMapFeatures();
7178
+ }
7179
+ });
7180
+ },
7181
+ destroyMap() {
7182
+ if (this.mapLayer) {
7183
+ this.mapLayer.remove();
7184
+ this.mapLayer = null;
7185
+ }
7186
+ if (this.mapInstance) {
7187
+ this.mapInstance.remove();
7188
+ this.mapInstance = null;
7189
+ }
7190
+ },
7191
+ updateMapFeatures() {
7192
+ if (!this.mapInstance) {
7193
+ return;
7194
+ }
7195
+
7196
+ // Remove existing layer
7197
+ if (this.mapLayer) {
7198
+ this.mapLayer.remove();
7199
+ this.mapLayer = null;
7200
+ }
7201
+
7202
+ // Auto-select first geoJSON field if none selected
7203
+ if (!this.selectedGeoField && this.geoJsonFields.length > 0) {
7204
+ this.selectedGeoField = this.geoJsonFields[0].path;
7205
+ }
7206
+
7207
+ if (!this.selectedGeoField) {
7208
+ return;
7209
+ }
7210
+
7211
+ // Build GeoJSON FeatureCollection from documents
7212
+ const features = [];
7213
+ for (const doc of this.documents) {
7214
+ const geoValue = mpath.get(this.selectedGeoField, doc);
7215
+ if (this.isGeoJsonValue(geoValue)) {
7216
+ features.push({
7217
+ type: 'Feature',
7218
+ geometry: geoValue,
7219
+ properties: {
7220
+ _id: doc._id,
7221
+ documentId: doc._id
7222
+ }
7223
+ });
7224
+ }
7225
+ }
7226
+
7227
+ if (features.length === 0) {
7228
+ return;
7229
+ }
7230
+
7231
+ const featureCollection = {
7232
+ type: 'FeatureCollection',
7233
+ features: features
7234
+ };
7235
+
7236
+ // Add layer with click handler for popups
7237
+ this.mapLayer = L.geoJSON(featureCollection, {
7238
+ style: {
7239
+ color: '#3388ff',
7240
+ weight: 2,
7241
+ opacity: 0.8,
7242
+ fillOpacity: 0.3
7243
+ },
7244
+ pointToLayer: (feature, latlng) => {
7245
+ return L.marker(latlng);
7246
+ },
7247
+ onEachFeature: (feature, layer) => {
7248
+ const docId = feature.properties._id;
7249
+ const docUrl = `#/model/${this.currentModel}/document/${xss(docId)}`;
7250
+ const popupContent = `
7251
+ <div style="min-width: 150px;">
7252
+ <div style="font-weight: bold; margin-bottom: 8px;">Document</div>
7253
+ <div style="font-family: monospace; font-size: 12px; word-break: break-all; margin-bottom: 8px;">${docId}</div>
7254
+ <a href="${docUrl}" style="color: #3388ff; text-decoration: underline;">Open Document</a>
7255
+ </div>
7256
+ `;
7257
+ layer.bindPopup(popupContent);
7258
+ }
7259
+ }).addTo(this.mapInstance);
7260
+
7261
+ // Fit bounds to show all features
7262
+ const bounds = this.mapLayer.getBounds();
7263
+ if (bounds.isValid()) {
7264
+ this.mapInstance.fitBounds(bounds, { padding: [20, 20], maxZoom: 16 });
7265
+ }
6914
7266
  },
6915
7267
  buildDocumentFetchParams(options = {}) {
6916
7268
  const params = {
@@ -6964,6 +7316,13 @@ module.exports = app => app.component('models', {
6964
7316
  this.filteredPaths = this.filteredPaths.filter(x => filter.includes(x.path));
6965
7317
  }
6966
7318
  this.status = 'loaded';
7319
+
7320
+ // Initialize map if output type is map
7321
+ if (this.outputType === 'map') {
7322
+ this.$nextTick(() => {
7323
+ this.initMap();
7324
+ });
7325
+ }
6967
7326
  },
6968
7327
  async dropIndex(name) {
6969
7328
  const { mongoDBIndexes } = await api.Model.dropIndex({ model: this.currentModel, name });
@@ -29517,6 +29876,705 @@ var bson = /*#__PURE__*/Object.freeze({
29517
29876
  //# sourceMappingURL=bson.mjs.map
29518
29877
 
29519
29878
 
29879
+ /***/ },
29880
+
29881
+ /***/ "./node_modules/cssfilter/lib/css.js"
29882
+ /*!*******************************************!*\
29883
+ !*** ./node_modules/cssfilter/lib/css.js ***!
29884
+ \*******************************************/
29885
+ (module, __unused_webpack_exports, __webpack_require__) {
29886
+
29887
+ /**
29888
+ * cssfilter
29889
+ *
29890
+ * @author 老雷<leizongmin@gmail.com>
29891
+ */
29892
+
29893
+ var DEFAULT = __webpack_require__(/*! ./default */ "./node_modules/cssfilter/lib/default.js");
29894
+ var parseStyle = __webpack_require__(/*! ./parser */ "./node_modules/cssfilter/lib/parser.js");
29895
+ var _ = __webpack_require__(/*! ./util */ "./node_modules/cssfilter/lib/util.js");
29896
+
29897
+
29898
+ /**
29899
+ * 返回值是否为空
29900
+ *
29901
+ * @param {Object} obj
29902
+ * @return {Boolean}
29903
+ */
29904
+ function isNull (obj) {
29905
+ return (obj === undefined || obj === null);
29906
+ }
29907
+
29908
+ /**
29909
+ * 浅拷贝对象
29910
+ *
29911
+ * @param {Object} obj
29912
+ * @return {Object}
29913
+ */
29914
+ function shallowCopyObject (obj) {
29915
+ var ret = {};
29916
+ for (var i in obj) {
29917
+ ret[i] = obj[i];
29918
+ }
29919
+ return ret;
29920
+ }
29921
+
29922
+ /**
29923
+ * 创建CSS过滤器
29924
+ *
29925
+ * @param {Object} options
29926
+ * - {Object} whiteList
29927
+ * - {Function} onAttr
29928
+ * - {Function} onIgnoreAttr
29929
+ * - {Function} safeAttrValue
29930
+ */
29931
+ function FilterCSS (options) {
29932
+ options = shallowCopyObject(options || {});
29933
+ options.whiteList = options.whiteList || DEFAULT.whiteList;
29934
+ options.onAttr = options.onAttr || DEFAULT.onAttr;
29935
+ options.onIgnoreAttr = options.onIgnoreAttr || DEFAULT.onIgnoreAttr;
29936
+ options.safeAttrValue = options.safeAttrValue || DEFAULT.safeAttrValue;
29937
+ this.options = options;
29938
+ }
29939
+
29940
+ FilterCSS.prototype.process = function (css) {
29941
+ // 兼容各种奇葩输入
29942
+ css = css || '';
29943
+ css = css.toString();
29944
+ if (!css) return '';
29945
+
29946
+ var me = this;
29947
+ var options = me.options;
29948
+ var whiteList = options.whiteList;
29949
+ var onAttr = options.onAttr;
29950
+ var onIgnoreAttr = options.onIgnoreAttr;
29951
+ var safeAttrValue = options.safeAttrValue;
29952
+
29953
+ var retCSS = parseStyle(css, function (sourcePosition, position, name, value, source) {
29954
+
29955
+ var check = whiteList[name];
29956
+ var isWhite = false;
29957
+ if (check === true) isWhite = check;
29958
+ else if (typeof check === 'function') isWhite = check(value);
29959
+ else if (check instanceof RegExp) isWhite = check.test(value);
29960
+ if (isWhite !== true) isWhite = false;
29961
+
29962
+ // 如果过滤后 value 为空则直接忽略
29963
+ value = safeAttrValue(name, value);
29964
+ if (!value) return;
29965
+
29966
+ var opts = {
29967
+ position: position,
29968
+ sourcePosition: sourcePosition,
29969
+ source: source,
29970
+ isWhite: isWhite
29971
+ };
29972
+
29973
+ if (isWhite) {
29974
+
29975
+ var ret = onAttr(name, value, opts);
29976
+ if (isNull(ret)) {
29977
+ return name + ':' + value;
29978
+ } else {
29979
+ return ret;
29980
+ }
29981
+
29982
+ } else {
29983
+
29984
+ var ret = onIgnoreAttr(name, value, opts);
29985
+ if (!isNull(ret)) {
29986
+ return ret;
29987
+ }
29988
+
29989
+ }
29990
+ });
29991
+
29992
+ return retCSS;
29993
+ };
29994
+
29995
+
29996
+ module.exports = FilterCSS;
29997
+
29998
+
29999
+ /***/ },
30000
+
30001
+ /***/ "./node_modules/cssfilter/lib/default.js"
30002
+ /*!***********************************************!*\
30003
+ !*** ./node_modules/cssfilter/lib/default.js ***!
30004
+ \***********************************************/
30005
+ (__unused_webpack_module, exports) {
30006
+
30007
+ /**
30008
+ * cssfilter
30009
+ *
30010
+ * @author 老雷<leizongmin@gmail.com>
30011
+ */
30012
+
30013
+ function getDefaultWhiteList () {
30014
+ // 白名单值说明:
30015
+ // true: 允许该属性
30016
+ // Function: function (val) { } 返回true表示允许该属性,其他值均表示不允许
30017
+ // RegExp: regexp.test(val) 返回true表示允许该属性,其他值均表示不允许
30018
+ // 除上面列出的值外均表示不允许
30019
+ var whiteList = {};
30020
+
30021
+ whiteList['align-content'] = false; // default: auto
30022
+ whiteList['align-items'] = false; // default: auto
30023
+ whiteList['align-self'] = false; // default: auto
30024
+ whiteList['alignment-adjust'] = false; // default: auto
30025
+ whiteList['alignment-baseline'] = false; // default: baseline
30026
+ whiteList['all'] = false; // default: depending on individual properties
30027
+ whiteList['anchor-point'] = false; // default: none
30028
+ whiteList['animation'] = false; // default: depending on individual properties
30029
+ whiteList['animation-delay'] = false; // default: 0
30030
+ whiteList['animation-direction'] = false; // default: normal
30031
+ whiteList['animation-duration'] = false; // default: 0
30032
+ whiteList['animation-fill-mode'] = false; // default: none
30033
+ whiteList['animation-iteration-count'] = false; // default: 1
30034
+ whiteList['animation-name'] = false; // default: none
30035
+ whiteList['animation-play-state'] = false; // default: running
30036
+ whiteList['animation-timing-function'] = false; // default: ease
30037
+ whiteList['azimuth'] = false; // default: center
30038
+ whiteList['backface-visibility'] = false; // default: visible
30039
+ whiteList['background'] = true; // default: depending on individual properties
30040
+ whiteList['background-attachment'] = true; // default: scroll
30041
+ whiteList['background-clip'] = true; // default: border-box
30042
+ whiteList['background-color'] = true; // default: transparent
30043
+ whiteList['background-image'] = true; // default: none
30044
+ whiteList['background-origin'] = true; // default: padding-box
30045
+ whiteList['background-position'] = true; // default: 0% 0%
30046
+ whiteList['background-repeat'] = true; // default: repeat
30047
+ whiteList['background-size'] = true; // default: auto
30048
+ whiteList['baseline-shift'] = false; // default: baseline
30049
+ whiteList['binding'] = false; // default: none
30050
+ whiteList['bleed'] = false; // default: 6pt
30051
+ whiteList['bookmark-label'] = false; // default: content()
30052
+ whiteList['bookmark-level'] = false; // default: none
30053
+ whiteList['bookmark-state'] = false; // default: open
30054
+ whiteList['border'] = true; // default: depending on individual properties
30055
+ whiteList['border-bottom'] = true; // default: depending on individual properties
30056
+ whiteList['border-bottom-color'] = true; // default: current color
30057
+ whiteList['border-bottom-left-radius'] = true; // default: 0
30058
+ whiteList['border-bottom-right-radius'] = true; // default: 0
30059
+ whiteList['border-bottom-style'] = true; // default: none
30060
+ whiteList['border-bottom-width'] = true; // default: medium
30061
+ whiteList['border-collapse'] = true; // default: separate
30062
+ whiteList['border-color'] = true; // default: depending on individual properties
30063
+ whiteList['border-image'] = true; // default: none
30064
+ whiteList['border-image-outset'] = true; // default: 0
30065
+ whiteList['border-image-repeat'] = true; // default: stretch
30066
+ whiteList['border-image-slice'] = true; // default: 100%
30067
+ whiteList['border-image-source'] = true; // default: none
30068
+ whiteList['border-image-width'] = true; // default: 1
30069
+ whiteList['border-left'] = true; // default: depending on individual properties
30070
+ whiteList['border-left-color'] = true; // default: current color
30071
+ whiteList['border-left-style'] = true; // default: none
30072
+ whiteList['border-left-width'] = true; // default: medium
30073
+ whiteList['border-radius'] = true; // default: 0
30074
+ whiteList['border-right'] = true; // default: depending on individual properties
30075
+ whiteList['border-right-color'] = true; // default: current color
30076
+ whiteList['border-right-style'] = true; // default: none
30077
+ whiteList['border-right-width'] = true; // default: medium
30078
+ whiteList['border-spacing'] = true; // default: 0
30079
+ whiteList['border-style'] = true; // default: depending on individual properties
30080
+ whiteList['border-top'] = true; // default: depending on individual properties
30081
+ whiteList['border-top-color'] = true; // default: current color
30082
+ whiteList['border-top-left-radius'] = true; // default: 0
30083
+ whiteList['border-top-right-radius'] = true; // default: 0
30084
+ whiteList['border-top-style'] = true; // default: none
30085
+ whiteList['border-top-width'] = true; // default: medium
30086
+ whiteList['border-width'] = true; // default: depending on individual properties
30087
+ whiteList['bottom'] = false; // default: auto
30088
+ whiteList['box-decoration-break'] = true; // default: slice
30089
+ whiteList['box-shadow'] = true; // default: none
30090
+ whiteList['box-sizing'] = true; // default: content-box
30091
+ whiteList['box-snap'] = true; // default: none
30092
+ whiteList['box-suppress'] = true; // default: show
30093
+ whiteList['break-after'] = true; // default: auto
30094
+ whiteList['break-before'] = true; // default: auto
30095
+ whiteList['break-inside'] = true; // default: auto
30096
+ whiteList['caption-side'] = false; // default: top
30097
+ whiteList['chains'] = false; // default: none
30098
+ whiteList['clear'] = true; // default: none
30099
+ whiteList['clip'] = false; // default: auto
30100
+ whiteList['clip-path'] = false; // default: none
30101
+ whiteList['clip-rule'] = false; // default: nonzero
30102
+ whiteList['color'] = true; // default: implementation dependent
30103
+ whiteList['color-interpolation-filters'] = true; // default: auto
30104
+ whiteList['column-count'] = false; // default: auto
30105
+ whiteList['column-fill'] = false; // default: balance
30106
+ whiteList['column-gap'] = false; // default: normal
30107
+ whiteList['column-rule'] = false; // default: depending on individual properties
30108
+ whiteList['column-rule-color'] = false; // default: current color
30109
+ whiteList['column-rule-style'] = false; // default: medium
30110
+ whiteList['column-rule-width'] = false; // default: medium
30111
+ whiteList['column-span'] = false; // default: none
30112
+ whiteList['column-width'] = false; // default: auto
30113
+ whiteList['columns'] = false; // default: depending on individual properties
30114
+ whiteList['contain'] = false; // default: none
30115
+ whiteList['content'] = false; // default: normal
30116
+ whiteList['counter-increment'] = false; // default: none
30117
+ whiteList['counter-reset'] = false; // default: none
30118
+ whiteList['counter-set'] = false; // default: none
30119
+ whiteList['crop'] = false; // default: auto
30120
+ whiteList['cue'] = false; // default: depending on individual properties
30121
+ whiteList['cue-after'] = false; // default: none
30122
+ whiteList['cue-before'] = false; // default: none
30123
+ whiteList['cursor'] = false; // default: auto
30124
+ whiteList['direction'] = false; // default: ltr
30125
+ whiteList['display'] = true; // default: depending on individual properties
30126
+ whiteList['display-inside'] = true; // default: auto
30127
+ whiteList['display-list'] = true; // default: none
30128
+ whiteList['display-outside'] = true; // default: inline-level
30129
+ whiteList['dominant-baseline'] = false; // default: auto
30130
+ whiteList['elevation'] = false; // default: level
30131
+ whiteList['empty-cells'] = false; // default: show
30132
+ whiteList['filter'] = false; // default: none
30133
+ whiteList['flex'] = false; // default: depending on individual properties
30134
+ whiteList['flex-basis'] = false; // default: auto
30135
+ whiteList['flex-direction'] = false; // default: row
30136
+ whiteList['flex-flow'] = false; // default: depending on individual properties
30137
+ whiteList['flex-grow'] = false; // default: 0
30138
+ whiteList['flex-shrink'] = false; // default: 1
30139
+ whiteList['flex-wrap'] = false; // default: nowrap
30140
+ whiteList['float'] = false; // default: none
30141
+ whiteList['float-offset'] = false; // default: 0 0
30142
+ whiteList['flood-color'] = false; // default: black
30143
+ whiteList['flood-opacity'] = false; // default: 1
30144
+ whiteList['flow-from'] = false; // default: none
30145
+ whiteList['flow-into'] = false; // default: none
30146
+ whiteList['font'] = true; // default: depending on individual properties
30147
+ whiteList['font-family'] = true; // default: implementation dependent
30148
+ whiteList['font-feature-settings'] = true; // default: normal
30149
+ whiteList['font-kerning'] = true; // default: auto
30150
+ whiteList['font-language-override'] = true; // default: normal
30151
+ whiteList['font-size'] = true; // default: medium
30152
+ whiteList['font-size-adjust'] = true; // default: none
30153
+ whiteList['font-stretch'] = true; // default: normal
30154
+ whiteList['font-style'] = true; // default: normal
30155
+ whiteList['font-synthesis'] = true; // default: weight style
30156
+ whiteList['font-variant'] = true; // default: normal
30157
+ whiteList['font-variant-alternates'] = true; // default: normal
30158
+ whiteList['font-variant-caps'] = true; // default: normal
30159
+ whiteList['font-variant-east-asian'] = true; // default: normal
30160
+ whiteList['font-variant-ligatures'] = true; // default: normal
30161
+ whiteList['font-variant-numeric'] = true; // default: normal
30162
+ whiteList['font-variant-position'] = true; // default: normal
30163
+ whiteList['font-weight'] = true; // default: normal
30164
+ whiteList['grid'] = false; // default: depending on individual properties
30165
+ whiteList['grid-area'] = false; // default: depending on individual properties
30166
+ whiteList['grid-auto-columns'] = false; // default: auto
30167
+ whiteList['grid-auto-flow'] = false; // default: none
30168
+ whiteList['grid-auto-rows'] = false; // default: auto
30169
+ whiteList['grid-column'] = false; // default: depending on individual properties
30170
+ whiteList['grid-column-end'] = false; // default: auto
30171
+ whiteList['grid-column-start'] = false; // default: auto
30172
+ whiteList['grid-row'] = false; // default: depending on individual properties
30173
+ whiteList['grid-row-end'] = false; // default: auto
30174
+ whiteList['grid-row-start'] = false; // default: auto
30175
+ whiteList['grid-template'] = false; // default: depending on individual properties
30176
+ whiteList['grid-template-areas'] = false; // default: none
30177
+ whiteList['grid-template-columns'] = false; // default: none
30178
+ whiteList['grid-template-rows'] = false; // default: none
30179
+ whiteList['hanging-punctuation'] = false; // default: none
30180
+ whiteList['height'] = true; // default: auto
30181
+ whiteList['hyphens'] = false; // default: manual
30182
+ whiteList['icon'] = false; // default: auto
30183
+ whiteList['image-orientation'] = false; // default: auto
30184
+ whiteList['image-resolution'] = false; // default: normal
30185
+ whiteList['ime-mode'] = false; // default: auto
30186
+ whiteList['initial-letters'] = false; // default: normal
30187
+ whiteList['inline-box-align'] = false; // default: last
30188
+ whiteList['justify-content'] = false; // default: auto
30189
+ whiteList['justify-items'] = false; // default: auto
30190
+ whiteList['justify-self'] = false; // default: auto
30191
+ whiteList['left'] = false; // default: auto
30192
+ whiteList['letter-spacing'] = true; // default: normal
30193
+ whiteList['lighting-color'] = true; // default: white
30194
+ whiteList['line-box-contain'] = false; // default: block inline replaced
30195
+ whiteList['line-break'] = false; // default: auto
30196
+ whiteList['line-grid'] = false; // default: match-parent
30197
+ whiteList['line-height'] = false; // default: normal
30198
+ whiteList['line-snap'] = false; // default: none
30199
+ whiteList['line-stacking'] = false; // default: depending on individual properties
30200
+ whiteList['line-stacking-ruby'] = false; // default: exclude-ruby
30201
+ whiteList['line-stacking-shift'] = false; // default: consider-shifts
30202
+ whiteList['line-stacking-strategy'] = false; // default: inline-line-height
30203
+ whiteList['list-style'] = true; // default: depending on individual properties
30204
+ whiteList['list-style-image'] = true; // default: none
30205
+ whiteList['list-style-position'] = true; // default: outside
30206
+ whiteList['list-style-type'] = true; // default: disc
30207
+ whiteList['margin'] = true; // default: depending on individual properties
30208
+ whiteList['margin-bottom'] = true; // default: 0
30209
+ whiteList['margin-left'] = true; // default: 0
30210
+ whiteList['margin-right'] = true; // default: 0
30211
+ whiteList['margin-top'] = true; // default: 0
30212
+ whiteList['marker-offset'] = false; // default: auto
30213
+ whiteList['marker-side'] = false; // default: list-item
30214
+ whiteList['marks'] = false; // default: none
30215
+ whiteList['mask'] = false; // default: border-box
30216
+ whiteList['mask-box'] = false; // default: see individual properties
30217
+ whiteList['mask-box-outset'] = false; // default: 0
30218
+ whiteList['mask-box-repeat'] = false; // default: stretch
30219
+ whiteList['mask-box-slice'] = false; // default: 0 fill
30220
+ whiteList['mask-box-source'] = false; // default: none
30221
+ whiteList['mask-box-width'] = false; // default: auto
30222
+ whiteList['mask-clip'] = false; // default: border-box
30223
+ whiteList['mask-image'] = false; // default: none
30224
+ whiteList['mask-origin'] = false; // default: border-box
30225
+ whiteList['mask-position'] = false; // default: center
30226
+ whiteList['mask-repeat'] = false; // default: no-repeat
30227
+ whiteList['mask-size'] = false; // default: border-box
30228
+ whiteList['mask-source-type'] = false; // default: auto
30229
+ whiteList['mask-type'] = false; // default: luminance
30230
+ whiteList['max-height'] = true; // default: none
30231
+ whiteList['max-lines'] = false; // default: none
30232
+ whiteList['max-width'] = true; // default: none
30233
+ whiteList['min-height'] = true; // default: 0
30234
+ whiteList['min-width'] = true; // default: 0
30235
+ whiteList['move-to'] = false; // default: normal
30236
+ whiteList['nav-down'] = false; // default: auto
30237
+ whiteList['nav-index'] = false; // default: auto
30238
+ whiteList['nav-left'] = false; // default: auto
30239
+ whiteList['nav-right'] = false; // default: auto
30240
+ whiteList['nav-up'] = false; // default: auto
30241
+ whiteList['object-fit'] = false; // default: fill
30242
+ whiteList['object-position'] = false; // default: 50% 50%
30243
+ whiteList['opacity'] = false; // default: 1
30244
+ whiteList['order'] = false; // default: 0
30245
+ whiteList['orphans'] = false; // default: 2
30246
+ whiteList['outline'] = false; // default: depending on individual properties
30247
+ whiteList['outline-color'] = false; // default: invert
30248
+ whiteList['outline-offset'] = false; // default: 0
30249
+ whiteList['outline-style'] = false; // default: none
30250
+ whiteList['outline-width'] = false; // default: medium
30251
+ whiteList['overflow'] = false; // default: depending on individual properties
30252
+ whiteList['overflow-wrap'] = false; // default: normal
30253
+ whiteList['overflow-x'] = false; // default: visible
30254
+ whiteList['overflow-y'] = false; // default: visible
30255
+ whiteList['padding'] = true; // default: depending on individual properties
30256
+ whiteList['padding-bottom'] = true; // default: 0
30257
+ whiteList['padding-left'] = true; // default: 0
30258
+ whiteList['padding-right'] = true; // default: 0
30259
+ whiteList['padding-top'] = true; // default: 0
30260
+ whiteList['page'] = false; // default: auto
30261
+ whiteList['page-break-after'] = false; // default: auto
30262
+ whiteList['page-break-before'] = false; // default: auto
30263
+ whiteList['page-break-inside'] = false; // default: auto
30264
+ whiteList['page-policy'] = false; // default: start
30265
+ whiteList['pause'] = false; // default: implementation dependent
30266
+ whiteList['pause-after'] = false; // default: implementation dependent
30267
+ whiteList['pause-before'] = false; // default: implementation dependent
30268
+ whiteList['perspective'] = false; // default: none
30269
+ whiteList['perspective-origin'] = false; // default: 50% 50%
30270
+ whiteList['pitch'] = false; // default: medium
30271
+ whiteList['pitch-range'] = false; // default: 50
30272
+ whiteList['play-during'] = false; // default: auto
30273
+ whiteList['position'] = false; // default: static
30274
+ whiteList['presentation-level'] = false; // default: 0
30275
+ whiteList['quotes'] = false; // default: text
30276
+ whiteList['region-fragment'] = false; // default: auto
30277
+ whiteList['resize'] = false; // default: none
30278
+ whiteList['rest'] = false; // default: depending on individual properties
30279
+ whiteList['rest-after'] = false; // default: none
30280
+ whiteList['rest-before'] = false; // default: none
30281
+ whiteList['richness'] = false; // default: 50
30282
+ whiteList['right'] = false; // default: auto
30283
+ whiteList['rotation'] = false; // default: 0
30284
+ whiteList['rotation-point'] = false; // default: 50% 50%
30285
+ whiteList['ruby-align'] = false; // default: auto
30286
+ whiteList['ruby-merge'] = false; // default: separate
30287
+ whiteList['ruby-position'] = false; // default: before
30288
+ whiteList['shape-image-threshold'] = false; // default: 0.0
30289
+ whiteList['shape-outside'] = false; // default: none
30290
+ whiteList['shape-margin'] = false; // default: 0
30291
+ whiteList['size'] = false; // default: auto
30292
+ whiteList['speak'] = false; // default: auto
30293
+ whiteList['speak-as'] = false; // default: normal
30294
+ whiteList['speak-header'] = false; // default: once
30295
+ whiteList['speak-numeral'] = false; // default: continuous
30296
+ whiteList['speak-punctuation'] = false; // default: none
30297
+ whiteList['speech-rate'] = false; // default: medium
30298
+ whiteList['stress'] = false; // default: 50
30299
+ whiteList['string-set'] = false; // default: none
30300
+ whiteList['tab-size'] = false; // default: 8
30301
+ whiteList['table-layout'] = false; // default: auto
30302
+ whiteList['text-align'] = true; // default: start
30303
+ whiteList['text-align-last'] = true; // default: auto
30304
+ whiteList['text-combine-upright'] = true; // default: none
30305
+ whiteList['text-decoration'] = true; // default: none
30306
+ whiteList['text-decoration-color'] = true; // default: currentColor
30307
+ whiteList['text-decoration-line'] = true; // default: none
30308
+ whiteList['text-decoration-skip'] = true; // default: objects
30309
+ whiteList['text-decoration-style'] = true; // default: solid
30310
+ whiteList['text-emphasis'] = true; // default: depending on individual properties
30311
+ whiteList['text-emphasis-color'] = true; // default: currentColor
30312
+ whiteList['text-emphasis-position'] = true; // default: over right
30313
+ whiteList['text-emphasis-style'] = true; // default: none
30314
+ whiteList['text-height'] = true; // default: auto
30315
+ whiteList['text-indent'] = true; // default: 0
30316
+ whiteList['text-justify'] = true; // default: auto
30317
+ whiteList['text-orientation'] = true; // default: mixed
30318
+ whiteList['text-overflow'] = true; // default: clip
30319
+ whiteList['text-shadow'] = true; // default: none
30320
+ whiteList['text-space-collapse'] = true; // default: collapse
30321
+ whiteList['text-transform'] = true; // default: none
30322
+ whiteList['text-underline-position'] = true; // default: auto
30323
+ whiteList['text-wrap'] = true; // default: normal
30324
+ whiteList['top'] = false; // default: auto
30325
+ whiteList['transform'] = false; // default: none
30326
+ whiteList['transform-origin'] = false; // default: 50% 50% 0
30327
+ whiteList['transform-style'] = false; // default: flat
30328
+ whiteList['transition'] = false; // default: depending on individual properties
30329
+ whiteList['transition-delay'] = false; // default: 0s
30330
+ whiteList['transition-duration'] = false; // default: 0s
30331
+ whiteList['transition-property'] = false; // default: all
30332
+ whiteList['transition-timing-function'] = false; // default: ease
30333
+ whiteList['unicode-bidi'] = false; // default: normal
30334
+ whiteList['vertical-align'] = false; // default: baseline
30335
+ whiteList['visibility'] = false; // default: visible
30336
+ whiteList['voice-balance'] = false; // default: center
30337
+ whiteList['voice-duration'] = false; // default: auto
30338
+ whiteList['voice-family'] = false; // default: implementation dependent
30339
+ whiteList['voice-pitch'] = false; // default: medium
30340
+ whiteList['voice-range'] = false; // default: medium
30341
+ whiteList['voice-rate'] = false; // default: normal
30342
+ whiteList['voice-stress'] = false; // default: normal
30343
+ whiteList['voice-volume'] = false; // default: medium
30344
+ whiteList['volume'] = false; // default: medium
30345
+ whiteList['white-space'] = false; // default: normal
30346
+ whiteList['widows'] = false; // default: 2
30347
+ whiteList['width'] = true; // default: auto
30348
+ whiteList['will-change'] = false; // default: auto
30349
+ whiteList['word-break'] = true; // default: normal
30350
+ whiteList['word-spacing'] = true; // default: normal
30351
+ whiteList['word-wrap'] = true; // default: normal
30352
+ whiteList['wrap-flow'] = false; // default: auto
30353
+ whiteList['wrap-through'] = false; // default: wrap
30354
+ whiteList['writing-mode'] = false; // default: horizontal-tb
30355
+ whiteList['z-index'] = false; // default: auto
30356
+
30357
+ return whiteList;
30358
+ }
30359
+
30360
+
30361
+ /**
30362
+ * 匹配到白名单上的一个属性时
30363
+ *
30364
+ * @param {String} name
30365
+ * @param {String} value
30366
+ * @param {Object} options
30367
+ * @return {String}
30368
+ */
30369
+ function onAttr (name, value, options) {
30370
+ // do nothing
30371
+ }
30372
+
30373
+ /**
30374
+ * 匹配到不在白名单上的一个属性时
30375
+ *
30376
+ * @param {String} name
30377
+ * @param {String} value
30378
+ * @param {Object} options
30379
+ * @return {String}
30380
+ */
30381
+ function onIgnoreAttr (name, value, options) {
30382
+ // do nothing
30383
+ }
30384
+
30385
+ var REGEXP_URL_JAVASCRIPT = /javascript\s*\:/img;
30386
+
30387
+ /**
30388
+ * 过滤属性值
30389
+ *
30390
+ * @param {String} name
30391
+ * @param {String} value
30392
+ * @return {String}
30393
+ */
30394
+ function safeAttrValue(name, value) {
30395
+ if (REGEXP_URL_JAVASCRIPT.test(value)) return '';
30396
+ return value;
30397
+ }
30398
+
30399
+
30400
+ exports.whiteList = getDefaultWhiteList();
30401
+ exports.getDefaultWhiteList = getDefaultWhiteList;
30402
+ exports.onAttr = onAttr;
30403
+ exports.onIgnoreAttr = onIgnoreAttr;
30404
+ exports.safeAttrValue = safeAttrValue;
30405
+
30406
+
30407
+ /***/ },
30408
+
30409
+ /***/ "./node_modules/cssfilter/lib/index.js"
30410
+ /*!*********************************************!*\
30411
+ !*** ./node_modules/cssfilter/lib/index.js ***!
30412
+ \*********************************************/
30413
+ (module, exports, __webpack_require__) {
30414
+
30415
+ /**
30416
+ * cssfilter
30417
+ *
30418
+ * @author 老雷<leizongmin@gmail.com>
30419
+ */
30420
+
30421
+ var DEFAULT = __webpack_require__(/*! ./default */ "./node_modules/cssfilter/lib/default.js");
30422
+ var FilterCSS = __webpack_require__(/*! ./css */ "./node_modules/cssfilter/lib/css.js");
30423
+
30424
+
30425
+ /**
30426
+ * XSS过滤
30427
+ *
30428
+ * @param {String} css 要过滤的CSS代码
30429
+ * @param {Object} options 选项:whiteList, onAttr, onIgnoreAttr
30430
+ * @return {String}
30431
+ */
30432
+ function filterCSS (html, options) {
30433
+ var xss = new FilterCSS(options);
30434
+ return xss.process(html);
30435
+ }
30436
+
30437
+
30438
+ // 输出
30439
+ exports = module.exports = filterCSS;
30440
+ exports.FilterCSS = FilterCSS;
30441
+ for (var i in DEFAULT) exports[i] = DEFAULT[i];
30442
+
30443
+ // 在浏览器端使用
30444
+ if (typeof window !== 'undefined') {
30445
+ window.filterCSS = module.exports;
30446
+ }
30447
+
30448
+
30449
+ /***/ },
30450
+
30451
+ /***/ "./node_modules/cssfilter/lib/parser.js"
30452
+ /*!**********************************************!*\
30453
+ !*** ./node_modules/cssfilter/lib/parser.js ***!
30454
+ \**********************************************/
30455
+ (module, __unused_webpack_exports, __webpack_require__) {
30456
+
30457
+ /**
30458
+ * cssfilter
30459
+ *
30460
+ * @author 老雷<leizongmin@gmail.com>
30461
+ */
30462
+
30463
+ var _ = __webpack_require__(/*! ./util */ "./node_modules/cssfilter/lib/util.js");
30464
+
30465
+
30466
+ /**
30467
+ * 解析style
30468
+ *
30469
+ * @param {String} css
30470
+ * @param {Function} onAttr 处理属性的函数
30471
+ * 参数格式: function (sourcePosition, position, name, value, source)
30472
+ * @return {String}
30473
+ */
30474
+ function parseStyle (css, onAttr) {
30475
+ css = _.trimRight(css);
30476
+ if (css[css.length - 1] !== ';') css += ';';
30477
+ var cssLength = css.length;
30478
+ var isParenthesisOpen = false;
30479
+ var lastPos = 0;
30480
+ var i = 0;
30481
+ var retCSS = '';
30482
+
30483
+ function addNewAttr () {
30484
+ // 如果没有正常的闭合圆括号,则直接忽略当前属性
30485
+ if (!isParenthesisOpen) {
30486
+ var source = _.trim(css.slice(lastPos, i));
30487
+ var j = source.indexOf(':');
30488
+ if (j !== -1) {
30489
+ var name = _.trim(source.slice(0, j));
30490
+ var value = _.trim(source.slice(j + 1));
30491
+ // 必须有属性名称
30492
+ if (name) {
30493
+ var ret = onAttr(lastPos, retCSS.length, name, value, source);
30494
+ if (ret) retCSS += ret + '; ';
30495
+ }
30496
+ }
30497
+ }
30498
+ lastPos = i + 1;
30499
+ }
30500
+
30501
+ for (; i < cssLength; i++) {
30502
+ var c = css[i];
30503
+ if (c === '/' && css[i + 1] === '*') {
30504
+ // 备注开始
30505
+ var j = css.indexOf('*/', i + 2);
30506
+ // 如果没有正常的备注结束,则后面的部分全部跳过
30507
+ if (j === -1) break;
30508
+ // 直接将当前位置调到备注结尾,并且初始化状态
30509
+ i = j + 1;
30510
+ lastPos = i + 1;
30511
+ isParenthesisOpen = false;
30512
+ } else if (c === '(') {
30513
+ isParenthesisOpen = true;
30514
+ } else if (c === ')') {
30515
+ isParenthesisOpen = false;
30516
+ } else if (c === ';') {
30517
+ if (isParenthesisOpen) {
30518
+ // 在圆括号里面,忽略
30519
+ } else {
30520
+ addNewAttr();
30521
+ }
30522
+ } else if (c === '\n') {
30523
+ addNewAttr();
30524
+ }
30525
+ }
30526
+
30527
+ return _.trim(retCSS);
30528
+ }
30529
+
30530
+ module.exports = parseStyle;
30531
+
30532
+
30533
+ /***/ },
30534
+
30535
+ /***/ "./node_modules/cssfilter/lib/util.js"
30536
+ /*!********************************************!*\
30537
+ !*** ./node_modules/cssfilter/lib/util.js ***!
30538
+ \********************************************/
30539
+ (module) {
30540
+
30541
+ module.exports = {
30542
+ indexOf: function (arr, item) {
30543
+ var i, j;
30544
+ if (Array.prototype.indexOf) {
30545
+ return arr.indexOf(item);
30546
+ }
30547
+ for (i = 0, j = arr.length; i < j; i++) {
30548
+ if (arr[i] === item) {
30549
+ return i;
30550
+ }
30551
+ }
30552
+ return -1;
30553
+ },
30554
+ forEach: function (arr, fn, scope) {
30555
+ var i, j;
30556
+ if (Array.prototype.forEach) {
30557
+ return arr.forEach(fn, scope);
30558
+ }
30559
+ for (i = 0, j = arr.length; i < j; i++) {
30560
+ fn.call(scope, arr[i], i, arr);
30561
+ }
30562
+ },
30563
+ trim: function (str) {
30564
+ if (String.prototype.trim) {
30565
+ return str.trim();
30566
+ }
30567
+ return str.replace(/(^\s*)|(\s*$)/g, '');
30568
+ },
30569
+ trimRight: function (str) {
30570
+ if (String.prototype.trimRight) {
30571
+ return str.trimRight();
30572
+ }
30573
+ return str.replace(/(\s*$)/g, '');
30574
+ }
30575
+ };
30576
+
30577
+
29520
30578
  /***/ },
29521
30579
 
29522
30580
  /***/ "./node_modules/marked/lib/marked.cjs"
@@ -33580,6 +34638,1091 @@ const compile = () => {
33580
34638
 
33581
34639
 
33582
34640
 
34641
+ /***/ },
34642
+
34643
+ /***/ "./node_modules/xss/lib/default.js"
34644
+ /*!*****************************************!*\
34645
+ !*** ./node_modules/xss/lib/default.js ***!
34646
+ \*****************************************/
34647
+ (__unused_webpack_module, exports, __webpack_require__) {
34648
+
34649
+ /**
34650
+ * default settings
34651
+ *
34652
+ * @author Zongmin Lei<leizongmin@gmail.com>
34653
+ */
34654
+
34655
+ var FilterCSS = (__webpack_require__(/*! cssfilter */ "./node_modules/cssfilter/lib/index.js").FilterCSS);
34656
+ var getDefaultCSSWhiteList = (__webpack_require__(/*! cssfilter */ "./node_modules/cssfilter/lib/index.js").getDefaultWhiteList);
34657
+ var _ = __webpack_require__(/*! ./util */ "./node_modules/xss/lib/util.js");
34658
+
34659
+ function getDefaultWhiteList() {
34660
+ return {
34661
+ a: ["target", "href", "title"],
34662
+ abbr: ["title"],
34663
+ address: [],
34664
+ area: ["shape", "coords", "href", "alt"],
34665
+ article: [],
34666
+ aside: [],
34667
+ audio: [
34668
+ "autoplay",
34669
+ "controls",
34670
+ "crossorigin",
34671
+ "loop",
34672
+ "muted",
34673
+ "preload",
34674
+ "src",
34675
+ ],
34676
+ b: [],
34677
+ bdi: ["dir"],
34678
+ bdo: ["dir"],
34679
+ big: [],
34680
+ blockquote: ["cite"],
34681
+ br: [],
34682
+ caption: [],
34683
+ center: [],
34684
+ cite: [],
34685
+ code: [],
34686
+ col: ["align", "valign", "span", "width"],
34687
+ colgroup: ["align", "valign", "span", "width"],
34688
+ dd: [],
34689
+ del: ["datetime"],
34690
+ details: ["open"],
34691
+ div: [],
34692
+ dl: [],
34693
+ dt: [],
34694
+ em: [],
34695
+ figcaption: [],
34696
+ figure: [],
34697
+ font: ["color", "size", "face"],
34698
+ footer: [],
34699
+ h1: [],
34700
+ h2: [],
34701
+ h3: [],
34702
+ h4: [],
34703
+ h5: [],
34704
+ h6: [],
34705
+ header: [],
34706
+ hr: [],
34707
+ i: [],
34708
+ img: ["src", "alt", "title", "width", "height", "loading"],
34709
+ ins: ["datetime"],
34710
+ kbd: [],
34711
+ li: [],
34712
+ mark: [],
34713
+ nav: [],
34714
+ ol: [],
34715
+ p: [],
34716
+ pre: [],
34717
+ s: [],
34718
+ section: [],
34719
+ small: [],
34720
+ span: [],
34721
+ sub: [],
34722
+ summary: [],
34723
+ sup: [],
34724
+ strong: [],
34725
+ strike: [],
34726
+ table: ["width", "border", "align", "valign"],
34727
+ tbody: ["align", "valign"],
34728
+ td: ["width", "rowspan", "colspan", "align", "valign"],
34729
+ tfoot: ["align", "valign"],
34730
+ th: ["width", "rowspan", "colspan", "align", "valign"],
34731
+ thead: ["align", "valign"],
34732
+ tr: ["rowspan", "align", "valign"],
34733
+ tt: [],
34734
+ u: [],
34735
+ ul: [],
34736
+ video: [
34737
+ "autoplay",
34738
+ "controls",
34739
+ "crossorigin",
34740
+ "loop",
34741
+ "muted",
34742
+ "playsinline",
34743
+ "poster",
34744
+ "preload",
34745
+ "src",
34746
+ "height",
34747
+ "width",
34748
+ ],
34749
+ };
34750
+ }
34751
+
34752
+ var defaultCSSFilter = new FilterCSS();
34753
+
34754
+ /**
34755
+ * default onTag function
34756
+ *
34757
+ * @param {String} tag
34758
+ * @param {String} html
34759
+ * @param {Object} options
34760
+ * @return {String}
34761
+ */
34762
+ function onTag(tag, html, options) {
34763
+ // do nothing
34764
+ }
34765
+
34766
+ /**
34767
+ * default onIgnoreTag function
34768
+ *
34769
+ * @param {String} tag
34770
+ * @param {String} html
34771
+ * @param {Object} options
34772
+ * @return {String}
34773
+ */
34774
+ function onIgnoreTag(tag, html, options) {
34775
+ // do nothing
34776
+ }
34777
+
34778
+ /**
34779
+ * default onTagAttr function
34780
+ *
34781
+ * @param {String} tag
34782
+ * @param {String} name
34783
+ * @param {String} value
34784
+ * @return {String}
34785
+ */
34786
+ function onTagAttr(tag, name, value) {
34787
+ // do nothing
34788
+ }
34789
+
34790
+ /**
34791
+ * default onIgnoreTagAttr function
34792
+ *
34793
+ * @param {String} tag
34794
+ * @param {String} name
34795
+ * @param {String} value
34796
+ * @return {String}
34797
+ */
34798
+ function onIgnoreTagAttr(tag, name, value) {
34799
+ // do nothing
34800
+ }
34801
+
34802
+ /**
34803
+ * default escapeHtml function
34804
+ *
34805
+ * @param {String} html
34806
+ */
34807
+ function escapeHtml(html) {
34808
+ return html.replace(REGEXP_LT, "&lt;").replace(REGEXP_GT, "&gt;");
34809
+ }
34810
+
34811
+ /**
34812
+ * default safeAttrValue function
34813
+ *
34814
+ * @param {String} tag
34815
+ * @param {String} name
34816
+ * @param {String} value
34817
+ * @param {Object} cssFilter
34818
+ * @return {String}
34819
+ */
34820
+ function safeAttrValue(tag, name, value, cssFilter) {
34821
+ // unescape attribute value firstly
34822
+ value = friendlyAttrValue(value);
34823
+
34824
+ if (name === "href" || name === "src") {
34825
+ // filter `href` and `src` attribute
34826
+ // only allow the value that starts with `http://` | `https://` | `mailto:` | `/` | `#`
34827
+ value = _.trim(value);
34828
+ if (value === "#") return "#";
34829
+ if (
34830
+ !(
34831
+ value.substr(0, 7) === "http://" ||
34832
+ value.substr(0, 8) === "https://" ||
34833
+ value.substr(0, 7) === "mailto:" ||
34834
+ value.substr(0, 4) === "tel:" ||
34835
+ value.substr(0, 11) === "data:image/" ||
34836
+ value.substr(0, 6) === "ftp://" ||
34837
+ value.substr(0, 2) === "./" ||
34838
+ value.substr(0, 3) === "../" ||
34839
+ value[0] === "#" ||
34840
+ value[0] === "/"
34841
+ )
34842
+ ) {
34843
+ return "";
34844
+ }
34845
+ } else if (name === "background") {
34846
+ // filter `background` attribute (maybe no use)
34847
+ // `javascript:`
34848
+ REGEXP_DEFAULT_ON_TAG_ATTR_4.lastIndex = 0;
34849
+ if (REGEXP_DEFAULT_ON_TAG_ATTR_4.test(value)) {
34850
+ return "";
34851
+ }
34852
+ } else if (name === "style") {
34853
+ // `expression()`
34854
+ REGEXP_DEFAULT_ON_TAG_ATTR_7.lastIndex = 0;
34855
+ if (REGEXP_DEFAULT_ON_TAG_ATTR_7.test(value)) {
34856
+ return "";
34857
+ }
34858
+ // `url()`
34859
+ REGEXP_DEFAULT_ON_TAG_ATTR_8.lastIndex = 0;
34860
+ if (REGEXP_DEFAULT_ON_TAG_ATTR_8.test(value)) {
34861
+ REGEXP_DEFAULT_ON_TAG_ATTR_4.lastIndex = 0;
34862
+ if (REGEXP_DEFAULT_ON_TAG_ATTR_4.test(value)) {
34863
+ return "";
34864
+ }
34865
+ }
34866
+ if (cssFilter !== false) {
34867
+ cssFilter = cssFilter || defaultCSSFilter;
34868
+ value = cssFilter.process(value);
34869
+ }
34870
+ }
34871
+
34872
+ // escape `<>"` before returns
34873
+ value = escapeAttrValue(value);
34874
+ return value;
34875
+ }
34876
+
34877
+ // RegExp list
34878
+ var REGEXP_LT = /</g;
34879
+ var REGEXP_GT = />/g;
34880
+ var REGEXP_QUOTE = /"/g;
34881
+ var REGEXP_QUOTE_2 = /&quot;/g;
34882
+ var REGEXP_ATTR_VALUE_1 = /&#([a-zA-Z0-9]*);?/gim;
34883
+ var REGEXP_ATTR_VALUE_COLON = /&colon;?/gim;
34884
+ var REGEXP_ATTR_VALUE_NEWLINE = /&newline;?/gim;
34885
+ // var REGEXP_DEFAULT_ON_TAG_ATTR_3 = /\/\*|\*\//gm;
34886
+ var REGEXP_DEFAULT_ON_TAG_ATTR_4 =
34887
+ /((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a):/gi;
34888
+ // var REGEXP_DEFAULT_ON_TAG_ATTR_5 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:/gi;
34889
+ // var REGEXP_DEFAULT_ON_TAG_ATTR_6 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:\s*image\//gi;
34890
+ var REGEXP_DEFAULT_ON_TAG_ATTR_7 =
34891
+ /e\s*x\s*p\s*r\s*e\s*s\s*s\s*i\s*o\s*n\s*\(.*/gi;
34892
+ var REGEXP_DEFAULT_ON_TAG_ATTR_8 = /u\s*r\s*l\s*\(.*/gi;
34893
+
34894
+ /**
34895
+ * escape double quote
34896
+ *
34897
+ * @param {String} str
34898
+ * @return {String} str
34899
+ */
34900
+ function escapeQuote(str) {
34901
+ return str.replace(REGEXP_QUOTE, "&quot;");
34902
+ }
34903
+
34904
+ /**
34905
+ * unescape double quote
34906
+ *
34907
+ * @param {String} str
34908
+ * @return {String} str
34909
+ */
34910
+ function unescapeQuote(str) {
34911
+ return str.replace(REGEXP_QUOTE_2, '"');
34912
+ }
34913
+
34914
+ /**
34915
+ * escape html entities
34916
+ *
34917
+ * @param {String} str
34918
+ * @return {String}
34919
+ */
34920
+ function escapeHtmlEntities(str) {
34921
+ return str.replace(REGEXP_ATTR_VALUE_1, function replaceUnicode(str, code) {
34922
+ return code[0] === "x" || code[0] === "X"
34923
+ ? String.fromCharCode(parseInt(code.substr(1), 16))
34924
+ : String.fromCharCode(parseInt(code, 10));
34925
+ });
34926
+ }
34927
+
34928
+ /**
34929
+ * escape html5 new danger entities
34930
+ *
34931
+ * @param {String} str
34932
+ * @return {String}
34933
+ */
34934
+ function escapeDangerHtml5Entities(str) {
34935
+ return str
34936
+ .replace(REGEXP_ATTR_VALUE_COLON, ":")
34937
+ .replace(REGEXP_ATTR_VALUE_NEWLINE, " ");
34938
+ }
34939
+
34940
+ /**
34941
+ * clear nonprintable characters
34942
+ *
34943
+ * @param {String} str
34944
+ * @return {String}
34945
+ */
34946
+ function clearNonPrintableCharacter(str) {
34947
+ var str2 = "";
34948
+ for (var i = 0, len = str.length; i < len; i++) {
34949
+ str2 += str.charCodeAt(i) < 32 ? " " : str.charAt(i);
34950
+ }
34951
+ return _.trim(str2);
34952
+ }
34953
+
34954
+ /**
34955
+ * get friendly attribute value
34956
+ *
34957
+ * @param {String} str
34958
+ * @return {String}
34959
+ */
34960
+ function friendlyAttrValue(str) {
34961
+ str = unescapeQuote(str);
34962
+ str = escapeHtmlEntities(str);
34963
+ str = escapeDangerHtml5Entities(str);
34964
+ str = clearNonPrintableCharacter(str);
34965
+ return str;
34966
+ }
34967
+
34968
+ /**
34969
+ * unescape attribute value
34970
+ *
34971
+ * @param {String} str
34972
+ * @return {String}
34973
+ */
34974
+ function escapeAttrValue(str) {
34975
+ str = escapeQuote(str);
34976
+ str = escapeHtml(str);
34977
+ return str;
34978
+ }
34979
+
34980
+ /**
34981
+ * `onIgnoreTag` function for removing all the tags that are not in whitelist
34982
+ */
34983
+ function onIgnoreTagStripAll() {
34984
+ return "";
34985
+ }
34986
+
34987
+ /**
34988
+ * remove tag body
34989
+ * specify a `tags` list, if the tag is not in the `tags` list then process by the specify function (optional)
34990
+ *
34991
+ * @param {array} tags
34992
+ * @param {function} next
34993
+ */
34994
+ function StripTagBody(tags, next) {
34995
+ if (typeof next !== "function") {
34996
+ next = function () {};
34997
+ }
34998
+
34999
+ var isRemoveAllTag = !Array.isArray(tags);
35000
+ function isRemoveTag(tag) {
35001
+ if (isRemoveAllTag) return true;
35002
+ return _.indexOf(tags, tag) !== -1;
35003
+ }
35004
+
35005
+ var removeList = [];
35006
+ var posStart = false;
35007
+
35008
+ return {
35009
+ onIgnoreTag: function (tag, html, options) {
35010
+ if (isRemoveTag(tag)) {
35011
+ if (options.isClosing) {
35012
+ var ret = "[/removed]";
35013
+ var end = options.position + ret.length;
35014
+ removeList.push([
35015
+ posStart !== false ? posStart : options.position,
35016
+ end,
35017
+ ]);
35018
+ posStart = false;
35019
+ return ret;
35020
+ } else {
35021
+ if (!posStart) {
35022
+ posStart = options.position;
35023
+ }
35024
+ return "[removed]";
35025
+ }
35026
+ } else {
35027
+ return next(tag, html, options);
35028
+ }
35029
+ },
35030
+ remove: function (html) {
35031
+ var rethtml = "";
35032
+ var lastPos = 0;
35033
+ _.forEach(removeList, function (pos) {
35034
+ rethtml += html.slice(lastPos, pos[0]);
35035
+ lastPos = pos[1];
35036
+ });
35037
+ rethtml += html.slice(lastPos);
35038
+ return rethtml;
35039
+ },
35040
+ };
35041
+ }
35042
+
35043
+ /**
35044
+ * remove html comments
35045
+ *
35046
+ * @param {String} html
35047
+ * @return {String}
35048
+ */
35049
+ function stripCommentTag(html) {
35050
+ var retHtml = "";
35051
+ var lastPos = 0;
35052
+ while (lastPos < html.length) {
35053
+ var i = html.indexOf("<!--", lastPos);
35054
+ if (i === -1) {
35055
+ retHtml += html.slice(lastPos);
35056
+ break;
35057
+ }
35058
+ retHtml += html.slice(lastPos, i);
35059
+ var j = html.indexOf("-->", i);
35060
+ if (j === -1) {
35061
+ break;
35062
+ }
35063
+ lastPos = j + 3;
35064
+ }
35065
+ return retHtml;
35066
+ }
35067
+
35068
+ /**
35069
+ * remove invisible characters
35070
+ *
35071
+ * @param {String} html
35072
+ * @return {String}
35073
+ */
35074
+ function stripBlankChar(html) {
35075
+ var chars = html.split("");
35076
+ chars = chars.filter(function (char) {
35077
+ var c = char.charCodeAt(0);
35078
+ if (c === 127) return false;
35079
+ if (c <= 31) {
35080
+ if (c === 10 || c === 13) return true;
35081
+ return false;
35082
+ }
35083
+ return true;
35084
+ });
35085
+ return chars.join("");
35086
+ }
35087
+
35088
+ exports.whiteList = getDefaultWhiteList();
35089
+ exports.getDefaultWhiteList = getDefaultWhiteList;
35090
+ exports.onTag = onTag;
35091
+ exports.onIgnoreTag = onIgnoreTag;
35092
+ exports.onTagAttr = onTagAttr;
35093
+ exports.onIgnoreTagAttr = onIgnoreTagAttr;
35094
+ exports.safeAttrValue = safeAttrValue;
35095
+ exports.escapeHtml = escapeHtml;
35096
+ exports.escapeQuote = escapeQuote;
35097
+ exports.unescapeQuote = unescapeQuote;
35098
+ exports.escapeHtmlEntities = escapeHtmlEntities;
35099
+ exports.escapeDangerHtml5Entities = escapeDangerHtml5Entities;
35100
+ exports.clearNonPrintableCharacter = clearNonPrintableCharacter;
35101
+ exports.friendlyAttrValue = friendlyAttrValue;
35102
+ exports.escapeAttrValue = escapeAttrValue;
35103
+ exports.onIgnoreTagStripAll = onIgnoreTagStripAll;
35104
+ exports.StripTagBody = StripTagBody;
35105
+ exports.stripCommentTag = stripCommentTag;
35106
+ exports.stripBlankChar = stripBlankChar;
35107
+ exports.attributeWrapSign = '"';
35108
+ exports.cssFilter = defaultCSSFilter;
35109
+ exports.getDefaultCSSWhiteList = getDefaultCSSWhiteList;
35110
+
35111
+
35112
+ /***/ },
35113
+
35114
+ /***/ "./node_modules/xss/lib/index.js"
35115
+ /*!***************************************!*\
35116
+ !*** ./node_modules/xss/lib/index.js ***!
35117
+ \***************************************/
35118
+ (module, exports, __webpack_require__) {
35119
+
35120
+ /**
35121
+ * xss
35122
+ *
35123
+ * @author Zongmin Lei<leizongmin@gmail.com>
35124
+ */
35125
+
35126
+ var DEFAULT = __webpack_require__(/*! ./default */ "./node_modules/xss/lib/default.js");
35127
+ var parser = __webpack_require__(/*! ./parser */ "./node_modules/xss/lib/parser.js");
35128
+ var FilterXSS = __webpack_require__(/*! ./xss */ "./node_modules/xss/lib/xss.js");
35129
+
35130
+ /**
35131
+ * filter xss function
35132
+ *
35133
+ * @param {String} html
35134
+ * @param {Object} options { whiteList, onTag, onTagAttr, onIgnoreTag, onIgnoreTagAttr, safeAttrValue, escapeHtml }
35135
+ * @return {String}
35136
+ */
35137
+ function filterXSS(html, options) {
35138
+ var xss = new FilterXSS(options);
35139
+ return xss.process(html);
35140
+ }
35141
+
35142
+ exports = module.exports = filterXSS;
35143
+ exports.filterXSS = filterXSS;
35144
+ exports.FilterXSS = FilterXSS;
35145
+
35146
+ (function () {
35147
+ for (var i in DEFAULT) {
35148
+ exports[i] = DEFAULT[i];
35149
+ }
35150
+ for (var j in parser) {
35151
+ exports[j] = parser[j];
35152
+ }
35153
+ })();
35154
+
35155
+ // using `xss` on the browser, output `filterXSS` to the globals
35156
+ if (typeof window !== "undefined") {
35157
+ window.filterXSS = module.exports;
35158
+ }
35159
+
35160
+ // using `xss` on the WebWorker, output `filterXSS` to the globals
35161
+ function isWorkerEnv() {
35162
+ return (
35163
+ typeof self !== "undefined" &&
35164
+ typeof DedicatedWorkerGlobalScope !== "undefined" &&
35165
+ self instanceof DedicatedWorkerGlobalScope
35166
+ );
35167
+ }
35168
+ if (isWorkerEnv()) {
35169
+ self.filterXSS = module.exports;
35170
+ }
35171
+
35172
+
35173
+ /***/ },
35174
+
35175
+ /***/ "./node_modules/xss/lib/parser.js"
35176
+ /*!****************************************!*\
35177
+ !*** ./node_modules/xss/lib/parser.js ***!
35178
+ \****************************************/
35179
+ (__unused_webpack_module, exports, __webpack_require__) {
35180
+
35181
+ /**
35182
+ * Simple HTML Parser
35183
+ *
35184
+ * @author Zongmin Lei<leizongmin@gmail.com>
35185
+ */
35186
+
35187
+ var _ = __webpack_require__(/*! ./util */ "./node_modules/xss/lib/util.js");
35188
+
35189
+ /**
35190
+ * get tag name
35191
+ *
35192
+ * @param {String} html e.g. '<a hef="#">'
35193
+ * @return {String}
35194
+ */
35195
+ function getTagName(html) {
35196
+ var i = _.spaceIndex(html);
35197
+ var tagName;
35198
+ if (i === -1) {
35199
+ tagName = html.slice(1, -1);
35200
+ } else {
35201
+ tagName = html.slice(1, i + 1);
35202
+ }
35203
+ tagName = _.trim(tagName).toLowerCase();
35204
+ if (tagName.slice(0, 1) === "/") tagName = tagName.slice(1);
35205
+ if (tagName.slice(-1) === "/") tagName = tagName.slice(0, -1);
35206
+ return tagName;
35207
+ }
35208
+
35209
+ /**
35210
+ * is close tag?
35211
+ *
35212
+ * @param {String} html 如:'<a hef="#">'
35213
+ * @return {Boolean}
35214
+ */
35215
+ function isClosing(html) {
35216
+ return html.slice(0, 2) === "</";
35217
+ }
35218
+
35219
+ /**
35220
+ * parse input html and returns processed html
35221
+ *
35222
+ * @param {String} html
35223
+ * @param {Function} onTag e.g. function (sourcePosition, position, tag, html, isClosing)
35224
+ * @param {Function} escapeHtml
35225
+ * @return {String}
35226
+ */
35227
+ function parseTag(html, onTag, escapeHtml) {
35228
+ "use strict";
35229
+
35230
+ var rethtml = "";
35231
+ var lastPos = 0;
35232
+ var tagStart = false;
35233
+ var quoteStart = false;
35234
+ var currentPos = 0;
35235
+ var len = html.length;
35236
+ var currentTagName = "";
35237
+ var currentHtml = "";
35238
+
35239
+ chariterator: for (currentPos = 0; currentPos < len; currentPos++) {
35240
+ var c = html.charAt(currentPos);
35241
+ if (tagStart === false) {
35242
+ if (c === "<") {
35243
+ tagStart = currentPos;
35244
+ continue;
35245
+ }
35246
+ } else {
35247
+ if (quoteStart === false) {
35248
+ if (c === "<") {
35249
+ rethtml += escapeHtml(html.slice(lastPos, currentPos));
35250
+ tagStart = currentPos;
35251
+ lastPos = currentPos;
35252
+ continue;
35253
+ }
35254
+ if (c === ">" || currentPos === len - 1) {
35255
+ rethtml += escapeHtml(html.slice(lastPos, tagStart));
35256
+ currentHtml = html.slice(tagStart, currentPos + 1);
35257
+ currentTagName = getTagName(currentHtml);
35258
+ rethtml += onTag(
35259
+ tagStart,
35260
+ rethtml.length,
35261
+ currentTagName,
35262
+ currentHtml,
35263
+ isClosing(currentHtml)
35264
+ );
35265
+ lastPos = currentPos + 1;
35266
+ tagStart = false;
35267
+ continue;
35268
+ }
35269
+ if (c === '"' || c === "'") {
35270
+ var i = 1;
35271
+ var ic = html.charAt(currentPos - i);
35272
+
35273
+ while (ic.trim() === "" || ic === "=") {
35274
+ if (ic === "=") {
35275
+ quoteStart = c;
35276
+ continue chariterator;
35277
+ }
35278
+ ic = html.charAt(currentPos - ++i);
35279
+ }
35280
+ }
35281
+ } else {
35282
+ if (c === quoteStart) {
35283
+ quoteStart = false;
35284
+ continue;
35285
+ }
35286
+ }
35287
+ }
35288
+ }
35289
+ if (lastPos < len) {
35290
+ rethtml += escapeHtml(html.substr(lastPos));
35291
+ }
35292
+
35293
+ return rethtml;
35294
+ }
35295
+
35296
+ var REGEXP_ILLEGAL_ATTR_NAME = /[^a-zA-Z0-9\\_:.-]/gim;
35297
+
35298
+ /**
35299
+ * parse input attributes and returns processed attributes
35300
+ *
35301
+ * @param {String} html e.g. `href="#" target="_blank"`
35302
+ * @param {Function} onAttr e.g. `function (name, value)`
35303
+ * @return {String}
35304
+ */
35305
+ function parseAttr(html, onAttr) {
35306
+ "use strict";
35307
+
35308
+ var lastPos = 0;
35309
+ var lastMarkPos = 0;
35310
+ var retAttrs = [];
35311
+ var tmpName = false;
35312
+ var len = html.length;
35313
+
35314
+ function addAttr(name, value) {
35315
+ name = _.trim(name);
35316
+ name = name.replace(REGEXP_ILLEGAL_ATTR_NAME, "").toLowerCase();
35317
+ if (name.length < 1) return;
35318
+ var ret = onAttr(name, value || "");
35319
+ if (ret) retAttrs.push(ret);
35320
+ }
35321
+
35322
+ // 逐个分析字符
35323
+ for (var i = 0; i < len; i++) {
35324
+ var c = html.charAt(i);
35325
+ var v, j;
35326
+ if (tmpName === false && c === "=") {
35327
+ tmpName = html.slice(lastPos, i);
35328
+ lastPos = i + 1;
35329
+ lastMarkPos = html.charAt(lastPos) === '"' || html.charAt(lastPos) === "'" ? lastPos : findNextQuotationMark(html, i + 1);
35330
+ continue;
35331
+ }
35332
+ if (tmpName !== false) {
35333
+ if (
35334
+ i === lastMarkPos
35335
+ ) {
35336
+ j = html.indexOf(c, i + 1);
35337
+ if (j === -1) {
35338
+ break;
35339
+ } else {
35340
+ v = _.trim(html.slice(lastMarkPos + 1, j));
35341
+ addAttr(tmpName, v);
35342
+ tmpName = false;
35343
+ i = j;
35344
+ lastPos = i + 1;
35345
+ continue;
35346
+ }
35347
+ }
35348
+ }
35349
+ if (/\s|\n|\t/.test(c)) {
35350
+ html = html.replace(/\s|\n|\t/g, " ");
35351
+ if (tmpName === false) {
35352
+ j = findNextEqual(html, i);
35353
+ if (j === -1) {
35354
+ v = _.trim(html.slice(lastPos, i));
35355
+ addAttr(v);
35356
+ tmpName = false;
35357
+ lastPos = i + 1;
35358
+ continue;
35359
+ } else {
35360
+ i = j - 1;
35361
+ continue;
35362
+ }
35363
+ } else {
35364
+ j = findBeforeEqual(html, i - 1);
35365
+ if (j === -1) {
35366
+ v = _.trim(html.slice(lastPos, i));
35367
+ v = stripQuoteWrap(v);
35368
+ addAttr(tmpName, v);
35369
+ tmpName = false;
35370
+ lastPos = i + 1;
35371
+ continue;
35372
+ } else {
35373
+ continue;
35374
+ }
35375
+ }
35376
+ }
35377
+ }
35378
+
35379
+ if (lastPos < html.length) {
35380
+ if (tmpName === false) {
35381
+ addAttr(html.slice(lastPos));
35382
+ } else {
35383
+ addAttr(tmpName, stripQuoteWrap(_.trim(html.slice(lastPos))));
35384
+ }
35385
+ }
35386
+
35387
+ return _.trim(retAttrs.join(" "));
35388
+ }
35389
+
35390
+ function findNextEqual(str, i) {
35391
+ for (; i < str.length; i++) {
35392
+ var c = str[i];
35393
+ if (c === " ") continue;
35394
+ if (c === "=") return i;
35395
+ return -1;
35396
+ }
35397
+ }
35398
+
35399
+ function findNextQuotationMark(str, i) {
35400
+ for (; i < str.length; i++) {
35401
+ var c = str[i];
35402
+ if (c === " ") continue;
35403
+ if (c === "'" || c === '"') return i;
35404
+ return -1;
35405
+ }
35406
+ }
35407
+
35408
+ function findBeforeEqual(str, i) {
35409
+ for (; i > 0; i--) {
35410
+ var c = str[i];
35411
+ if (c === " ") continue;
35412
+ if (c === "=") return i;
35413
+ return -1;
35414
+ }
35415
+ }
35416
+
35417
+ function isQuoteWrapString(text) {
35418
+ if (
35419
+ (text[0] === '"' && text[text.length - 1] === '"') ||
35420
+ (text[0] === "'" && text[text.length - 1] === "'")
35421
+ ) {
35422
+ return true;
35423
+ } else {
35424
+ return false;
35425
+ }
35426
+ }
35427
+
35428
+ function stripQuoteWrap(text) {
35429
+ if (isQuoteWrapString(text)) {
35430
+ return text.substr(1, text.length - 2);
35431
+ } else {
35432
+ return text;
35433
+ }
35434
+ }
35435
+
35436
+ exports.parseTag = parseTag;
35437
+ exports.parseAttr = parseAttr;
35438
+
35439
+
35440
+ /***/ },
35441
+
35442
+ /***/ "./node_modules/xss/lib/util.js"
35443
+ /*!**************************************!*\
35444
+ !*** ./node_modules/xss/lib/util.js ***!
35445
+ \**************************************/
35446
+ (module) {
35447
+
35448
+ module.exports = {
35449
+ indexOf: function (arr, item) {
35450
+ var i, j;
35451
+ if (Array.prototype.indexOf) {
35452
+ return arr.indexOf(item);
35453
+ }
35454
+ for (i = 0, j = arr.length; i < j; i++) {
35455
+ if (arr[i] === item) {
35456
+ return i;
35457
+ }
35458
+ }
35459
+ return -1;
35460
+ },
35461
+ forEach: function (arr, fn, scope) {
35462
+ var i, j;
35463
+ if (Array.prototype.forEach) {
35464
+ return arr.forEach(fn, scope);
35465
+ }
35466
+ for (i = 0, j = arr.length; i < j; i++) {
35467
+ fn.call(scope, arr[i], i, arr);
35468
+ }
35469
+ },
35470
+ trim: function (str) {
35471
+ if (String.prototype.trim) {
35472
+ return str.trim();
35473
+ }
35474
+ return str.replace(/(^\s*)|(\s*$)/g, "");
35475
+ },
35476
+ spaceIndex: function (str) {
35477
+ var reg = /\s|\n|\t/;
35478
+ var match = reg.exec(str);
35479
+ return match ? match.index : -1;
35480
+ },
35481
+ };
35482
+
35483
+
35484
+ /***/ },
35485
+
35486
+ /***/ "./node_modules/xss/lib/xss.js"
35487
+ /*!*************************************!*\
35488
+ !*** ./node_modules/xss/lib/xss.js ***!
35489
+ \*************************************/
35490
+ (module, __unused_webpack_exports, __webpack_require__) {
35491
+
35492
+ /**
35493
+ * filter xss
35494
+ *
35495
+ * @author Zongmin Lei<leizongmin@gmail.com>
35496
+ */
35497
+
35498
+ var FilterCSS = (__webpack_require__(/*! cssfilter */ "./node_modules/cssfilter/lib/index.js").FilterCSS);
35499
+ var DEFAULT = __webpack_require__(/*! ./default */ "./node_modules/xss/lib/default.js");
35500
+ var parser = __webpack_require__(/*! ./parser */ "./node_modules/xss/lib/parser.js");
35501
+ var parseTag = parser.parseTag;
35502
+ var parseAttr = parser.parseAttr;
35503
+ var _ = __webpack_require__(/*! ./util */ "./node_modules/xss/lib/util.js");
35504
+
35505
+ /**
35506
+ * returns `true` if the input value is `undefined` or `null`
35507
+ *
35508
+ * @param {Object} obj
35509
+ * @return {Boolean}
35510
+ */
35511
+ function isNull(obj) {
35512
+ return obj === undefined || obj === null;
35513
+ }
35514
+
35515
+ /**
35516
+ * get attributes for a tag
35517
+ *
35518
+ * @param {String} html
35519
+ * @return {Object}
35520
+ * - {String} html
35521
+ * - {Boolean} closing
35522
+ */
35523
+ function getAttrs(html) {
35524
+ var i = _.spaceIndex(html);
35525
+ if (i === -1) {
35526
+ return {
35527
+ html: "",
35528
+ closing: html[html.length - 2] === "/",
35529
+ };
35530
+ }
35531
+ html = _.trim(html.slice(i + 1, -1));
35532
+ var isClosing = html[html.length - 1] === "/";
35533
+ if (isClosing) html = _.trim(html.slice(0, -1));
35534
+ return {
35535
+ html: html,
35536
+ closing: isClosing,
35537
+ };
35538
+ }
35539
+
35540
+ /**
35541
+ * shallow copy
35542
+ *
35543
+ * @param {Object} obj
35544
+ * @return {Object}
35545
+ */
35546
+ function shallowCopyObject(obj) {
35547
+ var ret = {};
35548
+ for (var i in obj) {
35549
+ ret[i] = obj[i];
35550
+ }
35551
+ return ret;
35552
+ }
35553
+
35554
+ function keysToLowerCase(obj) {
35555
+ var ret = {};
35556
+ for (var i in obj) {
35557
+ if (Array.isArray(obj[i])) {
35558
+ ret[i.toLowerCase()] = obj[i].map(function (item) {
35559
+ return item.toLowerCase();
35560
+ });
35561
+ } else {
35562
+ ret[i.toLowerCase()] = obj[i];
35563
+ }
35564
+ }
35565
+ return ret;
35566
+ }
35567
+
35568
+ /**
35569
+ * FilterXSS class
35570
+ *
35571
+ * @param {Object} options
35572
+ * whiteList (or allowList), onTag, onTagAttr, onIgnoreTag,
35573
+ * onIgnoreTagAttr, safeAttrValue, escapeHtml
35574
+ * stripIgnoreTagBody, allowCommentTag, stripBlankChar
35575
+ * css{whiteList, onAttr, onIgnoreAttr} `css=false` means don't use `cssfilter`
35576
+ */
35577
+ function FilterXSS(options) {
35578
+ options = shallowCopyObject(options || {});
35579
+
35580
+ if (options.stripIgnoreTag) {
35581
+ if (options.onIgnoreTag) {
35582
+ console.error(
35583
+ 'Notes: cannot use these two options "stripIgnoreTag" and "onIgnoreTag" at the same time'
35584
+ );
35585
+ }
35586
+ options.onIgnoreTag = DEFAULT.onIgnoreTagStripAll;
35587
+ }
35588
+ if (options.whiteList || options.allowList) {
35589
+ options.whiteList = keysToLowerCase(options.whiteList || options.allowList);
35590
+ } else {
35591
+ options.whiteList = DEFAULT.whiteList;
35592
+ }
35593
+
35594
+ this.attributeWrapSign = options.singleQuotedAttributeValue === true ? "'" : DEFAULT.attributeWrapSign;
35595
+
35596
+ options.onTag = options.onTag || DEFAULT.onTag;
35597
+ options.onTagAttr = options.onTagAttr || DEFAULT.onTagAttr;
35598
+ options.onIgnoreTag = options.onIgnoreTag || DEFAULT.onIgnoreTag;
35599
+ options.onIgnoreTagAttr = options.onIgnoreTagAttr || DEFAULT.onIgnoreTagAttr;
35600
+ options.safeAttrValue = options.safeAttrValue || DEFAULT.safeAttrValue;
35601
+ options.escapeHtml = options.escapeHtml || DEFAULT.escapeHtml;
35602
+ this.options = options;
35603
+
35604
+ if (options.css === false) {
35605
+ this.cssFilter = false;
35606
+ } else {
35607
+ options.css = options.css || {};
35608
+ this.cssFilter = new FilterCSS(options.css);
35609
+ }
35610
+ }
35611
+
35612
+ /**
35613
+ * start process and returns result
35614
+ *
35615
+ * @param {String} html
35616
+ * @return {String}
35617
+ */
35618
+ FilterXSS.prototype.process = function (html) {
35619
+ // compatible with the input
35620
+ html = html || "";
35621
+ html = html.toString();
35622
+ if (!html) return "";
35623
+
35624
+ var me = this;
35625
+ var options = me.options;
35626
+ var whiteList = options.whiteList;
35627
+ var onTag = options.onTag;
35628
+ var onIgnoreTag = options.onIgnoreTag;
35629
+ var onTagAttr = options.onTagAttr;
35630
+ var onIgnoreTagAttr = options.onIgnoreTagAttr;
35631
+ var safeAttrValue = options.safeAttrValue;
35632
+ var escapeHtml = options.escapeHtml;
35633
+ var attributeWrapSign = me.attributeWrapSign;
35634
+ var cssFilter = me.cssFilter;
35635
+
35636
+ // remove invisible characters
35637
+ if (options.stripBlankChar) {
35638
+ html = DEFAULT.stripBlankChar(html);
35639
+ }
35640
+
35641
+ // remove html comments
35642
+ if (!options.allowCommentTag) {
35643
+ html = DEFAULT.stripCommentTag(html);
35644
+ }
35645
+
35646
+ // if enable stripIgnoreTagBody
35647
+ var stripIgnoreTagBody = false;
35648
+ if (options.stripIgnoreTagBody) {
35649
+ stripIgnoreTagBody = DEFAULT.StripTagBody(
35650
+ options.stripIgnoreTagBody,
35651
+ onIgnoreTag
35652
+ );
35653
+ onIgnoreTag = stripIgnoreTagBody.onIgnoreTag;
35654
+ }
35655
+
35656
+ var retHtml = parseTag(
35657
+ html,
35658
+ function (sourcePosition, position, tag, html, isClosing) {
35659
+ var info = {
35660
+ sourcePosition: sourcePosition,
35661
+ position: position,
35662
+ isClosing: isClosing,
35663
+ isWhite: Object.prototype.hasOwnProperty.call(whiteList, tag),
35664
+ };
35665
+
35666
+ // call `onTag()`
35667
+ var ret = onTag(tag, html, info);
35668
+ if (!isNull(ret)) return ret;
35669
+
35670
+ if (info.isWhite) {
35671
+ if (info.isClosing) {
35672
+ return "</" + tag + ">";
35673
+ }
35674
+
35675
+ var attrs = getAttrs(html);
35676
+ var whiteAttrList = whiteList[tag];
35677
+ var attrsHtml = parseAttr(attrs.html, function (name, value) {
35678
+ // call `onTagAttr()`
35679
+ var isWhiteAttr = _.indexOf(whiteAttrList, name) !== -1;
35680
+ var ret = onTagAttr(tag, name, value, isWhiteAttr);
35681
+ if (!isNull(ret)) return ret;
35682
+
35683
+ if (isWhiteAttr) {
35684
+ // call `safeAttrValue()`
35685
+ value = safeAttrValue(tag, name, value, cssFilter);
35686
+ if (value) {
35687
+ return name + '=' + attributeWrapSign + value + attributeWrapSign;
35688
+ } else {
35689
+ return name;
35690
+ }
35691
+ } else {
35692
+ // call `onIgnoreTagAttr()`
35693
+ ret = onIgnoreTagAttr(tag, name, value, isWhiteAttr);
35694
+ if (!isNull(ret)) return ret;
35695
+ return;
35696
+ }
35697
+ });
35698
+
35699
+ // build new tag html
35700
+ html = "<" + tag;
35701
+ if (attrsHtml) html += " " + attrsHtml;
35702
+ if (attrs.closing) html += " /";
35703
+ html += ">";
35704
+ return html;
35705
+ } else {
35706
+ // call `onIgnoreTag()`
35707
+ ret = onIgnoreTag(tag, html, info);
35708
+ if (!isNull(ret)) return ret;
35709
+ return escapeHtml(html);
35710
+ }
35711
+ },
35712
+ escapeHtml
35713
+ );
35714
+
35715
+ // if enable stripIgnoreTagBody
35716
+ if (stripIgnoreTagBody) {
35717
+ retHtml = stripIgnoreTagBody.remove(retHtml);
35718
+ }
35719
+
35720
+ return retHtml;
35721
+ };
35722
+
35723
+ module.exports = FilterXSS;
35724
+
35725
+
33583
35726
  /***/ },
33584
35727
 
33585
35728
  /***/ "./package.json"
@@ -33589,7 +35732,7 @@ const compile = () => {
33589
35732
  (module) {
33590
35733
 
33591
35734
  "use strict";
33592
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.2.6","description":"A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js","test:frontend":"mocha test/frontend/*.test.js"}}');
35735
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.2.7","description":"A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x","xss":"^1.0.15"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js","test:frontend":"mocha test/frontend/*.test.js"}}');
33593
35736
 
33594
35737
  /***/ }
33595
35738