mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge remote-tracking branch 'dspace/main' into accessibility-settings-main
This commit is contained in:
@@ -12,7 +12,6 @@
|
||||
"eslint-plugin-rxjs",
|
||||
"eslint-plugin-simple-import-sort",
|
||||
"eslint-plugin-import-newlines",
|
||||
"eslint-plugin-jsonc",
|
||||
"dspace-angular-ts",
|
||||
"dspace-angular-html"
|
||||
],
|
||||
@@ -303,10 +302,13 @@
|
||||
"*.json5"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:jsonc/recommended-with-jsonc"
|
||||
"plugin:jsonc/recommended-with-json5"
|
||||
],
|
||||
"rules": {
|
||||
"no-irregular-whitespace": "error",
|
||||
// The ESLint core no-irregular-whitespace rule doesn't work well in JSON
|
||||
// See: https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-irregular-whitespace.html
|
||||
"no-irregular-whitespace": "off",
|
||||
"jsonc/no-irregular-whitespace": "error",
|
||||
"no-trailing-spaces": "error",
|
||||
"jsonc/comma-dangle": [
|
||||
"error",
|
||||
|
@@ -93,7 +93,10 @@ services:
|
||||
volumes:
|
||||
# Keep Solr data directory between reboots
|
||||
- solr_data:/var/solr/data
|
||||
# Initialize all DSpace Solr cores using the mounted configsets (see above), then start Solr
|
||||
# NOTE: We are not running Solr as "root", but we need root permissions to copy our cores to the mounted
|
||||
# /var/solr/data directory. Then we start Solr as the "solr" user.
|
||||
user: root
|
||||
# Initialize all DSpace Solr cores, then start Solr
|
||||
entrypoint:
|
||||
- /bin/bash
|
||||
- '-c'
|
||||
@@ -111,7 +114,8 @@ services:
|
||||
cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent
|
||||
precreate-core suggestion /opt/solr/server/solr/configsets/suggestion
|
||||
cp -r /opt/solr/server/solr/configsets/suggestion/* suggestion
|
||||
exec solr -f
|
||||
chown -R solr:solr /var/solr
|
||||
runuser -u solr -- solr-foreground
|
||||
volumes:
|
||||
assetstore:
|
||||
pgdata:
|
||||
|
@@ -97,11 +97,16 @@ services:
|
||||
volumes:
|
||||
# Keep Solr data directory between reboots
|
||||
- solr_data:/var/solr/data
|
||||
# NOTE: We are not running Solr as "root", but we need root permissions to copy our cores to the mounted
|
||||
# /var/solr/data directory. Then we start Solr as the "solr" user.
|
||||
user: root
|
||||
# Initialize all DSpace Solr cores using the mounted local configsets (see above), then start Solr
|
||||
# * First, run precreate-core to create the core (if it doesn't yet exist). If exists already, this is a no-op
|
||||
# * Second, copy configsets to this core:
|
||||
# Updates to Solr configs require the container to be rebuilt/restarted:
|
||||
# `docker compose -p d7 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d --build dspacesolr`
|
||||
# * Third, ensure all new folders are owned by "solr" user
|
||||
# * Finally, start Solr as the "solr" user via the provided solr-foreground script
|
||||
entrypoint:
|
||||
- /bin/bash
|
||||
- '-c'
|
||||
@@ -119,7 +124,8 @@ services:
|
||||
cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent
|
||||
precreate-core suggestion /opt/solr/server/solr/configsets/suggestion
|
||||
cp -r /opt/solr/server/solr/configsets/suggestion/* suggestion
|
||||
exec solr -f
|
||||
chown -R solr:solr /var/solr
|
||||
runuser -u solr -- solr-foreground
|
||||
volumes:
|
||||
assetstore:
|
||||
pgdata:
|
||||
|
195
package-lock.json
generated
195
package-lock.json
generated
@@ -20,8 +20,8 @@
|
||||
"@angular/platform-browser-dynamic": "^18.2.12",
|
||||
"@angular/platform-server": "^18.2.12",
|
||||
"@angular/router": "^18.2.12",
|
||||
"@angular/ssr": "^18.2.18",
|
||||
"@babel/runtime": "7.27.0",
|
||||
"@angular/ssr": "^18.2.19",
|
||||
"@babel/runtime": "7.27.1",
|
||||
"@kolkov/ngx-gallery": "^2.0.1",
|
||||
"@ng-bootstrap/ng-bootstrap": "^12.0.0",
|
||||
"@ng-dynamic-forms/core": "^16.0.0",
|
||||
@@ -35,7 +35,7 @@
|
||||
"@terraformer/wkt": "^2.2.1",
|
||||
"altcha": "^0.9.0",
|
||||
"angulartics2": "^12.2.0",
|
||||
"axios": "^1.8.4",
|
||||
"axios": "^1.9.0",
|
||||
"bootstrap": "^5.3",
|
||||
"cerialize": "0.1.18",
|
||||
"cli-progress": "^3.12.0",
|
||||
@@ -53,7 +53,7 @@
|
||||
"filesize": "^10.1.6",
|
||||
"http-proxy-middleware": "^2.0.9",
|
||||
"http-terminator": "^3.2.0",
|
||||
"isbot": "^5.1.26",
|
||||
"isbot": "^5.1.27",
|
||||
"js-cookie": "2.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json5": "^2.2.3",
|
||||
@@ -86,7 +86,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-builders/custom-webpack": "~18.0.0",
|
||||
"@angular-devkit/build-angular": "^18.2.18",
|
||||
"@angular-devkit/build-angular": "^18.2.19",
|
||||
"@angular-eslint/builder": "^18.4.1",
|
||||
"@angular-eslint/bundled-angular-compiler": "^18.4.1",
|
||||
"@angular-eslint/eslint-plugin": "^18.4.1",
|
||||
@@ -94,13 +94,13 @@
|
||||
"@angular-eslint/schematics": "^18.4.1",
|
||||
"@angular-eslint/template-parser": "^18.4.1",
|
||||
"@angular-eslint/utils": "^18.4.1",
|
||||
"@angular/cli": "^18.2.18",
|
||||
"@angular/cli": "^18.2.19",
|
||||
"@angular/compiler-cli": "^18.2.12",
|
||||
"@angular/language-service": "^18.2.12",
|
||||
"@cypress/schematic": "^1.5.0",
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@ngrx/store-devtools": "^18.1.1",
|
||||
"@ngtools/webpack": "^18.2.18",
|
||||
"@ngtools/webpack": "^18.2.19",
|
||||
"@types/deep-freeze": "0.1.5",
|
||||
"@types/ejs": "^3.1.2",
|
||||
"@types/express": "^4.17.17",
|
||||
@@ -150,12 +150,12 @@
|
||||
"postcss-loader": "^4.0.3",
|
||||
"postcss-preset-env": "^7.4.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"sass": "~1.86.3",
|
||||
"sass": "~1.87.0",
|
||||
"sass-loader": "^12.6.0",
|
||||
"sass-resources-loader": "^2.2.5",
|
||||
"ts-node": "^8.10.2",
|
||||
"typescript": "~5.4.5",
|
||||
"webpack": "5.99.5",
|
||||
"webpack": "5.99.7",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.15.1"
|
||||
}
|
||||
@@ -266,13 +266,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/architect": {
|
||||
"version": "0.1802.18",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.18.tgz",
|
||||
"integrity": "sha512-3OitvTddHp7bSqEGOJlH7Zqv07DdmZHktU2jsekjcbUxmoC1WIpWSYy+Bqyu7HjidJc0xVP7wyE/NPYkrwT5SA==",
|
||||
"version": "0.1802.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.19.tgz",
|
||||
"integrity": "sha512-M4B1tzxGX1nWCZr9GMM8OO0yBJO2HFSdK8M8P74vEFQfKIeq3y16IQ5zlEveJrkCOFVtmlIy2C9foMCdNyBRMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "18.2.18",
|
||||
"@angular-devkit/core": "18.2.19",
|
||||
"rxjs": "7.8.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -291,17 +291,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/build-angular": {
|
||||
"version": "18.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.18.tgz",
|
||||
"integrity": "sha512-yNw5b46BB27YW2lgP9pAt15xtfTS8F1JdWR79bLci0MYL7VPmRBrRtZk+sozRCziit1+oNAVpOUT8QyvDmvAZA==",
|
||||
"version": "18.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.19.tgz",
|
||||
"integrity": "sha512-xwY7v+nGE7TXOc4pgY6u57bLzIPSHuecosYr3TiWHAl9iEcKHzkCCFKsLZyunohHmq/i1uA6g3cC6iwp2xNYyg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "2.3.0",
|
||||
"@angular-devkit/architect": "0.1802.18",
|
||||
"@angular-devkit/build-webpack": "0.1802.18",
|
||||
"@angular-devkit/core": "18.2.18",
|
||||
"@angular/build": "18.2.18",
|
||||
"@angular-devkit/architect": "0.1802.19",
|
||||
"@angular-devkit/build-webpack": "0.1802.19",
|
||||
"@angular-devkit/core": "18.2.19",
|
||||
"@angular/build": "18.2.19",
|
||||
"@babel/core": "7.26.10",
|
||||
"@babel/generator": "7.26.10",
|
||||
"@babel/helper-annotate-as-pure": "7.25.9",
|
||||
@@ -312,7 +312,7 @@
|
||||
"@babel/preset-env": "7.26.9",
|
||||
"@babel/runtime": "7.26.10",
|
||||
"@discoveryjs/json-ext": "0.6.1",
|
||||
"@ngtools/webpack": "18.2.18",
|
||||
"@ngtools/webpack": "18.2.19",
|
||||
"ansi-colors": "4.1.3",
|
||||
"autoprefixer": "10.4.20",
|
||||
"babel-loader": "9.1.3",
|
||||
@@ -322,7 +322,7 @@
|
||||
"css-loader": "7.1.2",
|
||||
"esbuild-wasm": "0.23.0",
|
||||
"fast-glob": "3.3.2",
|
||||
"http-proxy-middleware": "3.0.3",
|
||||
"http-proxy-middleware": "3.0.5",
|
||||
"https-proxy-agent": "7.0.5",
|
||||
"istanbul-lib-instrument": "6.0.3",
|
||||
"jsonc-parser": "3.3.1",
|
||||
@@ -418,13 +418,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/build-webpack": {
|
||||
"version": "0.1802.18",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.18.tgz",
|
||||
"integrity": "sha512-xSiUC2EeELKgs70aceet/iK57y2nk6VobgeeQzGzTtE5HXWX0n5/g9FIOVM1rznv/tj+9VFZpQKCdLqiP7JmCQ==",
|
||||
"version": "0.1802.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.19.tgz",
|
||||
"integrity": "sha512-axz1Sasn+c+GJpJexBL+B3Rh1w3wJrQq8k8gkniodjJ594p4ti2qGk7i9Tj8A4cXx5fGY+EpuZvKfI/9Tr7QwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/architect": "0.1802.18",
|
||||
"@angular-devkit/architect": "0.1802.19",
|
||||
"rxjs": "7.8.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -694,10 +694,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/build-angular/node_modules/http-proxy-middleware": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz",
|
||||
"integrity": "sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz",
|
||||
"integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/http-proxy": "^1.17.15",
|
||||
"debug": "^4.3.6",
|
||||
@@ -740,6 +741,7 @@
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -1083,9 +1085,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/build-angular/node_modules/webpack-dev-server/node_modules/http-proxy-middleware": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.8.tgz",
|
||||
"integrity": "sha512-/iazaeFPmL8KLA6QB7DFAU4O5j+9y/TA0D019MbLtPuFI56VK4BXFzM6j6QS9oGpScy8IIDH4S2LHv3zg/63Bw==",
|
||||
"version": "2.0.9",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz",
|
||||
"integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1149,9 +1151,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/core": {
|
||||
"version": "18.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.18.tgz",
|
||||
"integrity": "sha512-gncn8QN73mi4in7oAfoWnJglLx5iI8d87796h1LTuAxULSkfzhW3E03NZU764FBiIAWFxuty4PWmrHxMlmbtbw==",
|
||||
"version": "18.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz",
|
||||
"integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1186,13 +1188,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/schematics": {
|
||||
"version": "18.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.18.tgz",
|
||||
"integrity": "sha512-i7dy3x32Z8+lmVMKlKHdrSuCya5hUP24BOUn5lXKFAFGcJC0JT30OJrDPqQMA2RzNQiiyacPhxaCdLloEFVh3Q==",
|
||||
"version": "18.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.19.tgz",
|
||||
"integrity": "sha512-P/0KjkzOf2ZShuShx3cBbjLI7XlcS6B/yCRBo1MQfCC4cZfmzPQoUEOSQeYZgy5pnC24f+dKh/+TWc5uYL/Lvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "18.2.18",
|
||||
"@angular-devkit/core": "18.2.19",
|
||||
"jsonc-parser": "3.3.1",
|
||||
"magic-string": "0.30.11",
|
||||
"ora": "5.4.1",
|
||||
@@ -1324,14 +1326,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build": {
|
||||
"version": "18.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.18.tgz",
|
||||
"integrity": "sha512-8PEhrkS1t9xpvBLaLVgi0OWt/0B72ENKKVc6BAKEZ5gg+SD7uf47sJcT1d23r7d/V6FaOJnWim6BrqgFs4rW9A==",
|
||||
"version": "18.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.19.tgz",
|
||||
"integrity": "sha512-dTqR+mhcZWtCRyOafvzHNVpYxMQnt8HHHqNM0kyEMzcztXL2L9zDlKr0H9d+AgGGq/v4qwCh+1gFDxsHByZwMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "2.3.0",
|
||||
"@angular-devkit/architect": "0.1802.18",
|
||||
"@angular-devkit/architect": "0.1802.19",
|
||||
"@babel/core": "7.25.2",
|
||||
"@babel/helper-annotate-as-pure": "7.24.7",
|
||||
"@babel/helper-split-export-declaration": "7.24.7",
|
||||
@@ -1797,9 +1799,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/@types/node": {
|
||||
"version": "22.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz",
|
||||
"integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==",
|
||||
"version": "22.15.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz",
|
||||
"integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -1856,9 +1858,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@angular/build/node_modules/vite": {
|
||||
"version": "5.4.17",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.17.tgz",
|
||||
"integrity": "sha512-5+VqZryDj4wgCs55o9Lp+p8GE78TLVg0lasCH5xFZ4jacZjtqZa6JUw9/p0WeAojaOfncSM6v77InkFPGnvPvg==",
|
||||
"version": "5.4.18",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.18.tgz",
|
||||
"integrity": "sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1971,18 +1973,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli": {
|
||||
"version": "18.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.18.tgz",
|
||||
"integrity": "sha512-UwwI03FVvTHbb9kgR9D0HdLajxsVm1jYkcWMfbSMnQGYM1qy1EWj9HvGnfIoQxAEzA8aeQbmsn9+h3w6MQmyCg==",
|
||||
"version": "18.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.19.tgz",
|
||||
"integrity": "sha512-LGVMTc36JQuw8QX8Sclxyei306EQW3KslopXbf7cfqt6D5/fHS+FqqA0O7V8ob/vOGMca+l6hQD27nW5Y3W6pA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/architect": "0.1802.18",
|
||||
"@angular-devkit/core": "18.2.18",
|
||||
"@angular-devkit/schematics": "18.2.18",
|
||||
"@angular-devkit/architect": "0.1802.19",
|
||||
"@angular-devkit/core": "18.2.19",
|
||||
"@angular-devkit/schematics": "18.2.19",
|
||||
"@inquirer/prompts": "5.3.8",
|
||||
"@listr2/prompt-adapter-inquirer": "2.0.15",
|
||||
"@schematics/angular": "18.2.18",
|
||||
"@schematics/angular": "18.2.19",
|
||||
"@yarnpkg/lockfile": "1.1.0",
|
||||
"ini": "4.1.3",
|
||||
"jsonc-parser": "3.3.1",
|
||||
@@ -2230,9 +2232,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/ssr": {
|
||||
"version": "18.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-18.2.18.tgz",
|
||||
"integrity": "sha512-WJ56mpiRGp18vcSH4jFHWR6dylBtUk3QOz+RQhuqYFPAfKk2YXEH5BiBXcjNicW5tIxaU8NlHfZVwWHQyEjpiA==",
|
||||
"version": "18.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-18.2.19.tgz",
|
||||
"integrity": "sha512-kMNPWZiLGhtrXFwQpDn1laKXxwMpaiXVajpDT7m/yQkyKMH5EbyZASFcyDHK6EsRV2LQsPaXeKzeQof/C1zNcw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"critters": "0.0.24",
|
||||
@@ -3783,13 +3785,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.27.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
|
||||
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz",
|
||||
"integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -5960,9 +5959,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ngtools/webpack": {
|
||||
"version": "18.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.18.tgz",
|
||||
"integrity": "sha512-rFTf3zrAMp7KJF8F/sOn0SNits+HhRaNKw4g20Pxk4QG5XZsXChsQIKrrzAnmlCfMb3nQmBnElAhr1rvBmzZWQ==",
|
||||
"version": "18.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.19.tgz",
|
||||
"integrity": "sha512-bExj5JrByKPibsqBbn5Pjn8lo91AUOTsyP2hgKpnOnmSr62rhWSiRwXltgz2MCiZRmuUznpt93WiOLixgYfYvQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -6958,14 +6957,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@schematics/angular": {
|
||||
"version": "18.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.18.tgz",
|
||||
"integrity": "sha512-ko5KmtCZz8SqZLKrNeqMauS2LPHBKf7mT01waoOD1uN2gQkSIiLzDEYuXOaIarG6VnxAy5pL6NjkD+EmPsH6eg==",
|
||||
"version": "18.2.19",
|
||||
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.19.tgz",
|
||||
"integrity": "sha512-s9aynH/fwB/LT94miVfsaL2C4Qd5BLgjMzWFx7iJ8Hyv7FjOBGYO6eGVovjCt2c6/abG+GQAk4EBOCfg3AUtCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "18.2.18",
|
||||
"@angular-devkit/schematics": "18.2.18",
|
||||
"@angular-devkit/core": "18.2.19",
|
||||
"@angular-devkit/schematics": "18.2.19",
|
||||
"jsonc-parser": "3.3.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -8830,9 +8829,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.8.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
|
||||
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
|
||||
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
@@ -9109,9 +9108,9 @@
|
||||
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
|
||||
},
|
||||
"node_modules/bootstrap": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
|
||||
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
|
||||
"version": "5.3.5",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.5.tgz",
|
||||
"integrity": "sha512-ct1CHKtiobRimyGzmsSldEtM03E8fcEX4Tb3dGXz1V8faRwM50+vfHwTzOxB3IlKO7m+9vTH3s/3C6T2EAPeTA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -9122,6 +9121,7 @@
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@popperjs/core": "^2.11.8"
|
||||
}
|
||||
@@ -11059,9 +11059,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
||||
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
@@ -14730,9 +14730,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/isbot": {
|
||||
"version": "5.1.26",
|
||||
"resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.26.tgz",
|
||||
"integrity": "sha512-3wqJEYSIm59dYQjEF7zJ7T42aqaqxbCyJQda5rKCudJykuAnISptCHR/GSGpOnw8UrvU+mGueNLRJS5HXnbsXQ==",
|
||||
"version": "5.1.27",
|
||||
"resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.27.tgz",
|
||||
"integrity": "sha512-V3W56Hnztt4Wdh3VUlAMbdNicX/tOM38eChW3a2ixP6KEBJAeehxzYzTD59JrU5NCTgBZwRt9lRWr8D7eMZVYQ==",
|
||||
"license": "Unlicense",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -20194,7 +20194,8 @@
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/regenerator-transform": {
|
||||
"version": "0.15.2",
|
||||
@@ -20676,9 +20677,9 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.86.3",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.86.3.tgz",
|
||||
"integrity": "sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw==",
|
||||
"version": "1.87.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.87.0.tgz",
|
||||
"integrity": "sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -20806,10 +20807,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/schema-utils": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
|
||||
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
|
||||
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"ajv": "^8.9.0",
|
||||
@@ -22770,14 +22772,15 @@
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.99.5",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.5.tgz",
|
||||
"integrity": "sha512-q+vHBa6H9qwBLUlHL4Y7L0L1/LlyBKZtS9FHNCQmtayxjI5RKC9yD8gpvLeqGv5lCQp1Re04yi0MF40pf30Pvg==",
|
||||
"version": "5.99.7",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.7.tgz",
|
||||
"integrity": "sha512-CNqKBRMQjwcmKR0idID5va1qlhrqVUKpovi+Ec79ksW8ux7iS1+A6VqzfZXgVYCFRKl7XL5ap3ZoMpwBJxcg0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.7",
|
||||
"@types/estree": "^1.0.6",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"@webassemblyjs/ast": "^1.14.1",
|
||||
"@webassemblyjs/wasm-edit": "^1.14.1",
|
||||
"@webassemblyjs/wasm-parser": "^1.14.1",
|
||||
@@ -22794,7 +22797,7 @@
|
||||
"loader-runner": "^4.2.0",
|
||||
"mime-types": "^2.1.27",
|
||||
"neo-async": "^2.6.2",
|
||||
"schema-utils": "^4.3.0",
|
||||
"schema-utils": "^4.3.2",
|
||||
"tapable": "^2.1.1",
|
||||
"terser-webpack-plugin": "^5.3.11",
|
||||
"watchpack": "^2.4.1",
|
||||
|
18
package.json
18
package.json
@@ -102,8 +102,8 @@
|
||||
"@angular/platform-browser-dynamic": "^18.2.12",
|
||||
"@angular/platform-server": "^18.2.12",
|
||||
"@angular/router": "^18.2.12",
|
||||
"@angular/ssr": "^18.2.18",
|
||||
"@babel/runtime": "7.27.0",
|
||||
"@angular/ssr": "^18.2.19",
|
||||
"@babel/runtime": "7.27.1",
|
||||
"@kolkov/ngx-gallery": "^2.0.1",
|
||||
"@ng-bootstrap/ng-bootstrap": "^12.0.0",
|
||||
"@ng-dynamic-forms/core": "^16.0.0",
|
||||
@@ -117,7 +117,7 @@
|
||||
"@terraformer/wkt": "^2.2.1",
|
||||
"altcha": "^0.9.0",
|
||||
"angulartics2": "^12.2.0",
|
||||
"axios": "^1.8.4",
|
||||
"axios": "^1.9.0",
|
||||
"bootstrap": "^5.3",
|
||||
"cerialize": "0.1.18",
|
||||
"cli-progress": "^3.12.0",
|
||||
@@ -135,7 +135,7 @@
|
||||
"filesize": "^10.1.6",
|
||||
"http-proxy-middleware": "^2.0.9",
|
||||
"http-terminator": "^3.2.0",
|
||||
"isbot": "^5.1.26",
|
||||
"isbot": "^5.1.27",
|
||||
"js-cookie": "2.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json5": "^2.2.3",
|
||||
@@ -168,7 +168,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-builders/custom-webpack": "~18.0.0",
|
||||
"@angular-devkit/build-angular": "^18.2.18",
|
||||
"@angular-devkit/build-angular": "^18.2.19",
|
||||
"@angular-eslint/builder": "^18.4.1",
|
||||
"@angular-eslint/bundled-angular-compiler": "^18.4.1",
|
||||
"@angular-eslint/eslint-plugin": "^18.4.1",
|
||||
@@ -176,13 +176,13 @@
|
||||
"@angular-eslint/schematics": "^18.4.1",
|
||||
"@angular-eslint/template-parser": "^18.4.1",
|
||||
"@angular-eslint/utils": "^18.4.1",
|
||||
"@angular/cli": "^18.2.18",
|
||||
"@angular/cli": "^18.2.19",
|
||||
"@angular/compiler-cli": "^18.2.12",
|
||||
"@angular/language-service": "^18.2.12",
|
||||
"@cypress/schematic": "^1.5.0",
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@ngrx/store-devtools": "^18.1.1",
|
||||
"@ngtools/webpack": "^18.2.18",
|
||||
"@ngtools/webpack": "^18.2.19",
|
||||
"@types/deep-freeze": "0.1.5",
|
||||
"@types/ejs": "^3.1.2",
|
||||
"@types/express": "^4.17.17",
|
||||
@@ -232,12 +232,12 @@
|
||||
"postcss-loader": "^4.0.3",
|
||||
"postcss-preset-env": "^7.4.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"sass": "~1.86.3",
|
||||
"sass": "~1.87.0",
|
||||
"sass-loader": "^12.6.0",
|
||||
"sass-resources-loader": "^2.2.5",
|
||||
"ts-node": "^8.10.2",
|
||||
"typescript": "~5.4.5",
|
||||
"webpack": "5.99.5",
|
||||
"webpack": "5.99.7",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.15.1"
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@
|
||||
</div>
|
||||
}
|
||||
@if (canImpersonate$ | async) {
|
||||
<div between class="btn-group ms-1">
|
||||
<div between class="btn-group">
|
||||
@if (!isImpersonated) {
|
||||
<button class="btn btn-primary" type="button" (click)="impersonate()">
|
||||
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.impersonate' | translate}}
|
||||
|
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
AsyncPipe,
|
||||
NgClass,
|
||||
} from '@angular/common';
|
||||
import { AsyncPipe } from '@angular/common';
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
@@ -84,7 +81,6 @@ import { ValidateEmailNotTaken } from './validators/email-taken.validator';
|
||||
FormComponent,
|
||||
AsyncPipe,
|
||||
TranslateModule,
|
||||
NgClass,
|
||||
ThemedLoadingComponent,
|
||||
PaginationComponent,
|
||||
RouterLink,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
@if (registryService.getActiveMetadataField() | async) {
|
||||
@if (activeMetadataField$ | async) {
|
||||
<h2>{{messagePrefix + '.edit' | translate}}</h2>
|
||||
} @else {
|
||||
<h2>{{messagePrefix + '.create' | translate}}</h2>
|
||||
|
@@ -19,7 +19,7 @@ import {
|
||||
TranslateModule,
|
||||
TranslateService,
|
||||
} from '@ngx-translate/core';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { Observable } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
|
||||
@@ -109,6 +109,8 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
@Output() submitForm: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
activeMetadataField$: Observable<MetadataField>;
|
||||
|
||||
constructor(public registryService: RegistryService,
|
||||
private formBuilderService: FormBuilderService,
|
||||
private translateService: TranslateService) {
|
||||
@@ -117,71 +119,65 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* Initialize the component, setting up the necessary Models for the dynamic form
|
||||
*/
|
||||
ngOnInit() {
|
||||
combineLatest([
|
||||
this.translateService.get(`${this.messagePrefix}.element`),
|
||||
this.translateService.get(`${this.messagePrefix}.qualifier`),
|
||||
this.translateService.get(`${this.messagePrefix}.scopenote`),
|
||||
]).subscribe(([element, qualifier, scopenote]) => {
|
||||
this.element = new DynamicInputModel({
|
||||
id: 'element',
|
||||
label: element,
|
||||
name: 'element',
|
||||
validators: {
|
||||
required: null,
|
||||
pattern: '^[^. ,]*$',
|
||||
maxLength: 64,
|
||||
},
|
||||
required: true,
|
||||
errorMessages: {
|
||||
pattern: 'error.validation.metadata.element.invalid-pattern',
|
||||
maxLength: 'error.validation.metadata.element.max-length',
|
||||
},
|
||||
});
|
||||
this.qualifier = new DynamicInputModel({
|
||||
id: 'qualifier',
|
||||
label: qualifier,
|
||||
name: 'qualifier',
|
||||
validators: {
|
||||
pattern: '^[^. ,]*$',
|
||||
maxLength: 64,
|
||||
},
|
||||
required: false,
|
||||
errorMessages: {
|
||||
pattern: 'error.validation.metadata.qualifier.invalid-pattern',
|
||||
maxLength: 'error.validation.metadata.qualifier.max-length',
|
||||
},
|
||||
});
|
||||
this.scopeNote = new DynamicTextAreaModel({
|
||||
id: 'scopeNote',
|
||||
label: scopenote,
|
||||
name: 'scopeNote',
|
||||
required: false,
|
||||
rows: 5,
|
||||
});
|
||||
this.formModel = [
|
||||
new DynamicFormGroupModel(
|
||||
{
|
||||
id: 'metadatadatafieldgroup',
|
||||
group:[this.element, this.qualifier, this.scopeNote],
|
||||
}),
|
||||
];
|
||||
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
||||
this.registryService.getActiveMetadataField().subscribe((field: MetadataField): void => {
|
||||
if (field == null) {
|
||||
this.clearFields();
|
||||
} else {
|
||||
this.formGroup.patchValue({
|
||||
metadatadatafieldgroup: {
|
||||
element: field.element,
|
||||
qualifier: field.qualifier,
|
||||
scopeNote: field.scopeNote,
|
||||
},
|
||||
});
|
||||
this.element.disabled = true;
|
||||
this.qualifier.disabled = true;
|
||||
}
|
||||
});
|
||||
ngOnInit(): void {
|
||||
this.activeMetadataField$ = this.registryService.getActiveMetadataField();
|
||||
this.element = new DynamicInputModel({
|
||||
id: 'element',
|
||||
label: this.translateService.instant(`${this.messagePrefix}.element`),
|
||||
name: 'element',
|
||||
validators: {
|
||||
required: null,
|
||||
pattern: '^[^. ,]*$',
|
||||
maxLength: 64,
|
||||
},
|
||||
required: true,
|
||||
errorMessages: {
|
||||
pattern: 'error.validation.metadata.element.invalid-pattern',
|
||||
maxLength: 'error.validation.metadata.element.max-length',
|
||||
},
|
||||
});
|
||||
this.qualifier = new DynamicInputModel({
|
||||
id: 'qualifier',
|
||||
label: this.translateService.instant(`${this.messagePrefix}.qualifier`),
|
||||
name: 'qualifier',
|
||||
validators: {
|
||||
pattern: '^[^. ,]*$',
|
||||
maxLength: 64,
|
||||
},
|
||||
required: false,
|
||||
errorMessages: {
|
||||
pattern: 'error.validation.metadata.qualifier.invalid-pattern',
|
||||
maxLength: 'error.validation.metadata.qualifier.max-length',
|
||||
},
|
||||
});
|
||||
this.scopeNote = new DynamicTextAreaModel({
|
||||
id: 'scopeNote',
|
||||
label: this.translateService.instant(`${this.messagePrefix}.scopenote`),
|
||||
name: 'scopeNote',
|
||||
required: false,
|
||||
rows: 5,
|
||||
});
|
||||
this.formModel = [
|
||||
new DynamicFormGroupModel({
|
||||
id: 'metadatadatafieldgroup',
|
||||
group:[this.element, this.qualifier, this.scopeNote],
|
||||
}),
|
||||
];
|
||||
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
||||
this.registryService.getActiveMetadataField().subscribe((field: MetadataField): void => {
|
||||
if (field == null) {
|
||||
this.clearFields();
|
||||
} else {
|
||||
this.formGroup.patchValue({
|
||||
metadatadatafieldgroup: {
|
||||
element: field.element,
|
||||
qualifier: field.qualifier,
|
||||
scopeNote: field.scopeNote,
|
||||
},
|
||||
});
|
||||
this.element.disabled = true;
|
||||
this.qualifier.disabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -33,12 +33,14 @@ import { reloadGuard } from './core/reload/reload.guard';
|
||||
import { forgotPasswordCheckGuard } from './core/rest-property/forgot-password-check-guard.guard';
|
||||
import { ServerCheckGuard } from './core/server-check/server-check.guard';
|
||||
import { ThemedForbiddenComponent } from './forbidden/themed-forbidden.component';
|
||||
import { homePageResolver } from './home-page/home-page.resolver';
|
||||
import { ITEM_MODULE_PATH } from './item-page/item-page-routing-paths';
|
||||
import { provideSuggestionNotificationsState } from './notifications/provide-suggestion-notifications-state';
|
||||
import { ThemedPageErrorComponent } from './page-error/themed-page-error.component';
|
||||
import { ThemedPageInternalServerErrorComponent } from './page-internal-server-error/themed-page-internal-server-error.component';
|
||||
import { ThemedPageNotFoundComponent } from './pagenotfound/themed-pagenotfound.component';
|
||||
import { PROCESS_MODULE_PATH } from './process-page/process-page-routing.paths';
|
||||
import { viewTrackerResolver } from './statistics/angulartics/dspace/view-tracker.resolver';
|
||||
import { provideSubmissionState } from './submission/provide-submission-state';
|
||||
import { SUGGESTION_MODULE_PATH } from './suggestions-page/suggestions-page-routing-paths';
|
||||
|
||||
@@ -61,9 +63,17 @@ export const APP_ROUTES: Route[] = [
|
||||
path: 'home',
|
||||
loadChildren: () => import('./home-page/home-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
data: { showBreadcrumbs: false, enableRSS: true },
|
||||
data: {
|
||||
showBreadcrumbs: false,
|
||||
enableRSS: true,
|
||||
dsoPath: 'site',
|
||||
},
|
||||
providers: [provideSuggestionNotificationsState()],
|
||||
canActivate: [endUserAgreementCurrentUserGuard],
|
||||
resolve: {
|
||||
site: homePageResolver,
|
||||
tracking: viewTrackerResolver,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'community-list',
|
||||
@@ -99,14 +109,12 @@ export const APP_ROUTES: Route[] = [
|
||||
path: COMMUNITY_MODULE_PATH,
|
||||
loadChildren: () => import('./community-page/community-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
data: { enableRSS: true },
|
||||
canActivate: [endUserAgreementCurrentUserGuard],
|
||||
},
|
||||
{
|
||||
path: COLLECTION_MODULE_PATH,
|
||||
loadChildren: () => import('./collection-page/collection-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
data: { enableRSS: true },
|
||||
canActivate: [endUserAgreementCurrentUserGuard],
|
||||
},
|
||||
{
|
||||
|
@@ -12,7 +12,7 @@
|
||||
}
|
||||
|
||||
<ng-template #breadcrumb let-text="text" let-url="url">
|
||||
<li class="breadcrumb-item"><div class="breadcrumb-item-limiter"><a [routerLink]="url" class="text-truncate" [ngbTooltip]="text | translate" placement="bottom" >{{text | translate}}</a></div></li>
|
||||
<li class="breadcrumb-item"><div class="breadcrumb-item-limiter"><a [routerLink]="url" class="text-truncate" [ngbTooltip]="text | translate" placement="bottom" role="link" tabindex="0">{{text | translate}}</a></div></li>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #activeBreadcrumb let-text="text">
|
||||
|
@@ -20,6 +20,8 @@
|
||||
<a class="btn btn-primary"
|
||||
[routerLink]="['/search']"
|
||||
[queryParams]="queryParams"
|
||||
[queryParamsHandling]="'merge'">
|
||||
[queryParamsHandling]="'merge'"
|
||||
role="link"
|
||||
tabindex="0">
|
||||
{{ 'browse.taxonomy.button' | translate }}</a>
|
||||
</section>
|
||||
|
@@ -9,6 +9,7 @@ import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso
|
||||
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
|
||||
import { ComcolSearchSectionComponent } from '../shared/comcol/sections/comcol-search-section/comcol-search-section.component';
|
||||
import { MenuRoute } from '../shared/menu/menu-route.model';
|
||||
import { viewTrackerResolver } from '../statistics/angulartics/dspace/view-tracker.resolver';
|
||||
import { collectionPageResolver } from './collection-page.resolver';
|
||||
import { collectionPageAdministratorGuard } from './collection-page-administrator.guard';
|
||||
import {
|
||||
@@ -99,6 +100,7 @@ export const ROUTES: Route[] = [
|
||||
data: {
|
||||
breadcrumbKey: 'collection.search',
|
||||
menuRoute: MenuRoute.COLLECTION_PAGE,
|
||||
enableRSS: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -115,6 +117,9 @@ export const ROUTES: Route[] = [
|
||||
},
|
||||
},
|
||||
],
|
||||
resolve: {
|
||||
tracking: viewTrackerResolver,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -5,7 +5,6 @@
|
||||
<div @fadeInOut>
|
||||
@if (collectionRD?.payload; as collection) {
|
||||
<div>
|
||||
<ds-view-tracker [object]="collection"></ds-view-tracker>
|
||||
<div class="d-flex flex-row border-bottom mb-4 pb-4">
|
||||
<header class="comcol-header me-auto">
|
||||
<!-- Collection Name -->
|
||||
|
@@ -47,7 +47,6 @@ import { ThemedLoadingComponent } from '../shared/loading/themed-loading.compone
|
||||
import { ObjectCollectionComponent } from '../shared/object-collection/object-collection.component';
|
||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||
import { VarDirective } from '../shared/utils/var.directive';
|
||||
import { ViewTrackerComponent } from '../statistics/angulartics/dspace/view-tracker.component';
|
||||
import { getCollectionPageRoute } from './collection-page-routing-paths';
|
||||
|
||||
@Component({
|
||||
@@ -64,7 +63,6 @@ import { getCollectionPageRoute } from './collection-page-routing-paths';
|
||||
ErrorComponent,
|
||||
ThemedLoadingComponent,
|
||||
TranslateModule,
|
||||
ViewTrackerComponent,
|
||||
VarDirective,
|
||||
AsyncPipe,
|
||||
ComcolPageHeaderComponent,
|
||||
|
@@ -12,7 +12,7 @@
|
||||
<div class="align-middle my-auto">
|
||||
@if ((dataSource.loading$ | async) !== true) {
|
||||
<button (click)="getNextPage(node)"
|
||||
class="btn btn-outline-primary btn-sm" role="button">
|
||||
class="btn btn-outline-primary btn-sm" role="button" tabindex="0">
|
||||
<i class="fas fa-angle-down"></i> {{ 'communityList.showMore' | translate }}
|
||||
</button>
|
||||
}
|
||||
@@ -34,7 +34,11 @@
|
||||
<button type="button" class="btn btn-default btn-transparent" cdkTreeNodeToggle
|
||||
[attr.aria-label]="(node.isExpanded ? 'communityList.collapse' : 'communityList.expand') | translate:{ name: dsoNameService.getName(node.payload) }"
|
||||
(click)="toggleExpanded(node)"
|
||||
data-test="expand-button">
|
||||
(keyup.enter)="toggleExpanded(node)"
|
||||
(keyup.space)="toggleExpanded(node)"
|
||||
data-test="expand-button"
|
||||
role="button"
|
||||
tabindex="0">
|
||||
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
||||
aria-hidden="true"></span>
|
||||
<span class="sr-only">{{ (node.isExpanded ? 'communityList.collapse' : 'communityList.expand') | translate:{ name: dsoNameService.getName(node.payload) } }}</span>
|
||||
@@ -48,7 +52,7 @@
|
||||
}
|
||||
<div class="d-flex flex-row">
|
||||
<span class="d-flex align-middle my-auto">
|
||||
<a [routerLink]="node.route" class="lead">{{ dsoNameService.getName(node.payload) }}</a>
|
||||
<a [routerLink]="node.route" class="lead" role="link" tabindex="0">{{ dsoNameService.getName(node.payload) }}</a>
|
||||
<span class="pe-2"> </span>
|
||||
@if (node.payload.archivedItemsCount >= 0) {
|
||||
<span class="badge rounded-pill bg-secondary align-top archived-items-lead my-auto ps-2 pe-2">{{node.payload.archivedItemsCount}}</span>
|
||||
@@ -88,7 +92,7 @@
|
||||
<span class="fa fa-chevron-right"></span>
|
||||
</span>
|
||||
<h6 class="align-middle my-auto">
|
||||
<a [routerLink]="node.route" class="lead">{{ dsoNameService.getName(node.payload) }}</a>
|
||||
<a [routerLink]="node.route" class="lead" role="link" tabindex="0">{{ dsoNameService.getName(node.payload) }}</a>
|
||||
</h6>
|
||||
</div>
|
||||
<ds-truncatable [id]="node.id">
|
||||
|
@@ -8,6 +8,7 @@ import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso
|
||||
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
|
||||
import { ComcolSearchSectionComponent } from '../shared/comcol/sections/comcol-search-section/comcol-search-section.component';
|
||||
import { MenuRoute } from '../shared/menu/menu-route.model';
|
||||
import { viewTrackerResolver } from '../statistics/angulartics/dspace/view-tracker.resolver';
|
||||
import { communityPageResolver } from './community-page.resolver';
|
||||
import { communityPageAdministratorGuard } from './community-page-administrator.guard';
|
||||
import {
|
||||
@@ -70,6 +71,9 @@ export const ROUTES: Route[] = [
|
||||
data: {
|
||||
menuRoute: MenuRoute.COMMUNITY_PAGE,
|
||||
},
|
||||
resolve: {
|
||||
tracking: viewTrackerResolver,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
@@ -86,6 +90,7 @@ export const ROUTES: Route[] = [
|
||||
data: {
|
||||
breadcrumbKey: 'community.search',
|
||||
menuRoute: MenuRoute.COMMUNITY_PAGE,
|
||||
enableRSS: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@@ -3,7 +3,6 @@
|
||||
<div class="community-page" @fadeInOut>
|
||||
@if (communityRD?.payload; as communityPayload) {
|
||||
<div>
|
||||
<ds-view-tracker [object]="communityPayload"></ds-view-tracker>
|
||||
<div class="d-flex flex-row border-bottom mb-4 pb-4">
|
||||
<header class="comcol-header me-auto">
|
||||
<!-- Community name -->
|
||||
|
@@ -38,7 +38,6 @@ import { hasValue } from '../shared/empty.util';
|
||||
import { ErrorComponent } from '../shared/error/error.component';
|
||||
import { ThemedLoadingComponent } from '../shared/loading/themed-loading.component';
|
||||
import { VarDirective } from '../shared/utils/var.directive';
|
||||
import { ViewTrackerComponent } from '../statistics/angulartics/dspace/view-tracker.component';
|
||||
import { getCommunityPageRoute } from './community-page-routing-paths';
|
||||
import { ThemedCollectionPageSubCollectionListComponent } from './sections/sub-com-col-section/sub-collection-list/themed-community-page-sub-collection-list.component';
|
||||
import { ThemedCommunityPageSubCommunityListComponent } from './sections/sub-com-col-section/sub-community-list/themed-community-page-sub-community-list.component';
|
||||
@@ -62,7 +61,6 @@ import { ThemedCommunityPageSubCommunityListComponent } from './sections/sub-com
|
||||
ComcolPageLogoComponent,
|
||||
ComcolPageHeaderComponent,
|
||||
AsyncPipe,
|
||||
ViewTrackerComponent,
|
||||
VarDirective,
|
||||
RouterOutlet,
|
||||
RouterModule,
|
||||
|
@@ -37,6 +37,8 @@ import {
|
||||
} from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
import objectContaining = jasmine.objectContaining;
|
||||
import { RestResponse } from '../cache/response.models';
|
||||
import { RequestEntry } from './request-entry.model';
|
||||
|
||||
describe('BitstreamDataService', () => {
|
||||
let service: BitstreamDataService;
|
||||
@@ -47,6 +49,7 @@ describe('BitstreamDataService', () => {
|
||||
let rdbService: RemoteDataBuildService;
|
||||
let bundleDataService: BundleDataService;
|
||||
const bitstreamFormatHref = 'rest-api/bitstreamformats';
|
||||
let responseCacheEntry: RequestEntry;
|
||||
|
||||
const bitstream1 = Object.assign(new Bitstream(), {
|
||||
id: 'fake-bitstream1',
|
||||
@@ -71,8 +74,13 @@ describe('BitstreamDataService', () => {
|
||||
const url = 'fake-bitstream-url';
|
||||
|
||||
beforeEach(() => {
|
||||
responseCacheEntry = new RequestEntry();
|
||||
responseCacheEntry.request = { href: 'https://rest.api/' } as any;
|
||||
responseCacheEntry.response = new RestResponse(true, 200, 'Success');
|
||||
|
||||
objectCache = jasmine.createSpyObj('objectCache', {
|
||||
remove: jasmine.createSpy('remove'),
|
||||
getByHref: observableOf(responseCacheEntry),
|
||||
});
|
||||
requestService = getMockRequestService();
|
||||
halService = Object.assign(new HALEndpointServiceStub(url));
|
||||
|
@@ -163,12 +163,25 @@ export class BitstreamDataService extends IdentifiableDataService<Bitstream> imp
|
||||
sendRequest(this.requestService),
|
||||
take(1),
|
||||
).subscribe(() => {
|
||||
this.requestService.removeByHrefSubstring(bitstream.self + '/format');
|
||||
this.deleteFormatCache(bitstream);
|
||||
});
|
||||
|
||||
return this.rdbService.buildFromRequestUUID(requestId);
|
||||
}
|
||||
|
||||
private deleteFormatCache(bitstream: Bitstream) {
|
||||
const bitsreamFormatUrl = bitstream.self + '/format';
|
||||
this.requestService.setStaleByHrefSubstring(bitsreamFormatUrl);
|
||||
// Delete also cache by uuid as the format could be cached also there
|
||||
this.objectCache.getByHref(bitsreamFormatUrl).pipe(take(1)).subscribe((cachedRequest) => {
|
||||
if (cachedRequest.requestUUIDs && cachedRequest.requestUUIDs.length > 0){
|
||||
const requestUuid = cachedRequest.requestUUIDs[0];
|
||||
if (this.requestService.hasByUUID(requestUuid)) {
|
||||
this.requestService.setStaleByUUID(requestUuid);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable of {@link RemoteData} of a {@link Bitstream}, based on a handle and an
|
||||
* optional sequenceId or filename, with a list of {@link FollowLinkConfig}, to automatically
|
||||
|
@@ -31,6 +31,7 @@ import {
|
||||
import {
|
||||
EPersonMock,
|
||||
EPersonMock2,
|
||||
EPersonMockWithNoName,
|
||||
} from '../../shared/testing/eperson.mock';
|
||||
import { GroupMock } from '../../shared/testing/group-mock';
|
||||
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
||||
@@ -281,6 +282,37 @@ describe('EPersonDataService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateEPerson with non existing metadata', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(service, 'findByHref').and.returnValue(createSuccessfulRemoteDataObject$(EPersonMockWithNoName));
|
||||
});
|
||||
describe('add name that was not previously set', () => {
|
||||
beforeEach(() => {
|
||||
const changedEPerson = Object.assign(new EPerson(), {
|
||||
id: EPersonMock.id,
|
||||
metadata: Object.assign(EPersonMock.metadata, {
|
||||
'eperson.firstname': [
|
||||
{
|
||||
language: null,
|
||||
value: 'User',
|
||||
},
|
||||
],
|
||||
}),
|
||||
email: EPersonMock.email,
|
||||
canLogIn: EPersonMock.canLogIn,
|
||||
requireCertificate: EPersonMock.requireCertificate,
|
||||
_links: EPersonMock._links,
|
||||
});
|
||||
service.updateEPerson(changedEPerson).subscribe();
|
||||
});
|
||||
it('should send PatchRequest with add email operation', () => {
|
||||
const operations = [{ op: 'add', path: '/eperson.firstname', value: [{ language: null, value: 'User' }] }];
|
||||
const expected = new PatchRequest(requestService.generateRequestId(), epersonsEndpoint + '/' + EPersonMock.uuid, operations);
|
||||
expect(requestService.send).toHaveBeenCalledWith(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearEPersonRequests', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(halService, 'getEndpoint').and.callFake((linkPath: string) => {
|
||||
|
@@ -269,7 +269,8 @@ export class EPersonDataService extends IdentifiableDataService<EPerson> impleme
|
||||
* @param newEPerson
|
||||
*/
|
||||
private generateOperations(oldEPerson: EPerson, newEPerson: EPerson): Operation[] {
|
||||
let operations = this.comparator.diff(oldEPerson, newEPerson).filter((operation: Operation) => operation.op === 'replace');
|
||||
let operations = this.comparator.diff(oldEPerson, newEPerson)
|
||||
.filter((operation: Operation) => ['replace', 'add'].includes(operation.op));
|
||||
if (hasValue(oldEPerson.email) && oldEPerson.email !== newEPerson.email) {
|
||||
operations = [...operations, {
|
||||
op: 'replace', path: '/email', value: newEPerson.email,
|
||||
|
@@ -1,34 +1,26 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
Router,
|
||||
UrlTree,
|
||||
} from '@angular/router';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
import {
|
||||
combineLatest as observableCombineLatest,
|
||||
Observable,
|
||||
of as observableOf,
|
||||
} from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
|
||||
import { environment } from '../../../../environments/environment.test';
|
||||
import { getMockRemoteDataBuildService } from '../../../shared/mocks/remote-data-build.service.mock';
|
||||
import { getMockRequestService } from '../../../shared/mocks/request.service.mock';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||
import { FacetValues } from '../../../shared/search/models/facet-values.model';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||
import { SearchFilterConfig } from '../../../shared/search/models/search-filter-config.model';
|
||||
import { SearchObjects } from '../../../shared/search/models/search-objects.model';
|
||||
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||
import { HALEndpointServiceStub } from '../../../shared/testing/hal-endpoint-service.stub';
|
||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||
import { routeServiceStub } from '../../../shared/testing/route-service.stub';
|
||||
import { RouterStub } from '../../../shared/testing/router.stub';
|
||||
import { SearchConfigurationServiceStub } from '../../../shared/testing/search-configuration-service.stub';
|
||||
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
||||
import { CommunityDataService } from '../../data/community-data.service';
|
||||
import { DSpaceObjectDataService } from '../../data/dspace-object-data.service';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { RequestService } from '../../data/request.service';
|
||||
import { RequestEntry } from '../../data/request-entry.model';
|
||||
import { RequestEntryState } from '../../data/request-entry-state.model';
|
||||
import { PaginationService } from '../../pagination/pagination.service';
|
||||
import { RouteService } from '../../services/route.service';
|
||||
import { HALEndpointService } from '../hal-endpoint.service';
|
||||
@@ -36,7 +28,8 @@ import { ViewMode } from '../view-mode.model';
|
||||
import { SearchService } from './search.service';
|
||||
import { SearchConfigurationService } from './search-configuration.service';
|
||||
import anything = jasmine.anything;
|
||||
|
||||
import SpyObj = jasmine.SpyObj;
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: '',
|
||||
@@ -47,179 +40,302 @@ class DummyComponent {
|
||||
}
|
||||
|
||||
describe('SearchService', () => {
|
||||
describe('By default', () => {
|
||||
let searchService: SearchService;
|
||||
const router = new RouterStub();
|
||||
const route = new ActivatedRouteStub();
|
||||
const searchConfigService = { paginationID: 'page-id' };
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterTestingModule.withRoutes([
|
||||
{ path: 'search', component: DummyComponent, pathMatch: 'full' },
|
||||
]),
|
||||
DummyComponent,
|
||||
],
|
||||
providers: [
|
||||
{ provide: Router, useValue: router },
|
||||
{ provide: RouteService, useValue: routeServiceStub },
|
||||
{ provide: RequestService, useValue: getMockRequestService() },
|
||||
{ provide: RemoteDataBuildService, useValue: {} },
|
||||
{ provide: HALEndpointService, useValue: {} },
|
||||
{ provide: CommunityDataService, useValue: {} },
|
||||
{ provide: DSpaceObjectDataService, useValue: {} },
|
||||
{ provide: PaginationService, useValue: {} },
|
||||
{ provide: SearchConfigurationService, useValue: searchConfigService },
|
||||
{ provide: Angulartics2, useValue: {} },
|
||||
SearchService,
|
||||
],
|
||||
});
|
||||
searchService = TestBed.inject(SearchService);
|
||||
});
|
||||
let service: SearchService;
|
||||
|
||||
it('should return list view mode', () => {
|
||||
searchService.getViewMode().subscribe((viewMode) => {
|
||||
expect(viewMode).toBe(ViewMode.ListElement);
|
||||
});
|
||||
let halService: HALEndpointServiceStub;
|
||||
let paginationService: PaginationServiceStub;
|
||||
let remoteDataBuildService: RemoteDataBuildService;
|
||||
let requestService: SpyObj<RequestService>;
|
||||
let routeService: RouteService;
|
||||
let searchConfigService: SearchConfigurationServiceStub;
|
||||
|
||||
let testScheduler: TestScheduler;
|
||||
let msToLive: number;
|
||||
let remoteDataTimestamp: number;
|
||||
|
||||
beforeEach(() => {
|
||||
halService = new HALEndpointServiceStub(environment.rest.baseUrl);
|
||||
paginationService = new PaginationServiceStub();
|
||||
remoteDataBuildService = getMockRemoteDataBuildService();
|
||||
requestService = getMockRequestService();
|
||||
searchConfigService = new SearchConfigurationServiceStub();
|
||||
|
||||
initTestData();
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forRoot([]),
|
||||
],
|
||||
providers: [
|
||||
{ provide: RouteService, useValue: routeServiceStub },
|
||||
{ provide: RequestService, useValue: requestService },
|
||||
{ provide: RemoteDataBuildService, useValue: remoteDataBuildService },
|
||||
{ provide: HALEndpointService, useValue: halService },
|
||||
{ provide: DSpaceObjectDataService, useValue: {} },
|
||||
{ provide: PaginationService, useValue: paginationService },
|
||||
{ provide: SearchConfigurationService, useValue: searchConfigService },
|
||||
{ provide: Angulartics2, useValue: {} },
|
||||
SearchService,
|
||||
],
|
||||
});
|
||||
service = TestBed.inject(SearchService);
|
||||
routeService = TestBed.inject(RouteService);
|
||||
});
|
||||
describe('', () => {
|
||||
let searchService: SearchService;
|
||||
const router = new RouterStub();
|
||||
let routeService;
|
||||
|
||||
const halService = {
|
||||
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */
|
||||
getEndpoint: () => {
|
||||
},
|
||||
/* eslint-enable no-empty,@typescript-eslint/no-empty-function */
|
||||
|
||||
};
|
||||
|
||||
const remoteDataBuildService = {
|
||||
toRemoteDataObservable: (requestEntryObs: Observable<RequestEntry>, payloadObs: Observable<any>) => {
|
||||
return observableCombineLatest([requestEntryObs, payloadObs]).pipe(
|
||||
map(([req, pay]) => {
|
||||
return { req, pay };
|
||||
}),
|
||||
);
|
||||
},
|
||||
aggregate: (input: Observable<RemoteData<any>>[]): Observable<RemoteData<any[]>> => {
|
||||
return createSuccessfulRemoteDataObject$([]);
|
||||
},
|
||||
buildFromHref: (href: string): Observable<RemoteData<any>> => {
|
||||
return createSuccessfulRemoteDataObject$(Object.assign(new SearchObjects(), {
|
||||
page: [],
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
const paginationService = new PaginationServiceStub();
|
||||
const searchConfigService = { paginationID: 'page-id' };
|
||||
const requestService = getMockRequestService();
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterTestingModule.withRoutes([
|
||||
{ path: 'search', component: DummyComponent, pathMatch: 'full' },
|
||||
]),
|
||||
DummyComponent,
|
||||
],
|
||||
providers: [
|
||||
{ provide: Router, useValue: router },
|
||||
{ provide: RouteService, useValue: routeServiceStub },
|
||||
{ provide: RequestService, useValue: requestService },
|
||||
{ provide: RemoteDataBuildService, useValue: remoteDataBuildService },
|
||||
{ provide: HALEndpointService, useValue: halService },
|
||||
{ provide: CommunityDataService, useValue: {} },
|
||||
{ provide: DSpaceObjectDataService, useValue: {} },
|
||||
{ provide: PaginationService, useValue: paginationService },
|
||||
{ provide: SearchConfigurationService, useValue: searchConfigService },
|
||||
{ provide: Angulartics2, useValue: {} },
|
||||
SearchService,
|
||||
],
|
||||
});
|
||||
searchService = TestBed.inject(SearchService);
|
||||
routeService = TestBed.inject(RouteService);
|
||||
const urlTree = Object.assign(new UrlTree(), { root: { children: { primary: 'search' } } });
|
||||
router.parseUrl.and.returnValue(urlTree);
|
||||
function initTestData(): void {
|
||||
testScheduler = new TestScheduler((actual, expected) => {
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
msToLive = 15 * 60 * 1000;
|
||||
// The response's lastUpdated equals the time of 60 seconds after the test started, ensuring they are not perceived
|
||||
// as cached values.
|
||||
remoteDataTimestamp = new Date().getTime() + 60 * 1000;
|
||||
}
|
||||
|
||||
describe('setViewMode', () => {
|
||||
it('should call the navigate method on the Router with view mode list parameter as a parameter when setViewMode is called', () => {
|
||||
searchService.setViewMode(ViewMode.ListElement);
|
||||
expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith('page-id', ['/search'], { page: 1 }, { view: ViewMode.ListElement },
|
||||
);
|
||||
service.setViewMode(ViewMode.ListElement);
|
||||
|
||||
expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith('test-id', ['/search'], { page: 1 }, { view: ViewMode.ListElement });
|
||||
});
|
||||
|
||||
it('should call the navigate method on the Router with view mode grid parameter as a parameter when setViewMode is called', () => {
|
||||
searchService.setViewMode(ViewMode.GridElement);
|
||||
expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith('page-id', ['/search'], { page: 1 }, { view: ViewMode.GridElement },
|
||||
);
|
||||
service.setViewMode(ViewMode.GridElement);
|
||||
|
||||
expect(paginationService.updateRouteWithUrl).toHaveBeenCalledWith('test-id', ['/search'], { page: 1 }, { view: ViewMode.GridElement });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getViewMode', () => {
|
||||
it('should return list view mode', () => {
|
||||
testScheduler.run(({ expectObservable }) => {
|
||||
expectObservable(service.getViewMode()).toBe('(a|)', {
|
||||
a: ViewMode.ListElement,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return ViewMode.List when the viewMode is set to ViewMode.List in the ActivatedRoute', () => {
|
||||
let viewMode = ViewMode.GridElement;
|
||||
spyOn(routeService, 'getQueryParamMap').and.returnValue(observableOf(new Map([
|
||||
['view', ViewMode.ListElement],
|
||||
])));
|
||||
testScheduler.run(({ expectObservable }) => {
|
||||
spyOn(routeService, 'getQueryParamMap').and.returnValue(observableOf(new Map([
|
||||
['view', ViewMode.ListElement],
|
||||
])));
|
||||
|
||||
searchService.getViewMode().subscribe((mode) => viewMode = mode);
|
||||
expect(viewMode).toEqual(ViewMode.ListElement);
|
||||
expectObservable(service.getViewMode()).toBe('(a|)', {
|
||||
a: ViewMode.ListElement,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return ViewMode.Grid when the viewMode is set to ViewMode.Grid in the ActivatedRoute', () => {
|
||||
let viewMode = ViewMode.ListElement;
|
||||
spyOn(routeService, 'getQueryParamMap').and.returnValue(observableOf(new Map([
|
||||
['view', ViewMode.GridElement],
|
||||
])));
|
||||
searchService.getViewMode().subscribe((mode) => viewMode = mode);
|
||||
expect(viewMode).toEqual(ViewMode.GridElement);
|
||||
});
|
||||
testScheduler.run(({ expectObservable }) => {
|
||||
spyOn(routeService, 'getQueryParamMap').and.returnValue(observableOf(new Map([
|
||||
['view', ViewMode.GridElement],
|
||||
])));
|
||||
|
||||
describe('when search is called', () => {
|
||||
const endPoint = 'http://endpoint.com/test/test';
|
||||
const searchOptions = new PaginatedSearchOptions({});
|
||||
beforeEach(() => {
|
||||
spyOn((searchService as any).halService, 'getEndpoint').and.returnValue(observableOf(endPoint));
|
||||
spyOn((searchService as any).rdb, 'buildFromHref').and.callThrough();
|
||||
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */
|
||||
searchService.search(searchOptions).subscribe((t) => {
|
||||
}); // subscribe to make sure all methods are called
|
||||
/* eslint-enable no-empty,@typescript-eslint/no-empty-function */
|
||||
});
|
||||
|
||||
it('should call getEndpoint on the halService', () => {
|
||||
expect((searchService as any).halService.getEndpoint).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should send out the request on the request service', () => {
|
||||
expect((searchService as any).requestService.send).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call getByHref on the request service with the correct request url', () => {
|
||||
expect((searchService as any).rdb.buildFromHref).toHaveBeenCalledWith(endPoint);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when getFacetValuesFor is called with a filterQuery', () => {
|
||||
it('should add the encoded filterQuery to the args list', () => {
|
||||
jasmine.getEnv().allowRespy(true);
|
||||
const spyRequest = spyOn((searchService as any), 'request').and.stub();
|
||||
spyOn(requestService, 'send').and.returnValue(true);
|
||||
const searchFilterConfig = new SearchFilterConfig();
|
||||
searchFilterConfig._links = {
|
||||
self: {
|
||||
href: 'https://demo.dspace.org/',
|
||||
},
|
||||
};
|
||||
|
||||
searchService.getFacetValuesFor(searchFilterConfig, 1, undefined, 'filter&Query');
|
||||
|
||||
expect(spyRequest).toHaveBeenCalledWith(anything(), 'https://demo.dspace.org?page=0&size=5&prefix=filter%26Query');
|
||||
expectObservable(service.getViewMode()).toBe('(a|)', {
|
||||
a: ViewMode.GridElement,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
let remoteDataMocks: Record<string, RemoteData<SearchObjects<any>>>;
|
||||
|
||||
beforeEach(() => {
|
||||
remoteDataMocks = {
|
||||
RequestPending: new RemoteData(undefined, msToLive, remoteDataTimestamp, RequestEntryState.RequestPending, undefined, undefined, undefined),
|
||||
ResponsePending: new RemoteData(undefined, msToLive, remoteDataTimestamp, RequestEntryState.ResponsePending, undefined, undefined, undefined),
|
||||
Success: new RemoteData(remoteDataTimestamp, msToLive, remoteDataTimestamp, RequestEntryState.Success, undefined, new SearchObjects(), 200),
|
||||
SuccessStale: new RemoteData(remoteDataTimestamp, msToLive, remoteDataTimestamp, RequestEntryState.SuccessStale, undefined, new SearchObjects(), 200),
|
||||
};
|
||||
});
|
||||
|
||||
describe('when useCachedVersionIfAvailable is true', () => {
|
||||
it(`should emit a cached completed RemoteData immediately, and keep emitting if it gets re-requested`, () => {
|
||||
testScheduler.run(({ cold, expectObservable }) => {
|
||||
spyOn(remoteDataBuildService, 'buildFromHref').and.returnValue(cold('a-b-c-d-e', {
|
||||
a: remoteDataMocks.Success,
|
||||
b: remoteDataMocks.RequestPending,
|
||||
c: remoteDataMocks.ResponsePending,
|
||||
d: remoteDataMocks.Success,
|
||||
e: remoteDataMocks.SuccessStale,
|
||||
}));
|
||||
const expected = 'a-b-c-d-e';
|
||||
const values = {
|
||||
a: remoteDataMocks.Success,
|
||||
b: remoteDataMocks.RequestPending,
|
||||
c: remoteDataMocks.ResponsePending,
|
||||
d: remoteDataMocks.Success,
|
||||
e: remoteDataMocks.SuccessStale,
|
||||
};
|
||||
|
||||
expectObservable(service.search(undefined, msToLive, true)).toBe(expected, values);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when useCachedVersionIfAvailable is false', () => {
|
||||
it('should not emit a cached completed RemoteData', () => {
|
||||
// Old cached value from 1 minute before the test started
|
||||
const oldCachedSucceededData: RemoteData<SearchObjects<any>> = Object.assign(new SearchObjects(), remoteDataMocks.Success, {
|
||||
timeCompleted: remoteDataTimestamp - 2 * 60 * 1000,
|
||||
lastUpdated: remoteDataTimestamp - 2 * 60 * 1000,
|
||||
});
|
||||
testScheduler.run(({ cold, expectObservable }) => {
|
||||
spyOn(remoteDataBuildService, 'buildFromHref').and.returnValue(cold('a-b-c-d-e', {
|
||||
a: oldCachedSucceededData,
|
||||
b: remoteDataMocks.RequestPending,
|
||||
c: remoteDataMocks.ResponsePending,
|
||||
d: remoteDataMocks.Success,
|
||||
e: remoteDataMocks.SuccessStale,
|
||||
}));
|
||||
const expected = '--b-c-d-e';
|
||||
const values = {
|
||||
b: remoteDataMocks.RequestPending,
|
||||
c: remoteDataMocks.ResponsePending,
|
||||
d: remoteDataMocks.Success,
|
||||
e: remoteDataMocks.SuccessStale,
|
||||
};
|
||||
|
||||
expectObservable(service.search(undefined, msToLive, false)).toBe(expected, values);
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit the first completed RemoteData since the request was made', () => {
|
||||
testScheduler.run(({ cold, expectObservable }) => {
|
||||
spyOn(remoteDataBuildService, 'buildFromHref').and.returnValue(cold('a-b', {
|
||||
a: remoteDataMocks.Success,
|
||||
b: remoteDataMocks.SuccessStale,
|
||||
}));
|
||||
const expected = 'a-b';
|
||||
const values = {
|
||||
a: remoteDataMocks.Success,
|
||||
b: remoteDataMocks.SuccessStale,
|
||||
};
|
||||
expectObservable(service.search(undefined, msToLive, false)).toBe(expected, values);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should call getEndpoint on the halService', () => {
|
||||
spyOn(halService, 'getEndpoint').and.callThrough();
|
||||
|
||||
service.search(new PaginatedSearchOptions({})).subscribe();
|
||||
|
||||
expect(halService.getEndpoint).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should send out the request on the request service', () => {
|
||||
service.search(new PaginatedSearchOptions({})).subscribe();
|
||||
|
||||
expect(requestService.send).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call getByHref on the request service with the correct request url', () => {
|
||||
spyOn(remoteDataBuildService, 'buildFromHref').and.callThrough();
|
||||
|
||||
service.search(new PaginatedSearchOptions({})).subscribe();
|
||||
|
||||
expect(remoteDataBuildService.buildFromHref).toHaveBeenCalledWith(environment.rest.baseUrl + '/discover/search/objects');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFacetValuesFor', () => {
|
||||
let remoteDataMocks: Record<string, RemoteData<FacetValues>>;
|
||||
let filterConfig: SearchFilterConfig;
|
||||
|
||||
beforeEach(() => {
|
||||
remoteDataMocks = {
|
||||
RequestPending: new RemoteData(undefined, msToLive, remoteDataTimestamp, RequestEntryState.RequestPending, undefined, undefined, undefined),
|
||||
ResponsePending: new RemoteData(undefined, msToLive, remoteDataTimestamp, RequestEntryState.ResponsePending, undefined, undefined, undefined),
|
||||
Success: new RemoteData(remoteDataTimestamp, msToLive, remoteDataTimestamp, RequestEntryState.Success, undefined, new FacetValues(), 200),
|
||||
SuccessStale: new RemoteData(remoteDataTimestamp, msToLive, remoteDataTimestamp, RequestEntryState.SuccessStale, undefined, new FacetValues(), 200),
|
||||
};
|
||||
filterConfig = new SearchFilterConfig();
|
||||
filterConfig._links = {
|
||||
self: {
|
||||
href: environment.rest.baseUrl,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('when useCachedVersionIfAvailable is true', () => {
|
||||
it(`should emit a cached completed RemoteData immediately, and keep emitting if it gets re-requested`, () => {
|
||||
testScheduler.run(({ cold, expectObservable }) => {
|
||||
spyOn(remoteDataBuildService, 'buildFromHref').and.returnValue(cold('a-b-c-d-e', {
|
||||
a: remoteDataMocks.Success,
|
||||
b: remoteDataMocks.RequestPending,
|
||||
c: remoteDataMocks.ResponsePending,
|
||||
d: remoteDataMocks.Success,
|
||||
e: remoteDataMocks.SuccessStale,
|
||||
}));
|
||||
const expected = 'a-b-c-d-e';
|
||||
const values = {
|
||||
a: remoteDataMocks.Success,
|
||||
b: remoteDataMocks.RequestPending,
|
||||
c: remoteDataMocks.ResponsePending,
|
||||
d: remoteDataMocks.Success,
|
||||
e: remoteDataMocks.SuccessStale,
|
||||
};
|
||||
|
||||
expectObservable(service.getFacetValuesFor(filterConfig, 1, undefined, undefined, true)).toBe(expected, values);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when useCachedVersionIfAvailable is false', () => {
|
||||
it('should not emit a cached completed RemoteData', () => {
|
||||
// Old cached value from 1 minute before the test started
|
||||
const oldCachedSucceededData: RemoteData<FacetValues> = Object.assign(new FacetValues(), remoteDataMocks.Success, {
|
||||
timeCompleted: remoteDataTimestamp - 2 * 60 * 1000,
|
||||
lastUpdated: remoteDataTimestamp - 2 * 60 * 1000,
|
||||
});
|
||||
testScheduler.run(({ cold, expectObservable }) => {
|
||||
spyOn(remoteDataBuildService, 'buildFromHref').and.returnValue(cold('a-b-c-d-e', {
|
||||
a: oldCachedSucceededData,
|
||||
b: remoteDataMocks.RequestPending,
|
||||
c: remoteDataMocks.ResponsePending,
|
||||
d: remoteDataMocks.Success,
|
||||
e: remoteDataMocks.SuccessStale,
|
||||
}));
|
||||
const expected = '--b-c-d-e';
|
||||
const values = {
|
||||
b: remoteDataMocks.RequestPending,
|
||||
c: remoteDataMocks.ResponsePending,
|
||||
d: remoteDataMocks.Success,
|
||||
e: remoteDataMocks.SuccessStale,
|
||||
};
|
||||
|
||||
expectObservable(service.getFacetValuesFor(filterConfig, 1, undefined, undefined, false)).toBe(expected, values);
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit the first completed RemoteData since the request was made', () => {
|
||||
testScheduler.run(({ cold, expectObservable }) => {
|
||||
spyOn(remoteDataBuildService, 'buildFromHref').and.returnValue(cold('a-b', {
|
||||
a: remoteDataMocks.Success,
|
||||
b: remoteDataMocks.SuccessStale,
|
||||
}));
|
||||
const expected = 'a-b';
|
||||
const values = {
|
||||
a: remoteDataMocks.Success,
|
||||
b: remoteDataMocks.SuccessStale,
|
||||
};
|
||||
expectObservable(service.getFacetValuesFor(filterConfig, 1, undefined, undefined, false)).toBe(expected, values);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should encode the filterQuery', () => {
|
||||
spyOn((service as any), 'request').and.callThrough();
|
||||
|
||||
service.getFacetValuesFor(filterConfig, 1, undefined, 'filter&Query');
|
||||
|
||||
expect((service as any).request).toHaveBeenCalledWith(anything(), environment.rest.baseUrl + '?page=0&size=5&prefix=filter%26Query');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -9,6 +9,7 @@ import {
|
||||
import {
|
||||
distinctUntilChanged,
|
||||
map,
|
||||
skipWhile,
|
||||
switchMap,
|
||||
take,
|
||||
tap,
|
||||
@@ -168,6 +169,7 @@ export class SearchService {
|
||||
search<T extends DSpaceObject>(searchOptions?: PaginatedSearchOptions, responseMsToLive?: number, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<SearchObjects<T>>> {
|
||||
const href$ = this.getEndpoint(searchOptions);
|
||||
|
||||
let startTime: number;
|
||||
href$.pipe(
|
||||
take(1),
|
||||
map((href: string) => {
|
||||
@@ -191,6 +193,7 @@ export class SearchService {
|
||||
searchOptions: searchOptions,
|
||||
});
|
||||
|
||||
startTime = new Date().getTime();
|
||||
this.requestService.send(request, useCachedVersionIfAvailable);
|
||||
});
|
||||
|
||||
@@ -198,7 +201,13 @@ export class SearchService {
|
||||
switchMap((href: string) => this.rdb.buildFromHref<SearchObjects<T>>(href)),
|
||||
);
|
||||
|
||||
return this.directlyAttachIndexableObjects(sqr$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
return this.directlyAttachIndexableObjects(sqr$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe(
|
||||
// This skip ensures that if a stale object is present in the cache when you do a
|
||||
// call it isn't immediately returned, but we wait until the remote data for the new request
|
||||
// is created. If useCachedVersionIfAvailable is false it also ensures you don't get a
|
||||
// cached completed object
|
||||
skipWhile((rd: RemoteData<SearchObjects<T>>) => rd.isStale || (!useCachedVersionIfAvailable && rd.lastUpdated < startTime)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -304,9 +313,15 @@ export class SearchService {
|
||||
return FacetValueResponseParsingService;
|
||||
},
|
||||
});
|
||||
const startTime = new Date().getTime();
|
||||
this.requestService.send(request, useCachedVersionIfAvailable);
|
||||
|
||||
return this.rdb.buildFromHref(href).pipe(
|
||||
// This skip ensures that if a stale object is present in the cache when you do a
|
||||
// call it isn't immediately returned, but we wait until the remote data for the new request
|
||||
// is created. If useCachedVersionIfAvailable is false it also ensures you don't get a
|
||||
// cached completed object
|
||||
skipWhile((rd: RemoteData<FacetValues>) => rd.isStale || (!useCachedVersionIfAvailable && rd.lastUpdated < startTime)),
|
||||
tap((facetValuesRD: RemoteData<FacetValues>) => {
|
||||
if (facetValuesRD.hasSucceeded) {
|
||||
const appliedFilters: AppliedFilter[] = (facetValuesRD.payload.appliedFilters ?? [])
|
||||
|
@@ -32,7 +32,7 @@ export class UsageReport extends HALResource {
|
||||
id: string;
|
||||
|
||||
@autoserializeAs('report-type')
|
||||
reportType: string;
|
||||
reportType: string;
|
||||
|
||||
@autoserialize
|
||||
points: Point[];
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<a
|
||||
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
||||
class="card-img-top full-width" [attr.title]="'search.results.view-result' | translate">
|
||||
class="card-img-top full-width" [attr.title]="'search.results.view-result' | translate" role="link" tabindex="0">
|
||||
<div>
|
||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
@@ -48,7 +48,7 @@
|
||||
<div class="text-center">
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
||||
class="lead btn btn-primary viewButton">{{ 'search.results.view-result' | translate}}</a>
|
||||
class="lead btn btn-primary viewButton" role="link" tabindex="0">{{ 'search.results.view-result' | translate}}</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<a
|
||||
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
||||
class="card-img-top full-width" [attr.title]="'search.results.view-result' | translate">
|
||||
class="card-img-top full-width" [attr.title]="'search.results.view-result' | translate" role="link" tabindex="0">
|
||||
<div>
|
||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
@@ -48,7 +48,7 @@
|
||||
<div class="text-center">
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
||||
class="lead btn btn-primary viewButton">{{ 'search.results.view-result' | translate}}</a>
|
||||
class="lead btn btn-primary viewButton" role="link" tabindex="0">{{ 'search.results.view-result' | translate}}</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<a
|
||||
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
||||
class="card-img-top full-width" [attr.title]="'search.results.view-result' | translate">
|
||||
class="card-img-top full-width" [attr.title]="'search.results.view-result' | translate" role="link" tabindex="0">
|
||||
<div>
|
||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
@@ -54,7 +54,7 @@
|
||||
<div class="text-center">
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
||||
class="lead btn btn-primary viewButton">{{ 'search.results.view-result' | translate}}</a>
|
||||
class="lead btn btn-primary viewButton" role="link" tabindex="0">{{ 'search.results.view-result' | translate}}</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@@ -4,7 +4,7 @@
|
||||
@if (linkType !== linkTypes.None) {
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
||||
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out">
|
||||
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out" role="link" tabindex="0">
|
||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="true">
|
||||
</ds-thumbnail>
|
||||
</a>
|
||||
@@ -26,7 +26,7 @@
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
||||
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out"
|
||||
[innerHTML]="dsoTitle"></a>
|
||||
[innerHTML]="dsoTitle" role="link" tabindex="0"></a>
|
||||
}
|
||||
@if (linkType === linkTypes.None) {
|
||||
<span
|
||||
|
@@ -4,7 +4,7 @@
|
||||
@if (linkType !== linkTypes.None) {
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
||||
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out">
|
||||
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out" role="link" tabindex="0">
|
||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="true">
|
||||
</ds-thumbnail>
|
||||
</a>
|
||||
@@ -26,7 +26,7 @@
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
||||
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out"
|
||||
[innerHTML]="dsoTitle"></a>
|
||||
[innerHTML]="dsoTitle" role="link" tabindex="0"></a>
|
||||
}
|
||||
@if (linkType === linkTypes.None) {
|
||||
<span
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<div class="col-3 col-md-2">
|
||||
@if (linkType !== linkTypes.None) {
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'" [attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
||||
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out">
|
||||
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out" role="link" tabindex="0">
|
||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="true">
|
||||
</ds-thumbnail>
|
||||
</a>
|
||||
@@ -24,7 +24,7 @@
|
||||
@if (linkType !== linkTypes.None) {
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'" [attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
||||
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out"
|
||||
[innerHTML]="dsoTitle"></a>
|
||||
[innerHTML]="dsoTitle" role="link" tabindex="0"></a>
|
||||
}
|
||||
@if (linkType === linkTypes.None) {
|
||||
<span
|
||||
|
@@ -53,7 +53,7 @@
|
||||
[label]="'journalissue.page.keyword'">
|
||||
</ds-generic-item-page-field>
|
||||
<div>
|
||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']">
|
||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="button" tabindex="0">
|
||||
{{"item.page.link.full" | translate}}
|
||||
</a>
|
||||
</div>
|
||||
|
@@ -36,7 +36,7 @@
|
||||
[label]="'journalvolume.page.description'">
|
||||
</ds-generic-item-page-field>
|
||||
<div>
|
||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']">
|
||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="button" tabindex="0">
|
||||
{{"item.page.link.full" | translate}}
|
||||
</a>
|
||||
</div>
|
||||
|
@@ -35,7 +35,7 @@
|
||||
[label]="'journal.page.description'">
|
||||
</ds-generic-item-page-field>
|
||||
<div>
|
||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']">
|
||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="button" tabindex="0">
|
||||
{{"item.page.link.full" | translate}}
|
||||
</a>
|
||||
</div>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<a
|
||||
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
||||
class="card-img-top full-width" [attr.title]="'search.results.view-result' | translate">
|
||||
class="card-img-top full-width" [attr.title]="'search.results.view-result' | translate" role="link" tabindex="0">
|
||||
<div>
|
||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
@@ -56,7 +56,7 @@
|
||||
<div class="text-center">
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
||||
class="lead btn btn-primary viewButton">{{ 'search.results.view-result' | translate}}</a>
|
||||
class="lead btn btn-primary viewButton" role="link" tabindex="0">{{ 'search.results.view-result' | translate}}</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<a
|
||||
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
||||
class="card-img-top full-width" [attr.title]="'search.results.view-result' | translate">
|
||||
class="card-img-top full-width" [attr.title]="'search.results.view-result' | translate" role="link" tabindex="0">
|
||||
<div>
|
||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
@@ -47,7 +47,7 @@
|
||||
<div class="text-center">
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
||||
class="lead btn btn-primary viewButton">{{ 'search.results.view-result' | translate}}</a>
|
||||
class="lead btn btn-primary viewButton" role="link" tabindex="0">{{ 'search.results.view-result' | translate}}</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<a
|
||||
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
||||
class="card-img-top full-width" [attr.title]="'search.results.view-result' | translate">
|
||||
class="card-img-top full-width" [attr.title]="'search.results.view-result' | translate" role="link" tabindex="0">
|
||||
<div>
|
||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
||||
</ds-thumbnail>
|
||||
@@ -40,7 +40,7 @@
|
||||
<div class="text-center">
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
||||
class="lead btn btn-primary viewButton">{{ 'search.results.view-result' | translate}}</a>
|
||||
class="lead btn btn-primary viewButton" role="link" tabindex="0">{{ 'search.results.view-result' | translate}}</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@@ -4,7 +4,7 @@
|
||||
@if (linkType !== linkTypes.None) {
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
||||
[routerLink]="[itemPageRoute]" class="dont-break-out">
|
||||
[routerLink]="[itemPageRoute]" class="dont-break-out" role="link" tabindex="0">
|
||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async"
|
||||
[defaultImage]="'assets/images/orgunit-placeholder.svg'"
|
||||
[alt]="'thumbnail.orgunit.alt'"
|
||||
@@ -32,7 +32,7 @@
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
||||
[routerLink]="[itemPageRoute]" class="lead"
|
||||
[innerHTML]="dsoTitle || ('orgunit.listelement.no-title' | translate)"></a>
|
||||
[innerHTML]="dsoTitle || ('orgunit.listelement.no-title' | translate)" role="link" tabindex="0"></a>
|
||||
}
|
||||
@if (linkType === linkTypes.None) {
|
||||
<span
|
||||
|
@@ -4,7 +4,7 @@
|
||||
@if (linkType !== linkTypes.None) {
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
||||
[routerLink]="[itemPageRoute]" class="dont-break-out">
|
||||
[routerLink]="[itemPageRoute]" class="dont-break-out" role="link" tabindex="0">
|
||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async"
|
||||
[defaultImage]="'assets/images/person-placeholder.svg'"
|
||||
[alt]="'thumbnail.person.alt'"
|
||||
@@ -32,7 +32,7 @@
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
||||
[routerLink]="[itemPageRoute]" class="lead"
|
||||
[innerHTML]="dsoTitle || ('person.listelement.no-title' | translate)"></a>
|
||||
[innerHTML]="dsoTitle || ('person.listelement.no-title' | translate)" role="link" tabindex="0"></a>
|
||||
}
|
||||
@if (linkType === linkTypes.None) {
|
||||
<span
|
||||
|
@@ -4,7 +4,7 @@
|
||||
@if (linkType !== linkTypes.None) {
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
||||
[routerLink]="[itemPageRoute]" class="dont-break-out">
|
||||
[routerLink]="[itemPageRoute]" class="dont-break-out" role="link" tabindex="0">
|
||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async"
|
||||
[defaultImage]="'assets/images/project-placeholder.svg'"
|
||||
[alt]="'thumbnail.project.alt'"
|
||||
@@ -32,7 +32,7 @@
|
||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
||||
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out"
|
||||
[innerHTML]="dsoTitle"></a>
|
||||
[innerHTML]="dsoTitle" role="link" tabindex="0"></a>
|
||||
}
|
||||
@if (linkType === linkTypes.None) {
|
||||
<span
|
||||
|
@@ -56,7 +56,7 @@
|
||||
[label]="'orgunit.page.description'">
|
||||
</ds-generic-item-page-field>
|
||||
<div>
|
||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']">
|
||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="button" tabindex="0">
|
||||
{{"item.page.link.full" | translate}}
|
||||
</a>
|
||||
</div>
|
||||
|
@@ -52,7 +52,7 @@
|
||||
[label]="'person.page.name'">
|
||||
</ds-generic-item-page-field>
|
||||
<div>
|
||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']">
|
||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="button" tabindex="0">
|
||||
{{"item.page.link.full" | translate}}
|
||||
</a>
|
||||
</div>
|
||||
|
@@ -64,7 +64,7 @@
|
||||
[label]="'project.page.keyword'">
|
||||
</ds-generic-item-page-field>
|
||||
<div>
|
||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']">
|
||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="button" tabindex="0">
|
||||
{{"item.page.link.full" | translate}}
|
||||
</a>
|
||||
</div>
|
||||
|
@@ -26,7 +26,7 @@
|
||||
<h5 class="text-uppercase">Footer Content</h5>
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li>
|
||||
<a routerLink="./" class="">Suspendisse potenti</a>
|
||||
<a routerLink="./" class="" role="link" tabindex="0">Suspendisse potenti</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -53,14 +53,14 @@
|
||||
<div class="content-container">
|
||||
<p class="m-0">
|
||||
<a class="text-white"
|
||||
href="http://www.dspace.org/">{{ 'footer.link.dspace' | translate}}</a>
|
||||
href="http://www.dspace.org/" role="link" tabindex="0">{{ 'footer.link.dspace' | translate}}</a>
|
||||
{{ 'footer.copyright' | translate:{year: dateObj | date:'y'} }}
|
||||
<a class="text-white"
|
||||
href="https://www.lyrasis.org/">{{ 'footer.link.lyrasis' | translate}}</a>
|
||||
href="https://www.lyrasis.org/" role="link" tabindex="0">{{ 'footer.link.lyrasis' | translate}}</a>
|
||||
</p>
|
||||
<ul class="footer-info list-unstyled d-flex justify-content-center mb-0">
|
||||
<li>
|
||||
<button class="btn btn-link text-white" type="button" (click)="showCookieSettings()">
|
||||
<button class="btn btn-link text-white" type="button" (click)="showCookieSettings()" role="button" tabindex="0">
|
||||
{{ 'footer.link.cookies' | translate}}
|
||||
</button>
|
||||
</li>
|
||||
@@ -71,26 +71,26 @@
|
||||
@if (showPrivacyPolicy) {
|
||||
<li>
|
||||
<a class="btn text-white"
|
||||
routerLink="info/privacy">{{ 'footer.link.privacy-policy' | translate}}</a>
|
||||
routerLink="info/privacy" role="link" tabindex="0">{{ 'footer.link.privacy-policy' | translate}}</a>
|
||||
</li>
|
||||
}
|
||||
@if (showEndUserAgreement) {
|
||||
<li>
|
||||
<a class="btn text-white"
|
||||
routerLink="info/end-user-agreement">{{ 'footer.link.end-user-agreement' | translate}}</a>
|
||||
routerLink="info/end-user-agreement" role="link" tabindex="0">{{ 'footer.link.end-user-agreement' | translate}}</a>
|
||||
</li>
|
||||
}
|
||||
@if (showSendFeedback$ | async) {
|
||||
<li>
|
||||
<a class="btn text-white"
|
||||
routerLink="info/feedback">{{ 'footer.link.feedback' | translate}}</a>
|
||||
routerLink="info/feedback" role="link" tabindex="0">{{ 'footer.link.feedback' | translate}}</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
@if (coarLdnEnabled$ | async) {
|
||||
<div class="notify-enabled text-white">
|
||||
<a class="coar-notify-support-route" routerLink="info/coar-notify-support">
|
||||
<a class="coar-notify-support-route" routerLink="info/coar-notify-support" role="link" tabindex="0">
|
||||
<img class="n-coar" src="assets/images/n-coar.svg" [attr.alt]="'menu.header.image.logo' | translate" />
|
||||
{{ 'footer.link.coar-notify-support' | translate }}
|
||||
</a>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<header>
|
||||
<div class="container">
|
||||
<div class="d-flex flex-row justify-content-between">
|
||||
<a class="navbar-brand my-2" routerLink="/home">
|
||||
<a class="navbar-brand my-2" routerLink="/home" role="button" tabindex="0">
|
||||
<img src="assets/images/dspace-logo.svg" [attr.alt]="'menu.header.image.logo' | translate"/>
|
||||
</a>
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<div class="jumbotron py-4 px-2 py-sm-5 px-sm-0 mt-0 mb-4">
|
||||
<div class="jumbotron py-4 px-2 py-sm-5 px-sm-0 mt-ncs mb-4">
|
||||
<div class="container">
|
||||
<div class="d-flex flex-wrap">
|
||||
<div>
|
||||
@@ -14,7 +14,7 @@
|
||||
<li>issue permanent urls and trustworthy identifiers, including optional integrations with handle.net and DataCite DOI</li>
|
||||
</ul>
|
||||
<p>Join an international community of <a href="https://wiki.lyrasis.org/display/DSPACE/DSpace+Positioning"
|
||||
target="_blank">leading institutions using DSpace</a>.
|
||||
target="_blank" role="link" tabindex="0">leading institutions using DSpace</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -2,7 +2,6 @@ import { Route } from '@angular/router';
|
||||
|
||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
||||
import { homePageResolver } from './home-page.resolver';
|
||||
import { ThemedHomePageComponent } from './themed-home-page.component';
|
||||
|
||||
export const ROUTES: Route[] = [
|
||||
@@ -26,8 +25,5 @@ export const ROUTES: Route[] = [
|
||||
}],
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
site: homePageResolver,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@@ -18,9 +18,6 @@
|
||||
<ds-suggestions-popup></ds-suggestions-popup>
|
||||
|
||||
<ng-template #homeContent>
|
||||
@if ((site$ | async); as site) {
|
||||
<ds-view-tracker [object]="site"></ds-view-tracker>
|
||||
}
|
||||
<ds-search-form [inPlaceSearch]="false"
|
||||
[searchPlaceholder]="'home.search-form.placeholder' | translate">
|
||||
</ds-search-form>
|
||||
|
@@ -22,7 +22,6 @@ import { SuggestionsPopupComponent } from '../notifications/suggestions/popup/su
|
||||
import { ThemedConfigurationSearchPageComponent } from '../search-page/themed-configuration-search-page.component';
|
||||
import { ThemedSearchFormComponent } from '../shared/search-form/themed-search-form.component';
|
||||
import { PageWithSidebarComponent } from '../shared/sidebar/page-with-sidebar.component';
|
||||
import { ViewTrackerComponent } from '../statistics/angulartics/dspace/view-tracker.component';
|
||||
import { HomeCoarComponent } from './home-coar/home-coar.component';
|
||||
import { ThemedHomeNewsComponent } from './home-news/themed-home-news.component';
|
||||
import { RecentItemListComponent } from './recent-item-list/recent-item-list.component';
|
||||
@@ -33,7 +32,7 @@ import { ThemedTopLevelCommunityListComponent } from './top-level-community-list
|
||||
styleUrls: ['./home-page.component.scss'],
|
||||
templateUrl: './home-page.component.html',
|
||||
standalone: true,
|
||||
imports: [ThemedHomeNewsComponent, NgTemplateOutlet, ViewTrackerComponent, ThemedSearchFormComponent, ThemedTopLevelCommunityListComponent, RecentItemListComponent, AsyncPipe, TranslateModule, NgClass, SuggestionsPopupComponent, ThemedConfigurationSearchPageComponent, PageWithSidebarComponent, HomeCoarComponent],
|
||||
imports: [ThemedHomeNewsComponent, NgTemplateOutlet, ThemedSearchFormComponent, ThemedTopLevelCommunityListComponent, RecentItemListComponent, AsyncPipe, TranslateModule, NgClass, SuggestionsPopupComponent, ThemedConfigurationSearchPageComponent, PageWithSidebarComponent, HomeCoarComponent],
|
||||
})
|
||||
export class HomePageComponent implements OnInit {
|
||||
|
||||
|
@@ -9,7 +9,7 @@
|
||||
</ds-listable-object-component-loader>
|
||||
</div>
|
||||
}
|
||||
<button (click)="onLoadMore()" class="btn btn-primary search-button mt-4 float-start ng-tns-c290-40"> {{'vocabulary-treeview.load-more' | translate }} ...</button>
|
||||
<button (click)="onLoadMore()" class="btn btn-primary search-button mt-4 float-start ng-tns-c290-40" role="button" tabindex="0"> {{'vocabulary-treeview.load-more' | translate }} ...</button>
|
||||
</div>
|
||||
}
|
||||
@if (itemRD?.hasFailed) {
|
||||
|
@@ -8,6 +8,7 @@
|
||||
<ds-viewable-collection
|
||||
[config]="config"
|
||||
[sortConfig]="sortConfig"
|
||||
[showRSS]="rssSortConfig"
|
||||
[objects]="communitiesRD$ | async"
|
||||
[hideGear]="true">
|
||||
</ds-viewable-collection>
|
||||
|
@@ -65,9 +65,10 @@ export class TopLevelCommunityListComponent implements OnInit, OnDestroy {
|
||||
pageId = 'tl';
|
||||
|
||||
/**
|
||||
* The sorting configuration
|
||||
* The sorting configuration for the community list itself, and the optional RSS feed button
|
||||
*/
|
||||
sortConfig: SortOptions;
|
||||
rssSortConfig: SortOptions;
|
||||
|
||||
/**
|
||||
* The subscription to the observable for the current page.
|
||||
@@ -84,6 +85,7 @@ export class TopLevelCommunityListComponent implements OnInit, OnDestroy {
|
||||
this.config.pageSize = appConfig.homePage.topLevelCommunityList.pageSize;
|
||||
this.config.currentPage = 1;
|
||||
this.sortConfig = new SortOptions('dc.title', SortDirection.ASC);
|
||||
this.rssSortConfig = new SortOptions('dc.date.accessioned', SortDirection.DESC);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
@@ -10,7 +10,7 @@
|
||||
<div class="d-flex justify-content-between flex-wrap">
|
||||
<span class="align-self-center">{{'item.alerts.withdrawn' | translate}}</span>
|
||||
<div class="gap-2 d-flex">
|
||||
<a routerLink="/home" class="btn btn-primary btn-sm">{{"404.link.home-page" | translate}}</a>
|
||||
<a routerLink="/home" class="btn btn-primary btn-sm" role="button" tabindex="0">{{"404.link.home-page" | translate}}</a>
|
||||
@if (showReinstateButton$ | async) {
|
||||
<a class="btn btn-primary btn-sm" (click)="openReinstateModal()">{{ 'item.alerts.reinstate-request' | translate}}</a>
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<div class="container">
|
||||
<ds-alert [type]="'alert-info'" [content]="'item.edit.authorizations.heading'"></ds-alert>
|
||||
<ds-resource-policies [resourceType]="'item'" [resourceName]="(getItemName() | async)"
|
||||
[resourceUUID]="(getItemUUID() | async)">
|
||||
<ds-alert [type]="AlertType.Info" [content]="'item.edit.authorizations.heading'"></ds-alert>
|
||||
<ds-resource-policies [resourceType]="'item'" [resourceName]="itemName$ | async"
|
||||
[resourceUUID]="(item$ | async)?.id">
|
||||
</ds-resource-policies>
|
||||
@for (bundle of (bundles$ | async); track bundle) {
|
||||
<ds-resource-policies [resourceType]="'bundle'" [resourceUUID]="bundle.id" [resourceName]="bundle.name">
|
||||
|
@@ -169,17 +169,9 @@ describe('ItemAuthorizationsComponent test suite', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it('should get the item UUID', () => {
|
||||
|
||||
expect(comp.getItemUUID()).toBeObservable(cold('(a|)', {
|
||||
a: item.id,
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
it('should get the item\'s bundle', () => {
|
||||
|
||||
expect(comp.getItemBundles()).toBeObservable(cold('a', {
|
||||
expect(comp.bundles$).toBeObservable(cold('a', {
|
||||
a: bundles,
|
||||
}));
|
||||
|
||||
|
@@ -17,7 +17,6 @@ import {
|
||||
import {
|
||||
catchError,
|
||||
filter,
|
||||
first,
|
||||
map,
|
||||
mergeMap,
|
||||
take,
|
||||
@@ -37,11 +36,11 @@ import {
|
||||
getFirstSucceededRemoteDataWithNotEmptyPayload,
|
||||
} from '../../../core/shared/operators';
|
||||
import { AlertComponent } from '../../../shared/alert/alert.component';
|
||||
import { AlertType } from '../../../shared/alert/alert-type';
|
||||
import {
|
||||
hasValue,
|
||||
isNotEmpty,
|
||||
} from '../../../shared/empty.util';
|
||||
import { NgForTrackByIdDirective } from '../../../shared/ng-for-track-by-id.directive';
|
||||
import { ResourcePoliciesComponent } from '../../../shared/resource-policies/resource-policies.component';
|
||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||
|
||||
@@ -61,7 +60,6 @@ interface BundleBitstreamsMapEntry {
|
||||
ResourcePoliciesComponent,
|
||||
NgbCollapseModule,
|
||||
TranslateModule,
|
||||
NgForTrackByIdDirective,
|
||||
AsyncPipe,
|
||||
AlertComponent,
|
||||
],
|
||||
@@ -88,7 +86,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
|
||||
* The target editing item
|
||||
* @type {Observable<Item>}
|
||||
*/
|
||||
private item$: Observable<Item>;
|
||||
item$: Observable<Item>;
|
||||
|
||||
/**
|
||||
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||
@@ -127,16 +125,13 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
private bitstreamPageSize = 4;
|
||||
|
||||
/**
|
||||
* Initialize instance variables
|
||||
*
|
||||
* @param {LinkService} linkService
|
||||
* @param {ActivatedRoute} route
|
||||
* @param nameService
|
||||
*/
|
||||
itemName$: Observable<string>;
|
||||
|
||||
readonly AlertType = AlertType;
|
||||
|
||||
constructor(
|
||||
private linkService: LinkService,
|
||||
private route: ActivatedRoute,
|
||||
protected linkService: LinkService,
|
||||
protected route: ActivatedRoute,
|
||||
public nameService: DSONameService,
|
||||
) {
|
||||
}
|
||||
@@ -146,36 +141,18 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.getBundlesPerItem();
|
||||
this.itemName$ = this.getItemName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the item's UUID
|
||||
* Return the item's name
|
||||
*/
|
||||
getItemUUID(): Observable<string> {
|
||||
return this.item$.pipe(
|
||||
map((item: Item) => item.id),
|
||||
first((UUID: string) => isNotEmpty(UUID)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the item's name
|
||||
*/
|
||||
getItemName(): Observable<string> {
|
||||
private getItemName(): Observable<string> {
|
||||
return this.item$.pipe(
|
||||
map((item: Item) => this.nameService.getName(item)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all item's bundles
|
||||
*
|
||||
* @return an observable that emits all item's bundles
|
||||
*/
|
||||
getItemBundles(): Observable<Bundle[]> {
|
||||
return this.bundles$.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all bundles per item
|
||||
* and all the bitstreams per bundle
|
||||
|
@@ -30,7 +30,6 @@ import {
|
||||
map,
|
||||
switchMap,
|
||||
take,
|
||||
tap,
|
||||
} from 'rxjs/operators';
|
||||
import { AlertComponent } from 'src/app/shared/alert/alert.component';
|
||||
import { AlertType } from 'src/app/shared/alert/alert-type';
|
||||
@@ -239,15 +238,28 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
||||
this.itemService.getBundles(this.item.id, new PaginatedSearchOptions({ pagination: this.bundlesOptions })).pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
tap((bundlesPL: PaginatedList<Bundle>) =>
|
||||
this.showLoadMoreLink$.next(bundlesPL.pageInfo.currentPage < bundlesPL.pageInfo.totalPages),
|
||||
),
|
||||
map((bundlePage: PaginatedList<Bundle>) => bundlePage.page),
|
||||
).subscribe((bundles: Bundle[]) => {
|
||||
this.bundlesSubject.next([...this.bundlesSubject.getValue(), ...bundles]);
|
||||
).subscribe((bundles: PaginatedList<Bundle>) => {
|
||||
this.updateBundles(bundles);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the subject containing the bundles with the provided bundles.
|
||||
* Also updates the showLoadMoreLink observable so it does not show up when it is no longer necessary.
|
||||
*/
|
||||
updateBundles(newBundlesPL: PaginatedList<Bundle>) {
|
||||
const currentBundles = this.bundlesSubject.getValue();
|
||||
|
||||
// Only add bundles to the bundle subject if they are not present yet
|
||||
const bundlesToAdd = newBundlesPL.page
|
||||
.filter(bundleToAdd => !currentBundles.some(currentBundle => currentBundle.id === bundleToAdd.id));
|
||||
|
||||
const updatedBundles = [...currentBundles, ...bundlesToAdd];
|
||||
|
||||
this.showLoadMoreLink$.next(updatedBundles.length < newBundlesPL.totalElements);
|
||||
this.bundlesSubject.next(updatedBundles);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Submit the current changes
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<ds-metadata-field-wrapper [label]="label | translate">
|
||||
<div class="collections">
|
||||
@for (collection of (this.collections$ | async); track collection; let last = $last) {
|
||||
<a [routerLink]="['/collections', collection.id]">
|
||||
<a [routerLink]="['/collections', collection.id]" role="button" tabindex="0">
|
||||
<span>{{ dsoNameService.getName(collection) }}</span>@if (!last) {
|
||||
<span [innerHTML]="separator"></span>
|
||||
}
|
||||
@@ -21,6 +21,8 @@
|
||||
class="load-more-btn btn btn-sm btn-outline-secondary"
|
||||
role="button"
|
||||
href="javascript:void(0);"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
{{'item.page.collections.load-more' | translate}}
|
||||
</a>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<ds-metadata-field-wrapper [label]="label | translate">
|
||||
@for (mdValue of mdValues; track mdValue; let last = $last) {
|
||||
<a class="dont-break-out" [href]="mdValue.value" [target]="hasInternalLink(mdValue.value) ? '_self' : '_blank'">
|
||||
<a class="dont-break-out" [href]="mdValue.value" [target]="hasInternalLink(mdValue.value) ? '_self' : '_blank'" role="link" tabindex="0">
|
||||
{{ linktext || mdValue.value }}@if (!last) {
|
||||
<span [innerHTML]="separator"></span>
|
||||
}
|
||||
|
@@ -23,14 +23,14 @@
|
||||
<a class="dont-break-out ds-simple-metadata-link"
|
||||
[href]="value"
|
||||
[attr.target]="getLinkAttributes(value).target"
|
||||
[attr.rel]="getLinkAttributes(value).rel">
|
||||
[attr.rel]="getLinkAttributes(value).rel" role="link" tabindex="0">
|
||||
{{value}}
|
||||
</a>
|
||||
</ng-template>
|
||||
|
||||
<!-- Render value as a link with icon -->
|
||||
<ng-template #linkImg let-img="img" let-value="value">
|
||||
<a [href]="value" class="link-anchor dont-break-out ds-simple-metadata-link" target="_blank">
|
||||
<a [href]="value" class="link-anchor dont-break-out ds-simple-metadata-link" target="_blank" role="link" tabindex="0">
|
||||
<img class="link-logo"
|
||||
[alt]="img.alt | translate"
|
||||
[style.height]="'var(' + img.heightVar + ', --ds-item-page-img-field-default-inline-height)'"
|
||||
@@ -48,5 +48,5 @@
|
||||
<ng-template #browselink let-value="value">
|
||||
<a class="dont-break-out preserve-line-breaks ds-browse-link"
|
||||
[routerLink]="['/browse', browseDefinition.id]"
|
||||
[queryParams]="getQueryParams(value)">{{value}}</a>
|
||||
[queryParams]="getQueryParams(value)" role="link" tabindex="0">{{value}}</a>
|
||||
</ng-template>
|
||||
|
@@ -5,7 +5,6 @@
|
||||
<div>
|
||||
<ds-item-alerts [item]="item"></ds-item-alerts>
|
||||
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
|
||||
<ds-view-tracker [object]="item"></ds-view-tracker>
|
||||
@if (!item.isWithdrawn || (isAdmin$|async)) {
|
||||
<div class="full-item-info">
|
||||
<div class="d-flex flex-row">
|
||||
|
@@ -45,7 +45,6 @@ import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||
import { TruncatePipe } from '../../shared/utils/truncate.pipe';
|
||||
import { VarDirective } from '../../shared/utils/var.directive';
|
||||
import { ViewTrackerComponent } from '../../statistics/angulartics/dspace/view-tracker.component';
|
||||
import { ThemedItemAlertsComponent } from '../alerts/themed-item-alerts.component';
|
||||
import { CollectionsComponent } from '../field-components/collections/collections.component';
|
||||
import { ThemedItemPageTitleFieldComponent } from '../simple/field-components/specific-field/title/themed-item-page-field.component';
|
||||
@@ -162,7 +161,6 @@ describe('FullItemPageComponent', () => {
|
||||
ThemedLoadingComponent,
|
||||
ThemedItemPageTitleFieldComponent,
|
||||
DsoEditMenuComponent,
|
||||
ViewTrackerComponent,
|
||||
ThemedItemAlertsComponent,
|
||||
CollectionsComponent,
|
||||
ThemedFullFileSectionComponent,
|
||||
|
@@ -42,7 +42,6 @@ import { hasValue } from '../../shared/empty.util';
|
||||
import { ErrorComponent } from '../../shared/error/error.component';
|
||||
import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component';
|
||||
import { VarDirective } from '../../shared/utils/var.directive';
|
||||
import { ViewTrackerComponent } from '../../statistics/angulartics/dspace/view-tracker.component';
|
||||
import { ThemedItemAlertsComponent } from '../alerts/themed-item-alerts.component';
|
||||
import { CollectionsComponent } from '../field-components/collections/collections.component';
|
||||
import { ThemedItemPageTitleFieldComponent } from '../simple/field-components/specific-field/title/themed-item-page-field.component';
|
||||
@@ -75,7 +74,6 @@ import { ThemedFullFileSectionComponent } from './field-components/file-section/
|
||||
ThemedItemPageTitleFieldComponent,
|
||||
DsoEditMenuComponent,
|
||||
ItemVersionsNoticeComponent,
|
||||
ViewTrackerComponent,
|
||||
ThemedItemAlertsComponent,
|
||||
VarDirective,
|
||||
],
|
||||
|
@@ -5,6 +5,7 @@ import { accessTokenResolver } from '../core/auth/access-token.resolver';
|
||||
import { authenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { itemBreadcrumbResolver } from '../core/breadcrumbs/item-breadcrumb.resolver';
|
||||
import { MenuRoute } from '../shared/menu/menu-route.model';
|
||||
import { viewTrackerResolver } from '../statistics/angulartics/dspace/view-tracker.resolver';
|
||||
import { BitstreamRequestACopyPageComponent } from './bitstreams/request-a-copy/bitstream-request-a-copy-page.component';
|
||||
import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component';
|
||||
import { ThemedFullItemPageComponent } from './full/themed-full-item-page.component';
|
||||
@@ -38,7 +39,9 @@ export const ROUTES: Route[] = [
|
||||
data: {
|
||||
menuRoute: MenuRoute.ITEM_PAGE,
|
||||
},
|
||||
|
||||
resolve: {
|
||||
tracking: viewTrackerResolver,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'full',
|
||||
@@ -46,7 +49,9 @@ export const ROUTES: Route[] = [
|
||||
data: {
|
||||
menuRoute: MenuRoute.ITEM_PAGE,
|
||||
},
|
||||
|
||||
resolve: {
|
||||
tracking: viewTrackerResolver,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ITEM_EDIT_PATH,
|
||||
|
@@ -1,16 +1,16 @@
|
||||
<div class="container mb-5">
|
||||
<h1>{{'person.orcid.registry.auth' | translate}}</h1>
|
||||
@if ((isLinkedToOrcid() | async)) {
|
||||
@if ((isOrcidLinked$ | async)) {
|
||||
<div data-test="orcidLinked">
|
||||
<div class="row">
|
||||
@if ((hasOrcidAuthorizations() | async)) {
|
||||
@if ((hasOrcidAuthorizations$ | async)) {
|
||||
<div class="col-sm-6 mb-3" data-test="hasOrcidAuthorizations">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">{{ 'person.page.orcid.granted-authorizations'| translate }}</div>
|
||||
<div class="card-body">
|
||||
<div class="container p-0">
|
||||
<ul>
|
||||
@for (auth of (getOrcidAuthorizations() | async); track auth) {
|
||||
@for (auth of (profileAuthorizationScopes$ | async); track auth) {
|
||||
<li data-test="orcidAuthorization">
|
||||
{{getAuthorizationDescription(auth) | translate}}
|
||||
</li>
|
||||
@@ -26,16 +26,16 @@
|
||||
<div class="card-header">{{ 'person.page.orcid.missing-authorizations'| translate }}</div>
|
||||
<div class="card-body">
|
||||
<div class="container">
|
||||
@if ((hasMissingOrcidAuthorizations() | async) !== true) {
|
||||
<ds-alert [type]="'alert-success'" data-test="noMissingOrcidAuthorizations">
|
||||
@if ((hasMissingOrcidAuthorizations$ | async) !== true) {
|
||||
<ds-alert [type]="AlertType.Success" data-test="noMissingOrcidAuthorizations">
|
||||
{{'person.page.orcid.no-missing-authorizations-message' | translate}}
|
||||
</ds-alert>
|
||||
}
|
||||
@if ((hasMissingOrcidAuthorizations() | async)) {
|
||||
<ds-alert [type]="'alert-warning'" data-test="missingOrcidAuthorizations">
|
||||
@if ((hasMissingOrcidAuthorizations$ | async)) {
|
||||
<ds-alert [type]="AlertType.Warning" data-test="missingOrcidAuthorizations">
|
||||
{{'person.page.orcid.missing-authorizations-message' | translate}}
|
||||
<ul>
|
||||
@for (auth of (getMissingOrcidAuthorizations() | async); track auth) {
|
||||
@for (auth of (profileAuthorizationScopes$ | async); track auth) {
|
||||
<li data-test="missingOrcidAuthorization">
|
||||
{{getAuthorizationDescription(auth) | translate }}
|
||||
</li>
|
||||
@@ -48,13 +48,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if ((onlyAdminCanDisconnectProfileFromOrcid() | async) && (ownerCanDisconnectProfileFromOrcid() | async) !== true) {
|
||||
@if ((onlyAdminCanDisconnectProfileFromOrcid$ | async) && (ownerCanDisconnectProfileFromOrcid$ | async) !== true) {
|
||||
<ds-alert
|
||||
[type]="'alert-warning'" data-test="unlinkOnlyAdmin">
|
||||
[type]="AlertType.Warning" data-test="unlinkOnlyAdmin">
|
||||
{{ 'person.page.orcid.remove-orcid-message' | translate}}
|
||||
</ds-alert>
|
||||
}
|
||||
@if ((ownerCanDisconnectProfileFromOrcid() | async)) {
|
||||
@if ((ownerCanDisconnectProfileFromOrcid$ | async)) {
|
||||
<div class="row" data-test="unlinkOwner">
|
||||
<div class="col">
|
||||
<button type="submit" class="btn btn-danger float-end" (click)="unlinkOrcid()"
|
||||
@@ -68,7 +68,7 @@
|
||||
class='fas fa-circle-notch fa-spin'></i> {{'person.page.orcid.unlink.processing' | translate}}</span>
|
||||
}
|
||||
</button>
|
||||
@if ((hasMissingOrcidAuthorizations() | async)) {
|
||||
@if ((hasMissingOrcidAuthorizations$ | async)) {
|
||||
<button type="submit"
|
||||
class="btn btn-primary float-end" (click)="linkOrcid()">
|
||||
<span><i class="fas fa-check"></i> {{ 'person.page.orcid.grant-authorizations' | translate }}</span>
|
||||
@@ -83,7 +83,7 @@
|
||||
<div class="row">
|
||||
<div class="col-2"><img alt="orcid-logo" src="../../../../assets/images/orcid.logo.icon.svg"/></div>
|
||||
<div class="col">
|
||||
<ds-alert [type]="'alert-info'">{{ getOrcidNotLinkedMessage() | async }}</ds-alert>
|
||||
<ds-alert [type]="AlertType.Info">{{ getOrcidNotLinkedMessage() }}</ds-alert>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@@ -97,103 +97,3 @@
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<ng-template #orcidLinked>
|
||||
<div data-test="orcidLinked">
|
||||
<div class="row">
|
||||
@if ((hasOrcidAuthorizations() | async)) {
|
||||
<div class="col-sm-6 mb-3" data-test="hasOrcidAuthorizations">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">{{ 'person.page.orcid.granted-authorizations'| translate }}</div>
|
||||
<div class="card-body">
|
||||
<div class="container p-0">
|
||||
<ul>
|
||||
@for (auth of (getOrcidAuthorizations() | async); track auth) {
|
||||
<li data-test="orcidAuthorization">
|
||||
{{getAuthorizationDescription(auth) | translate}}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="col-sm-6 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">{{ 'person.page.orcid.missing-authorizations'| translate }}</div>
|
||||
<div class="card-body">
|
||||
<div class="container">
|
||||
@if ((hasMissingOrcidAuthorizations() | async) !== true) {
|
||||
<ds-alert [type]="'alert-success'" data-test="noMissingOrcidAuthorizations">
|
||||
{{'person.page.orcid.no-missing-authorizations-message' | translate}}
|
||||
</ds-alert>
|
||||
}
|
||||
@if ((hasMissingOrcidAuthorizations() | async)) {
|
||||
<ds-alert [type]="'alert-warning'" data-test="missingOrcidAuthorizations">
|
||||
{{'person.page.orcid.missing-authorizations-message' | translate}}
|
||||
<ul>
|
||||
@for (auth of (getMissingOrcidAuthorizations() | async); track auth) {
|
||||
<li data-test="missingOrcidAuthorization">
|
||||
{{getAuthorizationDescription(auth) | translate }}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</ds-alert>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if ((onlyAdminCanDisconnectProfileFromOrcid() | async) && (ownerCanDisconnectProfileFromOrcid() | async) !== true) {
|
||||
<ds-alert
|
||||
[type]="'alert-warning'" data-test="unlinkOnlyAdmin">
|
||||
{{ 'person.page.orcid.remove-orcid-message' | translate}}
|
||||
</ds-alert>
|
||||
}
|
||||
@if ((ownerCanDisconnectProfileFromOrcid() | async)) {
|
||||
<div class="row" data-test="unlinkOwner">
|
||||
<div class="col">
|
||||
<button type="submit" class="btn btn-danger float-end" (click)="unlinkOrcid()"
|
||||
[dsBtnDisabled]="(unlinkProcessing | async)">
|
||||
@if ((unlinkProcessing | async) !== true) {
|
||||
<span><i
|
||||
class="fas fa-unlink"></i> {{ 'person.page.orcid.unlink' | translate }}</span>
|
||||
}
|
||||
@if ((unlinkProcessing | async)) {
|
||||
<span><i
|
||||
class='fas fa-circle-notch fa-spin'></i> {{'person.page.orcid.unlink.processing' | translate}}</span>
|
||||
}
|
||||
</button>
|
||||
@if ((hasMissingOrcidAuthorizations() | async)) {
|
||||
<button type="submit"
|
||||
class="btn btn-primary float-end" (click)="linkOrcid()">
|
||||
<span><i class="fas fa-check"></i> {{ 'person.page.orcid.grant-authorizations' | translate }}</span>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #orcidNotLinked>
|
||||
<div data-test="orcidNotLinked">
|
||||
<div class="row">
|
||||
<div class="col-2"><img alt="orcid-logo" src="../../../../assets/images/orcid.logo.icon.svg"/></div>
|
||||
<div class="col">
|
||||
<ds-alert [type]="'alert-info'">{{ getOrcidNotLinkedMessage() | async }}</ds-alert>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<button class="btn btn-primary float-end" (click)="linkOrcid()">
|
||||
<i class="fas fa-link"></i>
|
||||
{{'person.page.orcid.link' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
@@ -30,6 +30,7 @@ import {
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
||||
import { AlertComponent } from '../../../shared/alert/alert.component';
|
||||
import { AlertType } from '../../../shared/alert/alert-type';
|
||||
import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { createFailedRemoteDataObjectFromError$ } from '../../../shared/remote-data.utils';
|
||||
@@ -56,43 +57,49 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
|
||||
/**
|
||||
* The list of exposed orcid authorization scopes for the orcid profile
|
||||
*/
|
||||
profileAuthorizationScopes: BehaviorSubject<string[]> = new BehaviorSubject<string[]>([]);
|
||||
profileAuthorizationScopes$: BehaviorSubject<string[]> = new BehaviorSubject([]);
|
||||
|
||||
hasOrcidAuthorizations$: Observable<boolean>;
|
||||
|
||||
/**
|
||||
* The list of all orcid authorization scopes missing in the orcid profile
|
||||
*/
|
||||
missingAuthorizationScopes: BehaviorSubject<string[]> = new BehaviorSubject<string[]>([]);
|
||||
missingAuthorizationScopes: BehaviorSubject<string[]> = new BehaviorSubject([]);
|
||||
|
||||
hasMissingOrcidAuthorizations$: Observable<boolean>;
|
||||
|
||||
/**
|
||||
* The list of all orcid authorization scopes available
|
||||
*/
|
||||
orcidAuthorizationScopes: BehaviorSubject<string[]> = new BehaviorSubject<string[]>([]);
|
||||
orcidAuthorizationScopes: BehaviorSubject<string[]> = new BehaviorSubject([]);
|
||||
|
||||
/**
|
||||
* A boolean representing if unlink operation is processing
|
||||
*/
|
||||
unlinkProcessing: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
unlinkProcessing: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
|
||||
/**
|
||||
* A boolean representing if orcid profile is linked
|
||||
*/
|
||||
private isOrcidLinked$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
isOrcidLinked$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
|
||||
/**
|
||||
* A boolean representing if only admin can disconnect orcid profile
|
||||
*/
|
||||
private onlyAdminCanDisconnectProfileFromOrcid$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
onlyAdminCanDisconnectProfileFromOrcid$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
|
||||
/**
|
||||
* A boolean representing if owner can disconnect orcid profile
|
||||
*/
|
||||
private ownerCanDisconnectProfileFromOrcid$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
ownerCanDisconnectProfileFromOrcid$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
|
||||
/**
|
||||
* An event emitted when orcid profile is unliked successfully
|
||||
*/
|
||||
@Output() unlink: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
readonly AlertType = AlertType;
|
||||
|
||||
constructor(
|
||||
private orcidAuthService: OrcidAuthService,
|
||||
private translateService: TranslateService,
|
||||
@@ -106,6 +113,8 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
|
||||
this.orcidAuthorizationScopes.next(scopes);
|
||||
this.initOrcidAuthSettings();
|
||||
});
|
||||
this.hasOrcidAuthorizations$ = this.hasOrcidAuthorizations();
|
||||
this.hasMissingOrcidAuthorizations$ = this.hasMissingOrcidAuthorizations();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
@@ -118,18 +127,11 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
|
||||
* Check if the list of exposed orcid authorization scopes for the orcid profile has values
|
||||
*/
|
||||
hasOrcidAuthorizations(): Observable<boolean> {
|
||||
return this.profileAuthorizationScopes.asObservable().pipe(
|
||||
return this.profileAuthorizationScopes$.pipe(
|
||||
map((scopes: string[]) => scopes.length > 0),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of exposed orcid authorization scopes for the orcid profile
|
||||
*/
|
||||
getOrcidAuthorizations(): Observable<string[]> {
|
||||
return this.profileAuthorizationScopes.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the list of exposed orcid authorization scopes for the orcid profile has values
|
||||
*/
|
||||
@@ -139,26 +141,12 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of exposed orcid authorization scopes for the orcid profile
|
||||
*/
|
||||
getMissingOrcidAuthorizations(): Observable<string[]> {
|
||||
return this.profileAuthorizationScopes.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a boolean representing if orcid profile is linked
|
||||
*/
|
||||
isLinkedToOrcid(): Observable<boolean> {
|
||||
return this.isOrcidLinked$.asObservable();
|
||||
}
|
||||
|
||||
getOrcidNotLinkedMessage(): Observable<string> {
|
||||
getOrcidNotLinkedMessage(): string {
|
||||
const orcid = this.item.firstMetadataValue('person.identifier.orcid');
|
||||
if (orcid) {
|
||||
return this.translateService.get('person.page.orcid.orcid-not-linked-message', { 'orcid': orcid });
|
||||
return this.translateService.instant('person.page.orcid.orcid-not-linked-message', { 'orcid': orcid });
|
||||
} else {
|
||||
return this.translateService.get('person.page.orcid.no-orcid-message');
|
||||
return this.translateService.instant('person.page.orcid.no-orcid-message');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,13 +159,6 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
|
||||
return 'person.page.orcid.scope.' + scope.substring(1).replace('/', '-');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a boolean representing if only admin can disconnect orcid profile
|
||||
*/
|
||||
onlyAdminCanDisconnectProfileFromOrcid(): Observable<boolean> {
|
||||
return this.onlyAdminCanDisconnectProfileFromOrcid$.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a boolean representing if owner can disconnect orcid profile
|
||||
*/
|
||||
@@ -243,7 +224,7 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
private setOrcidAuthorizationsFromItem(): void {
|
||||
this.profileAuthorizationScopes.next(this.orcidAuthService.getOrcidAuthorizationScopesByItem(this.item));
|
||||
this.profileAuthorizationScopes$.next(this.orcidAuthService.getOrcidAuthorizationScopesByItem(this.item));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -5,16 +5,16 @@
|
||||
<div class="container">
|
||||
<h2>{{ 'person.orcid.registry.queue' | translate }}</h2>
|
||||
|
||||
@if ((processing$ | async) !== true && (getList() | async)?.payload?.totalElements === 0) {
|
||||
@if ((processing$ | async) !== true && (list$ | async)?.payload?.totalElements === 0) {
|
||||
<ds-alert
|
||||
[type]="AlertTypeEnum.Info">
|
||||
{{ 'person.page.orcid.sync-queue.empty-message' | translate}}
|
||||
</ds-alert>
|
||||
}
|
||||
@if ((processing$ | async) !== true && (getList() | async)?.payload?.totalElements > 0) {
|
||||
@if ((processing$ | async) !== true && (list$ | async)?.payload?.totalElements > 0) {
|
||||
<ds-pagination
|
||||
[paginationOptions]="paginationOptions"
|
||||
[collectionSize]="(getList() | async)?.payload?.totalElements"
|
||||
[collectionSize]="(list$ | async)?.payload?.totalElements"
|
||||
[retainScrollPosition]="false" [hideGear]="true" (paginationChange)="updateList()">
|
||||
<div class="table-responsive">
|
||||
<table id="groups" class="table table-sm table-striped table-hover table-bordered">
|
||||
@@ -26,7 +26,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (entry of (getList() | async)?.payload?.page; track entry) {
|
||||
@for (entry of (list$ | async)?.payload?.page; track entry) {
|
||||
<tr data-test="orcidQueueElementRow">
|
||||
<td style="width: 15%" class="text-center align-middle">
|
||||
<i [ngClass]="getIconClass(entry)" [ngbTooltip]="getIconTooltip(entry) | translate"
|
||||
|
@@ -80,13 +80,12 @@ export class OrcidQueueComponent implements OnInit, OnDestroy, OnChanges {
|
||||
/**
|
||||
* A list of orcid queue records
|
||||
*/
|
||||
private list$: BehaviorSubject<RemoteData<PaginatedList<OrcidQueue>>> = new BehaviorSubject<RemoteData<PaginatedList<OrcidQueue>>>({} as any);
|
||||
list$: BehaviorSubject<RemoteData<PaginatedList<OrcidQueue>>> = new BehaviorSubject<RemoteData<PaginatedList<OrcidQueue>>>({} as any);
|
||||
|
||||
/**
|
||||
* The AlertType enumeration
|
||||
* @type {AlertType}
|
||||
*/
|
||||
AlertTypeEnum = AlertType;
|
||||
readonly AlertTypeEnum = AlertType;
|
||||
|
||||
/**
|
||||
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||
@@ -132,13 +131,6 @@ export class OrcidQueueComponent implements OnInit, OnDestroy, OnChanges {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of orcid queue records
|
||||
*/
|
||||
getList(): Observable<RemoteData<PaginatedList<OrcidQueue>>> {
|
||||
return this.list$.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the icon class for the queue object type
|
||||
*
|
||||
|
@@ -9,7 +9,6 @@
|
||||
<ds-qa-event-notification [item]="item"></ds-qa-event-notification>
|
||||
<ds-notify-requests-status [itemUuid]="item.uuid"></ds-notify-requests-status>
|
||||
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
|
||||
<ds-view-tracker [object]="item"></ds-view-tracker>
|
||||
@if (!item.isWithdrawn || (isAdmin$|async)) {
|
||||
<ds-listable-object-component-loader [object]="item" [viewMode]="viewMode"></ds-listable-object-component-loader>
|
||||
}
|
||||
|
@@ -44,7 +44,6 @@ import {
|
||||
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
|
||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||
import { VarDirective } from '../../shared/utils/var.directive';
|
||||
import { ViewTrackerComponent } from '../../statistics/angulartics/dspace/view-tracker.component';
|
||||
import { ThemedItemAlertsComponent } from '../alerts/themed-item-alerts.component';
|
||||
import { ItemVersionsComponent } from '../versions/item-versions.component';
|
||||
import { ItemVersionsNoticeComponent } from '../versions/notice/item-versions-notice.component';
|
||||
@@ -142,7 +141,6 @@ describe('ItemPageComponent', () => {
|
||||
remove: { imports: [
|
||||
ThemedItemAlertsComponent,
|
||||
ItemVersionsNoticeComponent,
|
||||
ViewTrackerComponent,
|
||||
ListableObjectComponentLoaderComponent,
|
||||
ItemVersionsComponent,
|
||||
ErrorComponent,
|
||||
|
@@ -51,7 +51,6 @@ import { ErrorComponent } from '../../shared/error/error.component';
|
||||
import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component';
|
||||
import { ListableObjectComponentLoaderComponent } from '../../shared/object-collection/shared/listable-object/listable-object-component-loader.component';
|
||||
import { VarDirective } from '../../shared/utils/var.directive';
|
||||
import { ViewTrackerComponent } from '../../statistics/angulartics/dspace/view-tracker.component';
|
||||
import { ThemedItemAlertsComponent } from '../alerts/themed-item-alerts.component';
|
||||
import { getItemPageRoute } from '../item-page-routing-paths';
|
||||
import { ItemVersionsComponent } from '../versions/item-versions.component';
|
||||
@@ -76,7 +75,6 @@ import { QaEventNotificationComponent } from './qa-event-notification/qa-event-n
|
||||
VarDirective,
|
||||
ThemedItemAlertsComponent,
|
||||
ItemVersionsNoticeComponent,
|
||||
ViewTrackerComponent,
|
||||
ListableObjectComponentLoaderComponent,
|
||||
ItemVersionsComponent,
|
||||
ErrorComponent,
|
||||
|
@@ -116,7 +116,7 @@
|
||||
</ds-geospatial-item-page-field>
|
||||
}
|
||||
<div>
|
||||
<a class="btn btn-outline-primary" role="button" [routerLink]="[itemPageRoute + '/full']">
|
||||
<a class="btn btn-outline-primary" role="button" [routerLink]="[itemPageRoute + '/full']" role="button" tabindex="0">
|
||||
<i class="fas fa-info-circle"></i> {{"item.page.link.full" | translate}}
|
||||
</a>
|
||||
</div>
|
||||
|
@@ -104,7 +104,7 @@
|
||||
<ds-item-page-cc-license-field [item]="object" [variant]="'full'">
|
||||
</ds-item-page-cc-license-field>
|
||||
<div>
|
||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="button">
|
||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="button" role="button" tabindex="0">
|
||||
<i class="fas fa-info-circle"></i> {{"item.page.link.full" | translate}}
|
||||
</a>
|
||||
</div>
|
||||
|
@@ -3,6 +3,6 @@
|
||||
<h2><small><em>{{missingItem}}</em></small></h2>
|
||||
<br />
|
||||
<p class="text-center">
|
||||
<a routerLink="/home" class="btn btn-primary">{{"404.link.home-page" | translate}}</a>
|
||||
<a routerLink="/home" class="btn btn-primary" role="button" tabindex="0">{{"404.link.home-page" | translate}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<nav [ngClass]="{'open': (menuCollapsed | async) !== true}"
|
||||
[@slideMobileNav]="(windowService.isXsOrSm() | async) !== true ? 'default' : ((menuCollapsed | async) ? 'collapsed' : 'expanded')"
|
||||
[@slideMobileNav]="(isMobile$ | async) !== true ? 'default' : ((menuCollapsed | async) ? 'collapsed' : 'expanded')"
|
||||
class="navbar navbar-light navbar-expand-md px-md-0 navbar-container" role="navigation"
|
||||
[attr.aria-label]="'nav.main.description' | translate" id="main-navbar">
|
||||
<!-- TODO remove navbar-container class when https://github.com/twbs/bootstrap/issues/24726 is fixed -->
|
||||
|
@@ -5,6 +5,6 @@
|
||||
<p>{{"error-page." + code | translate}}</p>
|
||||
<br/>
|
||||
<p class="text-center">
|
||||
<a href="/home" class="btn btn-primary">{{ status + ".link.home-page" | translate}}</a>
|
||||
<a href="/home" class="btn btn-primary" role="link" tabindex="0">{{ status + ".link.home-page" | translate}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
@@ -5,6 +5,6 @@
|
||||
<p>{{"404.help" | translate}}</p>
|
||||
<br/>
|
||||
<p class="text-center">
|
||||
<a routerLink="/home" class="btn btn-primary">{{"404.link.home-page" | translate}}</a>
|
||||
<a routerLink="/home" class="btn btn-primary" role="button" tabindex="0">{{"404.link.home-page" | translate}}</a>
|
||||
</p>
|
||||
</div>
|
@@ -0,0 +1,11 @@
|
||||
<input [attr.aria-label]="'process.new.parameter.label' | translate" required #integer="ngModel" type="number" step="1" name="integer-value-{{index}}" class="form-control" id="integer-value-{{index}}" [ngModel]="value" (ngModelChange)="setValue($event)"/>
|
||||
@if (integer.invalid && (integer.dirty || integer.touched)) {
|
||||
<div
|
||||
class="alert alert-danger validation-error mb-0">
|
||||
@if (integer.errors.required) {
|
||||
<div>
|
||||
{{'process.new.parameter.integer.required' | translate}}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(var(--bs-spacer) / 2);
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
import {
|
||||
ComponentFixture,
|
||||
fakeAsync,
|
||||
TestBed,
|
||||
tick,
|
||||
waitForAsync,
|
||||
} from '@angular/core/testing';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import {
|
||||
TranslateLoader,
|
||||
TranslateModule,
|
||||
} from '@ngx-translate/core';
|
||||
|
||||
import { TranslateLoaderMock } from '../../../../../shared/mocks/translate-loader.mock';
|
||||
import { IntegerValueInputComponent } from './integer-value-input.component';
|
||||
|
||||
describe('IntegerValueInputComponent', () => {
|
||||
let component: IntegerValueInputComponent;
|
||||
let fixture: ComponentFixture<IntegerValueInputComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
FormsModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderMock,
|
||||
},
|
||||
}),
|
||||
IntegerValueInputComponent,
|
||||
],
|
||||
providers: [],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(IntegerValueInputComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not show a validation error if the input field was left untouched but left empty', () => {
|
||||
const validationError = fixture.debugElement.query(By.css('.validation-error'));
|
||||
expect(validationError).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should show a validation error if the input field was touched but left empty', fakeAsync(() => {
|
||||
component.value = undefined;
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
const input = fixture.debugElement.query(By.css('input'));
|
||||
input.triggerEventHandler('blur', null);
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
const validationError = fixture.debugElement.query(By.css('.validation-error'));
|
||||
expect(validationError).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should not show a validation error if the input field was touched but not left empty', fakeAsync(() => {
|
||||
component.value = 1;
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
|
||||
const input = fixture.debugElement.query(By.css('input'));
|
||||
input.triggerEventHandler('blur', null);
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
const validationError = fixture.debugElement.query(By.css('.validation-error'));
|
||||
expect(validationError).toBeFalsy();
|
||||
}));
|
||||
});
|
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
OnInit,
|
||||
Optional,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
ControlContainer,
|
||||
FormsModule,
|
||||
NgForm,
|
||||
} from '@angular/forms';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { controlContainerFactory } from '../../../process-form-factory';
|
||||
import { ValueInputComponent } from '../value-input.component';
|
||||
|
||||
/**
|
||||
* Represents the user-inputted value of an integer parameter
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-integer-value-input',
|
||||
templateUrl: './integer-value-input.component.html',
|
||||
styleUrls: ['./integer-value-input.component.scss'],
|
||||
viewProviders: [{ provide: ControlContainer,
|
||||
useFactory: controlContainerFactory,
|
||||
deps: [[new Optional(), NgForm]] }],
|
||||
standalone: true,
|
||||
imports: [FormsModule, TranslateModule],
|
||||
})
|
||||
export class IntegerValueInputComponent extends ValueInputComponent<number> implements OnInit {
|
||||
/**
|
||||
* The current value of the integer
|
||||
*/
|
||||
value: number;
|
||||
|
||||
/**
|
||||
* Initial value of the field
|
||||
*/
|
||||
@Input() initialValue;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.value = this.initialValue;
|
||||
}
|
||||
|
||||
setValue(value) {
|
||||
this.value = value;
|
||||
this.updateValue.emit(value);
|
||||
}
|
||||
}
|
@@ -3,6 +3,9 @@
|
||||
@case (parameterTypes.STRING) {
|
||||
<ds-string-value-input [initialValue]="initialValue" (updateValue)="updateValue.emit($event)" [index]="index"></ds-string-value-input>
|
||||
}
|
||||
@case (parameterTypes.INTEGER) {
|
||||
<ds-integer-value-input [initialValue]="initialValue" (updateValue)="updateValue.emit($event)" [index]="index"></ds-integer-value-input>
|
||||
}
|
||||
@case (parameterTypes.OUTPUT) {
|
||||
<ds-string-value-input [initialValue]="initialValue" (updateValue)="updateValue.emit($event)" [index]="index"></ds-string-value-input>
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ import { controlContainerFactory } from '../../process-form-factory';
|
||||
import { BooleanValueInputComponent } from './boolean-value-input/boolean-value-input.component';
|
||||
import { DateValueInputComponent } from './date-value-input/date-value-input.component';
|
||||
import { FileValueInputComponent } from './file-value-input/file-value-input.component';
|
||||
import { IntegerValueInputComponent } from './number-value-input/integer-value-input.component';
|
||||
import { StringValueInputComponent } from './string-value-input/string-value-input.component';
|
||||
|
||||
/**
|
||||
@@ -30,7 +31,7 @@ import { StringValueInputComponent } from './string-value-input/string-value-inp
|
||||
useFactory: controlContainerFactory,
|
||||
deps: [[new Optional(), NgForm]] }],
|
||||
standalone: true,
|
||||
imports: [StringValueInputComponent, DateValueInputComponent, FileValueInputComponent, BooleanValueInputComponent],
|
||||
imports: [StringValueInputComponent, DateValueInputComponent, FileValueInputComponent, BooleanValueInputComponent, IntegerValueInputComponent],
|
||||
})
|
||||
export class ParameterValueInputComponent {
|
||||
@Input() index: number;
|
||||
|
@@ -62,20 +62,20 @@ class="fas fa-plus pe-2"></i>{{'process.overview.new' | translate}}</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
@if ((processBulkDeleteService.isProcessing$() | async) !== true) {
|
||||
<div>{{'process.overview.delete.body' | translate: {count: processBulkDeleteService.getAmountOfSelectedProcesses()} }}</div>
|
||||
}
|
||||
@if (processBulkDeleteService.isProcessing$() |async) {
|
||||
@let isProcessing = (isProcessing$ | async);
|
||||
@if (isProcessing) {
|
||||
<div class="alert alert-info">
|
||||
<span class="spinner-border spinner-border-sm spinner-button" role="status" aria-hidden="true"></span>
|
||||
<span> {{ 'process.overview.delete.processing' | translate: {count: processBulkDeleteService.getAmountOfSelectedProcesses()} }}</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div>{{'process.overview.delete.body' | translate: {count: processBulkDeleteService.getAmountOfSelectedProcesses()} }}</div>
|
||||
}
|
||||
<div class="mt-4">
|
||||
<button class="btn btn-primary me-2" [dsBtnDisabled]="processBulkDeleteService.isProcessing$() |async"
|
||||
<button class="btn btn-primary me-2" [dsBtnDisabled]="isProcessing"
|
||||
(click)="closeModal()">{{'process.detail.delete.cancel' | translate}}</button>
|
||||
<button id="delete-confirm" class="btn btn-danger"
|
||||
[dsBtnDisabled]="processBulkDeleteService.isProcessing$() |async"
|
||||
[dsBtnDisabled]="isProcessing"
|
||||
(click)="deleteSelected()">{{ 'process.overview.delete' | translate: {count: processBulkDeleteService.getAmountOfSelectedProcesses()} }}
|
||||
</button>
|
||||
</div>
|
||||
|
@@ -12,7 +12,10 @@ import {
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import {
|
||||
Observable,
|
||||
Subscription,
|
||||
} from 'rxjs';
|
||||
|
||||
import { BtnDisabledDirective } from '../../shared/btn-disabled.directive';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
@@ -45,6 +48,8 @@ export class ProcessOverviewComponent implements OnInit, OnDestroy {
|
||||
|
||||
isProcessingSub: Subscription;
|
||||
|
||||
isProcessing$: Observable<boolean>;
|
||||
|
||||
constructor(protected processOverviewService: ProcessOverviewService,
|
||||
protected modalService: NgbModal,
|
||||
public processBulkDeleteService: ProcessBulkDeleteService,
|
||||
@@ -53,6 +58,7 @@ export class ProcessOverviewComponent implements OnInit, OnDestroy {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.processBulkDeleteService.clearAllProcesses();
|
||||
this.isProcessing$ = this.processBulkDeleteService.isProcessing$();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
export enum ScriptParameterType {
|
||||
STRING = 'String',
|
||||
INTEGER = 'Integer',
|
||||
DATE = 'date',
|
||||
BOOLEAN = 'boolean',
|
||||
FILE = 'InputStream',
|
||||
|
@@ -17,15 +17,15 @@
|
||||
</div>
|
||||
}
|
||||
@if (!researcherProfile) {
|
||||
@let processingCreate = (processingCreate$ | async);
|
||||
<button class="btn btn-primary me-2"
|
||||
[dsBtnDisabled]="(isProcessingCreate() | async)"
|
||||
[dsBtnDisabled]="processingCreate"
|
||||
(click)="createProfile()">
|
||||
@if ((isProcessingCreate() | async)) {
|
||||
@if (processingCreate) {
|
||||
<span>
|
||||
<i class='fas fa-circle-notch fa-spin'></i> {{'researcher.profile.action.processing' | translate}}
|
||||
</span>
|
||||
}
|
||||
@if ((isProcessingCreate() | async) !== true) {
|
||||
} @else {
|
||||
<span>
|
||||
<i class="fas fa-plus"></i> {{'researcher.profile.create.new' | translate}}
|
||||
</span>
|
||||
@@ -37,12 +37,11 @@
|
||||
<i class="fas fa-info-circle"></i> {{'researcher.profile.view' | translate}}
|
||||
</button>
|
||||
<button class="btn btn-danger" [dsBtnDisabled]="!researcherProfile" (click)="deleteProfile(researcherProfile)">
|
||||
@if ((isProcessingDelete() | async)) {
|
||||
@if ((processingDelete$ | async)) {
|
||||
<span>
|
||||
<i class='fas fa-circle-notch fa-spin'></i> {{'researcher.profile.action.processing' | translate}}
|
||||
</span>
|
||||
}
|
||||
@if ((isProcessingDelete() | async) !== true) {
|
||||
} @else {
|
||||
<span>
|
||||
<i class="fas fa-trash-alt"></i> {{'researcher.profile.delete' | translate}}
|
||||
</span>
|
||||
|
@@ -11,10 +11,7 @@ import {
|
||||
TranslateService,
|
||||
} from '@ngx-translate/core';
|
||||
import { UiSwitchModule } from 'ngx-ui-switch';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
Observable,
|
||||
} from 'rxjs';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import {
|
||||
map,
|
||||
mergeMap,
|
||||
@@ -187,24 +184,6 @@ export class ProfilePageResearcherFormComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a boolean representing if a delete operation is pending.
|
||||
*
|
||||
* @return {Observable<boolean>}
|
||||
*/
|
||||
isProcessingDelete(): Observable<boolean> {
|
||||
return this.processingDelete$.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a boolean representing if a create operation is pending.
|
||||
*
|
||||
* @return {Observable<boolean>}
|
||||
*/
|
||||
isProcessingCreate(): Observable<boolean> {
|
||||
return this.processingCreate$.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new profile related to the current user from scratch.
|
||||
*/
|
||||
|
@@ -54,15 +54,15 @@
|
||||
</ds-alert>
|
||||
}
|
||||
|
||||
@if (isRecaptchaCookieAccepted() && (googleRecaptchaService.captchaVersion() | async) === 'v2') {
|
||||
@if (isRecaptchaCookieAccepted() && (captchaVersion$ | async) === 'v2') {
|
||||
<div class="my-3">
|
||||
<ds-google-recaptcha [captchaMode]="(googleRecaptchaService.captchaMode() | async)"
|
||||
<ds-google-recaptcha [captchaMode]="(captchaMode$ | async)"
|
||||
(executeRecaptcha)="register($event)" (checkboxChecked)="onCheckboxChecked($event)"
|
||||
(showNotification)="showNotification($event)"></ds-google-recaptcha>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if ((!registrationVerification || ((googleRecaptchaService.captchaVersion() | async) !== 'v2' && (googleRecaptchaService.captchaMode() | async) === 'invisible'))) {
|
||||
@if ((!registrationVerification || ((captchaVersion$ | async) !== 'v2' && (captchaMode$ | async) === 'invisible'))) {
|
||||
<button class="btn btn-primary" [dsBtnDisabled]="form.invalid || registrationVerification && !isRecaptchaCookieAccepted() || disableUntilChecked" (click)="register()">
|
||||
{{ MESSAGE_PREFIX + '.submit' | translate }}
|
||||
</button>
|
||||
|
@@ -107,13 +107,9 @@ export class RegisterEmailFormComponent implements OnDestroy, OnInit {
|
||||
|
||||
subscriptions: Subscription[] = [];
|
||||
|
||||
captchaVersion(): Observable<string> {
|
||||
return this.googleRecaptchaService.captchaVersion();
|
||||
}
|
||||
captchaVersion$: Observable<string>;
|
||||
|
||||
captchaMode(): Observable<string> {
|
||||
return this.googleRecaptchaService.captchaMode();
|
||||
}
|
||||
captchaMode$: Observable<string>;
|
||||
|
||||
constructor(
|
||||
private epersonRegistrationService: EpersonRegistrationService,
|
||||
@@ -135,6 +131,8 @@ export class RegisterEmailFormComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.captchaVersion$ = this.googleRecaptchaService.captchaVersion();
|
||||
this.captchaMode$ = this.googleRecaptchaService.captchaMode();
|
||||
const validators: ValidatorFn[] = [
|
||||
Validators.required,
|
||||
Validators.email,
|
||||
@@ -191,7 +189,7 @@ export class RegisterEmailFormComponent implements OnDestroy, OnInit {
|
||||
register(tokenV2?) {
|
||||
if (!this.form.invalid) {
|
||||
if (this.registrationVerification) {
|
||||
this.subscriptions.push(combineLatest([this.captchaVersion(), this.captchaMode()]).pipe(
|
||||
this.subscriptions.push(combineLatest([this.captchaVersion$, this.captchaMode$]).pipe(
|
||||
switchMap(([captchaVersion, captchaMode]) => {
|
||||
if (captchaVersion === 'v3') {
|
||||
return this.googleRecaptchaService.getRecaptchaToken('register_email');
|
||||
@@ -254,7 +252,7 @@ export class RegisterEmailFormComponent implements OnDestroy, OnInit {
|
||||
*/
|
||||
disableUntilCheckedFcn(): Observable<boolean> {
|
||||
const checked$ = this.checkboxCheckedSubject$.asObservable();
|
||||
return combineLatest([this.captchaVersion(), this.captchaMode(), checked$]).pipe(
|
||||
return combineLatest([this.captchaVersion$, this.captchaMode$, checked$]).pipe(
|
||||
// disable if checkbox is not checked or if reCaptcha is not in v2 checkbox mode
|
||||
switchMap(([captchaVersion, captchaMode, checked]) => captchaVersion === 'v2' && captchaMode === 'checkbox' ? of(!checked) : of(false)),
|
||||
startWith(true),
|
||||
|
@@ -42,7 +42,7 @@
|
||||
<div class="processed-message">
|
||||
<p>{{ 'grant-deny-request-copy.processed' | translate }}</p>
|
||||
<p class="text-center">
|
||||
<a routerLink="/home" class="btn btn-primary">{{ 'grant-deny-request-copy.home-page' | translate }}</a>
|
||||
<a routerLink="/home" class="btn btn-primary" role="button" tabindex="0">{{ 'grant-deny-request-copy.home-page' | translate }}</a>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
[class.display]="searchExpanded ? 'inline-block' : 'none'"
|
||||
[tabIndex]="searchExpanded ? 0 : -1"
|
||||
[attr.data-test]="'header-search-box' | dsBrowserOnly">
|
||||
<button class="submit-icon btn btn-link btn-link-inline" [attr.aria-label]="'nav.search.button' | translate" type="button" (click)="searchExpanded ? onSubmit(searchForm.value) : expand()" [attr.data-test]="'header-search-icon' | dsBrowserOnly">
|
||||
<button class="submit-icon btn btn-link btn-link-inline" [attr.aria-label]="'nav.search.button' | translate" type="button" (click)="searchExpanded ? onSubmit(searchForm.value) : expand()" [attr.data-test]="'header-search-icon' | dsBrowserOnly" tabindex="0" role="button">
|
||||
<em class="fas fa-search fa-lg fa-fw"></em>
|
||||
</button>
|
||||
</form>
|
||||
|
@@ -8,6 +8,7 @@
|
||||
<a href="javascript:void(0);" class="dropdownLogin px-0.5" [attr.aria-label]="'nav.login' |translate"
|
||||
(click)="$event.preventDefault()" [attr.data-test]="'login-menu' | dsBrowserOnly"
|
||||
role="menuitem"
|
||||
tabindex="0"
|
||||
aria-haspopup="menu"
|
||||
aria-controls="loginDropdownMenu"
|
||||
[attr.aria-expanded]="loginDrop.isOpen()"
|
||||
@@ -26,6 +27,7 @@
|
||||
<div ngbDropdown #loggedInDrop="ngbDropdown" display="dynamic" placement="bottom-right" class="d-inline-block" @fadeInOut>
|
||||
<a href="javascript:void(0);"
|
||||
role="menuitem"
|
||||
tabindex="0"
|
||||
[attr.aria-label]="'nav.user-profile-menu-and-logout' | translate"
|
||||
aria-controls="user-menu-dropdown"
|
||||
(click)="$event.preventDefault()" [title]="'nav.user-profile-menu-and-logout' | translate"
|
||||
@@ -43,12 +45,12 @@
|
||||
} @else {
|
||||
<div data-test="auth-nav">
|
||||
@if ((isAuthenticated | async) !== true) {
|
||||
<a routerLink="/login" routerLinkActive="active" class="loginLink px-0.5" role="button">
|
||||
<a routerLink="/login" routerLinkActive="active" class="loginLink px-0.5" role="button" tabindex="0">
|
||||
{{ 'nav.login' | translate }}<span class="sr-only">(current)</span>
|
||||
</a>
|
||||
}
|
||||
@if ((isAuthenticated | async)) {
|
||||
<a role="button" [attr.aria-label]="'nav.logout' |translate" [title]="'nav.logout' | translate" routerLink="/logout" routerLinkActive="active" class="logoutLink px-1">
|
||||
<a role="button" [attr.aria-label]="'nav.logout' |translate" [title]="'nav.logout' | translate" routerLink="/logout" routerLinkActive="active" class="logoutLink px-1" role="button" tabindex="0">
|
||||
<i class="fas fa-sign-out-alt fa-lg fa-fw"></i>
|
||||
<span class="sr-only">(current)</span>
|
||||
</a>
|
||||
|
@@ -10,7 +10,9 @@
|
||||
role="tab"
|
||||
[routerLink]="option.routerLink"
|
||||
[queryParams]="option.params"
|
||||
[class.active]="(currentOption$ | async)?.id === option.id">
|
||||
[class.active]="(currentOption$ | async)?.id === option.id"
|
||||
role="tab"
|
||||
tabindex="0">
|
||||
{{ option.label | translate }}
|
||||
</a>
|
||||
}
|
||||
|
@@ -3,6 +3,6 @@
|
||||
@if (title) {
|
||||
<span class="mb-0">{{ title | translate }}</span>
|
||||
}
|
||||
<a [href]="getHandle()">{{getHandle()}}</a>
|
||||
<a [href]="getHandle()" role="link" tabindex="0">{{getHandle()}}</a>
|
||||
</p>
|
||||
}
|
||||
|
@@ -433,4 +433,31 @@ describe('BrowserOrejimeService', () => {
|
||||
expect(service.orejimeConfig.apps).not.toContain(jasmine.objectContaining({ name: googleAnalytics }));
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyUpdateSettingsCallbackToApps', () => {
|
||||
let user2: EPerson;
|
||||
let mockApp1, mockApp2;
|
||||
let updateSettingsSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
user2 = Object.assign(new EPerson(), { uuid: 'test-user' });
|
||||
mockApp1 = { name: 'app1', callback: jasmine.createSpy('originalCallback1') };
|
||||
mockApp2 = { name: 'app2', callback: jasmine.createSpy('originalCallback2') };
|
||||
service.orejimeConfig.apps = [mockApp1, mockApp2];
|
||||
updateSettingsSpy = spyOn(service, 'updateSettingsForUsers');
|
||||
});
|
||||
|
||||
it('calls updateSettingsForUsers in a debounced manner when a callback is triggered', (done) => {
|
||||
service.applyUpdateSettingsCallbackToApps(user2);
|
||||
|
||||
mockApp1.callback(true);
|
||||
mockApp2.callback(false);
|
||||
|
||||
setTimeout(() => {
|
||||
expect(updateSettingsSpy).toHaveBeenCalledTimes(1);
|
||||
expect(updateSettingsSpy).toHaveBeenCalledWith(user2);
|
||||
done();
|
||||
}, 400);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -192,12 +192,39 @@ export class BrowserOrejimeService extends OrejimeService {
|
||||
this.translateConfiguration();
|
||||
|
||||
this.orejimeConfig.apps = this.filterConfigApps(appsToHide);
|
||||
this.lazyOrejime.then(({ init }) => {
|
||||
|
||||
this.applyUpdateSettingsCallbackToApps(user);
|
||||
|
||||
void this.lazyOrejime.then(({ init }) => {
|
||||
this.orejimeInstance = init(this.orejimeConfig);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a debounced callback to update user settings for all apps in the Orejime configuration.
|
||||
*
|
||||
* This method modifies the `callback` property of each app in the `orejimeConfig.apps` array.
|
||||
* It ensures that the `updateSettingsForUsers` method is called in a debounced manner whenever
|
||||
* a consent change occurs for any app. Additionally, it preserves and invokes the original
|
||||
* callback for each app if one is defined.
|
||||
*
|
||||
* @param {EPerson} user - The authenticated user whose settings are being updated.
|
||||
*/
|
||||
applyUpdateSettingsCallbackToApps(user: EPerson) {
|
||||
const updateSettingsCallback = debounce(() => this.updateSettingsForUsers(user), updateDebounce);
|
||||
|
||||
this.orejimeConfig.apps.forEach((app) => {
|
||||
const originalCallback = app.callback;
|
||||
app.callback = (consent: boolean) => {
|
||||
updateSettingsCallback();
|
||||
if (originalCallback) {
|
||||
originalCallback(consent);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return saved preferences stored in the orejime cookie
|
||||
*/
|
||||
@@ -220,7 +247,6 @@ export class BrowserOrejimeService extends OrejimeService {
|
||||
* @param user The authenticated user
|
||||
*/
|
||||
private initializeUser(user: EPerson) {
|
||||
this.orejimeConfig.callback = debounce((consent, app) => this.updateSettingsForUsers(user), updateDebounce);
|
||||
this.orejimeConfig.cookieName = this.getStorageName(user.uuid);
|
||||
|
||||
const anonCookie = this.cookieService.get(ANONYMOUS_STORAGE_NAME_OREJIME);
|
||||
@@ -387,7 +413,9 @@ export class BrowserOrejimeService extends OrejimeService {
|
||||
* @param user
|
||||
*/
|
||||
updateSettingsForUsers(user: EPerson) {
|
||||
this.setSettingsForUser(user, this.cookieService.get(this.getStorageName(user.uuid)));
|
||||
if (user) {
|
||||
this.setSettingsForUser(user, this.cookieService.get(this.getStorageName(user.uuid)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,15 +1,15 @@
|
||||
<div class="mt-3" @fadeInOut>
|
||||
@if (isListOfEPerson) {
|
||||
<ds-eperson-search-box (search)="onSearch($event)"></ds-eperson-search-box>
|
||||
}
|
||||
@if (!isListOfEPerson) {
|
||||
} @else {
|
||||
<ds-group-search-box (search)="onSearch($event)"></ds-group-search-box>
|
||||
}
|
||||
|
||||
@if ((getList() | async)?.payload?.totalElements > 0) {
|
||||
@let list = (list$ | async);
|
||||
@if (list && list.totalElements > 0) {
|
||||
<ds-pagination
|
||||
[paginationOptions]="paginationOptions"
|
||||
[collectionSize]="(getList() | async)?.payload?.totalElements"
|
||||
[collectionSize]="list.totalElements"
|
||||
[retainScrollPosition]="true"
|
||||
[hideGear]="true">
|
||||
<div class="table-responsive">
|
||||
@@ -22,11 +22,10 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (entry of (getList() | async)?.payload?.page; track entry) {
|
||||
<tr
|
||||
[class.table-primary]="isSelected(entry) | async">
|
||||
<td>{{entry.id}}</td>
|
||||
<td>{{dsoNameService.getName(entry)}}</td>
|
||||
@for (entry of list.page; track entry) {
|
||||
<tr [class.table-primary]="(entrySelectedId$ | async) === entry.id">
|
||||
<td>{{ entry.id }}</td>
|
||||
<td>{{ dsoNameService.getName(entry) }}</td>
|
||||
<td class="text-center">
|
||||
<button class="btn btn-sm btn-outline-primary" (click)="emitSelect(entry)">
|
||||
{{'resource-policies.form.eperson-group-list.select.btn' | translate}}
|
||||
|
@@ -13,7 +13,7 @@ import {
|
||||
} from '@angular/core/testing';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { cold } from 'jasmine-marbles';
|
||||
import { hot } from 'jasmine-marbles';
|
||||
import uniqueId from 'lodash/uniqueId';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
|
||||
@@ -49,14 +49,13 @@ const mockDataServiceMap: LazyDataServicesMap = new Map([
|
||||
[GROUP.value, () => import('../../core/eperson/group-data.service').then(m => m.GroupDataService)],
|
||||
]);
|
||||
|
||||
describe('EpersonGroupListComponent test suite', () => {
|
||||
describe('EpersonGroupListComponent', () => {
|
||||
let comp: EpersonGroupListComponent;
|
||||
let compAsAny: any;
|
||||
let fixture: ComponentFixture<EpersonGroupListComponent>;
|
||||
let de;
|
||||
let groupService: any;
|
||||
let epersonService: any;
|
||||
let paginationService;
|
||||
let paginationService: PaginationServiceStub;
|
||||
|
||||
const paginationOptions: PaginationComponentOptions = new PaginationComponentOptions();
|
||||
paginationOptions.id = uniqueId('eperson-group-list-pagination-test');
|
||||
@@ -129,7 +128,6 @@ describe('EpersonGroupListComponent test suite', () => {
|
||||
}));
|
||||
|
||||
describe('', () => {
|
||||
let testComp: TestComponent;
|
||||
let testFixture: ComponentFixture<TestComponent>;
|
||||
|
||||
// synchronous beforeEach
|
||||
@@ -139,7 +137,6 @@ describe('EpersonGroupListComponent test suite', () => {
|
||||
<ds-eperson-group-list [isListOfEPerson]="isListOfEPerson" [initSelected]="initSelected"></ds-eperson-group-list>`;
|
||||
|
||||
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||
testComp = testFixture.componentInstance;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -167,7 +164,6 @@ describe('EpersonGroupListComponent test suite', () => {
|
||||
afterEach(() => {
|
||||
comp = null;
|
||||
compAsAny = null;
|
||||
de = null;
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
@@ -181,29 +177,25 @@ describe('EpersonGroupListComponent test suite', () => {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should init entrySelectedId', fakeAsync(() => {
|
||||
it('should init entrySelectedId', fakeAsync(async () => {
|
||||
spyOn(comp, 'updateList');
|
||||
comp.initSelected = EPersonMock.id;
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(compAsAny.entrySelectedId.value).toBe(EPersonMock.id);
|
||||
});
|
||||
|
||||
await fixture.whenStable();
|
||||
expect(comp.entrySelectedId$.value).toBe(EPersonMock.id);
|
||||
}));
|
||||
|
||||
it('should init the list of eperson', fakeAsync(() => {
|
||||
it('should init the list of eperson', fakeAsync(async () => {
|
||||
epersonService.searchByScope.and.returnValue(observableOf(epersonPaginatedListRD));
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(compAsAny.list$.value).toEqual(epersonPaginatedListRD);
|
||||
expect(comp.getList()).toBeObservable(cold('a', {
|
||||
a: epersonPaginatedListRD,
|
||||
}));
|
||||
});
|
||||
await fixture.whenStable();
|
||||
expect(comp.list$).toBeObservable(hot('(a|)', {
|
||||
a: epersonPaginatedList,
|
||||
}));
|
||||
}));
|
||||
|
||||
it('should emit select event', () => {
|
||||
@@ -211,23 +203,13 @@ describe('EpersonGroupListComponent test suite', () => {
|
||||
comp.emitSelect(EPersonMock);
|
||||
|
||||
expect(comp.select.emit).toHaveBeenCalled();
|
||||
expect(compAsAny.entrySelectedId.value).toBe(EPersonMock.id);
|
||||
expect(comp.entrySelectedId$.value).toBe(EPersonMock.id);
|
||||
});
|
||||
|
||||
it('should return true when entry is selected', () => {
|
||||
compAsAny.entrySelectedId.next(EPersonMock.id);
|
||||
it('should return the entrySelectedId$ value', () => {
|
||||
comp.entrySelectedId$.next(EPersonMock.id);
|
||||
|
||||
expect(comp.isSelected(EPersonMock)).toBeObservable(cold('a', {
|
||||
a: true,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should return false when entry is not selected', () => {
|
||||
compAsAny.entrySelectedId.next('');
|
||||
|
||||
expect(comp.isSelected(EPersonMock)).toBeObservable(cold('a', {
|
||||
a: false,
|
||||
}));
|
||||
expect(comp.entrySelectedId$.value).toBe(EPersonMock.id);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -245,7 +227,6 @@ describe('EpersonGroupListComponent test suite', () => {
|
||||
afterEach(() => {
|
||||
comp = null;
|
||||
compAsAny = null;
|
||||
de = null;
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
@@ -260,27 +241,24 @@ describe('EpersonGroupListComponent test suite', () => {
|
||||
|
||||
}));
|
||||
|
||||
it('should init entrySelectedId', fakeAsync(() => {
|
||||
it('should init entrySelectedId', fakeAsync(async () => {
|
||||
spyOn(comp, 'updateList');
|
||||
comp.initSelected = GroupMock.id;
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(compAsAny.entrySelectedId.value).toBe(GroupMock.id);
|
||||
});
|
||||
await fixture.whenStable();
|
||||
expect(comp.entrySelectedId$.value).toBe(GroupMock.id);
|
||||
}));
|
||||
|
||||
it('should init the list of group', fakeAsync(() => {
|
||||
it('should init the list of group', fakeAsync(async () => {
|
||||
groupService.searchGroups.and.returnValue(observableOf(groupPaginatedListRD));
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(compAsAny.list$.value).toEqual(groupPaginatedListRD);
|
||||
expect(comp.getList()).toBeObservable(cold('a', {
|
||||
a: groupPaginatedListRD,
|
||||
}));
|
||||
});
|
||||
await fixture.whenStable();
|
||||
expect(comp.list$).toBeObservable(hot('(a|)', {
|
||||
a: groupPaginatedList,
|
||||
}));
|
||||
}));
|
||||
|
||||
it('should emit select event', () => {
|
||||
@@ -288,27 +266,16 @@ describe('EpersonGroupListComponent test suite', () => {
|
||||
comp.emitSelect(GroupMock);
|
||||
|
||||
expect(comp.select.emit).toHaveBeenCalled();
|
||||
expect(compAsAny.entrySelectedId.value).toBe(GroupMock.id);
|
||||
expect(comp.entrySelectedId$.value).toBe(GroupMock.id);
|
||||
});
|
||||
|
||||
it('should return true when entry is selected', () => {
|
||||
compAsAny.entrySelectedId.next(EPersonMock.id);
|
||||
it('should return the entrySelectedId$ value', () => {
|
||||
comp.entrySelectedId$.next(GroupMock.id);
|
||||
|
||||
expect(comp.isSelected(EPersonMock)).toBeObservable(cold('a', {
|
||||
a: true,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should return false when entry is not selected', () => {
|
||||
compAsAny.entrySelectedId.next('');
|
||||
|
||||
expect(comp.isSelected(EPersonMock)).toBeObservable(cold('a', {
|
||||
a: false,
|
||||
}));
|
||||
expect(comp.entrySelectedId$.value).toBe(GroupMock.id);
|
||||
});
|
||||
|
||||
it('should update list on search triggered', () => {
|
||||
const options: PaginationComponentOptions = comp.paginationOptions;
|
||||
const event: SearchEvent = {
|
||||
scope: 'metadata',
|
||||
query: 'test',
|
||||
@@ -316,7 +283,7 @@ describe('EpersonGroupListComponent test suite', () => {
|
||||
spyOn(comp, 'updateList');
|
||||
comp.onSearch(event);
|
||||
|
||||
expect(compAsAny.updateList).toHaveBeenCalledWith('metadata', 'test');
|
||||
expect(comp.updateList).toHaveBeenCalledWith('metadata', 'test');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user