mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge branch 'dspace-8_x' into accessibility-settings-8_x
This commit is contained in:
@@ -12,7 +12,6 @@
|
|||||||
"eslint-plugin-rxjs",
|
"eslint-plugin-rxjs",
|
||||||
"eslint-plugin-simple-import-sort",
|
"eslint-plugin-simple-import-sort",
|
||||||
"eslint-plugin-import-newlines",
|
"eslint-plugin-import-newlines",
|
||||||
"eslint-plugin-jsonc",
|
|
||||||
"dspace-angular-ts",
|
"dspace-angular-ts",
|
||||||
"dspace-angular-html"
|
"dspace-angular-html"
|
||||||
],
|
],
|
||||||
@@ -302,10 +301,13 @@
|
|||||||
"*.json5"
|
"*.json5"
|
||||||
],
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
"plugin:jsonc/recommended-with-jsonc"
|
"plugin:jsonc/recommended-with-json5"
|
||||||
],
|
],
|
||||||
"rules": {
|
"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",
|
"no-trailing-spaces": "error",
|
||||||
"jsonc/comma-dangle": [
|
"jsonc/comma-dangle": [
|
||||||
"error",
|
"error",
|
||||||
|
105
.github/workflows/build.yml
vendored
105
.github/workflows/build.yml
vendored
@@ -190,12 +190,115 @@ jobs:
|
|||||||
# Get homepage and verify that the <meta name="title"> tag includes "DSpace".
|
# Get homepage and verify that the <meta name="title"> tag includes "DSpace".
|
||||||
# If it does, then SSR is working, as this tag is created by our MetadataService.
|
# If it does, then SSR is working, as this tag is created by our MetadataService.
|
||||||
# This step also prints entire HTML of homepage for easier debugging if grep fails.
|
# This step also prints entire HTML of homepage for easier debugging if grep fails.
|
||||||
- name: Verify SSR (server-side rendering)
|
- name: Verify SSR (server-side rendering) on Homepage
|
||||||
run: |
|
run: |
|
||||||
result=$(wget -O- -q http://127.0.0.1:4000/home)
|
result=$(wget -O- -q http://127.0.0.1:4000/home)
|
||||||
echo "$result"
|
echo "$result"
|
||||||
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep DSpace
|
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep DSpace
|
||||||
|
|
||||||
|
# Get a specific community in our test data and verify that the "<h1>" tag includes "Publications" (the community name).
|
||||||
|
# If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on a Community page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/communities/0958c910-2037-42a9-81c7-dca80e3892b4)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<h1 [^>]*>[^><]*</h1>" | grep Publications
|
||||||
|
|
||||||
|
# Get a specific collection in our test data and verify that the "<h1>" tag includes "Articles" (the collection name).
|
||||||
|
# If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on a Collection page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/collections/282164f5-d325-4740-8dd1-fa4d6d3e7200)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<h1 [^>]*>[^><]*</h1>" | grep Articles
|
||||||
|
|
||||||
|
# Get a specific publication in our test data and verify that the <meta name="title"> tag includes
|
||||||
|
# the title of this publication. If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on a Publication page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/entities/publication/6160810f-1e53-40db-81ef-f6621a727398)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "An Economic Model of Mortality Salience"
|
||||||
|
|
||||||
|
# Get a specific person in our test data and verify that the <meta name="title"> tag includes
|
||||||
|
# the name of the person. If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on a Person page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/entities/person/b1b2c768-bda1-448a-a073-fc541e8b24d9)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "Simmons, Cameron"
|
||||||
|
|
||||||
|
# Get a specific project in our test data and verify that the <meta name="title"> tag includes
|
||||||
|
# the name of the project. If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on a Project page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/entities/project/46ccb608-a74c-4bf6-bc7a-e29cc7defea9)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "University Research Fellowship"
|
||||||
|
|
||||||
|
# Get a specific orgunit in our test data and verify that the <meta name="title"> tag includes
|
||||||
|
# the name of the orgunit. If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on an OrgUnit page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/entities/orgunit/9851674d-bd9a-467b-8d84-068deb568ccf)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "Law and Development"
|
||||||
|
|
||||||
|
# Get a specific journal in our test data and verify that the <meta name="title"> tag includes
|
||||||
|
# the name of the journal. If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on a Journal page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/entities/journal/d4af6c3e-53d0-4757-81eb-566f3b45d63a)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "Environmental & Architectural Phenomenology"
|
||||||
|
|
||||||
|
# Get a specific journal volume in our test data and verify that the <meta name="title"> tag includes
|
||||||
|
# the name of the volume. If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on a Journal Volume page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/entities/journalvolume/07c6249f-4bf7-494d-9ce3-6ffdb2aed538)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "Environmental & Architectural Phenomenology Volume 28 (2017)"
|
||||||
|
|
||||||
|
# Get a specific journal issue in our test data and verify that the <meta name="title"> tag includes
|
||||||
|
# the name of the issue. If it does, then SSR is working.
|
||||||
|
- name: Verify SSR on a Journal Issue page
|
||||||
|
run: |
|
||||||
|
result=$(wget -O- -q http://127.0.0.1:4000/entities/journalissue/44c29473-5de2-48fa-b005-e5029aa1a50b)
|
||||||
|
echo "$result"
|
||||||
|
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep "Environmental & Architectural Phenomenology Vol. 28, No. 1"
|
||||||
|
|
||||||
|
# Verify 301 Handle redirect behavior
|
||||||
|
# Note: /handle/123456789/260 is the same test Publication used by our e2e tests
|
||||||
|
- name: Verify 301 redirect from '/handle' URLs
|
||||||
|
run: |
|
||||||
|
result=$(wget --server-response --quiet http://127.0.0.1:4000/handle/123456789/260 2>&1 | head -1 | awk '{print $2}')
|
||||||
|
echo "$result"
|
||||||
|
[[ "$result" -eq "301" ]]
|
||||||
|
|
||||||
|
# Verify 403 error code behavior
|
||||||
|
- name: Verify 403 error code from '/403'
|
||||||
|
run: |
|
||||||
|
result=$(wget --server-response --quiet http://127.0.0.1:4000/403 2>&1 | head -1 | awk '{print $2}')
|
||||||
|
echo "$result"
|
||||||
|
[[ "$result" -eq "403" ]]
|
||||||
|
|
||||||
|
# Verify 404 error code behavior
|
||||||
|
- name: Verify 404 error code from '/404' and on invalid pages
|
||||||
|
run: |
|
||||||
|
result=$(wget --server-response --quiet http://127.0.0.1:4000/404 2>&1 | head -1 | awk '{print $2}')
|
||||||
|
echo "$result"
|
||||||
|
result2=$(wget --server-response --quiet http://127.0.0.1:4000/invalidurl 2>&1 | head -1 | awk '{print $2}')
|
||||||
|
echo "$result2"
|
||||||
|
[[ "$result" -eq "404" && "$result2" -eq "404" ]]
|
||||||
|
|
||||||
|
# Verify 500 error code behavior
|
||||||
|
- name: Verify 500 error code from '/500'
|
||||||
|
run: |
|
||||||
|
result=$(wget --server-response --quiet http://127.0.0.1:4000/500 2>&1 | head -1 | awk '{print $2}')
|
||||||
|
echo "$result"
|
||||||
|
[[ "$result" -eq "500" ]]
|
||||||
|
|
||||||
- name: Stop running app
|
- name: Stop running app
|
||||||
run: kill -9 $(lsof -t -i:4000)
|
run: kill -9 $(lsof -t -i:4000)
|
||||||
|
|
||||||
|
@@ -23,10 +23,24 @@ ssr:
|
|||||||
# Determining which styles are critical is a relatively expensive operation; this option is
|
# Determining which styles are critical is a relatively expensive operation; this option is
|
||||||
# disabled (false) by default to boost server performance at the expense of loading smoothness.
|
# disabled (false) by default to boost server performance at the expense of loading smoothness.
|
||||||
inlineCriticalCss: false
|
inlineCriticalCss: false
|
||||||
# Path prefixes to enable SSR for. By default these are limited to paths of primary DSpace objects.
|
# Patterns to be run as regexes against the path of the page to check if SSR is allowed.
|
||||||
# NOTE: The "/handle/" path ensures Handle redirects work via SSR. The "/reload/" path ensures
|
# If the path match any of the regexes it will be served directly in CSR.
|
||||||
# hard refreshes (e.g. after login) trigger SSR while fully reloading the page.
|
# By default, excludes community and collection browse, global browse, global search, community list, statistics and various administrative tools.
|
||||||
paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/', '/reload/' ]
|
excludePathPatterns:
|
||||||
|
- pattern: "^/communities/[a-f0-9-]{36}/browse(/.*)?$"
|
||||||
|
flag: "i"
|
||||||
|
- pattern: "^/collections/[a-f0-9-]{36}/browse(/.*)?$"
|
||||||
|
flag: "i"
|
||||||
|
- pattern: "^/browse/"
|
||||||
|
- pattern: "^/search$"
|
||||||
|
- pattern: "^/community-list$"
|
||||||
|
- pattern: "^/admin/"
|
||||||
|
- pattern: "^/processes/?"
|
||||||
|
- pattern: "^/notifications/"
|
||||||
|
- pattern: "^/statistics/?"
|
||||||
|
- pattern: "^/access-control/"
|
||||||
|
- pattern: "^/health$"
|
||||||
|
|
||||||
# Whether to enable rendering of Search component on SSR.
|
# Whether to enable rendering of Search component on SSR.
|
||||||
# If set to true the component will be included in the HTML returned from the server side rendering.
|
# If set to true the component will be included in the HTML returned from the server side rendering.
|
||||||
# If set to false the component will not be included in the HTML returned from the server side rendering.
|
# If set to false the component will not be included in the HTML returned from the server side rendering.
|
||||||
|
34
package.json
34
package.json
@@ -66,8 +66,8 @@
|
|||||||
"@angular/platform-browser-dynamic": "^17.3.11",
|
"@angular/platform-browser-dynamic": "^17.3.11",
|
||||||
"@angular/platform-server": "^17.3.11",
|
"@angular/platform-server": "^17.3.11",
|
||||||
"@angular/router": "^17.3.11",
|
"@angular/router": "^17.3.11",
|
||||||
"@angular/ssr": "^17.3.11",
|
"@angular/ssr": "^17.3.17",
|
||||||
"@babel/runtime": "7.26.7",
|
"@babel/runtime": "7.27.6",
|
||||||
"@kolkov/ngx-gallery": "^2.0.1",
|
"@kolkov/ngx-gallery": "^2.0.1",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^11.0.0",
|
"@ng-bootstrap/ng-bootstrap": "^11.0.0",
|
||||||
"@ng-dynamic-forms/core": "^16.0.0",
|
"@ng-dynamic-forms/core": "^16.0.0",
|
||||||
@@ -78,14 +78,14 @@
|
|||||||
"@ngx-translate/core": "^14.0.0",
|
"@ngx-translate/core": "^14.0.0",
|
||||||
"@nicky-lenaers/ngx-scroll-to": "^14.0.0",
|
"@nicky-lenaers/ngx-scroll-to": "^14.0.0",
|
||||||
"angulartics2": "^12.2.0",
|
"angulartics2": "^12.2.0",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.10.0",
|
||||||
"bootstrap": "^4.6.1",
|
"bootstrap": "^4.6.1",
|
||||||
"cerialize": "0.1.18",
|
"cerialize": "0.1.18",
|
||||||
"cli-progress": "^3.12.0",
|
"cli-progress": "^3.12.0",
|
||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
"compression": "^1.7.5",
|
"compression": "^1.8.0",
|
||||||
"cookie-parser": "1.4.7",
|
"cookie-parser": "1.4.7",
|
||||||
"core-js": "^3.40.0",
|
"core-js": "^3.42.0",
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
"date-fns-tz": "^1.3.7",
|
"date-fns-tz": "^1.3.7",
|
||||||
"deepmerge": "^4.3.1",
|
"deepmerge": "^4.3.1",
|
||||||
@@ -94,9 +94,9 @@
|
|||||||
"express-rate-limit": "^5.1.3",
|
"express-rate-limit": "^5.1.3",
|
||||||
"fast-json-patch": "^3.1.1",
|
"fast-json-patch": "^3.1.1",
|
||||||
"filesize": "^6.1.0",
|
"filesize": "^6.1.0",
|
||||||
"http-proxy-middleware": "^2.0.7",
|
"http-proxy-middleware": "^2.0.9",
|
||||||
"http-terminator": "^3.2.0",
|
"http-terminator": "^3.2.0",
|
||||||
"isbot": "^5.1.22",
|
"isbot": "^5.1.28",
|
||||||
"js-cookie": "2.2.1",
|
"js-cookie": "2.2.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
@@ -119,20 +119,20 @@
|
|||||||
"nouislider": "^15.7.1",
|
"nouislider": "^15.7.1",
|
||||||
"pem": "1.14.8",
|
"pem": "1.14.8",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.0",
|
"rxjs": "^7.8.2",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"zone.js": "~0.15.0"
|
"zone.js": "~0.14.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-builders/custom-webpack": "~17.0.2",
|
"@angular-builders/custom-webpack": "~17.0.2",
|
||||||
"@angular-devkit/build-angular": "^17.3.11",
|
"@angular-devkit/build-angular": "^17.3.17",
|
||||||
"@angular-eslint/builder": "17.5.3",
|
"@angular-eslint/builder": "17.5.3",
|
||||||
"@angular-eslint/bundled-angular-compiler": "17.5.3",
|
"@angular-eslint/bundled-angular-compiler": "17.5.3",
|
||||||
"@angular-eslint/eslint-plugin": "17.5.3",
|
"@angular-eslint/eslint-plugin": "17.5.3",
|
||||||
"@angular-eslint/eslint-plugin-template": "17.5.3",
|
"@angular-eslint/eslint-plugin-template": "17.5.3",
|
||||||
"@angular-eslint/schematics": "17.5.3",
|
"@angular-eslint/schematics": "17.5.3",
|
||||||
"@angular-eslint/template-parser": "17.5.3",
|
"@angular-eslint/template-parser": "17.5.3",
|
||||||
"@angular/cli": "^17.3.11",
|
"@angular/cli": "^17.3.17",
|
||||||
"@angular/compiler-cli": "^17.3.11",
|
"@angular/compiler-cli": "^17.3.11",
|
||||||
"@angular/language-service": "^17.3.11",
|
"@angular/language-service": "^17.3.11",
|
||||||
"@cypress/schematic": "^1.5.0",
|
"@cypress/schematic": "^1.5.0",
|
||||||
@@ -147,13 +147,13 @@
|
|||||||
"@types/grecaptcha": "^3.0.9",
|
"@types/grecaptcha": "^3.0.9",
|
||||||
"@types/jasmine": "~3.6.0",
|
"@types/jasmine": "~3.6.0",
|
||||||
"@types/js-cookie": "2.2.6",
|
"@types/js-cookie": "2.2.6",
|
||||||
"@types/lodash": "^4.17.15",
|
"@types/lodash": "^4.17.17",
|
||||||
"@types/node": "^14.14.9",
|
"@types/node": "^14.14.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||||
"@typescript-eslint/parser": "^7.2.0",
|
"@typescript-eslint/parser": "^7.2.0",
|
||||||
"@typescript-eslint/rule-tester": "^7.2.0",
|
"@typescript-eslint/rule-tester": "^7.2.0",
|
||||||
"@typescript-eslint/utils": "^7.2.0",
|
"@typescript-eslint/utils": "^7.2.0",
|
||||||
"axe-core": "^4.10.2",
|
"axe-core": "^4.10.3",
|
||||||
"compression-webpack-plugin": "^9.2.0",
|
"compression-webpack-plugin": "^9.2.0",
|
||||||
"copy-webpack-plugin": "^6.4.1",
|
"copy-webpack-plugin": "^6.4.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-import-newlines": "^1.3.1",
|
"eslint-plugin-import-newlines": "^1.3.1",
|
||||||
"eslint-plugin-jsdoc": "^45.0.0",
|
"eslint-plugin-jsdoc": "^45.0.0",
|
||||||
"eslint-plugin-jsonc": "^2.19.1",
|
"eslint-plugin-jsonc": "^2.20.1",
|
||||||
"eslint-plugin-lodash": "^7.4.0",
|
"eslint-plugin-lodash": "^7.4.0",
|
||||||
"eslint-plugin-rxjs": "^5.0.3",
|
"eslint-plugin-rxjs": "^5.0.3",
|
||||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
@@ -183,7 +183,7 @@
|
|||||||
"karma-jasmine": "~4.0.0",
|
"karma-jasmine": "~4.0.0",
|
||||||
"karma-jasmine-html-reporter": "^1.5.0",
|
"karma-jasmine-html-reporter": "^1.5.0",
|
||||||
"karma-mocha-reporter": "2.2.5",
|
"karma-mocha-reporter": "2.2.5",
|
||||||
"ng-mocks": "^14.13.2",
|
"ng-mocks": "^14.13.5",
|
||||||
"ngx-mask": "14.2.4",
|
"ngx-mask": "14.2.4",
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^2.0.22",
|
||||||
"postcss": "^8.5",
|
"postcss": "^8.5",
|
||||||
@@ -195,12 +195,12 @@
|
|||||||
"react-copy-to-clipboard": "^5.1.0",
|
"react-copy-to-clipboard": "^5.1.0",
|
||||||
"react-dom": "^16.14.0",
|
"react-dom": "^16.14.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"sass": "~1.84.0",
|
"sass": "~1.89.2",
|
||||||
"sass-loader": "^12.6.0",
|
"sass-loader": "^12.6.0",
|
||||||
"sass-resources-loader": "^2.2.5",
|
"sass-resources-loader": "^2.2.5",
|
||||||
"ts-node": "^8.10.2",
|
"ts-node": "^8.10.2",
|
||||||
"typescript": "~5.4.5",
|
"typescript": "~5.4.5",
|
||||||
"webpack": "5.97.1",
|
"webpack": "5.99.9",
|
||||||
"webpack-cli": "^5.1.4",
|
"webpack-cli": "^5.1.4",
|
||||||
"webpack-dev-server": "^4.15.1"
|
"webpack-dev-server": "^4.15.1"
|
||||||
}
|
}
|
||||||
|
18
server.ts
18
server.ts
@@ -58,6 +58,7 @@ import {
|
|||||||
REQUEST,
|
REQUEST,
|
||||||
RESPONSE,
|
RESPONSE,
|
||||||
} from './src/express.tokens';
|
} from './src/express.tokens';
|
||||||
|
import { SsrExcludePatterns } from "./src/config/ssr-config.interface";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set path for the browser application's dist folder
|
* Set path for the browser application's dist folder
|
||||||
@@ -221,7 +222,7 @@ export function app() {
|
|||||||
* The callback function to serve server side angular
|
* The callback function to serve server side angular
|
||||||
*/
|
*/
|
||||||
function ngApp(req, res, next) {
|
function ngApp(req, res, next) {
|
||||||
if (environment.ssr.enabled && req.method === 'GET' && (req.path === '/' || environment.ssr.paths.some(pathPrefix => req.path.startsWith(pathPrefix)))) {
|
if (environment.ssr.enabled && req.method === 'GET' && (req.path === '/' || !isExcludedFromSsr(req.path, environment.ssr.excludePathPatterns))) {
|
||||||
// Render the page to user via SSR (server side rendering)
|
// Render the page to user via SSR (server side rendering)
|
||||||
serverSideRender(req, res, next);
|
serverSideRender(req, res, next);
|
||||||
} else {
|
} else {
|
||||||
@@ -627,6 +628,21 @@ function start() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if SSR should be skipped for path
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* @param excludePathPattern
|
||||||
|
*/
|
||||||
|
function isExcludedFromSsr(path: string, excludePathPattern: SsrExcludePatterns[]): boolean {
|
||||||
|
const patterns = excludePathPattern.map(p =>
|
||||||
|
new RegExp(p.pattern, p.flag || '')
|
||||||
|
);
|
||||||
|
return patterns.some((regex) => {
|
||||||
|
return regex.test(path)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The callback function to serve health check requests
|
* The callback function to serve health check requests
|
||||||
*/
|
*/
|
||||||
|
@@ -1,4 +1,7 @@
|
|||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import {
|
||||||
|
Component,
|
||||||
|
NO_ERRORS_SCHEMA,
|
||||||
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ComponentFixture,
|
ComponentFixture,
|
||||||
TestBed,
|
TestBed,
|
||||||
@@ -62,10 +65,17 @@ describe('BulkAccessComponent', () => {
|
|||||||
'file': { },
|
'file': { },
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockSettings: any = jasmine.createSpyObj('AccessControlFormContainerComponent', {
|
@Component({
|
||||||
getValue: jasmine.createSpy('getValue'),
|
selector: 'ds-bulk-access-settings',
|
||||||
reset: jasmine.createSpy('reset'),
|
template: '',
|
||||||
});
|
exportAs: 'dsBulkSettings',
|
||||||
|
standalone: true,
|
||||||
|
})
|
||||||
|
class MockBulkAccessSettingsComponent {
|
||||||
|
isFormValid = jasmine.createSpy('isFormValid').and.returnValue(false);
|
||||||
|
getValue = jasmine.createSpy('getValue');
|
||||||
|
reset = jasmine.createSpy('reset');
|
||||||
|
}
|
||||||
const selection: any[] = [{ indexableObject: { uuid: '1234' } }, { indexableObject: { uuid: '5678' } }];
|
const selection: any[] = [{ indexableObject: { uuid: '1234' } }, { indexableObject: { uuid: '5678' } }];
|
||||||
const selectableListState: SelectableListState = { id: 'test', selection };
|
const selectableListState: SelectableListState = { id: 'test', selection };
|
||||||
const expectedIdList = ['1234', '5678'];
|
const expectedIdList = ['1234', '5678'];
|
||||||
@@ -93,6 +103,9 @@ describe('BulkAccessComponent', () => {
|
|||||||
BulkAccessSettingsComponent,
|
BulkAccessSettingsComponent,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
add: {
|
||||||
|
imports: [MockBulkAccessSettingsComponent],
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
});
|
});
|
||||||
@@ -109,13 +122,12 @@ describe('BulkAccessComponent', () => {
|
|||||||
fixture.destroy();
|
fixture.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when there are no elements selected', () => {
|
describe('when there are no elements selected and step two form is invalid', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
||||||
(component as any).selectableListService.getSelectableList.and.returnValue(of(selectableListStateEmpty));
|
(component as any).selectableListService.getSelectableList.and.returnValue(of(selectableListStateEmpty));
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.settings = mockSettings;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
@@ -138,7 +150,6 @@ describe('BulkAccessComponent', () => {
|
|||||||
|
|
||||||
(component as any).selectableListService.getSelectableList.and.returnValue(of(selectableListState));
|
(component as any).selectableListService.getSelectableList.and.returnValue(of(selectableListState));
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.settings = mockSettings;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
@@ -149,15 +160,29 @@ describe('BulkAccessComponent', () => {
|
|||||||
expect(component.objectsSelected$.value).toEqual(expectedIdList);
|
expect(component.objectsSelected$.value).toEqual(expectedIdList);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should enable the execute button when there are objects selected', () => {
|
it('should not enable the execute button when there are objects selected and step two form is invalid', () => {
|
||||||
component.objectsSelected$.next(['1234']);
|
component.objectsSelected$.next(['1234']);
|
||||||
expect(component.canExport()).toBe(true);
|
expect(component.canExport()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call the settings reset method when reset is called', () => {
|
it('should call the settings reset method when reset is called', () => {
|
||||||
component.reset();
|
component.reset();
|
||||||
expect(component.settings.reset).toHaveBeenCalled();
|
expect(component.settings.reset).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
describe('when there are elements selected and the step two form is valid', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
|
||||||
|
(component as any).selectableListService.getSelectableList.and.returnValue(of(selectableListState));
|
||||||
|
fixture.detectChanges();
|
||||||
|
(component as any).settings.isFormValid.and.returnValue(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should enable the execute button when there are objects selected and step two form is valid', () => {
|
||||||
|
component.objectsSelected$.next(['1234']);
|
||||||
|
expect(component.canExport()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('should call the bulkAccessControlService executeScript method when submit is called', () => {
|
it('should call the bulkAccessControlService executeScript method when submit is called', () => {
|
||||||
(component.settings as any).getValue.and.returnValue(mockFormState);
|
(component.settings as any).getValue.and.returnValue(mockFormState);
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
OnInit,
|
OnInit,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
@@ -31,6 +32,7 @@ import { BulkAccessSettingsComponent } from './settings/bulk-access-settings.com
|
|||||||
BtnDisabledDirective,
|
BtnDisabledDirective,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class BulkAccessComponent implements OnInit {
|
export class BulkAccessComponent implements OnInit {
|
||||||
|
|
||||||
@@ -70,7 +72,7 @@ export class BulkAccessComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
canExport(): boolean {
|
canExport(): boolean {
|
||||||
return this.objectsSelected$.value?.length > 0;
|
return this.objectsSelected$.value?.length > 0 && this.settings?.isFormValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -43,4 +43,8 @@ export class BulkAccessSettingsComponent {
|
|||||||
this.controlForm.reset();
|
this.controlForm.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isFormValid() {
|
||||||
|
return this.controlForm.isValid();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
NgClass,
|
|
||||||
NgFor,
|
NgFor,
|
||||||
NgIf,
|
NgIf,
|
||||||
} from '@angular/common';
|
} from '@angular/common';
|
||||||
@@ -88,7 +87,6 @@ import { ValidateEmailNotTaken } from './validators/email-taken.validator';
|
|||||||
NgFor,
|
NgFor,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
NgClass,
|
|
||||||
ThemedLoadingComponent,
|
ThemedLoadingComponent,
|
||||||
PaginationComponent,
|
PaginationComponent,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<div *ngIf="registryService.getActiveMetadataField() | async; then editheader; else createHeader"></div>
|
<div *ngIf="activeMetadataField$ | async; then editheader; else createHeader"></div>
|
||||||
|
|
||||||
<ng-template #createHeader>
|
<ng-template #createHeader>
|
||||||
<h2>{{messagePrefix + '.create' | translate}}</h2>
|
<h2>{{messagePrefix + '.create' | translate}}</h2>
|
||||||
|
@@ -22,7 +22,7 @@ import {
|
|||||||
TranslateModule,
|
TranslateModule,
|
||||||
TranslateService,
|
TranslateService,
|
||||||
} from '@ngx-translate/core';
|
} from '@ngx-translate/core';
|
||||||
import { combineLatest } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { take } from 'rxjs/operators';
|
import { take } from 'rxjs/operators';
|
||||||
|
|
||||||
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
|
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
|
||||||
@@ -113,6 +113,8 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
@Output() submitForm: EventEmitter<any> = new EventEmitter();
|
@Output() submitForm: EventEmitter<any> = new EventEmitter();
|
||||||
|
|
||||||
|
activeMetadataField$: Observable<MetadataField>;
|
||||||
|
|
||||||
constructor(public registryService: RegistryService,
|
constructor(public registryService: RegistryService,
|
||||||
private formBuilderService: FormBuilderService,
|
private formBuilderService: FormBuilderService,
|
||||||
private translateService: TranslateService) {
|
private translateService: TranslateService) {
|
||||||
@@ -121,15 +123,11 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* Initialize the component, setting up the necessary Models for the dynamic form
|
* Initialize the component, setting up the necessary Models for the dynamic form
|
||||||
*/
|
*/
|
||||||
ngOnInit() {
|
ngOnInit(): void {
|
||||||
combineLatest([
|
this.activeMetadataField$ = this.registryService.getActiveMetadataField();
|
||||||
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({
|
this.element = new DynamicInputModel({
|
||||||
id: 'element',
|
id: 'element',
|
||||||
label: element,
|
label: this.translateService.instant(`${this.messagePrefix}.element`),
|
||||||
name: 'element',
|
name: 'element',
|
||||||
validators: {
|
validators: {
|
||||||
required: null,
|
required: null,
|
||||||
@@ -144,7 +142,7 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
this.qualifier = new DynamicInputModel({
|
this.qualifier = new DynamicInputModel({
|
||||||
id: 'qualifier',
|
id: 'qualifier',
|
||||||
label: qualifier,
|
label: this.translateService.instant(`${this.messagePrefix}.qualifier`),
|
||||||
name: 'qualifier',
|
name: 'qualifier',
|
||||||
validators: {
|
validators: {
|
||||||
pattern: '^[^. ,]*$',
|
pattern: '^[^. ,]*$',
|
||||||
@@ -158,7 +156,7 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
this.scopeNote = new DynamicTextAreaModel({
|
this.scopeNote = new DynamicTextAreaModel({
|
||||||
id: 'scopeNote',
|
id: 'scopeNote',
|
||||||
label: scopenote,
|
label: this.translateService.instant(`${this.messagePrefix}.scopenote`),
|
||||||
name: 'scopeNote',
|
name: 'scopeNote',
|
||||||
required: false,
|
required: false,
|
||||||
rows: 5,
|
rows: 5,
|
||||||
@@ -186,7 +184,6 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
|
|||||||
this.qualifier.disabled = true;
|
this.qualifier.disabled = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -11,8 +11,8 @@ import {
|
|||||||
REGISTRIES_MODULE_PATH,
|
REGISTRIES_MODULE_PATH,
|
||||||
REPORTS_MODULE_PATH,
|
REPORTS_MODULE_PATH,
|
||||||
} from './admin-routing-paths';
|
} from './admin-routing-paths';
|
||||||
import { AdminSearchPageComponent } from './admin-search-page/admin-search-page.component';
|
import { ThemedAdminSearchPageComponent } from './admin-search-page/themed-admin-search-page.component';
|
||||||
import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow-page.component';
|
import { ThemedAdminWorkflowPageComponent } from './admin-workflow-page/themed-admin-workflow-page.component';
|
||||||
|
|
||||||
export const ROUTES: Route[] = [
|
export const ROUTES: Route[] = [
|
||||||
{
|
{
|
||||||
@@ -28,13 +28,13 @@ export const ROUTES: Route[] = [
|
|||||||
{
|
{
|
||||||
path: 'search',
|
path: 'search',
|
||||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||||
component: AdminSearchPageComponent,
|
component: ThemedAdminSearchPageComponent,
|
||||||
data: { title: 'admin.search.title', breadcrumbKey: 'admin.search' },
|
data: { title: 'admin.search.title', breadcrumbKey: 'admin.search' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'workflow',
|
path: 'workflow',
|
||||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||||
component: AdminWorkflowPageComponent,
|
component: ThemedAdminWorkflowPageComponent,
|
||||||
data: { title: 'admin.workflow.title', breadcrumbKey: 'admin.workflow' },
|
data: { title: 'admin.workflow.title', breadcrumbKey: 'admin.workflow' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -4,7 +4,7 @@ import { Context } from '../../core/shared/context.model';
|
|||||||
import { ThemedConfigurationSearchPageComponent } from '../../search-page/themed-configuration-search-page.component';
|
import { ThemedConfigurationSearchPageComponent } from '../../search-page/themed-configuration-search-page.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-admin-search-page',
|
selector: 'ds-base-admin-search-page',
|
||||||
templateUrl: './admin-search-page.component.html',
|
templateUrl: './admin-search-page.component.html',
|
||||||
styleUrls: ['./admin-search-page.component.scss'],
|
styleUrls: ['./admin-search-page.component.scss'],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
|
52
src/app/admin/admin-search-page/admin-search.module.ts
Normal file
52
src/app/admin/admin-search-page/admin-search.module.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { JournalEntitiesModule } from '../../entity-groups/journal-entities/journal-entities.module';
|
||||||
|
import { ResearchEntitiesModule } from '../../entity-groups/research-entities/research-entities.module';
|
||||||
|
import { SearchModule } from '../../shared/search/search.module';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { AdminSearchPageComponent } from './admin-search-page.component';
|
||||||
|
import { CollectionAdminSearchResultGridElementComponent } from './admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component';
|
||||||
|
import { CommunityAdminSearchResultGridElementComponent } from './admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component';
|
||||||
|
import { ItemAdminSearchResultGridElementComponent } from './admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component';
|
||||||
|
import { CollectionAdminSearchResultListElementComponent } from './admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component';
|
||||||
|
import { CommunityAdminSearchResultListElementComponent } from './admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component';
|
||||||
|
import { ItemAdminSearchResultListElementComponent } from './admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component';
|
||||||
|
import { ItemAdminSearchResultActionsComponent } from './admin-search-results/item-admin-search-result-actions.component';
|
||||||
|
import { ThemedAdminSearchPageComponent } from './themed-admin-search-page.component';
|
||||||
|
|
||||||
|
const ENTRY_COMPONENTS = [
|
||||||
|
// put only entry components that use custom decorator
|
||||||
|
ItemAdminSearchResultListElementComponent,
|
||||||
|
CommunityAdminSearchResultListElementComponent,
|
||||||
|
CollectionAdminSearchResultListElementComponent,
|
||||||
|
ItemAdminSearchResultGridElementComponent,
|
||||||
|
CommunityAdminSearchResultGridElementComponent,
|
||||||
|
CollectionAdminSearchResultGridElementComponent,
|
||||||
|
ItemAdminSearchResultActionsComponent,
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
SearchModule,
|
||||||
|
SharedModule.withEntryComponents(),
|
||||||
|
JournalEntitiesModule.withEntryComponents(),
|
||||||
|
ResearchEntitiesModule.withEntryComponents(),
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ThemedAdminSearchPageComponent,
|
||||||
|
AdminSearchPageComponent,
|
||||||
|
...ENTRY_COMPONENTS,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AdminSearchModule {
|
||||||
|
/**
|
||||||
|
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
||||||
|
* which are not loaded during SSR otherwise
|
||||||
|
*/
|
||||||
|
static withEntryComponents() {
|
||||||
|
return {
|
||||||
|
ngModule: SharedModule,
|
||||||
|
providers: ENTRY_COMPONENTS.map((component) => ({ provide: component })),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,29 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
||||||
|
import { AdminSearchPageComponent } from './admin-search-page.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for {@link AdminSearchPageComponent}
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-admin-search-page',
|
||||||
|
templateUrl: '../../shared/theme-support/themed.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [AdminSearchPageComponent],
|
||||||
|
})
|
||||||
|
export class ThemedAdminSearchPageComponent extends ThemedComponent<AdminSearchPageComponent> {
|
||||||
|
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'AdminSearchPageComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../../themes/${themeName}/app/admin/admin-search-page/admin-search-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import('./admin-search-page.component');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -4,7 +4,7 @@ import { Context } from '../../core/shared/context.model';
|
|||||||
import { ThemedConfigurationSearchPageComponent } from '../../search-page/themed-configuration-search-page.component';
|
import { ThemedConfigurationSearchPageComponent } from '../../search-page/themed-configuration-search-page.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-admin-workflow-page',
|
selector: 'ds-base-admin-workflow-page',
|
||||||
templateUrl: './admin-workflow-page.component.html',
|
templateUrl: './admin-workflow-page.component.html',
|
||||||
styleUrls: ['./admin-workflow-page.component.scss'],
|
styleUrls: ['./admin-workflow-page.component.scss'],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
|
43
src/app/admin/admin-workflow-page/admin-workflow.module.ts
Normal file
43
src/app/admin/admin-workflow-page/admin-workflow.module.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { SearchModule } from '../../shared/search/search.module';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { AdminWorkflowPageComponent } from './admin-workflow-page.component';
|
||||||
|
import { WorkflowItemSearchResultAdminWorkflowGridElementComponent } from './admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component';
|
||||||
|
import { WorkflowItemSearchResultAdminWorkflowListElementComponent } from './admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component';
|
||||||
|
import { WorkflowItemAdminWorkflowActionsComponent } from './admin-workflow-search-results/workflow-item-admin-workflow-actions.component';
|
||||||
|
import { ThemedAdminWorkflowPageComponent } from './themed-admin-workflow-page.component';
|
||||||
|
|
||||||
|
const ENTRY_COMPONENTS = [
|
||||||
|
// put only entry components that use custom decorator
|
||||||
|
WorkflowItemSearchResultAdminWorkflowListElementComponent,
|
||||||
|
WorkflowItemSearchResultAdminWorkflowGridElementComponent,
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
SearchModule,
|
||||||
|
SharedModule.withEntryComponents(),
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ThemedAdminWorkflowPageComponent,
|
||||||
|
AdminWorkflowPageComponent,
|
||||||
|
WorkflowItemAdminWorkflowActionsComponent,
|
||||||
|
...ENTRY_COMPONENTS,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
AdminWorkflowPageComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AdminWorkflowModuleModule {
|
||||||
|
/**
|
||||||
|
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
||||||
|
* which are not loaded during SSR otherwise
|
||||||
|
*/
|
||||||
|
static withEntryComponents() {
|
||||||
|
return {
|
||||||
|
ngModule: SharedModule,
|
||||||
|
providers: ENTRY_COMPONENTS.map((component) => ({ provide: component })),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,29 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
||||||
|
import { AdminWorkflowPageComponent } from './admin-workflow-page.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for {@link AdminWorkflowPageComponent}
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-admin-workflow-page',
|
||||||
|
templateUrl: '../../shared/theme-support/themed.component.html',
|
||||||
|
standalone: true,
|
||||||
|
imports: [AdminWorkflowPageComponent],
|
||||||
|
})
|
||||||
|
export class ThemedAdminWorkflowPageComponent extends ThemedComponent<AdminWorkflowPageComponent> {
|
||||||
|
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'AdminWorkflowPageComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../../themes/${themeName}/app/admin/admin-workflow-page/admin-workflow-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import('./admin-workflow-page.component');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -33,6 +33,7 @@ import { reloadGuard } from './core/reload/reload.guard';
|
|||||||
import { forgotPasswordCheckGuard } from './core/rest-property/forgot-password-check-guard.guard';
|
import { forgotPasswordCheckGuard } from './core/rest-property/forgot-password-check-guard.guard';
|
||||||
import { ServerCheckGuard } from './core/server-check/server-check.guard';
|
import { ServerCheckGuard } from './core/server-check/server-check.guard';
|
||||||
import { ThemedForbiddenComponent } from './forbidden/themed-forbidden.component';
|
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 { ITEM_MODULE_PATH } from './item-page/item-page-routing-paths';
|
||||||
import { menuResolver } from './menuResolver';
|
import { menuResolver } from './menuResolver';
|
||||||
import { provideSuggestionNotificationsState } from './notifications/provide-suggestion-notifications-state';
|
import { provideSuggestionNotificationsState } from './notifications/provide-suggestion-notifications-state';
|
||||||
@@ -40,6 +41,7 @@ import { ThemedPageErrorComponent } from './page-error/themed-page-error.compone
|
|||||||
import { ThemedPageInternalServerErrorComponent } from './page-internal-server-error/themed-page-internal-server-error.component';
|
import { ThemedPageInternalServerErrorComponent } from './page-internal-server-error/themed-page-internal-server-error.component';
|
||||||
import { ThemedPageNotFoundComponent } from './pagenotfound/themed-pagenotfound.component';
|
import { ThemedPageNotFoundComponent } from './pagenotfound/themed-pagenotfound.component';
|
||||||
import { PROCESS_MODULE_PATH } from './process-page/process-page-routing.paths';
|
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 { provideSubmissionState } from './submission/provide-submission-state';
|
||||||
import { SUGGESTION_MODULE_PATH } from './suggestions-page/suggestions-page-routing-paths';
|
import { SUGGESTION_MODULE_PATH } from './suggestions-page/suggestions-page-routing-paths';
|
||||||
|
|
||||||
@@ -63,9 +65,16 @@ export const APP_ROUTES: Route[] = [
|
|||||||
path: 'home',
|
path: 'home',
|
||||||
loadChildren: () => import('./home-page/home-page-routes')
|
loadChildren: () => import('./home-page/home-page-routes')
|
||||||
.then((m) => m.ROUTES),
|
.then((m) => m.ROUTES),
|
||||||
data: { showBreadcrumbs: false },
|
data: {
|
||||||
|
showBreadcrumbs: false,
|
||||||
|
dsoPath: 'site',
|
||||||
|
},
|
||||||
providers: [provideSuggestionNotificationsState()],
|
providers: [provideSuggestionNotificationsState()],
|
||||||
canActivate: [endUserAgreementCurrentUserGuard],
|
canActivate: [endUserAgreementCurrentUserGuard],
|
||||||
|
resolve: {
|
||||||
|
site: homePageResolver,
|
||||||
|
tracking: viewTrackerResolver,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'community-list',
|
path: 'community-list',
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<ng-template #breadcrumb let-text="text" let-url="url">
|
<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>
|
||||||
|
|
||||||
<ng-template #activeBreadcrumb let-text="text">
|
<ng-template #activeBreadcrumb let-text="text">
|
||||||
|
@@ -18,6 +18,8 @@
|
|||||||
<a class="btn btn-primary"
|
<a class="btn btn-primary"
|
||||||
[routerLink]="['/search']"
|
[routerLink]="['/search']"
|
||||||
[queryParams]="queryParams"
|
[queryParams]="queryParams"
|
||||||
[queryParamsHandling]="'merge'">
|
[queryParamsHandling]="'merge'"
|
||||||
|
role="link"
|
||||||
|
tabindex="0">
|
||||||
{{ 'browse.taxonomy.button' | translate }}</a>
|
{{ 'browse.taxonomy.button' | translate }}</a>
|
||||||
</section>
|
</section>
|
||||||
|
@@ -11,6 +11,7 @@ import { ComcolSearchSectionComponent } from '../shared/comcol/sections/comcol-s
|
|||||||
import { dsoEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
import { dsoEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||||
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
||||||
|
import { viewTrackerResolver } from '../statistics/angulartics/dspace/view-tracker.resolver';
|
||||||
import { collectionPageResolver } from './collection-page.resolver';
|
import { collectionPageResolver } from './collection-page.resolver';
|
||||||
import { collectionPageAdministratorGuard } from './collection-page-administrator.guard';
|
import { collectionPageAdministratorGuard } from './collection-page-administrator.guard';
|
||||||
import {
|
import {
|
||||||
@@ -84,6 +85,7 @@ export const ROUTES: Route[] = [
|
|||||||
component: ThemedCollectionPageComponent,
|
component: ThemedCollectionPageComponent,
|
||||||
resolve: {
|
resolve: {
|
||||||
menu: dsoEditMenuResolver,
|
menu: dsoEditMenuResolver,
|
||||||
|
tracking: viewTrackerResolver,
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
*ngVar="(collectionRD$ | async) as collectionRD">
|
*ngVar="(collectionRD$ | async) as collectionRD">
|
||||||
<div *ngIf="collectionRD?.hasSucceeded" @fadeInOut>
|
<div *ngIf="collectionRD?.hasSucceeded" @fadeInOut>
|
||||||
<div *ngIf="collectionRD?.payload as collection">
|
<div *ngIf="collectionRD?.payload as collection">
|
||||||
<ds-view-tracker [object]="collection"></ds-view-tracker>
|
|
||||||
<div class="d-flex flex-row border-bottom mb-4 pb-4">
|
<div class="d-flex flex-row border-bottom mb-4 pb-4">
|
||||||
<header class="comcol-header mr-auto">
|
<header class="comcol-header mr-auto">
|
||||||
<!-- Collection Name -->
|
<!-- Collection Name -->
|
||||||
|
@@ -50,7 +50,6 @@ import { ThemedLoadingComponent } from '../shared/loading/themed-loading.compone
|
|||||||
import { ObjectCollectionComponent } from '../shared/object-collection/object-collection.component';
|
import { ObjectCollectionComponent } from '../shared/object-collection/object-collection.component';
|
||||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||||
import { VarDirective } from '../shared/utils/var.directive';
|
import { VarDirective } from '../shared/utils/var.directive';
|
||||||
import { ViewTrackerComponent } from '../statistics/angulartics/dspace/view-tracker.component';
|
|
||||||
import { getCollectionPageRoute } from './collection-page-routing-paths';
|
import { getCollectionPageRoute } from './collection-page-routing-paths';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -68,7 +67,6 @@ import { getCollectionPageRoute } from './collection-page-routing-paths';
|
|||||||
NgIf,
|
NgIf,
|
||||||
ThemedLoadingComponent,
|
ThemedLoadingComponent,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
ViewTrackerComponent,
|
|
||||||
VarDirective,
|
VarDirective,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
ComcolPageHeaderComponent,
|
ComcolPageHeaderComponent,
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<div class="align-middle my-auto">
|
<div class="align-middle my-auto">
|
||||||
<button *ngIf="(dataSource.loading$ | async) !== true" (click)="getNextPage(node)"
|
<button *ngIf="(dataSource.loading$ | async) !== true" (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 }}
|
<i class="fas fa-angle-down"></i> {{ 'communityList.showMore' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<ds-loading *ngIf="node===loadingNode && dataSource.loading$ | async" class="ds-themed-loading"></ds-loading>
|
<ds-loading *ngIf="node===loadingNode && dataSource.loading$ | async" class="ds-themed-loading"></ds-loading>
|
||||||
@@ -27,7 +27,12 @@
|
|||||||
<button *ngIf="hasChild(null, node) | async" type="button" class="btn btn-default" cdkTreeNodeToggle
|
<button *ngIf="hasChild(null, node) | async" type="button" class="btn btn-default" cdkTreeNodeToggle
|
||||||
[attr.aria-label]="(node.isExpanded ? 'communityList.collapse' : 'communityList.expand') | translate:{ name: dsoNameService.getName(node.payload) }"
|
[attr.aria-label]="(node.isExpanded ? 'communityList.collapse' : 'communityList.expand') | translate:{ name: dsoNameService.getName(node.payload) }"
|
||||||
(click)="toggleExpanded(node)"
|
(click)="toggleExpanded(node)"
|
||||||
data-test="expand-button">
|
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'}}"
|
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
||||||
aria-hidden="true"></span>
|
aria-hidden="true"></span>
|
||||||
<span class="sr-only">{{ (node.isExpanded ? 'communityList.collapse' : 'communityList.expand') | translate:{ name: dsoNameService.getName(node.payload) } }}</span>
|
<span class="sr-only">{{ (node.isExpanded ? 'communityList.collapse' : 'communityList.expand') | translate:{ name: dsoNameService.getName(node.payload) } }}</span>
|
||||||
@@ -38,7 +43,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<div class="d-flex flex-row">
|
<div class="d-flex flex-row">
|
||||||
<span class="d-flex align-middle my-auto">
|
<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="pr-2"> </span>
|
<span class="pr-2"> </span>
|
||||||
<span *ngIf="node.payload.archivedItemsCount >= 0" class="badge badge-pill badge-secondary align-top archived-items-lead my-auto">{{node.payload.archivedItemsCount}}</span>
|
<span *ngIf="node.payload.archivedItemsCount >= 0" class="badge badge-pill badge-secondary align-top archived-items-lead my-auto">{{node.payload.archivedItemsCount}}</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -72,7 +77,7 @@
|
|||||||
<span class="fa fa-chevron-right"></span>
|
<span class="fa fa-chevron-right"></span>
|
||||||
</span>
|
</span>
|
||||||
<h6 class="align-middle my-auto">
|
<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>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
<ds-truncatable [id]="node.id">
|
<ds-truncatable [id]="node.id">
|
||||||
|
@@ -10,6 +10,7 @@ import { ComcolSearchSectionComponent } from '../shared/comcol/sections/comcol-s
|
|||||||
import { dsoEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
import { dsoEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||||
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
||||||
|
import { viewTrackerResolver } from '../statistics/angulartics/dspace/view-tracker.resolver';
|
||||||
import { communityPageResolver } from './community-page.resolver';
|
import { communityPageResolver } from './community-page.resolver';
|
||||||
import { communityPageAdministratorGuard } from './community-page-administrator.guard';
|
import { communityPageAdministratorGuard } from './community-page-administrator.guard';
|
||||||
import {
|
import {
|
||||||
@@ -71,6 +72,7 @@ export const ROUTES: Route[] = [
|
|||||||
component: ThemedCommunityPageComponent,
|
component: ThemedCommunityPageComponent,
|
||||||
resolve: {
|
resolve: {
|
||||||
menu: dsoEditMenuResolver,
|
menu: dsoEditMenuResolver,
|
||||||
|
tracking: viewTrackerResolver,
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
<div class="container" *ngVar="(communityRD$ | async) as communityRD">
|
<div class="container" *ngVar="(communityRD$ | async) as communityRD">
|
||||||
<div class="community-page" *ngIf="communityRD?.hasSucceeded" @fadeInOut>
|
<div class="community-page" *ngIf="communityRD?.hasSucceeded" @fadeInOut>
|
||||||
<div *ngIf="communityRD?.payload; let communityPayload">
|
<div *ngIf="communityRD?.payload; let communityPayload">
|
||||||
<ds-view-tracker [object]="communityPayload"></ds-view-tracker>
|
|
||||||
<div class="d-flex flex-row border-bottom mb-4 pb-4">
|
<div class="d-flex flex-row border-bottom mb-4 pb-4">
|
||||||
<header class="comcol-header mr-auto">
|
<header class="comcol-header mr-auto">
|
||||||
<!-- Community name -->
|
<!-- Community name -->
|
||||||
|
@@ -41,7 +41,6 @@ import { hasValue } from '../shared/empty.util';
|
|||||||
import { ErrorComponent } from '../shared/error/error.component';
|
import { ErrorComponent } from '../shared/error/error.component';
|
||||||
import { ThemedLoadingComponent } from '../shared/loading/themed-loading.component';
|
import { ThemedLoadingComponent } from '../shared/loading/themed-loading.component';
|
||||||
import { VarDirective } from '../shared/utils/var.directive';
|
import { VarDirective } from '../shared/utils/var.directive';
|
||||||
import { ViewTrackerComponent } from '../statistics/angulartics/dspace/view-tracker.component';
|
|
||||||
import { getCommunityPageRoute } from './community-page-routing-paths';
|
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 { 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';
|
import { ThemedCommunityPageSubCommunityListComponent } from './sections/sub-com-col-section/sub-community-list/themed-community-page-sub-community-list.component';
|
||||||
@@ -66,7 +65,6 @@ import { ThemedCommunityPageSubCommunityListComponent } from './sections/sub-com
|
|||||||
ComcolPageLogoComponent,
|
ComcolPageLogoComponent,
|
||||||
ComcolPageHeaderComponent,
|
ComcolPageHeaderComponent,
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
ViewTrackerComponent,
|
|
||||||
VarDirective,
|
VarDirective,
|
||||||
RouterOutlet,
|
RouterOutlet,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
|
@@ -288,7 +288,7 @@ describe('AuthService test', () => {
|
|||||||
(state as any).core = Object.create({});
|
(state as any).core = Object.create({});
|
||||||
(state as any).core.auth = authenticatedState;
|
(state as any).core.auth = authenticatedState;
|
||||||
});
|
});
|
||||||
authService = new AuthService({}, window, undefined, authReqService, mockEpersonDataService, router, routeService, cookieService, store, hardRedirectService, notificationsService, translateService);
|
authService = new AuthService(window, authReqService, mockEpersonDataService, router, routeService, cookieService, store, hardRedirectService, notificationsService, translateService);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should return true when user is logged in', () => {
|
it('should return true when user is logged in', () => {
|
||||||
@@ -373,7 +373,7 @@ describe('AuthService test', () => {
|
|||||||
(state as any).core = Object.create({});
|
(state as any).core = Object.create({});
|
||||||
(state as any).core.auth = authenticatedState;
|
(state as any).core.auth = authenticatedState;
|
||||||
});
|
});
|
||||||
authService = new AuthService({}, window, undefined, authReqService, mockEpersonDataService, router, routeService, cookieService, store, hardRedirectService, notificationsService, translateService);
|
authService = new AuthService(window, authReqService, mockEpersonDataService, router, routeService, cookieService, store, hardRedirectService, notificationsService, translateService);
|
||||||
storage = (authService as any).storage;
|
storage = (authService as any).storage;
|
||||||
routeServiceMock = TestBed.inject(RouteService);
|
routeServiceMock = TestBed.inject(RouteService);
|
||||||
routerStub = TestBed.inject(Router);
|
routerStub = TestBed.inject(Router);
|
||||||
@@ -593,7 +593,7 @@ describe('AuthService test', () => {
|
|||||||
(state as any).core = Object.create({});
|
(state as any).core = Object.create({});
|
||||||
(state as any).core.auth = unAuthenticatedState;
|
(state as any).core.auth = unAuthenticatedState;
|
||||||
});
|
});
|
||||||
authService = new AuthService({}, window, undefined, authReqService, mockEpersonDataService, router, routeService, cookieService, store, hardRedirectService, notificationsService, translateService);
|
authService = new AuthService(window, authReqService, mockEpersonDataService, router, routeService, cookieService, store, hardRedirectService, notificationsService, translateService);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should return null for the shortlived token', () => {
|
it('should return null for the shortlived token', () => {
|
||||||
@@ -633,7 +633,7 @@ describe('AuthService test', () => {
|
|||||||
(state as any).core = Object.create({});
|
(state as any).core = Object.create({});
|
||||||
(state as any).core.auth = idleState;
|
(state as any).core.auth = idleState;
|
||||||
});
|
});
|
||||||
authService = new AuthService({}, window, undefined, authReqService, mockEpersonDataService, router, routeService, cookieService, store, hardRedirectService, notificationsService, translateService);
|
authService = new AuthService(window, authReqService, mockEpersonDataService, router, routeService, cookieService, store, hardRedirectService, notificationsService, translateService);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('isUserIdle should return true when user is not idle', () => {
|
it('isUserIdle should return true when user is not idle', () => {
|
||||||
|
@@ -2,7 +2,6 @@ import { HttpHeaders } from '@angular/common/http';
|
|||||||
import {
|
import {
|
||||||
Inject,
|
Inject,
|
||||||
Injectable,
|
Injectable,
|
||||||
Optional,
|
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import {
|
import {
|
||||||
@@ -24,10 +23,6 @@ import {
|
|||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
|
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import {
|
|
||||||
REQUEST,
|
|
||||||
RESPONSE,
|
|
||||||
} from '../../../express.tokens';
|
|
||||||
import { AppState } from '../../app.reducer';
|
import { AppState } from '../../app.reducer';
|
||||||
import {
|
import {
|
||||||
hasNoValue,
|
hasNoValue,
|
||||||
@@ -112,9 +107,8 @@ export class AuthService {
|
|||||||
*/
|
*/
|
||||||
private tokenRefreshTimer;
|
private tokenRefreshTimer;
|
||||||
|
|
||||||
constructor(@Inject(REQUEST) protected req: any,
|
constructor(
|
||||||
@Inject(NativeWindowService) protected _window: NativeWindowRef,
|
@Inject(NativeWindowService) protected _window: NativeWindowRef,
|
||||||
@Optional() @Inject(RESPONSE) private response: any,
|
|
||||||
protected authRequestService: AuthRequestService,
|
protected authRequestService: AuthRequestService,
|
||||||
protected epersonService: EPersonDataService,
|
protected epersonService: EPersonDataService,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
@@ -122,8 +116,8 @@ export class AuthService {
|
|||||||
protected storage: CookieService,
|
protected storage: CookieService,
|
||||||
protected store: Store<AppState>,
|
protected store: Store<AppState>,
|
||||||
protected hardRedirectService: HardRedirectService,
|
protected hardRedirectService: HardRedirectService,
|
||||||
private notificationService: NotificationsService,
|
protected notificationService: NotificationsService,
|
||||||
private translateService: TranslateService,
|
protected translateService: TranslateService,
|
||||||
) {
|
) {
|
||||||
this.store.pipe(
|
this.store.pipe(
|
||||||
// when this service is constructed the store is not fully initialized yet
|
// when this service is constructed the store is not fully initialized yet
|
||||||
@@ -522,10 +516,6 @@ export class AuthService {
|
|||||||
if (this._window.nativeWindow.location) {
|
if (this._window.nativeWindow.location) {
|
||||||
// Hard redirect to login page, so that all state is definitely lost
|
// Hard redirect to login page, so that all state is definitely lost
|
||||||
this._window.nativeWindow.location.href = redirectUrl;
|
this._window.nativeWindow.location.href = redirectUrl;
|
||||||
} else if (this.response) {
|
|
||||||
if (!this.response._headerSent) {
|
|
||||||
this.response.redirect(302, redirectUrl);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.router.navigateByUrl(redirectUrl);
|
this.router.navigateByUrl(redirectUrl);
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,40 @@
|
|||||||
import { HttpHeaders } from '@angular/common/http';
|
import { HttpHeaders } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import {
|
||||||
|
Inject,
|
||||||
|
Injectable,
|
||||||
|
Optional,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import {
|
||||||
|
REQUEST,
|
||||||
|
RESPONSE,
|
||||||
|
} from '../../../express.tokens';
|
||||||
|
import { AppState } from '../../app.reducer';
|
||||||
import {
|
import {
|
||||||
hasValue,
|
hasValue,
|
||||||
isNotEmpty,
|
isNotEmpty,
|
||||||
} from '../../shared/empty.util';
|
} from '../../shared/empty.util';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { RemoteData } from '../data/remote-data';
|
import { RemoteData } from '../data/remote-data';
|
||||||
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
|
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
|
||||||
import { AuthService } from './auth.service';
|
import { EPersonDataService } from '../eperson/eperson-data.service';
|
||||||
|
import { CookieService } from '../services/cookie.service';
|
||||||
|
import { HardRedirectService } from '../services/hard-redirect.service';
|
||||||
|
import { RouteService } from '../services/route.service';
|
||||||
|
import {
|
||||||
|
NativeWindowRef,
|
||||||
|
NativeWindowService,
|
||||||
|
} from '../services/window.service';
|
||||||
|
import {
|
||||||
|
AuthService,
|
||||||
|
LOGIN_ROUTE,
|
||||||
|
} from './auth.service';
|
||||||
|
import { AuthRequestService } from './auth-request.service';
|
||||||
import { AuthStatus } from './models/auth-status.model';
|
import { AuthStatus } from './models/auth-status.model';
|
||||||
import { AuthTokenInfo } from './models/auth-token-info.model';
|
import { AuthTokenInfo } from './models/auth-token-info.model';
|
||||||
|
|
||||||
@@ -19,6 +44,34 @@ import { AuthTokenInfo } from './models/auth-token-info.model';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class ServerAuthService extends AuthService {
|
export class ServerAuthService extends AuthService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(REQUEST) protected req: any,
|
||||||
|
@Optional() @Inject(RESPONSE) private response: any,
|
||||||
|
@Inject(NativeWindowService) protected _window: NativeWindowRef,
|
||||||
|
protected authRequestService: AuthRequestService,
|
||||||
|
protected epersonService: EPersonDataService,
|
||||||
|
protected router: Router,
|
||||||
|
protected routeService: RouteService,
|
||||||
|
protected storage: CookieService,
|
||||||
|
protected store: Store<AppState>,
|
||||||
|
protected hardRedirectService: HardRedirectService,
|
||||||
|
protected notificationService: NotificationsService,
|
||||||
|
protected translateService: TranslateService,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
_window,
|
||||||
|
authRequestService,
|
||||||
|
epersonService,
|
||||||
|
router,
|
||||||
|
routeService,
|
||||||
|
storage,
|
||||||
|
store,
|
||||||
|
hardRedirectService,
|
||||||
|
notificationService,
|
||||||
|
translateService,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the authenticated user
|
* Returns the authenticated user
|
||||||
* @returns {User}
|
* @returns {User}
|
||||||
@@ -62,4 +115,18 @@ export class ServerAuthService extends AuthService {
|
|||||||
map((rd: RemoteData<AuthStatus>) => Object.assign(new AuthStatus(), rd.payload)),
|
map((rd: RemoteData<AuthStatus>) => Object.assign(new AuthStatus(), rd.payload)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override redirectToLoginWhenTokenExpired() {
|
||||||
|
const redirectUrl = LOGIN_ROUTE + '?expired=true';
|
||||||
|
if (this._window.nativeWindow.location) {
|
||||||
|
// Hard redirect to login page, so that all state is definitely lost
|
||||||
|
this._window.nativeWindow.location.href = redirectUrl;
|
||||||
|
} else if (this.response) {
|
||||||
|
if (!this.response._headerSent) {
|
||||||
|
this.response.redirect(302, redirectUrl);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.router.navigateByUrl(redirectUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -37,6 +37,8 @@ import {
|
|||||||
} from './request.models';
|
} from './request.models';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import objectContaining = jasmine.objectContaining;
|
import objectContaining = jasmine.objectContaining;
|
||||||
|
import { RestResponse } from '../cache/response.models';
|
||||||
|
import { RequestEntry } from './request-entry.model';
|
||||||
|
|
||||||
describe('BitstreamDataService', () => {
|
describe('BitstreamDataService', () => {
|
||||||
let service: BitstreamDataService;
|
let service: BitstreamDataService;
|
||||||
@@ -47,6 +49,7 @@ describe('BitstreamDataService', () => {
|
|||||||
let rdbService: RemoteDataBuildService;
|
let rdbService: RemoteDataBuildService;
|
||||||
let bundleDataService: BundleDataService;
|
let bundleDataService: BundleDataService;
|
||||||
const bitstreamFormatHref = 'rest-api/bitstreamformats';
|
const bitstreamFormatHref = 'rest-api/bitstreamformats';
|
||||||
|
let responseCacheEntry: RequestEntry;
|
||||||
|
|
||||||
const bitstream1 = Object.assign(new Bitstream(), {
|
const bitstream1 = Object.assign(new Bitstream(), {
|
||||||
id: 'fake-bitstream1',
|
id: 'fake-bitstream1',
|
||||||
@@ -71,8 +74,13 @@ describe('BitstreamDataService', () => {
|
|||||||
const url = 'fake-bitstream-url';
|
const url = 'fake-bitstream-url';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
responseCacheEntry = new RequestEntry();
|
||||||
|
responseCacheEntry.request = { href: 'https://rest.api/' } as any;
|
||||||
|
responseCacheEntry.response = new RestResponse(true, 200, 'Success');
|
||||||
|
|
||||||
objectCache = jasmine.createSpyObj('objectCache', {
|
objectCache = jasmine.createSpyObj('objectCache', {
|
||||||
remove: jasmine.createSpy('remove'),
|
remove: jasmine.createSpy('remove'),
|
||||||
|
getByHref: observableOf(responseCacheEntry),
|
||||||
});
|
});
|
||||||
requestService = getMockRequestService();
|
requestService = getMockRequestService();
|
||||||
halService = Object.assign(new HALEndpointServiceStub(url));
|
halService = Object.assign(new HALEndpointServiceStub(url));
|
||||||
|
@@ -163,12 +163,25 @@ export class BitstreamDataService extends IdentifiableDataService<Bitstream> imp
|
|||||||
sendRequest(this.requestService),
|
sendRequest(this.requestService),
|
||||||
take(1),
|
take(1),
|
||||||
).subscribe(() => {
|
).subscribe(() => {
|
||||||
this.requestService.removeByHrefSubstring(bitstream.self + '/format');
|
this.deleteFormatCache(bitstream);
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.rdbService.buildFromRequestUUID(requestId);
|
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
|
* 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
|
* optional sequenceId or filename, with a list of {@link FollowLinkConfig}, to automatically
|
||||||
|
@@ -4,5 +4,6 @@
|
|||||||
export interface SignpostingLink {
|
export interface SignpostingLink {
|
||||||
href?: string,
|
href?: string,
|
||||||
rel?: string,
|
rel?: string,
|
||||||
type?: string
|
type?: string,
|
||||||
|
profile?: string
|
||||||
}
|
}
|
||||||
|
@@ -31,6 +31,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
EPersonMock,
|
EPersonMock,
|
||||||
EPersonMock2,
|
EPersonMock2,
|
||||||
|
EPersonMockWithNoName,
|
||||||
} from '../../shared/testing/eperson.mock';
|
} from '../../shared/testing/eperson.mock';
|
||||||
import { GroupMock } from '../../shared/testing/group-mock';
|
import { GroupMock } from '../../shared/testing/group-mock';
|
||||||
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
||||||
@@ -280,6 +281,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', () => {
|
describe('clearEPersonRequests', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(halService, 'getEndpoint').and.callFake((linkPath: string) => {
|
spyOn(halService, 'getEndpoint').and.callFake((linkPath: string) => {
|
||||||
|
@@ -269,7 +269,8 @@ export class EPersonDataService extends IdentifiableDataService<EPerson> impleme
|
|||||||
* @param newEPerson
|
* @param newEPerson
|
||||||
*/
|
*/
|
||||||
private generateOperations(oldEPerson: EPerson, newEPerson: EPerson): Operation[] {
|
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) {
|
if (hasValue(oldEPerson.email) && oldEPerson.email !== newEPerson.email) {
|
||||||
operations = [...operations, {
|
operations = [...operations, {
|
||||||
op: 'replace', path: '/email', value: newEPerson.email,
|
op: 'replace', path: '/email', value: newEPerson.email,
|
||||||
|
@@ -186,6 +186,7 @@ export class HeadTagService {
|
|||||||
this.setCitationKeywordsTag();
|
this.setCitationKeywordsTag();
|
||||||
|
|
||||||
this.setCitationAbstractUrlTag();
|
this.setCitationAbstractUrlTag();
|
||||||
|
this.setCitationDoiTag();
|
||||||
this.setCitationPdfUrlTag();
|
this.setCitationPdfUrlTag();
|
||||||
this.setCitationPublisherTag();
|
this.setCitationPublisherTag();
|
||||||
|
|
||||||
@@ -198,7 +199,6 @@ export class HeadTagService {
|
|||||||
// this.setCitationIssueTag();
|
// this.setCitationIssueTag();
|
||||||
// this.setCitationFirstPageTag();
|
// this.setCitationFirstPageTag();
|
||||||
// this.setCitationLastPageTag();
|
// this.setCitationLastPageTag();
|
||||||
// this.setCitationDOITag();
|
|
||||||
// this.setCitationPMIDTag();
|
// this.setCitationPMIDTag();
|
||||||
|
|
||||||
// this.setCitationFullTextTag();
|
// this.setCitationFullTextTag();
|
||||||
@@ -319,6 +319,18 @@ export class HeadTagService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add <meta name="citation_doi" ... > to the <head>
|
||||||
|
*/
|
||||||
|
protected setCitationDoiTag(): void {
|
||||||
|
if (this.currentObject.value instanceof Item) {
|
||||||
|
const doi = this.getMetaTagValue('dc.identifier.doi');
|
||||||
|
if (hasValue(doi)) {
|
||||||
|
this.addMetaTag('citation_doi', doi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add <meta name="citation_pdf_url" ... > to the <head>
|
* Add <meta name="citation_pdf_url" ... > to the <head>
|
||||||
*/
|
*/
|
||||||
|
@@ -27,7 +27,7 @@ export class MetadataService {
|
|||||||
* Returns undefined otherwise.
|
* Returns undefined otherwise.
|
||||||
*/
|
*/
|
||||||
public virtualValue(metadataValue: MetadataValue | undefined): string {
|
public virtualValue(metadataValue: MetadataValue | undefined): string {
|
||||||
if (this.isVirtual) {
|
if (this.isVirtual(metadataValue)) {
|
||||||
return metadataValue.authority.substring(metadataValue.authority.indexOf(VIRTUAL_METADATA_PREFIX) + VIRTUAL_METADATA_PREFIX.length);
|
return metadataValue.authority.substring(metadataValue.authority.indexOf(VIRTUAL_METADATA_PREFIX) + VIRTUAL_METADATA_PREFIX.length);
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@@ -1,15 +1,10 @@
|
|||||||
import {
|
import { Injectable } from '@angular/core';
|
||||||
Inject,
|
|
||||||
Injectable,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { CookieAttributes } from 'js-cookie';
|
import { CookieAttributes } from 'js-cookie';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
Subject,
|
Subject,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
|
|
||||||
import { REQUEST } from '../../../express.tokens';
|
|
||||||
|
|
||||||
export interface ICookieService {
|
export interface ICookieService {
|
||||||
readonly cookies$: Observable<{ readonly [key: string]: any }>;
|
readonly cookies$: Observable<{ readonly [key: string]: any }>;
|
||||||
|
|
||||||
@@ -27,9 +22,6 @@ export abstract class CookieService implements ICookieService {
|
|||||||
protected readonly cookieSource = new Subject<{ readonly [key: string]: any }>();
|
protected readonly cookieSource = new Subject<{ readonly [key: string]: any }>();
|
||||||
public readonly cookies$ = this.cookieSource.asObservable();
|
public readonly cookies$ = this.cookieSource.asObservable();
|
||||||
|
|
||||||
constructor(@Inject(REQUEST) protected req: any) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract set(name: string, value: any, options?: CookieAttributes): void;
|
public abstract set(name: string, value: any, options?: CookieAttributes): void;
|
||||||
|
|
||||||
public abstract remove(name: string, options?: CookieAttributes): void;
|
public abstract remove(name: string, options?: CookieAttributes): void;
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import {
|
||||||
|
Inject,
|
||||||
|
Injectable,
|
||||||
|
} from '@angular/core';
|
||||||
import { CookieAttributes } from 'js-cookie';
|
import { CookieAttributes } from 'js-cookie';
|
||||||
|
|
||||||
|
import { REQUEST } from '../../../express.tokens';
|
||||||
import {
|
import {
|
||||||
CookieService,
|
CookieService,
|
||||||
ICookieService,
|
ICookieService,
|
||||||
@@ -9,6 +13,10 @@ import {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class ServerCookieService extends CookieService implements ICookieService {
|
export class ServerCookieService extends CookieService implements ICookieService {
|
||||||
|
|
||||||
|
constructor(@Inject(REQUEST) protected req: any) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
public set(name: string, value: any, options?: CookieAttributes): void {
|
public set(name: string, value: any, options?: CookieAttributes): void {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { getTestScheduler } from 'jasmine-marbles';
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
|
import { of } from 'rxjs';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
|
||||||
import { FormFieldMetadataValueObject } from '../../shared/form/builder/models/form-field-metadata-value.model';
|
import { FormFieldMetadataValueObject } from '../../shared/form/builder/models/form-field-metadata-value.model';
|
||||||
@@ -13,6 +14,7 @@ import {
|
|||||||
SubmissionRequest,
|
SubmissionRequest,
|
||||||
} from '../data/request.models';
|
} from '../data/request.models';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
|
import { RequestEntry } from '../data/request-entry.model';
|
||||||
import { SubmissionRestService } from './submission-rest.service';
|
import { SubmissionRestService } from './submission-rest.service';
|
||||||
|
|
||||||
describe('SubmissionRestService test suite', () => {
|
describe('SubmissionRestService test suite', () => {
|
||||||
@@ -38,7 +40,9 @@ describe('SubmissionRestService test suite', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
requestService = getMockRequestService();
|
requestService = getMockRequestService(of(Object.assign(new RequestEntry(), {
|
||||||
|
request: new SubmissionRequest('mock-request-uuid', 'mock-request-href'),
|
||||||
|
})));
|
||||||
rdbService = getMockRemoteDataBuildService();
|
rdbService = getMockRemoteDataBuildService();
|
||||||
scheduler = getTestScheduler();
|
scheduler = getTestScheduler();
|
||||||
halService = new HALEndpointServiceStub(resourceEndpointURL);
|
halService = new HALEndpointServiceStub(resourceEndpointURL);
|
||||||
@@ -62,7 +66,7 @@ describe('SubmissionRestService test suite', () => {
|
|||||||
scheduler.schedule(() => service.getDataById(resourceEndpoint, resourceScope).subscribe());
|
scheduler.schedule(() => service.getDataById(resourceEndpoint, resourceScope).subscribe());
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
expect(requestService.send).toHaveBeenCalledWith(expected);
|
expect(requestService.send).toHaveBeenCalledWith(expected, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,19 +1,23 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
import {
|
||||||
|
Observable,
|
||||||
|
skipWhile,
|
||||||
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
distinctUntilChanged,
|
distinctUntilChanged,
|
||||||
filter,
|
filter,
|
||||||
map,
|
map,
|
||||||
mergeMap,
|
mergeMap,
|
||||||
|
switchMap,
|
||||||
tap,
|
tap,
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
hasValue,
|
hasValue,
|
||||||
|
hasValueOperator,
|
||||||
isNotEmpty,
|
isNotEmpty,
|
||||||
} from '../../shared/empty.util';
|
} from '../../shared/empty.util';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { ErrorResponse } from '../cache/response.models';
|
|
||||||
import { RemoteData } from '../data/remote-data';
|
import { RemoteData } from '../data/remote-data';
|
||||||
import {
|
import {
|
||||||
DeleteRequest,
|
DeleteRequest,
|
||||||
@@ -24,8 +28,6 @@ import {
|
|||||||
SubmissionRequest,
|
SubmissionRequest,
|
||||||
} from '../data/request.models';
|
} from '../data/request.models';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { RequestError } from '../data/request-error.model';
|
|
||||||
import { RestRequest } from '../data/rest-request.model';
|
|
||||||
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
|
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { getFirstCompletedRemoteData } from '../shared/operators';
|
import { getFirstCompletedRemoteData } from '../shared/operators';
|
||||||
@@ -33,6 +35,23 @@ import { SubmitDataResponseDefinitionObject } from '../shared/submit-data-respon
|
|||||||
import { URLCombiner } from '../url-combiner/url-combiner';
|
import { URLCombiner } from '../url-combiner/url-combiner';
|
||||||
import { SubmissionResponse } from './submission-response.model';
|
import { SubmissionResponse } from './submission-response.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the first emitting payload's dataDefinition, or throw an error if the request failed
|
||||||
|
*/
|
||||||
|
export const getFirstDataDefinition = () =>
|
||||||
|
(source: Observable<RemoteData<SubmissionResponse>>): Observable<SubmitDataResponseDefinitionObject> =>
|
||||||
|
source.pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
map((response: RemoteData<SubmissionResponse>) => {
|
||||||
|
if (response.hasFailed) {
|
||||||
|
throw new Error(response.errorMessage);
|
||||||
|
} else {
|
||||||
|
return hasValue(response?.payload?.dataDefinition) ? response.payload.dataDefinition : [response.payload];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The service handling all submission REST requests
|
* The service handling all submission REST requests
|
||||||
*/
|
*/
|
||||||
@@ -56,15 +75,7 @@ export class SubmissionRestService {
|
|||||||
*/
|
*/
|
||||||
protected fetchRequest(requestId: string): Observable<SubmitDataResponseDefinitionObject> {
|
protected fetchRequest(requestId: string): Observable<SubmitDataResponseDefinitionObject> {
|
||||||
return this.rdbService.buildFromRequestUUID<SubmissionResponse>(requestId).pipe(
|
return this.rdbService.buildFromRequestUUID<SubmissionResponse>(requestId).pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstDataDefinition(),
|
||||||
map((response: RemoteData<SubmissionResponse>) => {
|
|
||||||
if (response.hasFailed) {
|
|
||||||
throw new ErrorResponse({ statusText: response.errorMessage, statusCode: response.statusCode } as RequestError);
|
|
||||||
} else {
|
|
||||||
return hasValue(response.payload) ? response.payload.dataDefinition : response.payload;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
distinctUntilChanged(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,21 +127,52 @@ export class SubmissionRestService {
|
|||||||
* The endpoint link name
|
* The endpoint link name
|
||||||
* @param id
|
* @param id
|
||||||
* The submission Object to retrieve
|
* The submission Object to retrieve
|
||||||
|
* @param useCachedVersionIfAvailable
|
||||||
|
* If this is true, the request will only be sent if there's no valid & cached version. Defaults to false
|
||||||
* @return Observable<SubmitDataResponseDefinitionObject>
|
* @return Observable<SubmitDataResponseDefinitionObject>
|
||||||
* server response
|
* server response
|
||||||
*/
|
*/
|
||||||
public getDataById(linkName: string, id: string): Observable<SubmitDataResponseDefinitionObject> {
|
public getDataById(linkName: string, id: string, useCachedVersionIfAvailable = false): Observable<SubmitDataResponseDefinitionObject> {
|
||||||
const requestId = this.requestService.generateRequestId();
|
|
||||||
return this.halService.getEndpoint(linkName).pipe(
|
return this.halService.getEndpoint(linkName).pipe(
|
||||||
map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, id)),
|
map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, id)),
|
||||||
filter((href: string) => isNotEmpty(href)),
|
filter((href: string) => isNotEmpty(href)),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
map((endpointURL: string) => new SubmissionRequest(requestId, endpointURL)),
|
mergeMap((endpointURL: string) => {
|
||||||
tap((request: RestRequest) => {
|
this.sendGetDataRequest(endpointURL, useCachedVersionIfAvailable);
|
||||||
this.requestService.send(request);
|
const startTime: number = new Date().getTime();
|
||||||
|
return this.requestService.getByHref(endpointURL).pipe(
|
||||||
|
map((requestEntry) => requestEntry?.request?.uuid),
|
||||||
|
hasValueOperator(),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
switchMap((requestId) => this.rdbService.buildFromRequestUUID<SubmissionResponse>(requestId)),
|
||||||
|
// 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<SubmissionResponse>) => rd.isStale || (!useCachedVersionIfAvailable && rd.lastUpdated < startTime)),
|
||||||
|
tap((rd: RemoteData<SubmissionResponse>) => {
|
||||||
|
if (hasValue(rd) && rd.isStale) {
|
||||||
|
this.sendGetDataRequest(endpointURL, useCachedVersionIfAvailable);
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
mergeMap(() => this.fetchRequest(requestId)),
|
);
|
||||||
distinctUntilChanged());
|
}),
|
||||||
|
getFirstDataDefinition(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a GET SubmissionRequest
|
||||||
|
*
|
||||||
|
* @param href
|
||||||
|
* Endpoint URL of the submission data
|
||||||
|
* @param useCachedVersionIfAvailable
|
||||||
|
* If this is true, the request will only be sent if there's no valid & cached version. Defaults to false
|
||||||
|
*/
|
||||||
|
private sendGetDataRequest(href: string, useCachedVersionIfAvailable = false) {
|
||||||
|
const requestId = this.requestService.generateRequestId();
|
||||||
|
const request = new SubmissionRequest(requestId, href);
|
||||||
|
this.requestService.send(request, useCachedVersionIfAvailable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -116,6 +116,14 @@ describe('MetadataFieldSelectorComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should sort the fields by name to ensure the one without a qualifier is first', () => {
|
||||||
|
component.mdField = 'dc.relation';
|
||||||
|
|
||||||
|
component.validate();
|
||||||
|
|
||||||
|
expect(registryService.queryMetadataFields).toHaveBeenCalledWith('dc.relation', { elementsPerPage: 10, sort: new SortOptions('fieldName', SortDirection.ASC) }, true, false, followLink('schema'));
|
||||||
|
});
|
||||||
|
|
||||||
describe('when querying the metadata fields returns an error response', () => {
|
describe('when querying the metadata fields returns an error response', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(registryService.queryMetadataFields as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$('Failed'));
|
(registryService.queryMetadataFields as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$('Failed'));
|
||||||
|
@@ -45,6 +45,7 @@ import {
|
|||||||
SortDirection,
|
SortDirection,
|
||||||
SortOptions,
|
SortOptions,
|
||||||
} from '../../../core/cache/models/sort-options.model';
|
} from '../../../core/cache/models/sort-options.model';
|
||||||
|
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
||||||
import { RegistryService } from '../../../core/registry/registry.service';
|
import { RegistryService } from '../../../core/registry/registry.service';
|
||||||
import {
|
import {
|
||||||
getAllSucceededRemoteData,
|
getAllSucceededRemoteData,
|
||||||
@@ -129,6 +130,11 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterV
|
|||||||
*/
|
*/
|
||||||
showInvalid = false;
|
showInvalid = false;
|
||||||
|
|
||||||
|
searchOptions: FindListOptions = {
|
||||||
|
elementsPerPage: 10,
|
||||||
|
sort: new SortOptions('fieldName', SortDirection.ASC),
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscriptions to unsubscribe from on destroy
|
* Subscriptions to unsubscribe from on destroy
|
||||||
*/
|
*/
|
||||||
@@ -211,7 +217,7 @@ export class MetadataFieldSelectorComponent implements OnInit, OnDestroy, AfterV
|
|||||||
* Upon subscribing to the returned observable, the showInvalid flag is updated accordingly to show the feedback under the input
|
* Upon subscribing to the returned observable, the showInvalid flag is updated accordingly to show the feedback under the input
|
||||||
*/
|
*/
|
||||||
validate(): Observable<boolean> {
|
validate(): Observable<boolean> {
|
||||||
return this.registryService.queryMetadataFields(this.mdField, null, true, false, followLink('schema')).pipe(
|
return this.registryService.queryMetadataFields(this.mdField, this.searchOptions, true, false, followLink('schema')).pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
switchMap((rd) => {
|
switchMap((rd) => {
|
||||||
if (rd.hasSucceeded) {
|
if (rd.hasSucceeded) {
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
<a *ngIf="linkType !== linkTypes.None"
|
<a *ngIf="linkType !== linkTypes.None"
|
||||||
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
[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>
|
<div>
|
||||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
||||||
</ds-thumbnail>
|
</ds-thumbnail>
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<div *ngIf="linkType !== linkTypes.None" class="text-center">
|
<div *ngIf="linkType !== linkTypes.None" class="text-center">
|
||||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
[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>
|
||||||
</div>
|
</div>
|
||||||
</ds-truncatable>
|
</ds-truncatable>
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
<a *ngIf="linkType !== linkTypes.None"
|
<a *ngIf="linkType !== linkTypes.None"
|
||||||
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
[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>
|
<div>
|
||||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
||||||
</ds-thumbnail>
|
</ds-thumbnail>
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<div *ngIf="linkType !== linkTypes.None" class="text-center">
|
<div *ngIf="linkType !== linkTypes.None" class="text-center">
|
||||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
[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>
|
||||||
</div>
|
</div>
|
||||||
</ds-truncatable>
|
</ds-truncatable>
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
<a *ngIf="linkType !== linkTypes.None"
|
<a *ngIf="linkType !== linkTypes.None"
|
||||||
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
[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>
|
<div>
|
||||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
||||||
</ds-thumbnail>
|
</ds-thumbnail>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<div *ngIf="linkType !== linkTypes.None" class="text-center">
|
<div *ngIf="linkType !== linkTypes.None" class="text-center">
|
||||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
[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>
|
||||||
</div>
|
</div>
|
||||||
</ds-truncatable>
|
</ds-truncatable>
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div *ngIf="showThumbnails" class="col-3 col-md-2">
|
<div *ngIf="showThumbnails" class="col-3 col-md-2">
|
||||||
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
[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 [thumbnail]="dso?.thumbnail | async" [limitWidth]="true">
|
||||||
</ds-thumbnail>
|
</ds-thumbnail>
|
||||||
</a>
|
</a>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
[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"
|
||||||
[innerHTML]="dsoTitle"></a>
|
[innerHTML]="dsoTitle" role="link" tabindex="0"></a>
|
||||||
<span *ngIf="linkType === linkTypes.None"
|
<span *ngIf="linkType === linkTypes.None"
|
||||||
class="lead item-list-title dont-break-out"
|
class="lead item-list-title dont-break-out"
|
||||||
[innerHTML]="dsoTitle"></span>
|
[innerHTML]="dsoTitle"></span>
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div *ngIf="showThumbnails" class="col-3 col-md-2">
|
<div *ngIf="showThumbnails" class="col-3 col-md-2">
|
||||||
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
[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 [thumbnail]="dso?.thumbnail | async" [limitWidth]="true">
|
||||||
</ds-thumbnail>
|
</ds-thumbnail>
|
||||||
</a>
|
</a>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
[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"
|
||||||
[innerHTML]="dsoTitle"></a>
|
[innerHTML]="dsoTitle" role="link" tabindex="0"></a>
|
||||||
<span *ngIf="linkType === linkTypes.None"
|
<span *ngIf="linkType === linkTypes.None"
|
||||||
class="lead item-list-title dont-break-out"
|
class="lead item-list-title dont-break-out"
|
||||||
[innerHTML]="dsoTitle"></span>
|
[innerHTML]="dsoTitle"></span>
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div *ngIf="showThumbnails" class="col-3 col-md-2">
|
<div *ngIf="showThumbnails" class="col-3 col-md-2">
|
||||||
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'" [attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
<a *ngIf="linkType !== linkTypes.None" [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 [thumbnail]="dso?.thumbnail | async" [limitWidth]="true">
|
||||||
</ds-thumbnail>
|
</ds-thumbnail>
|
||||||
</a>
|
</a>
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
<ds-truncatable [id]="dso.id">
|
<ds-truncatable [id]="dso.id">
|
||||||
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'" [attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
<a *ngIf="linkType !== linkTypes.None" [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"
|
||||||
[innerHTML]="dsoTitle"></a>
|
[innerHTML]="dsoTitle" role="link" tabindex="0"></a>
|
||||||
<span *ngIf="linkType === linkTypes.None"
|
<span *ngIf="linkType === linkTypes.None"
|
||||||
class="lead item-list-title dont-break-out"
|
class="lead item-list-title dont-break-out"
|
||||||
[innerHTML]="dsoTitle"></span>
|
[innerHTML]="dsoTitle"></span>
|
||||||
|
@@ -51,7 +51,7 @@
|
|||||||
[label]="'journalissue.page.keyword'">
|
[label]="'journalissue.page.keyword'">
|
||||||
</ds-generic-item-page-field>
|
</ds-generic-item-page-field>
|
||||||
<div>
|
<div>
|
||||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']">
|
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="link" tabindex="0">
|
||||||
{{"item.page.link.full" | translate}}
|
{{"item.page.link.full" | translate}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -34,7 +34,7 @@
|
|||||||
[label]="'journalvolume.page.description'">
|
[label]="'journalvolume.page.description'">
|
||||||
</ds-generic-item-page-field>
|
</ds-generic-item-page-field>
|
||||||
<div>
|
<div>
|
||||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']">
|
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="link" tabindex="0">
|
||||||
{{"item.page.link.full" | translate}}
|
{{"item.page.link.full" | translate}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -33,7 +33,7 @@
|
|||||||
[label]="'journal.page.description'">
|
[label]="'journal.page.description'">
|
||||||
</ds-generic-item-page-field>
|
</ds-generic-item-page-field>
|
||||||
<div>
|
<div>
|
||||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']">
|
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="link" tabindex="0">
|
||||||
{{"item.page.link.full" | translate}}
|
{{"item.page.link.full" | translate}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
<a *ngIf="linkType !== linkTypes.None"
|
<a *ngIf="linkType !== linkTypes.None"
|
||||||
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
[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>
|
<div>
|
||||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
||||||
</ds-thumbnail>
|
</ds-thumbnail>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
<div *ngIf="linkType !== linkTypes.None" class="text-center">
|
<div *ngIf="linkType !== linkTypes.None" class="text-center">
|
||||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
[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>
|
||||||
</div>
|
</div>
|
||||||
</ds-truncatable>
|
</ds-truncatable>
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
<a *ngIf="linkType !== linkTypes.None"
|
<a *ngIf="linkType !== linkTypes.None"
|
||||||
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
[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>
|
<div>
|
||||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
||||||
</ds-thumbnail>
|
</ds-thumbnail>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<div *ngIf="linkType !== linkTypes.None" class="text-center">
|
<div *ngIf="linkType !== linkTypes.None" class="text-center">
|
||||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
[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>
|
||||||
</div>
|
</div>
|
||||||
</ds-truncatable>
|
</ds-truncatable>
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
<a *ngIf="linkType !== linkTypes.None"
|
<a *ngIf="linkType !== linkTypes.None"
|
||||||
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
[target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
[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>
|
<div>
|
||||||
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
<ds-thumbnail [thumbnail]="dso?.thumbnail | async" [limitWidth]="false">
|
||||||
</ds-thumbnail>
|
</ds-thumbnail>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
<div *ngIf="linkType !== linkTypes.None" class="text-center">
|
<div *ngIf="linkType !== linkTypes.None" class="text-center">
|
||||||
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null" [routerLink]="[itemPageRoute]"
|
[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>
|
||||||
</div>
|
</div>
|
||||||
</ds-truncatable>
|
</ds-truncatable>
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div *ngIf="showThumbnails" class="col-3 col-md-2">
|
<div *ngIf="showThumbnails" class="col-3 col-md-2">
|
||||||
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
[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"
|
<ds-thumbnail [thumbnail]="dso?.thumbnail | async"
|
||||||
[defaultImage]="'assets/images/orgunit-placeholder.svg'"
|
[defaultImage]="'assets/images/orgunit-placeholder.svg'"
|
||||||
[alt]="'thumbnail.orgunit.alt'"
|
[alt]="'thumbnail.orgunit.alt'"
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
||||||
[routerLink]="[itemPageRoute]" class="lead"
|
[routerLink]="[itemPageRoute]" class="lead"
|
||||||
[innerHTML]="dsoTitle || ('orgunit.listelement.no-title' | translate)"></a>
|
[innerHTML]="dsoTitle || ('orgunit.listelement.no-title' | translate)" role="link" tabindex="0"></a>
|
||||||
<span *ngIf="linkType === linkTypes.None"
|
<span *ngIf="linkType === linkTypes.None"
|
||||||
class="lead"
|
class="lead"
|
||||||
[innerHTML]="dsoTitle || ('orgunit.listelement.no-title' | translate)"></span>
|
[innerHTML]="dsoTitle || ('orgunit.listelement.no-title' | translate)"></span>
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div *ngIf="showThumbnails" class="col-3 col-md-2">
|
<div *ngIf="showThumbnails" class="col-3 col-md-2">
|
||||||
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
[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"
|
<ds-thumbnail [thumbnail]="dso?.thumbnail | async"
|
||||||
[defaultImage]="'assets/images/person-placeholder.svg'"
|
[defaultImage]="'assets/images/person-placeholder.svg'"
|
||||||
[alt]="'thumbnail.person.alt'"
|
[alt]="'thumbnail.person.alt'"
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
||||||
[routerLink]="[itemPageRoute]" class="lead"
|
[routerLink]="[itemPageRoute]" class="lead"
|
||||||
[innerHTML]="dsoTitle || ('person.listelement.no-title' | translate)"></a>
|
[innerHTML]="dsoTitle || ('person.listelement.no-title' | translate)" role="link" tabindex="0"></a>
|
||||||
<span *ngIf="linkType === linkTypes.None"
|
<span *ngIf="linkType === linkTypes.None"
|
||||||
class="lead"
|
class="lead"
|
||||||
[innerHTML]="dsoTitle || ('person.listelement.no-title' | translate)"></span>
|
[innerHTML]="dsoTitle || ('person.listelement.no-title' | translate)"></span>
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div *ngIf="showThumbnails" class="col-3 col-md-2">
|
<div *ngIf="showThumbnails" class="col-3 col-md-2">
|
||||||
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
[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"
|
<ds-thumbnail [thumbnail]="dso?.thumbnail | async"
|
||||||
[defaultImage]="'assets/images/project-placeholder.svg'"
|
[defaultImage]="'assets/images/project-placeholder.svg'"
|
||||||
[alt]="'thumbnail.project.alt'"
|
[alt]="'thumbnail.project.alt'"
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
<a *ngIf="linkType !== linkTypes.None" [target]="(linkType === linkTypes.ExternalLink) ? '_blank' : '_self'"
|
||||||
[attr.rel]="(linkType === linkTypes.ExternalLink) ? 'noopener noreferrer' : null"
|
[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"
|
||||||
[innerHTML]="dsoTitle"></a>
|
[innerHTML]="dsoTitle" role="link" tabindex="0"></a>
|
||||||
<span *ngIf="linkType === linkTypes.None"
|
<span *ngIf="linkType === linkTypes.None"
|
||||||
class="lead item-list-title dont-break-out"
|
class="lead item-list-title dont-break-out"
|
||||||
[innerHTML]="dsoTitle"></span>
|
[innerHTML]="dsoTitle"></span>
|
||||||
|
@@ -54,7 +54,7 @@
|
|||||||
[label]="'orgunit.page.description'">
|
[label]="'orgunit.page.description'">
|
||||||
</ds-generic-item-page-field>
|
</ds-generic-item-page-field>
|
||||||
<div>
|
<div>
|
||||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']">
|
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="link" tabindex="0">
|
||||||
{{"item.page.link.full" | translate}}
|
{{"item.page.link.full" | translate}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -50,7 +50,7 @@
|
|||||||
[label]="'person.page.name'">
|
[label]="'person.page.name'">
|
||||||
</ds-generic-item-page-field>
|
</ds-generic-item-page-field>
|
||||||
<div>
|
<div>
|
||||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']">
|
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="link" tabindex="0">
|
||||||
{{"item.page.link.full" | translate}}
|
{{"item.page.link.full" | translate}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -62,7 +62,7 @@
|
|||||||
[label]="'project.page.keyword'">
|
[label]="'project.page.keyword'">
|
||||||
</ds-generic-item-page-field>
|
</ds-generic-item-page-field>
|
||||||
<div>
|
<div>
|
||||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']">
|
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="link" tabindex="0">
|
||||||
{{"item.page.link.full" | translate}}
|
{{"item.page.link.full" | translate}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -11,13 +11,13 @@
|
|||||||
|
|
||||||
<ul class="list-unstyled mb-0">
|
<ul class="list-unstyled mb-0">
|
||||||
<li>
|
<li>
|
||||||
<a routerLink="./" class="">Lorem ipsum</a>
|
<a routerLink="./" class="" role="link" tabindex="0">Lorem ipsum</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a routerLink="./" class="">Ut facilisis</a>
|
<a routerLink="./" class="" role="link" tabindex="0">Ut facilisis</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a routerLink="./" class="">Aenean sit</a>
|
<a routerLink="./" class="" role="link" tabindex="0">Aenean sit</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
<ul class="list-unstyled mb-0">
|
<ul class="list-unstyled mb-0">
|
||||||
<li>
|
<li>
|
||||||
<a routerLink="./" class="">Suspendisse potenti</a>
|
<a routerLink="./" class="" role="link" tabindex="0">Suspendisse potenti</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,14 +57,14 @@
|
|||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
<p class="m-0">
|
<p class="m-0">
|
||||||
<a class="text-white"
|
<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'} }}
|
{{ 'footer.copyright' | translate:{year: dateObj | date:'y'} }}
|
||||||
<a class="text-white"
|
<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>
|
</p>
|
||||||
<ul class="footer-info list-unstyled d-flex justify-content-center mb-0">
|
<ul class="footer-info list-unstyled d-flex justify-content-center mb-0">
|
||||||
<li>
|
<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}}
|
{{ 'footer.link.cookies' | translate}}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
@@ -74,20 +74,20 @@
|
|||||||
</li>
|
</li>
|
||||||
<li *ngIf="showPrivacyPolicy">
|
<li *ngIf="showPrivacyPolicy">
|
||||||
<a class="btn text-white"
|
<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>
|
</li>
|
||||||
<li *ngIf="showEndUserAgreement">
|
<li *ngIf="showEndUserAgreement">
|
||||||
<a class="btn text-white"
|
<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>
|
</li>
|
||||||
<li *ngIf="showSendFeedback$ | async">
|
<li *ngIf="showSendFeedback$ | async">
|
||||||
<a class="btn text-white"
|
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="coarLdnEnabled$ | async" class="notify-enabled text-white">
|
<div *ngIf="coarLdnEnabled$ | async" 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" />
|
<img class="n-coar" src="assets/images/n-coar.svg" [attr.alt]="'menu.header.image.logo' | translate" />
|
||||||
{{ 'footer.link.coar-notify-support' | translate }}
|
{{ 'footer.link.coar-notify-support' | translate }}
|
||||||
</a>
|
</a>
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<header>
|
<header>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="d-flex flex-row justify-content-between">
|
<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"/>
|
<img src="assets/images/dspace-logo.svg" [attr.alt]="'menu.header.image.logo' | translate"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
<li>issue permanent urls and trustworthy identifiers, including optional integrations with handle.net and DataCite DOI</li>
|
<li>issue permanent urls and trustworthy identifiers, including optional integrations with handle.net and DataCite DOI</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>Join an international community of <a href="https://wiki.lyrasis.org/display/DSPACE/DSpace+Positioning"
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -2,7 +2,6 @@ import { Route } from '@angular/router';
|
|||||||
|
|
||||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||||
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
||||||
import { homePageResolver } from './home-page.resolver';
|
|
||||||
import { ThemedHomePageComponent } from './themed-home-page.component';
|
import { ThemedHomePageComponent } from './themed-home-page.component';
|
||||||
|
|
||||||
export const ROUTES: Route[] = [
|
export const ROUTES: Route[] = [
|
||||||
@@ -26,8 +25,5 @@ export const ROUTES: Route[] = [
|
|||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
resolve: {
|
|
||||||
site: homePageResolver,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@@ -14,9 +14,6 @@
|
|||||||
<ds-suggestions-popup></ds-suggestions-popup>
|
<ds-suggestions-popup></ds-suggestions-popup>
|
||||||
|
|
||||||
<ng-template #homeContent>
|
<ng-template #homeContent>
|
||||||
<ng-container *ngIf="(site$ | async) as site">
|
|
||||||
<ds-view-tracker [object]="site"></ds-view-tracker>
|
|
||||||
</ng-container>
|
|
||||||
<ds-search-form [inPlaceSearch]="false"
|
<ds-search-form [inPlaceSearch]="false"
|
||||||
[searchPlaceholder]="'home.search-form.placeholder' | translate">
|
[searchPlaceholder]="'home.search-form.placeholder' | translate">
|
||||||
</ds-search-form>
|
</ds-search-form>
|
||||||
|
@@ -23,7 +23,6 @@ import { SuggestionsPopupComponent } from '../notifications/suggestions-popup/su
|
|||||||
import { ThemedConfigurationSearchPageComponent } from '../search-page/themed-configuration-search-page.component';
|
import { ThemedConfigurationSearchPageComponent } from '../search-page/themed-configuration-search-page.component';
|
||||||
import { ThemedSearchFormComponent } from '../shared/search-form/themed-search-form.component';
|
import { ThemedSearchFormComponent } from '../shared/search-form/themed-search-form.component';
|
||||||
import { PageWithSidebarComponent } from '../shared/sidebar/page-with-sidebar.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 { HomeCoarComponent } from './home-coar/home-coar.component';
|
||||||
import { ThemedHomeNewsComponent } from './home-news/themed-home-news.component';
|
import { ThemedHomeNewsComponent } from './home-news/themed-home-news.component';
|
||||||
import { RecentItemListComponent } from './recent-item-list/recent-item-list.component';
|
import { RecentItemListComponent } from './recent-item-list/recent-item-list.component';
|
||||||
@@ -34,7 +33,7 @@ import { ThemedTopLevelCommunityListComponent } from './top-level-community-list
|
|||||||
styleUrls: ['./home-page.component.scss'],
|
styleUrls: ['./home-page.component.scss'],
|
||||||
templateUrl: './home-page.component.html',
|
templateUrl: './home-page.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [ThemedHomeNewsComponent, NgTemplateOutlet, NgIf, ViewTrackerComponent, ThemedSearchFormComponent, ThemedTopLevelCommunityListComponent, RecentItemListComponent, AsyncPipe, TranslateModule, NgClass, SuggestionsPopupComponent, ThemedConfigurationSearchPageComponent, PageWithSidebarComponent, HomeCoarComponent],
|
imports: [ThemedHomeNewsComponent, NgTemplateOutlet, NgIf, ThemedSearchFormComponent, ThemedTopLevelCommunityListComponent, RecentItemListComponent, AsyncPipe, TranslateModule, NgClass, SuggestionsPopupComponent, ThemedConfigurationSearchPageComponent, PageWithSidebarComponent, HomeCoarComponent],
|
||||||
})
|
})
|
||||||
export class HomePageComponent implements OnInit {
|
export class HomePageComponent implements OnInit {
|
||||||
|
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
<ds-listable-object-component-loader [object]="item" [viewMode]="viewMode" class="pb-4">
|
<ds-listable-object-component-loader [object]="item" [viewMode]="viewMode" class="pb-4">
|
||||||
</ds-listable-object-component-loader>
|
</ds-listable-object-component-loader>
|
||||||
</div>
|
</div>
|
||||||
<button (click)="onLoadMore()" class="btn btn-primary search-button mt-4"> {{'vocabulary-treeview.load-more' | translate }} ...</button>
|
<button (click)="onLoadMore()" class="btn btn-primary search-button mt-4" role="button" tabindex="0"> {{'vocabulary-treeview.load-more' | translate }} ...</button>
|
||||||
</div>
|
</div>
|
||||||
<ds-error *ngIf="itemRD?.hasFailed" message="{{'error.recent-submissions' | translate}}"></ds-error>
|
<ds-error *ngIf="itemRD?.hasFailed" message="{{'error.recent-submissions' | translate}}"></ds-error>
|
||||||
<ds-loading *ngIf="!itemRD || itemRD.isLoading" message="{{'loading.recent-submissions' | translate}}">
|
<ds-loading *ngIf="!itemRD || itemRD.isLoading" message="{{'loading.recent-submissions' | translate}}">
|
||||||
|
@@ -7,8 +7,8 @@
|
|||||||
<div class="d-flex justify-content-between flex-wrap">
|
<div class="d-flex justify-content-between flex-wrap">
|
||||||
<span class="align-self-center">{{'item.alerts.withdrawn' | translate}}</span>
|
<span class="align-self-center">{{'item.alerts.withdrawn' | translate}}</span>
|
||||||
<div class="gap-2 d-flex">
|
<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="link" tabindex="0">{{"404.link.home-page" | translate}}</a>
|
||||||
<a *ngIf="showReinstateButton$ | async" class="btn btn-primary btn-sm" (click)="openReinstateModal()">{{ 'item.alerts.reinstate-request' | translate}}</a>
|
<a *ngIf="showReinstateButton$ | async" class="btn btn-primary btn-sm" (click)="openReinstateModal()" role="button" tabindex="0">{{ 'item.alerts.reinstate-request' | translate}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ds-alert>
|
</ds-alert>
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<ds-alert [type]="'alert-info'" [content]="'item.edit.authorizations.heading'"></ds-alert>
|
<ds-alert [type]="AlertType.Info" [content]="'item.edit.authorizations.heading'"></ds-alert>
|
||||||
<ds-resource-policies [resourceType]="'item'" [resourceName]="(getItemName() | async)"
|
<ds-resource-policies [resourceType]="'item'" [resourceName]="itemName$ | async"
|
||||||
[resourceUUID]="(getItemUUID() | async)">
|
[resourceUUID]="(item$ | async)?.id">
|
||||||
</ds-resource-policies>
|
</ds-resource-policies>
|
||||||
<ng-container *ngFor="let bundle of (bundles$ | async); trackById">
|
<ng-container *ngFor="let bundle of (bundles$ | async)">
|
||||||
<ds-resource-policies [resourceType]="'bundle'" [resourceUUID]="bundle.id" [resourceName]="bundle.name">
|
<ds-resource-policies [resourceType]="'bundle'" [resourceUUID]="bundle.id" [resourceName]="bundle.name">
|
||||||
</ds-resource-policies>
|
</ds-resource-policies>
|
||||||
<ng-container *ngIf="(bundleBitstreamsMap.get(bundle.id)?.bitstreams | async)?.length > 0">
|
<ng-container *ngIf="(bundleBitstreamsMap.get(bundle.id)?.bitstreams | async)?.length > 0">
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body" [id]="bundle.id" [ngbCollapse]="bundleBitstreamsMap.get(bundle.id).isCollapsed">
|
<div class="card-body" [id]="bundle.id" [ngbCollapse]="bundleBitstreamsMap.get(bundle.id).isCollapsed">
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngFor="let bitstream of (bundleBitstreamsMap.get(bundle.id).bitstreams | async); trackById">
|
*ngFor="let bitstream of (bundleBitstreamsMap.get(bundle.id).bitstreams | async)">
|
||||||
<ds-resource-policies [resourceType]="'bitstream'" [resourceUUID]="bitstream.id"
|
<ds-resource-policies [resourceType]="'bitstream'" [resourceUUID]="bitstream.id"
|
||||||
[resourceName]="bitstream.name"></ds-resource-policies>
|
[resourceName]="bitstream.name"></ds-resource-policies>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@@ -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', () => {
|
it('should get the item\'s bundle', () => {
|
||||||
|
|
||||||
expect(comp.getItemBundles()).toBeObservable(cold('a', {
|
expect(comp.bundles$).toBeObservable(cold('a', {
|
||||||
a: bundles,
|
a: bundles,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@@ -21,7 +21,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
catchError,
|
catchError,
|
||||||
filter,
|
filter,
|
||||||
first,
|
|
||||||
map,
|
map,
|
||||||
mergeMap,
|
mergeMap,
|
||||||
take,
|
take,
|
||||||
@@ -41,11 +40,11 @@ import {
|
|||||||
getFirstSucceededRemoteDataWithNotEmptyPayload,
|
getFirstSucceededRemoteDataWithNotEmptyPayload,
|
||||||
} from '../../../core/shared/operators';
|
} from '../../../core/shared/operators';
|
||||||
import { AlertComponent } from '../../../shared/alert/alert.component';
|
import { AlertComponent } from '../../../shared/alert/alert.component';
|
||||||
|
import { AlertType } from '../../../shared/alert/alert-type';
|
||||||
import {
|
import {
|
||||||
hasValue,
|
hasValue,
|
||||||
isNotEmpty,
|
isNotEmpty,
|
||||||
} from '../../../shared/empty.util';
|
} from '../../../shared/empty.util';
|
||||||
import { NgForTrackByIdDirective } from '../../../shared/ng-for-track-by-id.directive';
|
|
||||||
import { ResourcePoliciesComponent } from '../../../shared/resource-policies/resource-policies.component';
|
import { ResourcePoliciesComponent } from '../../../shared/resource-policies/resource-policies.component';
|
||||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||||
|
|
||||||
@@ -66,7 +65,6 @@ interface BundleBitstreamsMapEntry {
|
|||||||
NgbCollapseModule,
|
NgbCollapseModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
NgForOf,
|
NgForOf,
|
||||||
NgForTrackByIdDirective,
|
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
NgIf,
|
NgIf,
|
||||||
AlertComponent,
|
AlertComponent,
|
||||||
@@ -94,7 +92,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
|
|||||||
* The target editing item
|
* The target editing item
|
||||||
* @type {Observable<Item>}
|
* @type {Observable<Item>}
|
||||||
*/
|
*/
|
||||||
private item$: Observable<Item>;
|
item$: Observable<Item>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array to track all subscriptions and unsubscribe them onDestroy
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
@@ -133,16 +131,13 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
private bitstreamPageSize = 4;
|
private bitstreamPageSize = 4;
|
||||||
|
|
||||||
/**
|
itemName$: Observable<string>;
|
||||||
* Initialize instance variables
|
|
||||||
*
|
readonly AlertType = AlertType;
|
||||||
* @param {LinkService} linkService
|
|
||||||
* @param {ActivatedRoute} route
|
|
||||||
* @param nameService
|
|
||||||
*/
|
|
||||||
constructor(
|
constructor(
|
||||||
private linkService: LinkService,
|
protected linkService: LinkService,
|
||||||
private route: ActivatedRoute,
|
protected route: ActivatedRoute,
|
||||||
public nameService: DSONameService,
|
public nameService: DSONameService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
@@ -152,36 +147,18 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.getBundlesPerItem();
|
this.getBundlesPerItem();
|
||||||
}
|
this.itemName$ = this.getItemName();
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the item's UUID
|
|
||||||
*/
|
|
||||||
getItemUUID(): Observable<string> {
|
|
||||||
return this.item$.pipe(
|
|
||||||
map((item: Item) => item.id),
|
|
||||||
first((UUID: string) => isNotEmpty(UUID)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the item's name
|
* Return the item's name
|
||||||
*/
|
*/
|
||||||
getItemName(): Observable<string> {
|
private getItemName(): Observable<string> {
|
||||||
return this.item$.pipe(
|
return this.item$.pipe(
|
||||||
map((item: Item) => this.nameService.getName(item)),
|
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
|
* Get all bundles per item
|
||||||
* and all the bitstreams per bundle
|
* and all the bitstreams per bundle
|
||||||
|
@@ -32,7 +32,6 @@ import {
|
|||||||
map,
|
map,
|
||||||
switchMap,
|
switchMap,
|
||||||
take,
|
take,
|
||||||
tap,
|
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
import { AlertComponent } from 'src/app/shared/alert/alert.component';
|
import { AlertComponent } from 'src/app/shared/alert/alert.component';
|
||||||
import { AlertType } from 'src/app/shared/alert/alert-type';
|
import { AlertType } from 'src/app/shared/alert/alert-type';
|
||||||
@@ -243,15 +242,28 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
|||||||
this.itemService.getBundles(this.item.id, new PaginatedSearchOptions({ pagination: this.bundlesOptions })).pipe(
|
this.itemService.getBundles(this.item.id, new PaginatedSearchOptions({ pagination: this.bundlesOptions })).pipe(
|
||||||
getFirstSucceededRemoteData(),
|
getFirstSucceededRemoteData(),
|
||||||
getRemoteDataPayload(),
|
getRemoteDataPayload(),
|
||||||
tap((bundlesPL: PaginatedList<Bundle>) =>
|
).subscribe((bundles: PaginatedList<Bundle>) => {
|
||||||
this.showLoadMoreLink$.next(bundlesPL.pageInfo.currentPage < bundlesPL.pageInfo.totalPages),
|
this.updateBundles(bundles);
|
||||||
),
|
|
||||||
map((bundlePage: PaginatedList<Bundle>) => bundlePage.page),
|
|
||||||
).subscribe((bundles: Bundle[]) => {
|
|
||||||
this.bundlesSubject.next([...this.bundlesSubject.getValue(), ...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
|
* Submit the current changes
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<ds-metadata-field-wrapper [label]="label | translate">
|
<ds-metadata-field-wrapper [label]="label | translate">
|
||||||
<div class="collections">
|
<div class="collections">
|
||||||
<a *ngFor="let collection of (this.collections$ | async); let last=last;" [routerLink]="['/collections', collection.id]">
|
<a *ngFor="let collection of (this.collections$ | async); let last=last;" [routerLink]="['/collections', collection.id]" role="button" tabindex="0">
|
||||||
<span>{{ dsoNameService.getName(collection) }}</span><span *ngIf="!last" [innerHTML]="separator"></span>
|
<span>{{ dsoNameService.getName(collection) }}</span><span *ngIf="!last" [innerHTML]="separator"></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
(click)="$event.preventDefault(); handleLoadMore()"
|
(click)="$event.preventDefault(); handleLoadMore()"
|
||||||
class="load-more-btn btn btn-sm btn-outline-secondary"
|
class="load-more-btn btn btn-sm btn-outline-secondary"
|
||||||
role="button"
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
href="javascript:void(0);"
|
href="javascript:void(0);"
|
||||||
>
|
>
|
||||||
{{'item.page.collections.load-more' | translate}}
|
{{'item.page.collections.load-more' | translate}}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<ds-metadata-field-wrapper [label]="label | translate">
|
<ds-metadata-field-wrapper [label]="label | translate">
|
||||||
<a class="dont-break-out" *ngFor="let mdValue of mdValues; let last=last;" [href]="mdValue.value" [target]="hasInternalLink(mdValue.value) ? '_self' : '_blank'">
|
<a class="dont-break-out" *ngFor="let mdValue of mdValues; let last=last;" [href]="mdValue.value" [target]="hasInternalLink(mdValue.value) ? '_self' : '_blank'" role="link" tabindex="0">
|
||||||
{{ linktext || mdValue.value }}<span *ngIf="!last" [innerHTML]="separator"></span>
|
{{ linktext || mdValue.value }}<span *ngIf="!last" [innerHTML]="separator"></span>
|
||||||
</a>
|
</a>
|
||||||
</ds-metadata-field-wrapper>
|
</ds-metadata-field-wrapper>
|
||||||
|
@@ -21,14 +21,14 @@
|
|||||||
<a class="dont-break-out ds-simple-metadata-link"
|
<a class="dont-break-out ds-simple-metadata-link"
|
||||||
[href]="value"
|
[href]="value"
|
||||||
[attr.target]="getLinkAttributes(value).target"
|
[attr.target]="getLinkAttributes(value).target"
|
||||||
[attr.rel]="getLinkAttributes(value).rel">
|
[attr.rel]="getLinkAttributes(value).rel" role="link" tabindex="0">
|
||||||
{{value}}
|
{{value}}
|
||||||
</a>
|
</a>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<!-- Render value as a link with icon -->
|
<!-- Render value as a link with icon -->
|
||||||
<ng-template #linkImg let-img="img" let-value="value">
|
<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"
|
<img class="link-logo"
|
||||||
[alt]="img.alt | translate"
|
[alt]="img.alt | translate"
|
||||||
[style.height]="'var(' + img.heightVar + ', --ds-item-page-img-field-default-inline-height)'"
|
[style.height]="'var(' + img.heightVar + ', --ds-item-page-img-field-default-inline-height)'"
|
||||||
@@ -46,5 +46,5 @@
|
|||||||
<ng-template #browselink let-value="value">
|
<ng-template #browselink let-value="value">
|
||||||
<a class="dont-break-out preserve-line-breaks ds-browse-link"
|
<a class="dont-break-out preserve-line-breaks ds-browse-link"
|
||||||
[routerLink]="['/browse', browseDefinition.id]"
|
[routerLink]="['/browse', browseDefinition.id]"
|
||||||
[queryParams]="getQueryParams(value)">{{value}}</a>
|
[queryParams]="getQueryParams(value)" role="link" tabindex="0">{{value}}</a>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@@ -3,14 +3,13 @@
|
|||||||
<div *ngIf="itemRD?.payload as item">
|
<div *ngIf="itemRD?.payload as item">
|
||||||
<ds-item-alerts [item]="item"></ds-item-alerts>
|
<ds-item-alerts [item]="item"></ds-item-alerts>
|
||||||
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
|
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
|
||||||
<ds-view-tracker [object]="item"></ds-view-tracker>
|
|
||||||
<div *ngIf="!item.isWithdrawn || (isAdmin$|async)" class="full-item-info">
|
<div *ngIf="!item.isWithdrawn || (isAdmin$|async)" class="full-item-info">
|
||||||
<div class="d-flex flex-row">
|
<div class="d-flex flex-row">
|
||||||
<ds-item-page-title-field class="mr-auto" [item]="item"></ds-item-page-title-field>
|
<ds-item-page-title-field class="mr-auto" [item]="item"></ds-item-page-title-field>
|
||||||
<ds-dso-edit-menu></ds-dso-edit-menu>
|
<ds-dso-edit-menu></ds-dso-edit-menu>
|
||||||
</div>
|
</div>
|
||||||
<div class="simple-view-link my-3" *ngIf="!fromSubmissionObject">
|
<div class="simple-view-link my-3" *ngIf="!fromSubmissionObject">
|
||||||
<a class="btn btn-outline-primary" [routerLink]="[(itemPageRoute$ | async)]">
|
<a class="btn btn-outline-primary" [routerLink]="[(itemPageRoute$ | async)]" role="button" tabindex="0">
|
||||||
{{"item.page.link.simple" | translate}}
|
{{"item.page.link.simple" | translate}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -45,7 +45,7 @@ import { createPaginatedList } from '../../shared/testing/utils.test';
|
|||||||
import { ThemeService } from '../../shared/theme-support/theme.service';
|
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||||
import { TruncatePipe } from '../../shared/utils/truncate.pipe';
|
import { TruncatePipe } from '../../shared/utils/truncate.pipe';
|
||||||
import { VarDirective } from '../../shared/utils/var.directive';
|
import { VarDirective } from '../../shared/utils/var.directive';
|
||||||
import { ViewTrackerComponent } from '../../statistics/angulartics/dspace/view-tracker.component';
|
import { ViewTrackerResolverService } from '../../statistics/angulartics/dspace/view-tracker-resolver.service';
|
||||||
import { ThemedItemAlertsComponent } from '../alerts/themed-item-alerts.component';
|
import { ThemedItemAlertsComponent } from '../alerts/themed-item-alerts.component';
|
||||||
import { CollectionsComponent } from '../field-components/collections/collections.component';
|
import { CollectionsComponent } from '../field-components/collections/collections.component';
|
||||||
import { ThemedItemPageTitleFieldComponent } from '../simple/field-components/specific-field/title/themed-item-page-field.component';
|
import { ThemedItemPageTitleFieldComponent } from '../simple/field-components/specific-field/title/themed-item-page-field.component';
|
||||||
@@ -162,7 +162,7 @@ describe('FullItemPageComponent', () => {
|
|||||||
ThemedLoadingComponent,
|
ThemedLoadingComponent,
|
||||||
ThemedItemPageTitleFieldComponent,
|
ThemedItemPageTitleFieldComponent,
|
||||||
DsoEditMenuComponent,
|
DsoEditMenuComponent,
|
||||||
ViewTrackerComponent,
|
ViewTrackerResolverService,
|
||||||
ThemedItemAlertsComponent,
|
ThemedItemAlertsComponent,
|
||||||
CollectionsComponent,
|
CollectionsComponent,
|
||||||
ThemedFullFileSectionComponent,
|
ThemedFullFileSectionComponent,
|
||||||
|
@@ -44,7 +44,6 @@ import { hasValue } from '../../shared/empty.util';
|
|||||||
import { ErrorComponent } from '../../shared/error/error.component';
|
import { ErrorComponent } from '../../shared/error/error.component';
|
||||||
import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component';
|
import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component';
|
||||||
import { VarDirective } from '../../shared/utils/var.directive';
|
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 { ThemedItemAlertsComponent } from '../alerts/themed-item-alerts.component';
|
||||||
import { CollectionsComponent } from '../field-components/collections/collections.component';
|
import { CollectionsComponent } from '../field-components/collections/collections.component';
|
||||||
import { ThemedItemPageTitleFieldComponent } from '../simple/field-components/specific-field/title/themed-item-page-field.component';
|
import { ThemedItemPageTitleFieldComponent } from '../simple/field-components/specific-field/title/themed-item-page-field.component';
|
||||||
@@ -79,7 +78,6 @@ import { ThemedFullFileSectionComponent } from './field-components/file-section/
|
|||||||
ThemedItemPageTitleFieldComponent,
|
ThemedItemPageTitleFieldComponent,
|
||||||
DsoEditMenuComponent,
|
DsoEditMenuComponent,
|
||||||
ItemVersionsNoticeComponent,
|
ItemVersionsNoticeComponent,
|
||||||
ViewTrackerComponent,
|
|
||||||
ThemedItemAlertsComponent,
|
ThemedItemAlertsComponent,
|
||||||
VarDirective,
|
VarDirective,
|
||||||
],
|
],
|
||||||
|
@@ -6,6 +6,7 @@ import { itemBreadcrumbResolver } from '../core/breadcrumbs/item-breadcrumb.reso
|
|||||||
import { dsoEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
import { dsoEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||||
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
||||||
|
import { viewTrackerResolver } from '../statistics/angulartics/dspace/view-tracker.resolver';
|
||||||
import { BitstreamRequestACopyPageComponent } from './bitstreams/request-a-copy/bitstream-request-a-copy-page.component';
|
import { BitstreamRequestACopyPageComponent } from './bitstreams/request-a-copy/bitstream-request-a-copy-page.component';
|
||||||
import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component';
|
import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component';
|
||||||
import { ThemedFullItemPageComponent } from './full/themed-full-item-page.component';
|
import { ThemedFullItemPageComponent } from './full/themed-full-item-page.component';
|
||||||
@@ -36,6 +37,7 @@ export const ROUTES: Route[] = [
|
|||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
resolve: {
|
resolve: {
|
||||||
menu: dsoEditMenuResolver,
|
menu: dsoEditMenuResolver,
|
||||||
|
tracking: viewTrackerResolver,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -43,6 +45,7 @@ export const ROUTES: Route[] = [
|
|||||||
component: ThemedFullItemPageComponent,
|
component: ThemedFullItemPageComponent,
|
||||||
resolve: {
|
resolve: {
|
||||||
menu: dsoEditMenuResolver,
|
menu: dsoEditMenuResolver,
|
||||||
|
tracking: viewTrackerResolver,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -16,12 +16,7 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
<ng-template #showThumbnail>
|
<ng-template #showThumbnail>
|
||||||
<ds-media-viewer-image *ngIf="mediaOptions.image && mediaOptions.video"
|
<ds-thumbnail [thumbnail]="(thumbnailsRD$ | async)?.payload?.page[0]">
|
||||||
[image]="(thumbnailsRD$ | async)?.payload?.page[0]?._links.content.href || thumbnailPlaceholder"
|
|
||||||
[preview]="false"
|
|
||||||
></ds-media-viewer-image>
|
|
||||||
<ds-thumbnail *ngIf="!(mediaOptions.image && mediaOptions.video)"
|
|
||||||
[thumbnail]="(thumbnailsRD$ | async)?.payload?.page[0]">
|
|
||||||
</ds-thumbnail>
|
</ds-thumbnail>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@@ -1,4 +1,7 @@
|
|||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import {
|
||||||
|
NO_ERRORS_SCHEMA,
|
||||||
|
PLATFORM_ID,
|
||||||
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ComponentFixture,
|
ComponentFixture,
|
||||||
TestBed,
|
TestBed,
|
||||||
@@ -14,7 +17,9 @@ import { of as observableOf } from 'rxjs';
|
|||||||
|
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
||||||
|
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||||
import { Bitstream } from '../../core/shared/bitstream.model';
|
import { Bitstream } from '../../core/shared/bitstream.model';
|
||||||
|
import { FileService } from '../../core/shared/file.service';
|
||||||
import { MediaViewerItem } from '../../core/shared/media-viewer-item.model';
|
import { MediaViewerItem } from '../../core/shared/media-viewer-item.model';
|
||||||
import { MetadataFieldWrapperComponent } from '../../shared/metadata-field-wrapper/metadata-field-wrapper.component';
|
import { MetadataFieldWrapperComponent } from '../../shared/metadata-field-wrapper/metadata-field-wrapper.component';
|
||||||
import { AuthServiceMock } from '../../shared/mocks/auth.service.mock';
|
import { AuthServiceMock } from '../../shared/mocks/auth.service.mock';
|
||||||
@@ -31,6 +36,9 @@ import { MediaViewerComponent } from './media-viewer.component';
|
|||||||
describe('MediaViewerComponent', () => {
|
describe('MediaViewerComponent', () => {
|
||||||
let comp: MediaViewerComponent;
|
let comp: MediaViewerComponent;
|
||||||
let fixture: ComponentFixture<MediaViewerComponent>;
|
let fixture: ComponentFixture<MediaViewerComponent>;
|
||||||
|
let authService;
|
||||||
|
let authorizationService;
|
||||||
|
let fileService;
|
||||||
|
|
||||||
const mockBitstream: Bitstream = Object.assign(new Bitstream(), {
|
const mockBitstream: Bitstream = Object.assign(new Bitstream(), {
|
||||||
sizeBytes: 10201,
|
sizeBytes: 10201,
|
||||||
@@ -55,7 +63,7 @@ describe('MediaViewerComponent', () => {
|
|||||||
'dc.title': [
|
'dc.title': [
|
||||||
{
|
{
|
||||||
language: null,
|
language: null,
|
||||||
value: 'test_word.docx',
|
value: 'test_image.jpg',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -73,6 +81,15 @@ describe('MediaViewerComponent', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
|
authService = jasmine.createSpyObj('AuthService', {
|
||||||
|
isAuthenticated: observableOf(true),
|
||||||
|
});
|
||||||
|
authorizationService = jasmine.createSpyObj('AuthorizationService', {
|
||||||
|
isAuthorized: observableOf(true),
|
||||||
|
});
|
||||||
|
fileService = jasmine.createSpyObj('FileService', {
|
||||||
|
retrieveFileDownloadLink: null,
|
||||||
|
});
|
||||||
return TestBed.configureTestingModule({
|
return TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
@@ -88,6 +105,10 @@ describe('MediaViewerComponent', () => {
|
|||||||
MetadataFieldWrapperComponent,
|
MetadataFieldWrapperComponent,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
{ provide: AuthService, useValue: authService },
|
||||||
|
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||||
|
{ provide: FileService, useValue: fileService },
|
||||||
|
{ provide: PLATFORM_ID, useValue: 'browser' },
|
||||||
{ provide: BitstreamDataService, useValue: bitstreamDataService },
|
{ provide: BitstreamDataService, useValue: bitstreamDataService },
|
||||||
{ provide: ThemeService, useValue: getMockThemeService() },
|
{ provide: ThemeService, useValue: getMockThemeService() },
|
||||||
{ provide: AuthService, useValue: new AuthServiceMock() },
|
{ provide: AuthService, useValue: new AuthServiceMock() },
|
||||||
@@ -150,9 +171,9 @@ describe('MediaViewerComponent', () => {
|
|||||||
expect(mediaItem.thumbnail).toBe(null);
|
expect(mediaItem.thumbnail).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display a default, thumbnail', () => {
|
it('should display a default thumbnail', () => {
|
||||||
const defaultThumbnail = fixture.debugElement.query(
|
const defaultThumbnail = fixture.debugElement.query(
|
||||||
By.css('ds-media-viewer-image'),
|
By.css('ds-thumbnail'),
|
||||||
);
|
);
|
||||||
expect(defaultThumbnail.nativeElement).toBeDefined();
|
expect(defaultThumbnail.nativeElement).toBeDefined();
|
||||||
});
|
});
|
||||||
|
@@ -1,18 +1,18 @@
|
|||||||
<div class="container mb-5">
|
<div class="container mb-5">
|
||||||
<h1>{{'person.orcid.registry.auth' | translate}}</h1>
|
<h1>{{'person.orcid.registry.auth' | translate}}</h1>
|
||||||
<ng-container *ngIf="(isLinkedToOrcid() | async); then orcidLinked; else orcidNotLinked"></ng-container>
|
<ng-container *ngIf="(isOrcidLinked$ | async); then orcidLinked; else orcidNotLinked"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #orcidLinked>
|
<ng-template #orcidLinked>
|
||||||
<div data-test="orcidLinked">
|
<div data-test="orcidLinked">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div *ngIf="(hasOrcidAuthorizations() | async)" class="col-sm-6 mb-3" data-test="hasOrcidAuthorizations">
|
<div *ngIf="(hasOrcidAuthorizations$ | async)" class="col-sm-6 mb-3" data-test="hasOrcidAuthorizations">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-header">{{ 'person.page.orcid.granted-authorizations'| translate }}</div>
|
<div class="card-header">{{ 'person.page.orcid.granted-authorizations'| translate }}</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="container p-0">
|
<div class="container p-0">
|
||||||
<ul>
|
<ul>
|
||||||
<li *ngFor="let auth of (getOrcidAuthorizations() | async)" data-test="orcidAuthorization">
|
<li *ngFor="let auth of profileAuthorizationScopes$ | async" data-test="orcidAuthorization">
|
||||||
{{getAuthorizationDescription(auth) | translate}}
|
{{getAuthorizationDescription(auth) | translate}}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -25,13 +25,13 @@
|
|||||||
<div class="card-header">{{ 'person.page.orcid.missing-authorizations'| translate }}</div>
|
<div class="card-header">{{ 'person.page.orcid.missing-authorizations'| translate }}</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<ds-alert *ngIf="(hasMissingOrcidAuthorizations() | async) !== true" [type]="'alert-success'" data-test="noMissingOrcidAuthorizations">
|
<ds-alert *ngIf="(hasMissingOrcidAuthorizations$ | async) !== true" [type]="AlertType.Success" data-test="noMissingOrcidAuthorizations">
|
||||||
{{'person.page.orcid.no-missing-authorizations-message' | translate}}
|
{{'person.page.orcid.no-missing-authorizations-message' | translate}}
|
||||||
</ds-alert>
|
</ds-alert>
|
||||||
<ds-alert *ngIf="(hasMissingOrcidAuthorizations() | async)" [type]="'alert-warning'" data-test="missingOrcidAuthorizations">
|
<ds-alert *ngIf="(hasMissingOrcidAuthorizations$ | async)" [type]="AlertType.Warning" data-test="missingOrcidAuthorizations">
|
||||||
{{'person.page.orcid.missing-authorizations-message' | translate}}
|
{{'person.page.orcid.missing-authorizations-message' | translate}}
|
||||||
<ul>
|
<ul>
|
||||||
<li *ngFor="let auth of (getMissingOrcidAuthorizations() | async)" data-test="missingOrcidAuthorization">
|
<li *ngFor="let auth of profileAuthorizationScopes$ | async" data-test="missingOrcidAuthorization">
|
||||||
{{getAuthorizationDescription(auth) | translate }}
|
{{getAuthorizationDescription(auth) | translate }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -41,11 +41,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ds-alert *ngIf="(onlyAdminCanDisconnectProfileFromOrcid() | async) && (ownerCanDisconnectProfileFromOrcid() | async) !== true"
|
<ds-alert *ngIf="(onlyAdminCanDisconnectProfileFromOrcid$ | async) && (ownerCanDisconnectProfileFromOrcid$ | async) !== true"
|
||||||
[type]="'alert-warning'" data-test="unlinkOnlyAdmin">
|
[type]="AlertType.Warning" data-test="unlinkOnlyAdmin">
|
||||||
{{ 'person.page.orcid.remove-orcid-message' | translate}}
|
{{ 'person.page.orcid.remove-orcid-message' | translate}}
|
||||||
</ds-alert>
|
</ds-alert>
|
||||||
<div class="row" *ngIf="(ownerCanDisconnectProfileFromOrcid() | async)" data-test="unlinkOwner">
|
<div class="row" *ngIf="(ownerCanDisconnectProfileFromOrcid$ | async)" data-test="unlinkOwner">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<button type="submit" class="btn btn-danger float-right" (click)="unlinkOrcid()"
|
<button type="submit" class="btn btn-danger float-right" (click)="unlinkOrcid()"
|
||||||
[dsBtnDisabled]="(unlinkProcessing | async)">
|
[dsBtnDisabled]="(unlinkProcessing | async)">
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
<span *ngIf="(unlinkProcessing | async)"><i
|
<span *ngIf="(unlinkProcessing | async)"><i
|
||||||
class='fas fa-circle-notch fa-spin'></i> {{'person.page.orcid.unlink.processing' | translate}}</span>
|
class='fas fa-circle-notch fa-spin'></i> {{'person.page.orcid.unlink.processing' | translate}}</span>
|
||||||
</button>
|
</button>
|
||||||
<button *ngIf="(hasMissingOrcidAuthorizations() | async)" type="submit"
|
<button *ngIf="(hasMissingOrcidAuthorizations$ | async)" type="submit"
|
||||||
class="btn btn-primary float-right" (click)="linkOrcid()">
|
class="btn btn-primary float-right" (click)="linkOrcid()">
|
||||||
<span><i class="fas fa-check"></i> {{ 'person.page.orcid.grant-authorizations' | translate }}</span>
|
<span><i class="fas fa-check"></i> {{ 'person.page.orcid.grant-authorizations' | translate }}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-2"><img alt="orcid-logo" src="../../../../assets/images/orcid.logo.icon.svg"/></div>
|
<div class="col-2"><img alt="orcid-logo" src="../../../../assets/images/orcid.logo.icon.svg"/></div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<ds-alert [type]="'alert-info'">{{ getOrcidNotLinkedMessage() | async }}</ds-alert>
|
<ds-alert [type]="AlertType.Info">{{ getOrcidNotLinkedMessage() }}</ds-alert>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@@ -34,6 +34,7 @@ import {
|
|||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
||||||
import { AlertComponent } from '../../../shared/alert/alert.component';
|
import { AlertComponent } from '../../../shared/alert/alert.component';
|
||||||
|
import { AlertType } from '../../../shared/alert/alert-type';
|
||||||
import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive';
|
import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { createFailedRemoteDataObjectFromError$ } from '../../../shared/remote-data.utils';
|
import { createFailedRemoteDataObjectFromError$ } from '../../../shared/remote-data.utils';
|
||||||
@@ -62,43 +63,49 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
|
|||||||
/**
|
/**
|
||||||
* The list of exposed orcid authorization scopes for the orcid profile
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* 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
|
* An event emitted when orcid profile is unliked successfully
|
||||||
*/
|
*/
|
||||||
@Output() unlink: EventEmitter<void> = new EventEmitter<void>();
|
@Output() unlink: EventEmitter<void> = new EventEmitter<void>();
|
||||||
|
|
||||||
|
readonly AlertType = AlertType;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private orcidAuthService: OrcidAuthService,
|
private orcidAuthService: OrcidAuthService,
|
||||||
private translateService: TranslateService,
|
private translateService: TranslateService,
|
||||||
@@ -112,6 +119,8 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
|
|||||||
this.orcidAuthorizationScopes.next(scopes);
|
this.orcidAuthorizationScopes.next(scopes);
|
||||||
this.initOrcidAuthSettings();
|
this.initOrcidAuthSettings();
|
||||||
});
|
});
|
||||||
|
this.hasOrcidAuthorizations$ = this.hasOrcidAuthorizations();
|
||||||
|
this.hasMissingOrcidAuthorizations$ = this.hasMissingOrcidAuthorizations();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
@@ -124,18 +133,11 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
|
|||||||
* Check if the list of exposed orcid authorization scopes for the orcid profile has values
|
* Check if the list of exposed orcid authorization scopes for the orcid profile has values
|
||||||
*/
|
*/
|
||||||
hasOrcidAuthorizations(): Observable<boolean> {
|
hasOrcidAuthorizations(): Observable<boolean> {
|
||||||
return this.profileAuthorizationScopes.asObservable().pipe(
|
return this.profileAuthorizationScopes$.pipe(
|
||||||
map((scopes: string[]) => scopes.length > 0),
|
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
|
* Check if the list of exposed orcid authorization scopes for the orcid profile has values
|
||||||
*/
|
*/
|
||||||
@@ -145,26 +147,12 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
getOrcidNotLinkedMessage(): string {
|
||||||
* 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> {
|
|
||||||
const orcid = this.item.firstMetadataValue('person.identifier.orcid');
|
const orcid = this.item.firstMetadataValue('person.identifier.orcid');
|
||||||
if (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 {
|
} else {
|
||||||
return this.translateService.get('person.page.orcid.no-orcid-message');
|
return this.translateService.instant('person.page.orcid.no-orcid-message');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,13 +165,6 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
|
|||||||
return 'person.page.orcid.scope.' + scope.substring(1).replace('/', '-');
|
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
|
* Return a boolean representing if owner can disconnect orcid profile
|
||||||
*/
|
*/
|
||||||
@@ -249,7 +230,7 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setOrcidAuthorizationsFromItem(): void {
|
private setOrcidAuthorizationsFromItem(): void {
|
||||||
this.profileAuthorizationScopes.next(this.orcidAuthService.getOrcidAuthorizationScopesByItem(this.item));
|
this.profileAuthorizationScopes$.next(this.orcidAuthService.getOrcidAuthorizationScopesByItem(this.item));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,13 +3,13 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>{{ 'person.orcid.registry.queue' | translate }}</h2>
|
<h2>{{ 'person.orcid.registry.queue' | translate }}</h2>
|
||||||
|
|
||||||
<ds-alert *ngIf="(processing$ | async) !== true && (getList() | async)?.payload?.totalElements === 0"
|
<ds-alert *ngIf="(processing$ | async) !== true && (list$ | async)?.payload?.totalElements === 0"
|
||||||
[type]="AlertTypeEnum.Info">
|
[type]="AlertTypeEnum.Info">
|
||||||
{{ 'person.page.orcid.sync-queue.empty-message' | translate}}
|
{{ 'person.page.orcid.sync-queue.empty-message' | translate}}
|
||||||
</ds-alert>
|
</ds-alert>
|
||||||
<ds-pagination *ngIf="(processing$ | async) !== true && (getList() | async)?.payload?.totalElements > 0"
|
<ds-pagination *ngIf="(processing$ | async) !== true && (list$ | async)?.payload?.totalElements > 0"
|
||||||
[paginationOptions]="paginationOptions"
|
[paginationOptions]="paginationOptions"
|
||||||
[collectionSize]="(getList() | async)?.payload?.totalElements"
|
[collectionSize]="(list$ | async)?.payload?.totalElements"
|
||||||
[retainScrollPosition]="false" [hideGear]="true" (paginationChange)="updateList()">
|
[retainScrollPosition]="false" [hideGear]="true" (paginationChange)="updateList()">
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let entry of (getList() | async)?.payload?.page" data-test="orcidQueueElementRow">
|
<tr *ngFor="let entry of (list$ | async)?.payload?.page" data-test="orcidQueueElementRow">
|
||||||
<td style="width: 15%" class="text-center align-middle">
|
<td style="width: 15%" class="text-center align-middle">
|
||||||
<i [ngClass]="getIconClass(entry)" [ngbTooltip]="getIconTooltip(entry) | translate"
|
<i [ngClass]="getIconClass(entry)" [ngbTooltip]="getIconTooltip(entry) | translate"
|
||||||
class="fa-2x" aria-hidden="true"></i>
|
class="fa-2x" aria-hidden="true"></i>
|
||||||
|
@@ -80,13 +80,12 @@ export class OrcidQueueComponent implements OnInit, OnDestroy, OnChanges {
|
|||||||
/**
|
/**
|
||||||
* A list of orcid queue records
|
* 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
|
* The AlertType enumeration
|
||||||
* @type {AlertType}
|
|
||||||
*/
|
*/
|
||||||
AlertTypeEnum = AlertType;
|
readonly AlertTypeEnum = AlertType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array to track all subscriptions and unsubscribe them onDestroy
|
* 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
|
* Return the icon class for the queue object type
|
||||||
*
|
*
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
<ds-qa-event-notification [item]="item"></ds-qa-event-notification>
|
<ds-qa-event-notification [item]="item"></ds-qa-event-notification>
|
||||||
<ds-notify-requests-status [itemUuid]="item.uuid"></ds-notify-requests-status>
|
<ds-notify-requests-status [itemUuid]="item.uuid"></ds-notify-requests-status>
|
||||||
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
|
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
|
||||||
<ds-view-tracker [object]="item"></ds-view-tracker>
|
|
||||||
<ds-listable-object-component-loader *ngIf="!item.isWithdrawn || (isAdmin$|async)" [object]="item" [viewMode]="viewMode"></ds-listable-object-component-loader>
|
<ds-listable-object-component-loader *ngIf="!item.isWithdrawn || (isAdmin$|async)" [object]="item" [viewMode]="viewMode"></ds-listable-object-component-loader>
|
||||||
<ds-item-versions class="mt-2" [item]="item" [displayActions]="false"></ds-item-versions>
|
<ds-item-versions class="mt-2" [item]="item" [displayActions]="false"></ds-item-versions>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -44,7 +44,7 @@ import {
|
|||||||
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
|
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
|
||||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||||
import { VarDirective } from '../../shared/utils/var.directive';
|
import { VarDirective } from '../../shared/utils/var.directive';
|
||||||
import { ViewTrackerComponent } from '../../statistics/angulartics/dspace/view-tracker.component';
|
import { ViewTrackerResolverService } from '../../statistics/angulartics/dspace/view-tracker-resolver.service';
|
||||||
import { ThemedItemAlertsComponent } from '../alerts/themed-item-alerts.component';
|
import { ThemedItemAlertsComponent } from '../alerts/themed-item-alerts.component';
|
||||||
import { ItemVersionsComponent } from '../versions/item-versions.component';
|
import { ItemVersionsComponent } from '../versions/item-versions.component';
|
||||||
import { ItemVersionsNoticeComponent } from '../versions/notice/item-versions-notice.component';
|
import { ItemVersionsNoticeComponent } from '../versions/notice/item-versions-notice.component';
|
||||||
@@ -142,7 +142,7 @@ describe('ItemPageComponent', () => {
|
|||||||
remove: { imports: [
|
remove: { imports: [
|
||||||
ThemedItemAlertsComponent,
|
ThemedItemAlertsComponent,
|
||||||
ItemVersionsNoticeComponent,
|
ItemVersionsNoticeComponent,
|
||||||
ViewTrackerComponent,
|
ViewTrackerResolverService,
|
||||||
ListableObjectComponentLoaderComponent,
|
ListableObjectComponentLoaderComponent,
|
||||||
ItemVersionsComponent,
|
ItemVersionsComponent,
|
||||||
ErrorComponent,
|
ErrorComponent,
|
||||||
|
@@ -48,7 +48,6 @@ import { ErrorComponent } from '../../shared/error/error.component';
|
|||||||
import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component';
|
import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component';
|
||||||
import { ListableObjectComponentLoaderComponent } from '../../shared/object-collection/shared/listable-object/listable-object-component-loader.component';
|
import { ListableObjectComponentLoaderComponent } from '../../shared/object-collection/shared/listable-object/listable-object-component-loader.component';
|
||||||
import { VarDirective } from '../../shared/utils/var.directive';
|
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 { ThemedItemAlertsComponent } from '../alerts/themed-item-alerts.component';
|
||||||
import { getItemPageRoute } from '../item-page-routing-paths';
|
import { getItemPageRoute } from '../item-page-routing-paths';
|
||||||
import { ItemVersionsComponent } from '../versions/item-versions.component';
|
import { ItemVersionsComponent } from '../versions/item-versions.component';
|
||||||
@@ -72,7 +71,6 @@ import { QaEventNotificationComponent } from './qa-event-notification/qa-event-n
|
|||||||
VarDirective,
|
VarDirective,
|
||||||
ThemedItemAlertsComponent,
|
ThemedItemAlertsComponent,
|
||||||
ItemVersionsNoticeComponent,
|
ItemVersionsNoticeComponent,
|
||||||
ViewTrackerComponent,
|
|
||||||
ListableObjectComponentLoaderComponent,
|
ListableObjectComponentLoaderComponent,
|
||||||
ItemVersionsComponent,
|
ItemVersionsComponent,
|
||||||
ErrorComponent,
|
ErrorComponent,
|
||||||
@@ -168,7 +166,8 @@ export class ItemPageComponent implements OnInit, OnDestroy {
|
|||||||
this.signpostingLinks = signpostingLinks;
|
this.signpostingLinks = signpostingLinks;
|
||||||
|
|
||||||
signpostingLinks.forEach((link: SignpostingLink) => {
|
signpostingLinks.forEach((link: SignpostingLink) => {
|
||||||
links = links + (isNotEmpty(links) ? ', ' : '') + `<${link.href}> ; rel="${link.rel}"` + (isNotEmpty(link.type) ? ` ; type="${link.type}" ` : ' ');
|
links = links + (isNotEmpty(links) ? ', ' : '') + `<${link.href}> ; rel="${link.rel}"` + (isNotEmpty(link.type) ? ` ; type="${link.type}" ` : ' ')
|
||||||
|
+ (isNotEmpty(link.profile) ? ` ; profile="${link.profile}" ` : '');
|
||||||
let tag: LinkDefinition = {
|
let tag: LinkDefinition = {
|
||||||
href: link.href,
|
href: link.href,
|
||||||
rel: link.rel,
|
rel: link.rel,
|
||||||
@@ -178,6 +177,11 @@ export class ItemPageComponent implements OnInit, OnDestroy {
|
|||||||
type: link.type,
|
type: link.type,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (isNotEmpty(link.profile)) {
|
||||||
|
tag = Object.assign(tag, {
|
||||||
|
profile: link.profile,
|
||||||
|
});
|
||||||
|
}
|
||||||
this.linkHeadService.addTag(tag);
|
this.linkHeadService.addTag(tag);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -101,7 +101,7 @@
|
|||||||
[label]="'item.page.referenced'">
|
[label]="'item.page.referenced'">
|
||||||
</ds-item-page-uri-field>
|
</ds-item-page-uri-field>
|
||||||
<div>
|
<div>
|
||||||
<a class="btn btn-outline-primary" role="button" [routerLink]="[itemPageRoute + '/full']">
|
<a class="btn btn-outline-primary" role="button" [routerLink]="[itemPageRoute + '/full']" tabindex="0">
|
||||||
<i class="fas fa-info-circle"></i> {{"item.page.link.full" | translate}}
|
<i class="fas fa-info-circle"></i> {{"item.page.link.full" | translate}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -89,7 +89,7 @@
|
|||||||
<ds-item-page-cc-license-field [item]="object" [variant]="'full'">
|
<ds-item-page-cc-license-field [item]="object" [variant]="'full'">
|
||||||
</ds-item-page-cc-license-field>
|
</ds-item-page-cc-license-field>
|
||||||
<div>
|
<div>
|
||||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="button">
|
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="button" tabindex="0">
|
||||||
<i class="fas fa-info-circle"></i> {{"item.page.link.full" | translate}}
|
<i class="fas fa-info-circle"></i> {{"item.page.link.full" | translate}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -2,4 +2,5 @@ export enum RequestStatusEnum {
|
|||||||
ACCEPTED = 'ACCEPTED',
|
ACCEPTED = 'ACCEPTED',
|
||||||
REJECTED = 'REJECTED',
|
REJECTED = 'REJECTED',
|
||||||
REQUESTED = 'REQUESTED',
|
REQUESTED = 'REQUESTED',
|
||||||
|
TENTATIVE_REJECT = 'TENTATIVE_REJECT',
|
||||||
}
|
}
|
||||||
|
@@ -74,6 +74,13 @@ export class RequestStatusAlertBoxComponent implements OnInit {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case RequestStatusEnum.TENTATIVE_REJECT:
|
||||||
|
this.displayOptions = {
|
||||||
|
alertType: 'alert-warning',
|
||||||
|
text: 'request-status-alert-box.tentative_rejected',
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
case RequestStatusEnum.REQUESTED:
|
case RequestStatusEnum.REQUESTED:
|
||||||
this.displayOptions = {
|
this.displayOptions = {
|
||||||
alertType: 'alert-warning',
|
alertType: 'alert-warning',
|
||||||
|
@@ -3,6 +3,6 @@
|
|||||||
<h2><small><em>{{missingItem}}</em></small></h2>
|
<h2><small><em>{{missingItem}}</em></small></h2>
|
||||||
<br />
|
<br />
|
||||||
<p class="text-center">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<nav [ngClass]="{'open': (menuCollapsed | async) !== true}"
|
<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 pt-md-0 pt-3 navbar-container" role="navigation"
|
class="navbar navbar-light navbar-expand-md px-md-0 pt-md-0 pt-3 navbar-container" role="navigation"
|
||||||
[attr.aria-label]="'nav.main.description' | translate" id="main-navbar">
|
[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 -->
|
<!-- TODO remove navbar-container class when https://github.com/twbs/bootstrap/issues/24726 is fixed -->
|
||||||
|
@@ -9,9 +9,9 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { getTestScheduler } from 'jasmine-marbles';
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
|
||||||
import { ItemSearchResultListElementComponent } from '../../../themes/custom/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component';
|
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
import { mockSuggestionPublicationOne } from '../../shared/mocks/publication-claim.mock';
|
import { mockSuggestionPublicationOne } from '../../shared/mocks/publication-claim.mock';
|
||||||
|
import { ItemSearchResultListElementComponent } from '../../shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component';
|
||||||
import { SuggestionActionsComponent } from '../suggestion-actions/suggestion-actions.component';
|
import { SuggestionActionsComponent } from '../suggestion-actions/suggestion-actions.component';
|
||||||
import { SuggestionEvidencesComponent } from './suggestion-evidences/suggestion-evidences.component';
|
import { SuggestionEvidencesComponent } from './suggestion-evidences/suggestion-evidences.component';
|
||||||
import { SuggestionListElementComponent } from './suggestion-list-element.component';
|
import { SuggestionListElementComponent } from './suggestion-list-element.component';
|
||||||
|
@@ -6,14 +6,13 @@ import {
|
|||||||
OnInit,
|
OnInit,
|
||||||
Output,
|
Output,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { Suggestion } from 'src/app/core/notifications/suggestions/models/suggestion.model';
|
import { Suggestion } from 'src/app/core/notifications/suggestions/models/suggestion.model';
|
||||||
|
|
||||||
import { ItemSearchResultListElementComponent } from '../../../themes/custom/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component';
|
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
import { fadeIn } from '../../shared/animations/fade';
|
import { fadeIn } from '../../shared/animations/fade';
|
||||||
import { isNotEmpty } from '../../shared/empty.util';
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
|
import { ItemSearchResultListElementComponent } from '../../shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component';
|
||||||
import { SuggestionActionsComponent } from '../suggestion-actions/suggestion-actions.component';
|
import { SuggestionActionsComponent } from '../suggestion-actions/suggestion-actions.component';
|
||||||
import { SuggestionApproveAndImport } from './suggestion-approve-and-import';
|
import { SuggestionApproveAndImport } from './suggestion-approve-and-import';
|
||||||
import { SuggestionEvidencesComponent } from './suggestion-evidences/suggestion-evidences.component';
|
import { SuggestionEvidencesComponent } from './suggestion-evidences/suggestion-evidences.component';
|
||||||
@@ -62,13 +61,6 @@ export class SuggestionListElementComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
@Output() selected = new EventEmitter<boolean>();
|
@Output() selected = new EventEmitter<boolean>();
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize instance variables
|
|
||||||
*
|
|
||||||
* @param {NgbModal} modalService
|
|
||||||
*/
|
|
||||||
constructor(private modalService: NgbModal) { }
|
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.listableObject = {
|
this.listableObject = {
|
||||||
indexableObject: Object.assign(new Item(), { id: this.object.id, metadata: this.object.metadata }),
|
indexableObject: Object.assign(new Item(), { id: this.object.id, metadata: this.object.metadata }),
|
||||||
|
@@ -5,6 +5,6 @@
|
|||||||
<p>{{"error-page." + code | translate}}</p>
|
<p>{{"error-page." + code | translate}}</p>
|
||||||
<br/>
|
<br/>
|
||||||
<p class="text-center">
|
<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="button" tabindex="0">{{ status + ".link.home-page" | translate}}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -5,6 +5,6 @@
|
|||||||
<p>{{"404.help" | translate}}</p>
|
<p>{{"404.help" | translate}}</p>
|
||||||
<br/>
|
<br/>
|
||||||
<p class="text-center">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user