From cd0b9f48629fc24d40f5988fcae98d9046f204ec Mon Sep 17 00:00:00 2001 From: Yannick Marcon Date: Wed, 8 Jan 2025 15:17:31 +0100 Subject: [PATCH] feat: arema main portal and video indexing (#64) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: added some field hints #56 * feat: upload multiple files #56 * feat: markdown edition support #56 * feat: building fields added #56 * feat: fire classes #56 * fix: building material can have multiple types * feat: clust results in frontend * feat: entity page added to frontend (wip) * feat: entity page added to frontend (wip) * feat: search page state in store (wip) * feat: fire properties have low/high and input suggestions * feat: markdown guide link * feat: use search service to retrieve document from éain portal * feat: FTKG font applied * feat: physical characteristics * feat: search panel, hash path * feat: index page style * feat: relations are indexed * feat: added building relation with tc via be * feat: document images carousel * feat: display images * feat: file can be a url, youtube/vimeo videos handled in a player * fix: ensure taxos before getting tags labels * feat: search link added in doc page * feat: select all terms if none are selected, otherwise clear selections * feat: term btns are smaller than voc ones * fix: taxo labels typo * fix: fire norms * feat: prepare frontend for it lang * feat: map's list is scrollable * feat: styling * chore: tr * chore: close drawer on route update * feat: videos indexed and displayed in a search interface #63 * fix: videos page title * feat: extract viedo links from markdown text #63 * fix: text input model type --- admin/.prettierrc | 11 +- admin/package-lock.json | 954 +++++++++++++++++- admin/package.json | 1 + admin/quasar.config.js | 16 + admin/quasar.extensions.json | 5 + admin/src/components/BuildingDialog.vue | 139 +-- .../src/components/BuildingMaterialDialog.vue | 76 +- admin/src/components/CircleMapInput.vue | 82 +- admin/src/components/FilesInput.vue | 97 +- .../src/components/NaturalResourceDialog.vue | 74 +- admin/src/components/PhysicalEntityForm.vue | 99 +- admin/src/components/PointMapInput.vue | 76 +- admin/src/components/ProfessionalDialog.vue | 103 +- ...rtyFormItem.vue => PropertyFormNumber.vue} | 14 +- admin/src/components/PropertyFormSuggest.vue | 120 +++ admin/src/components/TaxonomySelect.vue | 11 +- .../TechnicalConstructionDialog.vue | 86 +- admin/src/components/TextInput.vue | 50 + admin/src/components/models.ts | 6 + admin/src/i18n/en/index.js | 201 ++-- admin/src/i18n/fr/index.js | 119 ++- admin/src/layouts/MainLayout.vue | 47 +- admin/src/models.ts | 11 +- admin/src/stores/taxonomies.ts | 30 +- backend/api/data/building-material.yml | 2 +- backend/api/data/natural-resource.yml | 6 +- backend/api/data/technical-construction.yml | 294 +++--- backend/api/models/domain.py | 11 +- backend/api/services/building_materials.py | 46 +- backend/api/services/buildings.py | 44 +- backend/api/services/natural_resources.py | 38 +- backend/api/services/professionals.py | 43 +- backend/api/services/search.py | 379 ++++++- .../api/services/technical_constructions.py | 41 +- backend/api/views/search.py | 75 +- ...12_16_1623-86ee029f0372_building_fields.py | 37 + .../2024_12_16_1707-9dd17a356a73_fire.py | 34 + ...4_12_18_0952-659fc00a69fa_fire_low_high.py | 64 ++ backend/poetry.lock | 37 +- backend/pyproject.toml | 1 + frontend/.eslintrc-auto-import.json | 3 +- frontend/.prettierrc | 11 +- frontend/package-lock.json | 954 +++++++++++++++++- frontend/package.json | 1 + frontend/quasar.config.js | 18 +- frontend/quasar.extensions.json | 3 + frontend/src/auto-imports.d.ts | 27 +- frontend/src/components/AboutPanel.vue | 9 +- frontend/src/components/DocumentPanel.vue | 206 ++++ frontend/src/components/HeaderPanel.vue | 11 +- frontend/src/components/ListResults.vue | 54 +- frontend/src/components/MapResults.vue | 74 +- frontend/src/components/MapView.vue | 164 ++- frontend/src/components/NavDrawer.vue | 13 - .../components/PhysicalParametersPanel.vue | 87 ++ frontend/src/components/ResultsGrid.vue | 63 -- frontend/src/components/SearchPanel.vue | 266 +++++ frontend/src/components/TagsBadges.vue | 32 +- frontend/src/components/VideosResults.vue | 82 ++ frontend/src/components/VideosSearchPanel.vue | 162 +++ frontend/src/components/models.ts | 10 +- frontend/src/css/app.scss | 20 + .../src/css/fonts/FTKunstGrotesk-Medium.woff | Bin 0 -> 77220 bytes .../fonts/FTKunstGrotesk-MediumItalic.woff | Bin 0 -> 80556 bytes frontend/src/i18n/en/index.ts | 64 +- frontend/src/it/index.ts | 1 + frontend/src/models.ts | 25 +- frontend/src/pages/DocumentPage.vue | 27 + frontend/src/pages/IndexPage.vue | 48 +- frontend/src/pages/SearchPage.vue | 170 +--- frontend/src/pages/VideosPage.vue | 9 + frontend/src/router/routes.ts | 6 + frontend/src/stores/search.ts | 61 +- frontend/src/utils/files.ts | 51 + frontend/src/utils/maps.ts | 2 + 75 files changed, 4801 insertions(+), 1513 deletions(-) create mode 100644 admin/quasar.extensions.json rename admin/src/components/{PropertyFormItem.vue => PropertyFormNumber.vue} (81%) create mode 100644 admin/src/components/PropertyFormSuggest.vue create mode 100644 admin/src/components/TextInput.vue create mode 100644 backend/migrations/versions/2024_12_16_1623-86ee029f0372_building_fields.py create mode 100644 backend/migrations/versions/2024_12_16_1707-9dd17a356a73_fire.py create mode 100644 backend/migrations/versions/2024_12_18_0952-659fc00a69fa_fire_low_high.py create mode 100644 frontend/quasar.extensions.json create mode 100644 frontend/src/components/DocumentPanel.vue create mode 100644 frontend/src/components/PhysicalParametersPanel.vue delete mode 100644 frontend/src/components/ResultsGrid.vue create mode 100644 frontend/src/components/SearchPanel.vue create mode 100644 frontend/src/components/VideosResults.vue create mode 100644 frontend/src/components/VideosSearchPanel.vue create mode 100644 frontend/src/css/fonts/FTKunstGrotesk-Medium.woff create mode 100644 frontend/src/css/fonts/FTKunstGrotesk-MediumItalic.woff create mode 100644 frontend/src/it/index.ts create mode 100644 frontend/src/pages/DocumentPage.vue create mode 100644 frontend/src/pages/VideosPage.vue create mode 100644 frontend/src/utils/files.ts diff --git a/admin/.prettierrc b/admin/.prettierrc index 650cb88..75322b0 100644 --- a/admin/.prettierrc +++ b/admin/.prettierrc @@ -1,4 +1,13 @@ { "singleQuote": true, - "semi": true + "semi": true, + "printWidth": 120, + "overrides": [ + { + "files": "src/i18n/**/index.js", + "options": { + "printWidth": 10000 + } + } + ] } diff --git a/admin/package-lock.json b/admin/package-lock.json index 3bc294f..6fc3b26 100644 --- a/admin/package-lock.json +++ b/admin/package-lock.json @@ -1,11 +1,11 @@ { - "name": "admin", + "name": "arema-admin", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "admin", + "name": "arema-admin", "version": "0.0.1", "dependencies": { "@mapbox/mapbox-gl-draw": "^1.4.3", @@ -28,6 +28,7 @@ "devDependencies": { "@intlify/vite-plugin-vue-i18n": "^7.0.0", "@quasar/app-vite": "^1.3.0", + "@quasar/quasar-app-extension-qmarkdown": "^2.0.0-beta.10", "@types/node": "^12.20.21", "@typescript-eslint/eslint-plugin": "^7.12.0", "@typescript-eslint/parser": "^7.12.0", @@ -970,6 +971,49 @@ "url": "https://donate.quasar.dev" } }, + "node_modules/@quasar/quasar-app-extension-qmarkdown": { + "version": "2.0.0-beta.10", + "resolved": "https://registry.npmjs.org/@quasar/quasar-app-extension-qmarkdown/-/quasar-app-extension-qmarkdown-2.0.0-beta.10.tgz", + "integrity": "sha512-D/2/oxMiNM19D7p1xAkWG5ptW3IzXmLwGtmtqbDJ0gmtzYvkqQxzimdst9uUyZs6G3jmMQSdCN6mJ/iZcyw38g==", + "dev": true, + "dependencies": { + "@quasar/quasar-ui-qmarkdown": "^2.0.0-beta.10", + "front-matter": "^4.0.2", + "markdown-it-abbr": "^1.0.4", + "markdown-it-deflist": "^2.1.0", + "markdown-it-emoji": "^2.0.0", + "markdown-it-footnote": "^3.0.3", + "markdown-it-ins": "^3.0.1", + "markdown-it-mark": "^3.0.1", + "markdown-it-sub": "^1.0.0", + "markdown-it-sup": "^1.0.0", + "markdown-it-task-lists": "^2.1.1", + "raw-loader": "^4.0.2", + "webpack-merge": "^5.8.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/hawkeye64" + } + }, + "node_modules/@quasar/quasar-ui-qmarkdown": { + "version": "2.0.0-beta.10", + "resolved": "https://registry.npmjs.org/@quasar/quasar-ui-qmarkdown/-/quasar-ui-qmarkdown-2.0.0-beta.10.tgz", + "integrity": "sha512-dglYq169H7SQ14eR0mm/Rp+k5gftkSmZyd/3vIH4M94Z/3z2eys4Ch620VhM4oFuxnUUumXYxvs66IQDx3272g==", + "dev": true, + "dependencies": { + "@types/markdown-it": "^12.2.3", + "markdown-it": "^12.3.2", + "markdown-it-container": "^3.0.0", + "markdown-it-imsize": "^2.0.1", + "markdown-it-toc-and-anchor": "^4.2.0", + "prismjs": "^1.28.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/hawkeye64" + } + }, "node_modules/@quasar/render-ssr-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@quasar/render-ssr-error/-/render-ssr-error-1.0.3.tgz", @@ -2626,10 +2670,32 @@ "integrity": "sha512-rkiiTuf/z2wTd4RxFOb+clE7PF4AEJU0hsczbUdkHHBtkUmpWQpEddynNfJYKYtZFJKbq4F+brfekt1kx85IZA==", "dev": true }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, "node_modules/@types/express": { @@ -2696,11 +2762,23 @@ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, "node_modules/@types/junit-report-builder": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/junit-report-builder/-/junit-report-builder-3.0.2.tgz", "integrity": "sha512-R5M+SYhMbwBeQcNXYWNCZkl09vkVfAtcPIaCGdzIkkbeaTrVbGQ7HVgi4s+EmM/M1K4ZuWQH0jGcvMvNePfxYA==" }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true + }, "node_modules/@types/mapbox__point-geometry": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", @@ -2716,6 +2794,22 @@ "@types/pbf": "*" } }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -3204,6 +3298,181 @@ } } }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "peer": true + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3218,9 +3487,9 @@ } }, "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -3254,6 +3523,37 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "peer": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -3519,6 +3819,15 @@ } ] }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3616,9 +3925,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", "dev": true, "funding": [ { @@ -3635,10 +3944,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -3750,9 +4059,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001640", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz", - "integrity": "sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==", + "version": "1.0.30001689", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001689.tgz", + "integrity": "sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g==", "dev": true, "funding": [ { @@ -3815,6 +4124,16 @@ "fsevents": "~2.3.2" } }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -4391,9 +4710,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.582", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.582.tgz", - "integrity": "sha512-89o0MGoocwYbzqUUjc+VNpeOFSOK9nIdC5wY4N+PVUarUK0MtjyTjks75AZS2bW4Kl8MdewdFsWaH0jLy+JNoA==", + "version": "1.5.73", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz", + "integrity": "sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg==", "dev": true }, "node_modules/elementtree": { @@ -4414,6 +4733,15 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -4432,6 +4760,20 @@ "once": "^1.4.0" } }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -4462,6 +4804,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true, + "peer": true + }, "node_modules/esbuild": { "version": "0.14.51", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.51.tgz", @@ -4818,9 +5167,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "engines": { "node": ">=6" @@ -5035,6 +5384,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", @@ -5091,6 +5453,16 @@ "node": ">= 0.6" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/express": { "version": "4.21.1", "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", @@ -5455,6 +5827,37 @@ "node": ">= 0.6" } }, + "node_modules/front-matter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", + "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==", + "dev": true, + "dependencies": { + "js-yaml": "^3.13.1" + } + }, + "node_modules/front-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/front-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -5645,6 +6048,13 @@ "node": ">= 6" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "peer": true + }, "node_modules/global-prefix": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", @@ -6198,6 +6608,37 @@ "node": ">=0.10.0" } }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/js-tokens": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", @@ -6222,6 +6663,13 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "peer": true + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -6239,6 +6687,18 @@ "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonc-eslint-parser": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-1.4.1.tgz", @@ -6393,6 +6853,39 @@ "node": ">= 0.8.0" } }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, "node_modules/local-pkg": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", @@ -6666,6 +7159,122 @@ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-abbr": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-it-abbr/-/markdown-it-abbr-1.0.4.tgz", + "integrity": "sha512-ZeA4Z4SaBbYysZap5iZcxKmlPL6bYA8grqhzJIHB1ikn7njnzaP8uwbtuXc4YXD5LicI4/2Xmc0VwmSiFV04gg==", + "dev": true + }, + "node_modules/markdown-it-container": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-container/-/markdown-it-container-3.0.0.tgz", + "integrity": "sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw==", + "dev": true + }, + "node_modules/markdown-it-deflist": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/markdown-it-deflist/-/markdown-it-deflist-2.1.0.tgz", + "integrity": "sha512-3OuqoRUlSxJiuQYu0cWTLHNhhq2xtoSFqsZK8plANg91+RJQU1ziQ6lA2LzmFAEes18uPBsHZpcX6We5l76Nzg==", + "dev": true + }, + "node_modules/markdown-it-emoji": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz", + "integrity": "sha512-zLftSaNrKuYl0kR5zm4gxXjHaOI3FAOEaloKmRA5hijmJZvSjmxcokOLlzycb/HXlUFWzXqpIEoyEMCE4i9MvQ==", + "dev": true + }, + "node_modules/markdown-it-footnote": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-it-footnote/-/markdown-it-footnote-3.0.3.tgz", + "integrity": "sha512-YZMSuCGVZAjzKMn+xqIco9d1cLGxbELHZ9do/TSYVzraooV8ypsppKNmUJ0fVH5ljkCInQAtFpm8Rb3eXSrt5w==", + "dev": true + }, + "node_modules/markdown-it-imsize": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/markdown-it-imsize/-/markdown-it-imsize-2.0.1.tgz", + "integrity": "sha512-5SH90ademqcR8ifQCBXRCfIR4HGfZZOh5pO0j2TglulfSQH+SBXM4Iw/QlTUbSoUwVZArCYgECoMvktDS2kP3w==", + "dev": true + }, + "node_modules/markdown-it-ins": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/markdown-it-ins/-/markdown-it-ins-3.0.1.tgz", + "integrity": "sha512-32SSfZqSzqyAmmQ4SHvhxbFqSzPDqsZgMHDwxqPzp+v+t8RsmqsBZRG+RfRQskJko9PfKC2/oxyOs4Yg/CfiRw==", + "dev": true + }, + "node_modules/markdown-it-mark": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/markdown-it-mark/-/markdown-it-mark-3.0.1.tgz", + "integrity": "sha512-HyxjAu6BRsdt6Xcv6TKVQnkz/E70TdGXEFHRYBGLncRE9lBFwDNLVtFojKxjJWgJ+5XxUwLaHXy+2sGBbDn+4A==", + "dev": true + }, + "node_modules/markdown-it-sub": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz", + "integrity": "sha512-z2Rm/LzEE1wzwTSDrI+FlPEveAAbgdAdPhdWarq/ZGJrGW/uCQbKAnhoCsE4hAbc3SEym26+W2z/VQB0cQiA9Q==", + "dev": true + }, + "node_modules/markdown-it-sup": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz", + "integrity": "sha512-E32m0nV9iyhRR7CrhnzL5msqic7rL1juWre6TQNxsnApg7Uf+F97JOKxUijg5YwXz86lZ0mqfOnutoryyNdntQ==", + "dev": true + }, + "node_modules/markdown-it-task-lists": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz", + "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==", + "dev": true + }, + "node_modules/markdown-it-toc-and-anchor": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/markdown-it-toc-and-anchor/-/markdown-it-toc-and-anchor-4.2.0.tgz", + "integrity": "sha512-DusSbKtg8CwZ92ztN7bOojDpP4h0+w7BVOPuA3PHDIaabMsERYpwsazLYSP/UlKedoQjOz21mwlai36TQ04EpA==", + "dev": true, + "dependencies": { + "clone": "^2.1.0", + "uslug": "^1.0.4" + } + }, + "node_modules/markdown-it-toc-and-anchor/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -6684,6 +7293,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "peer": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -6836,6 +7452,13 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "peer": true + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -6854,9 +7477,9 @@ "optional": true }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true }, "node_modules/normalize-path": { @@ -7337,6 +7960,15 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -7471,6 +8103,26 @@ "node": ">= 0.8" } }, + "node_modules/raw-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz", + "integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, "node_modules/rbush": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/rbush/-/rbush-2.0.2.tgz", @@ -7828,6 +8480,55 @@ "integrity": "sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==", "dev": true }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "node_modules/scule": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", @@ -7892,9 +8593,9 @@ "dev": true }, "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "dependencies": { "randombytes": "^2.1.0" @@ -8159,6 +8860,12 @@ "node": ">=0.10.0" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "node_modules/stack-trace": { "version": "1.0.0-pre2", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-1.0.0-pre2.tgz", @@ -8318,6 +9025,16 @@ "node": ">=10.0.0" } }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -8352,6 +9069,61 @@ "node": ">=10" } }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", + "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dev": true, + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -8520,6 +9292,12 @@ "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz", "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==" }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, "node_modules/ufo": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", @@ -8619,6 +9397,15 @@ "node": ">= 10.0.0" } }, + "node_modules/unorm": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz", + "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -8724,9 +9511,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "dev": true, "funding": [ { @@ -8743,8 +9530,8 @@ } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -8762,6 +9549,18 @@ "punycode": "^2.1.0" } }, + "node_modules/uslug": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/uslug/-/uslug-1.0.4.tgz", + "integrity": "sha512-Jrbpp/NS3TvIGNjfJT1sn3/BCeykoxR8GbNYW5lF6fUscLkbXFwj1b7m4DvIkHm8k3Qr6Co68lbTmoZTMGk/ow==", + "dev": true, + "dependencies": { + "unorm": ">= 1.0.0" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -8910,6 +9709,20 @@ "vue": "^3.2.0" } }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dev": true, + "peer": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -8919,6 +9732,53 @@ "defaults": "^1.0.3" } }, + "node_modules/webpack": { + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, "node_modules/webpack-merge": { "version": "5.10.0", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", @@ -8948,6 +9808,30 @@ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", "dev": true }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/wgs84": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/wgs84/-/wgs84-0.0.0.tgz", diff --git a/admin/package.json b/admin/package.json index 409c9da..7262eff 100644 --- a/admin/package.json +++ b/admin/package.json @@ -34,6 +34,7 @@ "devDependencies": { "@intlify/vite-plugin-vue-i18n": "^7.0.0", "@quasar/app-vite": "^1.3.0", + "@quasar/quasar-app-extension-qmarkdown": "^2.0.0-beta.10", "@types/node": "^12.20.21", "@typescript-eslint/eslint-plugin": "^7.12.0", "@typescript-eslint/parser": "^7.12.0", diff --git a/admin/quasar.config.js b/admin/quasar.config.js index 6bb1f34..0c0f085 100644 --- a/admin/quasar.config.js +++ b/admin/quasar.config.js @@ -9,6 +9,7 @@ // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js const { configure } = require('quasar/wrappers'); +const { mergeConfig } = require('vite'); const path = require('path'); module.exports = configure(function (ctx) { @@ -71,6 +72,21 @@ module.exports = configure(function (ctx) { // distDir // extendViteConf (viteConf) {}, + // https://github.com/quasarframework/quasar/issues/8513#issuecomment-1127654470 + extendViteConf(viteConf, { isServer, isClient }) { + viteConf.base = ''; + viteConf.css = mergeConfig(viteConf.css, { + preprocessorOptions: { + // silence deprecation warnings from quasar-ui-qmarkdown + sass: { + silenceDeprecations: ['color-functions'], + }, + scss: { + silenceDeprecations: ['color-functions'], + }, + }, + }); + }, // viteVuePluginOptions: {}, vitePlugins: [ diff --git a/admin/quasar.extensions.json b/admin/quasar.extensions.json new file mode 100644 index 0000000..3c9d325 --- /dev/null +++ b/admin/quasar.extensions.json @@ -0,0 +1,5 @@ +{ + "@quasar/qmarkdown": { + "import_md": true + } +} \ No newline at end of file diff --git a/admin/src/components/BuildingDialog.vue b/admin/src/components/BuildingDialog.vue index 562c74d..454dd57 100644 --- a/admin/src/components/BuildingDialog.vue +++ b/admin/src/components/BuildingDialog.vue @@ -10,8 +10,8 @@ - - + + @@ -21,27 +21,18 @@
- +
- +
- +
+ +
+
+ +
+
+ +
+ + - - - - + +
{{ $t('building_elements') }} -
+
{{ $t('no_building_elements') }}
- + @@ -163,12 +148,7 @@ - + @@ -178,19 +158,8 @@ - - + + @@ -202,19 +171,14 @@ export default defineComponent({ }); diff --git a/admin/src/components/NaturalResourceDialog.vue b/admin/src/components/NaturalResourceDialog.vue index 5fec209..036c6cf 100644 --- a/admin/src/components/NaturalResourceDialog.vue +++ b/admin/src/components/NaturalResourceDialog.vue @@ -10,11 +10,8 @@ - - + + @@ -23,11 +20,7 @@
- +
- - - - - + + + + +
- + @@ -89,19 +49,8 @@ - - + + @@ -118,6 +67,7 @@ import { notifyError } from 'src/utils/notify'; import PhysicalEntityForm from 'src/components/PhysicalEntityForm.vue'; import FilesInput from 'src/components/FilesInput.vue'; import TaxonomySelect from 'src/components/TaxonomySelect.vue'; +import TextInput from 'src/components/TextInput.vue'; interface DialogProps { modelValue: boolean; diff --git a/admin/src/components/PhysicalEntityForm.vue b/admin/src/components/PhysicalEntityForm.vue index 3a93b17..84de0bd 100644 --- a/admin/src/components/PhysicalEntityForm.vue +++ b/admin/src/components/PhysicalEntityForm.vue @@ -3,19 +3,15 @@
{{ $t('structural') }}
-
- {{ $t('hygrothermal') }}
- - {{ $t('acoustic') }}
- {{ $t('fire') }}
-
-
{{ $t('others') }}
- +
@@ -105,7 +91,9 @@ export default defineComponent({ diff --git a/admin/src/components/PointMapInput.vue b/admin/src/components/PointMapInput.vue index a8f40d0..6ab3eac 100644 --- a/admin/src/components/PointMapInput.vue +++ b/admin/src/components/PointMapInput.vue @@ -47,37 +47,22 @@ export default defineComponent({ } }); - function onFeatureSelected( - selectedFeatures: Feature[], - ) { + function onFeatureSelected(selectedFeatures: Feature[]) { if (selectedFeatures && selectedFeatures.length > 0) { const value = selectedFeatures.pop(); - if ( - value && - value.properties && - value.geometry.coordinates.length > 0 - ) { - value.properties.circleRadius = round( - value.properties.circleRadius, - 0, - ); + if (value && value.properties && value.geometry.coordinates.length > 0) { + value.properties.circleRadius = round(value.properties.circleRadius, 0); const center = value.geometry.coordinates; - geocoderApi - .reverseGeocode({ query: { lon: center[0], lat: center[1] } }) - .then((collection) => { - if ( - collection && - collection.features && - collection.features.length - ) { - const location = collection.features.pop(); - value.properties = { - ...location?.properties, - }; - } - address.value = value.properties?.display_name; - emit('update:modelValue', value); - }); + geocoderApi.reverseGeocode({ query: { lon: center[0], lat: center[1] } }).then((collection) => { + if (collection && collection.features && collection.features.length) { + const location = collection.features.pop(); + value.properties = { + ...location?.properties, + }; + } + address.value = value.properties?.display_name; + emit('update:modelValue', value); + }); } } else { emit('update:modelValue', null); @@ -105,20 +90,12 @@ export default defineComponent({ function lookupAddress(val, update) { update(() => { if (val && val.length > 2) { - geocoderApi - .forwardGeocode({ query: val, limit: 5 }) - .then((collection) => { - if ( - collection && - collection.features && - collection.features.length - ) { - suggestedFeatures.value = collection.features; - suggestions.value = collection.features.map( - (feature) => feature.properties.display_name, - ); - } - }); + geocoderApi.forwardGeocode({ query: val, limit: 5 }).then((collection) => { + if (collection && collection.features && collection.features.length) { + suggestedFeatures.value = collection.features; + suggestions.value = collection.features.map((feature) => feature.properties.display_name); + } + }); } else { suggestedFeatures.value = []; suggestions.value = []; @@ -127,9 +104,7 @@ export default defineComponent({ } function onAddressUpdate() { - const location = suggestedFeatures.value.find( - (feature) => feature.properties?.text === address.value, - ); + const location = suggestedFeatures.value.find((feature) => feature.properties?.text === address.value); if (location) updateWithLocation(location); } @@ -159,14 +134,7 @@ export default defineComponent({
- + - - + + @@ -21,11 +21,7 @@
- +
-
@@ -58,45 +53,31 @@
- +
- - - - + - - + + @@ -164,15 +134,12 @@ export default defineComponent({ }); + diff --git a/admin/src/components/TaxonomySelect.vue b/admin/src/components/TaxonomySelect.vue index c7069f0..94009ad 100644 --- a/admin/src/components/TaxonomySelect.vue +++ b/admin/src/components/TaxonomySelect.vue @@ -16,9 +16,9 @@ @@ -68,6 +68,11 @@ function loadOptions() { } function onSelection() { + if (Array.isArray(selected.value)) { + selected.value = selected.value.filter((v) => options.value.find((o) => o.value === v)?.selectable); + } else { + selected.value = options.value.find((o) => o.value === selected.value)?.selectable ? selected.value : null; + } emit('update:modelValue', selected.value); } diff --git a/admin/src/components/TechnicalConstructionDialog.vue b/admin/src/components/TechnicalConstructionDialog.vue index c7427a3..5aa45ad 100644 --- a/admin/src/components/TechnicalConstructionDialog.vue +++ b/admin/src/components/TechnicalConstructionDialog.vue @@ -10,11 +10,8 @@ - - + + @@ -24,11 +21,7 @@
- +
- - - - - +
- + @@ -104,9 +84,7 @@ emit-value use-chips :label="$t('building_materials')" - :hint=" - $t('technical_construction_building_material_constituants_hint') - " + :hint="$t('technical_construction_building_material_constituants_hint')" class="q-mb-md" /> @@ -116,19 +94,8 @@ - - + + @@ -145,6 +112,7 @@ import { notifyError } from 'src/utils/notify'; import PhysicalEntityForm from 'src/components/PhysicalEntityForm.vue'; import FilesInput from 'src/components/FilesInput.vue'; import TaxonomySelect from 'src/components/TaxonomySelect.vue'; +import TextInput from 'src/components/TextInput.vue'; interface DialogProps { modelValue: boolean; @@ -168,9 +136,7 @@ const selected = ref({ const editMode = ref(false); const tab = ref('general'); const buildingMaterials = ref([]); -const buildingMaterialsOptions = ref< - { label: string | undefined; value: number | undefined }[] ->([]); +const buildingMaterialsOptions = ref<{ label: string | undefined; value: number | undefined }[]>([]); const isValid = computed(() => { return selected.value.name && selected.value.types; @@ -189,22 +155,18 @@ watch( bmService .find({ $limit: 100, - $select: ['id', 'name', 'type'], + $select: ['id', 'name', 'types'], filter: {}, }) .then((res) => { - buildingMaterialsOptions.value = res.data.map( - (item: BuildingMaterial) => ({ - label: item.name, - value: item.id, - }), - ); + buildingMaterialsOptions.value = res.data.map((item: BuildingMaterial) => ({ + label: item.name, + value: item.id, + })); }); if (editMode.value) { buildingMaterials.value = selected.value.building_materials - ? selected.value.building_materials.map( - (item: BuildingMaterial) => item.id, - ) + ? selected.value.building_materials.map((item: BuildingMaterial) => item.id) : []; } } diff --git a/admin/src/components/TextInput.vue b/admin/src/components/TextInput.vue new file mode 100644 index 0000000..2397082 --- /dev/null +++ b/admin/src/components/TextInput.vue @@ -0,0 +1,50 @@ + + + + diff --git a/admin/src/components/models.ts b/admin/src/components/models.ts index 81ec165..05427d1 100644 --- a/admin/src/components/models.ts +++ b/admin/src/components/models.ts @@ -21,4 +21,10 @@ export interface Option { value: string; label: string; level?: number; + selectable?: boolean; +} + +export interface Suggestions { + key: string; + options: string[]; } diff --git a/admin/src/i18n/en/index.js b/admin/src/i18n/en/index.js index 0b3d67e..c8d9ae3 100644 --- a/admin/src/i18n/en/index.js +++ b/admin/src/i18n/en/index.js @@ -20,142 +20,159 @@ export default { }, 'content-manager': 'Content manager', 'content-reviewer': 'Content reviewer', + absorption_coefficient_hint: 'α', + absorption_coefficient: 'Absorption coefficient (at 500Hz)', + acoustic: 'Acoustic', action: 'Action', add: 'Add', address: 'Address', admin: 'Administrator', administration: 'Administration', aerial: 'Aerial', + air_tightness_hint: 'δ', + air_tightness: 'Air tightness', + all_items_indexed: 'All items indexed ({count})', amount: 'Amount', areaDelivery: 'Area of delivery', + article_bottom_bd_hint: 'Specialities.', + article_bottom_bm_hint: 'Usage and handling.', + article_bottom_pro_hint: 'Engagements/expertise/regionalism', + article_bottom_tc_hint: 'Usage and handling.', + article_bottom: 'Article (bottom)', + article_top_bd_hint: 'Background, used materials/techniques/design.', + article_top_bm_hint: 'Background and manufacturing.', + article_top_pro_hint: 'Bio/history.', + article_top_tc_hint: 'Background, availibility and manufacturing.', + article_top: 'Article (top)', + building_building_material_used_hint: 'Building material used.', + building_element_professionals_hint: 'Professionals involved.', + building_element_technical_construction_used_hint: 'Technical construction used.', building_elements: 'Building elements', - building_materials: 'Building materials', - building_material_constituants_hint: - 'Building material are made of natural resources.', - natural_resource_constituants: 'Natural resources constituants', - technical_construction_natural_resource_constituants_hint: - 'Technical constructions are made of natural resources.', + building_material_constituants_hint: 'Building material are made of natural resources.', building_material_constituants: 'Building material constituants', - technical_construction_building_material_constituants_hint: - 'Technical constructions are made of building materials.', + building_materials: 'Building materials', + building_professionals_hint: 'Professionals involved.', + building_technical_construction_used_hint: 'Technical construction used.', buildings: 'Buildings', cancel: 'Cancel', + class: 'Class', classic: 'Classic', + client: 'Client', + compressive_strength_hint: 'σc, Mpa', + compressive_strength: 'Compressive strength', constituants: 'Constituants', content: 'Content', dark: 'Dark', dashboard: 'Dashboard', + density_hint: 'ρ, kg/m3', density: 'Density', + description_bd_hint: 'General description, building type.', + description_bm_hint: 'General description, main properties and uses.', + description_pro_hint: 'General description.', + description_tc_hint: 'General description, main properties and uses.', description: 'Description', + diffusivity_hint: 'D, *10^-6*m2/s', + diffusivity: 'Diffusivity', dimension: 'Dimension', + drop_index: 'Drop index', edit: 'Edit', + effusivity_hint: 'E, W*sqrt(h)/(m2*K)', + effusivity: 'Effusivity', email: 'Email', error_not_found: 'Oops. Nothing here...', + external_links: 'External links', + files: 'Files', + fire_resistance_class_hint: 'DIN EN 13501-1', + fire_resistance_class: 'Fire resistance class', + fire_resistance: 'Fire resistance', + fire: 'Fire', + general: 'General', + gross_internal_area: 'Gross internal area', + gross_internal_area_hint: 'In m2', guest: 'Guest', + help: 'Help', + high: 'High', + hygrothermal: 'Hygrothermal', inactive: 'Inactive', + index_all: 'Index all', + index_drop_error: 'Index drop error', + index_dropped: 'Index dropped', last_modification: 'Last modification', + layout_help: "This is a preview of the layout of each item. It indicates how each field is placed in the item's page in the main web portal of AREMA.", + layout: 'Layout', + legend: 'Legend', light: 'Light', + location: 'Location', logout: 'Logout', + low: 'Low', + markdown_guide: 'Markdown guide', + materials: 'Materials', + moisture_buffering_hint: 'MBV or w, kg/(m2*sqrt(h))', + moisture_buffering: 'Moisture buffering', + multimedia: 'Multimedia', name: 'Name', + natural_resource_constituants: 'Natural resources constituants', natural_resources: 'Natural resources', + no_building_elements: 'No building elements', no_results: 'No results', other: 'Other', + others: 'Others', password_edit_hint: 'Leave empty to NOT modify the password.', phone: 'Phone', + physical_characteristics: 'Physical characteristics', + preview: 'Preview', + professional_building_material_expertise_hint: 'Building material expertise.', + professional_technical_construction_expertise_hint: 'Technical construction expertise.', professionals: 'Professionals', + properties: 'Properties', radius: 'Radius', + reaction_to_fire_hint: 'DIN EN 13501-2', + reaction_to_fire: 'Reaction to fire', + relations: 'Relations', remove_selected: 'Remove selected items', role: 'Role', save: 'Save', + search_index_help: 'The search index is used to search for items by free text and tags. It is an essential functionality of the main web portal of AREMA. Each item is automatically indexed, as soon as it is created or updated; your intervention is normally not needed. Nevertheless, for advanced administration purpose, it is possible to drop the index at any anytime and then re-index all the items.', + search_index: 'Search index', + settlement_hint: 'mm/m', + settlement: 'Settlement', + shrinkage_hint: 'mm/m', + shrinkage: 'Shrinkage', + side_note_bm_hint: 'Practice hints and special considerations.', + side_note_pro_hint: 'Main projects/highlights.', + side_note_tc_hint: 'Practice hints and special considerations.', + side_note: 'Side note', + sound_reduction_index_hint: 'Rw, dB', + sound_reduction_index: 'Weighted sound reduction index', + status: 'Status', + std: 'Standard', + structural: 'Structural', + technical_construction_building_material_constituants_hint: 'Technical constructions are made of building materials.', + technical_construction_natural_resource_constituants_hint: 'Technical constructions are made of natural resources.', + technical_construction: 'Technical construction', technical_constructions: 'Technical constructions', + tensile_strength_hint: 'σc, Mpa', + tensile_strength: 'Tensile strength', + thermal_capacity_hint: 'c, J(kg*K)', + thermal_capacity: 'Thermal capacity', + thermal_conductivity_hint: 'λ, W/(m*K)', + thermal_conductivity: 'Thermal conductivity', type: 'Type', types: 'Types', + u_hint: 'U, W/(m2*K)', + u: 'U', + upload_files_hint: 'Upload one or more image, video or document.', + upload_files: 'Upload files', + url: 'URL', + url_hint: 'URL of an image, a video or a file. Press enter to validate.', user: 'User', users: 'Users', - website: 'Website', - zone: 'Zone', - general: 'General', - properties: 'Properties', - structural: 'Structural', - hygrothermal: 'Hygrothermal', - acoustic: 'Acoustic', - fire_resistance: 'Fire resistance', - others: 'Others', - compressive_strength: 'Compressive strength', - tensile_strength: 'Tensile strength', - youngs_modulus: 'Youngs modulus', - shrinkage: 'Shrinkage', - settlement: 'Settlement', - thermal_conductivity: 'Thermal conductivity', - thermal_capacity: 'Thermal capacity', + vapor_diffusion_resistance_hint: 'µ', vapor_diffusion_resistance: 'Vapor diffusion resistance', - moisture_buffering: 'Moisture buffering', - u: 'U', - effusivity: 'Effusivity', - diffusivity: 'Diffusivity', - absorption_coefficient: 'Absorption coefficient (at 500Hz)', - sound_reduction_index: 'Weighted sound reduction index', - reaction_to_fire: 'Reaction to fire', - building_material_class: 'Building material class', - fire_resistance_class: 'Fire resistance class', - air_tightness: 'Air tightness', - density_hint: 'ρ, kg/m3', - compressive_strength_hint: 'σc, Mpa', - tensile_strength_hint: 'σc, Mpa', + website: 'Website', + write: 'Write', + year_of_construction: 'Year of construction', youngs_modulus_hint: 'E, Mpa', - shrinkage_hint: 'mm/m', - settlement_hint: 'mm/m', - thermal_conductivity_hint: 'λ, W/(m*K)', - thermal_capacity_hint: 'c, J(kg*K)', - vapor_diffusion_resistance_hint: 'µ', - moisture_buffering_hint: 'MBV or w, kg/(m2*sqrt(h))', - u_hint: 'U, W/(m2*K)', - effusivity_hint: 'E, W*sqrt(h)/(m2*K)', - diffusivity_hint: 'D, *10^-6*m2/s', - absorption_coefficient_hint: 'α', - sound_reduction_index_hint: 'Rw, dB', - reaction_to_fire_hint: 'DIN 4102-1, RF', - building_material_class_hint: 'DIN EN ISO 11925-2', - fire_resistance_class_hint: 'DIN 4102-2:1977-09', - air_tightness_hint: 'δ', - professional_building_material_expertise_hint: 'Building material expertise.', - professional_technical_construction_expertise_hint: - 'Technical construction expertise.', - building_building_material_used_hint: 'Building material used.', - building_technical_construction_used_hint: 'Technical construction used.', - building_professionals_hint: 'Professionals involved.', - layout: 'Layout', - layout_help: - "This is a preview of the layout of each item. It indicates how each field is placed in the item's page in the main web portal of AREMA.", - physical_characteristics: 'Physical characteristics', - article_top: 'Article (top)', - article_bottom: 'Article (bottom)', - multimedia: 'Multimedia', - legend: 'Legend', - side_note: 'Side note', - external_links: 'External links', - relations: 'Relations', - location: 'Location', - upload_file: 'Upload file', - upload_file_hint: 'Upload an image, a video or a document.', - status: 'Status', - materials: 'Materials', - fire: 'Fire', - class: 'Class', - low: 'Low', - std: 'Standard', - high: 'High', - technical_construction: 'Technical construction', - building_element_technical_construction_used_hint: - 'Technical construction used.', - building_element_professionals_hint: 'Professionals involved.', - no_building_elements: 'No building elements', - all_items_indexed: 'All items indexed ({count})', - search_index: 'Search index', - search_index_help: - 'The search index is used to search for items by free text and tags. It is an essential functionality of the main web portal of AREMA. Each item is automatically indexed, as soon as it is created or updated; your intervention is normally not needed. Nevertheless, for advanced administration purpose, it is possible to drop the index at any anytime and then re-index all the items.', - drop_index: 'Drop index', - index_all: 'Index all', - index_dropped: 'Index dropped', - index_drop_error: 'Index drop error', + youngs_modulus: 'Youngs modulus', + zone: 'Zone', }; diff --git a/admin/src/i18n/fr/index.js b/admin/src/i18n/fr/index.js index 911570b..187a41c 100644 --- a/admin/src/i18n/fr/index.js +++ b/admin/src/i18n/fr/index.js @@ -20,95 +20,112 @@ export default { admin: 'Administrateur', administration: 'Administration', aerial: 'Aérien', + all_items_indexed: 'Tous les éléments sont indexés ({count}).', amount: 'Quantité', areaDelivery: 'Zone de livraison', + article_bottom_bd_hint: 'Spécificités.', + article_bottom_bm_hint: 'Utilisation et mise en oeuvre.', + article_bottom_pro_hint: 'Engagements/expertise/regionalisme', + article_bottom_tc_hint: 'Utilisation et mise en oeuvre.', + article_bottom: 'Article (bas)', + article_top_bd_hint: 'Origine, matériaux/techniques/design utilisés.', + article_top_bm_hint: 'Origine et fabrication.', + article_top_pro_hint: 'Bio/histoire.', + article_top_tc_hint: 'Origine, disponibilité et fabrication.', + article_top: 'Article (haut)', + building_building_material_used_hint: 'Matériaux de construction utilisés.', + building_element_professionals_hint: 'Professionnels impliqués dans la construction.', + building_element_technical_construction_used_hint: 'Constructions techniques utilisées.', building_elements: 'Éléments de bâtiment', - building_materials: 'Matériaux de construction', - building_material_constituants_hint: - 'Les matériaux de construction sont fabriqués à partir de ressources naturelles.', - natural_resource_constituants: 'Ressources naturelles constituants', - technical_construction_natural_resource_constituants_hint: - 'Les constructions techniques sont fabriquées à partir de ressources naturelles.', + building_material_constituants_hint: 'Les matériaux de construction sont fabriqués à partir de ressources naturelles.', building_material_constituants: 'Matériaux de construction constituants', - technical_construction_building_material_constituants_hint: - 'Les constructions techniques sont fabriquées à partir de matériaux de construction.', + building_materials: 'Matériaux de construction', + building_professionals_hint: 'Professionnels impliqués dans la construction.', + building_technical_construction_used_hint: 'Constructions techniques utilisées.', buildings: 'Bâtiments', cancel: 'Annuler', + class: 'Classe', classic: 'Classique', + client: 'Client', constituants: 'Constituants', content: 'Contenu', dark: 'Sombre', dashboard: 'Tableau de bord', density: 'Densité', + description_bd_hint: 'Description générale et type de bâtiment.', + description_bm_hint: 'Description générale, principales propriétés et utilisations.', + description_pro_hint: 'Description générale.', + description_tc_hint: 'Description générale, principales propriétés et utilisations.', description: 'Description', dimension: 'Dimension', + drop_index: "Supprimer l'index", edit: 'Editer', email: 'Courriel', error_not_found: "Oups. Il n'y a rien ici...", + external_links: 'Liens externes', + files: 'Fichiers', + fire: 'Feu', + general: 'Général', + gross_internal_area: 'Surface brute intérieure', + gross_internal_area_hint: 'En m2', guest: 'Invité', + help: 'Aide', + high: 'Haut', inactive: 'Inactif', + index_all: 'Tout indexer', + index_drop_error: "Erreur lors de la suppression de l'index", + index_dropped: 'Index supprimé', last_modification: 'Dernière modification', + layout_help: "Ceci est un exemple de disposition de page de chaque élément permettant de visualiser comment chaque champ est placé dans la page correspondante du portail web d'AREMA.", + layout: 'Disposition', + legend: 'Légende', light: 'Clair', + location: 'Emplacement', logout: 'Déconnexion', + low: 'Bas', + markdown_guide: 'Guide Markdown', + materials: 'Matériaux', + multimedia: 'Multimedia', name: 'Nom', + natural_resource_constituants: 'Ressources naturelles constituants', natural_resources: 'Ressources naturelles', + no_building_elements: 'Aucun élément de bâtiment', no_results: 'Aucun résultat', other: 'Autre', password_edit_hint: 'Laisser vide pour NE PAS modifier le mot de passe.', phone: 'Téléphone', + physical_characteristics: 'Caractéristiques physiques', + preview: 'Aperçu', + professional_building_material_expertise_hint: 'Expertise en matériaux de construction.', + professional_technical_construction_expertise_hint: 'Expertise en constructions techniques.', professionals: 'Professionnels', radius: 'Rayon', + relations: 'Relations', remove_selected: 'Supprimer les items sélectionnés', role: 'Rôle', save: 'Sauvegarder', + search_index_help: "L'index de recherche permet de rechercher des éléments par texte libre et par tags. Il s'agit d'une fonctionnalité essentielle du portail web principal d'AREMA. Chaque élément est automatiquement indexé, dès sa création ou sa mise à jour ; votre intervention n'est normalement pas nécessaire. Néanmoins, pour des besoins d'administration avancés, il est possible de supprimer l'index à tout moment, puis de réindexer tous les éléments.", + search_index: 'Index de recherche', + side_note: 'Note de page', + side_not_bm_hint: 'Conseils pratiques et considérations spéciales.', + side_note_pro_hint: 'Projets principaux/faits saillants.', + side_not_tc_hint: 'Conseils pratiques et considérations spéciales.', + status: 'Etat', + std: 'Standard', + technical_construction_building_material_constituants_hint: 'Les constructions techniques sont fabriquées à partir de matériaux de construction.', + technical_construction_natural_resource_constituants_hint: 'Les constructions techniques sont fabriquées à partir de ressources naturelles.', + technical_construction: 'Construction technique', technical_constructions: 'Constructions techniques', type: 'Type', types: 'Types', + upload_files_hint: 'Téléverser une ou plusieurs images, vidéos ou fichiers PDF.', + upload_files: 'Téléverser des fichiers', + url: 'URL', + url_hint: "URL d'une image, d'une vidéo ou d'un fichier PDF. Taper 'Entrée' pour valider.", user: 'Utilisateur', users: 'Utilisateurs', website: 'Site web', + write: 'Ecrire', + year_of_construction: 'Année de construction', zone: 'Zone', - professional_building_material_expertise_hint: - 'Expertise en matériaux de construction.', - professional_technical_construction_expertise_hint: - 'Expertise en constructions techniques.', - building_building_material_used_hint: 'Matériaux de construction utilisés.', - building_technical_construction_used_hint: - 'Constructions techniques utilisées.', - building_professionals_hint: 'Professionnels impliqués dans la construction.', - layout: 'Disposition', - layout_help: - "Ceci est un exemple de disposition de page de chaque élément permettant de visualiser comment chaque champ est placé dans la page correspondante du portail web d'AREMA.", - physical_characteristics: 'Caractéristiques physiques', - article_top: 'Article (haut)', - article_bottom: 'Article (bas)', - multimedia: 'Multimedia', - legend: 'Légende', - side_note: 'Note de page', - external_links: 'Liens externes', - relations: 'Relations', - location: 'Emplacement', - upload_file: 'Téléverser un fichier', - upload_file_hint: 'Téléverser une image, une vidéo ou un fichier PDF.', - status: 'Etat', - materials: 'Matériaux', - fire: 'Feu', - class: 'Classe', - low: 'Bas', - std: 'Standard', - high: 'Haut', - technical_construction: 'Construction technique', - building_element_technical_construction_used_hint: - 'Constructions techniques utilisées.', - building_element_professionals_hint: - 'Professionnels impliqués dans la construction.', - no_building_elements: 'Aucun élément de bâtiment', - all_items_indexed: 'Tous les éléments sont indexés ({count}).', - search_index: 'Index de recherche', - search_index_help: - "L'index de recherche permet de rechercher des éléments par texte libre et par tags. Il s'agit d'une fonctionnalité essentielle du portail web principal d'AREMA. Chaque élément est automatiquement indexé, dès sa création ou sa mise à jour ; votre intervention n'est normalement pas nécessaire. Néanmoins, pour des besoins d'administration avancés, il est possible de supprimer l'index à tout moment, puis de réindexer tous les éléments.", - drop_index: "Supprimer l'index", - index_all: 'Tout indexer', - index_dropped: 'Index supprimé', - index_drop_error: "Erreur lors de la suppression de l'index", }; diff --git a/admin/src/layouts/MainLayout.vue b/admin/src/layouts/MainLayout.vue index cfe30eb..cbcd400 100644 --- a/admin/src/layouts/MainLayout.vue +++ b/admin/src/layouts/MainLayout.vue @@ -2,14 +2,7 @@ - + {{ $t('main.brand') }} @@ -17,12 +10,7 @@ - + @@ -68,9 +56,7 @@ - {{ - $t('technical_constructions') - }} + {{ $t('technical_constructions') }} @@ -90,20 +76,15 @@ - - {{ - $t('administration') - }} - - - - - - - {{ $t('users') }} - - - + {{ $t('help') }} + + + + + + {{ $t('markdown_guide') }} + + @@ -146,4 +127,8 @@ function toggleLeftDrawer() { function onLogout() { authStore.logout(); } + +function onOpenUrl(url: string) { + window.open(url, '_blank'); +} diff --git a/admin/src/models.ts b/admin/src/models.ts index 8fb1da4..91928a5 100644 --- a/admin/src/models.ts +++ b/admin/src/models.ts @@ -10,7 +10,8 @@ export interface FileRef { } export interface FileItem { - ref: FileRef; + ref?: FileRef; + url?: string; legend?: string; } @@ -61,7 +62,6 @@ export interface PhysicalEntity extends Entity { absorption_coefficient?: number; sound_reduction_index?: number; reaction_to_fire?: string; - building_material_class?: string; fire_resistance_class?: string; air_tightness?: number; @@ -80,6 +80,8 @@ export interface PhysicalEntity extends Entity { diffusivity_low?: number; absorption_coefficient_low?: number; sound_reduction_index_low?: number; + reaction_to_fire_low?: string; + fire_resistance_class_low?: string; air_tightness_low?: number; density_high?: number; @@ -97,6 +99,8 @@ export interface PhysicalEntity extends Entity { diffusivity_high?: number; absorption_coefficient_high?: number; sound_reduction_index_high?: number; + reaction_to_fire_high?: string; + fire_resistance_class_high?: string; air_tightness_high?: number; } @@ -147,6 +151,9 @@ export interface Building extends Entity { type?: string; status?: string; materials?: string[]; + client?: string; + gross_internal_area?: number; + year?: number; address?: string; long?: number; lat?: number; diff --git a/admin/src/stores/taxonomies.ts b/admin/src/stores/taxonomies.ts index 3714f09..57ca5cc 100644 --- a/admin/src/stores/taxonomies.ts +++ b/admin/src/stores/taxonomies.ts @@ -23,24 +23,17 @@ export const useTaxonomyStore = defineStore('taxonomies', () => { return `${URN_PREFIX}:${entityType}:${Array.isArray(path) ? path.join('.') : path}`; } - async function getTaxonomy( - entityType: string, - ): Promise { + async function getTaxonomy(entityType: string): Promise { if (!taxonomies.value) { return init().then(() => { if (!taxonomies.value) return undefined; else return getTaxonomy(entityType); }); } - return Promise.resolve( - taxonomies.value?.taxonomy.find((tx) => tx.id === entityType), - ); + return Promise.resolve(taxonomies.value?.taxonomy.find((tx) => tx.id === entityType)); } - async function getTaxonomyNode( - entityType: string, - path: string | string[] = [], - ): Promise { + async function getTaxonomyNode(entityType: string, path: string | string[] = []): Promise { const tx = await getTaxonomy(entityType); if (!tx) return Promise.resolve(undefined); return Promise.resolve(getNodeFromPath(tx, path)); @@ -56,10 +49,7 @@ export const useTaxonomyStore = defineStore('taxonomies', () => { return getNodeFromPath(root, tokens[1]); } - function getNodeFromPath( - node: TaxonomyNode, - path: string | string[] = [], - ): TaxonomyNode | undefined { + function getNodeFromPath(node: TaxonomyNode, path: string | string[] = []): TaxonomyNode | undefined { const elements = Array.isArray(path) ? path : path.split('.'); if (!elements || elements.length === 0) return node; if (!node.children || node.children.length === 0) return node; @@ -70,9 +60,7 @@ export const useTaxonomyStore = defineStore('taxonomies', () => { return child; } - function getLabel( - labels: Record | undefined, - ): string | undefined { + function getLabel(labels: Record | undefined): string | undefined { if (labels === undefined) return undefined; if (labels[locale.value]) return labels[locale.value]; return labels['en']; @@ -94,15 +82,11 @@ export const useTaxonomyStore = defineStore('taxonomies', () => { value: toUrn(entityType, [...prefix, child.id]), label: getLabel(child.names) || child.id, level: level, + selectable: child.children ? child.children.length === 0 : true, }; options.push(opt); if (child.children?.length) { - const opts = asOptions( - entityType, - child, - [...prefix, child.id], - level + 1, - ); + const opts = asOptions(entityType, child, [...prefix, child.id], level + 1); options.push(...opts); } } diff --git a/backend/api/data/building-material.yml b/backend/api/data/building-material.yml index 31a28d7..c431881 100644 --- a/backend/api/data/building-material.yml +++ b/backend/api/data/building-material.yml @@ -1,7 +1,7 @@ taxonomy: - id: building-material names: - en: Building materials + en: Building Materials de: Baustoffe fr: Matériaux de construction it: Materiali da costruzione diff --git a/backend/api/data/natural-resource.yml b/backend/api/data/natural-resource.yml index 306bfab..1b432a8 100644 --- a/backend/api/data/natural-resource.yml +++ b/backend/api/data/natural-resource.yml @@ -1,7 +1,7 @@ taxonomy: - id: natural-resource names: - en: Natural resources + en: Natural Resources de: Natürliche Ressourcen fr: Ressources naturelles it: Risorse naturali @@ -47,5 +47,5 @@ taxonomy: names: en: "Other Fibers" de: "Andere Fasern" - fr: "Autres Fibres" - it: "Altre Fibre" \ No newline at end of file + fr: "Autres fibres" + it: "Altre fibre" \ No newline at end of file diff --git a/backend/api/data/technical-construction.yml b/backend/api/data/technical-construction.yml index de06695..1b3deb3 100644 --- a/backend/api/data/technical-construction.yml +++ b/backend/api/data/technical-construction.yml @@ -1,7 +1,7 @@ taxonomy: - id: technical-construction names: - en: Technical constructions + en: Technical Constructions de: Technische Konstruktionen fr: Constructions techniques it: Costruzioni tecniche @@ -13,135 +13,163 @@ taxonomy: fr: Type it: Tipo children: - - id: be1 - names: - en: Foundation - de: Fundament - fr: Fondation - it: Fondazione - - id: be2 - names: - en: Floor slab ground floor - de: Bodenplatte EG - fr: Dalle de sol - it: Pavimento terra - - id: be3 - names: - en: Exterior wall above ground - de: Aussenwand über Terrain - fr: Mur extérieur au-dessus du sol - it: Parete esterna sopra il terreno - - id: be4 - names: - en: Exterior wall under ground - de: Aussenwand unter Terrain - fr: Mur extérieur sous le sol - it: Parete esterna sotto il terreno - - id: be5 - names: - en: Balcony - de: Balkon - fr: Balcon - it: Balcone - - id: be6 - names: - en: Interior wall - de: Innenwand - fr: Mur intérieur - it: Parete interna - - id: be7 - names: - en: Roof - de: Dach - fr: Toit - it: Tetto - - id: be8 - names: - en: Ceiling - de: Decke - fr: Plafond - it: Soffitto - - id: be9 - names: - en: Column - de: Stütze - fr: Colonne - it: Colonna - - id: be10 - names: - en: Stair - de: Treppe - fr: Escalier - it: Scala - - id: be11 - names: - en: Roof covering - de: Bedachung - fr: Couverture de toit - it: Copertura del tetto - - id: be12 - names: - en: Exterior thermal insulation - de: Aussenwärmedämmung - fr: Isolation thermique extérieure - it: Isolamento termico esterno - - id: be13 - names: - en: Window & door - de: Fenster & Türen - fr: Fenêtre & Porte - it: Finestra & Porta - - id: be14 - names: - en: Exterior wall finishing - de: Äussere Beschichtung unter Terrain - fr: Finition mur extérieur sous-sol - it: Rivestimento parete esterna seminterrato - - id: be15 - names: - en: Exterior wall finishing - de: Fassadenbekleidung - fr: Finition mur extérieur au-dessus du sol - it: Rivestimento parete esterna sopra il terreno - - id: be16 - names: - en: Sun & weather protection - de: Sonnen & Wetterschutz - fr: Protection solaire et contre les intempéries - it: Protezione solare e contro le intemperie - - id: be17 - names: - en: Interior wall finishing - de: Wandbekleidung - fr: Finition mur intérieur - it: Rivestimento parete interna - - id: be18 - names: - en: Interior door - de: Innentür - fr: Porte intérieure - it: Porta interna - - id: be19 - names: - en: Partition wall - de: Trennwand - fr: Cloison - it: Parete divisoria - - id: be20 - names: - en: Ceiling finishing - de: Deckenbekleidung - fr: Finition de plafond - it: Rivestimento soffitto - - id: be21 - names: - en: Floor covering - de: Bodenbelag - fr: Revêtement de sol - it: Rivestimento del pavimento - - id: be22 - names: - en: Technical equipement - de: Technik - fr: Équipement technique - it: Attrezzatura tecnica \ No newline at end of file + - id: structure + names: + en: Structure + de: Struktur + fr: Structure + it: Struttura + children: + - id: be1 + names: + en: Foundation + de: Fundament + fr: Fondation + it: Fondazione + - id: be2 + names: + en: Floor slab ground floor + de: Bodenplatte EG + fr: Dalle de sol + it: Pavimento terra + - id: be3 + names: + en: Exterior wall above ground + de: Aussenwand über Terrain + fr: Mur extérieur au-dessus du sol + it: Parete esterna sopra il terreno + - id: be4 + names: + en: Exterior wall under ground + de: Aussenwand unter Terrain + fr: Mur extérieur sous le sol + it: Parete esterna sotto il terreno + - id: be5 + names: + en: Balcony + de: Balkon + fr: Balcon + it: Balcone + - id: be6 + names: + en: Interior wall + de: Innenwand + fr: Mur intérieur + it: Parete interna + - id: be7 + names: + en: Roof + de: Dach + fr: Toit + it: Tetto + - id: be8 + names: + en: Ceiling + de: Decke + fr: Plafond + it: Soffitto + - id: be9 + names: + en: Column + de: Stütze + fr: Colonne + it: Colonna + - id: be10 + names: + en: Stair + de: Treppe + fr: Escalier + it: Scala + - id: envelope + names: + en: Envelope + de: Hülle + fr: Enveloppe + it: Involucro + children: + - id: be11 + names: + en: Roof covering + de: Bedachung + fr: Couverture de toit + it: Copertura del tetto + - id: be12 + names: + en: Exterior thermal insulation + de: Aussenwärmedämmung + fr: Isolation thermique extérieure + it: Isolamento termico esterno + - id: be13 + names: + en: Window & door + de: Fenster & Türen + fr: Fenêtre & Porte + it: Finestra & Porta + - id: be14 + names: + en: Exterior wall finishing + de: Äussere Beschichtung unter Terrain + fr: Finition mur extérieur sous-sol + it: Rivestimento parete esterna seminterrato + - id: be15 + names: + en: Exterior wall finishing + de: Fassadenbekleidung + fr: Finition mur extérieur au-dessus du sol + it: Rivestimento parete esterna sopra il terreno + - id: be16 + names: + en: Sun & weather protection + de: Sonnen & Wetterschutz + fr: Protection solaire et contre les intempéries + it: Protezione solare e contro le intemperie + - id: finishing + names: + en: Finishing + de: Ausbau + fr: Finition + it: Finitura + children: + - id: be17 + names: + en: Interior wall finishing + de: Wandbekleidung + fr: Finition mur intérieur + it: Rivestimento parete interna + - id: be18 + names: + en: Interior door + de: Innentür + fr: Porte intérieure + it: Porta interna + - id: be19 + names: + en: Partition wall + de: Trennwand + fr: Cloison + it: Parete divisoria + - id: be20 + names: + en: Ceiling finishing + de: Deckenbekleidung + fr: Finition de plafond + it: Rivestimento soffitto + - id: be21 + names: + en: Floor covering + de: Bodenbelag + fr: Revêtement de sol + it: Rivestimento del pavimento + - id: other + names: + en: Other + de: Sonstiges + fr: Autre + it: Altro + children: + - id: be22 + names: + en: Technical equipement + de: Technik + fr: Équipement technique + it: Attrezzatura tecnica \ No newline at end of file diff --git a/backend/api/models/domain.py b/backend/api/models/domain.py index ab8fbcc..1710efd 100644 --- a/backend/api/models/domain.py +++ b/backend/api/models/domain.py @@ -10,7 +10,8 @@ class FileItem(BaseModel): - ref: FileRef + ref: Optional[FileRef] = Field(default=None) + url: Optional[str] = Field(default=None) legend: Optional[str] = Field(default=None) @@ -47,7 +48,6 @@ class PhysicalEntity(Entity): absorption_coefficient: Optional[float] = Field(default=None) sound_reduction_index: Optional[float] = Field(default=None) reaction_to_fire: Optional[str] = Field(default=None) - building_material_class: Optional[str] = Field(default=None) fire_resistance_class: Optional[str] = Field(default=None) air_tightness: Optional[float] = Field(default=None) @@ -66,6 +66,8 @@ class PhysicalEntity(Entity): diffusivity_low: Optional[float] = Field(default=None) absorption_coefficient_low: Optional[float] = Field(default=None) sound_reduction_index_low: Optional[float] = Field(default=None) + reaction_to_fire_low: Optional[str] = Field(default=None) + fire_resistance_class_low: Optional[str] = Field(default=None) air_tightness_low: Optional[float] = Field(default=None) density_high: Optional[float] = Field(default=None) @@ -83,6 +85,8 @@ class PhysicalEntity(Entity): diffusivity_high: Optional[float] = Field(default=None) absorption_coefficient_high: Optional[float] = Field(default=None) sound_reduction_index_high: Optional[float] = Field(default=None) + reaction_to_fire_high: Optional[str] = Field(default=None) + fire_resistance_class_high: Optional[str] = Field(default=None) air_tightness_high: Optional[float] = Field(default=None) # Association tables @@ -240,6 +244,9 @@ class BuildingBase(Entity): status: Optional[str] = Field(default=None) materials: Optional[List[str]] = Field( default=None, sa_column=Column(JSON)) + client: Optional[str] = Field(default=None) + gross_internal_area: Optional[float] = Field(default=None) + year: Optional[int] = Field(default=None) address: Optional[str] = Field(default=None) long: Optional[float] = Field(default=None) lat: Optional[float] = Field(default=None) diff --git a/backend/api/services/building_materials.py b/backend/api/services/building_materials.py index ccf538a..ad1fdaa 100644 --- a/backend/api/services/building_materials.py +++ b/backend/api/services/building_materials.py @@ -4,14 +4,14 @@ from sqlalchemy.orm import selectinload from sqlmodel import select from fastapi import HTTPException -from api.models.domain import FileItem, BuildingMaterial, BuildingMaterial, NaturalResource +from api.models.domain import FileItem, BuildingMaterial, BuildingMaterial, NaturalResource, BuildingMaterialNaturalResource, TechnicalConstructionBuildingMaterial, BuildingBuildingMaterial, ProfessionalBuildingMaterial from api.models.query import BuildingMaterialResult, BuildingMaterialDraft from enacit4r_sql.utils.query import QueryBuilder from datetime import datetime from api.services.s3 import s3_client from api.utils.files import moveTempFile from api.auth import User -from api.services.search import IndexService +from api.services.search import EntityIndexer class BuildingMaterialQueryBuilder(QueryBuilder): @@ -43,14 +43,14 @@ def __init__(self, session: AsyncSession): async def indexAll(self) -> int: """Index all building materials""" - indexService = IndexService() + indexService = EntityIndexer() # delete documents of this type indexService.deleteEntities(self.entityType) # add all documents count = 0 for entity in (await self.session.exec(select(BuildingMaterial))).all(): indexService.addEntity( - self.entityType, entity, self._makeTags(entity)) + self.entityType, entity, self._makeTags(entity), await self._makeRelations(entity)) count += 1 debug(f"Indexed {count} building materials") return count @@ -86,7 +86,7 @@ async def delete(self, id: int) -> BuildingMaterial: await self.session.delete(entity) await self.session.commit() # delete from index - IndexService().deleteEntity(self.entityType, entity.id) + EntityIndexer().deleteEntity(self.entityType, entity.id) return entity async def find(self, filter: dict, fields: list, sort: list, range: list) -> BuildingMaterialResult: @@ -134,13 +134,16 @@ async def create(self, payload: BuildingMaterialDraft, user: User = None) -> Bui s3_folder = f"{self.folder}/{entity.id}" new_files = [] for i, item_dict in enumerate(entity.files): - item = await moveTempFile(FileItem(**item_dict), i, s3_folder) - new_files.append(item.model_dump()) + if "ref" in item_dict: + item = await moveTempFile(FileItem(**item_dict), i, s3_folder) + new_files.append(item.model_dump()) + elif "url" in item_dict: + new_files.append(item_dict) entity.files = new_files await self.session.commit() # add to index - IndexService().addEntity( - self.entityType, entity, self._makeTags(entity)) + EntityIndexer().addEntity( + self.entityType, entity, self._makeTags(entity), await self._makeRelations(entity)) return entity async def update(self, id: int, payload: BuildingMaterialDraft, user: User = None) -> BuildingMaterial: @@ -165,8 +168,11 @@ async def update(self, id: int, payload: BuildingMaterialDraft, user: User = Non s3_folder = f"{self.folder}/{entity.id}" new_files = [] for i, item_dict in enumerate(entity.files): - item = await moveTempFile(FileItem(**item_dict), i, s3_folder) - new_files.append(item.model_dump()) + if "ref" in item_dict: + item = await moveTempFile(FileItem(**item_dict), i, s3_folder) + new_files.append(item.model_dump()) + elif "url" in item_dict: + new_files.append(item_dict) entity.files = new_files # handle relationships new_nrs = await self._get_natural_resources(payload.natural_resource_ids) @@ -174,8 +180,8 @@ async def update(self, id: int, payload: BuildingMaterialDraft, user: User = Non entity.natural_resources.extend(new_nrs) await self.session.commit() # update in index - IndexService().updateEntity( - self.entityType, entity, self._makeTags(entity)) + EntityIndexer().updateEntity( + self.entityType, entity, self._makeTags(entity), await self._makeRelations(entity)) return entity def _makeTags(self, entity: BuildingMaterial) -> list[str]: @@ -186,5 +192,19 @@ def _makeTags(self, entity: BuildingMaterial) -> list[str]: tags.extend(entity.materials) return tags + async def _makeRelations(self, entity: BuildingMaterial) -> list[str]: + relations = (await self.session.exec(select(BuildingMaterialNaturalResource).where(BuildingMaterialNaturalResource.building_material_id == entity.id))).all() + relates_to = [ + f"natural-resource:{rel.natural_resource_id}" for rel in relations] + relations = (await self.session.exec(select(TechnicalConstructionBuildingMaterial).where(TechnicalConstructionBuildingMaterial.building_material_id == entity.id))).all() + relates_to.extend( + [f"technical-construction:{rel.technical_construction_id}" for rel in relations]) + relations = (await self.session.exec(select(BuildingBuildingMaterial).where(BuildingBuildingMaterial.building_material_id == entity.id))).all() + relates_to.extend([f"building:{rel.building_id}" for rel in relations]) + relations = (await self.session.exec(select(ProfessionalBuildingMaterial).where(ProfessionalBuildingMaterial.building_material_id == entity.id))).all() + relates_to.extend( + [f"professional:{rel.professional_id}" for rel in relations]) + return relates_to + async def _get_natural_resources(self, ids: list[int]): return await self.session.exec(select(NaturalResource).filter(NaturalResource.id.in_(ids))) diff --git a/backend/api/services/buildings.py b/backend/api/services/buildings.py index ae148dc..5243986 100644 --- a/backend/api/services/buildings.py +++ b/backend/api/services/buildings.py @@ -4,7 +4,7 @@ from sqlalchemy.orm import selectinload from sqlmodel import select from fastapi import HTTPException -from api.models.domain import FileItem, Building, BuildingMaterial, BuildingElement, Professional +from api.models.domain import FileItem, Building, BuildingMaterial, BuildingElement, Professional, BuildingBuildingMaterial, ProfessionalBuilding from api.models.query import BuildingDraft, BuildingResult, BuildingElementDraft from api.services.building_elements import BuildingElementService from enacit4r_sql.utils.query import QueryBuilder @@ -12,7 +12,7 @@ from api.services.s3 import s3_client from api.utils.files import moveTempFile from api.auth import User -from api.services.search import IndexService +from api.services.search import EntityIndexer class BuildingQueryBuilder(QueryBuilder): @@ -45,14 +45,14 @@ def __init__(self, session: AsyncSession): async def indexAll(self) -> int: """Index all buildings""" - indexService = IndexService() + indexService = EntityIndexer() # delete documents of this type indexService.deleteEntities(self.entityType) # add all documents count = 0 for entity in (await self.session.exec(select(Building))).all(): indexService.addEntity( - self.entityType, entity, self._makeTags(entity)) + self.entityType, entity, self._makeTags(entity), await self._makeRelations(entity)) count += 1 debug(f"Indexed {count} buildings") return count @@ -92,7 +92,7 @@ async def delete(self, id: int) -> Building: await self.session.delete(entity) await self.session.commit() # delete from index - IndexService().deleteEntity(self.entityType, entity.id) + EntityIndexer().deleteEntity(self.entityType, entity.id) return entity async def find(self, filter: dict, fields: list, sort: list, range: list) -> BuildingResult: @@ -146,8 +146,11 @@ async def create(self, payload: BuildingDraft, user: User = None) -> Building: s3_folder = f"{self.folder}/{entity.id}" new_files = [] for i, item_dict in enumerate(entity.files): - item = await moveTempFile(FileItem(**item_dict), i, s3_folder) - new_files.append(item.model_dump()) + if "ref" in item_dict: + item = await moveTempFile(FileItem(**item_dict), i, s3_folder) + new_files.append(item.model_dump()) + elif "url" in item_dict: + new_files.append(item_dict) entity.files = new_files await self.session.commit() @@ -155,8 +158,8 @@ async def create(self, payload: BuildingDraft, user: User = None) -> Building: await self._apply_building_elements(entity.id, payload.building_elements) # add to index - IndexService().addEntity( - self.entityType, entity, self._makeTags(entity)) + EntityIndexer().addEntity( + self.entityType, entity, self._makeTags(entity), await self._makeRelations(entity)) return entity @@ -183,8 +186,11 @@ async def update(self, id: int, payload: BuildingDraft, user: User = None) -> Bu s3_folder = f"{self.folder}/{entity.id}" new_files = [] for i, item_dict in enumerate(entity.files): - item = await moveTempFile(FileItem(**item_dict), i, s3_folder) - new_files.append(item.model_dump()) + if "ref" in item_dict: + item = await moveTempFile(FileItem(**item_dict), i, s3_folder) + new_files.append(item.model_dump()) + elif "url" in item_dict: + new_files.append(item_dict) entity.files = new_files # handle relationships new_bms = await self._get_building_materials(payload.building_material_ids) @@ -197,8 +203,8 @@ async def update(self, id: int, payload: BuildingDraft, user: User = None) -> Bu # handle building elements await self._apply_building_elements(entity.id, payload.building_elements) # update in index - IndexService().updateEntity( - self.entityType, entity, self._makeTags(entity)) + EntityIndexer().updateEntity( + self.entityType, entity, self._makeTags(entity), await self._makeRelations(entity)) return entity async def _apply_building_elements(self, building_id: int, elements: list[BuildingElementDraft]): @@ -229,6 +235,18 @@ def _makeTags(self, entity: Building) -> list[str]: tags.extend(entity.materials) return tags + async def _makeRelations(self, entity: Building) -> list[str]: + relations = (await self.session.exec(select(BuildingBuildingMaterial).where(BuildingBuildingMaterial.building_id == entity.id))).all() + relates_to = [ + f"building-material:{rel.building_material_id}" for rel in relations] + relations = (await self.session.exec(select(ProfessionalBuilding).where(ProfessionalBuilding.building_id == entity.id))).all() + relates_to.extend( + [f"professional:{rel.professional_id}" for rel in relations]) + relations = (await self.session.exec(select(BuildingElement).where(BuildingElement.building_id == entity.id))).all() + relates_to.extend( + [f"technical-construction:{rel.technical_construction_id}" for rel in relations]) + return relates_to + async def _get_building_materials(self, ids: list[int]): return await self.session.exec(select(BuildingMaterial).filter(BuildingMaterial.id.in_(ids))) diff --git a/backend/api/services/natural_resources.py b/backend/api/services/natural_resources.py index d26f92b..41a033d 100644 --- a/backend/api/services/natural_resources.py +++ b/backend/api/services/natural_resources.py @@ -4,14 +4,14 @@ from sqlalchemy.orm import selectinload from sqlmodel import select from fastapi import HTTPException -from api.models.domain import FileItem, NaturalResource, BuildingMaterial +from api.models.domain import FileItem, NaturalResource, BuildingMaterial, BuildingMaterialNaturalResource from api.models.query import NaturalResourceResult from enacit4r_sql.utils.query import QueryBuilder from datetime import datetime from api.services.s3 import s3_client from api.utils.files import moveTempFile from api.auth import User -from api.services.search import IndexService +from api.services.search import EntityIndexer class NaturalResourceQueryBuilder(QueryBuilder): @@ -43,14 +43,14 @@ def __init__(self, session: AsyncSession): async def indexAll(self) -> int: """Index all natural resources""" - indexService = IndexService() + indexService = EntityIndexer() # delete documents of this type indexService.deleteEntities(self.entityType) # add all documents count = 0 for entity in (await self.session.exec(select(NaturalResource))).all(): indexService.addEntity( - self.entityType, entity, self._makeTags(entity)) + self.entityType, entity, self._makeTags(entity), await self._makeRelations(entity)) count += 1 debug(f"Indexed {count} natural resources") return count @@ -84,7 +84,7 @@ async def delete(self, id: int) -> NaturalResource: await self.session.delete(entity) await self.session.commit() # delete from index - IndexService().deleteEntity(self.entityType, entity.id) + EntityIndexer().deleteEntity(self.entityType, entity.id) return entity async def find(self, filter: dict, fields: list, sort: list, range: list) -> NaturalResourceResult: @@ -128,13 +128,16 @@ async def create(self, payload: NaturalResource, user: User = None) -> NaturalRe s3_folder = f"{self.folder}/{entity.id}" new_files = [] for i, item_dict in enumerate(entity.files): - item = await moveTempFile(FileItem(**item_dict), i, s3_folder) - new_files.append(item.model_dump()) + if "ref" in item_dict: + item = await moveTempFile(FileItem(**item_dict), i, s3_folder) + new_files.append(item.model_dump()) + elif "url" in item_dict: + new_files.append(item_dict) entity.files = new_files await self.session.commit() # add to index - IndexService().addEntity( - self.entityType, entity, self._makeTags(entity)) + EntityIndexer().addEntity( + self.entityType, entity, self._makeTags(entity), await self._makeRelations(entity)) return entity async def update(self, id: int, payload: NaturalResource, user: User = None) -> NaturalResource: @@ -158,14 +161,23 @@ async def update(self, id: int, payload: NaturalResource, user: User = None) -> s3_folder = f"{self.folder}/{entity.id}" new_files = [] for i, item_dict in enumerate(entity.files): - item = await moveTempFile(FileItem(**item_dict), i, s3_folder) - new_files.append(item.model_dump()) + if "ref" in item_dict: + item = await moveTempFile(FileItem(**item_dict), i, s3_folder) + new_files.append(item.model_dump()) + elif "url" in item_dict: + new_files.append(item_dict) entity.files = new_files await self.session.commit() # update in index - IndexService().updateEntity( - self.entityType, entity, self._makeTags(entity)) + EntityIndexer().updateEntity( + self.entityType, entity, self._makeTags(entity), await self._makeRelations(entity)) return entity def _makeTags(self, entity: NaturalResource) -> list[str]: return [entity.type] if entity.type else [] + + async def _makeRelations(self, entity: NaturalResource) -> list[str]: + relations = (await self.session.exec(select(BuildingMaterialNaturalResource).where(BuildingMaterialNaturalResource.natural_resource_id == entity.id))).all() + relates_to = [ + f"building-material:{rel.building_material_id}" for rel in relations] + return relates_to diff --git a/backend/api/services/professionals.py b/backend/api/services/professionals.py index dc2d7dc..eac9129 100644 --- a/backend/api/services/professionals.py +++ b/backend/api/services/professionals.py @@ -4,14 +4,14 @@ from sqlalchemy.orm import selectinload from sqlmodel import select from fastapi import HTTPException -from api.models.domain import FileItem, Professional, Building, BuildingMaterial, TechnicalConstruction +from api.models.domain import FileItem, Professional, Building, BuildingMaterial, TechnicalConstruction, ProfessionalBuildingMaterial, ProfessionalTechnicalConstruction, ProfessionalBuilding from api.models.query import ProfessionalDraft, ProfessionalResult from enacit4r_sql.utils.query import QueryBuilder from datetime import datetime from api.services.s3 import s3_client from api.utils.files import moveTempFile from api.auth import User -from api.services.search import IndexService +from api.services.search import EntityIndexer class ProfessionalQueryBuilder(QueryBuilder): @@ -43,14 +43,14 @@ def __init__(self, session: AsyncSession): async def indexAll(self) -> int: """Index all professionals""" - indexService = IndexService() + indexService = EntityIndexer() # delete documents of this type indexService.deleteEntities(self.entityType) # add all documents count = 0 for entity in (await self.session.exec(select(Professional))).all(): indexService.addEntity( - self.entityType, entity, self._makeTags(entity)) + self.entityType, entity, self._makeTags(entity), await self._makeRelations(entity)) count += 1 debug(f"Indexed {count} professionals") return count @@ -88,7 +88,7 @@ async def delete(self, id: int) -> Professional: await self.session.delete(entity) await self.session.commit() # delete from index - IndexService().deleteEntity(self.entityType, entity.id) + EntityIndexer().deleteEntity(self.entityType, entity.id) return entity async def find(self, filter: dict, fields: list, sort: list, range: list) -> ProfessionalResult: @@ -142,14 +142,17 @@ async def create(self, payload: ProfessionalDraft, user: User = None) -> Profess s3_folder = f"{self.folder}/{entity.id}" new_files = [] for i, item_dict in enumerate(entity.files): - item = await moveTempFile(FileItem(**item_dict), i, s3_folder) - new_files.append(item.model_dump()) + if "ref" in item_dict: + item = await moveTempFile(FileItem(**item_dict), i, s3_folder) + new_files.append(item.model_dump()) + elif "url" in item_dict: + new_files.append(item_dict) entity.files = new_files await self.session.commit() # add to index - IndexService().addEntity( - self.entityType, entity, self._makeTags(entity)) + EntityIndexer().addEntity( + self.entityType, entity, self._makeTags(entity), await self._makeRelations(entity)) return entity async def update(self, id: int, payload: ProfessionalDraft, user: User = None) -> Professional: @@ -175,8 +178,11 @@ async def update(self, id: int, payload: ProfessionalDraft, user: User = None) - s3_folder = f"{self.folder}/{entity.id}" new_files = [] for i, item_dict in enumerate(entity.files): - item = await moveTempFile(FileItem(**item_dict), i, s3_folder) - new_files.append(item.model_dump()) + if "ref" in item_dict: + item = await moveTempFile(FileItem(**item_dict), i, s3_folder) + new_files.append(item.model_dump()) + elif "url" in item_dict: + new_files.append(item_dict) entity.files = new_files # handle relationships @@ -188,8 +194,8 @@ async def update(self, id: int, payload: ProfessionalDraft, user: User = None) - entity.technical_constructions.extend(new_tcs) await self.session.commit() # update in index - IndexService().updateEntity( - self.entityType, entity, self._makeTags(entity)) + EntityIndexer().updateEntity( + self.entityType, entity, self._makeTags(entity), await self._makeRelations(entity)) return entity def _makeTags(self, entity: Professional) -> list[str]: @@ -200,6 +206,17 @@ def _makeTags(self, entity: Professional) -> list[str]: tags.extend(entity.materials) return tags + async def _makeRelations(self, entity: Professional) -> list[str]: + relations = (await self.session.exec(select(ProfessionalBuildingMaterial).where(ProfessionalBuildingMaterial.professional_id == entity.id))).all() + relates_to = [ + f"building-material:{rel.building_material_id}" for rel in relations] + relations = (await self.session.exec(select(ProfessionalTechnicalConstruction).where(ProfessionalTechnicalConstruction.professional_id == entity.id))).all() + relates_to.extend( + [f"technical-construction:{rel.technical_construction_id}" for rel in relations]) + relations = (await self.session.exec(select(ProfessionalBuilding).where(ProfessionalBuilding.professional_id == entity.id))).all() + relates_to.extend([f"building:{rel.building_id}" for rel in relations]) + return relates_to + async def _get_building_materials(self, ids: list[int]): return await self.session.exec(select(BuildingMaterial).filter(BuildingMaterial.id.in_(ids))) diff --git a/backend/api/services/search.py b/backend/api/services/search.py index 52ac4a2..d2cc88f 100644 --- a/backend/api/services/search.py +++ b/backend/api/services/search.py @@ -1,18 +1,84 @@ -from datetime import datetime +from abc import ABC, abstractmethod +import copy +import re +from markdown_it import MarkdownIt from elasticsearch import Elasticsearch from api.config import config from api.models.query import SearchResult -ANALYZED_FIELDS = ["name", "description", "article_top", - "article_bottom", "side_note", "address"] +ENTITY_ANALYZED_FIELDS = ["name", "description", "article_top", + "article_bottom", "side_note", "address"] +VIDEO_ANALYZED_FIELDS = ["name", "legend"] -class IndexService: - def __init__(self, index_name: str = "arema"): + +class IndexService(ABC): + def __init__(self, index_name: str): self.client = Elasticsearch(config.ES_URL) self.index_name = index_name - def addEntity(self, entity_type: str, entity, tags: list[str]): + def refreshIndex(self): + """Refresh the index to make changes visible + """ + self._ensureIndex() + self.client.indices.refresh(index=self.index_name) + + def deleteIndex(self): + """Drop the index, its mapping definitions and all its data + """ + self.client.indices.delete( + index=self.index_name, ignore_unavailable=True) + + def search(self, query: dict, skip: int = 0, limit: int = 10): + """Search documents in the index + + Args: + query (dict): The Elasticsearch query object + skip (int, optional): Documents to skip. Defaults to 0. + limit (int, optional): Maximum count of documents to return. Defaults to 10. + + Returns: + SearchResult: The result object + """ + self._ensureIndex() + res = self.client.search( + index=self.index_name, body=query, from_=skip, size=limit) + total = res["hits"]["total"]["value"] + hits = [hit["_source"] for hit in res["hits"]["hits"]] + return SearchResult(total=total, skip=skip, limit=limit, data=hits) + + # Internals + + def _addDocument(self, doc_id, doc): + self._ensureIndex() + self.client.index(index=self.index_name, id=doc_id, body=doc) + + def _updateDocument(self, doc_id, doc): + self._deleteDocument(doc_id) + self._addDocument(doc_id, doc) + + def _deleteDocument(self, doc_id): + self._ensureIndex() + if self.client.exists(index=self.index_name, id=doc_id): + self.client.delete(index=self.index_name, id=doc_id) + + def _deleteDocuments(self, query: dict): + self._ensureIndex() + self.client.delete_by_query( + index=self.index_name, body=query, ignore_unavailable=True) + + @abstractmethod + def _ensureIndex(self): + """Ensure the index exists and has the correct mapping + """ + pass + + +class EntityIndexService(IndexService): + def __init__(self): + super().__init__("entities") + + def addEntity(self, entity_type: str, entity, tags: list[str], relates_to: list[str] = []): """Add a new entity to the index Args: @@ -24,11 +90,12 @@ def addEntity(self, entity_type: str, entity, tags: list[str]): doc = entity.model_dump() doc["entity_type"] = entity_type doc["tags"] = tags + doc["relates_to"] = relates_to if "long" in doc and "lat" in doc: doc["location"] = {"lat": doc["lat"], "lon": doc["long"]} self._addDocument(doc_id, doc) - def updateEntity(self, entity_type: str, entity, tags: list[str]): + def updateEntity(self, entity_type: str, entity, tags: list[str], relates_to: list[str] = []): """Update an entity of the index Args: @@ -40,6 +107,7 @@ def updateEntity(self, entity_type: str, entity, tags: list[str]): doc = entity.model_dump() doc["entity_type"] = entity_type doc["tags"] = tags + doc["relates_to"] = relates_to self._updateDocument(doc_id, doc) def deleteEntity(self, entity_type: str, entity_id: int): @@ -61,54 +129,209 @@ def deleteEntities(self, entity_type: str): query = {"query": {"term": {"entity_type": entity_type}}} self._deleteDocuments(query) - def refreshIndex(self): - """Refresh the index to make changes visible + def _ensureIndex(self): + if not self.client.indices.exists(index=self.index_name): + configuration = { + "settings": { + "analysis": { + "analyzer": { + "ngram_analyzer": { + "type": "custom", + "tokenizer": "standard", + "filter": ["lowercase", "ngram_filter"] + } + }, + "filter": { + "ngram_filter": { + "type": "ngram", + "min_gram": 3, # minimum n-gram length + "max_gram": 4 # maximum n-gram length + } + } + } + }, + "mappings": { + "properties": { + "tags": { + "type": "keyword" + }, + "relates_to": { + "type": "keyword" + }, + "entity_type": { + "type": "keyword" + }, + "location": { + "type": "geo_point" + } + } + } + } + for field in ENTITY_ANALYZED_FIELDS: + configuration["mappings"]["properties"][field] = { + "type": "text", + "analyzer": "ngram_analyzer" + } + self.client.indices.create( + index=self.index_name, body=configuration) + self.client.indices.refresh(index=self.index_name) + self.client.cluster.health(wait_for_status="yellow") + + +class VideoIndexService(IndexService): + def __init__(self): + super().__init__("videos") + + def isVideoUrl(self, url: str): + """Check if a given url is a video + + Args: + url (str): The url to check + + Returns: + bool: True if the url is a video """ - self._ensureIndex() - self.client.indices.refresh(index=self.index_name) + return url is not None and ("youtube.com" in url or "youtu.be" in url or "vimeo.com" in url or "srf.ch/play/tv" in url or "rts.ch/play/tv" in url) - def deleteIndex(self): - """Drop the index, its mapping definitions and all its data + def addVideos(self, entity_type: str, entity, tags: list[str]): + """Add videos of a new entity to the index + + Args: + entity_type (str): The class of the entity + entity (_type_): The entity object to add """ - self.client.indices.delete( - index=self.index_name, ignore_unavailable=True) + for doc in self._getVideos(entity_type, entity, tags): + self._addDocument(doc["id"], doc) - def search(self, query: dict, skip: int = 0, limit: int = 10): - """Search documents in the index + def updateVideos(self, entity_type: str, entity, tags: list[str]): + """Update videos of an entity of the index Args: - query (dict): The Elasticsearch query object - skip (int, optional): Documents to skip. Defaults to 0. - limit (int, optional): Maximum count of documents to return. Defaults to 10. + entity_type (str): The class of the entity + entity (_type_): The entity object to add + tags (list[str]): The associated tags + """ + for doc in self._getVideos(entity_type, entity, tags): + self._updateDocument(doc["id"], doc) + + def deleteVideos(self, entity_type: str, entity_id: int): + """Delete videos of an entity from the index + + Args: + entity_type (str): The class of the entity + entity_id (int): The id of the entity + """ + parent_id = f"{entity_type}:{entity_id}" + query = {"query": {"term": {"parent_id": parent_id}}} + self._deleteDocument(query) + + def deleteVideos(self, entity_type: str): + """Delete all videos of a given type + + Args: + entity_type (str): The class of the entities + """ + query = {"query": {"term": {"entity_type": entity_type}}} + self._deleteDocuments(query) + + def _getVideos(self, entity_type: str, entity, tags: list[str]): + """Get videos of an entity + + Args: + entity_type (str): The class of the entity + entity (_type_): The entity object to add + tags (list[str]): The associated tags Returns: - SearchResult: The result object + list[dict]: The list of video documents """ - self._ensureIndex() - res = self.client.search( - index=self.index_name, body=query, from_=skip, size=limit) - total = res["hits"]["total"]["value"] - hits = [hit["_source"] for hit in res["hits"]["hits"]] - return SearchResult(total=total, skip=skip, limit=limit, data=hits) + docs = [] + docs += self._getVideosFromFiles(entity_type, entity, tags) + for field in ["external_links", "article_top", "article_bottom", "side_note"]: + docs += self._getVideosFromField(field, entity_type, entity, tags) + return docs - # Internals + def _getVideosFromFiles(self, entity_type: str, entity, tags: list[str]): + """Get videos of an entity from the files attribute - def _addDocument(self, doc_id, doc): - self._ensureIndex() - self.client.index(index=self.index_name, id=doc_id, body=doc) + Args: + entity_type (str): The class of the entity + entity (_type_): The entity object to add + tags (list[str]): The associated tags - def _updateDocument(self, doc_id, doc): - self._deleteDocument(doc_id) - self._addDocument(doc_id, doc) + Returns: + list[dict]: The list of video documents + """ + if not entity.files: + return [] + docs = [] + parent_id = f"{entity_type}:{entity.id}" + for idx, file in enumerate(entity.files): + doc_id = f"{entity_type}:{entity.id}:file:{idx}" + if "url" in file and self.isVideoUrl(file["url"]): + doc = copy.deepcopy(file) + doc["id"] = doc_id + doc["name"] = entity.name + doc["entity_type"] = entity_type + doc["parent_id"] = parent_id + doc["tags"] = tags + docs.append(doc) + return docs - def _deleteDocument(self, doc_id): - self._ensureIndex() - self.client.delete(index=self.index_name, id=doc_id) + def _getVideosFromField(self, field, entity_type: str, entity, tags: list[str]): + """Get videos of an entity from the files attribute - def _deleteDocuments(self, query: dict): - self._ensureIndex() - self.client.delete_by_query( - index=self.index_name, body=query, ignore_unavailable=True) + Args: + field (str): The field to search for videos + entity_type (str): The class of the entity + entity (_type_): The entity object to add + tags (list[str]): The associated tags + + Returns: + list[dict]: The list of video documents + """ + text = getattr(entity, field) + if not text: + return [] + docs = [] + parent_id = f"{entity_type}:{entity.id}" + links = self.extract_all_links_with_text(text) + for idx, link in enumerate(links): + doc_id = f"{entity_type}:{entity.id}:{field}:{idx}" + if "url" in link and self.isVideoUrl(link["url"]): + doc = link + doc["id"] = doc_id + doc["name"] = entity.name + doc["entity_type"] = entity_type + doc["parent_id"] = parent_id + doc["tags"] = tags + docs.append(doc) + return docs + + def extract_all_links_with_text(self, markdown_text): + md = MarkdownIt() + tokens = md.parse(markdown_text) + links = [] + + # Extract Markdown links + for i, token in enumerate(tokens): + if token.type == "link_open": + href = [attr[1] + for attr in token.attrs if attr[0] == "href"][0] + if i + 1 < len(tokens) and tokens[i + 1].type == "text": + legend = tokens[i + 1].content + links.append({"legend": legend, "url": href}) + + # Extract plain URLs + plain_url_pattern = r'(https?://[^\s]+)' + plain_urls = re.findall(plain_url_pattern, markdown_text) + + for url in plain_urls: + # Ensure plain URLs are not duplicates of Markdown links + if not any(link["url"] == url for link in links): + links.append({"legend": None, "url": url}) + + return links def _ensureIndex(self): if not self.client.indices.exists(index=self.index_name): @@ -136,16 +359,16 @@ def _ensureIndex(self): "tags": { "type": "keyword" }, - "entity_type": { + "parent_id": { "type": "keyword" }, - "location": { - "type": "geo_point" + "entity_type": { + "type": "keyword" } } } } - for field in ANALYZED_FIELDS: + for field in VIDEO_ANALYZED_FIELDS: configuration["mappings"]["properties"][field] = { "type": "text", "analyzer": "ngram_analyzer" @@ -154,3 +377,69 @@ def _ensureIndex(self): index=self.index_name, body=configuration) self.client.indices.refresh(index=self.index_name) self.client.cluster.health(wait_for_status="yellow") + + +class EntityIndexer: + def __init__(self): + self.entityIndexService = EntityIndexService() + self.videoIndexService = VideoIndexService() + + def addEntity(self, entity_type: str, entity, tags: list[str], relates_to: list[str] = []): + """Add a new entity to the index + + Args: + entity_type (str): The class of the entity + entity (_type_): The entity object to add + tags (list[str]): The associated tags + """ + self.entityIndexService.addEntity( + entity_type, entity, tags, relates_to) + self.videoIndexService.addVideos(entity_type, entity, tags) + + def updateEntity(self, entity_type: str, entity, tags: list[str], relates_to: list[str] = []): + """Update an entity of the index + + Args: + entity_type (str): The class of the entity + entity (_type_): The entity object to add + tags (list[str]): The associated tags + """ + self.entityIndexService.updateEntity( + entity_type, entity, tags, relates_to) + self.videoIndexService.updateVideos(entity_type, entity, tags) + + def deleteEntity(self, entity_type: str, entity_id: int): + """Delete an entity from the index + + Args: + entity_type (str): The class of the entity + entity_id (int): The id of the entity + """ + self.entityIndexService.deleteEntity(entity_type, entity_id) + self.videoIndexService.deleteVideos(entity_type, entity_id) + + def deleteEntities(self, entity_type: str): + """Delete all entities of a given type + + Args: + entity_type (str): The class of the entities + """ + self.entityIndexService.deleteEntities(entity_type) + self.videoIndexService.deleteVideos(entity_type) + + def deleteIndex(self): + """Drop the index, its mapping definitions and all its data + """ + self.entityIndexService.deleteIndex() + self.videoIndexService.deleteIndex() + + +class SearchService: + + @classmethod + def fromIndex(cls, index: str): + if index == "entities": + return EntityIndexService() + if index == "videos": + return VideoIndexService() + raise ValueError(f"Unknown index: {index}") diff --git a/backend/api/services/technical_constructions.py b/backend/api/services/technical_constructions.py index 71a6762..0d9ef47 100644 --- a/backend/api/services/technical_constructions.py +++ b/backend/api/services/technical_constructions.py @@ -4,14 +4,14 @@ from sqlalchemy.orm import selectinload from sqlmodel import select from fastapi import HTTPException -from api.models.domain import FileItem, TechnicalConstruction, BuildingMaterial +from api.models.domain import FileItem, TechnicalConstruction, BuildingMaterial, TechnicalConstructionBuildingMaterial, ProfessionalTechnicalConstruction from api.models.query import TechnicalConstructionResult, TechnicalConstructionDraft from enacit4r_sql.utils.query import QueryBuilder from datetime import datetime from api.services.s3 import s3_client from api.utils.files import moveTempFile from api.auth import User -from api.services.search import IndexService +from api.services.search import EntityIndexer class TechnicalConstructionQueryBuilder(QueryBuilder): @@ -43,7 +43,7 @@ def __init__(self, session: AsyncSession): async def indexAll(self) -> int: """Index all technical constructions""" - indexService = IndexService() + indexService = EntityIndexer() # delete documents of this type indexService.deleteEntities(self.entityType) # add all documents @@ -55,7 +55,7 @@ async def indexAll(self) -> int: if entity.materials: tags.extend(entity.materials) indexService.addEntity( - self.entityType, entity, self._makeTags(entity)) + self.entityType, entity, self._makeTags(entity), await self._makeRelations(entity)) count += 1 debug(f"Indexed {count} technical constructions") return count @@ -91,7 +91,7 @@ async def delete(self, id: int) -> TechnicalConstruction: await self.session.delete(entity) await self.session.commit() # delete from index - IndexService().deleteEntity(self.entityType, entity.id) + EntityIndexer().deleteEntity(self.entityType, entity.id) return entity async def find(self, filter: dict, fields: list, sort: list, range: list) -> TechnicalConstructionResult: @@ -139,14 +139,17 @@ async def create(self, payload: TechnicalConstructionDraft, user: User = None) - s3_folder = f"{self.folder}/{entity.id}" new_files = [] for i, item_dict in enumerate(entity.files): - item = await moveTempFile(FileItem(**item_dict), i, s3_folder) - new_files.append(item.model_dump()) + if "ref" in item_dict: + item = await moveTempFile(FileItem(**item_dict), i, s3_folder) + new_files.append(item.model_dump()) + elif "url" in item_dict: + new_files.append(item_dict) entity.files = new_files await self.session.commit() # add to index - IndexService().addEntity( - self.entityType, entity, self._makeTags(entity)) + EntityIndexer().addEntity( + self.entityType, entity, self._makeTags(entity), await self._makeRelations(entity)) return entity @@ -172,8 +175,11 @@ async def update(self, id: int, payload: TechnicalConstructionDraft, user: User s3_folder = f"{self.folder}/{entity.id}" new_files = [] for i, item_dict in enumerate(entity.files): - item = await moveTempFile(FileItem(**item_dict), i, s3_folder) - new_files.append(item.model_dump()) + if "ref" in item_dict: + item = await moveTempFile(FileItem(**item_dict), i, s3_folder) + new_files.append(item.model_dump()) + elif "url" in item_dict: + new_files.append(item_dict) entity.files = new_files # handle relationships new_bms = await self._get_building_materials(payload.building_material_ids) @@ -181,8 +187,8 @@ async def update(self, id: int, payload: TechnicalConstructionDraft, user: User entity.building_materials.extend(new_bms) await self.session.commit() # update in index - IndexService().updateEntity( - self.entityType, entity, self._makeTags(entity)) + EntityIndexer().updateEntity( + self.entityType, entity, self._makeTags(entity), await self._makeRelations(entity)) return entity def _makeTags(self, entity: TechnicalConstruction) -> list[str]: @@ -193,5 +199,14 @@ def _makeTags(self, entity: TechnicalConstruction) -> list[str]: tags.extend(entity.materials) return tags + async def _makeRelations(self, entity: TechnicalConstruction) -> list[str]: + relations = (await self.session.exec(select(TechnicalConstructionBuildingMaterial).where(TechnicalConstructionBuildingMaterial.technical_construction_id == entity.id))).all() + relates_to = [ + f"building-material:{rel.building_material_id}" for rel in relations] + relations = (await self.session.exec(select(ProfessionalTechnicalConstruction).where(ProfessionalTechnicalConstruction.technical_construction_id == entity.id))).all() + relates_to.extend( + [f"professional:{rel.professional_id}" for rel in relations]) + return relates_to + async def _get_building_materials(self, ids: list[int]): return await self.session.exec(select(BuildingMaterial).filter(BuildingMaterial.id.in_(ids))) diff --git a/backend/api/views/search.py b/backend/api/views/search.py index 0615022..461d49d 100644 --- a/backend/api/views/search.py +++ b/backend/api/views/search.py @@ -7,7 +7,7 @@ from api.services.technical_constructions import TechnicalConstructionService from api.services.buildings import BuildingService from api.services.professionals import ProfessionalService -from api.services.search import IndexService, ANALYZED_FIELDS +from api.services.search import SearchService, EntityIndexer, ENTITY_ANALYZED_FIELDS, VIDEO_ANALYZED_FIELDS from api.models.query import SearchResult from enacit4r_sql.utils.query import paramAsDict @@ -50,12 +50,12 @@ async def delete_index( user: User = Depends(kc_service.require_admin()) ): """Deindex all or specific entity type""" - indexService = IndexService() + indexer = EntityIndexer() try: if type: - indexService.deleteEntities(type) + indexer.deleteEntities(type) else: - indexService.deleteIndex() + indexer.deleteIndex() except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @@ -65,15 +65,68 @@ async def find( query: str = Query(None), skip: int = Query(0), limit: int = Query(10), + index: str = Query("entities"), user: User = Depends(kc_service.require_admin()) ) -> SearchResult: """Search documents matching the Elasticsearch query""" - indexService = IndexService() + indexService = SearchService.fromIndex(index) queryDict = paramAsDict(query) return indexService.search(query=queryDict, skip=skip, limit=limit) -@router.get("/", response_model=SearchResult, response_model_exclude_none=True) +@router.get("/_doc", response_model=SearchResult, response_model_exclude_none=True) +async def find(id: str = Query(None), + index: str = Query("entities"), + fields: List[str] = Query(None)) -> SearchResult: + """Search documents matching the Elasticsearch query""" + indexService = SearchService.fromIndex(index) + queryDict = {"query": {"term": {"_id": id}}} + if fields is not None and len(fields) > 0: + queryDict["_source"] = fields + return indexService.search(query=queryDict, skip=0, limit=1) + + +@router.get("/_videos", response_model=SearchResult, response_model_exclude_none=True) +async def find( + text: str = Query(None), + tags: List[str] = Query(None), + fields: List[str] = Query( + ["entity_type", "tags", "id", "parent_id", "name", "legend", "url"]), + exists: List[str] = Query([]), # filter documents with a non-empty field + skip: int = Query(0), + limit: int = Query(10), +) -> SearchResult: + """Search video documents by full text""" + indexService = SearchService.fromIndex("videos") + mustQueries = [] + + if text: + mustQueries.append({"multi_match": {"query": text, + "fields": VIDEO_ANALYZED_FIELDS}}) + if tags: + terms = {} + # group terms (urn) by their parent + for tag in tags: + vocabulary = tag.split('.', 1)[0] + if vocabulary not in terms: + terms[vocabulary] = [] + terms[vocabulary].append(tag) + for vocabulary in terms: + mustQueries.append({"terms": {"tags": terms[vocabulary]}}) + if exists: + for field in exists: + mustQueries.append({"exists": {"field": field}}) + + queryDict = {} + if len(mustQueries): + queryDict = {"query": {"bool": {"must": mustQueries}}} + if fields is not None and len(fields) > 0: + queryDict["_source"] = fields + + return indexService.search(query=queryDict, skip=skip, limit=limit) + + +@router.get("/_entities", response_model=SearchResult, response_model_exclude_none=True) async def find( text: str = Query(None), tags: List[str] = Query(None), @@ -83,18 +136,18 @@ async def find( skip: int = Query(0), limit: int = Query(10), ) -> SearchResult: - """Search documents by tags or full text""" - indexService = IndexService() + """Search entity documents by tags or full text""" + indexService = SearchService.fromIndex("entities") mustQueries = [] if text: mustQueries.append({"multi_match": {"query": text, - "fields": ANALYZED_FIELDS}}) + "fields": ENTITY_ANALYZED_FIELDS}}) if tags: terms = {} # group terms (urn) by their parent for tag in tags: - vocabulary = tag.rsplit('.', 1)[0] + vocabulary = tag.split('.', 1)[0] if vocabulary not in terms: terms[vocabulary] = [] terms[vocabulary].append(tag) @@ -108,7 +161,7 @@ async def find( queryDict = {} if len(mustQueries): queryDict = {"query": {"bool": {"must": mustQueries}}} - if fields: + if fields is not None and len(fields) > 0: queryDict["_source"] = fields return indexService.search(query=queryDict, skip=skip, limit=limit) diff --git a/backend/migrations/versions/2024_12_16_1623-86ee029f0372_building_fields.py b/backend/migrations/versions/2024_12_16_1623-86ee029f0372_building_fields.py new file mode 100644 index 0000000..09bb536 --- /dev/null +++ b/backend/migrations/versions/2024_12_16_1623-86ee029f0372_building_fields.py @@ -0,0 +1,37 @@ +"""building-fields + +Revision ID: 86ee029f0372 +Revises: e1c96da74747 +Create Date: 2024-12-16 16:23:30.967148 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import sqlmodel + + +# revision identifiers, used by Alembic. +revision: str = '86ee029f0372' +down_revision: Union[str, None] = 'e1c96da74747' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('building', sa.Column( + 'client', sqlmodel.sql.sqltypes.AutoString(), nullable=True)) + op.add_column('building', sa.Column( + 'gross_internal_area', sa.Float(), nullable=True)) + op.add_column('building', sa.Column('year', sa.Integer(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('building', 'year') + op.drop_column('building', 'gross_internal_area') + op.drop_column('building', 'client') + # ### end Alembic commands ### diff --git a/backend/migrations/versions/2024_12_16_1707-9dd17a356a73_fire.py b/backend/migrations/versions/2024_12_16_1707-9dd17a356a73_fire.py new file mode 100644 index 0000000..d2ae736 --- /dev/null +++ b/backend/migrations/versions/2024_12_16_1707-9dd17a356a73_fire.py @@ -0,0 +1,34 @@ +"""fire + +Revision ID: 9dd17a356a73 +Revises: 86ee029f0372 +Create Date: 2024-12-16 17:07:53.953740 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '9dd17a356a73' +down_revision: Union[str, None] = '86ee029f0372' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('buildingmaterial', 'building_material_class') + op.drop_column('naturalresource', 'building_material_class') + op.drop_column('technicalconstruction', 'building_material_class') + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('technicalconstruction', sa.Column('building_material_class', sa.VARCHAR(), autoincrement=False, nullable=True)) + op.add_column('naturalresource', sa.Column('building_material_class', sa.VARCHAR(), autoincrement=False, nullable=True)) + op.add_column('buildingmaterial', sa.Column('building_material_class', sa.VARCHAR(), autoincrement=False, nullable=True)) + # ### end Alembic commands ### diff --git a/backend/migrations/versions/2024_12_18_0952-659fc00a69fa_fire_low_high.py b/backend/migrations/versions/2024_12_18_0952-659fc00a69fa_fire_low_high.py new file mode 100644 index 0000000..fea0e3c --- /dev/null +++ b/backend/migrations/versions/2024_12_18_0952-659fc00a69fa_fire_low_high.py @@ -0,0 +1,64 @@ +"""fire-low-high + +Revision ID: 659fc00a69fa +Revises: 9dd17a356a73 +Create Date: 2024-12-18 09:52:55.635120 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import sqlmodel + +# revision identifiers, used by Alembic. +revision: str = '659fc00a69fa' +down_revision: Union[str, None] = '9dd17a356a73' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('buildingmaterial', sa.Column( + 'reaction_to_fire_low', sqlmodel.sql.sqltypes.AutoString(), nullable=True)) + op.add_column('buildingmaterial', sa.Column( + 'fire_resistance_class_low', sqlmodel.sql.sqltypes.AutoString(), nullable=True)) + op.add_column('buildingmaterial', sa.Column( + 'reaction_to_fire_high', sqlmodel.sql.sqltypes.AutoString(), nullable=True)) + op.add_column('buildingmaterial', sa.Column( + 'fire_resistance_class_high', sqlmodel.sql.sqltypes.AutoString(), nullable=True)) + op.add_column('naturalresource', sa.Column( + 'reaction_to_fire_low', sqlmodel.sql.sqltypes.AutoString(), nullable=True)) + op.add_column('naturalresource', sa.Column( + 'fire_resistance_class_low', sqlmodel.sql.sqltypes.AutoString(), nullable=True)) + op.add_column('naturalresource', sa.Column( + 'reaction_to_fire_high', sqlmodel.sql.sqltypes.AutoString(), nullable=True)) + op.add_column('naturalresource', sa.Column( + 'fire_resistance_class_high', sqlmodel.sql.sqltypes.AutoString(), nullable=True)) + op.add_column('technicalconstruction', sa.Column( + 'reaction_to_fire_low', sqlmodel.sql.sqltypes.AutoString(), nullable=True)) + op.add_column('technicalconstruction', sa.Column( + 'fire_resistance_class_low', sqlmodel.sql.sqltypes.AutoString(), nullable=True)) + op.add_column('technicalconstruction', sa.Column( + 'reaction_to_fire_high', sqlmodel.sql.sqltypes.AutoString(), nullable=True)) + op.add_column('technicalconstruction', sa.Column( + 'fire_resistance_class_high', sqlmodel.sql.sqltypes.AutoString(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('technicalconstruction', 'fire_resistance_class_high') + op.drop_column('technicalconstruction', 'reaction_to_fire_high') + op.drop_column('technicalconstruction', 'fire_resistance_class_low') + op.drop_column('technicalconstruction', 'reaction_to_fire_low') + op.drop_column('naturalresource', 'fire_resistance_class_high') + op.drop_column('naturalresource', 'reaction_to_fire_high') + op.drop_column('naturalresource', 'fire_resistance_class_low') + op.drop_column('naturalresource', 'reaction_to_fire_low') + op.drop_column('buildingmaterial', 'fire_resistance_class_high') + op.drop_column('buildingmaterial', 'reaction_to_fire_high') + op.drop_column('buildingmaterial', 'fire_resistance_class_low') + op.drop_column('buildingmaterial', 'reaction_to_fire_low') + # ### end Alembic commands ### diff --git a/backend/poetry.lock b/backend/poetry.lock index 6b4a619..ac9ee88 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -1138,6 +1138,30 @@ babel = ["Babel"] lingua = ["lingua"] testing = ["pytest"] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + [[package]] name = "markupsafe" version = "3.0.2" @@ -1208,6 +1232,17 @@ files = [ {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + [[package]] name = "multidict" version = "6.1.0" @@ -2341,4 +2376,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "42f4440aa1aaee09da91f8f4fa82b15a1cd92446a575c9cbf0b5e620dab174f8" +content-hash = "61a9392d4c19831c87b3668c5d6ffbc89361c1a4edf1221ae90b22224428801d" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index d27a338..2c8d7a0 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -23,6 +23,7 @@ python-dotenv = {extras = ["cli"], version = "^1.0.1"} uvicorn = "^0.32.0" pyyaml = "^6.0.2" elasticsearch = {extras = ["async"], version = "^8.16.0"} +markdown-it-py = "^3.0.0" [build-system] requires = ["poetry-core"] diff --git a/frontend/.eslintrc-auto-import.json b/frontend/.eslintrc-auto-import.json index 40cebe3..ad43420 100644 --- a/frontend/.eslintrc-auto-import.json +++ b/frontend/.eslintrc-auto-import.json @@ -105,6 +105,7 @@ "useTaxonomyStore": true, "useHome": true, "useSearch": true, - "useSearchService": true + "useSearchService": true, + "Service": true } } diff --git a/frontend/.prettierrc b/frontend/.prettierrc index 650cb88..75322b0 100644 --- a/frontend/.prettierrc +++ b/frontend/.prettierrc @@ -1,4 +1,13 @@ { "singleQuote": true, - "semi": true + "semi": true, + "printWidth": 120, + "overrides": [ + { + "files": "src/i18n/**/index.js", + "options": { + "printWidth": 10000 + } + } + ] } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a272a90..a4f328a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,11 +1,11 @@ { - "name": "alice-ethz-arema-frontend", + "name": "arema-frontend", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "alice-ethz-arema-frontend", + "name": "arema-frontend", "version": "0.0.1", "dependencies": { "@mapbox/mapbox-gl-draw": "^1.4.3", @@ -30,6 +30,7 @@ "devDependencies": { "@intlify/vite-plugin-vue-i18n": "^3.3.1", "@quasar/app-vite": "^1.3.0", + "@quasar/quasar-app-extension-qmarkdown": "^2.0.0-beta.10", "@types/node": "^12.20.21", "@typescript-eslint/eslint-plugin": "^7.12.0", "@typescript-eslint/parser": "^7.12.0", @@ -968,6 +969,49 @@ "url": "https://donate.quasar.dev" } }, + "node_modules/@quasar/quasar-app-extension-qmarkdown": { + "version": "2.0.0-beta.10", + "resolved": "https://registry.npmjs.org/@quasar/quasar-app-extension-qmarkdown/-/quasar-app-extension-qmarkdown-2.0.0-beta.10.tgz", + "integrity": "sha512-D/2/oxMiNM19D7p1xAkWG5ptW3IzXmLwGtmtqbDJ0gmtzYvkqQxzimdst9uUyZs6G3jmMQSdCN6mJ/iZcyw38g==", + "dev": true, + "dependencies": { + "@quasar/quasar-ui-qmarkdown": "^2.0.0-beta.10", + "front-matter": "^4.0.2", + "markdown-it-abbr": "^1.0.4", + "markdown-it-deflist": "^2.1.0", + "markdown-it-emoji": "^2.0.0", + "markdown-it-footnote": "^3.0.3", + "markdown-it-ins": "^3.0.1", + "markdown-it-mark": "^3.0.1", + "markdown-it-sub": "^1.0.0", + "markdown-it-sup": "^1.0.0", + "markdown-it-task-lists": "^2.1.1", + "raw-loader": "^4.0.2", + "webpack-merge": "^5.8.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/hawkeye64" + } + }, + "node_modules/@quasar/quasar-ui-qmarkdown": { + "version": "2.0.0-beta.10", + "resolved": "https://registry.npmjs.org/@quasar/quasar-ui-qmarkdown/-/quasar-ui-qmarkdown-2.0.0-beta.10.tgz", + "integrity": "sha512-dglYq169H7SQ14eR0mm/Rp+k5gftkSmZyd/3vIH4M94Z/3z2eys4Ch620VhM4oFuxnUUumXYxvs66IQDx3272g==", + "dev": true, + "dependencies": { + "@types/markdown-it": "^12.2.3", + "markdown-it": "^12.3.2", + "markdown-it-container": "^3.0.0", + "markdown-it-imsize": "^2.0.1", + "markdown-it-toc-and-anchor": "^4.2.0", + "prismjs": "^1.28.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/hawkeye64" + } + }, "node_modules/@quasar/render-ssr-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@quasar/render-ssr-error/-/render-ssr-error-1.0.3.tgz", @@ -2624,10 +2668,32 @@ "integrity": "sha512-rkiiTuf/z2wTd4RxFOb+clE7PF4AEJU0hsczbUdkHHBtkUmpWQpEddynNfJYKYtZFJKbq4F+brfekt1kx85IZA==", "dev": true }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, "node_modules/@types/express": { @@ -2694,11 +2760,23 @@ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, "node_modules/@types/junit-report-builder": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/junit-report-builder/-/junit-report-builder-3.0.2.tgz", "integrity": "sha512-R5M+SYhMbwBeQcNXYWNCZkl09vkVfAtcPIaCGdzIkkbeaTrVbGQ7HVgi4s+EmM/M1K4ZuWQH0jGcvMvNePfxYA==" }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true + }, "node_modules/@types/mapbox__point-geometry": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", @@ -2714,6 +2792,22 @@ "@types/pbf": "*" } }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -3184,6 +3278,181 @@ } } }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "peer": true + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3198,9 +3467,9 @@ } }, "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -3234,6 +3503,37 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "peer": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -3499,6 +3799,15 @@ } ] }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3596,9 +3905,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", "dev": true, "funding": [ { @@ -3615,10 +3924,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -3730,9 +4039,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001680", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz", - "integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==", + "version": "1.0.30001689", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001689.tgz", + "integrity": "sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g==", "dev": true, "funding": [ { @@ -3795,6 +4104,16 @@ "fsevents": "~2.3.2" } }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -4376,9 +4695,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.582", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.582.tgz", - "integrity": "sha512-89o0MGoocwYbzqUUjc+VNpeOFSOK9nIdC5wY4N+PVUarUK0MtjyTjks75AZS2bW4Kl8MdewdFsWaH0jLy+JNoA==", + "version": "1.5.73", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz", + "integrity": "sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg==", "dev": true }, "node_modules/elementtree": { @@ -4399,6 +4718,15 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -4417,6 +4745,20 @@ "once": "^1.4.0" } }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -4447,6 +4789,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true, + "peer": true + }, "node_modules/esbuild": { "version": "0.14.51", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.51.tgz", @@ -4803,9 +5152,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "engines": { "node": ">=6" @@ -5020,6 +5369,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", @@ -5076,6 +5438,16 @@ "node": ">= 0.6" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/express": { "version": "4.21.1", "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", @@ -5410,6 +5782,37 @@ "node": ">= 0.6" } }, + "node_modules/front-matter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", + "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==", + "dev": true, + "dependencies": { + "js-yaml": "^3.13.1" + } + }, + "node_modules/front-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/front-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -5600,6 +6003,13 @@ "node": ">= 6" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "peer": true + }, "node_modules/global-prefix": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", @@ -6153,6 +6563,37 @@ "node": ">=0.10.0" } }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/js-tokens": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", @@ -6177,6 +6618,13 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "peer": true + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -6194,6 +6642,18 @@ "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonc-eslint-parser": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-1.4.1.tgz", @@ -6343,6 +6803,39 @@ "node": ">= 0.8.0" } }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, "node_modules/local-pkg": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", @@ -6616,6 +7109,116 @@ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-abbr": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-it-abbr/-/markdown-it-abbr-1.0.4.tgz", + "integrity": "sha512-ZeA4Z4SaBbYysZap5iZcxKmlPL6bYA8grqhzJIHB1ikn7njnzaP8uwbtuXc4YXD5LicI4/2Xmc0VwmSiFV04gg==", + "dev": true + }, + "node_modules/markdown-it-container": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-container/-/markdown-it-container-3.0.0.tgz", + "integrity": "sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw==", + "dev": true + }, + "node_modules/markdown-it-deflist": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/markdown-it-deflist/-/markdown-it-deflist-2.1.0.tgz", + "integrity": "sha512-3OuqoRUlSxJiuQYu0cWTLHNhhq2xtoSFqsZK8plANg91+RJQU1ziQ6lA2LzmFAEes18uPBsHZpcX6We5l76Nzg==", + "dev": true + }, + "node_modules/markdown-it-emoji": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz", + "integrity": "sha512-zLftSaNrKuYl0kR5zm4gxXjHaOI3FAOEaloKmRA5hijmJZvSjmxcokOLlzycb/HXlUFWzXqpIEoyEMCE4i9MvQ==", + "dev": true + }, + "node_modules/markdown-it-footnote": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-it-footnote/-/markdown-it-footnote-3.0.3.tgz", + "integrity": "sha512-YZMSuCGVZAjzKMn+xqIco9d1cLGxbELHZ9do/TSYVzraooV8ypsppKNmUJ0fVH5ljkCInQAtFpm8Rb3eXSrt5w==", + "dev": true + }, + "node_modules/markdown-it-imsize": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/markdown-it-imsize/-/markdown-it-imsize-2.0.1.tgz", + "integrity": "sha512-5SH90ademqcR8ifQCBXRCfIR4HGfZZOh5pO0j2TglulfSQH+SBXM4Iw/QlTUbSoUwVZArCYgECoMvktDS2kP3w==", + "dev": true + }, + "node_modules/markdown-it-ins": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/markdown-it-ins/-/markdown-it-ins-3.0.1.tgz", + "integrity": "sha512-32SSfZqSzqyAmmQ4SHvhxbFqSzPDqsZgMHDwxqPzp+v+t8RsmqsBZRG+RfRQskJko9PfKC2/oxyOs4Yg/CfiRw==", + "dev": true + }, + "node_modules/markdown-it-mark": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/markdown-it-mark/-/markdown-it-mark-3.0.1.tgz", + "integrity": "sha512-HyxjAu6BRsdt6Xcv6TKVQnkz/E70TdGXEFHRYBGLncRE9lBFwDNLVtFojKxjJWgJ+5XxUwLaHXy+2sGBbDn+4A==", + "dev": true + }, + "node_modules/markdown-it-sub": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz", + "integrity": "sha512-z2Rm/LzEE1wzwTSDrI+FlPEveAAbgdAdPhdWarq/ZGJrGW/uCQbKAnhoCsE4hAbc3SEym26+W2z/VQB0cQiA9Q==", + "dev": true + }, + "node_modules/markdown-it-sup": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz", + "integrity": "sha512-E32m0nV9iyhRR7CrhnzL5msqic7rL1juWre6TQNxsnApg7Uf+F97JOKxUijg5YwXz86lZ0mqfOnutoryyNdntQ==", + "dev": true + }, + "node_modules/markdown-it-task-lists": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz", + "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==", + "dev": true + }, + "node_modules/markdown-it-toc-and-anchor": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/markdown-it-toc-and-anchor/-/markdown-it-toc-and-anchor-4.2.0.tgz", + "integrity": "sha512-DusSbKtg8CwZ92ztN7bOojDpP4h0+w7BVOPuA3PHDIaabMsERYpwsazLYSP/UlKedoQjOz21mwlai36TQ04EpA==", + "dev": true, + "dependencies": { + "clone": "^2.1.0", + "uslug": "^1.0.4" + } + }, + "node_modules/markdown-it-toc-and-anchor/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/marked": { "version": "9.1.6", "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", @@ -6627,6 +7230,12 @@ "node": ">= 16" } }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -6645,6 +7254,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "peer": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -6797,6 +7413,13 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "peer": true + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -6815,9 +7438,9 @@ "optional": true }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true }, "node_modules/normalize-path": { @@ -7307,6 +7930,15 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -7441,6 +8073,26 @@ "node": ">= 0.8" } }, + "node_modules/raw-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz", + "integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, "node_modules/rbush": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/rbush/-/rbush-2.0.2.tgz", @@ -7798,6 +8450,55 @@ "integrity": "sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==", "dev": true }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "node_modules/scule": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", @@ -7871,9 +8572,9 @@ "dev": true }, "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "dependencies": { "randombytes": "^2.1.0" @@ -8129,6 +8830,12 @@ "node": ">=0.10.0" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "node_modules/stack-trace": { "version": "1.0.0-pre2", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-1.0.0-pre2.tgz", @@ -8288,6 +8995,16 @@ "node": ">=10.0.0" } }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -8322,6 +9039,61 @@ "node": ">=10" } }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", + "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dev": true, + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -8490,6 +9262,12 @@ "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz", "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==" }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, "node_modules/ufo": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", @@ -8589,6 +9367,15 @@ "node": ">= 10.0.0" } }, + "node_modules/unorm": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz", + "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -8694,9 +9481,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "dev": true, "funding": [ { @@ -8713,8 +9500,8 @@ } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -8732,6 +9519,18 @@ "punycode": "^2.1.0" } }, + "node_modules/uslug": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/uslug/-/uslug-1.0.4.tgz", + "integrity": "sha512-Jrbpp/NS3TvIGNjfJT1sn3/BCeykoxR8GbNYW5lF6fUscLkbXFwj1b7m4DvIkHm8k3Qr6Co68lbTmoZTMGk/ow==", + "dev": true, + "dependencies": { + "unorm": ">= 1.0.0" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -8880,6 +9679,20 @@ "vue": "^3.2.0" } }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dev": true, + "peer": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -8889,6 +9702,53 @@ "defaults": "^1.0.3" } }, + "node_modules/webpack": { + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, "node_modules/webpack-merge": { "version": "5.10.0", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", @@ -8918,6 +9778,30 @@ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", "dev": true }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/wgs84": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/wgs84/-/wgs84-0.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index aef4ef6..baf6314 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -36,6 +36,7 @@ "devDependencies": { "@intlify/vite-plugin-vue-i18n": "^3.3.1", "@quasar/app-vite": "^1.3.0", + "@quasar/quasar-app-extension-qmarkdown": "^2.0.0-beta.10", "@types/node": "^12.20.21", "@typescript-eslint/eslint-plugin": "^7.12.0", "@typescript-eslint/parser": "^7.12.0", diff --git a/frontend/quasar.config.js b/frontend/quasar.config.js index 72406c7..368839a 100644 --- a/frontend/quasar.config.js +++ b/frontend/quasar.config.js @@ -9,6 +9,7 @@ // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js const { configure } = require('quasar/wrappers'); +const { mergeConfig } = require('vite'); const path = require('path'); module.exports = configure(function (ctx) { @@ -54,7 +55,7 @@ module.exports = configure(function (ctx) { node: 'node16', }, - vueRouterMode: 'history', // available values: 'hash', 'history' + vueRouterMode: 'hash', // available values: 'hash', 'history' // vueRouterBase, // vueDevtools, // vueOptionsAPI: false, @@ -71,6 +72,21 @@ module.exports = configure(function (ctx) { // distDir // extendViteConf (viteConf) {}, + // https://github.com/quasarframework/quasar/issues/8513#issuecomment-1127654470 + extendViteConf(viteConf, { isServer, isClient }) { + viteConf.base = ''; + viteConf.css = mergeConfig(viteConf.css, { + preprocessorOptions: { + // silence deprecation warnings from quasar-ui-qmarkdown + sass: { + silenceDeprecations: ['color-functions'], + }, + scss: { + silenceDeprecations: ['color-functions'], + }, + }, + }); + }, // viteVuePluginOptions: {}, vitePlugins: [ diff --git a/frontend/quasar.extensions.json b/frontend/quasar.extensions.json new file mode 100644 index 0000000..522bd8c --- /dev/null +++ b/frontend/quasar.extensions.json @@ -0,0 +1,3 @@ +{ + "@quasar/qmarkdown": {} +} \ No newline at end of file diff --git a/frontend/src/auto-imports.d.ts b/frontend/src/auto-imports.d.ts index 526d107..53eba6c 100644 --- a/frontend/src/auto-imports.d.ts +++ b/frontend/src/auto-imports.d.ts @@ -13,18 +13,19 @@ declare global { const $shallowRef: typeof import('vue/macros')['$shallowRef'] const $toRef: typeof import('vue/macros')['$toRef'] const EffectScope: typeof import('vue')['EffectScope'] + const Service: (typeof import('./stores/services'))['Service'] const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] const computed: typeof import('vue')['computed'] const createApp: typeof import('vue')['createApp'] const createPinia: typeof import('pinia')['createPinia'] - const createPiniaClient: typeof import('feathers-pinia')['createPiniaClient'] + const createPiniaClient: (typeof import('feathers-pinia'))['createPiniaClient'] const customRef: typeof import('vue')['customRef'] const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] const defineComponent: typeof import('vue')['defineComponent'] - const defineGetters: typeof import('feathers-pinia')['defineGetters'] - const defineSetters: typeof import('feathers-pinia')['defineSetters'] + const defineGetters: (typeof import('feathers-pinia'))['defineGetters'] + const defineSetters: (typeof import('feathers-pinia'))['defineSetters'] const defineStore: typeof import('pinia')['defineStore'] - const defineValues: typeof import('feathers-pinia')['defineValues'] + const defineValues: (typeof import('feathers-pinia'))['defineValues'] const effectScope: typeof import('vue')['effectScope'] const getActivePinia: typeof import('pinia')['getActivePinia'] const getCurrentInstance: typeof import('vue')['getCurrentInstance'] @@ -76,23 +77,23 @@ declare global { const triggerRef: typeof import('vue')['triggerRef'] const unref: typeof import('vue')['unref'] const useAttrs: typeof import('vue')['useAttrs'] - const useAuth: typeof import('feathers-pinia')['useAuth'] - const useAuthStore: typeof import('./stores/auth')['useAuthStore'] - const useCounterStore: typeof import('./stores/example-store')['useCounterStore'] + const useAuth: (typeof import('feathers-pinia'))['useAuth'] + const useAuthStore: (typeof import('./stores/auth'))['useAuthStore'] + const useCounterStore: (typeof import('./stores/example-store'))['useCounterStore'] const useCssModule: typeof import('vue')['useCssModule'] const useCssVars: typeof import('vue')['useCssVars'] - const useDataStore: typeof import('feathers-pinia')['useDataStore'] - const useFeathers: typeof import('./composables/feathers')['useFeathers'] - const useFeathersService: typeof import('./composables/feathers')['useFeathersService'] + const useDataStore: (typeof import('feathers-pinia'))['useDataStore'] + const useFeathers: (typeof import('./composables/feathers'))['useFeathers'] + const useFeathersService: (typeof import('./composables/feathers'))['useFeathersService'] const useHome: typeof import('./stores/home')['useHome'] const useI18n: typeof import('vue-i18n')['useI18n'] - const useInstanceDefaults: typeof import('feathers-pinia')['useInstanceDefaults'] + const useInstanceDefaults: (typeof import('feathers-pinia'))['useInstanceDefaults'] const useLink: typeof import('vue-router')['useLink'] const useRoute: typeof import('vue-router')['useRoute'] const useRouter: typeof import('vue-router')['useRouter'] - const useSearch: typeof import('./stores/search')['useSearch'] + const useSearch: (typeof import('./stores/search'))['useSearch'] const useSearchService: typeof import('./stores/search')['useSearchService'] - const useServiceInstance: typeof import('feathers-pinia')['useServiceInstance'] + const useServiceInstance: (typeof import('feathers-pinia'))['useServiceInstance'] const useSlots: typeof import('vue')['useSlots'] const useTaxonomyStore: typeof import('./stores/taxonomies')['useTaxonomyStore'] const watch: typeof import('vue')['watch'] diff --git a/frontend/src/components/AboutPanel.vue b/frontend/src/components/AboutPanel.vue index 295f73a..1936d6b 100644 --- a/frontend/src/components/AboutPanel.vue +++ b/frontend/src/components/AboutPanel.vue @@ -1,6 +1,6 @@ - diff --git a/frontend/src/components/DocumentPanel.vue b/frontend/src/components/DocumentPanel.vue new file mode 100644 index 0000000..eb1bce7 --- /dev/null +++ b/frontend/src/components/DocumentPanel.vue @@ -0,0 +1,206 @@ + + + diff --git a/frontend/src/components/HeaderPanel.vue b/frontend/src/components/HeaderPanel.vue index e55b21a..e90377c 100644 --- a/frontend/src/components/HeaderPanel.vue +++ b/frontend/src/components/HeaderPanel.vue @@ -3,7 +3,7 @@ - diff --git a/frontend/src/components/ListResults.vue b/frontend/src/components/ListResults.vue index 70ecff0..7bb96f6 100644 --- a/frontend/src/components/ListResults.vue +++ b/frontend/src/components/ListResults.vue @@ -1,7 +1,40 @@ - diff --git a/frontend/src/components/MapResults.vue b/frontend/src/components/MapResults.vue index 79d76e7..ee6c9ab 100644 --- a/frontend/src/components/MapResults.vue +++ b/frontend/src/components/MapResults.vue @@ -1,22 +1,29 @@ + +
{{ $t('no_results') }}
@@ -37,30 +51,18 @@
- diff --git a/frontend/src/components/MapView.vue b/frontend/src/components/MapView.vue index c692fd0..23c82a6 100644 --- a/frontend/src/components/MapView.vue +++ b/frontend/src/components/MapView.vue @@ -12,15 +12,15 @@ import '@maplibre/maplibre-gl-geocoder/dist/maplibre-gl-geocoder.css'; import 'maplibre-gl/dist/maplibre-gl.css'; import 'maplibregl-theme-switcher/styles.css'; import { style } from '../utils/maps'; -import { type Feature, type Point, FeatureCollection } from '@turf/turf'; +import { type FeatureCollection, Point } from 'geojson'; import { FullscreenControl, GeolocateControl, Map, - Marker, NavigationControl, ScaleControl, Popup, + GeoJSONSource, } from 'maplibre-gl'; const { t } = useI18n({ useScope: 'global' }); @@ -48,9 +48,8 @@ const props = withDefaults(defineProps(), { let map = shallowRef(); -// track which were the layers and éarlers added, to be able to remove them +// track which were the layers added, to be able to remove them const layerIds: string[] = []; -const markers: Marker[] = []; onMounted(() => { initMap(); @@ -93,45 +92,138 @@ watch( ); function displayFeatures() { - const features = props.features?.features; - if (map.value && layerIds.length > 0) { - layerIds.forEach((layerId) => { - map.value?.removeLayer(layerId); - map.value?.removeSource(layerId); - }); - if (layerIds.length) layerIds.splice(0); + if (!map.value) { + return; } - if (map.value && markers.length > 0) { - markers.forEach((marker) => { - marker.remove(); + + if (map.value.getSource('entities')) { + // update source + (map.value.getSource('entities') as GeoJSONSource)?.setData( + props.features || { + type: 'FeatureCollection', + features: [], + }, + ); + } else { + // set source and add layers + map.value.addSource('entities', { + type: 'geojson', + data: props.features || { + type: 'FeatureCollection', + features: [], + }, + cluster: true, + clusterMaxZoom: 14, // Max zoom to cluster points on + clusterRadius: 50, // Radius of each cluster when clustering points (defaults to 50) }); - markers.splice(0); - } - if (features) { - features.forEach((feature) => { - if (feature.geometry.type === 'Point') { - displayPoint(feature as Feature); + + map.value.addLayer({ + id: 'entities-clusters', + type: 'circle', + source: 'entities', + filter: ['has', 'point_count'], + paint: { + // Use step expressions (https://maplibre.org/maplibre-style-spec/#expressions-step) + // with three steps to implement three types of circles: + // * Blue, 20px circles when point count is less than 100 + // * Yellow, 30px circles when point count is between 100 and 750 + // * Pink, 40px circles when point count is greater than or equal to 750 + 'circle-color': '#ffffff', + 'circle-radius': ['step', ['get', 'point_count'], 20, 100, 30, 750, 40], + 'circle-stroke-width': 1, + 'circle-stroke-color': '#ddd', + }, + }); + layerIds.push('entities-clusters'); + + map.value.addLayer({ + id: 'entities-cluster-count', + type: 'symbol', + source: 'entities', + filter: ['has', 'point_count'], + layout: { + 'text-field': '{point_count_abbreviated}', + 'text-font': ['Roboto Regular'], + 'text-size': 20, + }, + paint: { + 'text-color': '#000', + }, + }); + layerIds.push('entities-cluster-count'); + + map.value.addLayer({ + id: 'entities-unclustered-point', + type: 'circle', + source: 'entities', + filter: ['!', ['has', 'point_count']], + paint: { + 'circle-color': '#be7437', + 'circle-radius': 10, + 'circle-stroke-width': 1, + 'circle-stroke-color': '#ddd', + }, + }); + layerIds.push('entities-unclustered-point'); + + // inspect a cluster on click + map.value.on('click', 'entities-clusters', async (e) => { + if (!map.value) { + return; } + const features = map.value.queryRenderedFeatures(e.point, { + layers: ['entities-clusters'], + }); + const clusterId = features[0].properties.cluster_id; + const zoom = await ( + map.value.getSource('entities') as GeoJSONSource + ).getClusterExpansionZoom(clusterId); + map.value.easeTo({ + center: (features[0].geometry as Point).coordinates as [number, number], + zoom, + }); }); - } -} -function displayPoint(feature: Feature) { - if (map.value) { - const center = feature.geometry.coordinates as [number, number]; - const popup = new Popup({ - closeButton: false, - offset: 25, - }).setHTML(` + // When a click event occurs on a feature in + // the unclustered-point layer, open a popup at + // the location of the feature, with + // description HTML from its properties. + map.value.on('click', 'entities-unclustered-point', (e) => { + if (!map.value) { + return; + } + const feature = e.features ? e.features[0] : null; + if (!feature) { + return; + } + // Ensure that if the map is zoomed out such that + // multiple copies of the feature are visible, the + // popup appears over the copy being pointed to. + const coordinates = (feature.geometry as Point).coordinates.slice() as [ + number, + number, + ]; + while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) { + coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360; + } + + new Popup() + .setLngLat(coordinates) + .setHTML( + `
${t(feature.properties?.entity_type)}
${feature.properties?.name}
-
${feature.properties?.description ? feature.properties?.description : ''}
`); - markers.push( - new Marker({ color: '#FF0000' }) - .setLngLat(center) - .setPopup(popup) - .addTo(map.value), - ); +
${feature.properties?.description ? feature.properties?.description : ''}
`, + ) + .addTo(map.value); + }); + + map.value.on('mouseenter', 'entities-clusters', () => { + if (map.value) map.value.getCanvas().style.cursor = 'pointer'; + }); + map.value.on('mouseleave', 'entities-clusters', () => { + if (map.value) map.value.getCanvas().style.cursor = ''; + }); } } diff --git a/frontend/src/components/NavDrawer.vue b/frontend/src/components/NavDrawer.vue index f965475..f3cc731 100644 --- a/frontend/src/components/NavDrawer.vue +++ b/frontend/src/components/NavDrawer.vue @@ -33,14 +33,6 @@ class="text-primary" > - - - diff --git a/frontend/src/components/ResultsGrid.vue b/frontend/src/components/ResultsGrid.vue deleted file mode 100644 index d2076c7..0000000 --- a/frontend/src/components/ResultsGrid.vue +++ /dev/null @@ -1,63 +0,0 @@ - - - - diff --git a/frontend/src/components/SearchPanel.vue b/frontend/src/components/SearchPanel.vue new file mode 100644 index 0000000..f0e8e76 --- /dev/null +++ b/frontend/src/components/SearchPanel.vue @@ -0,0 +1,266 @@ + + + diff --git a/frontend/src/components/TagsBadges.vue b/frontend/src/components/TagsBadges.vue index 8d7b8e1..eec74a5 100644 --- a/frontend/src/components/TagsBadges.vue +++ b/frontend/src/components/TagsBadges.vue @@ -1,7 +1,7 @@ - diff --git a/frontend/src/components/VideosResults.vue b/frontend/src/components/VideosResults.vue new file mode 100644 index 0000000..36f306d --- /dev/null +++ b/frontend/src/components/VideosResults.vue @@ -0,0 +1,82 @@ + + + diff --git a/frontend/src/components/VideosSearchPanel.vue b/frontend/src/components/VideosSearchPanel.vue new file mode 100644 index 0000000..6577e9b --- /dev/null +++ b/frontend/src/components/VideosSearchPanel.vue @@ -0,0 +1,162 @@ + + + diff --git a/frontend/src/components/models.ts b/frontend/src/components/models.ts index 597db26..9b2bf36 100644 --- a/frontend/src/components/models.ts +++ b/frontend/src/components/models.ts @@ -25,11 +25,17 @@ export interface Option { level?: number; } -export interface TaxonomyNodeOption { +export interface VocabularyOption { value: string; label: string; + urn: string; taxonomy: TaxonomyNode; vocabulary: TaxonomyNode; - term?: TaxonomyNode; +} + +export interface TermOption { + value: string; + label: string; urn: string; + children?: TermOption[]; } diff --git a/frontend/src/css/app.scss b/frontend/src/css/app.scss index b922baa..87887b6 100644 --- a/frontend/src/css/app.scss +++ b/frontend/src/css/app.scss @@ -1,4 +1,18 @@ // app global css in SCSS form +@font-face { + font-family: FTKunstGrotesk; + src: url(./fonts/FTKunstGrotesk-Medium.woff); +} +@font-face { + font-family: FTKunstGroteskItalic; + src: url(./fonts/FTKunstGrotesk-MediumItalic.woff); +} + +body { + font-family: FTKunstGrotesk, Helvetica, Arial, sans-serif; + text-rendering: geometricPrecision; + // padding-top: calc(var(--header-height) - 1px) +} a { color: black; @@ -52,3 +66,9 @@ a { .a-map { border-right: solid 2px $primary; } + +.a-legend { + background-color: $primary; + opacity: 0.8; + color: white; +} diff --git a/frontend/src/css/fonts/FTKunstGrotesk-Medium.woff b/frontend/src/css/fonts/FTKunstGrotesk-Medium.woff new file mode 100644 index 0000000000000000000000000000000000000000..81efca9d1e9286f15d9b38bd15e1dae5a0cdd49e GIT binary patch literal 77220 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%0DQknzDJel}T*wgOQ?(ag>9goL#WGAl)_Aa+Q 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 literal 0 HcmV?d00001 diff --git a/frontend/src/i18n/en/index.ts b/frontend/src/i18n/en/index.ts index 8cedd4a..fd2620e 100644 --- a/frontend/src/i18n/en/index.ts +++ b/frontend/src/i18n/en/index.ts @@ -1,29 +1,53 @@ export default { - purpose: 'Purpose', - purpose_text: - 'We are convinced that renewable biobased materials such as wood and fibers from agriculture, minimally processed geobased materials like earth and stone, as well those derived from reuse, must reclaim there place in the building culture. With over 40% of the annual global greenhouse gas emissions, the construction sector clearly emerges as one of the priority areas requiring tangible measures to achieve carbon reduction by 2050.', - read_manifesto: 'Read the manifesto', - filters: 'Filters', - home: 'Home', - contact: 'Contact', - atlas: 'Atlas', + 'building-material': 'Building Material', + 'natural-resource': 'Natural Resource', + 'technical-construction': 'Technical Construction', about: 'About', + absorption_coefficient: 'Absorption Coefficient', + acoustic: 'Acoustic', + atlas: 'Atlas', + building: 'Building', + compressive_strength: 'Compressive Strength', + contact: 'Contact', contribute: 'Contribute', - resources: 'Resources', + density: 'Density', + diffusivity: 'Diffusivity', + download: 'Download', + effusivity: 'Effusivity', + external_links: 'External links', + filters: 'Filters', + fire_resistance_class: 'Fire Resistance Class', + fire: 'Fire', go_back: 'Go back', - page_not_found: 'Oops. Nothing here...', - type_here: 'Type here...', + home: 'Home', + hygrothermal: 'Hygrothermal', + list: 'List', + map: 'Map', material: 'Material', - 'natural-resource': 'Natural resource', - 'building-material': 'Building material', - 'technical-construction': 'Technical construction', - building: 'Building', + moisture_buffering: 'Moisture Buffering', + no_results: 'No results', + page_not_found: 'Oops. Nothing here...', professional: 'Professional', - map: 'Map', - list: 'List', - videos: 'Videos', + purpose_text: + 'We are convinced that renewable biobased materials such as wood and fibers from agriculture, minimally processed geobased materials like earth and stone, as well those derived from reuse, must reclaim there place in the building culture. With over 40% of the annual global greenhouse gas emissions, the construction sector clearly emerges as one of the priority areas requiring tangible measures to achieve carbon reduction by 2050.', + purpose: 'Purpose', + reaction_to_fire: 'Reaction to Fire', + read_manifesto: 'Read the manifesto', + resources: 'Resources', search: 'Search', - showing_results: 'Showing results {count}/{total}', - no_results: 'No results', + settlement: 'Settlement', show_more: 'Show more', + showing_results: 'Showing results {count}/{total}', + shrinkage: 'Shrinkage', + sound_reduction_index: 'Sound Reduction Index', + structural: 'Structural', + tensile_strength: 'Tensile Strength', + thermal_capacity: 'Thermal Capacity', + thermal_conductivity: 'Thermal Conductivity', + type_here: 'Type here...', + u: 'U', + vapor_diffusion_resistance: 'Vapor Diffusion Resistance', + videos: 'Videos', + watch: 'Watch', + youngs_modulus: "Young's Modulus", }; diff --git a/frontend/src/it/index.ts b/frontend/src/it/index.ts new file mode 100644 index 0000000..ff8b4c5 --- /dev/null +++ b/frontend/src/it/index.ts @@ -0,0 +1 @@ +export default {}; diff --git a/frontend/src/models.ts b/frontend/src/models.ts index 72fa77b..6a2bb5c 100644 --- a/frontend/src/models.ts +++ b/frontend/src/models.ts @@ -10,7 +10,8 @@ export interface FileRef { } export interface FileItem { - ref: FileRef; + ref?: FileRef; + url?: string; legend?: string; } @@ -80,6 +81,8 @@ export interface PhysicalEntity extends Entity { diffusivity_low?: number; absorption_coefficient_low?: number; sound_reduction_index_low?: number; + reaction_to_fire_low?: string; + fire_resistance_class_low?: string; air_tightness_low?: number; density_high?: number; @@ -97,6 +100,8 @@ export interface PhysicalEntity extends Entity { diffusivity_high?: number; absorption_coefficient_high?: number; sound_reduction_index_high?: number; + reaction_to_fire_high?: string; + fire_resistance_class_high?: string; air_tightness_high?: number; } @@ -195,6 +200,7 @@ export interface Document extends Entity { lon: number; lat: number; }; + relates_to?: string[]; } export interface SearchResult { @@ -203,3 +209,20 @@ export interface SearchResult { limit: number; data?: Document[]; } + +export interface Video { + id: string; + name: string; + legend?: string; + url: string; + tags: string[]; + parent_id: string; + entity_type: string; +} + +export interface VideoResult { + total: number; + skip: number; + limit: number; + data?: Video[]; +} diff --git a/frontend/src/pages/DocumentPage.vue b/frontend/src/pages/DocumentPage.vue new file mode 100644 index 0000000..150d699 --- /dev/null +++ b/frontend/src/pages/DocumentPage.vue @@ -0,0 +1,27 @@ + + + diff --git a/frontend/src/pages/IndexPage.vue b/frontend/src/pages/IndexPage.vue index c3820e8..d76eccd 100644 --- a/frontend/src/pages/IndexPage.vue +++ b/frontend/src/pages/IndexPage.vue @@ -1,68 +1,28 @@ diff --git a/frontend/src/pages/SearchPage.vue b/frontend/src/pages/SearchPage.vue index 66140cd..91f17bb 100644 --- a/frontend/src/pages/SearchPage.vue +++ b/frontend/src/pages/SearchPage.vue @@ -1,175 +1,9 @@ diff --git a/frontend/src/pages/VideosPage.vue b/frontend/src/pages/VideosPage.vue new file mode 100644 index 0000000..54bf333 --- /dev/null +++ b/frontend/src/pages/VideosPage.vue @@ -0,0 +1,9 @@ + + + diff --git a/frontend/src/router/routes.ts b/frontend/src/router/routes.ts index 2ab3f09..00e14ee 100644 --- a/frontend/src/router/routes.ts +++ b/frontend/src/router/routes.ts @@ -12,6 +12,12 @@ const routes: RouteRecordRaw[] = [ children: [ { path: 'about', component: () => import('pages/AboutPage.vue') }, { path: 'search', component: () => import('pages/SearchPage.vue') }, + { path: 'videos', component: () => import('pages/VideosPage.vue') }, + { + name: 'doc', + path: '_/:id', + component: () => import('pages/DocumentPage.vue'), + }, ], }, diff --git a/frontend/src/stores/search.ts b/frontend/src/stores/search.ts index 4191d2b..5a58697 100644 --- a/frontend/src/stores/search.ts +++ b/frontend/src/stores/search.ts @@ -1,6 +1,6 @@ import { defineStore } from 'pinia'; import { api } from 'src/boot/api'; -import { SearchResult, Document } from 'src/models'; +import { SearchResult, Document, VideoResult } from 'src/models'; import { Feature, FeatureCollection, @@ -9,11 +9,13 @@ import { } from 'geojson'; export const useSearchService = defineStore('search', () => { + const selectedView = ref('map'); const selectedTerms = ref([]); const filterText = ref(''); const searching = ref(false); const results = ref(); const geoResults = ref(); + const videoResults = ref(); const skip = ref(0); const limit = ref(100); @@ -48,13 +50,34 @@ export const useSearchService = defineStore('search', () => { selectedTerms.value = []; } - function search(withLimit: number = limit.value) { + async function getDocument( + id: string, + fields: string[] = [], + index: string = 'entities', + ): Promise { + searching.value = true; + return api + .get('/search/_doc', { + params: { id, fields, index }, + paramsSerializer: { + indexes: null, // no brackets at all + }, + }) + .then((response) => { + const result = response.data; + return result.data?.length ? result.data[0] : undefined; + }) + .finally(() => (searching.value = false)); + } + + async function search_entities(withLimit: number = limit.value) { searching.value = true; results.value = undefined; + geoResults.value = undefined; limit.value = withLimit; return Promise.all([ api - .get('/search/', { + .get('/search/_entities', { params: { tags: selectedTerms.value, text: filterText.value, @@ -69,7 +92,7 @@ export const useSearchService = defineStore('search', () => { results.value = response.data; }), api - .get('/search/', { + .get('/search/_entities', { params: { tags: selectedTerms.value, text: filterText.value, @@ -89,17 +112,45 @@ export const useSearchService = defineStore('search', () => { }); } + async function search_videos(withLimit: number = limit.value) { + searching.value = true; + videoResults.value = undefined; + limit.value = withLimit; + return api + .get('/search/_videos', { + params: { + tags: selectedTerms.value, + text: filterText.value, + skip: skip.value, + limit: limit.value, + }, + paramsSerializer: { + indexes: null, // no brackets at all + }, + }) + .then((response) => { + videoResults.value = response.data; + }) + .finally(() => { + searching.value = false; + }); + } + return { + selectedView, selectedTerms, filterText, searching, results, geoResults, + videoResults, features, hasFilters, skip, limit, reset, - search, + search_entities, + search_videos, + getDocument, }; }); diff --git a/frontend/src/utils/files.ts b/frontend/src/utils/files.ts new file mode 100644 index 0000000..00bc495 --- /dev/null +++ b/frontend/src/utils/files.ts @@ -0,0 +1,51 @@ +import { cdnUrl } from 'src/boot/api'; +import { Document, FileItem } from 'src/models'; + +export function getImageUrls(row: Document) { + const images = row.files + ? row.files + .filter( + (fileRef) => + fileRef.ref && fileRef.ref.mime_type?.startsWith('image'), + ) + .map((fileRef) => `${cdnUrl}/${fileRef.ref?.path}`) + : []; + return images; +} + +export function isImage(file: FileItem) { + const name = file.url ? file.url : file.ref?.name; + return ['.png', '.jpg', '.jpeg', '.webp'].find( + (suffix) => name && name.toLowerCase().endsWith(suffix), + ); +} + +export function isVideo(file: FileItem) { + return ( + file.url?.includes('youtube.com') || + file.url?.includes('vimeo.com') || + file.url?.includes('srf.ch/play/tv') || + file.url?.includes('rts.ch/play/tv') + ); +} + +export function isPDF(file: FileItem) { + return file.ref?.mime_type === 'application/pdf'; +} + +export function toFileUrl(file: FileItem) { + if (file.url) { + return toEmbededVideoUrl(file.url); + } + return `${cdnUrl}/${file.ref?.path}`; +} + +export function toEmbededVideoUrl(url: string) { + if (url) { + return url + .replace('youtube.com/watch?v=', 'youtube.com/embed/') + .replace('youtu.be', 'youtube.com/embed') + .replace('vimeo.com', 'player.vimeo.com/video'); + } + return ''; +} diff --git a/frontend/src/utils/maps.ts b/frontend/src/utils/maps.ts index e2c2ded..0cccf47 100644 --- a/frontend/src/utils/maps.ts +++ b/frontend/src/utils/maps.ts @@ -13,6 +13,8 @@ export const style: StyleSpecification = { maxzoom: 20, }, }, + glyphs: + 'https://orangemug.github.io/font-glyphs/glyphs/{fontstack}/{range}.pbf', layers: [ { id: 'light',