Compare commits

..

68 Commits

Author SHA1 Message Date
01e10d6f9d Temporary fix for the keywords section when accessing from DOI 2024-09-18 15:28:52 +02:00
5dd8821667 Fixed scrolling when facet list is collapsed 2024-09-18 12:42:35 +02:00
9f076daf15 Merge branch 'feat/opensearch' into develop 2024-09-16 14:44:05 +02:00
de0ccdfcac Added comments 2024-09-16 13:31:51 +02:00
5eaf505669 Merge branch 'feat/opensearch' of https://gitea.geologie.ac.at/geolba/tethys.frontend into feat/opensearch 2024-09-16 13:00:54 +02:00
8e3f4fa88e Code commenting 2024-09-16 12:57:26 +02:00
fef92c392d - Minimap code commented
- Code cleaning
2024-09-12 16:31:24 +02:00
6f1b9f4c5f - Code cleaning for OpenSearch
- Added comments
- Facets menu small change
2024-09-12 15:54:59 +02:00
013d12e7b3 Attribution in minimap corrected 2024-09-10 16:23:20 +02:00
50ab318854 - Icon and link for licenses added
- Listing of authors in list of publications corrected
- Coverage section improved
- Contributor section moved down
2024-09-04 13:46:09 +02:00
bb7d8eb378 - Coverage section reformatted
- Keywords section converted in linked elements
2024-09-03 16:13:27 +02:00
826c5b8c4e - Pagination arrow color fixed according to page
- Data type showed in suggestions corrected according to user-friendly values
2024-09-03 12:53:02 +02:00
7953b3c09c Merge branch 'feat/opensearch' of https://gitea.geologie.ac.at/geolba/tethys.frontend into feat/opensearch 2024-09-03 10:38:38 +02:00
f4bd82a453 Adding comments 2024-09-03 08:55:11 +02:00
c47d77401b Minimap: - bounds corrected to prevent problems with coverages in the map limit.
- Sources info simplified
2024-09-02 16:31:00 +02:00
bd32e6071d Improvement minimap 2024-09-02 09:34:51 +02:00
2b7b5323d3 - Results box background colour changed
- Results message redesigned
- Pagination text aligned right
2024-08-30 13:50:38 +02:00
6fb7ad0e93 Code cleaning 2024-08-28 14:34:46 +02:00
9ad4037c74 Values for facet "language" changed to "English" and "Deutsch" 2024-08-28 10:29:03 +02:00
0126ae9f85 Datatype facet improved with user-friendly terms for the different types. 2024-08-26 16:36:09 +02:00
03a55f6a58 Addition of "doctype"/datatype as another possible facet and search element to use 2024-08-19 15:45:52 +02:00
110c2db8bf Facet filtering modified:
- Author and Subjects substituted by Creator and Keyword
- Facet labels now separated by "|" instead of comma
2024-08-09 16:17:30 +02:00
cc62df68da Merge Frank's branch 'dev.frontend/opensearch' into feat/opensearch 2024-08-06 16:41:48 +02:00
332923df2d Apparently OpenSearch works properly. Still some final tests pending 2024-06-21 15:07:57 +02:00
8767748f9c Ongoing improvement in OpenSearch query 2024-06-20 18:20:16 +02:00
32f743c04e Tests. TODO solving failure with the faceted search with 2 subjects 2024-06-19 16:21:31 +02:00
95c7c8ba7b Fixing facetedsearch 2024-06-19 11:12:53 +02:00
d135ab2d50 Progress OpenSearch. Pending solving onFilter issue with active filters 2024-06-13 17:01:14 +02:00
da430d6142 Facetsearch progressed. Pending fixing onClearFacetCategoryOPEN behaviour. Facets menu doesn't work properly. 2024-06-12 16:51:26 +02:00
a70e454cbc OpenSearch progress. Stetic changes in result list. Faceted search not started 2024-06-11 14:38:50 +02:00
9b8b2bd5ac OpenSearch: almost implemented, fixing case sensitive issues 2024-06-11 09:33:03 +02:00
4f53411d07 Opensearch progress.
- Term search works
- Pending faceted search
- Ongoing: highlight of fuzzy results
2024-06-07 17:44:13 +02:00
6d1c1b28c3 OpenSearch - some progress in migration 2024-06-06 17:11:54 +02:00
a853e93b68 Progress with OpenSearch tests with fuzzy search and wildcard 2024-05-28 16:33:55 +02:00
953f6551a1 Minimap: changing of colour, removal of zoom controls 2024-05-28 09:08:55 +02:00
9968b1e466 Publication minimap implemented 2024-05-24 13:47:28 +02:00
ccf4e238f3 Initial minimap concept added to the dataset-detail 2024-05-23 16:59:17 +02:00
2d38b690fa Merge branch 'master' of https://gitea.geologie.ac.at/geolba/tethys.frontend 2024-05-23 10:33:51 +02:00
376b828831 - Added link to CTS logo
- Modified responsiveness of logos at the base
- Changed GeoSphere logo in footer
2024-05-22 16:27:21 +02:00
97f6d359b9 Fixed BASE logo and development notice at the top 2024-05-15 13:01:46 +00:00
484be48d1e Added queries example files 2024-05-15 13:47:22 +02:00
8962345f99 Created a first method to test query to OpenSearch 2024-05-14 16:31:22 +02:00
fab69f496b Starting of conversion to OpenSearch 2024-05-13 16:56:15 +02:00
515ef24c54 initial push test for new branch 2024-05-13 14:41:54 +02:00
0611ba9437 Update CTS link target new window 2024-05-10 15:18:44 +02:00
d50e93a658 CTS link opens in new window. New comments on search related classes and functions 2024-05-10 15:01:57 +02:00
d50bf55fb3 test2 2024-05-10 10:50:10 +02:00
07bdcd9610 test 2024-05-10 10:47:21 +02:00
85ab6fb8cc Comments added and commented code removed 2024-05-08 17:11:55 +02:00
524e996c8a Fixed 3 logos shadow 2024-05-07 14:11:54 +02:00
3dbb04d737 Initial test comments added. Some original commented code deleted 2024-05-07 13:54:28 +02:00
d75ab4f367 Added space before CTS logo 2024-05-07 13:51:50 +02:00
1e2e37aef3 Merge production tethys to incorporate last push from arno. (rollback to Bulma 0.9) 2024-05-06 14:21:40 +02:00
afa3b8264b Merge branch 'master' of gitlab.geosphere.at:tethysrdr/software-development/frontend/frontend 2024-05-06 14:09:30 +02:00
a1b2f39e98 Add another script to serve in port 3002 2024-05-06 14:02:39 +02:00
8e3245c270 - reset to bulma 0.9*
- cahnged src/assets/scss/main-styles.scss
2024-05-06 13:37:42 +02:00
c040f6c380 Activating development banner again 2024-05-02 14:36:58 +02:00
1f855b9629 Fixing last changes of logos in bottom of main page 2024-05-02 11:06:48 +02:00
00d5bbb8db Logos redistributed. CTS link. Geosphere negative logo 2024-04-30 16:04:34 +02:00
debccc2e53 Test 2024-04-30 11:28:46 +02:00
b570e364b9 - example commit 2024-04-10 09:42:48 +02:00
e997c0e1a1 - switch to tailwind 2024-04-09 14:17:30 +02:00
c6469b00b4 - added src/compoenents/Pagination.vue
- npm updates
- added "type": "module" inside package.json
- renamed .eslintrc.js, postcss.config.js and vue.config.js
- adapted search-view-component
2024-03-19 16:43:25 +01:00
315eabf3bb - prepare project for using tailwind with postcss configuration files
- change linl to backend login
- change copyright hint with actual year
2024-03-18 10:04:36 +01:00
59a83c1978 - change env variable VUE_APP_PORTAL to VUE_API
- added .env.example file
2024-03-16 17:03:46 +01:00
a85debca92 - added CTS Logo
- updated npm packages like vue-facing-decorator and some prettier and eslint dev dependecies
- remove node-polyfill-webpack-plugin
2024-03-16 16:42:25 +01:00
Arno Kaimbacher
6f63db4a71 - new prservation plan January 2024 2024-01-19 13:11:29 +01:00
Arno Kaimbacher
1e33c340a2 - updated preservation plan
- updated contact-ciew-component.vue
2023-12-07 16:02:48 +01:00
61 changed files with 5364 additions and 16277 deletions

View File

@ -1,3 +1,3 @@
> 1% > 1%
last 2 versions last 4 versions
not dead not dead

6
.env.example Normal file
View File

@ -0,0 +1,6 @@
APP_URL=//tethys.at
VUE_API=//www.tethys.at
# SOLR_HOST=tethys.at
# SOLR_CORE=rdr_data
OPEN_HOST=192.168.21.18
OPEN_CORE=tethys-records

View File

@ -25,14 +25,23 @@ module.exports = {
// parser: "@typescript-eslint/parser", // parser: "@typescript-eslint/parser",
}, },
rules: { rules: {
// indent: [
// 'error',
// 2,
// {
// SwitchCase: 1,
// ignoredNodes: ['ConditionalExpression'], <-- I add this line
// },
// ],
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off", "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"@typescript-eslint/indent": ["error", 4, { ignoredNodes: ["PropertyDefinition"] }], "@typescript-eslint/indent": ["error", 4, {"SwitchCase": 1, ignoredNodes: ["PropertyDefinition"] }],
"arrow-parens": "off", "arrow-parens": "off",
semi: "error", semi: "error",
// "@vue-eslint/printWidth": ["error", 120], // "@vue-eslint/printWidth": ["error", 120],
"vue/v-bind-style": ["warn", "longform"], //autofix "vue/v-bind-style": ["warn", "longform"], //autofix
"vue/prop-name-casing": ["error", "camelCase"], //autofix "vue/prop-name-casing": ["error", "camelCase"], //autofix
"vue/multi-word-component-names": ["warn"],
// https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/attribute-hyphenation.md // https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/attribute-hyphenation.md
"vue/attribute-hyphenation": [ "vue/attribute-hyphenation": [
"error", "error",

9
OpenSearch queries.txt Normal file
View File

@ -0,0 +1,9 @@
Get all documents in the index ("core" in solr)
----------------------------------------------------
curl -XGET "http://192.168.21.18/tethys-records/_search?pretty" -H 'Content-Type: application/json' -d'
{
"query": {
"match_all": {}
}
}'

View File

@ -1,24 +1,38 @@
# typescript-vueapp # tethys.frontend is a typescript-vueapp
## Project setup ## Project setup
```
```bash
npm install npm install
``` ```
### Compiles and hot-reloads for development ## create .env file for environments
```bash
cp .env.example .env
``` ```
### Compiles and hot-reloads for development
```bash
npm run serve npm run serve
``` ```
### Compiles and minifies for production ### Compiles and minifies for production
```
```bash
npm run build npm run build
``` ```
Check out deployment instructions at <https://cli.vuejs.org/guide/deployment.html> \
After building, copy the dist folder to the web root of your Nginx server.
### Lints and fixes files ### Lints and fixes files
```
```bash
npm run lint npm run lint
``` ```
### Customize configuration ### Customize build and dev configuration
See [Configuration Reference](https://cli.vuejs.org/config/). See [Configuration Reference](https://cli.vuejs.org/config/).

60
SOLR queries.txt Normal file
View File

@ -0,0 +1,60 @@
Search by a specific value for one of the fields
https://tethys.at/solr/rdr_data/select?&q=year:2024
Search within a specific range of values for one of the fields
https://tethys.at/solr/rdr_data/select?&q=year:2023%20TO%202024 years 2023 to 2024
Search for a term (search done in predefined field?)
https://tethys.at/solr/rdr_data/select?&q=linz
Predefined Tethys Search
https://tethys.at/solr/rdr_data/select?&0=fl%3Did%2Clicence%2Cserver_date_published%2Cabstract_output%2Cidentifier%2Ctitle_output%2Ctitle_additional%2Cauthor%2Csubject%2Cdoctype&q=%2A&q.op=or&defType=edismax&qf=title%5E3%20author%5E2%20subject%5E1&indent=on&wt=json&rows=10&start=0&sort=server_date_published%20desc&facet=on&json.facet.language=%7B%20type%3A%20%22terms%22%2C%20field%3A%20%22language%22%20%7D&json.facet.subject=%7B%20type%3A%20%22terms%22%2C%20field%3A%20%22subject%22%2C%20limit%3A%20-1%20%7D&json.facet.year=%7B%20type%3A%20%22terms%22%2C%20field%3A%20%22year%22%20%7D&json.facet.author=%7B%20type%3A%20%22terms%22%2C%20field%3A%20%22author_facet%22%2C%20limit%3A%20-1%20%7D
Predefined Tethys search changing the last facet to "doctype"
https://tethys.at/solr/rdr_data/select?&0=fl%3Did%2Clicence%2Cserver_date_published%2Cabstract_output%2Cidentifier%2Ctitle_output%2Ctitle_additional%2Cauthor%2Csubject%2Cdoctype&q=*&q.op=or&defType=edismax&qf=title^3%20author^2%20subject^1&indent=on&wt=json&rows=10&start=0&sort=server_date_published%20desc&facet=on&json.facet.language={%20type%3A%20%22terms%22%2C%20field%3A%20%22language%22%20}&json.facet.subject={%20type%3A%20%22terms%22%2C%20field%3A%20%22subject%22%2C%20limit%3A%20-1%20}&json.facet.author={%20type%3A%20%22terms%22%2C%20field%3A%20%22author_facet%22%2C%20limit%3A%20-1%20}&json.facet.doctype={%20type%3A%20%22terms%22%2C%20field%3A%20%22doctype%22%2C%20limit%3A%20-1%20}
Giving a value for only one facet => Author: Coric, Stjepan (16)
https://tethys.at/solr/rdr_data/select?&0=fl%3Did%2Clicence%2Cserver_date_published%2Cabstract_output%2Cidentifier%2Ctitle_output%2Ctitle_additional%2Cauthor%2Csubject%2Cdoctype&q=%2A&q.op=or&defType=edismax&qf=title%5E3%20author%5E2%20subject%5E1&indent=on&wt=json&rows=10&fq=author%3A%28%22Coric%2C%20Stjepan%22%29&start=0&sort=server_date_published%20desc&facet=on&json.facet.language=%7B%20type%3A%20%22terms%22%2C%20field%3A%20%22language%22%20%7D&json.facet.subject=%7B%20type%3A%20%22terms%22%2C%20field%3A%20%22subject%22%2C%20limit%3A%20-1%20%7D&json.facet.year=%7B%20type%3A%20%22terms%22%2C%20field%3A%20%22year%22%20%7D&json.facet.author=%7B%20type%3A%20%22terms%22%2C%20field%3A%20%22author_facet%22%2C%20limit%3A%20-1%20%7D
ASCII
+----+-----+----+-----+----+-----+----+-----+
| Hx | Chr | Hx | Chr | Hx | Chr | Hx | Chr |
+----+-----+----+-----+----+-----+----+-----+
| 00 | NUL | 20 | SPC | 40 | @ | 60 | ` |
| 01 | SOH | 21 | ! | 41 | A | 61 | a |
| 02 | STX | 22 | " | 42 | B | 62 | b |
| 03 | ETX | 23 | # | 43 | C | 63 | c |
| 04 | EOT | 24 | $ | 44 | D | 64 | d |
| 05 | ENQ | 25 | % | 45 | E | 65 | e |
| 06 | ACK | 26 | & | 46 | F | 66 | f |
| 07 | BEL | 27 | ' | 47 | G | 67 | g |
| 08 | BS | 28 | ( | 48 | H | 68 | h |
| 09 | TAB | 29 | ) | 49 | I | 69 | i |
| 0A | LF | 2A | * | 4A | J | 6A | j |
| 0B | VT | 2B | + | 4B | K | 6B | k |
| 0C | FF | 2C | , | 4C | L | 6C | l |
| 0D | CR | 2D | - | 4D | M | 6D | m |
| 0E | SO | 2E | . | 4E | N | 6E | n |
| 0F | SI | 2F | / | 4F | O | 6F | o |
| 10 | DLE | 30 | 0 | 50 | P | 70 | p |
| 11 | DC1 | 31 | 1 | 51 | Q | 71 | q |
| 12 | DC2 | 32 | 2 | 52 | R | 72 | r |
| 13 | DC3 | 33 | 3 | 53 | S | 73 | s |
| 14 | DC4 | 34 | 4 | 54 | T | 74 | t |
| 15 | NAK | 35 | 5 | 55 | U | 75 | u |
| 16 | SYN | 36 | 6 | 56 | V | 76 | v |
| 17 | ETB | 37 | 7 | 57 | W | 77 | w |
| 18 | CAN | 38 | 8 | 58 | X | 78 | x |
| 19 | EM | 39 | 9 | 59 | Y | 79 | y |
| 1A | SUB | 3A | : | 5A | Z | 7A | z |
| 1B | ESC | 3B | ; | 5B | [ | 7B | { |
| 1C | FS | 3C | < | 5C | \ | 7C | | |
| 1D | GS | 3D | = | 5D | ] | 7D | } |
| 1E | RS | 3E | > | 5E | ^ | 7E | ~ |
| 1F | US | 3F | ? | 5F | _ | 7F | DEL |
+----+-----+----+-----+----+-----+----+-----+
https://www.asciitable.com/ see Hx
If you write encodeURIComponent(",") in your JavaScript console, then you will also get %2C. And with decodeURIComponent("%2C") you will get back the ","

5121
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve --port 3001", "serve": "vue-cli-service serve --port 3001",
"serve2": "vue-cli-service serve --port 3002",
"build": "vue-cli-service build", "build": "vue-cli-service build",
"lint": "vue-cli-service lint --check", "lint": "vue-cli-service lint --check",
"lint2": "eslint ./src --ext .js,.ts", "lint2": "eslint ./src --ext .js,.ts",
@ -11,15 +12,17 @@
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.1.1", "@fortawesome/fontawesome-free": "^6.1.1",
"@mdi/js": "^7.4.47",
"@openfonts/open-sans_all": "^1.44.2", "@openfonts/open-sans_all": "^1.44.2",
"axios": "^1.2.2", "axios": "^1.2.2",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"dayjs": "^1.10.7", "dayjs": "^1.10.7",
"leaflet": "^1.7.1", "dompurify": "^3.1.6",
"leaflet": "^1.9.4",
"qs": "^6.10.1", "qs": "^6.10.1",
"rxjs": "^7.5.5", "rxjs": "^7.5.5",
"vue": "^3.2.47", "vue": "^3.2.47",
"vue-facing-decorator": "^2.1.13", "vue-facing-decorator": "^3.0.4",
"vue-matomo": "^4.1.0", "vue-matomo": "^4.1.0",
"vue-router": "^4.0.12", "vue-router": "^4.0.12",
"xslt3": "^2.4.0" "xslt3": "^2.4.0"
@ -29,27 +32,32 @@
"@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.22.5", "@babel/plugin-proposal-decorators": "^7.22.5",
"@babel/preset-env": "^7.22.5", "@babel/preset-env": "^7.22.5",
"@tailwindcss/forms": "^0.5.7",
"@types/dompurify": "^3.0.5",
"@types/leaflet": "^1.7.9", "@types/leaflet": "^1.7.9",
"@typescript-eslint/eslint-plugin": "^5.48.1", "@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^5.40.1", "@typescript-eslint/parser": "^7.2.0",
"@vue/cli-plugin-eslint": "~5.0.4", "@vue/cli-plugin-eslint": "~5.0.4",
"@vue/cli-plugin-typescript": "~5.0.4", "@vue/cli-plugin-typescript": "~5.0.4",
"@vue/cli-service": "~5.0.4", "@vue/cli-service": "~5.0.4",
"@vue/compiler-sfc": "^3.0.0", "@vue/compiler-sfc": "^3.0.0",
"@vue/eslint-config-prettier": "^7.0.0", "@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^11.0.0", "@vue/eslint-config-typescript": "^13.0.0",
"autoprefixer": "^10.4.18",
"babel-preset-typescript-vue3": "^2.0.17", "babel-preset-typescript-vue3": "^2.0.17",
"bulma": "^0.9.3", "bulma": "^0.9.4",
"eslint": "^8.25.0", "eslint": "^8.25.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.9.0", "eslint-plugin-vue": "^9.9.0",
"node-polyfill-webpack-plugin": "^2.0.0", "postcss": "^8.4.36",
"prettier": "^2.7.1", "prettier": "^3.2.5",
"pug-plain-loader": "^1.1.0", "pug-plain-loader": "^1.1.0",
"sass": "^1.26.5", "sass": "^1.26.5",
"sass-loader": "^13.0.0", "sass-loader": "^14.1.1",
"tailwindcss": "^3.4.1",
"typescript": "^5.1.3", "typescript": "^5.1.3",
"webpack": "^5.72.1", "vue-loader": "^17.0.1",
"vue-loader": "^17.0.1" "webpack": "^5.72.1"
} },
"type": "module"
} }

6
postcss.config.cjs Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -1,17 +1,11 @@
<template> <template>
<!-- <HelloWorld msg="Welcome to Your Vue.js plus TypeScript App" /> --> <!-- <HelloWorld msg="Welcome to Your Vue.js plus TypeScript App" /> -->
<!-- <link
rel="stylesheet"
href="https://use.fontawesome.com/releases/v5.2.0/css/all.css"
integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ"
crossorigin="anonymous"
/> -->
<nav class="navbar navbar-light border-bottom" role="navigation" aria-label="main navigation"> <nav class="navbar navbar-light border-bottom" role="navigation" aria-label="main navigation">
<div class="navbar-brand"> <div class="navbar-brand">
<a class="navbar-item" href="/"> <a class="navbar-item" href="/">
<!-- <img src="./assets/images/TETHYS-Logo.svg" width="240px" height="86" alt="TETHYS Logo" /> --> <!-- <img src="./assets/images/TETHYS-Logo.svg" width="240px" height="86" alt="TETHYS Logo" /> -->
<img src="./assets/images/TETHYS-Logo.svg" width="240" height="86" /> <img src="./assets/images/TETHYS-Logo.svg" width="240" height="86" /> &nbsp;&nbsp;
<a href="https://doi.org/10.34894/TKWVFL" target="_blank"><img src="./assets/images/cts-logo.png" width="80" height="80" /></a>
</a> </a>
<a <a
id="menu-icon" id="menu-icon"
@ -61,7 +55,7 @@
<div class="navbar-end"> <div class="navbar-end">
<div class="navbar-item"> <div class="navbar-item">
<div class="buttons"> <div class="buttons">
<a v-bind:href="portal" class="button is-primary custom-button"> <a v-bind:href="portal" target="_blank" class="button is-primary custom-button">
<i class="fas fa-sign-in-alt"></i> <i class="fas fa-sign-in-alt"></i>
<!-- <fa :icon="['fas', 'phone']" class="fas fa-sign-in-alt" /> --> <!-- <fa :icon="['fas', 'phone']" class="fas fa-sign-in-alt" /> -->
<!-- </span> --> <!-- </span> -->
@ -137,7 +131,7 @@
<a href="mailto:repository@geologie.ac.at"><i class="fas fa-envelope pr-2"></i> repository(at)geosphere.at </a> <a href="mailto:repository@geologie.ac.at"><i class="fas fa-envelope pr-2"></i> repository(at)geosphere.at </a>
</li> </li>
</ul> </ul>
<img class="img-fluid pt-4" src="@/assets/site/img/geosphere-austria-logo.jpg" alt="GeoSphere Austria Logo" /> <img class="img-fluid pt-4" src="@/assets/site/img/geosphere-austria-logo-negativ.png" alt="GeoSphere Austria Logo" />
</div> </div>
</div> </div>
</div> </div>
@ -145,7 +139,7 @@
</footer> </footer>
<div class="container-fluid"> <div class="container-fluid">
<div class="copyright-notice">Copyright 2022 TETHYS RDR</div> <div class="copyright-notice">&#169; {{ currentYear }} TETHYS RDR</div>
</div> </div>
<!-- <vs-input <!-- <vs-input
@ -167,6 +161,7 @@ export default App;
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import 'leaflet/dist/leaflet.css';
// #app { // #app {
// font-family: Avenir, Helvetica, Arial, sans-serif; // font-family: Avenir, Helvetica, Arial, sans-serif;
// -webkit-font-smoothing: antialiased; // -webkit-font-smoothing: antialiased;
@ -244,7 +239,7 @@ footer .card-title {
margin-bottom: 0; margin-bottom: 0;
} }
.list-group-flush > .list-group-item { .list-group-flush > .list-group-item {
border-width: 0 0 1px; border-width: 0;
} }
.list-group-item:first-child { .list-group-item:first-child {
@ -267,4 +262,12 @@ footer .list-group-item a {
background-color: #384d6e; background-color: #384d6e;
padding: 0.3em 0; padding: 0.3em 0;
} }
.development-notice {
text-align: center;
color: #fff;
background-color: red;
padding: 1em 0;
font-weight: bold;
}
</style> </style>

View File

@ -6,6 +6,7 @@
<a class="navbar-item" v-bind:href="app_url"> <a class="navbar-item" v-bind:href="app_url">
<!-- <img src="./assets/images/TETHYS-Logo.svg" width="240px" height="86" alt="TETHYS Logo" /> --> <!-- <img src="./assets/images/TETHYS-Logo.svg" width="240px" height="86" alt="TETHYS Logo" /> -->
<img src="./assets/images/TETHYS-Logo.svg" width="240" height="86" /> <img src="./assets/images/TETHYS-Logo.svg" width="240" height="86" />
<img src="./assets/images/cts-logo.png" width="80" height="80" />
</a> </a>
<a <a
id="menu-icon" id="menu-icon"
@ -55,7 +56,7 @@
<div class="navbar-end"> <div class="navbar-end">
<div class="navbar-item"> <div class="navbar-item">
<div class="buttons"> <div class="buttons">
<a v-bind:href="portal" class="button is-primary custom-button"> <a v-bind:href="portal" target="_blank" class="button is-primary custom-button">
<i class="fas fa-sign-in-alt"></i> <i class="fas fa-sign-in-alt"></i>
<!-- <fa :icon="['fas', 'phone']" class="fas fa-sign-in-alt" /> --> <!-- <fa :icon="['fas', 'phone']" class="fas fa-sign-in-alt" /> -->
<!-- </span> --> <!-- </span> -->
@ -72,7 +73,7 @@
<router-view></router-view> <router-view></router-view>
<div class="container-fluid"> <div class="container-fluid">
<div class="copyright-notice">Copyright 2022 TETHYS RDR</div> <div class="copyright-notice">&#169; {{ currentYear }} TETHYS RDR</div>
</div> </div>
<!-- <vs-input <!-- <vs-input
@ -171,7 +172,7 @@ footer .card-title {
margin-bottom: 0; margin-bottom: 0;
} }
.list-group-flush > .list-group-item { .list-group-flush > .list-group-item {
border-width: 0 0 1px; border-width: 0;
} }
.list-group-item:first-child { .list-group-item:first-child {

View File

@ -1,45 +1,37 @@
import initializeAxios from "./axiosSetup"; // Import the necessary modules and functions
import { axiosRequestConfiguration } from "./config"; import initializeAxios from "./axiosSetup"; // Function to initialize the Axios instance
import { map } from "rxjs/operators"; import { axiosRequestConfiguration } from "./config"; // Axios configuration settings
// import { Observable } from "@reactivex/rxjs/compat"; import { map } from "rxjs/operators"; // Operator to transform the items emitted by an Observable
import { defer, Observable } from "rxjs"; import { defer, Observable } from "rxjs"; // RxJS utilities for creating and working with Observables
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios"; // Axios response type
// https://ichi.pro/de/so-wickeln-sie-axios-mit-typescript-und-react-in-rxjs-ein-118892823169891
// Initialize the Axios instance with the provided configuration
const axiosInstance = initializeAxios(axiosRequestConfiguration); const axiosInstance = initializeAxios(axiosRequestConfiguration);
// Function to make a GET request using Axios wrapped in an Observable
// eslint-disable-next-line
const get = <T>(url: string, queryParams?: any): Observable<T> => { const get = <T>(url: string, queryParams?: any): Observable<T> => {
return defer(() => axiosInstance.get<T>(url, { params: queryParams })).pipe(map((result: AxiosResponse) => result.data)); // Use defer to create an Observable that makes the Axios GET request when subscribed to
return defer(() => axiosInstance.get<T>(url, { params: queryParams }))
// Use map to transform the Axios response to extract the data property
.pipe(map((result: AxiosResponse) => result.data));
}; };
// const post = <T>(url: string, body: object, queryParams?: any): Observable<T | void> => { // Function to make a POST request using Axios wrapped in an Observable
// return defer(() => axiosInstance.post<T>(url, body, { params: queryParams })).pipe(map((result: AxiosResponse) => result.data)); const post = <T>(url: string, body: any, queryParams?: any): Observable<T> => {
// }; // Use defer to create an Observable that makes the Axios POST request when subscribed to
// console.log(body);
// console.log(queryParams);
// const put = <T>( return defer(() => axiosInstance.post<T>(url, body, {
// url: string, params: queryParams,
// body: object, headers: {
// queryParams?: object 'Accept': 'application/json',
// ): Observable<T | void> => { 'Content-Type': 'application/json'
// return defer(() => }
// axiosInstance.put<T>(url, body, { params: queryParams }) }))
// ).pipe(map((result) => result.data)); .pipe(map((result: AxiosResponse) => result.data)); // Use map to transform the Axios response to extract the data property
// }; };
// const patch = <T>( // Export the get and post functions as part of the default export
// url: string, export default { get, post };
// body: object,
// queryParams?: object
// ): Observable<T | void> => {
// return defer(() =>
// axiosInstance.patch<T>(url, body, { params: queryParams })
// ).pipe(map((result) => result.data));
// };
// const deleteR = <T>(url: string, id: number): Observable<T | void> => {
// return defer(() => axiosInstance.delete(`${url}/${id}`)).pipe(
// map((result) => result.data)
// );
// };
export default { get };

View File

@ -1,18 +1,10 @@
import axios, { AxiosRequestConfig, AxiosInstance } from "axios"; import axios, { AxiosRequestConfig, AxiosInstance } from "axios";
const initialization = (config: AxiosRequestConfig): AxiosInstance => { const initialization = (config: AxiosRequestConfig): AxiosInstance => {
//axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
delete axios.defaults.headers.common["X-Requested-With"];
// axios.defaults.withCredentials = true;
// const token = document.head.querySelector('meta[name="csrf-token"]');
// if (token) {
// axios.defaults.headers.common["X-CSRF-TOKEN"] = token.innerHTML;
// }
const axiosInstance = axios.create(config);
/* delete axios.defaults.headers.common["X-Requested-With"];
Add default headers, interceptors etc..
*/ const axiosInstance = axios.create(config);
return axiosInstance; return axiosInstance;
}; };

View File

@ -1,31 +1,16 @@
import { AxiosRequestConfig } from "axios"; import { AxiosRequestConfig } from "axios";
import { stringify } from "qs"; import { stringify } from "qs";
// let headers: AxiosRequestConfig['headers'] = /* This file configures Axios to send requests with the specified headers and parameters serialization format for URL-encoded form data */
// headers['Content-Type'] = 'multipart/form-data';
export const axiosRequestConfiguration: AxiosRequestConfig = { export const axiosRequestConfiguration: AxiosRequestConfig = {
// responseType: "text",
// headers: {
// // "Content-Type": "text/plain",
// "Content-Type": "application/x-www-form-urlencoded",
// // "Content-Type": "application/x-www-form-urlencoded",
// // credentials: "same-origin",
// // "Access-Control-Allow-Credentials": "true",
// // "Access-Control-Allow-Origin": "*",
// },
headers: { headers: {
//: AxiosHeaders | Partial<RawAxiosHeaders & MethodsHeaders & CommonHeaders> | undefined
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
}, },
// headers: {
// "Content-type": "application/json; charset=UTF-8",
// },
// paramsSerializer: {
// indexes: null, // by default: false
// },
paramsSerializer: { paramsSerializer: {
/* The serialize function takes an object of key-value pairs as input and uses the qs.stringify method to convert it into a URL-encoded string.
The arrayFormat: "repeat" option specifies how arrays should be serialized in the URL parameters. */
serialize: (params: Record<string, number>) => { serialize: (params: Record<string, number>) => {
return stringify(params, { arrayFormat: "repeat" }); return stringify(params, { arrayFormat: "repeat" });
}, },

View File

@ -1,5 +1,4 @@
import { Component, Vue, Watch } from "vue-facing-decorator"; import { Component, Vue, Watch } from "vue-facing-decorator";
// import { RouteLocation } from "vue-router";
import HomeViewComponent from "./views/home-view/home-view-component.vue"; import HomeViewComponent from "./views/home-view/home-view-component.vue";
import HelpViewComponent from "./views/help-view/help-view-component.vue"; import HelpViewComponent from "./views/help-view/help-view-component.vue";
import MapViewComponent from "./views/map-view/map-view.component.vue"; import MapViewComponent from "./views/map-view/map-view.component.vue";
@ -11,27 +10,10 @@ import ContactViewComponent from "./views/contact-view/contact-view-component.vu
import SitelinkViewComponent from "./views/sitelink-view/sitelink-view-component.vue"; import SitelinkViewComponent from "./views/sitelink-view/sitelink-view-component.vue";
import ImprintViewComponent from "./views/imprint-view/imprint-view-component.vue"; import ImprintViewComponent from "./views/imprint-view/imprint-view-component.vue";
import TermsViewComponent from "./views/terms-view/terms-view-component"; import TermsViewComponent from "./views/terms-view/terms-view-component";
import { VUE_APP_PORTAL } from "./constants";
// import VsInput from "./components/vs-input/vs-input.vue";
// import VsResult from "./components/vs-result/vs-result.vue";
// import FacetCategory from "./components/face-category/facet-category.vue";
// import ActiveFacetCategory from "./components/active-facet-category/active-facet-category.vue";
// import { SolrSettings } from "@/models/solr";
// import { DatasetService } from "./services/dataset.service";
// import { Suggestion } from "./models/dataset";
// import { SolrResponse, FacetFields, FacetItem, FacetResults, FacetInstance } from "./models/headers";
// import { ActiveFilterCategories } from "@/models/solr";
// https://devsoniq.com/how-to-toggle-bulma-css-navbar-in-your-vue-js-project/
@Component({ @Component({
components: { components: {
// HelloWorld,
HomeViewComponent, HomeViewComponent,
// VsInput,
// VsResult,
// FacetCategory,
// ActiveFacetCategory,
HelpViewComponent, HelpViewComponent,
MapViewComponent, MapViewComponent,
SearchViewComponent, SearchViewComponent,
@ -46,36 +28,42 @@ import { VUE_APP_PORTAL } from "./constants";
}) })
export default class App extends Vue { export default class App extends Vue {
public active = false; public active = false;
public portal = VUE_APP_PORTAL + "/login"; public portal = "https://data.tethys.at/login"; // VUE_API + "/login";
mounted(): void { /**
// const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll(".navbar-burger"), 0); * Computed property that returns the current year.
// // Check if there are any navbar burgers * @returns {number} The current year as a number.
// if ($navbarBurgers.length > 0) { */
// // Add a click event on each of them get currentYear() {
// $navbarBurgers.forEach((elNavBurger) => { return new Date().getFullYear();
// elNavBurger.addEventListener("click", () => {
// // Get the target from the "data-target" attribute
// const targetNavbar = elNavBurger.dataset.target;
// const $target = document.getElementById(targetNavbar);
// // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
// elNavBurger.classList.toggle("is-active");
// $target?.classList.toggle("is-active");
// });
// });
// }
} }
public showMobilemenu(event: PointerEvent): void { /**
// Don't follow the link * Lifecycle hook called when the component is mounted.
* Currently empty, but can be used to add setup logic when the component is mounted.
*/
mounted(): void {
}
/**
* Toggles the visibility of the mobile menu.
* @param {MouseEvent} event - The mouse event triggered by the user's interaction.
*/
public showMobilemenu(event: MouseEvent): void {
// Prevent the default behavior of the event (e.g., following a link)
event.preventDefault(); event.preventDefault();
// Toggle the active state of the mobile menu
this.active = !this.active; this.active = !this.active;
} }
/**
* Watcher that triggers when the route changes.
* It deactivates the mobile menu by setting `active` to false.
*/
@Watch("$route") @Watch("$route")
protected oRouteChangedChanged(): void { protected oRouteChangedChanged(): void {
//(to: RouteLocation, from: RouteLocation): void { // Close the mobile menu when the route changes
// console.log("setting " + from.path + " to " + to.path);
this.active = false; this.active = false;
} }
} }

View File

@ -10,7 +10,7 @@ import ContactViewComponent from "./views/contact-view/contact-view-component.vu
import SitelinkViewComponent from "./views/sitelink-view/sitelink-view-component.vue"; import SitelinkViewComponent from "./views/sitelink-view/sitelink-view-component.vue";
import ImprintViewComponent from "./views/imprint-view/imprint-view-component.vue"; import ImprintViewComponent from "./views/imprint-view/imprint-view-component.vue";
import TermsViewComponent from "./views/terms-view/terms-view-component"; import TermsViewComponent from "./views/terms-view/terms-view-component";
import { APP_URL, VUE_APP_PORTAL } from "./constants"; import { APP_URL } from "./constants";
// import VsInput from "./components/vs-input/vs-input.vue"; // import VsInput from "./components/vs-input/vs-input.vue";
// import VsResult from "./components/vs-result/vs-result.vue"; // import VsResult from "./components/vs-result/vs-result.vue";
// import FacetCategory from "./components/face-category/facet-category.vue"; // import FacetCategory from "./components/face-category/facet-category.vue";
@ -44,13 +44,17 @@ import { APP_URL, VUE_APP_PORTAL } from "./constants";
}) })
export default class App2 extends Vue { export default class App2 extends Vue {
public active = false; public active = false;
public portal = VUE_APP_PORTAL + "/login"; public portal = "https://data.tethys.at/login"; // VUE_API + "/login";
public app_url = APP_URL; public app_url = APP_URL;
public search_url = APP_URL + "/search"; public search_url = APP_URL + "/search";
public service_url = APP_URL + "/services"; public service_url = APP_URL + "/services";
public help_url = APP_URL + "/help"; public help_url = APP_URL + "/help";
public oai_url = APP_URL + "/oai"; public oai_url = APP_URL + "/oai";
get currentYear() {
return new Date().getFullYear();
}
mounted(): void { mounted(): void {
// const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll(".navbar-burger"), 0); // const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll(".navbar-burger"), 0);
// // Check if there are any navbar burgers // // Check if there are any navbar burgers
@ -69,7 +73,7 @@ export default class App2 extends Vue {
// } // }
} }
public showMobilemenu(event: PointerEvent): void { public showMobilemenu(event: MouseEvent): void {
// Don't follow the link // Don't follow the link
event.preventDefault(); event.preventDefault();
this.active = !this.active; this.active = !this.active;

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

103
src/colors.ts Normal file
View File

@ -0,0 +1,103 @@
const gradientBgBase = "bg-gradient-to-tr";
export const gradientBgPurplePink = `${gradientBgBase} from-purple-400 via-pink-500 to-red-500`;
export const gradientBgDark = `${gradientBgBase} from-slate-700 via-slate-900 to-slate-800`;
export const gradientBgPinkRed = `${gradientBgBase} from-pink-400 via-red-500 to-yellow-500`;
export const gradientBgGreenBlue = `${gradientBgBase} from-green-400 to-blue-400`;
export const colorsBgLight = {
white: "bg-white text-black",
light: "bg-white text-black dark:bg-slate-900/70 dark:text-white",
contrast: "bg-gray-800 text-white dark:bg-white dark:text-black",
success: "bg-emerald-500 border-emerald-500 text-white",
danger: "bg-red-500 border-red-500 text-white",
warning: "bg-yellow-500 border-yellow-500 text-white",
info: "bg-blue-500 border-blue-500 text-white",
};
export const colorsText = {
white: "text-black dark:text-slate-100",
light: "text-gray-700 dark:text-slate-400",
contrast: "dark:text-white",
success: "text-emerald-500",
danger: "text-red-500",
warning: "text-yellow-500",
info: "text-blue-500",
modern: "text-teal-500",
};
export const colorsOutline = {
white: [colorsText.white, "border-gray-100"],
light: [colorsText.light, "border-gray-100"],
contrast: [colorsText.contrast, "border-gray-900 dark:border-slate-100"],
success: [colorsText.success, "border-emerald-500"],
danger: [colorsText.danger, "border-red-500"],
warning: [colorsText.warning, "border-yellow-500"],
info: [colorsText.info, "border-blue-500"],
};
export const getButtonColor = (color: "white" | "contrast" | "light" | "success" | "danger" | "warning" | "modern", isOutlined: boolean, hasHover: boolean) => {
const colors = {
bg: {
white: "bg-white text-black",
contrast: "bg-gray-800 text-white dark:bg-white dark:text-black",
light: "bg-gray-50 text-black",
success: "bg-emerald-600 dark:bg-emerald-500 text-white",
danger: "bg-red-600 dark:bg-red-500 text-white",
warning: "bg-yellow-600 dark:bg-yellow-500 text-white",
info: "bg-blue-600 dark:bg-blue-500 text-white",
modern: "bg-teal-600 dark:bg-teal-500 text-white",
},
bgHover: {
white: "hover:bg-gray-50",
contrast: "hover:bg-gray-900 hover:dark:bg-slate-100",
light: "hover:bg-gray-200",
success: "hover:bg-emerald-700 hover:border-emerald-700 hover:dark:bg-emerald-600 hover:dark:border-emerald-600",
danger: "hover:bg-red-700 hover:border-red-700 hover:dark:bg-red-600 hover:dark:border-red-600",
warning: "hover:bg-yellow-700 hover:border-yellow-700 hover:dark:bg-yellow-600 hover:dark:border-yellow-600",
info: "hover:bg-blue-700 hover:border-blue-700 hover:dark:bg-blue-600 hover:dark:border-blue-600",
modern: "hover:bg-emerald-700 hover:border-emerald-700 hover:dark:bg-emerald-600 hover:dark:border-emerald-600",
},
borders: {
white: "border-gray-100",
contrast: "border-gray-900 dark:border-slate-100",
light: "border-gray-100 dark:border-slate-700",
success: "border-emerald-600 dark:border-emerald-500",
danger: "border-red-600 dark:border-red-500",
warning: "border-yellow-600 dark:border-yellow-500",
info: "border-blue-600 dark:border-blue-500",
modern: "border-teal-600 dark:border-teal-500",
},
text: {
white: "text-black dark:text-slate-100",
contrast: "dark:text-slate-100",
light: "text-gray-700 dark:text-slate-400",
success: "text-emerald-600 dark:text-emerald-500",
danger: "text-red-600 dark:text-red-500",
warning: "text-yellow-600 dark:text-yellow-500",
info: "text-blue-600 dark:text-blue-500",
modern: "text-teal-600 dark:text-teal-500",
},
outlineHover: {
white: "hover:bg-gray-100 hover:text-gray-900 dark:hover:text-slate-900",
contrast: "hover:bg-gray-800 hover:text-gray-100 hover:dark:bg-slate-100 hover:dark:text-black",
light: "hover:bg-gray-100 hover:text-gray-900 dark:hover:text-slate-900",
success: "hover:bg-emerald-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-emerald-600",
danger: "hover:bg-red-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-red-600",
warning: "hover:bg-yellow-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-yellow-600",
info: "hover:bg-blue-600 hover:text-white hover:dark:text-white hover:dark:border-blue-600",
modern: "hover:bg-teal-600 hover:text-teal hover:dark:bg-teal-100 hover:dark:text-black",
},
};
if (!colors.bg[color]) {
return color;
}
const base = [isOutlined ? colors.text[color] : colors.bg[color], colors.borders[color]];
if (hasHover) {
base.push(isOutlined ? colors.outlineHover[color] : colors.bgHover[color]);
}
return base;
};

View File

@ -0,0 +1,117 @@
<script setup="ts">
import { computed } from "vue";
// import { Link } from '@inertiajs/vue3';
// import { Link } from '@inertiajs/inertia-vue3';
import { getButtonColor } from "@/colors";
import BaseIcon from "@/components/BaseIcon.vue";
const props = defineProps({
label: {
type: [String, Number],
default: null,
},
icon: {
type: String,
default: null,
},
href: {
type: String,
default: null,
},
target: {
type: String,
default: null,
},
routeName: {
type: String,
default: null,
},
type: {
type: String,
default: null,
},
color: {
type: String,
default: "white",
},
as: {
type: String,
default: null,
},
small: Boolean,
outline: Boolean,
active: Boolean,
disabled: Boolean,
roundedFull: Boolean,
});
const is = computed(() => {
if (props.as) {
return props.as;
}
// if (props.routeName) {
// return Link;
// }
if (props.href) {
return "a";
}
return "button";
});
const computedType = computed(() => {
if (is.value === "button") {
return props.type ?? "button";
}
return null;
});
const labelClass = computed(() => (props.small && props.icon ? "px-1" : "px-2"));
const componentClass = computed(() => {
const base = [
"inline-flex",
"cursor-pointer",
"justify-center",
"items-center",
"whitespace-nowrap",
"focus:outline-none",
"transition-colors",
"focus:ring-2",
"duration-150",
"border",
props.roundedFull ? "rounded-full" : "rounded",
props.active ? "ring ring-black dark:ring-white" : "ring-blue-700",
getButtonColor(props.color, props.outline, !props.disabled),
];
if (props.small) {
base.push("text-sm", props.roundedFull ? "px-3 py-1" : "p-1");
} else {
base.push("py-2", props.roundedFull ? "px-6" : "px-3");
}
if (props.disabled) {
base.push("cursor-not-allowed", props.outline ? "opacity-50" : "opacity-70");
}
return base;
});
</script>
<template>
<component
v-bind:is="is"
v-bind:class="componentClass"
v-bind:href="routeName ? routeName : href"
v-bind:type="computedType"
v-bind:target="target"
v-bind:disabled="disabled"
>
<BaseIcon v-if="icon" v-bind:path="icon" />
<span v-if="label" v-bind:class="labelClass">{{ label }}</span>
</component>
</template>

View File

@ -0,0 +1,32 @@
<script setup lang="ts">
import { computed } from "vue";
const props = defineProps({
path: {
type: String,
required: true,
},
w: {
type: String,
default: "w-6",
},
h: {
type: String,
default: "h-6",
},
size: {
type: [String, Number],
default: 16,
},
});
const spanClass = computed(() => `inline-flex justify-center items-center ${props.w} ${props.h}`);
</script>
<template>
<span v-bind:class="spanClass">
<svg viewBox="0 0 24 24" v-bind:width="size" v-bind:height="size" class="inline-block">
<path fill="currentColor" v-bind:d="path" />
</svg>
</span>
</template>

View File

@ -0,0 +1,188 @@
<script setup lang="ts">
import { computed } from "vue";
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
});
// const nextPageLink = computed(() => {
// const url = new URL(document.location);
// url.searchParams.set("page", props.data.currentPage + 1);
// return url.href;
// // return url + '&page=' + (Number(props.data.currentPage) + 1);
// });
// const prevPageLink = computed(() => {
// const url = new URL(document.location);
// url.searchParams.set("page", props.data.currentPage - 1);
// return url.href;
// // return url + '&page=' + (props.data.currentPage - 1);
// });
const toPage = computed(() => {
const currentPage = props.data.currentPage;
const perPage = props.data.perPage;
if (props.data.currentPage == props.data.lastPage) {
return props.data.total;
} else {
return currentPage * perPage;
}
});
const fromPage = computed(() => {
const currentPage = props.data.currentPage;
const perPage = props.data.perPage;
return currentPage * perPage - (perPage - 1);
});
const emit = defineEmits(["menu-click"]);
const nextClick = () => {
const nextPage = props.data.currentPage + 1;
emit("menu-click", nextPage);
};
const prevClick = () => {
const prevPage = props.data.currentPage - 1;
emit("menu-click", prevPage);
};
</script>
<template>
<!-- <nav v-if="data.links.length > 3" -->
<nav v-if="data.total > 3" role="navigation" aria-label="Pagination Navigation" class="flex items-center justify-between">
<div id="mobileMenu" class="flex justify-between flex-1 sm:hidden">
<span
v-if="data.currentPage <= 1"
aria-disabled="true"
class="cursor-not-allowed opacity-50 relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md"
>
Previous
</span>
<!-- v-bind:href="data.previousPageUrl" -->
<a
v-else
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150"
@click="prevClick"
>
Previous
</a>
<!-- v-bind:href="data.nextPageUrl" -->
<a
v-if="data.currentPage < data.lastPage"
class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 rounded-md hover:text-gray-500 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150"
@click="nextClick"
>
Next
</a>
<span
v-else
aria-disabled="true"
class="cursor-not-allowed opacity-50 relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5 rounded-md"
>
Next
</span>
</div>
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div id="frontText" class="w-full text-right mr-4">
<p class="text-sm text-gray-700 leading-5">
Showing
<span class="font-medium">{{ fromPage }}</span>
to
<span class="font-medium">{{ toPage }}</span>
of
<span class="font-medium">{{ data.total }}</span>
results
</p>
</div>
<div>
<span class="relative z-0 inline-flex shadow-sm rounded-md">
<!-- previous button -->
<span v-if="props.data.currentPage <= 1" aria-disabled="true" aria-label="Previous">
<span
aria-disabled="true"
class="bg-gray-300 cursor-not-allowed opacity-30 relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-l-md leading-5"
aria-hidden="true"
>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
clip-rule="evenodd"
/>
</svg>
</span>
</span>
<!-- v-bind:href="prevPageLink" -->
<a
v-else
rel="prev"
class="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-l-md leading-5 hover:text-gray-400 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150"
aria-label="Previous"
@click="prevClick"
>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
clip-rule="evenodd"
/>
</svg>
</a>
<!-- <template v-for="(link, key) in data.links">
<template v-if="key > 0 && key < data.lastPage + 1">
<span v-if="!link.active && link.url === null" :key="key" aria-disabled="true">
<span class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 cursor-default leading-5">{{ link.label }}</span>
</span>
<span v-else-if="link.active" :key="`current-${key}`" aria-current="page">
<span class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default leading-5">{{ link.label }}</span>
</span>
<Link v-else :key="`link-${key}`" :href="link.url" v-html="link.label" class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 leading-5 hover:text-gray-500 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150" aria-label="`Go to page ${link.label}`" />
</template>
</template> -->
<!-- next button ----------------------------------------------------------------------------- -->
<!-- v-bind:href="nextPageLink" -->
<a
v-if="props.data.currentPage < props.data.lastPage"
rel="next"
class="relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-r-md leading-5 hover:text-gray-400 focus:z-10 focus:outline-none focus:ring ring-gray-300 focus:border-blue-300 active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150"
aria-label="Next"
@click="nextClick"
>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"
/>
</svg>
</a>
<!-- else disabled link -->
<span v-else aria-disabled="true" aria-label="Next">
<span
aria-disabled="true"
class="opacity-30 bg-gray-300 cursor-not-allowed relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-default rounded-r-md leading-5"
aria-hidden="true"
>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"
/>
</svg>
</span>
</span>
</span>
</div>
</div>
</nav>
</template>

View File

@ -0,0 +1,30 @@
<script setup lang="ts">
import { computed } from "vue";
import { gradientBgPurplePink, gradientBgPinkRed, gradientBgGreenBlue } from "@/colors";
const props = defineProps({
bg: {
type: String,
required: true,
validator: (value) => ["purplePink", "pinkRed", "greenBlue"].includes(value),
},
});
const colorClass = computed(() => {
switch (props.bg) {
case "purplePink":
return gradientBgPurplePink;
case "pinkRed":
return gradientBgPinkRed;
case "greenBlue":
return gradientBgGreenBlue;
}
return "";
});
</script>
<template>
<div v-bind:class="colorClass" class="mt-6 mb-6 rounded-2xl py-12 px-6 text-center">
<slot />
</div>
</template>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
import { mdiGithub } from "@mdi/js";
import BaseButton from "@/components/BaseButton.vue";
import SectionBanner from "@/components/SectionBanner.vue";
</script>
<template>
<SectionBanner bg="greenBlue">
<h1 class="text-3xl text-white mb-6">Like the project? Please star on <b>Gitea</b>!</h1>
<div>
<BaseButton href="https://gitea.geologie.ac.at/geolba/tethys" v-bind:icon="mdiGithub" v-bind:roundedFull="true" label="Gitea" target="_blank" />
</div>
</SectionBanner>
</template>

View File

@ -5,7 +5,6 @@ import { Component, Vue, Prop, Emit } from "vue-facing-decorator";
name: "ActiveFacetCategory", name: "ActiveFacetCategory",
}) })
export default class ActiveFacetCategory extends Vue { export default class ActiveFacetCategory extends Vue {
bar = "";
@Prop({ @Prop({
type: Array<string>, type: Array<string>,
@ -18,11 +17,65 @@ export default class ActiveFacetCategory extends Vue {
}) })
categoryName!: string; categoryName!: string;
replacements = new Map<string, string>([
["gis", "GIS"],
["analysisdata", "Analysis Data"],
["models", "Models"],
["monitoring", "Monitoring"],
["measurementdata", "Measurement Data"],
["mixedtype", "Mixed Type"],
["de", "Deutsch"],
["en", "English"]
]);
// @Prop([String]) // @Prop([String])
// alias; // alias;
get alias(): string { /**
return this.categoryName == "doctype" ? "datatype" : this.categoryName; * The alias for the Active facet box will be set depending on the name of the category.
* This will allow to display the customised terms "creator" and "keyword" instead of the values currently used in the OpenSearch index: "author" and "subjects"
* TODO: This should be corrected directly in the index
*/
get categoryAlias(): string {
// return this.categoryName == "doctype" ? "datatype" : this.categoryName;
// console.log("getAlias!");
switch (this.categoryName) {
case "author":
return "creator";
case "subjects":
return "keyword";
case "doctype":
return "data type";
default:
return this.categoryName;
}
}
/**
* The alias for the items inside the "doctype / Datatype" category will be set manually in order to show user-friendly terms instead of the predefined doctypes in the DB
* If the category alias is Data Type, the name of the items is set
* NOTE: This could be corrected directly in the index
*/
getFilterItemsAlias(categoryAlias: string): string {
console.log(categoryAlias);
if (categoryAlias === ("data type") || categoryAlias === ("language")) {
/**
* Iterate over the filterItems array using the map method to create a new array (updatedItems).
* For each item in the array, check if the item exists as a key in the replacements map.
* - If the item exists in the replacements map, replace it with the corresponding value from the map.
* - If the item does not exist in the replacements map, keep the original item unchanged.
* The map method returns a new array where each element is either the original item or its replacement.
* */
const updatedItems = this.filterItems.map((item) =>
this.replacements.get(item) || item
);
return updatedItems.join(" | ");
}
// console.log("other categories");
return this.filterItems.join(" | ");
} }
// get filterItems(): Array<string> { // get filterItems(): Array<string> {

View File

@ -1,9 +1,10 @@
<template> <template>
<div> <div>
<input v-bind:id="alias" v-bind:name="alias" type="checkbox" checked="checked" class="css-checkbox" @click.prevent="deactivateFacetCategory()" /> <input v-bind:id="categoryAlias" v-bind:name="categoryAlias" type="checkbox" checked="checked" class="css-checkbox" @click.prevent="deactivateFacetCategory()" />
<label v-bind:for="alias" class="css-label"> <label v-bind:for="categoryAlias" class="css-label">
<span>{{ alias + ": " }}</span> <span>{{ categoryAlias + ": " }}</span>
<a v-if="filterItems && filterItems.length > 0" class="gbaterm">{{ filterItems.join(", ") }}</a> <!-- <a v-if="filterItems && filterItems.length > 0" class="gsaterm">{{ filterItems.join(" | ") }}</a> -->
<a v-if="filterItems && filterItems.length > 0" class="gsaterm">{{ getFilterItemsAlias(categoryAlias) }}</a>
</label> </label>
</div> </div>
</template> </template>
@ -18,7 +19,7 @@ th,
td { td {
border-bottom: 0px solid #e1e1e1; border-bottom: 0px solid #e1e1e1;
} }
.gbaterm { .gsaterm {
color: #0099cc; color: #0099cc;
border: 1px solid rgb(200, 210, 255); border: 1px solid rgb(200, 210, 255);
padding: 4px; padding: 4px;
@ -42,6 +43,7 @@ input[type="checkbox"].css-checkbox {
} }
input[type="checkbox"].css-checkbox + label.css-label { input[type="checkbox"].css-checkbox + label.css-label {
text-transform: capitalize;
padding-left: 25px; padding-left: 25px;
/* height: 24px; /* height: 24px;
display: inline-block; */ display: inline-block; */

View File

@ -4,6 +4,14 @@ import testLogo from "@/assets/datacite/testLogo.vue";
const APIURL = "https://api.datacite.org"; const APIURL = "https://api.datacite.org";
interface DataciteResponse {
// [key: string]: any;
views: number;
downloads: number;
citations: number;
datacite: number;
}
@ComponentBase({ @ComponentBase({
name: "BaseWidget", name: "BaseWidget",
components: { components: {
@ -19,7 +27,7 @@ export default class BaseWidget extends Vue {
return ["citations", "views", "downloads"].some((r) => keys.includes(r)); return ["citations", "views", "downloads"].some((r) => keys.includes(r));
}, },
}) })
dataInput = {}; dataInput!: DataciteResponse;
@Prop({ @Prop({
type: String, type: String,
@ -82,7 +90,7 @@ export default class BaseWidget extends Vue {
return `${value.toLocaleString("en-us")} ${label}s`; return `${value.toLocaleString("en-us")} ${label}s`;
} }
public formatNumbers(num: any) { public formatNumbers(num: number) {
if (num < 1e3) return num; if (num < 1e3) return num;
if (num >= 1e3 && num < 1e6) return `${+(num / 1e3).toFixed(1)}K`; if (num >= 1e3 && num < 1e6) return `${+(num / 1e3).toFixed(1)}K`;
if (num >= 1e6 && num < 1e9) return `${+(num / 1e6).toFixed(1)}M`; if (num >= 1e6 && num < 1e9) return `${+(num / 1e6).toFixed(1)}M`;
@ -98,7 +106,7 @@ export default class BaseWidget extends Vue {
return true; return true;
} }
private grabMetrics(data: any) { private grabMetrics(data: DataciteResponse) {
this.views = (this.formatNumbers(data.views) as string) || ""; this.views = (this.formatNumbers(data.views) as string) || "";
this.downloads = (this.formatNumbers(data.downloads) as string) || ""; this.downloads = (this.formatNumbers(data.downloads) as string) || "";
this.citations = (this.formatNumbers(data.citations) as string) || ""; this.citations = (this.formatNumbers(data.citations) as string) || "";

View File

@ -19,8 +19,44 @@ export default class FacetCategory extends Vue {
}) })
filterName!: string; filterName!: string;
get alias(): string { replacements = new Map<string, string>([
return this.filterName == "datatype" ? "doctype" : this.filterName; ["gis", "GIS"],
["analysisdata", "Analysis Data"],
["models", "Models"],
["monitoring", "Monitoring"],
["measurementdata", "Measurement Data"],
["mixedtype", "Mixed Type"],
["de", "Deutsch"],
["en", "English"]
]);
/**
* The alias for the Active facet box will be set depending on the name of the category.
* This will allow to display the customised terms "creator" and "keyword" instead of the values currently used in the OpenSearch index: "author" and "subjects"
* NOTE: This should be corrected directly in the index
*/
get categoryAlias(): string {
// console.log("filterName:", this.filterName);
// return this.filterName == "datatype" ? "doctype" : this.filterName;
switch (this.filterName) {
case "author":
return "creator";
case "subjects":
return "keyword";
case "doctype":
return "Data Type";
default:
return this.filterName;
}
}
/**
* The alias for the items inside the "doctype / Datatype" category will be set manually in order to show user-friendly terms instead of the predefined doctypes in the DB
* NOTE: This could be corrected directly in the index
*/
itemAlias(val: string): string {
return this.replacements.get(val) || val;
} }
// get filterItems(): Array<FilterItem> { // get filterItems(): Array<FilterItem> {
@ -43,19 +79,34 @@ export default class FacetCategory extends Vue {
} }
toggle(): void { toggle(): void {
if (this.collapsed == true) { this.collapsed = !this.collapsed;
this.collapsed = false;
} else if (this.collapsed == false) { // Scroll to the top of the page only when collapsing
this.collapsed = true; if (this.collapsed) {
//list.children("li:gt(4)").hide(); this.scrollTop();
} }
} }
@Emit("filter") @Emit("filter")
activateItem(filterItem: FacetItem): FacetItem { activateItem(filterItem: FacetItem): FacetItem {
filterItem.category = this.alias; console.log("Emit: ActivateItem");
// filterItem.category = this.alias;
filterItem.category = this.filterName;
filterItem.active = true; filterItem.active = true;
// this.$emit("filter", filterItem); // this.$emit("filter", filterItem);
// Scroll to the top of the page when activating a new filter
this.scrollTop();
return filterItem; return filterItem;
} }
scrollTop(): void {
setTimeout(() => {
window.scrollTo({
top: 0,
behavior: 'smooth' // Smooth scroll to the top
});
}, 50); // Delay to allow the DOM to update
}
} }

View File

@ -2,14 +2,16 @@
<div class="card panel-default"> <div class="card panel-default">
<!-- <h3 class="panel-title filterViewModelName">{{ filterName }}</h3> --> <!-- <h3 class="panel-title filterViewModelName">{{ filterName }}</h3> -->
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title titlecase filterViewModelName">{{ filterName }}</h3> <h3 class="panel-title titlecase filterViewModelName">{{ categoryAlias }}</h3>
<!-- <h3 class="panel-title titlecase filterViewModelName">{{ filterName }}</h3> -->
</div> </div>
<div class="panel-body"> <div class="panel-body">
<!-- e.g.language --> <!-- e.g.language -->
<ul class="filter-items list-unstyled" v-bind:class="{ limited: facetItems.length > 1 && collapsed }"> <ul class="filter-items list-unstyled" v-bind:class="{ limited: facetItems.length > 1 && collapsed }">
<li v-for="(item, index) in facetItems" v-bind:key="index" class="list-group-item titlecase"> <li v-for="(item, index) in facetItems" v-bind:key="index" class="list-group-item titlecase">
<!-- <span :class="item.Active ? 'disabled' : ''" @click.prevent="activateItem(item)">{{ item.val }} ({{ item.count }}) </span> --> <!-- <span :class="item.Active ? 'disabled' : ''" @click.prevent="activateItem(item)">{{ item.val }} ({{ item.count }}) </span> -->
<span v-bind:class="item.active ? 'disabled' : ''" @click.prevent="activateItem(item)">{{ item.val }} ({{ item.count }}) </span> <!-- <span v-bind:class="item.active ? 'disabled' : ''" @click.prevent="activateItem(item)">{{ item.val }} ({{ item.count }}) </span> -->
<span v-bind:class="item.active ? 'disabled' : ''" @click.prevent="activateItem(item)">{{ itemAlias(item.val) }} ({{ item.count }}) </span>
</li> </li>
</ul> </ul>
<!-- <ul class="overflowing" v-if="overflowing == true"> <!-- <ul class="overflowing" v-if="overflowing == true">
@ -74,7 +76,10 @@ export default FacetCategory;
flex-grow: 1; flex-grow: 1;
flex-shrink: 0; flex-shrink: 0;
/* padding: 0.75rem; */ /* padding: 0.75rem; */
padding: 0.75em 2em; padding-top: 0em;
padding-right: 2em;
padding-bottom: 0.75em;
padding-left: 2em;
justify-content: left; justify-content: left;
} }
@ -87,6 +92,7 @@ export default FacetCategory;
} }
.panel-body { .panel-body {
padding: 0 2em; padding: 0 2em;
padding-bottom: 0.75em; /* Increase padding at the bottom */
} }
.disabled { .disabled {

View File

@ -0,0 +1,112 @@
import { Component, Prop, Vue } from 'vue-facing-decorator';
import * as L from 'leaflet';
import 'leaflet/dist/leaflet.css';
@Component({
name: 'Minimap',
})
export default class Minimap extends Vue {
@Prop({ type: Array, required: true }) bounds!: L.LatLngBoundsLiteral;
// private originalCenter: L.LatLngExpression = [47.71, 13.55]; // Original center
// private originalZoom: number = 6; // Original zoom level
/**
* Lifecycle hook called when the component is mounted.
* Initializes the Leaflet map, sets up base layers, and adds either a circle or rectangle
* based on the `bounds` prop passed to the component.
*/
mounted() {
// Initialize the map with specific center and zoom
const map = L.map('map', {
center: [47.71, 13.55], // Initial center coordinates
zoomControl: true, // Enable zoom controls
zoom: 6, // Initial zoom level
minZoom: 5, // Minimum zoom level allowed
maxBounds: [
[44.0, 9.0], // Southwest corner of the bounding box
[51.0, 18.0] // Northeast corner of the bounding box
],
maxBoundsViscosity: 1.0 // Prevent map from being dragged outside the defined bounds
});
// Remove Leaflet logo and text
map.attributionControl.setPrefix(false);
// Add basemap.at tile layer to the map
const basemapAtLayer = L.tileLayer('https://maps{s}.wien.gv.at/basemap/geolandbasemap/normal/google3857/{z}/{y}/{x}.png', {
attribution: '<a href="https://www.basemap.at">basemap.at</a>',
noWrap: true,
subdomains: ['', '1', '2', '3', '4']
}).addTo(map);
// Add Esri imagery tile layer
const esriImageryLayer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; <a href="https://www.esri.com/" target="_blank">Esri</a>'
});
// Add Esri topo map tile layer
const esriTopoLayer = L.tileLayer('https://server.arcgisonline.com/arcgis/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; <a href="https://www.esri.com/" target="_blank">Esri</a>'
});
// Define available base maps for the user to toggle between
const baseMaps = {
// "OpenStreetMap": openStreetMapLayer,
"basemap.at": basemapAtLayer,
"ESRI Imagery": esriImageryLayer,
"ESRI Topo": esriTopoLayer
};
// Define available base maps for the user to toggle between
L.control.layers(baseMaps).addTo(map);
const [southWest, northEast] = this.bounds;
if (southWest[0] === northEast[0] || southWest[1] === northEast[1]) {
// If y_min and y_max (and x_min and x_max) are equal, generate a circle
const center = [southWest[0], southWest[1]] as [number, number];
// Add a CircleMarker to the map at the center of the bounds. This kind of marker is used to maintain constant size regardless of zoom level
const circleMarker = L.circleMarker(center, {
color: '#30D5C8', // Outline color
fillColor: '#336699', // Fill color
fillOpacity: 1, // Opacity of the fill
opacity: 0.5, // Outline opacity
weight: 10, // Outline weight (thickness)
radius: 10 // Radius in pixels
}).addTo(map);
// Manually create a small bounding box around the marker's center to fit bounds
const buffer = 0.01; // Buffer size around the marker. Adjust this value to control the area around the marker
const markerBounds = L.latLngBounds(
[center[0] - buffer, center[1] - buffer], // Southwest corner of the bounding box
[center[0] + buffer, center[1] + buffer] // Northeast corner of the bounding box
);
// Add a click event handler to the CircleMarker
circleMarker.on('click', () => {
map.fitBounds(markerBounds, { padding: [10, 10] }); // Adjust map to fit within marker bounds
});
// Automatically adjust the map's view to fit the marker's bounds
map.fitBounds(markerBounds, { padding: [10, 10] });
} else {
// If bounds are not equal, draw a rectangle
const rectangle = L.rectangle(this.bounds, {
color: '#30D5C8', // Rectangle outline color //Alternative color: 336699
weight: 2, // Outline thickness
opacity: 1 // Opacity of the rectangle outline
}).addTo(map);
// Add a click event handler to the Rectangle
rectangle.on('click', () => {
map.fitBounds(this.bounds, { padding: [18, 18] }); // Adjust map to fit within rectangle bounds
});
// Automatically adjust the map's view to fit the rectangle's bounds with padding
map.fitBounds(this.bounds, { padding: [18, 18] });
}
}
}

View File

@ -0,0 +1,16 @@
<!-- Contains the template and references the TypeScript logic. -->
<template>
<div id="map" style="height: 300px;"></div>
</template>
<script lang="ts">
import Minimap from './Minimap';
export default Minimap;
</script>
<style scoped>
#map {
height: 100%;
width: 100%;
}
</style>

View File

@ -2,12 +2,17 @@
// import debounce from 'lodash/debounce'; // import debounce from 'lodash/debounce';
// import { DatasetService } from "../../services/dataset.service"; // import { DatasetService } from "../../services/dataset.service";
import DatasetService from "../../services/dataset.service"; import DatasetService from "../../services/dataset.service";
import { SolrSettings } from "@/models/solr"; // import { SolrSettings } from "@/models/solr"; // PENDING USE
// import { ref } from "vue";
import { OpenSettings } from "@/models/solr";
import { Component, Vue, Prop, Emit } from "vue-facing-decorator"; import { Component, Vue, Prop, Emit } from "vue-facing-decorator";
// import { Prop, Emit } from "vue-property-decorator";
import { Dataset, Suggestion, SearchType } from "@/models/dataset"; import { Dataset, Suggestion, SearchType } from "@/models/dataset";
import { SOLR_HOST, SOLR_CORE } from "@/constants"; // import { SOLR_HOST, SOLR_CORE } from "@/constants";
import { OPEN_HOST, OPEN_CORE } from "@/constants"; // PENDING USE
import { HitHighlight } from "@/models/headers";
import DOMPurify from 'dompurify'; // To sanitize the HTML content to prevent XSS attacks!
@Component({ @Component({
name: "VsInput", name: "VsInput",
@ -16,30 +21,40 @@ export default class VsInput extends Vue {
// @Prop() // @Prop()
// private title!: string; // private title!: string;
// Define the placeholder text for the input field
@Prop({ default: "Search" }) @Prop({ default: "Search" })
readonly placeholder!: string; readonly placeholder!: string;
private display = ""; private display = ""; // Input display value
@Prop() @Prop()
private propDisplay = ""; private propDisplay = "";
private value!: Suggestion | string; private value!: Suggestion | string;
private error = ""; private error = "";
private results: Array<Dataset> = []; private results: Array<Dataset> = []; // Array to store search results
private loading = false; private highlights: Array<HitHighlight> = [];
private selectedIndex = -1;
// private selectedDisplay = ""; private loading = false; // Loading state indicator
private solr: SolrSettings = { private selectedIndex = -1; // Index of the currently selected suggestion
core: SOLR_CORE, //"rdr_data", // SOLR.core;
host: SOLR_HOST, //"tethys.at", // private solr: SolrSettings = {
// core: SOLR_CORE, //"rdr_data", // SOLR.core;
// host: SOLR_HOST, //"tethys.at",
// };
private openSearch: OpenSettings = {
core: OPEN_CORE, //"rdr_data", // SOLR.core;
host: OPEN_HOST, //"tethys.at",
// core: "test_data", // SOLR.core; // core: "test_data", // SOLR.core;
// host: "repository.geologie.ac.at", // host: "repository.geologie.ac.at",
}; };
// private rdrAPI!: DatasetService;
itemRefs!: Array<Element>;
emits = ["filter"];
// private rdrAPI!: DatasetService;
itemRefs!: Array<Element>; // Array to store references to suggestion items
emits = ["filter"]; // Emits filter event
// Set reference for each item
setItemRef(el: Element): void { setItemRef(el: Element): void {
this.itemRefs.push(el); this.itemRefs.push(el);
} }
@ -68,53 +83,143 @@ export default class VsInput extends Vue {
return this.error !== null; return this.error !== null;
} }
// Computed property to generate suggestions based on search results
get suggestions(): Suggestion[] { get suggestions(): Suggestion[] {
// const suggestion = {
// titles: new Array<string>(),
// authors: new Array<string>(),
// subjects: new Array<string>(),
// };
const suggestions = new Array<Suggestion>(); const suggestions = new Array<Suggestion>();
this.results.forEach((dataset) => { // console.log("getSuggestions > Display:", this.display);
// const del = dataset.title_output?.toLowerCase(); // console.log("results:", this.results );
if (dataset.title_output.toLowerCase().includes(this.display.toLowerCase())) { // console.log("highlights:", this.highlights);
const title = dataset.title_output;
// if (!suggestion["titles"].find((value) => value === title)) {
// suggestion.titles.push(title);
// }
const hasTitleSuggestion = suggestions.some((suggestion) => suggestion.value === title && suggestion.type == SearchType.Title);
if (!hasTitleSuggestion) {
const suggestion = new Suggestion(title, SearchType.Title);
suggestions.push(suggestion);
}
}
if (this.find(dataset.author, this.display.toLowerCase()) !== "") {
const author = this.find(dataset.author, this.display.toLowerCase());
const hasAuthorSuggestion = suggestions.some((suggestion) => suggestion.value === author && suggestion.type == SearchType.Author); //The method checks if there are any highlighted titles in the highlight object. If found, it joins the highlighted fragments into a single string
// Generate suggestions based on search results
this.results.forEach((dataset, index) => {
const highlight = this.highlights[index];
// console.log("get suggestions:id", dataset.id);
// console.log("get suggestions:title_output", dataset.title_output);
// console.log("get suggestions:author", dataset.author);
// console.log("get suggestions:subjects", dataset.subjects);
// Checks if a suggestion with the same title and type already exists in the suggestions array. If not, it creates a new Suggestion object and adds it to the suggestions array.
if (highlight.title && highlight.title.length > 0) {
/** This line checks if the highlight object has a title property and if that property is an array with at least one element.
* The highlight object contains highlighted fragments of the search term in various fields (e.g., title, author, subjects) as returned by the OpenSearch API.
* This check ensures that we only process results that have highlighted titles. */
const highlightedTitle = highlight.title.join(" ");
/**
* The highlight.title property is an array of strings, where each string is a highlighted fragment of the title. join(" ") combines these fragments into a single string with spaces between them.
* This step constructs a full highlighted title from the individual fragments.
* OpenSearch can return multiple fragments of a field (like the title) in its response, especially when the field contains multiple terms that match the search query.
* This can happen because OpenSearch's highlighting feature is designed to provide context around each match within the field, which can result in multiple highlighted fragments.
*/
const hasTitleSuggestion = suggestions.some((suggestion) => suggestion.highlight.toLowerCase() === highlightedTitle.toLowerCase() && suggestion.type == SearchType.Title);
if (!hasTitleSuggestion) {
const suggestion = new Suggestion(dataset.title_output, highlightedTitle, SearchType.Title);
suggestions.push(suggestion);
}
}
if (highlight.author && highlight.author.length > 0) {
const highlightedAuthor = highlight.author.join(" ");
const datasetAuthor = this.find(dataset.author, this.display.toLowerCase());
const hasAuthorSuggestion = suggestions.some((suggestion) => suggestion.highlight.toLowerCase() === highlightedAuthor.toLowerCase() && suggestion.type == SearchType.Author);
if (!hasAuthorSuggestion) { if (!hasAuthorSuggestion) {
const suggestion = new Suggestion(author, SearchType.Author); const suggestion = new Suggestion(datasetAuthor, highlightedAuthor, SearchType.Author);
suggestions.push(suggestion); suggestions.push(suggestion);
} }
} }
if (this.find(dataset.subject, this.display.toLowerCase()) != "") { if (highlight.subjects && highlight.subjects.length > 0) {
const subject = this.find(dataset.subject, this.display.toLowerCase()); const highlightedSubject = highlight.subjects.join(" ");
const hasSubjectSuggestion = suggestions.some((suggestion) => suggestion.value === subject && suggestion.type == SearchType.Subject); const datasetSubject = this.find(dataset.subjects, this.display.toLowerCase());
const hasSubjectSuggestion = suggestions.some((suggestion) => suggestion.highlight.toLowerCase() === highlightedSubject.toLowerCase() && suggestion.type == SearchType.Subject);
if (!hasSubjectSuggestion) { if (!hasSubjectSuggestion) {
const suggestion = new Suggestion(subject, SearchType.Subject); const suggestion = new Suggestion(datasetSubject, highlightedSubject, SearchType.Subject);
suggestions.push(suggestion); suggestions.push(suggestion);
} }
} }
// To allow search by doctype
if (highlight.doctype && highlight.doctype.length > 0) {
const highlightedDoctype = highlight.doctype.join(" ");
const hasDoctypeSuggestion = suggestions.some((suggestion) => suggestion.highlight.toLowerCase() === highlightedDoctype.toLowerCase() && suggestion.type == SearchType.Doctype);
if (!hasDoctypeSuggestion) {
const suggestion = new Suggestion(dataset.doctype, highlightedDoctype, SearchType.Doctype);
suggestions.push(suggestion);
}
}
// ORIGINAL SOLR ===================================================================================================
// if (dataset.title_output.toLowerCase().includes(this.display.toLowerCase())) {
// const title = dataset.title_output;
// // Check if there is already a suggestion with this title and type
// const hasTitleSuggestion = suggestions.some((suggestion) => suggestion.value === title && suggestion.type == SearchType.Title);
// if (!hasTitleSuggestion) {
// // If there is no such suggestion, create a new one and add it to the suggestions array
// const suggestion = new Suggestion(title, SearchType.Title);
// suggestions.push(suggestion);
// }
// }
// if (this.find(dataset.author, this.display.toLowerCase()) !== "") {
// const author = this.find(dataset.author, this.display.toLowerCase());
// // Check if there is already a suggestion with this author and type
// const hasAuthorSuggestion = suggestions.some((suggestion) => suggestion.value === author && suggestion.type == SearchType.Author);
// if (!hasAuthorSuggestion) {
// const suggestion = new Suggestion(author, SearchType.Author);
// suggestions.push(suggestion);
// }
// }
// if (this.find(dataset.subjects, this.display.toLowerCase()) != "") {
// const subject = this.find(dataset.subjects, this.display.toLowerCase());
// const hasSubjectSuggestion = suggestions.some((suggestion) => suggestion.value === subject && suggestion.type == SearchType.Subject);
// if (!hasSubjectSuggestion) {
// const suggestion = new Suggestion(subject, SearchType.Subject);
// suggestions.push(suggestion);
// }
// }
}); });
return suggestions; return suggestions;
} }
/**
* This method combines the suggestion value and type into a single HTML string. It also sanitizes the HTML content using DOMPurify to prevent XSS attacks.
* The vue file uses the v-html directive to bind the combined HTML string to the label element. This ensures that the HTML content (e.g., <em>Wien</em>) is rendered correctly in the browser.
*/
formatSuggestion(result: Suggestion): string {
const sanitizedValue = DOMPurify.sanitize(result.highlight);
// Replacing the predefined format for highlights given by OpenSearch from <em> emphasys to <b> bold
const replacedValue = sanitizedValue.replace(/<em>/g, '<b>').replace(/<\/em>/g, '</b>');
// return `${replacedValue} <em>| ${result.type}</em>`;
return `${replacedValue} <em>| ${this.getTypeAlias(result.type)}</em>`;
}
/**
* The alias for the result type will be set depending on the name of the type.
* This will allow to display the customised terms instead of the values currently used in the OpenSearch index.
* TODO: This should be corrected directly in the index
*/
getTypeAlias(type: string): string {
switch (type) {
case "author":
return "creator";
case "subjects":
return "keyword";
case "doctype":
return "data type";
default:
return type;
}
}
/** /**
* Clear all values, results and errors * Clear all values, results and errors
**/ **/
clear(): void { clear(): void {
console.log("clear");
this.display = ""; this.display = "";
// this.value = null; // this.value = null;
this.results = []; this.results = [];
@ -122,15 +227,20 @@ export default class VsInput extends Vue {
// this.$emit("clear"); // this.$emit("clear");
} }
/* When the search button is clicked or the search input is changed, it updates the value property of the component with the current value of display,
and emits a search-change event with the current value of display as the argument. */
@Emit("search-change") @Emit("search-change")
search(): string { search(): string {
console.log("search");
this.results = []; this.results = [];
// this.$emit("search", this.display) // this.$emit("search", this.display)
this.value = this.display; //(obj["title_output"]) ? obj["title_output"] : obj.id this.value = this.display; //(obj["title_output"]) ? obj["title_output"] : obj.id
return this.display; return this.display;
} }
// Handler for search input change
searchChanged(): void { searchChanged(): void {
// console.log("Search changed!");
this.selectedIndex = -1; this.selectedIndex = -1;
// Let's warn the parent that a change was made // Let's warn the parent that a change was made
// this.$emit("input", this.display); // this.$emit("input", this.display);
@ -142,7 +252,9 @@ export default class VsInput extends Vue {
} }
} }
// Perform the search request
private resourceSearch() { private resourceSearch() {
// console.log("resourceSearch");
if (!this.display) { if (!this.display) {
this.results = []; this.results = [];
return; return;
@ -152,23 +264,29 @@ export default class VsInput extends Vue {
this.request(); this.request();
} }
// Make the API request to search for datasets
private request(): void { private request(): void {
DatasetService.searchTerm(this.display, this.solr.core, this.solr.host).subscribe({ console.log("request()");
next: (res: Dataset[]) => this.dataHandler(res), // DatasetService.searchTerm(this.display, this.solr.core, this.solr.host).subscribe({
DatasetService.searchTerm(this.display, this.openSearch.core, this.openSearch.host).subscribe({
// next: (res: Dataset[]) => this.dataHandler(res),
next: (res: { datasets: Dataset[], highlights: HitHighlight[] }) => this.dataHandler(res.datasets, res.highlights),
error: (error: string) => this.errorHandler(error), error: (error: string) => this.errorHandler(error),
complete: () => (this.loading = false), complete: () => (this.loading = false),
}); });
} }
private dataHandler(datasets: Dataset[]): void { // Handle the search results
private dataHandler(datasets: Dataset[], highlights: HitHighlight[]): void {
this.results = datasets; this.results = datasets;
// this.$emit("search", this.display); this.highlights = highlights; // Store highlights
// this.loading = false; // console.log(datasets);
} }
// Handle errors from the search request
private errorHandler(err: string): void { private errorHandler(err: string): void {
this.error = err; this.error = err;
// this.loading = false;
} }
/** /**
@ -180,7 +298,9 @@ export default class VsInput extends Vue {
return key === this.selectedIndex; return key === this.selectedIndex;
} }
// Handle arrow down key press to navigate suggestions
onArrowDown(ev: Event): void { onArrowDown(ev: Event): void {
console.log("onArrowDown");
ev.preventDefault(); ev.preventDefault();
if (this.selectedIndex === -1) { if (this.selectedIndex === -1) {
this.selectedIndex = 0; this.selectedIndex = 0;
@ -190,6 +310,7 @@ export default class VsInput extends Vue {
this.fixScrolling(); this.fixScrolling();
} }
// Scroll the selected suggestion into view
private fixScrolling() { private fixScrolling() {
const currentElement = this.itemRefs[this.selectedIndex]; const currentElement = this.itemRefs[this.selectedIndex];
currentElement.scrollIntoView({ currentElement.scrollIntoView({
@ -199,7 +320,9 @@ export default class VsInput extends Vue {
}); });
} }
// Handle arrow up key press to navigate suggestions
onArrowUp(ev: Event): void { onArrowUp(ev: Event): void {
console.log("onArrowUp");
ev.preventDefault(); ev.preventDefault();
if (this.selectedIndex === -1) { if (this.selectedIndex === -1) {
this.selectedIndex = this.suggestions.length - 1; this.selectedIndex = this.suggestions.length - 1;
@ -209,7 +332,10 @@ export default class VsInput extends Vue {
this.fixScrolling(); this.fixScrolling();
} }
// Handle enter key press to select a suggestion
onEnter(): void { onEnter(): void {
console.log("onEnter");
if (this.selectedIndex === -1) { if (this.selectedIndex === -1) {
// this.$emit("nothingSelected", this.display); // this.$emit("nothingSelected", this.display);
this.display && this.search(); this.display && this.search();
@ -222,18 +348,18 @@ export default class VsInput extends Vue {
@Emit("search-change") @Emit("search-change")
private select(obj: Suggestion): Suggestion { private select(obj: Suggestion): Suggestion {
// if (!obj) { console.log("select:");
// return; this.value = obj;
// } console.log(obj);
this.value = obj; //(obj["title_output"]) ? obj["title_output"] : obj.id
this.display = obj.value; // this.formatDisplay(obj) this.display = obj.value;
// this.selectedDisplay = this.display;
this.close(); this.close();
// this.$emit("update", this.value);
return this.value; return this.value;
} }
// Find a search term in an array
private find(myarray: Array<string>, searchterm: string): string { private find(myarray: Array<string>, searchterm: string): string {
for (let i = 0, len = myarray.length; i < len; i += 1) { for (let i = 0, len = myarray.length; i < len; i += 1) {
if (typeof myarray[i] === "string" && myarray[i].toLowerCase().indexOf(searchterm) !== -1) { if (typeof myarray[i] === "string" && myarray[i].toLowerCase().indexOf(searchterm) !== -1) {
@ -248,15 +374,11 @@ export default class VsInput extends Vue {
* Close the results list. If nothing was selected clear the search * Close the results list. If nothing was selected clear the search
*/ */
close(): void { close(): void {
console.log("close");
if (!this.value) { if (!this.value) {
this.clear(); this.clear();
} }
// if (this.selectedDisplay !== this.display && this.value) {
// this.display = this.selectedDisplay;
// }
this.results = []; this.results = [];
this.error = ""; this.error = "";
//this.removeEventListener()
// this.$emit("close");
} }
} }

View File

@ -1,33 +1,52 @@
<template> <template>
<!-- Parent container with multiple rows -->
<div class="is-multiline"> <div class="is-multiline">
<!-- Search input wrapper -->
<div class="column is-two-thirds-tablet is-half-desktop is-one-third-widescreen mx-auto"> <div class="column is-two-thirds-tablet is-half-desktop is-one-third-widescreen mx-auto">
<!-- Search box -->
<div class="search-box mx-auto"> <div class="search-box mx-auto">
<!-- Search field -->
<div class="field has-addons main-search-from-bg"> <div class="field has-addons main-search-from-bg">
<div class="control is-expanded"> <div class="control is-expanded">
<!-- Input field for search query -->
<input <input
id="search_query" id="search_query"
v-model="searchTerm" v-model="display"
class="input is-medium" class="input is-medium"
type="text" type="text"
name="q" name="q"
autocomplete="off" autocomplete="off"
v-bind:placeholder="placeholder" v-bind:placeholder="placeholder"
@input="searchChanged" @input="searchChanged"
@keydown.down="onArrowDown"
@keydown.up="onArrowUp"
@keydown.enter="onEnter"
@keydown.tab="close"
@focus="focus"
/> />
</div> </div>
<!-- Search button -->
<div class="control"> <div class="control">
<button class="button input is-medium search-button-icon" @click="search()"> <button class="button input is-medium search-button-icon" @click="search()">
<!-- Search icon -->
<i class="fas fa-search text-white"></i> <i class="fas fa-search text-white"></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Suggestions list -->
<div class="column is-two-thirds-tablet is-half-desktop is-one-third-widescreen mx-auto"> <div class="column is-two-thirds-tablet is-half-desktop is-one-third-widescreen mx-auto">
<ul v-show="showResults" class="autocomplete-results pure-u-23-24"> <ul v-show="showResults" class="autocomplete-results pure-u-23-24">
<!-- Loading indicator -->
<li v-if="isLoading" class="loading">Loading results...</li> <li v-if="isLoading" class="loading">Loading results...</li>
<!-- Iterating over suggestions -->
<li <li
v-for="(result, i) in searchResults" v-for="(result, i) in suggestions"
v-else v-else
v-bind:key="i" v-bind:key="i"
v-bind:ref="setItemRef" v-bind:ref="setItemRef"
@ -35,8 +54,10 @@
v-bind:class="{ 'is-active': isSelected(i) }" v-bind:class="{ 'is-active': isSelected(i) }"
@click.prevent="select(result)" @click.prevent="select(result)"
> >
<!-- Displaying suggestion result -->
<div class="small-label"> <div class="small-label">
<label>{{ result.title }}</label> <!-- <label>{{ result.value }} ({{ result.type }})</label> -->
<label v-html="formatSuggestion(result)"></label>
</div> </div>
</li> </li>
</ul> </ul>
@ -44,22 +65,6 @@
</div> </div>
</template> </template>
<script lang="ts">
export default {
data() {
return {
searchTerm: "",
searchResults: [],
};
},
methods: {
searchChanged() {
//...
},
},
};
</script>
<script lang="ts"> <script lang="ts">
import VsInput from "./vs-input"; import VsInput from "./vs-input";
export default VsInput; export default VsInput;
@ -131,7 +136,7 @@ input {
list-style-type: none; list-style-type: none;
z-index: 1000; z-index: 1000;
position: absolute; position: absolute;
max-height: 200px; max-height: 192px;
overflow-y: auto; overflow-y: auto;
overflow: hidden; overflow: hidden;
background: white; background: white;
@ -144,15 +149,17 @@ input {
.autocomplete-result-item { .autocomplete-result-item {
list-style: none; list-style: none;
text-align: left; text-align: left;
/* padding: 7px 10px; */ padding: 0px 0px 0px 5px; // top,right,bottom,left
cursor: pointer; cursor: pointer;
} }
.autocomplete-result-item.is-active { .autocomplete-result-item.is-active {
background: rgba(0, 180, 255, 0.15); background: rgba(0, 180, 255, 0.15);
// background: #3cc;
} }
.autocomplete-result-item:hover { .autocomplete-result-item:hover {
background: rgba(0, 180, 255, 0.075); // background: rgba(0, 180, 255, 0.075);
background: #baedf1;
} }
</style> </style>

View File

@ -14,6 +14,16 @@ export default class VsResult extends Vue {
return this.datasets; return this.datasets;
} }
public simplifyAuthor(author:string): string {
if (author.endsWith(" ")) {
return author.substring(0, author.indexOf(","));
} else {
let firstNameInitial:string = author.charAt(author.indexOf(",") + 2);
return author.substring(0, author.indexOf(",") + 2) + firstNameInitial;
}
}
public getDomainWithoutSubdomain(): string { public getDomainWithoutSubdomain(): string {
const urlParts = new URL(window.location.href).hostname.split("."); const urlParts = new URL(window.location.href).hostname.split(".");
@ -23,13 +33,15 @@ export default class VsResult extends Vue {
.join("."); .join(".");
} }
private convert(unixtimestamp: number): string { // private convert(unixtimestamp: number): string { // SOLR
private convert(unixtimestamp: string): string { // OpenSearch
// Unixtimestamp // Unixtimestamp
// var unixtimestamp = document.getElementById('timestamp').value; // var unixtimestamp = document.getElementById('timestamp').value;
// Months array // Months array
const months_arr = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; const months_arr = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
// Convert timestamp to milliseconds // Convert timestamp to milliseconds
const date = new Date(unixtimestamp * 1000); // const date = new Date(unixtimestamp * 1000); // SOLR
const date = new Date(Number(unixtimestamp) * 1000); // OpenSearch
// Year // Year
const year = date.getFullYear(); const year = date.getFullYear();
// Month // Month

View File

@ -3,49 +3,46 @@
<div class="card result-list-container"> <div class="card result-list-container">
<div class="card-content record-elem"> <div class="card-content record-elem">
<p v-if="document.identifier && document.identifier.length > 0"> <p v-if="document.identifier && document.identifier.length > 0">
<!-- <span>Author: {{ document.identifier.join(', ') }}</span> -->
<!-- <span v-for="(author,index) in document.author" :key="index">{{ author }}; </span> -->
<!-- <span>'https://doi.org/' + {{ document.identifier[0] }}</span> -->
<a target="_blank" v-bind:href="'https://doi.org/' + document.identifier[0]"> <a target="_blank" v-bind:href="'https://doi.org/' + document.identifier[0]">
{{ "https://doi.org/" + document.identifier[0] + " &#10148;" }} </a {{ "https://doi.org/" + document.identifier[0] + " &#10148;" }} </a
>&nbsp; >&nbsp;
<span v-if="document.author && document.author.length > 0" class="disabled">{{ document.author[0] }}</span>
<!-- Display authors conditionally -->
<span v-if="document.author && document.author.length > 0">
<!-- For one author, just display the author's name -->
<span v-if="document.author.length === 1" class="disabled">
{{ simplifyAuthor(document.author[0]) }}
</span>
<!-- For 2-3 authors, display them all -->
<span v-if="document.author.length > 1 && document.author.length < 3">
<span v-for="(author, index) in document.author" :key="index" class="disabled">
{{ simplifyAuthor(author) }}<span v-if="index < document.author.length - 1" class="disabled">; </span>
</span>
</span>
<!-- For 4 or more authors, display the first three and add "et al." -->
<span v-if="document.author.length >= 3" class="disabled">
<span v-for="(author, index) in document.author.slice(0, 2)" :key="index" class="disabled">
{{ simplifyAuthor(author) }}<span v-if="index < 1" class="disabled">; </span>
</span>
et al.
</span>
</span>
</p> </p>
<!-- <span class="label label-info" data-container="div" data-title="Publication date">
{{ convert(document.server_date_published) }}
</span>
<span class="label label-default ng-binding">{{ document.doctype }}</span>
<span v-if="openAccessLicences.includes(document.licence)" class="label label-success titlecase">Open Access</span> -->
<h4> <h4>
<!-- <a <router-link class="ng-binding" v-bind:to="{ name: 'dataset', params: { datasetId: document.id } }">
v-if="document.identifier && document.identifier.length > 0"
target="_self"
v-bind:href="'https://doi.' + getDomainWithoutSubdomain() + '/' + document.identifier[0]"
class="ng-binding"
>
{{ document.title_output }} {{ document.title_output }}
</a> --> </router-link>
<!-- <a target="_self" v-bind:href="'dataset/' + document.id" class="ng-binding">
{{ document.title_output }}
</a> -->
<router-link class="ng-binding" v-bind:to="{ name: 'dataset', params: { datasetId: document.id } }">{{
document.title_output
}}</router-link>
</h4> </h4>
<!-- <p v-if="document.author && document.author.length > 0">
<span>Author: {{ document.author.join(', ') }}</span>
<span v-for="(author, index) in document.author" :key="index">{{ author }}; </span>
</p> -->
<p class="clamped clamped-2"> <p class="clamped clamped-2">
<span class="disabled" data-container="div" data-title="Publication date"> <span class="disabled" data-container="div" data-title="Publication date">
{{ convert(document.server_date_published) + ":&nbsp;" }} {{ convert(document.server_date_published) + ":&nbsp;" }}
</span> </span>
<span class="text"> <span class="text">
{{ document.abstract_output }} <!-- {{ document.abstract_output }} -->
{{ document.abstract[0] }}
<span class="ellipsis">...</span> <span class="ellipsis">...</span>
<span class="fill"></span> <span class="fill"></span>
</span> </span>
@ -53,11 +50,9 @@
<p> <p>
<span class="label"><i class="fas fa-file"></i> {{ document.doctype }}</span> <span class="label"><i class="fas fa-file"></i> {{ document.doctype }}</span>
<!-- <span>Licence: {{ document.licence }}</span> -->
<span v-if="openAccessLicences.includes(document.licence)" class="label titlecase"><i class="fas fa-lock-open"></i> Open Access</span> <span v-if="openAccessLicences.includes(document.licence)" class="label titlecase"><i class="fas fa-lock-open"></i> Open Access</span>
</p> </p>
<!-- <span class="label label-keyword titlecase" v-for="(item, index) in document.subject" :key="index"> #{{ item }} </span> -->
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,20 +1,26 @@
// declare const POINT_URL: string;
// declare const EDGE_URL: string;
declare const APP_URL: string; declare const APP_URL: string;
declare const VUE_APP_PORTAL: string; declare const VUE_API: string;
declare const SOLR_HOST: string; // declare const SOLR_HOST: string;
declare const SOLR_CORE: string; // declare const SOLR_CORE: string;
// OPENSEARCH
declare const OPEN_HOST: string;
declare const OPEN_CORE: string;
// const _EDGE_URL = EDGE_URL;
// const _POINT_URL = POINT_URL;
const _APP_URL = APP_URL; const _APP_URL = APP_URL;
const _VUE_APP_PORTAL = VUE_APP_PORTAL; const _VUE_API = VUE_API;
const _SOLR_HOST = SOLR_HOST; // const _SOLR_HOST = SOLR_HOST;
const _SOLR_CORE = SOLR_CORE; // const _SOLR_CORE = SOLR_CORE;
// OPENSEARCH
const _OPEN_HOST = OPEN_HOST;
const _OPEN_CORE = OPEN_CORE;
// export { _EDGE_URL as EDGE_URL };
// export { _POINT_URL as POINT_URL };
export { _APP_URL as APP_URL }; export { _APP_URL as APP_URL };
export { _VUE_APP_PORTAL as VUE_APP_PORTAL }; export { _VUE_API as VUE_API };
export { _SOLR_HOST as SOLR_HOST }; // export { _SOLR_HOST as SOLR_HOST };
export { _SOLR_CORE as SOLR_CORE }; // export { _SOLR_CORE as SOLR_CORE };
// OPENSEARCH
export { _OPEN_HOST as OPEN_HOST };
export { _OPEN_CORE as OPEN_CORE };

3
src/index.css Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -1,18 +1,21 @@
import { createApp } from "vue"; import { createApp } from "vue";
import App from "./App.vue"; import App from "./App.vue";
import App2 from "./App2.vue"; import App2 from "./App2.vue";
// add bulma style
import "./assets/scss/main-styles.scss"; import "./assets/scss/main-styles.scss";
// import FontAwesomeIcon from "@/utilities/fontawesome"; // import FontAwesomeIcon from "@/utilities/fontawesome";
import index from "./router/index"; import index from "./router/index";
import route1 from "./router/route1"; import route1 from "./router/route1";
import "@fortawesome/fontawesome-free/css/all.css"; import "@fortawesome/fontawesome-free/css/all.css";
import VueMatomo from "vue-matomo"; import VueMatomo from "vue-matomo";
// // add tailwind style
// import "@/index.css";
const host = window.location.host; const host = window.location.host;
const parts = host.split("."); const parts = host.split(".");
// const domainLength = 2; // route1.example.com => domain length = 3 // const domainLength = 2; // route1.example.com => domain length = 3
let router, app; let router, app;
// let routes; // exmample change
if (parts[0] === "doi") { if (parts[0] === "doi") {
router = route1; router = route1;
app = App2; app = App2;

View File

@ -1,69 +1,92 @@
// import moment from "moment"; // import moment from "moment";
import dayjs from "dayjs"; import dayjs from "dayjs";
// // SOLR Dataset original
// export interface Dataset {
// abstract_additional: Array<string>;// OpenSearch: abstract: Array<string>
// abstract_output: string;// -----
// author: Array<string>;// EQUAL
// author_sort: Array<string>;// -----
// belongs_to_bibliography: boolean;// EQUAL
// creating_corporation: string;// EQUAL
// doctype: string;// EQUAL
// geo_location: string;// EQUAL
// id: number;// EQUAL
// identifier: Identifier;// OpenSearch: identifier: Array<string>
// language: string;// EQUAL
// licence: string;// EQUAL
// publisher_name: string;// EQUAL
// server_date_published: Array<number>;// OpenSearch not array!
// subject: Array<string>;// OpenSearch: subjectS
// title_output: string;// EQUAL
// year: number;// EQUAL
// year_inverted: number;// EQUAL
// }
// OpenSearch Dataset
export interface Dataset { export interface Dataset {
abstract_additional: Array<string>; abstract: Array<string>;// OpenSearch: abstract: Array<string>
abstract_output: string; // abstract_output: string;// ----- NOT in OpenSearch
author: Array<string>; author: Array<string>;// EQUAL
author_sort: Array<string>; // author_sort: Array<string>;// ----- NOT in OpenSearch
belongs_to_bibliography: boolean; belongs_to_bibliography: boolean;// EQUAL
creating_corporation: string; creating_corporation: string;// EQUAL
doctype: string; doctype: string;// EQUAL
geo_location: string; geo_location: string;// EQUAL
id: number; id: number;// EQUAL
identifier: Identifier; // identifier: Identifier;// OpenSearch: identifier: Array<string>
language: string; identifier: Array<string>// DIFF DATATYPE
licence: string; language: string;// EQUAL
publisher_name: string; licence: string;// EQUAL
server_date_published: Array<number>; publisher_name: string;// EQUAL
subject: Array<string>; // server_date_published: Array<number>;// OpenSearch string!
title_output: string; server_date_published: string;// DIFF DATATYPE
year: number; // subject: Array<string>;// OpenSearch: subjectS
year_inverted: number; subjects: Array<string>;// DIFF DATATYPE
title_output: string;// EQUAL
year: number;// EQUAL
year_inverted: number;// EQUAL
title: string // Unique in OpenSearch
title_additional: Array<string> // Unique in OpenSearch
bbox_xmin: string // Unique in OpenSearch
bbox_xmax: string // Unique in OpenSearch
bbox_ymin: string // Unique in OpenSearch
bbox_ymax: string // Unique in OpenSearch
reference: Array<string> // Unique in OpenSearch
abstract_additional: Array<string>;// Unique in OpenSearch
} }
export class Suggestion { export class Suggestion {
constructor(public value: string, public type: SearchType) {} constructor(
public value: string, // Store the text value returned by OpenSearch
// Store the highlight: i.e. the text value with the emphasised term that generated that results by OpenSearch.
// In this way we can highlight the real term existing in the publication independently of how different was the inserted term used for the FUZZY search. e.g. "Vien" fuzzy matched with "Wien"
public highlight: string,
public type: SearchType, // Type of search element
) {}
// value!: string; // value!: string;
// type!: SearchType; // type!: SearchType;
} }
// export class Suggestion {
// constructor(
// public value: string,
// public type: SearchType,
// ) {}
// // value!: string;
// // type!: SearchType;
// }
export enum SearchType { export enum SearchType {
Title = "title", Title = "title",
Author = "author", Author = "author",
Subject = "subject", Subject = "subjects", // ** !! The field has this name in OpenSearch!!
Doctype = "doctype"
} }
export class DbDataset { export class DbDataset {
// public id!: number;
// public url!: string;
// public contributing_corporation!: string;
// public creating_corporation!: string;
// public publisher_name!: string;
// public embargo_date!: string;
// public publish_id!: number;
// public project_id!: number;
// public type!: string;
// public language!: string;
// public server_state!: string;
// public belongs_to_bibliography!: boolean;
// public created_at!: string;
// public server_date_modified!: string;
// public server_date_published!: string;
// public account_id!: number;
// public editor_id!: number;
// public reviewer_id!: number;
// public preferred_reviewer!: number;
// public preferred_reviewer_email!: string;
// public reject_editor_note!: string;
// public reject_reviewer_note!: string;
// public reviewer_note_visible!: string;
// public titles!: Array<Title>;
// public abstracts!: Array<Abstract>;
// public authors!: Array<Author>;
// public contributors!: Array<Author>;
// public user!: Person;
// public subjects!: Array<Subject>;
constructor( constructor(
public id: string, public id: string,
@ -212,17 +235,31 @@ export class DbDataset {
const yMax = this.coverage.y_max; const yMax = this.coverage.y_max;
// const elevationAbsolut = this.coverage.elevation_absolut; // const elevationAbsolut = this.coverage.elevation_absolut;
// let geoLocation =
// "- SOUTH-BOUND LATITUDE: " +
// yMin +
// "\n" +
// "- WEST-BOUND LONGITUDE: " +
// xMin +
// "\n" +
// "- NORTH-BOUND LATITUDE: " +
// yMax +
// "\n" +
// "- EAST-BOUND LONGITUDE: " +
// xMax;
let geoLocation = let geoLocation =
"* SOUTH-BOUND LATITUDE: " + // "- SOUTH-BOUND LATITUDE: " +
"- South-bound Latitude: " +
yMin + yMin +
"\n" + "\n" +
"* WEST-BOUND LONGITUDE: " + "- West-bound Longitude: " +
xMin + xMin +
"\n" + "\n" +
"* NORTH-BOUND LATITUDE: " + "- North-bound Latitude: " +
yMax + yMax +
"\n" + "\n" +
"* EAST-BOUND LONGITUDE: " + "- East-bound Longitude: " +
xMax; xMax;
// geoLocation += elevationAbsolut != null ? ` * ELEVATION ABSOLUT: ${elevationAbsolut}\n` : ""; // geoLocation += elevationAbsolut != null ? ` * ELEVATION ABSOLUT: ${elevationAbsolut}\n` : "";
@ -231,36 +268,83 @@ export class DbDataset {
let elevation = ""; let elevation = "";
if (this.coverage.elevation_max != null && this.coverage.elevation_min != null) { if (this.coverage.elevation_max != null && this.coverage.elevation_min != null) {
elevation += "\n* ELEVATION MIN: " + this.coverage.elevation_min + " *\nELEVATION MAX: " + this.coverage.elevation_max; // elevation += "\n- ELEVATION MIN: " + this.coverage.elevation_min + " \n- ELEVATION MAX: " + this.coverage.elevation_max;
elevation += "\n- Elevation Min.: " + this.coverage.elevation_min + " m\n- Elevation Max.: " + this.coverage.elevation_max + " m";
} }
if (this.coverage.elevation_absolut != null) { if (this.coverage.elevation_absolut != null) {
elevation += "\n* ELEVATION ABSOLUT: " + this.coverage.elevation_absolut; elevation += "\n- Elevation Absolut: " + this.coverage.elevation_absolut + " m";
} }
if (elevation != "") geoLocation += elevation; // if (elevation != "") geoLocation += ("\n---" + elevation);
let depth = ""; let depth = "";
if (this.coverage.depth_max != null && this.coverage.depth_min != null) { if (this.coverage.depth_max != null && this.coverage.depth_min != null) {
depth += "\n* DEPTH MIN: " + this.coverage.depth_min + "\n* DEPTH MAX: " + this.coverage.depth_max; depth += "\n- Depth Min.: " + this.coverage.depth_min + " m\n- Depth Max.: " + this.coverage.depth_max + " m";
} }
if (this.coverage.elevation_absolut != null) { if (this.coverage.elevation_absolut != null) {
depth += "\n* DEPTH ABSOLUT: " + this.coverage.depth_absolut; depth += "\n- Depth Absolut: " + this.coverage.depth_absolut + " m";
} }
if (depth != "") geoLocation += depth; // if (depth != "") geoLocation += depth;
let time = ""; let time = "";
if (this.coverage.time_max != null && this.coverage.time_min != null) { if (this.coverage.time_max != null && this.coverage.time_min != null) {
time += "\n* TIME MIN: " + this.coverage.time_min + "\n* TIME MAX: " + this.coverage.time_max; time += "\n- Time Min.: " + this.coverage.time_min + "\n- Time Max.: " + this.coverage.time_max;
} }
if (this.coverage.time_absolut != null) { if (this.coverage.time_absolut != null) {
time += "\n* TIME ABSOLUT: " + this.coverage.time_absolut; time += "\n- Time Absolut: " + this.coverage.time_absolut;
} }
if (time != "") geoLocation += time; // if (time != "") geoLocation += time;
return geoLocation; // let elevation = "";
// if (this.coverage.elevation_max != null && this.coverage.elevation_min != null) {
// elevation += "\n* ELEVATION MIN: " + this.coverage.elevation_min + " \n* ELEVATION MAX: " + this.coverage.elevation_max;
// }
// if (this.coverage.elevation_absolut != null) {
// elevation += "\n* ELEVATION ABSOLUT: " + this.coverage.elevation_absolut;
// }
// if (elevation != "") geoLocation += elevation;
// let depth = "";
// if (this.coverage.depth_max != null && this.coverage.depth_min != null) {
// depth += "\n* DEPTH MIN: " + this.coverage.depth_min + "\n* DEPTH MAX: " + this.coverage.depth_max;
// }
// if (this.coverage.elevation_absolut != null) {
// depth += "\n* DEPTH ABSOLUT: " + this.coverage.depth_absolut;
// }
// if (depth != "") geoLocation += depth;
// let time = "";
// if (this.coverage.time_max != null && this.coverage.time_min != null) {
// time += "\n* TIME MIN: " + this.coverage.time_min + "\n* TIME MAX: " + this.coverage.time_max;
// }
// if (this.coverage.time_absolut != null) {
// time += "\n* TIME ABSOLUT: " + this.coverage.time_absolut;
// }
// if (time != "") geoLocation += time;
if (elevation != "" || depth != "" || time != "" ) {
return geoLocation + "\n ---" + elevation + depth + time;
} else {
return geoLocation + elevation + depth + time;
}
// return geoLocation;
} else { } else {
return ""; return "";
} }
} }
/* Provides the bounds of the publication for the Leaflet minimap */
public get Bounds(): L.LatLngBoundsLiteral | string {
if (this.coverage != undefined) {
return [
[Number(this.coverage.y_min), Number(this.coverage.x_min)], // Southwest corner
[Number(this.coverage.y_max), Number(this.coverage.x_max)] // Northeast corner
];
} else {
return "";
}
}
} }
type Nullable<T> = T | undefined; type Nullable<T> = T | undefined;

View File

@ -18,19 +18,6 @@ export interface ResponseHeaderParams {
rows?: number; rows?: number;
start?: number; start?: number;
wt?: string; wt?: string;
// 0:'fl=id,licence,server_date_published,abstract_output,identifier,title_output,title_additional,author,subject,doctype'
// df:'title'
// facet:'on'
// indent:'on'
// json.facet.language:'{ type: "terms", field: "language" }'
// json.facet.subject:'{ type: "terms", field: "subject" }'
// q:'title:Geodaten - Blatt 49 Wels (1:50.000)'
// q.op:'and'
// rows:'10'
// start:'0'
// wt:'json'
} }
export interface ResponseContent { export interface ResponseContent {
@ -39,36 +26,21 @@ export interface ResponseContent {
docs: Array<Dataset>; docs: Array<Dataset>;
} }
// export interface FacetCount { //Used
// facet_fields: FacetCategory<any>;
// }
// export class FacetCategory<T> {
// [key: string]: {
// values: T[];
// };
// }
export class FacetResults { export class FacetResults {
// language!: Array<FacetItem>;
// subject!: Array<FacetItem>;
[key: string]: Array<FacetItem>; [key: string]: Array<FacetItem>;
} }
//#region solr response facets
export class FacetFields { export class FacetFields {
// count: number;
language!: FacetInstance; language!: FacetInstance;
subject!: FacetInstance; subject!: FacetInstance;
// [key: string]: FacetInstance;
} }
export interface FacetInstance { export interface FacetInstance {
[key: string]: Array<FacetItem>; [key: string]: Array<FacetItem>;
// buckets: Array<FacetItem>;
} }
//Used
export class FacetItem { export class FacetItem {
val: string; val: string;
count: number; count: number;
@ -83,3 +55,85 @@ export class FacetItem {
} }
} }
//#endregion //#endregion
// OPENSEARCH
// ========================================================================
export interface OpenSearchResponse {
took: number;
timed_out: boolean;
_shards: Shards;
hits: Hits; // Equivalent SOLR: response > docs
aggregations?: Aggregations; // Equivalent SOLR: facets
}
export interface Shards {
total: number;
successful: number;
skipped: number;
failed: number;
}
export interface Hits {
total: Total;
max_score: number;
hits: Array<Hit>;
}
export interface Total {
value: number; // Equivalent SOLR: response > numFound
relation: string;
}
export interface Hit {
_index: string;
_id: string;
_score: number;
_source: Dataset;
highlight: HitHighlight; // !! This name is to avoid collision with Typescript "Highlight" class
}
export interface HitHighlight {
subjects?: Array<string>;
title?: Array<string>;
author?: Array<string>;
doctype?: Array<string>;
}
export interface Aggregations { // Equivalent SOLR: FacetFields
subjects: Subjects;
language: Language;
doctype: Doctype;
}
export interface Subjects {
doc_count_error_upper_bound: number;
sum_other_doc_count: number;
buckets: Array<Bucket>;
}
export interface Language {
doc_count_error_upper_bound: number;
sum_other_doc_count: number;
buckets: Array<Bucket>;
}
export interface Doctype {
doc_count_error_upper_bound: number;
sum_other_doc_count: number;
buckets: Array<Bucket>;
}
export interface Bucket {
key: string;
doc_count: number;
}
// // Needed?
// export interface Aggregations {
// [key: string]: Aggregation;
// }
// export interface Aggregation {
// buckets: Array<Bucket>;
// }

View File

@ -1,9 +1,9 @@
import { Dataset } from "./dataset"; import { Dataset } from "./dataset";
export interface Pagination { export interface IPagination {
total: number; total: number;
per_page?: number; perPage: number;
current_page: number; currentPage: number;
last_page?: number; lastPage?: number;
data: Array<Dataset>; data: Array<Dataset>;
} }

View File

@ -1,4 +1,9 @@
export interface SolrSettings { // export interface SolrSettings {
// core: string;
// host: string;
// }
export interface OpenSettings {
core: string; core: string;
host: string; host: string;
} }

View File

@ -119,7 +119,7 @@ const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
configureWebpack: { configureWebpack: {
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
VUE_APP_PORTAL: JSON.stringify(process.env.VUE_APP_PORTAL), VUE_API: JSON.stringify(process.env.VUE_API),
SOLR_HOST: JSON.stringify(process.env.SOLR_HOST), SOLR_HOST: JSON.stringify(process.env.SOLR_HOST),
SOLR_CORE: JSON.stringify(process.env.SOLR_CORE), SOLR_CORE: JSON.stringify(process.env.SOLR_CORE),
}), }),

View File

@ -1,698 +0,0 @@
<div class="columns">
<div class="column is-9 results_column" style="padding-top: 1.2rem; padding-right: 0.5rem">
<div class="column" id="loading_bar" style="padding-top: 0px; display: none">
<div class="column" style="padding-top: 0; padding-bottom: 0; margin-bottom: 0"></div>
<div class="card" style="margin-bottom: 0px">
<div class="card-content">
<div class="content">
<noscript>It appears that your Javascript is disabled. To view results on
this page, you will need to enable it. You might want to visit our
<a href="/lite/results">lite results</a>.
</noscript>
<progress class="progress is-large" max="100">60%</progress>
</div>
</div>
</div>
</div>
<div id="results">
<div class="column" style="padding-top: 0">
<div class="card" style="margin-bottom: 0px">
<div class="card-content">
<div class="content">
It looks like you're not logged in right now. you will need to
<a href="/login" target="_self">login to Pro</a> or become a
<a href="https://coil.com/?ref=InfinitySearch2229" target="_self">Coil Member</a>
to access the results.
<div class="block"></div>
<div class="columns" style="text-align: center">
<div class="column is-one-third">
<a class="button" style="width: 100%" href="/login" target="_self">Login</a>
</div>
<div class="column is-one-third">
<a class="button" style="width: 100%" href="/pro" target="_self">Learn more about
Infinity Pro</a>
</div>
<div class="column is-one-third">
<a class="button" style="width: 100%"
href="https://coil.com/?ref=InfinitySearch2229" target="_self">Learn more about
Coil</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div hidden="" id="id-custom-tab-page">
<div class="columns" style="margin-top: 1rem">
<div class="column is-one-quarter is-offset-2">
<p class="heading">Current Tabs</p>
<div id="id-current-tab-list">
<nav class="level is-mobile" id="id-web-tab">
<div class="level-item has-text-centered is-capitalized">web</div>
<div class="level-item has-text-centered is-one-quarter">
<div style="order: 2; margin-left: auto; margin-right: 0.5rem">
<img class="handle fa-ellipsis-v" src="/static/images/fa/ellipsis-v.svg" style="
cursor: pointer;
min-width: 0.5em;
width: 16px;
height: 16px;
" />
<img class="handle trash" src="/static/images/fa/trash-alt.svg"
id="id-remove-tab-icon-web" style="
cursor: pointer;
min-width: 0.5em;
width: 16px;
height: 16px;
margin-left: 0.5rem;
" onclick="removeTab(this);" />
</div>
</div>
</nav>
<nav class="level is-mobile" id="id-images-tab">
<div class="level-item has-text-centered is-capitalized">
images
</div>
<div class="level-item has-text-centered is-one-quarter">
<div style="order: 2; margin-left: auto; margin-right: 0.5rem">
<img class="handle fa-ellipsis-v" src="/static/images/fa/ellipsis-v.svg" style="
cursor: pointer;
min-width: 0.5em;
width: 16px;
height: 16px;
" />
<img class="handle trash" src="/static/images/fa/trash-alt.svg"
id="id-remove-tab-icon-images" style="
cursor: pointer;
min-width: 0.5em;
width: 16px;
height: 16px;
margin-left: 0.5rem;
" onclick="removeTab(this);" />
</div>
</div>
</nav>
<nav class="level is-mobile" id="id-videos-tab">
<div class="level-item has-text-centered is-capitalized">
videos
</div>
<div class="level-item has-text-centered is-one-quarter">
<div style="order: 2; margin-left: auto; margin-right: 0.5rem">
<img class="handle fa-ellipsis-v" src="/static/images/fa/ellipsis-v.svg" style="
cursor: pointer;
min-width: 0.5em;
width: 16px;
height: 16px;
" />
<img class="handle trash" src="/static/images/fa/trash-alt.svg"
id="id-remove-tab-icon-videos" style="
cursor: pointer;
min-width: 0.5em;
width: 16px;
height: 16px;
margin-left: 0.5rem;
" onclick="removeTab(this);" />
</div>
</div>
</nav>
<nav class="level is-mobile" id="id-homepages-tab">
<div class="level-item has-text-centered is-capitalized">
homepages
</div>
<div class="level-item has-text-centered is-one-quarter">
<div style="order: 2; margin-left: auto; margin-right: 0.5rem">
<img class="handle fa-ellipsis-v" src="/static/images/fa/ellipsis-v.svg" style="
cursor: pointer;
min-width: 0.5em;
width: 16px;
height: 16px;
" />
<img class="handle trash" src="/static/images/fa/trash-alt.svg"
id="id-remove-tab-icon-homepages" style="
cursor: pointer;
min-width: 0.5em;
width: 16px;
height: 16px;
margin-left: 0.5rem;
" onclick="removeTab(this);" />
</div>
</div>
</nav>
<nav class="level is-mobile" id="id-food-tab">
<div class="level-item has-text-centered is-capitalized">
food
</div>
<div class="level-item has-text-centered is-one-quarter">
<div style="order: 2; margin-left: auto; margin-right: 0.5rem">
<img class="handle fa-ellipsis-v" src="/static/images/fa/ellipsis-v.svg" style="
cursor: pointer;
min-width: 0.5em;
width: 16px;
height: 16px;
" />
<img class="handle trash" src="/static/images/fa/trash-alt.svg"
id="id-remove-tab-icon-food" style="
cursor: pointer;
min-width: 0.5em;
width: 16px;
height: 16px;
margin-left: 0.5rem;
" onclick="removeTab(this);" />
</div>
</div>
</nav>
<nav class="level is-mobile" id="id-books-tab">
<div class="level-item has-text-centered is-capitalized">
books
</div>
<div class="level-item has-text-centered is-one-quarter">
<div style="order: 2; margin-left: auto; margin-right: 0.5rem">
<img class="handle fa-ellipsis-v" src="/static/images/fa/ellipsis-v.svg" style="
cursor: pointer;
min-width: 0.5em;
width: 16px;
height: 16px;
" />
<img class="handle trash" src="/static/images/fa/trash-alt.svg"
id="id-remove-tab-icon-books" style="
cursor: pointer;
min-width: 0.5em;
width: 16px;
height: 16px;
margin-left: 0.5rem;
" onclick="removeTab(this);" />
</div>
</div>
</nav>
<nav class="level is-mobile" id="id-movies-tab">
<div class="level-item has-text-centered is-capitalized">
movies
</div>
<div class="level-item has-text-centered is-one-quarter">
<div style="order: 2; margin-left: auto; margin-right: 0.5rem">
<img class="handle fa-ellipsis-v" src="/static/images/fa/ellipsis-v.svg" style="
cursor: pointer;
min-width: 0.5em;
width: 16px;
height: 16px;
" />
<img class="handle trash" src="/static/images/fa/trash-alt.svg"
id="id-remove-tab-icon-movies" style="
cursor: pointer;
min-width: 0.5em;
width: 16px;
height: 16px;
margin-left: 0.5rem;
" onclick="removeTab(this);" />
</div>
</div>
</nav>
</div>
</div>
<div class="column is-one-quarter is-offset-1">
<p class="heading">Add New Tabs</p>
<nav class="level">
<div class="level-item">
<div class="select">
<select id="id-add-tab-selector" style="
font-family: BlinkMacSystemFont, -apple-system, 'Segoe UI',
Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans',
'Droid Sans', 'Helvetica Neue', Helvetica, Arial,
sans-serif;
">
<option id="id-tab-selector-web" value="web" style="display: none">
Web
</option>
<option id="id-tab-selector-images" value="images" style="display: none">
Images
</option>
<option id="id-tab-selector-videos" value="videos" style="display: none">
Videos
</option>
<option id="id-tab-selector-homepages" value="homepages" style="display: none">
Homepages
</option>
<option id="id-tab-selector-general" value="general">
General
</option>
<option id="id-tab-selector-food" value="food" style="display: none">
Food
</option>
<option id="id-tab-selector-books" value="books" style="display: none">
Books
</option>
<option id="id-tab-selector-movies" value="movies" style="display: none">
Movies
</option>
<option id="id-tab-selector-music" value="music">
Music
</option>
<option id="id-tab-selector-infinity" value="infinity">
Infinity
</option>
<option id="id-tab-selector-edu" value="edu">Edu</option>
<option id="id-tab-selector-pdf" value="pdf">Pdf</option>
<option id="id-tab-selector-qa" value="qa">Qa</option>
<option id="id-tab-selector-reddit" value="reddit">
Reddit
</option>
<option id="id-tab-selector-fandom" value="fandom">
Fandom
</option>
<option id="id-tab-selector-coil" value="coil">Coil</option>
<option id="id-tab-selector-no_js" value="no_js">
No Javascript
</option>
<option id="id-tab-selector-decentralized" value="decentralized">
Decentralized
</option>
</select>
</div>
</div>
<div class="level-item">
<button class="button" id="id-add-tab-button" onclick="addTab();" style="
font-family: BlinkMacSystemFont, -apple-system, 'Segoe UI',
Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans',
'Helvetica Neue', Helvetica, Arial, sans-serif;
">
Add
</button>
</div>
</nav>
<br />
<nav class="level">
<div class="level-item">
<button class="button" id="id-update-tabs-button" style="
order: 2;
margin-left: auto;
font-family: BlinkMacSystemFont, -apple-system, 'Segoe UI',
Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans',
'Helvetica Neue', Helvetica, Arial, sans-serif;
" onclick="resetTabsToOriginal();">
Reset
</button>
</div>
</nav>
<div id="id-custom-tabs-error"></div>
</div>
</div>
</div>
</div>
<div id="id-side-bar" class="column is-3 sidebar_column" style="padding-top: 1.2rem; padding-right: 1.5rem">
<div id="externals" class="">
<div class="card" name="external_card" style="margin-bottom: 0px">
<header class="card-header">
<p class="card-header-title" style="font-weight: normal; padding-right: 5px">
<a id="https://en.wikipedia.org/w/index.php?search="
href="https://en.wikipedia.org/w/index.php?search=test" name="external_link_0"
style="display: block" rel="noreferrer noopener" target="_self">
<img onerror="imgError(this)" src="/static/images/favicons/wikipedia.ico"
name="external_icon_0'" class="external-icon" />
<span hidden="" class="external-text" name="external_text_0"
style="font-size: 0.95rem; display: inline">
Wikipedia Results
</span>
</a>
</p>
<span class="clickableIcon" onclick="removeExternalClickable(this)" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 6px;
min-width: 0.5em;
">
<img class="handle fa-ellipsis-v" src="/static/images/fa/ellipsis-v.svg" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 12px;
min-width: 0.5em;
width: 16px;
height: 16px;
" />
</span>
</header>
</div>
<div class="card" name="external_card" style="margin-bottom: 0px">
<header class="card-header">
<p class="card-header-title" style="font-weight: normal; padding-right: 5px">
<a id="https://duckduckgo.com/?q=" href="https://duckduckgo.com/?q=test" name="external_link_1"
style="display: block" rel="noreferrer noopener" target="_self">
<img onerror="imgError(this)" src="/static/images/favicons/duckduckgo.ico"
name="external_icon_1'" class="external-icon" />
<span hidden="" class="external-text" name="external_text_1"
style="font-size: 0.95rem; display: inline">
DuckDuckGo Results
</span>
</a>
</p>
<span class="clickableIcon" onclick="removeExternalClickable(this)" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 6px;
min-width: 0.5em;
">
<img class="handle fa-ellipsis-v" src="/static/images/fa/ellipsis-v.svg" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 12px;
min-width: 0.5em;
width: 16px;
height: 16px;
" />
</span>
</header>
</div>
<div class="card" name="external_card" style="margin-bottom: 0px">
<header class="card-header">
<p class="card-header-title" style="font-weight: normal; padding-right: 5px">
<a id="https://www.google.com/search?q=" href="https://www.google.com/search?q=test"
name="external_link_2" style="display: block" rel="noreferrer noopener" target="_self">
<img onerror="imgError(this)" src="/static/images/favicons/google.ico"
name="external_icon_2'" class="external-icon" />
<span hidden="" class="external-text" name="external_text_2"
style="font-size: 0.95rem; display: inline">
Google Results
</span>
</a>
</p>
<span class="clickableIcon" onclick="removeExternalClickable(this)" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 6px;
min-width: 0.5em;
">
<img class="handle fa-ellipsis-v" src="/static/images/fa/ellipsis-v.svg" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 12px;
min-width: 0.5em;
width: 16px;
height: 16px;
" />
</span>
</header>
</div>
<div class="card" name="external_card" style="margin-bottom: 0px">
<header class="card-header">
<p class="card-header-title" style="font-weight: normal; padding-right: 5px">
<a id="https://www.youtube.com/results?search_query="
href="https://www.youtube.com/results?search_query=test" name="external_link_3"
style="display: block" rel="noreferrer noopener" target="_self">
<img onerror="imgError(this)" src="/static/images/favicons/youtube.svg"
name="external_icon_3'" class="external-icon" />
<span hidden="" class="external-text" name="external_text_3"
style="font-size: 0.95rem; display: inline">
Youtube Results
</span>
</a>
</p>
<span class="clickableIcon" onclick="removeExternalClickable(this)" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 6px;
min-width: 0.5em;
">
<img class="handle fa-ellipsis-v" src="/static/images/fa/ellipsis-v.svg" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 12px;
min-width: 0.5em;
width: 16px;
height: 16px;
" />
</span>
</header>
</div>
<div class="card" name="external_card" style="margin-bottom: 0px">
<header class="card-header">
<p class="card-header-title" style="font-weight: normal; padding-right: 5px">
<a id="https://www.amazon.com/s?k=" href="https://www.amazon.com/s?k=test"
name="external_link_4" style="display: block" rel="noreferrer noopener" target="_self">
<img onerror="imgError(this)" src="/static/images/favicons/amazon.ico"
name="external_icon_4'" class="external-icon" />
<span hidden="" class="external-text" name="external_text_4"
style="font-size: 0.95rem; display: inline">
Amazon Results
</span>
</a>
</p>
<span class="clickableIcon" onclick="removeExternalClickable(this)" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 6px;
min-width: 0.5em;
">
<img class="handle fa-ellipsis-v" src="/static/images/fa/ellipsis-v.svg" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 12px;
min-width: 0.5em;
width: 16px;
height: 16px;
" />
</span>
</header>
</div>
<div class="card" name="external_card" style="margin-bottom: 0px">
<header class="card-header">
<p class="card-header-title" style="font-weight: normal; padding-right: 5px">
<a id="https://twitter.com/search?q=" href="https://twitter.com/search?q=test"
name="external_link_5" style="display: block" rel="noreferrer noopener" target="_self">
<img onerror="imgError(this)" src="/static/images/favicons/twitter.png"
name="external_icon_5'" class="external-icon" />
<span hidden="" class="external-text" name="external_text_5"
style="font-size: 0.95rem; display: inline">
Twitter Results
</span>
</a>
</p>
<span class="clickableIcon" onclick="removeExternalClickable(this)" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 6px;
min-width: 0.5em;
">
<img class="handle fa-ellipsis-v" src="/static/images/fa/ellipsis-v.svg" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 12px;
min-width: 0.5em;
width: 16px;
height: 16px;
" />
</span>
</header>
</div>
<div class="card" name="external_card" style="margin-bottom: 0px">
<header class="card-header">
<p class="card-header-title" style="font-weight: normal; padding-right: 5px">
<a id="https://www.reddit.com/search/?q=" href="https://www.reddit.com/search/?q=test"
name="external_link_6" style="display: block" rel="noreferrer noopener" target="_self">
<img onerror="imgError(this)" src="/static/images/favicons/reddit.ico"
name="external_icon_6'" class="external-icon" />
<span hidden="" class="external-text" name="external_text_6"
style="font-size: 0.95rem; display: inline">
Reddit Results
</span>
</a>
</p>
<span class="clickableIcon" onclick="removeExternalClickable(this)" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 6px;
min-width: 0.5em;
">
<img class="handle fa-ellipsis-v" src="/static/images/fa/ellipsis-v.svg" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 12px;
min-width: 0.5em;
width: 16px;
height: 16px;
" />
</span>
</header>
</div>
<div class="card" name="external_card" style="margin-bottom: 0px">
<header class="card-header">
<p class="card-header-title" style="font-weight: normal; padding-right: 5px">
<a id="https://github.com/search?q=" href="https://github.com/search?q=test"
name="external_link_7" style="display: block" rel="noreferrer noopener" target="_self">
<img onerror="imgError(this)" src="/static/images/favicons/github.ico"
name="external_icon_7'" class="external-icon" />
<span hidden="" class="external-text" name="external_text_7"
style="font-size: 0.95rem; display: inline">
GitHub Results
</span>
</a>
</p>
<span class="clickableIcon" onclick="removeExternalClickable(this)" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 6px;
min-width: 0.5em;
">
<img class="handle fa-ellipsis-v" src="/static/images/fa/ellipsis-v.svg" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 12px;
min-width: 0.5em;
width: 16px;
height: 16px;
" />
</span>
</header>
</div>
<div class="card" name="external_card" style="margin-bottom: 0px">
<header class="card-header">
<p class="card-header-title" style="font-weight: normal; padding-right: 5px">
<a id="https://www.reuters.com/search/news?blob="
href="https://www.reuters.com/search/news?blob=test" name="external_link_8"
style="display: block" rel="noreferrer noopener" target="_self">
<img onerror="imgError(this)" src="/static/images/favicons/reuters.ico"
name="external_icon_8'" class="external-icon" />
<span hidden="" class="external-text" name="external_text_8"
style="font-size: 0.95rem; display: inline">
Reuters Results
</span>
</a>
</p>
<span class="clickableIcon" onclick="removeExternalClickable(this)" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 6px;
min-width: 0.5em;
">
<img class="handle fa-ellipsis-v" src="/static/images/fa/ellipsis-v.svg" style="
cursor: pointer;
display: block;
margin: auto;
margin-right: 12px;
min-width: 0.5em;
width: 16px;
height: 16px;
" />
</span>
</header>
</div>
</div>
<div id="externals_menu">
<div class="card is-fullwidth" id="plus-minus-info" style="
margin-top: 0.5rem;
margin-bottom: 0px;
height: 2.5rem;
padding: 0.7rem;
">
<div class="columns is-vcentered is-mobile">
<div class="column" style="text-align: center">
<button class="button is-small" id="external_add" style="
border-color: transparent;
font-family: BlinkMacSystemFont, -apple-system, 'Segoe UI',
Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans',
'Helvetica Neue', Helvetica, Arial, sans-serif;
">
<img src="/static/images/fa/plus.svg" class="cl-external-link-img" />
</button>
</div>
<div class="column is-narrow" style="text-align: center">|</div>
<div class="column" style="text-align: center">
<button class="button is-small" id="external_minus" style="
border-color: transparent;
font-family: BlinkMacSystemFont, -apple-system, 'Segoe UI',
Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans',
'Helvetica Neue', Helvetica, Arial, sans-serif;
">
<img src="/static/images/fa/minus.svg" class="cl-external-link-img" />
</button>
</div>
<div class="column is-narrow" style="text-align: center">|</div>
<div class="column" style="text-align: center">
<div id="external-info-button" style="text-align: center">
<div style="cursor: pointer" id="externals_layout_button"
onclick="toggleExternalsLayout();">
<img id="id-externals-layout-img" class="cl-external-link-img"
src="/static/images/fa/list.svg" />
</div>
</div>
</div>
</div>
</div>
<div id="external_link_add_box" style="margin: 0.5rem" hidden="">
<div class="box is-fullwidth is" style="margin: 0; padding: 0.5rem">
<input class="input is-small" id="new_external_label" placeholder="Search Label" style="
margin: 0.25rem;
font-family: BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans',
'Helvetica Neue', Helvetica, Arial, sans-serif;
" />
<input class="input is-small" id="new_external_link"
placeholder="Search Link (https://is.com/results?q=)" style="
margin: 0.25rem;
font-family: BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans',
'Helvetica Neue', Helvetica, Arial, sans-serif;
" />
<input class="input is-small" id="new_external_icon"
placeholder="Icon Link (https://is.com/img.png)" style="
margin: 0.25rem;
font-family: BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans',
'Helvetica Neue', Helvetica, Arial, sans-serif;
" />
</div>
<button class="button is-small is-outlined" id="external_input_add" style="
float: right;
margin: 0.5rem;
font-family: BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans',
'Helvetica Neue', Helvetica, Arial, sans-serif;
">
Add
</button>
<button class="button is-small is-outlined is-danger" id="external_input_cancel" style="
float: right;
margin-top: 0.5rem;
font-family: BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans',
'Helvetica Neue', Helvetica, Arial, sans-serif;
">
Cancel
</button>
</div>
<div id="external_input_defaults" hidden="">
<button class="button is-small is-outlined is-danger" id="external_input_defaults_button" style="
float: right;
margin-top: 0.5rem;
font-family: BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans',
'Helvetica Neue', Helvetica, Arial, sans-serif;
">
Reset
</button>
</div>
</div>
<br />
</div>
</div>

View File

@ -0,0 +1,161 @@
// public facetedSearchOPEN(
// suggestion: Suggestion | string,
// activeFilterCategories: ActiveFilterCategories,
// openCore: string,
// openHost: string,
// start?: string, // Starting page
// ): Observable<OpenSearchResponse> {
// // OpenSearch endpoint
// const host = openHost;
// const path = "/" + openCore + "/_search";
// const base = host + path;
// // Constructing Filters Based on Active Filter Categories
// const filters = Object.entries(activeFilterCategories).map(([category, values]) => {
// if (category === "language" || category === "year") {
// return { terms: { [category]: values } };
// } else {
// return { terms: { [`${category}.keyword`]: values } };
// }
// });
// console.log("filters:", filters);
// // Determine search term and query fields based on the suggestion type
// let query;
// if (typeof suggestion === "string") { // If suggestion is a string, append a wildcard (*) for partial matches
// const lowercaseTerm = suggestion.toLowerCase()
// query = {
// bool: {
// should: [
// { match: { title: { query: suggestion, fuzziness: "AUTO", boost: 3 } } },
// { match: { author: { query: suggestion, fuzziness: "AUTO", boost: 2 } } },
// { match: { subjects: { query: suggestion, fuzziness: "AUTO", boost: 1 } } },
// { wildcard: { title: { value: `${lowercaseTerm}*`, boost: 3 } } },
// { wildcard: { author: { value: `${lowercaseTerm}*`, boost: 2 } } },
// { wildcard: { subjects: { value: `${lowercaseTerm}*`, boost: 1 } } }
// ],
// minimum_should_match: 1
// }
// };
// } else if (suggestion instanceof Suggestion) { // If suggestion is a Suggestion object, form a specific query
// query = {
// match: {
// [suggestion.type]: {
// query: suggestion.value,
// operator: 'and' // all the terms in the query must be present in the field e.g. if is a title, the complete title
// }
// }
// };
// }
// // Set default value for start if not provided
// const startValue = start ? parseInt(start) : 0;
// // console.log(activeFilterCategories);
// // console.log("mainQuery:", mainQuery);
// // Construct the body of the OpenSearch query
// const body = {
// query: {
// bool: {
// must: [
// mainQuery, // Ensure the main query must be satisfied
// ...filters // Ensure all filters must be satisfied
// ]
// }
// },
// // // WORKS // Expected: 12 results
// // query: {
// // bool: {
// // "must": [
// // { "term": { "language": "en" } },
// // { "term": { "subjects.keyword": "Lower Austria" } },
// // { "term": { "subjects.keyword": "counting data" } }
// // ],
// // }
// // },
// // // THIS WORKS: // Expected: 19 results
// // query: {
// // bool: {
// // must: [
// // { "match": { "title": "Blatt" } },
// // { "term": { "subjects": "bayern" } }
// // ],
// // }
// // },
// // // WORKS // Expected: 4 results
// // query: {
// // bool: {
// // "must": [
// // { "match": { "title": "blatt" } },
// // { "term": { "subjects": "bayern" } },
// // { "term": { "subjects": "salzburg" } }
// // ],
// // }
// // },
// // // WORKS // Expected: 2 results
// // query: {
// // bool: {
// // "must": [
// // { "match": { "title": "blatt" } },
// // { "term": { "subjects": "ungarn" } },
// // { "term": { "subjects": "steiermark" } }
// // ],
// // }
// // },
// // WORKS // Expected: 12 results
// query: {
// bool: {
// "must": [
// { "term": { "language": "en" } },
// { "term": { "subjects.keyword": "Lower Austria" } },
// { "term": { "subjects.keyword": "counting data" } }
// ],
// "should": [
// { match: { title: { query: "halger", fuzziness: "AUTO", boost: 3 } } },
// { match: { author: { query: "halger", fuzziness: "AUTO", boost: 2 } } },
// { match: { subjects: { query: "halger", fuzziness: "AUTO", boost: 1 } } },
// { wildcard: { title: { value: "halger", boost: 3 } } },
// { wildcard: { author: { value: "halger", boost: 2 } } },
// { wildcard: { subjects: { value: "halger", boost: 1 } } }
// ],
// minimum_should_match: 1
// }
// },
// size: 10,
// from: startValue,
// sort: [{ _score: { order: "desc" } }],
// track_scores: true,
// aggs: {
// subjects: { terms: { field: "subjects.keyword", size: 1000 } },
// language: { terms: { field: "language" } },
// author: { terms: { field: "author.keyword", size: 1000 } },
// year: { terms: { field: "year", size: 100 } }
// },
// highlight: {
// fields: {
// title: {},
// author: {},
// subjects: {}
// }
// }
// };
// console.log("mainQuery:", mainQuery);
// console.log("filters:", filters);
// console.log("body:", body);
// // Make API call to OpenSearch and return the result
// const stations = api.post<OpenSearchResponse>(base, body);
// return stations;
// }

View File

@ -1,361 +1,386 @@
import api from "../api/api"; import api from "../api/api";
// import { Observable, of } from "rxjs";
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { map } from "rxjs/operators"; import { tap, map } from "rxjs/operators";
import { Dataset, DbDataset, Suggestion } from "@/models/dataset"; import { Dataset, DbDataset, Suggestion } from "@/models/dataset";
import { SolrResponse } from "@/models/headers"; import { HitHighlight, OpenSearchResponse, SolrResponse } from "@/models/headers";
import { ActiveFilterCategories } from "@/models/solr"; import { ActiveFilterCategories } from "@/models/solr";
import { VUE_APP_PORTAL } from "@/constants"; import { VUE_API } from "@/constants";
// import { deserialize, instanceToInstance } from "class-transformer";
import { deserialize } from "class-transformer"; import { deserialize } from "class-transformer";
// import { OAI_DATASETS } from "./mock-oai-datasets";
// import { OaiDataset, OaiPerson } from "@/models/oai";
// import xml2js from "xml2js";
class DatasetService { class DatasetService {
// for the autocomplete search /**
public searchTerm(term: string, solrCore: string, solrHost: string): Observable<Dataset[]> { * Search datasets with OpenSearch API, allowing for fuzzy search and boosting relevance in title, author, and subject fields.
// solr endpoint * @param {string} searchTerm - Search query term
// const host = 'http://voyagerdemo.com/'; * @param {string} openCore - The OpenSearch core to search in
// const host = 'https://www.tethys.at/';'' * @param {string} openHost - The OpenSearch host URL
const host = "https://" + solrHost; * @returns {Observable} - Observable emitting datasets and their highlights
const path = "/solr/" + solrCore + "/select?"; */
// const base = "https://geomon.geologie.ac.at/52n-sos-webapp/api/features"; //host + path; public searchTerm(term: string, openCore: string, openHost: string): Observable<{ datasets: Dataset[], highlights: HitHighlight[] }> {
const base = host + path;
//const fields = 'id,server_date_published,abstract_output,title_output,title_additional,author,subject'; // fields we want returned const host = openHost; // OpenSearch host URL
const fields = [ const path = "/" + openCore + "/_search"; // API endpoint for searching
"id", const base = host + path; // Complete URL for the request
"licence", /**
"server_date_published", * The match query used for title, author, and subjects fields is case-insensitive by default. The standard analyzer is typically used, which lowercases the terms.
"abstract_output", * The wildcard query is case-sensitive by default. To make it case-insensitive, it is needed to use a lowercase filter */
"title_output", const lowercaseTerm = term.toLowerCase(); // Lowercase the search term
"title_additional",
"author",
"subject",
"doctype",
].toString();
//var dismaxFields = "title^3 abstract^2 subject^1"; // Request body defining search query logic
const qfFields = "title^3 author^2 subject^1"; const body = {
// let params = "fl=" + fields; query: {
// // if (term == "*%3A*") { // *: bool: {
// // params += "&defType=edismax&wt=json&indent=on"; //edismax should: [
// // } else { { match: { title: { query: term, fuzziness: "AUTO", boost: 3 } } },
// params += "&defType=edismax&qf=" + qfFields + "&wt=json&indent=on"; //dismax { match: { author: { query: term, fuzziness: "AUTO", boost: 2 } } },
// // } { match: { subjects: { query: term, fuzziness: "AUTO", boost: 1 } } }, // In SOLR is "subject"!
{ match: { doctype: { query: term, fuzziness: "AUTO", boost: 1 } } }, // doctype
// const query = "&q=" + term + "*"; { wildcard: { title: { value: `${lowercaseTerm}*`, boost: 3 } } },
// const apiU = base + params + query; { wildcard: { author: { value: `${lowercaseTerm}*`, boost: 2 } } },
{ wildcard: { subjects: { value: `${lowercaseTerm}*`, boost: 1 } } }, // In SOLR is "subject"!
const q_params = { { wildcard: { doctype: { value: `${lowercaseTerm}*`, boost: 1 } } } // doctype
"0": "fl=" + fields, ],
q: term + "*", minimum_should_match: 1 // Require at least one match
defType: "edismax", }
qf: qfFields, },
indent: "on", size: 10, // Limit to 10 results
wt: "json", from: 0, // Pagination: start from the first result
sort: [{ _score: { order: "desc" } }], // Sort by relevance (_score)
// sort: [{ server_date_published: { order: "desc" } }],
track_scores: true, // This ensures "_score" is included even when sorting by other criteria. Otherwise the relevance score is not calculated
aggs: {
subjects: { terms: { field: "subjects.keyword", size: 1000 } }, // In SOLR is "subject"!
language: { terms: { field: "language" } }, // << ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS
author: { terms: { field: "author.keyword", size: 1000 } },
year: { terms: { field: "year", size: 100 } }, // << ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS
doctype: { terms: { field: "doctype", size: 50 } } // << ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS
},
highlight: {
fields: {
title: {}, // Highlight matching terms in title
author: {}, // Highlight matching terms in author
subjects: {}, // Highlight matching terms in subjects
doctype: {} // Highlight matching terms in document type
}
}
}; };
const stations = api.get<SolrResponse>(base, q_params).pipe(map((res: SolrResponse) => res.response.docs)); /**
* Make API call to OpenSearch and return the result
return stations; * When a POST request is made to the OpenSearch server using the api.post<OpenSearchResponse> method, the response received from OpenSearch is an object that includes various details about the search results.
* One of the key properties of this response object is _source, which is an array of documents (datasets) that match the search criteria.
* It is used the pipe method to chain RxJS operators to the Observable returned by api.get. The map operator is used to transform the emitted items of the Observable.
*/
return api.post<OpenSearchResponse>(base, body).pipe(
map(response => ({
datasets: response.hits.hits.map(hit => hit._source),
highlights: response.hits.hits.map(hit => hit.highlight)
}))
);
} }
// // For the autocomplete search. Method to perform a search based on a term
// public searchTermSOLR(term: string, solrCore: string, solrHost: string): Observable<Dataset[]> {
// // SOLR endpoint
// const host = "https://" + solrHost;
// const path = "/solr/" + solrCore + "/select?";
// const base = host + path;
// //const fields = 'id,server_date_published,abstract_output,title_output,title_additional,author,subject'; // fields we want returned
// const fields = [
// "id",
// "licence",
// "server_date_published",
// "abstract_output",
// "title_output",
// "title_additional",
// "author",
// "subject",
// "doctype",
// ].toString();
// const qfFields = "title^3 author^2 subject^1";
// const q_params = {
// "0": "fl=" + fields,
// q: term + "*",
// defType: "edismax",
// qf: qfFields,
// indent: "on",
// wt: "json",
// };
// // Make API call to Solr and return the result
// /**
// * When a GET request is made to the Solr server using the api.get<SolrResponse> method, the response received from Solr is an object that includes various details about the search results.
// * One of the key properties of this response object is docs, which is an array of documents (datasets) that match the search criteria.
// * It is used the pipe method to chain RxJS operators to the Observable returned by api.get. The map operator is used to transform the emitted items of the Observable.
// */
// const stations = api.get<SolrResponse>(base, q_params).pipe(map((res: SolrResponse) => res.response.docs));
// return stations;
// }
/**
* Perform faceted search with OpenSearch API using filters and suggestions
* @param {Suggestion | string} suggestion - Search term or suggestion
* @param {ActiveFilterCategories} activeFilterCategories - Active filters to apply
* @param {string} openCore - The OpenSearch core to search in
* @param {string} openHost - The OpenSearch host URL
* @param {string} start - Optional: starting page
* @returns {Observable<OpenSearchResponse>} - Observable emitting search results
*/
public facetedSearch( public facetedSearch(
suggestion: Suggestion | string, suggestion: Suggestion | string,
activeFilterCategories: ActiveFilterCategories, activeFilterCategories: ActiveFilterCategories,
solrCore: string, openCore: string,
solrHost: string, openHost: string,
start?: string, start?: string, // Starting page
): Observable<SolrResponse> { ): Observable<OpenSearchResponse> {
// solr endpoint const host = openHost;
// const host = 'http://voyagerdemo.com/'; const path = "/" + openCore + "/_search";
//const host = 'https://www.tethys.at/';
//const host = 'https://' + solrHost;
const host = "https://" + solrHost;
const path = "/solr/" + solrCore + "/select?";
const base = host + path; const base = host + path;
//const fields = 'id,server_date_published,abstract_output,title_output,title_additional,author,subject'; // fields we want returned const lowercaseTerm = typeof suggestion === 'string' ? suggestion.toLowerCase() : suggestion.value;
const fields = [
"id",
"licence",
"server_date_published",
"abstract_output",
"identifier",
"title_output",
"title_additional",
"author",
"subject",
"doctype",
].toString();
// const qfFields = "title^3 author^2 subject^1"; /**
// let params = "fl=" + fields; * The query construction depends on whether the suggestion is a string or a Suggestion object.
// if (term == "*%3A*") { * */
// params += "&defType=edismax&wt=json&indent=on"; //edismax
// } else {
// params += "&defType=dismax&qf=" + qfFields + "&wt=json&indent=on"; //dismax
// }
let term, queryOperator, qfFields; // When suggestion is a string:
if (typeof suggestion === "string") { const mainQuery = typeof suggestion === 'string'
term = suggestion + "*"; ? {
queryOperator = "or"; bool: {
qfFields = "title^3 author^2 subject^1"; should: [
} else if (suggestion instanceof Suggestion) { { match: { title: { query: suggestion, fuzziness: "AUTO", boost: 3 } } },
term = suggestion.type + ':"' + suggestion.value + '"'; { match: { author: { query: suggestion, fuzziness: "AUTO", boost: 2 } } },
queryOperator = "and"; { match: { subjects: { query: suggestion, fuzziness: "AUTO", boost: 1 } } },
qfFields = undefined; { match: { doctype: { query: suggestion, fuzziness: "AUTO", boost: 1 } } },
{ wildcard: { title: { value: `${lowercaseTerm}*`, boost: 3 } } },
{ wildcard: { author: { value: `${lowercaseTerm}*`, boost: 2 } } },
{ wildcard: { subjects: { value: `${lowercaseTerm}*`, boost: 1 } } },
{ wildcard: { doctype: { value: `${lowercaseTerm}*`, boost: 1 } } }
],
minimum_should_match: 1
} }
if (start === undefined) start = "0";
// start = "&start=" + start;
// const facetFields =
// "&facet=on&facet.field=language&facet.field={!key=datatype}doctype&facet.field=subject"; //&fq=year:(2019)";//&facet.query=year:2018";
const filterFields = new Array<string>();
if (Object.keys(activeFilterCategories).length > 0) {
// filterFields = '["';
// const filterFields: string[] = [];
// const facet_fields: FacetFields = res.facets;
let prop: keyof typeof activeFilterCategories;
for (prop in activeFilterCategories) {
const filterItems = activeFilterCategories[prop];
// const filterItems = facetCategory.values;
// filterItems.forEach((filterItem) => {
// console.log(`${key} ${valueArray}`);
filterItems.forEach(function (value: string) {
// filterFields += "&fq=" + key + ':("' + value + '")';
filterFields.push(prop + ':("' + value + '")');
// filterFields += prop + ":" + value;
});
} }
// filterFields += '"]'; // When suggestion is a suggestion object:
: {
match: {
[suggestion.type]: {
query: suggestion.value,
operator: 'and' // all the terms in the query must be present in the field
}
} }
// const query = "&sort=server_date_published desc" + "&q=" + term;
// const api =
// base + params + limit + start + query + filterFields + facetFields;
// https://solr.apache.org/guide/8_4/json-request-api.html
const q_params = {
"0": "fl=" + fields,
q: term,
"q.op": queryOperator,
defType: "edismax",
qf: qfFields,
// df: "title",
indent: "on",
wt: "json",
rows: 10,
// fq: ["subject:Steiermark", "language:de"],
fq: filterFields,
start: start,
sort: "server_date_published desc",
facet: "on",
// "facet.field": "language",
"json.facet.language": '{ type: "terms", field: "language" }',
"json.facet.subject": '{ type: "terms", field: "subject", limit: -1 }',
"json.facet.year": '{ type: "terms", field: "year" }',
"json.facet.author": '{ type: "terms", field: "author_facet", limit: -1 }',
}; };
const stations = api.get<SolrResponse>(base, q_params); // Build filters based on the active filter categories
// .pipe(map((res) => res.response.docs)); const filters = Object.entries(activeFilterCategories).map(([category, values]) => {
if (category === "language" || category === "year" || category === "doctype") {
return values.map(value => ({ term: { [category]: value } }));
} else {
return values.map(value => ({ term: { [`${category}.keyword`]: value } }));
}
}).flat();
// Request body for the faceted search
const body = {
query: {
bool: {
must: [
mainQuery, // Ensure the main query must be satisfied
...filters // Ensure all filters must be satisfied
]
}
},
size: 10,
from: start ? parseInt(start) : 0,
sort: [{ server_date_published: { order: "desc" } }], // Sort by publication date
// sort: [{ _score: { order: "desc" } }], // Sort by _score in descending order
track_scores: true,
/**
* Defines aggregations for facets
* terms: Aggregation type that returns the most common terms in a field.
* !For a large number of terms setting an extremely large size might not be efficient
* If you genuinely need all unique terms and expect a large number of them, consider using a composite aggregation for more efficient pagination of terms.
*/
aggs: {
subjects: { terms: { field: "subjects.keyword", size: 1000 } }, // In SOLR is "subject"!
language: { terms: { field: "language" } }, // ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS
author: { terms: { field: "author.keyword", size: 1000 } },
year: { terms: { field: "year", size: 100 } }, // ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS
doctype: { terms: { field: "doctype", size: 50 } } // ".keyword" HAS TO BE REMOVED. OTHERWISE BUCKETS ARE NOT OBTAINED FOR THIS
},
highlight: {
fields: {
title: {},
author: {},
subjects: {},
doctype: {}
}
}
};
// API call and return observable of search results
const stations = api.post<OpenSearchResponse>(base, body);
return stations; return stations;
} }
// /**
// * This method performs a faceted search on a Solr core. Faceted search allows the user to filter search results based on various categories (facets)
// */
// public facetedSearchSOLR(
// suggestion: Suggestion | string,
// activeFilterCategories: ActiveFilterCategories,
// solrCore: string,
// solrHost: string,
// start?: string, // Starting page
// ): Observable<SolrResponse> {
// // console.log("face:", suggestion);
// // console.log(activeFilterCategories);
// // console.log(solrCore);
// // console.log(solrHost);
// // console.log(start);
// console.log("facetedsearchSOLR > suggestion entered:");
// console.log(suggestion);
// // Construct Solr query parameters
// const host = "https://" + solrHost;
// const path = "/solr/" + solrCore + "/select?";
// const base = host + path;
// const fields = [
// "id",
// "licence",
// "server_date_published",
// "abstract_output",
// "identifier",
// "title_output",
// "title_additional",
// "author",
// "subject",
// "doctype",
// ].toString();
// // Determine search term, query operator, and query fields based on the suggestion type. Depending on whether suggestion is a string or a Suggestion object, it constructs the search term and query fields differently.
// let term, queryOperator, qfFields;
// if (typeof suggestion === "string") { // f suggestion is a string, it appends a wildcard (*) for partial matches.
// term = suggestion + "*";
// queryOperator = "or";
// qfFields = "title^3 author^2 subject^1";
// } else if (suggestion instanceof Suggestion) { // If suggestion is a Suggestion object, it forms a more specific query based on the type and value of the suggestion.
// term = suggestion.type + ':"' + suggestion.value + '"';
// queryOperator = "and";
// qfFields = undefined;
// }
// // Set default value for start if not provided
// if (start === undefined) start = "0";
// // Construct filter fields based on active filter categories
// const filterFields = new Array<string>();
// if (Object.keys(activeFilterCategories).length > 0) {
// /* Declare variable prop with a type that is a key of the activeFilterCategories. The 'keyof typeof' activeFilterCategories type represents all possible keys
// that can exist on the activeFilterCategories --> prop can only be assigned a value that is a key of the activeFilterCategories object */
// let prop: keyof typeof activeFilterCategories;
// for (prop in activeFilterCategories) {
// const filterItems = activeFilterCategories[prop];
// filterItems.forEach(function (value: string) {
// filterFields.push(prop + ':("' + value + '")');
// // e.g. Array [ 'subject:("Vektordaten")', 'author:("GeoSphere Austria, ")' ]
// });
// }
// }
// // https://solr.apache.org/guide/8_4/json-request-api.html
// // Construct Solr query parameters
// const q_params = {
// "0": "fl=" + fields,
// q: term,
// "q.op": queryOperator,
// defType: "edismax",
// qf: qfFields,
// // df: "title",
// indent: "on",
// wt: "json",
// rows: 10,
// // fq: ["subject:Steiermark", "language:de"],
// fq: filterFields,
// start: start,
// sort: "server_date_published desc",
// facet: "on",
// // "facet.field": "language",
// "json.facet.language": '{ type: "terms", field: "language" }',
// "json.facet.subject": '{ type: "terms", field: "subject", limit: -1 }',
// "json.facet.year": '{ type: "terms", field: "year" }',
// "json.facet.author": '{ type: "terms", field: "author_facet", limit: -1 }',
// };
// /* E.g.
// {"0":"fl=id,licence,server_date_published,abstract_output,identifier,title_output,title_additional,author,subject,doctype","q":"*","q.op":"or","defType":"edismax",
// "qf":"title^3 author^2 subject^1",
// "indent":"on","wt":"json","rows":10,
// "fq":["subject:(\"Vektordaten\")","author:(\"GeoSphere Austria, \")"],
// "start":"0","sort":"server_date_published desc","facet":"on",
// "json.facet.language":"{ type: \"terms\", field: \"language\" }",
// "json.facet.subject":"{ type: \"terms\", field: \"subject\", limit: -1 }",
// "json.facet.year":"{ type: \"terms\", field: \"year\" }",
// "json.facet.author":"{ type: \"terms\", field: \"author_facet\", limit: -1 }"}
// */
// // console.log(JSON.stringify(q_params));
// // Make API call to Solr and return the result
// const stations = api.get<SolrResponse>(base, q_params);
// return stations;
// }
// Method to fetch years
public getYears(): Observable<string[]> { public getYears(): Observable<string[]> {
// const heroes = of(HEROES); const host = VUE_API;
// const host = "https:" + VUE_APP_PORTAL;
const host = VUE_APP_PORTAL;
const path = "/api/years"; const path = "/api/years";
const base = host + path; const base = host + path;
const years = api.get<string[]>(base); const years = api.get<string[]>(base);
// this.messageService.add('HeroService: fetched heroes');
return years; return years;
} }
// Method to fetch documents for a specific year
public getDocuments(year: string): Observable<Array<DbDataset>> { public getDocuments(year: string): Observable<Array<DbDataset>> {
// const host = "https:" + VUE_APP_PORTAL; const host = VUE_API;
const host = VUE_APP_PORTAL;
const path = "/api/sitelinks/" + year; const path = "/api/sitelinks/" + year;
const base = host + path; const base = host + path;
const documents: Observable<DbDataset[]> = api.get<Array<DbDataset>>(base); const documents: Observable<DbDataset[]> = api.get<Array<DbDataset>>(base);
// this.messageService.add('HeroService: fetched heroes');
return documents; return documents;
} }
// Method to fetch a dataset by its ID
public getDataset(id: number): Observable<DbDataset> { public getDataset(id: number): Observable<DbDataset> {
// const host = "https:" + VUE_APP_PORTAL; const host = VUE_API;
const host = VUE_APP_PORTAL;
const path = "/api/dataset/" + id; const path = "/api/dataset/" + id;
const apiUrl = host + path; const apiUrl = host + path;
const dataset = api.get<DbDataset>(apiUrl).pipe(map((res) => this.prepareDataset(res)));
// const dataset = api.get<DbDataset>(apiUrl).pipe(map((res) => this.prepareDataset(res, apiUrl)));
// this.messageService.add('HeroService: fetched heroes'); const dataset = api.get<DbDataset>(apiUrl).pipe(map((res) => this.prepareDataset(res)));
return dataset; return dataset;
} }
// Method to fetch a dataset by its DOI
public getDatasetByDoi(doi: string): Observable<DbDataset> { public getDatasetByDoi(doi: string): Observable<DbDataset> {
// const host = "https:" + VUE_APP_PORTAL; const host = VUE_API;
const host = VUE_APP_PORTAL;
const path = "/api/dataset/10.24341/tethys." + doi; const path = "/api/dataset/10.24341/tethys." + doi;
const apiUrl = host + path; const apiUrl = host + path;
const dataset = api.get<DbDataset>(apiUrl).pipe(map((res) => this.prepareDataset(res)));
// const dataset = api.get<DbDataset>(apiUrl).pipe(map((res) => this.prepareDataset(res, apiUrl)));
// this.messageService.add('HeroService: fetched heroes'); const dataset = api.get<DbDataset>(apiUrl).pipe(map((res) => this.prepareDataset(res)));
return dataset; return dataset;
} }
// public getOaiDatasets(): Observable<OaiDataset[]> { // Prepare dataset object by deserializing it and adding a URL
// const apiUrl = "https://data.tethys.at/oai?verb=ListRecords&metadataPrefix=oai_datacite";
// const oaiDatasets = api.get<string>(apiUrl).pipe(
// map(
// (response: string) => {
// // const parser = new DOMParser();
// // const xmlDoc: XMLDocument = parser.parseFromString(response, "application/xml");
// // const xslDoc = parser.parseFromString(this.xsl, "application/xml");
// // const xsltProcessor = new XSLTProcessor();
// // xsltProcessor.importStylesheet(xslDoc);
// // console.log(xmlDoc);
// // const xmlDom = xsltProcessor.transformToDocument(xmlDoc);
// // const serializer = new XMLSerializer();
// // const html = serializer.serializeToString(xmlDom.documentElement);
// // console.log(html);
// // const arrOai = new Array<OaiDataset>();
// // return arrOai;
// const arrOai = this.parseXML(response);
// return arrOai;
// // .then((data) => {
// // return data;
// // });
// },
// // (error: string) => this.errorHandler(error),
// ),
// );
// // const oaiDatasets = of(OAI_DATASETS);
// // this.messageService.add('HeroService: fetched heroes');
// return oaiDatasets;
// }
// private parseXML(xmlStr: string): Array<OaiDataset> {
// // let k = "";
// const arr: OaiDataset[] = [];
// const domParser = new DOMParser();
// const doc = domParser.parseFromString(xmlStr, "application/xml");
// const records = doc.getElementsByTagName("ListRecords")[0];
// // // const rt = xmlNode.resumptionToken;
// // for (let i = 0; i < records.length; i++) {
// // console.log(records[i].getAttribute("name"));
// // }
// const parser: xml2js.Parser = new xml2js.Parser({
// trim: true,
// explicitArray: false,
// ignoreAttrs: false,
// // mergeAttrs: true,
// });
// parser.parseString(records.outerHTML, function (err: Error | null, result: any) {
// const xmlNode = result.ListRecords;
// // const rt = xmlNode.resumptionToken;
// for (const rNode in xmlNode.record) {
// const item = xmlNode.record[rNode];
// const dc = item.metadata.resource;
// const t = dc.titles.title;
// const id = dc.identifier._;
// const lang = "en"; //dc.titles.title.attributes("xml",True)->lang;
// let title: string;
// if (lang == "en" && t.length > 1) {
// title = t[1]._;
// } else {
// title = t[0]._;
// }
// let creator = "";
// if (dc.creators.creator instanceof Array) {
// dc.creators.creator.forEach((person: OaiPerson) => {
// creator += person.creatorName + "; ";
// });
// } else {
// creator += dc.creators.creator.creatorName._;
// }
// let contributor = "";
// if (dc.contributors) {
// if (dc.contributors.contributor instanceof Array) {
// dc.contributors.contributor.forEach((person: OaiPerson) => {
// contributor += person.contributorName + "; ";
// });
// } else {
// contributor += dc.contributors.contributor.contributorName;
// }
// }
// // ?.map((u: any) => u.creatorName._).join("; ");
// // foreach ($dc->creators->creator as $c) {
// // foreach ($c->creatorName as $d) {
// // if (count(explode(',',$d)) > 1) {
// // $creator .= explode(',',$d)[0] . ', ' . substr(explode(',',$d)[1],1,1) . '; ';
// // } else {
// // $creator .= explode(',',$d)[0];
// // }
// // }
// // }
// const north = dc.geoLocations.geoLocation.geoLocationBox.northBoundLatitude;
// const east = dc.geoLocations.geoLocation.geoLocationBox.eastBoundLongitude;
// const south = dc.geoLocations.geoLocation.geoLocationBox.southBoundLatitude;
// const west = dc.geoLocations.geoLocation.geoLocationBox.westBoundLongitude;
// const subject = dc.subjects.subject.map((u: any) => u._).join(", ");
// const oaiDataset = {
// doi: id,
// title: title,
// creator: creator,
// contributor: contributor,
// subject: subject,
// north: north,
// south: south,
// east: east,
// west: west,
// } as OaiDataset;
// arr.push(oaiDataset);
// }
// // resolve(arr);
// });
// return arr;
// }
// private prepareOAI(xml: any) : Array<OaiDataset> {
// //
// }
// private prepareDataset(datasetObj: DbDataset, apiUrl: string): DbDataset {
private prepareDataset(datasetObj: DbDataset): DbDataset { private prepareDataset(datasetObj: DbDataset): DbDataset {
const dataset = deserialize<DbDataset>(DbDataset, JSON.stringify(datasetObj)); const dataset = deserialize<DbDataset>(DbDataset, JSON.stringify(datasetObj));
dataset.url = document.documentURI; dataset.url = document.documentURI;
// this.internalDatasetId.generateInternalId(dataset);
// if (dataset.seriesParameters) {
// dataset.parameters = dataset.seriesParameters;
// delete dataset.seriesParameters;
// }
return dataset; return dataset;
} }
} }

View File

@ -6,7 +6,8 @@
<p class="lead">Want to keep updated or need further information?</p> <p class="lead">Want to keep updated or need further information?</p>
<hr class="center-line" /> <hr class="center-line" />
</div> </div>
<div class="column is-half contact-us-details" style="padding-top: 0; margin-top: 0"> <div class="columns">
<div class="column contact-us-details" style="padding-top: 0; margin-top: 0">
<h3>Our Location</h3> <h3>Our Location</h3>
<h5> <h5>
GeoSphere Austria <br /> GeoSphere Austria <br />
@ -16,6 +17,7 @@
<i class="fas fa-envelope-open-text"></i> <i class="fas fa-envelope-open-text"></i>
<a class="social-media" href="mailto:repository@geologie.ac.at"> repository@geologie.ac.at</a><br /> --> <a class="social-media" href="mailto:repository@geologie.ac.at"> repository@geologie.ac.at</a><br /> -->
</h5> </h5>
<ul class="social-links"> <ul class="social-links">
<li><i class="fas fa-phone-alt pr-2"></i> +43-1-7125674 <br /></li> <li><i class="fas fa-phone-alt pr-2"></i> +43-1-7125674 <br /></li>
<li> <li>
@ -43,6 +45,17 @@
</li> </li>
</ul> </ul>
</div> </div>
<div class="column contact-us-details" style="padding-top: 0; margin-top: 0">
<h3>Contact Person</h3>
<h5>
Viktoria Haider <br />
<a class="social-media" href="mailto:repository@geosphere.at"><i class="fas fa-envelope pr-2"></i> repository(at)geosphere.at </a>
<!-- <i class="fas fa-mobile-alt"></i> +43-1-7125674 <br />
<i class="fas fa-envelope-open-text"></i>
<a class="social-media" href="mailto:repository@geologie.ac.at"> repository@geologie.ac.at</a><br /> -->
</h5>
</div>
</div>
</section> </section>
</div> </div>
</template> </template>

View File

@ -1,69 +1,81 @@
// detail-dataset.component.ts
import { Component, Vue, Prop } from "vue-facing-decorator"; import { Component, Vue, Prop } from "vue-facing-decorator";
import { DbDataset } from "@/models/dataset"; import { DbDataset } from "@/models/dataset";
// import { Prop } from "vue-property-decorator";
import DatasetService from "../../services/dataset.service"; import DatasetService from "../../services/dataset.service";
import { Subscription } from "rxjs"; import { Subscription } from "rxjs";
import dayjs from "dayjs"; import dayjs from "dayjs";
import advancedFormat from "dayjs/plugin/advancedFormat"; import advancedFormat from "dayjs/plugin/advancedFormat";
// import SimpleSearchComponent from "@/components/simple-search/simple-search-component.vue";
import VsInput from "@/components/vs-input/vs-input.vue"; import VsInput from "@/components/vs-input/vs-input.vue";
import { Suggestion } from "@/models/dataset"; import { Suggestion } from "@/models/dataset";
import { VUE_APP_PORTAL } from "@/constants"; import { VUE_API } from "@/constants";
// import DataMetricsBadge from "data-metrics-badge/dist/data-metrics-badge.js"; // import DataMetricsBadge from "data-metrics-badge/dist/data-metrics-badge.js";
import DataMetricsBadge from "@/components/datacite/DataMetricsBadge.vue"; // import DataMetricsBadge from "@/components/datacite/DataMetricsBadge.vue";
import Minimap from '@/components/minimap/Minimap.vue';
import * as L from 'leaflet';
@Component({ @Component({
name: "DatasetDetailComponent", name: "DatasetDetailComponent",
components: { components: {
VsInput, VsInput,
DataMetricsBadge, Minimap
// DataMetricsBadge, // Commented out but prepared for future use
}, },
}) })
export default class DatasetDetailComponent extends Vue { export default class DatasetDetailComponent extends Vue {
@Prop() @Prop()
datasetId!: string; datasetId!: string; // datasetId is passed as a prop and is required.
// @Prop() searchTerm: string | Suggestion = ""; // Search term used in the search functionality.
// identifier!: string; private subscriptions: Array<Subscription> = []; // Subscriptions to RxJS observables to prevent memory leaks.
public dataset = {} as DbDataset; // Holds dataset details.
private error: string = ""; // Stores error messages, if any.
public loaded = false; // Indicates whether the dataset is fully loaded.
public openAccessLicences: Array<string> = ["CC-BY-4.0", "CC-BY-SA-4.0"]; // Available open-access licenses.
public portal = VUE_API + "/api/file/download/"; // Portal URL for file downloads.
searchTerm: string | Suggestion = ""; // If needed for stats
// public post = {
private subscriptions: Array<Subscription> = []; // views: 25, // Number of views for the dataset
public dataset = {} as DbDataset; // downloads: 1262, // Number of downloads
private error = ""; // citations: 2424, // Number of citations
public loaded = false; // };
public openAccessLicences: Array<string> = ["CC-BY-4.0", "CC-BY-SA-4.0"];
public portal = VUE_APP_PORTAL + "/api/file/download/";
public post = {
views: 25,
downloads: 1262,
citations: 2424,
};
/**
* Lifecycle hook: Called when the component is created.
* Extends dayjs with advanced format plugin and determines whether to
* fetch dataset by ID or by DOI.
*/
created(): void { created(): void {
dayjs.extend(advancedFormat); dayjs.extend(advancedFormat); // Adds advanced date formatting options to dayjs.
if (!this.datasetId.includes(".")) { if (!this.datasetId.includes(".")) {
// get datset by publish_id // Fetch dataset by publish_id (numeric ID)
this.getDataset(Number(this.datasetId)); this.getDataset(Number(this.datasetId));
} else { } else {
// get datset by doi_value // Fetch dataset by DOI (alphanumeric ID)
this.getDatasetByIdentifier(this.datasetId); this.getDatasetByIdentifier(this.datasetId);
} }
} }
/**
* Lifecycle hook: Called before the component is unmounted.
* Unsubscribes from all subscriptions to prevent memory leaks.
*/
beforeUnmount(): void { beforeUnmount(): void {
//unsunscribe to ensure no memory leaks
// this.subscription.unsubscribe();
for (const subs of this.subscriptions) { for (const subs of this.subscriptions) {
subs.unsubscribe(); subs.unsubscribe();
} }
} }
/**
* Handles search functionality based on user input or suggestion selection.
* Opens a new window or navigates internally based on the host's domain.
* @param suggestion - The suggestion or search term entered by the user.
*/
onSearch(suggestion: Suggestion | string): void { onSearch(suggestion: Suggestion | string): void {
const host = window.location.host; const host = window.location.host;
const parts = host.split("."); const parts = host.split(".");
if (parts[0] === "doi") { if (parts[0] === "doi") {
// If in DOI subdomain, open external search in a new window
let term; let term;
if (typeof suggestion === "string") { if (typeof suggestion === "string") {
term = suggestion; term = suggestion;
@ -74,6 +86,7 @@ export default class DatasetDetailComponent extends Vue {
window.open("https://tethys.at/search/" + term + "/" + type, "_self"); window.open("https://tethys.at/search/" + term + "/" + type, "_self");
} }
} else { } else {
// Otherwise, route internally to search page
let term; let term;
if (typeof suggestion === "string") { if (typeof suggestion === "string") {
term = suggestion; term = suggestion;
@ -83,76 +96,125 @@ export default class DatasetDetailComponent extends Vue {
this.$router.push({ name: "Search", params: { display: term, type: suggestion.type } }); this.$router.push({ name: "Search", params: { display: term, type: suggestion.type } });
} }
} }
// this.searchTerm = suggestion;
// this.$router.push({ name: "Search", params: { display: term, suggestion instanceof Suggestion ? ty} });
} }
/**
* Fetches the dataset details by ID from the service and updates the component state.
* @param id - The dataset's numeric ID.
*/
private getDataset(id: number): void { private getDataset(id: number): void {
const newSub = DatasetService.getDataset(id).subscribe({ const newSub = DatasetService.getDataset(id).subscribe({
next: (res: DbDataset) => { next: (res: DbDataset) => {
this.dataset = res; this.dataset = res; // Store dataset in component state.
this.loaded = true; this.loaded = true; // Mark as loaded.
},
error: (error: string) => {
this.error = error; // Capture any errors during fetch.
}, },
error: (error: string) => this.errorHandler(error),
}); });
this.subscriptions.push(newSub); this.subscriptions.push(newSub); // Add subscription to array to manage unsubscribing later.
} }
/**
* Fetches the dataset details by DOI from the service and updates the component state.
* @param id - The dataset's DOI (Digital Object Identifier).
*/
private getDatasetByIdentifier(id: string): void { private getDatasetByIdentifier(id: string): void {
const newSub = DatasetService.getDatasetByDoi(id).subscribe({ const newSub = DatasetService.getDatasetByDoi(id).subscribe({
next: (res: DbDataset) => { next: (res: DbDataset) => {
this.dataset = res; this.dataset = res; // Store dataset in component state.
this.loaded = true; this.loaded = true; // Mark as loaded.
}, },
error: (error: string) => this.errorHandler(error), error: (error: string) => this.errorHandler(error),
}); });
this.subscriptions.push(newSub); this.subscriptions.push(newSub); // Add subscription to array.
} }
/**
* Handles errors and updates the error message in the component.
* @param err - Error message.
*/
private errorHandler(err: string): void { private errorHandler(err: string): void {
this.error = err; this.error = err; // Update error message.
// this.loading = false;
} }
/**
* Navigates back by one page in the router history, similar to browser back.
*/
public goBack(): void { public goBack(): void {
// go back by one record, the same as history.back() this.$router.go(-1); // Go back one step in the browser history.
// router.go(-1);
this.$router.go(-1);
} }
/**
* Extracts the file extension from a given filename.
* @param filename - The name of the file.
* @returns The file extension as a string.
*/
public getExtension(filename: string): string { public getExtension(filename: string): string {
return filename.substring(filename.lastIndexOf(".") + 1, filename.length) || filename; return filename.substring(filename.lastIndexOf(".") + 1, filename.length) || filename;
} }
/**
* Formats the file size into a human-readable string with appropriate units.
* @param file_size - The size of the file in bytes.
* @returns The formatted file size string.
*/
public formatSize(file_size: number): string { public formatSize(file_size: number): string {
let size = file_size; let size = file_size;
const unit = ["Byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]; const unit = ["Byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]; // Units for size.
let i; let i;
for (i = 0; size >= 1024 && i < unit.length - 1; i++) { for (i = 0; size >= 1024 && i < unit.length - 1; i++) {
size = size / 1024; size = size / 1024; // Convert size to appropriate unit.
} }
// return Math.round((size * precision) / precision) + " " + unit[i];
return Math.round((size + Number.EPSILON) * 100) / 100 + " " + unit[i]; return Math.round((size + Number.EPSILON) * 100) / 100 + " " + unit[i];
} }
/**
* Formats a given date into a human-readable string with the full day, month, and year.
* @param date - The date string to format.
* @returns The formatted date string.
*/
public getPublishedDate(date: string): string { public getPublishedDate(date: string): string {
// return moment(date).format("ddd, MMMM Do, YYYY h:mm a");
return dayjs(date).format("ddd, MMMM Do, YYYY h:mm a"); return dayjs(date).format("ddd, MMMM Do, YYYY h:mm a");
} }
/**
* Formats a given date into a simpler "DD.MM.YYYY HH:mm" format.
* @param date - The date string to format.
* @returns The formatted date string.
*/
public getHumanDate(date: string): string { public getHumanDate(date: string): string {
// return moment(date).format("DD.MM.YYYY HH:mm");
return dayjs(date).format("DD.MM.YYYY HH:mm"); return dayjs(date).format("DD.MM.YYYY HH:mm");
} }
/**
* Extracts the year from a given date string.
* @param date - The date string to extract the year from.
* @returns The year as a string.
*/
public getYear(date: string): string { public getYear(date: string): string {
return dayjs(date).format("YYYY"); return dayjs(date).format("YYYY");
// return moment(date).format("YYYY");
} }
/**
* Returns the human-readable language string based on the language code.
* @param language - The language code (e.g., "de" for German).
* @returns The language name as a string.
*/
public getLanguage(language: string): string {
if (language === "de") {
return "Deutsch";
} else {
return "English";
}
}
/**
* Generates a citation string for the dataset based on its authors and publication details.
* @returns The citation as a string.
*/
public getCitation(): string { public getCitation(): string {
let citation = this.dataset.authors let citation = this.dataset.authors
.map((u) => { .map((u) => {
@ -161,14 +223,27 @@ export default class DatasetDetailComponent extends Vue {
name += ", " + u.first_name?.substring(0, 1).toUpperCase() + "."; name += ", " + u.first_name?.substring(0, 1).toUpperCase() + ".";
} }
return name; return name;
// u.last_name + ", " + u.first_name?.substring(0, 1).toUpperCase() + "."
}) })
.join(", "); .join(", ");
citation += " (" + dayjs(this.dataset.server_date_published).format("YYYY") + "): "; citation += " (" + dayjs(this.dataset.server_date_published).format("YYYY") + "): ";
citation += this.dataset.MainTitle?.value; citation += this.dataset.MainTitle?.value;
citation += "." + this.dataset.creating_corporation + ", "; citation += ". " + this.dataset.creating_corporation + ", ";
citation += this.dataset.publisher_name; citation += this.dataset.publisher_name;
citation += ", Wien"; citation += ", Wien";
return citation; return citation;
} }
accessNotFromDoi(): boolean {
const host = window.location.host;
const parts = host.split(".");
if (parts[0] === "doi") {
console.log("From DOI");
// If in DOI subdomain, open external search in a new window
return false;
} else {
console.log("Not From DOI");
return true;
}
}
} }

View File

@ -1,81 +1,55 @@
<!-- detail-dataset.component.vue -->
<template v-if="datasetId"> <template v-if="datasetId">
<!-- <div class="container">
<section class="section" v-if="dataset != undefined">
<h2 v-if="dataset.hasOwnProperty('titles')">{{ dataset.titles[0].value }} details!</h2>
<div v-if="dataset" class="dataset__blog-meta">published: {{ getHumanDate(dataset.server_date_published) }}</div>
<p v-if="dataset.hasOwnProperty('abstracts')" class="dataset__abstract">{{ dataset.abstracts[0].value }}</p>
<div><label>id: </label>{{ dataset.id }}</div>
<button v-on:click="goBack">Back</button>
</section>
</div> -->
<div class="container-fluid banner mz-5"> <div class="container-fluid banner mz-5">
<!-- <div class="column is-two-thirds-tablet is-half-desktop is-one-third-widescreen mx-auto"> <!-- Search input component -->
<div class="search-box mx-auto"> <!-- Placeholder text for search input, and triggers onSearch method when the search term changes -->
<div class="field has-addons main-search-from-bg">
<div class="control is-expanded">
<input
v-on:input="searchChanged"
id="search_query"
class="input is-medium"
type="text"
name="q"
autocomplete="off"
v-model="display"
v-bind:placeholder="placeholder"
v-on:keydown.down="onArrowDown"
v-on:keydown.up="onArrowUp"
v-on:keydown.enter="onEnter"
@keydown.tab="close"
v-on:focus="focus"
/>
</div>
<div class="control">
<button class="button input is-medium search-button-icon" @click="search()">
<i class="fas fa-search text-white"></i>
</button>
</div>
</div>
</div>
</div> -->
<!-- <simple-search-component></simple-search-component> -->
<vs-input v-bind:placeholder="'Enter your search term...'" @search-change="onSearch"></vs-input> <vs-input v-bind:placeholder="'Enter your search term...'" @search-change="onSearch"></vs-input>
</div> </div>
<!-- Section that shows the dataset details once the data is loaded -->
<section v-if="loaded" class="section"> <section v-if="loaded" class="section">
<div class="container"> <div class="container">
<!-- <span class="is-size-5"> Basic Table </span>
<br /> -->
<div class="columns"> <div class="columns">
<!-- Main content area displaying dataset details -->
<div class="column is-8 results_column" style="padding-top: 1.2rem; padding-right: 1rem; padding-left: 1rem"> <div class="column is-8 results_column" style="padding-top: 1.2rem; padding-right: 1rem; padding-left: 1rem">
<!-- Card displaying the publication date -->
<div class="card"> <div class="card">
<div class="column dataset__blog-meta"> <div class="column dataset__blog-meta">
<h2 class="label uppercase">published: {{ getPublishedDate(dataset.server_date_published) }}</h2> <h2 class="label uppercase">published: {{ getPublishedDate(dataset.server_date_published) }}</h2>
</div> </div>
</div> </div>
<!-- Card displaying the dataset citation -->
<div class="card"> <div class="card">
<div class="column"> <div class="column">
<label class="label"> <label class="label">
{{ getCitation() }} {{ getCitation() }}
<!-- Link to the dataset's DOI if available -->
<a v-if="dataset.identifier" target="_blank" class="link-label" v-bind:href="'https://doi.org/' + dataset.identifier.value" <a v-if="dataset.identifier" target="_blank" class="link-label" v-bind:href="'https://doi.org/' + dataset.identifier.value"
>({{ "https://doi.org/" + dataset.identifier.value }})</a >({{ "https://doi.org/" + dataset.identifier.value }})</a
> >
</label> </label>
</div> </div>
<!-- Section showing references related to the dataset -->
<div v-for="reference in dataset.references" v-bind:key="reference.id" class="columns"> <div v-for="reference in dataset.references" v-bind:key="reference.id" class="columns">
<div class="column is-3-desktop is-4-tablet label">{{ reference.relation }}</div> <div class="column is-3-desktop is-4-tablet label">{{ reference.relation }}</div>
<div class="column is-9-desktop is-8-tablet"> <div class="column is-9-desktop is-8-tablet">
{{ reference.type }}: {{ reference.type }}:
<!-- Link to the reference if it's a DOI -->
<a v-if="reference.type === 'DOI'" target="_blank" class="link-label" v-bind:href="reference.value"> <a v-if="reference.type === 'DOI'" target="_blank" class="link-label" v-bind:href="reference.value">
{{ reference.value }} {{ reference.value }}
</a> </a>
</div> </div>
</div> </div>
<!-- Section showing newer versions of the dataset -->
<div v-for="reference in dataset.referenced_by" v-bind:key="reference.id" class="columns"> <div v-for="reference in dataset.referenced_by" v-bind:key="reference.id" class="columns">
<div class="column is-3-desktop is-4-tablet label">has newer version:</div> <div class="column is-3-desktop is-4-tablet label">has newer version:</div>
<div class="column is-9-desktop is-8-tablet"> <div class="column is-9-desktop is-8-tablet">
<!-- {{ "https://doi.org/" + reference.value }} -->
{{ reference.type }}: {{ reference.type }}:
<!-- Link to the newer version's DOI -->
<a <a
v-if="reference.type === 'DOI'" v-if="reference.type === 'DOI'"
target="_blank" target="_blank"
@ -88,10 +62,11 @@
</div> </div>
</div> </div>
<!-- Card displaying dataset titles -->
<div class="card record-elem"> <div class="card record-elem">
<!-- Section for Main and Translated Titles -->
<div v-if="dataset.hasOwnProperty('titles')" class="columns"> <div v-if="dataset.hasOwnProperty('titles')" class="columns">
<div class="column is-3-desktop is-4-tablet label">Title/<br />title:</div> <div class="column is-3-desktop is-4-tablet label">Title/<br />title:</div>
<!-- <div class="column is-9-desktop is-8-tablet">{{ dataset.titles[0].value }}</div> -->
<div class="column is-9-desktop is-8-tablet"> <div class="column is-9-desktop is-8-tablet">
<p>{{ dataset.MainTitle?.value }}</p> <p>{{ dataset.MainTitle?.value }}</p>
<br /> <br />
@ -100,6 +75,8 @@
</p> </p>
</div> </div>
</div> </div>
<!-- Section for dataset abstracts -->
<div v-if="dataset.hasOwnProperty('abstracts')" class="columns"> <div v-if="dataset.hasOwnProperty('abstracts')" class="columns">
<div class="column is-3-desktop is-4-tablet label"> <div class="column is-3-desktop is-4-tablet label">
Zusammenfassung/<br /> Zusammenfassung/<br />
@ -113,6 +90,7 @@
</p> </p>
</div> </div>
</div> </div>
<!-- Section for series information -->
<div v-if="dataset.hasOwnProperty('abstracts')" class="columns"> <div v-if="dataset.hasOwnProperty('abstracts')" class="columns">
<div class="column is-3-desktop is-4-tablet label">Serieninformation/<br />series information:</div> <div class="column is-3-desktop is-4-tablet label">Serieninformation/<br />series information:</div>
<div v-if="dataset.hasSeriesInformationAbstract()" class="column is-9-desktop is-8-tablet"> <div v-if="dataset.hasSeriesInformationAbstract()" class="column is-9-desktop is-8-tablet">
@ -124,6 +102,8 @@
</div> </div>
<div v-else class="column is-9-desktop is-8-tablet">-</div> <div v-else class="column is-9-desktop is-8-tablet">-</div>
</div> </div>
<!-- Section for method description -->
<div v-if="dataset.hasOwnProperty('abstracts')" class="columns"> <div v-if="dataset.hasOwnProperty('abstracts')" class="columns">
<div class="column is-3-desktop is-4-tablet label">Methodik/<br />method:</div> <div class="column is-3-desktop is-4-tablet label">Methodik/<br />method:</div>
<div v-if="dataset.hasMethodsAbstract()" class="column is-9-desktop is-8-tablet"> <div v-if="dataset.hasMethodsAbstract()" class="column is-9-desktop is-8-tablet">
@ -132,9 +112,11 @@
<div v-else class="column is-9-desktop is-8-tablet">-</div> <div v-else class="column is-9-desktop is-8-tablet">-</div>
</div> </div>
<!-- Section for dataset files and their details -->
<div class="columns"> <div class="columns">
<div class="column is-3-desktop is-4-tablet label">Downloads/<br />downloads:</div> <div class="column is-3-desktop is-4-tablet label">Downloads/<br />downloads:</div>
<div v-if="dataset.files.length > 0" class="column is-9-desktop is-8-tablet"> <div v-if="dataset.files.length > 0" class="column is-9-desktop is-8-tablet">
<!-- Table showing file details if the embargo has passed -->
<table v-if="dataset.hasEmbargoPassed()" id="items" class="table is-bordered is-striped"> <table v-if="dataset.hasEmbargoPassed()" id="items" class="table is-bordered is-striped">
<thead> <thead>
<tr> <tr>
@ -148,7 +130,6 @@
<td> <td>
<a class="link-label" target="_blank" v-bind:href="portal + file.id"> {{ file.label }} </a> <a class="link-label" target="_blank" v-bind:href="portal + file.id"> {{ file.label }} </a>
<br /> <br />
<!-- <span>md5: {{ file.hashvalues.find((e) => e.type === "md5")?.value }}</span> -->
</td> </td>
<td> <td>
<span>{{ getExtension(file.path_name) }}</span> <span>{{ getExtension(file.path_name) }}</span>
@ -164,6 +145,7 @@
</div> </div>
</div> </div>
<!-- Section for technical metadata of the dataset -->
<div class="columns"> <div class="columns">
<div class="column is-3-desktop is-4-tablet label">Technische Metadaten/<br />technical metadata:</div> <div class="column is-3-desktop is-4-tablet label">Technische Metadaten/<br />technical metadata:</div>
<div class="column is-9-desktop is-8-tablet"> <div class="column is-9-desktop is-8-tablet">
@ -177,7 +159,10 @@
</div> </div>
</div> </div>
<!-- Sidebar displaying additional dataset details -->
<div id="id-side-bar" class="column is-4 sidebar_column" style="padding-top: 1.2rem; padding-right: 1rem; padding-left: 1rem"> <div id="id-side-bar" class="column is-4 sidebar_column" style="padding-top: 1.2rem; padding-right: 1rem; padding-left: 1rem">
<!-- Sidebar card for dataset details like creation year, coverage, language, etc. -->
<div class="card"> <div class="card">
<div class="column"> <div class="column">
<h2 class="label uppercase">Details</h2> <h2 class="label uppercase">Details</h2>
@ -188,22 +173,38 @@
</div> </div>
<div class="card"> <div class="card">
<div class="column"> <div class="column">
<h3 class="label uppercase">Beitragende/Contributor</h3> <!-- <h3 class="label uppercase">MAP</h3> -->
<p v-if="dataset.hasContributors()"> <Minimap :bounds="dataset.Bounds"></Minimap>
{{ dataset.contributors.map((u) => u.full_name).join(", ") }}
</p>
<p v-else>-</p>
</div> </div>
</div> </div>
<!-- Sidebar card showing dataset keywords -->
<div class="card"> <div class="card">
<div class="column"> <div class="column">
<h3 class="label uppercase">Schlüsselwörter/Keywords</h3> <h3 class="label uppercase">Schlüsselwörter/Keywords</h3>
<p v-if="dataset.hasOwnProperty('subjects')"> <p v-if="dataset.hasOwnProperty('subjects')">
<!-- Iterate through subjects and display them as router links -->
<span v-if="accessNotFromDoi()">
<span v-for="(subject, index) in dataset.subjects" :key="subject.value">
<router-link
:to="{ name: 'Search', params: { display: subject.value, type: 'subjects' } }"
class="link-label"
>
{{ subject.value }}
</router-link>
<!-- Add a comma and space after each keyword except the last one -->
<span v-if="index < dataset.subjects.length - 1">, </span>
</span>
</span>
<span v-else>
{{ dataset.subjects.map((u) => u.value).join(", ") }} {{ dataset.subjects.map((u) => u.value).join(", ") }}
</span>
</p> </p>
<p v-else>-</p> <p v-else>-</p>
</div> </div>
</div> </div>
<!-- Sidebar cards displaying year, coverage, language, object type, and other dataset details -->
<div class="card"> <div class="card">
<div class="column"> <div class="column">
<h3 class="label uppercase">Erstellungsjahr/Year</h3> <h3 class="label uppercase">Erstellungsjahr/Year</h3>
@ -224,7 +225,7 @@
<div class="column"> <div class="column">
<h3 class="label uppercase">Sprache/Language</h3> <h3 class="label uppercase">Sprache/Language</h3>
<p> <p>
{{ dataset.language }} {{ getLanguage(dataset.language) }}
</p> </p>
</div> </div>
</div> </div>
@ -236,14 +237,22 @@
</p> </p>
</div> </div>
</div> </div>
<!-- Sidebar card showing dataset licenses -->
<div class="card"> <div class="card">
<div class="column"> <div class="column">
<h3 class="label uppercase">Lizenz/License</h3> <h3 class="label uppercase">Lizenz/License</h3>
<p v-if="dataset.hasLicenses()"> <p v-if="dataset.hasLicenses()">
<label v-for="license in dataset.licenses" v-bind:key="license.id"> <label v-for="license in dataset.licenses" v-bind:key="license.id">
<!-- Link to the appropriate Creative Commons license -->
<span class="normal label"> <span class="normal label">
{{ license.name }} <a v-if="license.name=='CC-BY-4.0'" target="_blank" class="link-label" v-bind:href="'https://creativecommons.org/licenses/by/4.0/'"
><i class="fa-brands fa-creative-commons"></i>&nbsp;{{ license.name }}</a
>
<a v-else target="_blank" class="link-label" v-bind:href="'https://creativecommons.org/licenses/by-sa/4.0/'"
><i class="fa-brands fa-creative-commons"></i>&nbsp;{{ license.name }}</a
>
</span> </span>
<!-- Display Open Access label if the license allows it -->
<span v-if="openAccessLicences.includes(license.name)" class="normal label uppercase" <span v-if="openAccessLicences.includes(license.name)" class="normal label uppercase"
><i class="fas fa-lock-open"></i> Open Access</span ><i class="fas fa-lock-open"></i> Open Access</span
> >
@ -260,11 +269,14 @@
<p v-else>-</p> <p v-else>-</p>
</div> </div>
</div> </div>
<!-- Sidebar card showing references -->
<div class="card"> <div class="card">
<div class="column"> <div class="column">
<h3 class="label uppercase">Referenzen/References</h3> <h3 class="label uppercase">Referenzen/References</h3>
<ul v-if="dataset.references.length > 0"> <ul v-if="dataset.references.length > 0">
<li v-for="(reference, i) in dataset.references" v-bind:key="reference.id"> <li v-for="(reference, i) in dataset.references" v-bind:key="reference.id">
<!-- Link to reference if it's a DOI or URL -->
<a <a
v-if="reference.type == 'DOI' || reference.type == 'URL'" v-if="reference.type == 'DOI' || reference.type == 'URL'"
target="_blank" target="_blank"
@ -277,14 +289,12 @@
{{ `${reference.relation} (${reference.type}): ${reference.value}` }} {{ `${reference.relation} (${reference.type}): ${reference.value}` }}
</span> </span>
<span v-if="dataset.references.length > 0 && i < dataset.references.length - 1" class="normal label">--</span> <span v-if="dataset.references.length > 0 && i < dataset.references.length - 1" class="normal label">--</span>
<!-- <span v-if="openAccessLicences.includes(license.name)" class="normal label uppercase"
><i class="fas fa-lock-open"></i> Open Access</span
> -->
</li> </li>
</ul> </ul>
<p v-else>-</p> <p v-else>-</p>
</div> </div>
</div> </div>
<!-- Sidebar card for showing embargo details -->
<div class="card"> <div class="card">
<div class="column"> <div class="column">
<h3 class="label uppercase">Embargo</h3> <h3 class="label uppercase">Embargo</h3>
@ -294,63 +304,95 @@
<p v-else>-</p> <p v-else>-</p>
</div> </div>
</div> </div>
<!-- Sidebar card for displaying dataset contributors -->
<div class="card">
<div class="column">
<h3 class="label uppercase">Beitragende/Contributor</h3>
<p v-if="dataset.hasContributors()">
{{ dataset.contributors.map((u) => u.full_name).join(", ") }}
</p>
<p v-else>-</p>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- Footer section with partner logos -->
<div class="container-fluid" style="padding-top: 3.8em"> <div class="container-fluid" style="padding-top: 3.8em">
<div class="columns is-mobile partner-logos"> <div class="columns">
<div class="column col-sm text-center"> <div class="column col-sm">
<a target="_blank" href="https://www.re3data.org/repository/r3d100013400" <div class="card mx-auto" style="width: 18rem; box-shadow: none; border: 0rem">
><img src="@/assets/site/img/re3-data-logo-mono.jpg" alt="re3 data logo" <div class="card-body">
/></a> <a target="_blank" href="https://www.re3data.org/repository/r3d100013400">
<img src="@/assets/site/img/re3-data-logo-mono.jpg" alt="re3 data logo" />
</a>
</div> </div>
<div class="column col-sm text-center"> </div>
</div>
<div class="column col-sm">
<div class="card mx-auto" style="width: 28rem; box-shadow: none; border: 0rem">
<div class="card-body">
<a target="_blank" href="http://www.geosphere.at/"> <a target="_blank" href="http://www.geosphere.at/">
<img src="@/assets/site/img/geosphere-austria-logo.jpg" class="pb-3" alt="logo geosphere austria" /> <img src="@/assets/site/img/geosphere-austria-logo.jpg" alt="logo geosphere austria" />
<!-- <img src="@/assets/site/img/gbaLogoRGB_web.png" alt="GeoSphere Austria logo" /> -->
</a> </a>
</div> </div>
<div class="column col-sm text-center"> </div>
</div>
<div class="column col-sm">
<div class="card mx-auto" style="width: 18rem; box-shadow: none; border: 0rem">
<div class="card-body">
<a target="_blank" href="https://www.base-search.net/Search/Results?q=coll:fttethysrdr&refid=dctablede"> <a target="_blank" href="https://www.base-search.net/Search/Results?q=coll:fttethysrdr&refid=dctablede">
<img src="@/assets/site/img/base-logo.gif" alt="logo base" /> <img src="@/assets/site/img/base_logo.png" alt="logo base" />
</a> </a>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
</section> </section>
</template> </template>
<script lang="ts"> <script lang="ts">
import Minimap from "@/components/Minimap.vue";
import DatasetDetailComponent from "./dataset-detail.component"; import DatasetDetailComponent from "./dataset-detail.component";
export default DatasetDetailComponent; export default DatasetDetailComponent;
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
// @import 'leaflet/dist/leaflet.css';
.section { .section {
font-size: 0.8rem; font-size: 0.8rem;
padding: 0; padding: 0;
} }
.card { .card {
border-radius: 0; border-radius: 0;
/* rempve box-shadow */ /* Remove box-shadow for a flat design */
box-shadow: none; box-shadow: none;
} }
.link-label { .link-label {
color: #33cccc; color: #33cccc;
} }
.label { .label {
/* color: #363636; */ /* color: #363636; */
display: block; display: block;
font-size: 0.8rem; font-size: 0.8rem;
font-weight: 700; font-weight: 700;
} }
.label.uppercase { .label.uppercase {
text-transform: uppercase; text-transform: uppercase;
} }
.normal.label { .normal.label {
font-weight: 400; font-weight: 400;
} }
.column p span i { .column p span i {
color: #336699; color: #336699;
} }
@ -361,27 +403,4 @@ export default DatasetDetailComponent;
font-weight: 700; font-weight: 700;
background-color: #ccddf1; background-color: #ccddf1;
} }
// input {
// height: 2em;
// font-size: 1em;
// padding-left: 0.4em;
// }
// button {
// margin-top: 20px;
// font-family: Arial;
// background-color: #eee;
// border: none;
// padding: 5px 10px;
// border-radius: 4px;
// cursor: pointer;
// cursor: hand;
// }
// button:hover {
// background-color: #cfd8dc;
// }
// button:disabled {
// background-color: #eee;
// color: #ccc;
// cursor: auto;
// }
</style> </style>

View File

@ -1,8 +1,12 @@
// import { Prop } from "vue-property-decorator"; // import { Prop } from "vue-property-decorator";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import SectionBannerStarOnGitHub from "@/components/SectionBannerStarOnGitea.vue";
@Component({ @Component({
name: "HelpViewComponent", name: "HelpViewComponent",
components: {
SectionBannerStarOnGitHub,
},
}) })
export default class HelpViewComponent extends Vue { export default class HelpViewComponent extends Vue {
// results: Array<any> = []; // results: Array<any> = [];

View File

@ -82,6 +82,7 @@
</div> </div>
</div> </div>
</div> </div>
<SectionBannerStarOnGitHub />
</div> </div>
</template> </template>

View File

@ -11,6 +11,9 @@ import { Suggestion } from "@/models/dataset";
export default class HomeViewComponent extends Vue { export default class HomeViewComponent extends Vue {
public display = ""; public display = "";
/* This method is called when a search suggestion is selected. It takes a parameter suggestion which can be either a Suggestion object or a string.
If it's a string, the method extracts the term and navigates to the "Search" route with the term as a parameter. If it's a Suggestion object, it extracts
the value and type from the suggestion and navigates to the "Search" route with both parameters.*/
onSearch(suggestion: Suggestion | string): void { onSearch(suggestion: Suggestion | string): void {
let term; let term;
if (typeof suggestion === "string") { if (typeof suggestion === "string") {
@ -22,6 +25,7 @@ export default class HomeViewComponent extends Vue {
} }
} }
/* This method is called when the user initiates a search. It navigates to the "Search" route with the display property as a parameter. */
search(): void { search(): void {
this.$router.push({ name: "Search", params: { display: this.display } }); this.$router.push({ name: "Search", params: { display: this.display } });
} }

View File

@ -18,13 +18,6 @@
</a> </a>
</div> --> </div> -->
<!-- <div class="column">
<div class="col text-center py-3">
<h1>Tethys Research Data Repository</h1>
<p class="lead">Data Publisher for Geoscience Austria</p>
<hr class="center-line" />
</div>
</div> -->
<div class="column"> <div class="column">
<div class="col text-center py-3"> <div class="col text-center py-3">
<h1>Tethys Research Data Repository</h1> <h1>Tethys Research Data Repository</h1>
@ -189,17 +182,16 @@
</div> </div>
</div> </div>
<div class="container-fluid"> <!-- <div class="container-fluid">
<div class="columns is-mobile partner-logos"> <div class="columns is-mobile partner-logos">
<div class="column col-sm text-center"> <div class="column col-sm text-center">
<a target="_blank" href="https://www.re3data.org/repository/r3d100013400" <a target="_blank" href="https://www.re3data.org/repository/r3d100013400"
><img src="@/assets/site/img/re3-data-logo-mono.jpg" alt="re3 data logo" ><img src="@/assets/site/img/re3-data-logo-mono.jpg" alt="re3 data logo"/>
/></a> </a>
</div> </div>
<div class="column col-sm text-center"> <div class="column col-sm text-center">
<a target="_blank" href="http://www.geosphere.at/"> <a target="_blank" href="http://www.geosphere.at/">
<img src="@/assets/site/img/geosphere-austria-logo.jpg" alt="logo geosphere austria" /> <img src="@/assets/site/img/geosphere-austria-logo.jpg" alt="logo geosphere austria" />
<!-- <img src="@/assets/site/img/gbaLogoRGB_web.png" alt="GeoSphere Austria logo" /> -->
</a> </a>
</div> </div>
<div class="column col-sm text-center"> <div class="column col-sm text-center">
@ -208,6 +200,39 @@
</a> </a>
</div> </div>
</div> </div>
</div> -->
<div class="container-fluid">
<!-- <div class="columns is-mobile partner-logos"> -->
<div class="columns">
<div class="column col-sm">
<div class="card mx-auto" style="width: 18rem; box-shadow: none; border: 0rem">
<div class="card-body">
<!-- <h5 class="card-title">About TETHYS</h5> -->
<a target="_blank" href="https://www.re3data.org/repository/r3d100013400">
<img src="@/assets/site/img/re3-data-logo-mono.jpg" alt="re3 data logo" />
</a>
</div>
</div>
</div>
<div class="column col-sm">
<div class="card mx-auto" style="width: 28rem; box-shadow: none; border: 0rem">
<div class="card-body">
<a target="_blank" href="http://www.geosphere.at/">
<img src="@/assets/site/img/geosphere-austria-logo.jpg" alt="logo geosphere austria" />
</a>
</div>
</div>
</div>
<div class="column col-sm">
<div class="card mx-auto" style="width: 18rem; box-shadow: none; border: 0rem">
<div class="card-body">
<a target="_blank" href="https://www.base-search.net/Search/Results?q=coll:fttethysrdr&refid=dctablede">
<img src="@/assets/site/img/base_logo.png" alt="logo base" />
</a>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,17 +1,26 @@
// Import necessary modules, components, and models from Vue and the project
import { Component, Vue, Prop } from "vue-facing-decorator"; import { Component, Vue, Prop } from "vue-facing-decorator";
import VsInput from "@/components/vs-input/vs-input.vue"; import VsInput from "@/components/vs-input/vs-input.vue";
import VsResult from "@/components/vs-result/vs-result.vue"; import VsResult from "@/components/vs-result/vs-result.vue";
import FacetCategory from "@/components/face-category/facet-category.vue"; import FacetCategory from "@/components/face-category/facet-category.vue";
import ActiveFacetCategory from "@/components/active-facet-category/active-facet-category.vue"; import ActiveFacetCategory from "@/components/active-facet-category/active-facet-category.vue";
import { SolrSettings } from "@/models/solr";
// import { DatasetService } from "@/services/dataset.service"; // Import models and services
// import { SolrSettings } from "@/models/solr";
import { OpenSettings } from "@/models/solr";
import DatasetService from "../../services/dataset.service"; import DatasetService from "../../services/dataset.service";
import { Suggestion, Dataset, SearchType } from "@/models/dataset"; import { Suggestion, Dataset, SearchType } from "@/models/dataset";
import { SolrResponse, FacetFields, FacetItem, FacetResults, FacetInstance } from "@/models/headers"; // import { SolrResponse, FacetFields, FacetItem, FacetResults, FacetInstance } from "@/models/headers";
// import { SolrResponse, FacetFields, FacetItem, FacetResults, FacetInstance, OpenSearchResponse, HitHighlight } from "@/models/headers";
import { FacetItem, FacetResults, OpenSearchResponse } from "@/models/headers";
import { ActiveFilterCategories } from "@/models/solr"; import { ActiveFilterCategories } from "@/models/solr";
import { SOLR_HOST, SOLR_CORE } from "@/constants"; // import { SOLR_HOST, SOLR_CORE } from "@/constants";
import { Pagination } from "@/models/pagination"; import { IPagination } from "@/models/pagination";
import PaginationComponent from "@/components/PaginationComponent.vue";
import { OPEN_HOST, OPEN_CORE } from "@/constants";
// Define the Vue component, its name, and child components
@Component({ @Component({
name: "SearchViewComponent", name: "SearchViewComponent",
components: { components: {
@ -19,50 +28,79 @@ import { Pagination } from "@/models/pagination";
VsResult, VsResult,
FacetCategory, FacetCategory,
ActiveFacetCategory, ActiveFacetCategory,
PaginationComponent,
}, },
}) })
// Export the default class for the component
export default class SearchViewComponent extends Vue { export default class SearchViewComponent extends Vue {
// Define props passed from the parent component
@Prop() @Prop()
display!: string; display!: string; // Search display string
@Prop() @Prop()
type!: string; type!: string; // Search type
results: Array<Dataset> = []; // Declare variables used in the component
results: Array<Dataset> = []; // Array to hold search results
// facets: FacetFields = new FacetFields(); facets: FacetResults = new FacetResults(); // Object to hold facet results
facets: FacetResults = new FacetResults(); searchTerm: string | Suggestion = ""; // The search term input
searchTerm: string | Suggestion = ""; activeFilterCategories: ActiveFilterCategories = new ActiveFilterCategories(); // Active filter categories for search
// activeFilterCategories: Object = {}; pagination: IPagination = { // Pagination data for the results
activeFilterCategories: ActiveFilterCategories = new ActiveFilterCategories(); // = new Array<ActiveFilterCategory>();
pagination: Pagination = {
total: 0, total: 0,
per_page: 2, perPage: 10,
current_page: 0, currentPage: 1,
// last_page: 0,
data: [], data: [],
}; };
loaded = false; loaded = false; // Boolean to track whether data has been loaded
numFound!: number; numFound!: number; // Number of results found
private solr: SolrSettings = {
core: SOLR_CORE, //"rdr_data", // SOLR.core; // private solr: SolrSettings = {
host: SOLR_HOST, //"tethys.at", // core: SOLR_CORE, //"rdr_data", // SOLR.core;
// core: "test_data", // SOLR.core; // host: SOLR_HOST, //"tethys.at",
// host: "repository.geologie.ac.at", // };
// Define settings for the OpenSearch API (core and host information)
private open: OpenSettings = {
core: OPEN_CORE,
host: OPEN_HOST, //"https://catalog.geosphere.at",
}; };
// private rdrAPI!: DatasetService;
private error = ""; private error = "";
// Computed property to get search term as string
get stringSearchTerm(): string { get stringSearchTerm(): string {
// If searchTerm is a string, return it directly
if (typeof this.searchTerm === "string") { if (typeof this.searchTerm === "string") {
return this.searchTerm; return this.searchTerm;
// If searchTerm is a Suggestion, return its value and type alias
} else if (this.searchTerm instanceof Suggestion) { } else if (this.searchTerm instanceof Suggestion) {
return this.searchTerm.value + " (" + this.searchTerm.type + ")"; return this.searchTerm.value + " (" + this.getTypeAlias(this.searchTerm.type) + ")";
// return this.searchTerm.value + " (" + this.searchTerm.type + ")";
// Default to empty string
} else { } else {
return ""; return "";
} }
} }
/**
* The alias for the search term type will be set depending on the name of the type.
* This will allow to display the customised terms instead of the values currently used in the OpenSearch index.
* TODO: This should be corrected directly in the index
*/
getTypeAlias(type: string): string {
switch (type) {
case "author":
return "creator";
case "subjects":
return "keyword";
case "doctype":
return "data type";
default:
return type;
}
}
// Method to check if a search term is present
hasSearchTerm(): boolean { hasSearchTerm(): boolean {
if (typeof this.searchTerm === "string" && this.searchTerm !== "") { if (typeof this.searchTerm === "string" && this.searchTerm !== "") {
return true; return true;
@ -72,21 +110,22 @@ export default class SearchViewComponent extends Vue {
return false; return false;
} }
} }
// getKeyName(value: string) {
// return Object.entries(Suggestion).find(([key, val]) => val === value)?.[0]; // Method to get enum key by enum value
// }
getEnumKeyByEnumValue<T extends { [index: string]: string }>(myEnum: T, enumValue: string): keyof T | null { getEnumKeyByEnumValue<T extends { [index: string]: string }>(myEnum: T, enumValue: string): keyof T | null {
const keys = Object.keys(myEnum).filter((x) => myEnum[x] == enumValue); const keys = Object.keys(myEnum).filter((x) => myEnum[x] == enumValue);
return keys.length > 0 ? keys[0] : null; return keys.length > 0 ? keys[0] : null;
// return keys[0]; // return keys[0];
} }
// Lifecycle hook: executed before the component is mounted
beforeMount(): void { beforeMount(): void {
// this.rdrAPI = new DatasetService(); // Trigger search based on provided display and type props
if (this.display != "" && this.type != undefined) { if (this.display != "" && this.type != undefined) {
const enumKey = this.getEnumKeyByEnumValue(SearchType, this.type); const enumKey: "Title" | "Author" | "Subject" | "Doctype" | null = this.getEnumKeyByEnumValue(SearchType, this.type);
if (enumKey) { if (enumKey) {
const suggestion = new Suggestion(this.display, SearchType[enumKey]); const suggestion = new Suggestion(this.display, "NO-IDEA", SearchType[enumKey]);
// const suggestion = new Suggestion(this.display, "" , SearchType[enumKey]);
this.onSearch(suggestion); this.onSearch(suggestion);
} else { } else {
this.onSearch(this.display); this.onSearch(this.display);
@ -98,200 +137,294 @@ export default class SearchViewComponent extends Vue {
} }
} }
// onSearch(term: string): void { // Method to trigger a search
onSearch(suggestion: Suggestion | string): void { onSearch(suggestion: Suggestion | string): void {
// let queryOperator; // console.log("ONSEARCH");
// if (typeof suggestion === "string") {
// suggestion = suggestion + "*";
// queryOperator = "or";
// } else if (suggestion instanceof Suggestion) {
// // term = suggestion.value;
// queryOperator = "and";
// }
// if (term) {
// term = term.trim();
// } else {
// term = "*%3A*";
// }
// Reset active filter categories and facet results
this.activeFilterCategories = new ActiveFilterCategories(); this.activeFilterCategories = new ActiveFilterCategories();
this.facets = new FacetResults(); this.facets = new FacetResults();
// this.facets = {};
this.searchTerm = suggestion; this.searchTerm = suggestion;
DatasetService.facetedSearch(suggestion, this.activeFilterCategories, this.solr.core, this.solr.host, undefined).subscribe({
next: (res: SolrResponse) => this.dataHandler(res), // /* Perform faceted search. The method returns an Observable, and the code subscribes to this Observable to handle the response. If the response is successful, it calls the dataHandler method
// with the Solr response as a parameter. If there is an error, it calls the errorHandler method with the error message as a parameter */
// DatasetService.facetedSearchSOLR(suggestion, this.activeFilterCategories, this.solr.core, this.solr.host, undefined).subscribe({
// next: (res: SolrResponse) => this.dataHandler(res),
// error: (error: string) => this.errorHandler(error),
// });
DatasetService.facetedSearch(suggestion, this.activeFilterCategories, this.open.core, this.open.host, undefined).subscribe({
next: (res: OpenSearchResponse) => this.dataHandler(res),
error: (error: string) => this.errorHandler(error), error: (error: string) => this.errorHandler(error),
}); });
} }
private dataHandler(res: SolrResponse, filterItem?: FacetItem): void { // Handle the search results
// this.results = datasets; private dataHandler(res: OpenSearchResponse, filterItem?: FacetItem): void {
this.results = res.response.docs; this.results = res.hits.hits.map(hit => hit._source);
this.numFound = res.response.numFound; this.numFound = res.hits.total.value;
this.pagination.total = res.hits.total.value;
this.pagination.perPage = 10;
this.pagination.data = this.results;
this.pagination.lastPage = Math.ceil(this.pagination.total / this.pagination.perPage);
// pagination if (res.aggregations) {
this.pagination["total"] = res.response.numFound; const facet_fields = res.aggregations;
this.pagination["per_page"] = res.responseHeader.params.rows;
this.pagination["current_page"] = 1;
this.pagination["data"] = res.response.docs;
// facets
// const facet_fields = res.facet_counts.facet_fields;
// for (const prop in facet_fields) {
// const facetCategory: FacetCategory<any> = facet_fields[prop];
// const facetValues = facetCategory.key.values.map((facet_value: any, i: number) => {
// if (i % 2 === 0 && typeof facet_value == "string") {
// //var rObj = { value: facet, count: facet_fields[prop][i + 1] };
// // FiletrItem with value and count
// const rObj = new FilterItem(facet_value, facetCategory.key.values[i + 1]);
// return rObj;
// }
// });
// .filter(function (el: FilterItem) {
// return el != null && el.count > 0;
// });
// //this.facets.push({ filterName: prop, values: facetValues });
// this.facets[prop] = facetValues;
// }
const facet_fields: FacetFields = res.facets;
let prop: keyof typeof facet_fields; let prop: keyof typeof facet_fields;
// Iterate through facet fields
for (prop in facet_fields) { for (prop in facet_fields) {
const facetCategory = facet_fields[prop]; const facetCategory = facet_fields[prop];
if (facetCategory.buckets) { if (facetCategory.buckets) {
const facetItems: Array<FacetItem> = facetCategory.buckets; const facetItems = facetCategory.buckets.map(bucket => new FacetItem(bucket.key, bucket.doc_count));
let facetValues = facetItems.map((facetItem) => { let facetValues = facetItems.map((facetItem) => {
let rObj: FacetItem; let rObj: FacetItem;
// Check if current facet item matches filter item
if (filterItem?.val == facetItem.val) { if (filterItem?.val == facetItem.val) {
rObj = filterItem; rObj = filterItem;
} else if (this.facets[prop]?.some((e) => e.val === facetItem.val)) { } else if (this.facets[prop]?.some((e) => e.val === facetItem.val)) {
// console.log(facetValue + " is included")
const indexOfFacetValue = this.facets[prop].findIndex((i) => i.val === facetItem.val); const indexOfFacetValue = this.facets[prop].findIndex((i) => i.val === facetItem.val);
// console.log(indexOfFacetValue);
rObj = this.facets[prop][indexOfFacetValue]; rObj = this.facets[prop][indexOfFacetValue];
rObj.count = facetItem.count; rObj.count = facetItem.count;
// rObj = new FacetItem(val, count);
} else { } else {
// Create new facet item
rObj = new FacetItem(facetItem.val, facetItem.count); rObj = new FacetItem(facetItem.val, facetItem.count);
} }
return rObj; return rObj;
}); });
facetValues = facetValues.filter(function (el) { // Filter out null values and values with count <= 0
return el != null && el.count > 0; facetValues = facetValues.filter(el => el.count > 0);
});
// this.facets[prop] = facetCategory;
this.facets[prop] = facetValues; this.facets[prop] = facetValues;
} }
} }
} }
private errorHandler(err: string): void {
this.error = err;
// this.loading = false;
} }
// // Method to handle search response
// private dataHandlerSOLR(res: SolrResponse, filterItem?: FacetItem): void {
// // console.log("dataHandlerSOLR (docs, numFound):");
// // console.log(res.response.docs);
// // console.log(res.response.numFound);
// // Update results
// this.results = res.response.docs;
// this.numFound = res.response.numFound;
// // Update pagination
// this.pagination["total"] = res.response.numFound;
// this.pagination["perPage"] = res.responseHeader.params.rows as number;
// this.pagination["data"] = res.response.docs;
// this.pagination.lastPage = Math.ceil(this.pagination.total / this.pagination.perPage);
// const facet_fields: FacetFields = res.facets;
// /* This code declares a variable prop with a type of keys of the facet_fields object. The keyof typeof facet_fields type represents the keys of the facet_fields object.
// This means that prop can only hold values that are keys of the facet_fields object. */
// let prop: keyof typeof facet_fields;
// // Iterate through facet fields
// for (prop in facet_fields) {
// const facetCategory = facet_fields[prop];
// if (facetCategory.buckets) {
// const facetItems: Array<FacetItem> = facetCategory.buckets;
// let facetValues = facetItems.map((facetItem) => {
// let rObj: FacetItem;
// // Check if current facet item matches filter item
// if (filterItem?.val == facetItem.val) {
// rObj = filterItem;
// } else if (this.facets[prop]?.some((e) => e.val === facetItem.val)) {
// // console.log(facetValue + " is included")
// // Update existing facet item with new count
// const indexOfFacetValue = this.facets[prop].findIndex((i) => i.val === facetItem.val);
// // console.log(indexOfFacetValue);
// rObj = this.facets[prop][indexOfFacetValue];
// rObj.count = facetItem.count;
// // rObj = new FacetItem(val, count);
// } else {
// // Create new facet item
// rObj = new FacetItem(facetItem.val, facetItem.count);
// }
// return rObj;
// });
// // Filter out null values and values with count <= 0
// facetValues = facetValues.filter(function (el) {
// return el != null && el.count > 0;
// });
// // this.facets[prop] = facetCategory;
// // Update facet values
// this.facets[prop] = facetValues;
// }
// }
// }
// Method to handle search errors
private errorHandler(err: string): void {
this.error = err;
}
// Method to handle pagination
onMenuClick(page: number) {
console.log("onMenuClick");
this.pagination.currentPage = page;
const start = page * this.pagination.perPage - this.pagination.perPage;
// // Trigger new search with updated pagination parameters
// DatasetService.facetedSearchSOLR(this.searchTerm, this.activeFilterCategories, this.solr.core, this.solr.host, start.toString()).subscribe(
// (res: SolrResponse) => this.dataHandler(res),
// (error: string) => this.errorHandler(error),
// );
DatasetService.facetedSearch(this.searchTerm, this.activeFilterCategories, this.open.core, this.open.host, start.toString()).subscribe({
next: (res: OpenSearchResponse) => this.dataHandler(res),
error: (error: string) => this.errorHandler(error),
});
}
// Method to handle facet filtering
onFilter(facetItem: FacetItem): void { onFilter(facetItem: FacetItem): void {
// console.log(facetItem.val); console.log("onFilter");
// if (!this.activeFilterCategories.hasOwnProperty(facetItem.category)) {
// Reset current page
this.pagination.currentPage = 1;
// Check if filter item already exists
if (!Object.prototype.hasOwnProperty.call(this.activeFilterCategories, facetItem.category)) { if (!Object.prototype.hasOwnProperty.call(this.activeFilterCategories, facetItem.category)) {
this.activeFilterCategories[facetItem.category] = new Array<string>(); this.activeFilterCategories[facetItem.category] = new Array<string>();
} }
// if (!this.activeFilterCategories[facetItem.category].some((e) => e === facetItem.val)) {
// Check if filter item is not already applied
if (!this.activeFilterCategories[facetItem.category].some((e) => e === facetItem.val)) { if (!this.activeFilterCategories[facetItem.category].some((e) => e === facetItem.val)) {
// Add filter item to active filter categories
this.activeFilterCategories[facetItem.category].push(facetItem.val); this.activeFilterCategories[facetItem.category].push(facetItem.val);
DatasetService.facetedSearch(this.searchTerm, this.activeFilterCategories, this.solr.core, this.solr.host, undefined).subscribe( // DatasetService.facetedSearchSOLR(this.searchTerm, this.activeFilterCategories, this.solr.core, this.solr.host, undefined).subscribe(
(res: SolrResponse) => this.dataHandler(res, facetItem), // (res: SolrResponse) => this.dataHandler(res, facetItem),
(error: string) => this.errorHandler(error), // (error: string) => this.errorHandler(error),
); // );
// alert(this.activeFilterCategories[filter.Category]);
// var res = await rdrApi.search(this.searchTerm, this.activeFilterCategories, this.solrCore, this.solrHost); // Trigger new search with updated filter
DatasetService.facetedSearch(this.searchTerm, this.activeFilterCategories, this.open.core, this.open.host, undefined).subscribe({
next: (res: OpenSearchResponse) => this.dataHandler(res, facetItem),
error: (error: string) => this.errorHandler(error),
});
}
}
// // // Method to clear facet category filter
// onClearFacetCategorySOLR(categoryName: string): void {
// console.log("onClearFacetCategory");
// delete this.activeFilterCategories[categoryName];
// // Trigger new search with updated filter
// DatasetService.facetedSearch(this.searchTerm, this.activeFilterCategories, this.solr.core, this.solr.host, undefined).subscribe({
// next: (res: SolrResponse) => {
// this.results = res.response.docs; // this.results = res.response.docs;
// this.numFound = res.response.numFound; // this.numFound = res.response.numFound;
// // pagination // // pagination
// this.pagination['total'] = res.response.numFound; // this.pagination["total"] = res.response.numFound;
// this.pagination['per_page'] = res.responseHeader.params.rows; // this.pagination["perPage"] = res.responseHeader.params.rows as number;
// this.pagination['current_page'] = 1; // this.pagination["currentPage"] = 1;
// this.pagination['data'] = res.response.docs; // this.pagination["data"] = res.response.docs;
// var facet_fields = res.facet_counts.facet_fields; // const facet_fields: FacetFields = res.facets;
// for (var prop in facet_fields) { // let prop: keyof typeof facet_fields;
// var facetValues = facet_fields[prop].map((facetValue, i) => { // for (prop in facet_fields) {
// if (i % 2 === 0) { // const facetCategory: FacetInstance = facet_fields[prop];
// // var rObj = { value: facetValue, count: facet_fields[prop][i + 1] }; // if (facetCategory.buckets) {
// var rObj; // const facetItems: Array<FacetItem> = facetCategory.buckets;
// if (filter.value == facetValue) {
// rObj = filter; // const facetValues = facetItems.map((facetItem) => {
// } else if (this.facets[prop].some(e => e.value === facetValue)) { // let rObj: FacetItem;
// if (this.facets[prop]?.some((e) => e.val === facetItem.val)) {
// // console.log(facetValue + " is included") // // console.log(facetValue + " is included")
// var indexOfFacetValue = this.facets[prop].findIndex(i => i.value === facetValue); // // Update existing facet item with new count
// const indexOfFacetValue = this.facets[prop].findIndex((i) => i.val === facetItem.val);
// // console.log(indexOfFacetValue); // // console.log(indexOfFacetValue);
// rObj = this.facets[prop][indexOfFacetValue]; // rObj = this.facets[prop][indexOfFacetValue];
// rObj.count = facet_fields[prop][i + 1]; // rObj.count = facetItem.count;
// // rObj = new FacetItem(val, count);
// // if facet ccategory is reactivated category, deactivate all filter items
// if (prop == categoryName) {
// rObj.active = false;
// }
// } else { // } else {
// rObj = new FilterItem(facetValue, facet_fields[prop][i + 1]); // // Create new facet item
// rObj = new FacetItem(facetItem.val, facetItem.count);
// } // }
// return rObj; // return rObj;
// }
// }).filter(function (el) {
// return el != null && el.count > 0;
// }); // });
// // this.facets.push({ filterName: prop, values: facetValues });
// this.facets[prop] = facetValues; // this.facets[prop] = facetValues;
} // }
} // }
// },
// error: (error: string) => this.errorHandler(error),
// complete: () => console.log("clear facet category completed"),
// });
// }
// Method to clear facet category filter
onClearFacetCategory(categoryName: string): void { onClearFacetCategory(categoryName: string): void {
// alert(categoryName); // console.log("onClearFacetCategory");
delete this.activeFilterCategories[categoryName]; delete this.activeFilterCategories[categoryName];
DatasetService.facetedSearch(this.searchTerm, this.activeFilterCategories, this.solr.core, this.solr.host, undefined).subscribe({ // Trigger new search with updated filter
next: (res: SolrResponse) => { DatasetService.facetedSearch(this.searchTerm, this.activeFilterCategories, this.open.core, this.open.host, undefined).subscribe({
this.results = res.response.docs; next: (res: OpenSearchResponse) => {
this.numFound = res.response.numFound; this.results = res.hits.hits.map(hit => hit._source);
this.numFound = res.hits.total.value;
// pagination // Update pagination
this.pagination["total"] = res.response.numFound; this.pagination.total = res.hits.total.value;
this.pagination["per_page"] = res.responseHeader.params.rows; this.pagination.perPage = 10;
this.pagination["current_page"] = 1; this.pagination.currentPage = 1;
this.pagination["data"] = res.response.docs; this.pagination.data = this.results;
this.pagination.lastPage = Math.ceil(this.pagination.total / this.pagination.perPage);
if (res.aggregations) {
const facet_fields = res.aggregations;
const facet_fields: FacetFields = res.facets;
let prop: keyof typeof facet_fields; let prop: keyof typeof facet_fields;
for (prop in facet_fields) { for (prop in facet_fields) {
const facetCategory: FacetInstance = facet_fields[prop]; const facetCategory = facet_fields[prop];
if (facetCategory.buckets) { if (facetCategory.buckets) {
const facetItems: Array<FacetItem> = facetCategory.buckets; const facetItems = facetCategory.buckets.map(bucket => new FacetItem(bucket.key, bucket.doc_count));
const facetValues = facetItems.map((facetItem) => { const facetValues = facetItems.map((facetItem) => {
let rObj: FacetItem; let rObj: FacetItem;
if (this.facets[prop]?.some((e) => e.val === facetItem.val)) { if (this.facets[prop]?.some((e) => e.val === facetItem.val)) {
// console.log(facetValue + " is included") // Update existing facet item with new count
const indexOfFacetValue = this.facets[prop].findIndex((i) => i.val === facetItem.val); const indexOfFacetValue = this.facets[prop].findIndex((i) => i.val === facetItem.val);
// console.log(indexOfFacetValue);
rObj = this.facets[prop][indexOfFacetValue]; rObj = this.facets[prop][indexOfFacetValue];
rObj.count = facetItem.count; rObj.count = facetItem.count;
// rObj = new FacetItem(val, count); // if facet category is reactivated category, deactivate all filter items
//if facet ccategory is reactivated category, deactivate all filter items if (prop === categoryName) {
if (prop == categoryName) {
rObj.active = false; rObj.active = false;
} }
} else { } else {
// Create new facet item
rObj = new FacetItem(facetItem.val, facetItem.count); rObj = new FacetItem(facetItem.val, facetItem.count);
} }
return rObj; return rObj;
}); }).filter(el => el.count > 0); // Filter out items with count <= 0
this.facets[prop] = facetValues; this.facets[prop] = facetValues;
} }
} }
}
}, },
error: (error: string) => this.errorHandler(error), error: (error: string) => this.errorHandler(error),
complete: () => console.log("clear facet category completed"), complete: () => console.log("Clear facet category completed"),
}); });
} }
} }

View File

@ -1,116 +1,51 @@
<template> <template>
<div id="page_style" class="rows site-content page__style page__description" autocomplete="off"> <div id="page_style" class="rows site-content page__style page__description" autocomplete="off">
<!-- Search input section -->
<div class="container-fluid banner mz-5"> <div class="container-fluid banner mz-5">
<vs-input v-bind:propDisplay="searchTerm" v-bind:placeholder="'Enter your search term...'" @search-change="onSearch"></vs-input> <vs-input v-bind:propDisplay="searchTerm" v-bind:placeholder="'Enter your search term...'" @search-change="onSearch"></vs-input>
</div> </div>
<div class="column is-half is-offset-one-quarter" style="padding-top: 0; margin-top: 0">
<!-- <div class="tabs is-centered">
<ul id="id-results-tabs">
<li class="search_tab is-active">
<a target="_self">Web</a>
</li>
<li class="search_tab">
<a target="_self">Images</a>
</li>
<li class="search_tab">
<a target="_self">Videos</a>
</li>
<li class="search_tab">
<a target="_self">Homepages</a>
</li>
<li class="search_tab">
<a target="_self">Food</a>
</li>
<li class="search_tab">
<a target="_self">Books</a>
</li>
</ul>
</div> -->
<div v-if="results.length > 0" class="result-list-info">
<div v-if="hasSearchTerm()" class="resultheader">
Your search term {{ "'" + stringSearchTerm + "'" }} yielded <strong>{{ numFound }}</strong> results:
</div>
</div>
<div v-else-if="results.length == 0">
<div class="resultheader">
Your search yielded
<strong> 0</strong> results:
</div>
</div>
<!-- <div id="id-pro-sign-in" class="notification" style="">
<button
class="delete"
onclick="dismissProNotification();"
></button>
It appears that you're not signed in. You'll need to
<a href="/pro" target="_self">signup for Pro</a> or become a
<a
href="https://coil.com/?ref=InfinitySearch2229"
target="_self"
>Coil member</a
>
to access our results.
</div> -->
</div>
<!-- Results area on top of the list of publications -->
<div class="columns"> <div class="columns">
<div id="id-side-bar" class="column is-4 sidebar_column" style="padding-top: 1.2rem; padding-right: 1.5rem; padding-left: 1.5rem"> <!-- Left sidebar section. Empty, just to keep simetry -->
<div id="id-side-bar" class="column is-4 sidebar_column" style="padding-top: 0rem; padding-right: 1.5rem; padding-left: 1.5rem">
</div>
<!-- Results section -->
<div class="col col-8 column is-8 results_column" style="padding-top: 0.5rem; padding-right: 1rem; padding-left: 1rem; padding-bottom: 0rem;">
<!-- Display results if any -->
<div v-if="results.length > 0" class="result-list-info">
<div v-if="hasSearchTerm()" class="p-1 mb-0 text-sm bg-[#d8f4f7] rounded-lg" role="alert">
<span class="font-medium pl-5">Your search term</span> <span class="font-semibold">{{ "'" + stringSearchTerm + "'" }}</span> yielded <strong>{{ numFound }}</strong> results:
</div>
</div>
<!-- Display message if no results found -->
<div v-else-if="results.length == 0">
<div class="p-1 mb-0 text-sm bg-[#d8f4f7] rounded-lg" role="alert">
<!-- <span class="font-medium pl-5">Your search yielded <strong> 0</strong> results.</span> -->
<span class="font-medium pl-5">Your search term</span> <span class="font-semibold">{{ "'" + stringSearchTerm + "'" }}</span> yielded <strong>0</strong> results:
</div>
</div>
</div>
</div>
<!-- Area with the list of facets (left) and list of publications (right) -->
<div class="columns">
<!-- Sidebar with facets -->
<div id="id-side-bar" class="column is-4 sidebar_column" style="padding-top: 0.5rem; padding-right: 1.5rem; padding-left: 1.5rem">
<div id="externals" class=""> <div id="externals" class="">
<div v-for="(facetItems, key, index) in facets" v-bind:key="index" name="external_card" style="margin-bottom: 0px"> <div v-for="(facetItems, key, index) in facets" v-bind:key="index" name="external_card" style="margin-bottom: 0px">
<facet-category v-bind:facetItems="facetItems" v-bind:filterName="key" @filter="onFilter"></facet-category> <facet-category v-bind:facetItems="facetItems" v-bind:filterName="key" @filter="onFilter"></facet-category>
</div> </div>
<!-- <div class="card" name="external_card" style="margin-bottom: 0px">
<header class="card-header">
<p class="card-header-title" style="font-weight: normal; padding-right: 5px">
<a
id="https://en.wikipedia.org/w/index.php?search="
href="https://en.wikipedia.org/w/index.php?search=test"
name="external_link_0"
style="display: block"
rel="noreferrer noopener"
target="_self"
>
<img src="/static/images/favicons/wikipedia.ico" name="external_icon_0'" class="external-icon" />
<span hidden="" class="external-text" name="external_text_0" style="font-size: 0.95rem; display: inline"> Wikipedia Results </span>
</a>
</p>
</header>
</div>
<div class="card" name="external_card" style="margin-bottom: 0px">
<header class="card-header">
<p class="card-header-title" style="font-weight: normal; padding-right: 5px">
<a
id="https://duckduckgo.com/?q="
href="https://duckduckgo.com/?q=test"
name="external_link_1"
style="display: block"
rel="noreferrer noopener"
target="_self"
>
<img src="/static/images/favicons/duckduckgo.ico" name="external_icon_1'" class="external-icon" />
<span hidden="" class="external-text" name="external_text_1" style="font-size: 0.95rem; display: inline"> DuckDuckGo Results </span>
</a>
</p>
<span
class="clickableIcon"
onclick="removeExternalClickable(this)"
style="cursor: pointer; display: block; margin: auto; margin-right: 6px; min-width: 0.5em"
>
<img
class="handle fa-ellipsis-v"
src="/static/images/fa/ellipsis-v.svg"
style="cursor: pointer; display: block; margin: auto; margin-right: 12px; min-width: 0.5em; width: 16px; height: 16px"
/>
</span>
</header>
</div> -->
</div> </div>
</div> </div>
<div class="col col-8 column is-8 results_column" style="padding-top: 1.2rem; padding-right: 1rem; padding-left: 1rem"> <!-- Main results section with pagination and active filters -->
<div class="col col-8 column is-8 results_column" style="padding-top: 0.5rem; padding-right: 1rem; padding-left: 1rem">
<div v-if="activeFilterCategories && Object.keys(activeFilterCategories).length > 0" class="column"> <div v-if="activeFilterCategories && Object.keys(activeFilterCategories).length > 0" class="column">
<span v-for="(values, key, index) in activeFilterCategories" v-bind:key="index" class="active-filter-items"> <span v-for="(values, key, index) in activeFilterCategories" v-bind:key="index" class="active-filter-items">
<!-- Active filter categories -->
<active-facet-category <active-facet-category
v-bind:filterItems="values" v-bind:filterItems="values"
v-bind:categoryName="key" v-bind:categoryName="key"
@ -119,38 +54,58 @@
</span> </span>
</div> </div>
<div class="results"> <div class="results">
<!-- Pagination before search results -->
<PaginationComponent class="mb-5" v-bind:data="pagination" @menu-click="onMenuClick"></PaginationComponent>
<!-- Results section --> <!-- Results section -->
<vs-result v-bind:datasets="results"></vs-result> <vs-result v-bind:datasets="results"></vs-result>
<!-- Pagination after search results -->
<PaginationComponent class="mt-5" v-bind:data="pagination" @menu-click="onMenuClick"></PaginationComponent>
</div> </div>
</div> </div>
</div> </div>
<div class="container-fluid" style="padding-top: 3.8em"> <!-- Partner logos section -->
<div class="columns is-mobile partner-logos"> <div class="container-fluid">
<div class="column col-sm text-center"> <!-- <div class="columns is-mobile partner-logos"> -->
<a target="_blank" href="https://www.re3data.org/repository/r3d100013400" <div class="columns">
><img src="@/assets/site/img/re3-data-logo-mono.jpg" alt="re3 data logo" <div class="column col-sm">
/></a> <div class="card mx-auto" style="width: 18rem; box-shadow: none; border: 0rem">
<div class="card-body">
<!-- <h5 class="card-title">About TETHYS</h5> -->
<a target="_blank" href="https://www.re3data.org/repository/r3d100013400">
<img src="@/assets/site/img/re3-data-logo-mono.jpg" alt="re3 data logo" />
</a>
</div> </div>
<div class="column col-sm text-center"> </div>
</div>
<div class="column col-sm">
<div class="card mx-auto" style="width: 28rem; box-shadow: none; border: 0rem">
<div class="card-body">
<a target="_blank" href="http://www.geosphere.at/"> <a target="_blank" href="http://www.geosphere.at/">
<img src="@/assets/site/img/geosphere-austria-logo.jpg" alt="logo geosphere austria" /> <img src="@/assets/site/img/geosphere-austria-logo.jpg" alt="logo geosphere austria" />
<!-- <img src="@/assets/site/img/gbaLogoRGB_web.png" alt="GeoSphere Austria logo" /> -->
</a> </a>
</div> </div>
<div class="column col-sm text-center"> </div>
</div>
<div class="column col-sm">
<div class="card mx-auto" style="width: 18rem; box-shadow: none; border: 0rem">
<div class="card-body">
<a target="_blank" href="https://www.base-search.net/Search/Results?q=coll:fttethysrdr&refid=dctablede"> <a target="_blank" href="https://www.base-search.net/Search/Results?q=coll:fttethysrdr&refid=dctablede">
<img src="@/assets/site/img/base-logo.gif" alt="logo base" /> <img src="@/assets/site/img/base_logo.png" alt="logo base" />
</a> </a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import SearchViewComponent from "./search-view-component"; import SearchViewComponent from "./search-view-component";
export default SearchViewComponent; export default SearchViewComponent;
// import tailwind ss
import "@/index.css";
</script> </script>
<style scoped></style> <style scoped></style>

101
tailwind.config.js Normal file
View File

@ -0,0 +1,101 @@
/** @type {import('tailwindcss').Config} */
const plugin = require('tailwindcss/plugin');
module.exports = {
// purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
content: ["./src/**/*.{edge,js,ts,jsx,tsx,vue}"],
darkMode: "media", // or 'media' or 'class'
theme: {
asideScrollbars: {
light: 'light',
gray: 'gray',
},
extend: {
colors: {
primary: "#22C55E",
inprogress: "rgb(94 234 212)",
released: "rgb(52 211 153)",
"editor-accepted": "rgb(125 211 252)",
approved: "#BFCE40",
"rejected-editor": "#f97316",
"rejected-reviewer": "#f97316",
reviewed: "#34d399", // emerald
published: "#34d399", // sky
"primary-dark": "#DCFCE7",
lime: {
DEFAULT: "#BFCE40",
dark: "rgba(5,46,55,0.7)",
50: "#FBFCF7",
100: "#F8FBE1",
200: "#EEF69E",
300: "#DCEC53",
400: "#A8D619",
500: "#65DC21",
600: "#429E04",
700: "#357C06",
800: "#295B09",
900: "#20450A",
},
},
zIndex: {
'-1': '-1',
},
flexGrow: {
5: '5',
},
maxHeight: {
'screen-menu': 'calc(100vh - 3.5rem)',
'modal': 'calc(100vh - 160px)',
},
transitionProperty: {
position: 'right, left, top, bottom, margin, padding',
textColor: 'color',
},
keyframes: {
'fade-out': {
from: { opacity: 1 },
to: { opacity: 0 },
},
'fade-in': {
from: { opacity: 0 },
to: { opacity: 1 },
},
},
animation: {
'fade-out': 'fade-out 250ms ease-in-out',
'fade-in': 'fade-in 250ms ease-in-out',
},
},
},
plugins: [
require('@tailwindcss/forms'),
plugin(function ({ matchUtilities, theme }) {
matchUtilities(
{
'aside-scrollbars': (value) => {
const track = value === 'light' ? '100' : '900';
const thumb = value === 'light' ? '300' : '600';
const color = value === 'light' ? 'gray' : value;
return {
'scrollbarWidth': 'thin',
'scrollbarColor': `${theme(`colors.${color}.${thumb}`)} ${theme(`colors.${color}.${track}`)}`,
'&::-webkit-scrollbar': {
width: '8px',
height: '8px',
},
'&::-webkit-scrollbar-track': {
backgroundColor: theme(`colors.${color}.${track}`),
},
'&::-webkit-scrollbar-thumb': {
borderRadius: '0.25rem',
backgroundColor: theme(`colors.${color}.${thumb}`),
},
};
},
},
{ values: theme('asideScrollbars') },
);
}),
],
};

View File

@ -36,6 +36,7 @@
"tests/**/*.tsx" "tests/**/*.tsx"
], ],
"exclude": [ "exclude": [
"node_modules" "node_modules",
"dist"
] ]
} }

View File

@ -1,10 +1,11 @@
/* eslint @typescript-eslint/no-var-requires: "off" */ /* eslint @typescript-eslint/no-var-requires: "off" */
const webpack = require("webpack"); const webpack = require("webpack");
// const { defineConfig } = require("@vue/cli-service"); // const { defineConfig } = require("@vue/cli-service");
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin"); // const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
const { VueLoaderPlugin } = require("vue-loader"); const { VueLoaderPlugin } = require("vue-loader");
module.exports = { module.exports = {
lintOnSave: false,
publicPath: "/", publicPath: "/",
chainWebpack: (config) => { chainWebpack: (config) => {
const vueRule = config.module.rule("vue"); const vueRule = config.module.rule("vue");
@ -15,7 +16,7 @@ module.exports = {
// .tap(args => { // .tap(args => {
// args[0] = { // args[0] = {
// ...args[0], // ...args[0],
// VUE_APP_PORTAL: JSON.stringify(process.env.PORTAL), // VUE_API: JSON.stringify(process.env.PORTAL),
// // other stuff // // other stuff
// } // }
// return args // return args
@ -81,14 +82,20 @@ module.exports = {
extensions: [".tsx", ".ts", ".mjs", ".js", ".jsx", ".vue", ".json", ".wasm"], extensions: [".tsx", ".ts", ".mjs", ".js", ".jsx", ".vue", ".json", ".wasm"],
}, },
plugins: [ plugins: [
// new VueLoaderPlugin(), new VueLoaderPlugin(),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
__VUE_OPTIONS_API__: "true",
__VUE_PROD_DEVTOOLS__: "false",
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: "false",
APP_URL: JSON.stringify(process.env.APP_URL), APP_URL: JSON.stringify(process.env.APP_URL),
VUE_APP_PORTAL: JSON.stringify(process.env.VUE_APP_PORTAL), VUE_API: JSON.stringify(process.env.VUE_API),
SOLR_HOST: JSON.stringify(process.env.SOLR_HOST), // SOLR_HOST: JSON.stringify(process.env.SOLR_HOST),
SOLR_CORE: JSON.stringify(process.env.SOLR_CORE), // SOLR_CORE: JSON.stringify(process.env.SOLR_CORE),
// OPENSEARCH
OPEN_HOST: JSON.stringify(process.env.OPEN_HOST),
OPEN_CORE: JSON.stringify(process.env.OPEN_CORE),
}), }),
new NodePolyfillPlugin(), // new NodePolyfillPlugin(),
], ],
}, },
}; };