Merge remote-tracking branch 'lyrasis/main' into task/main/CST-18964

# Conflicts:
#	package-lock.json
#	package.json
This commit is contained in:
Andrea Barbasso
2025-03-11 10:31:49 +01:00
400 changed files with 11699 additions and 8384 deletions

View File

@@ -35,6 +35,19 @@ 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.
enableBrowseComponent: false enableBrowseComponent: false
# Enable state transfer from the server-side application to the client-side application.
# Defaults to true.
# Note: When using an external application cache layer, it's recommended not to transfer the state to avoid caching it.
# Disabling it ensures that dynamic state information is not inadvertently cached, which can improve security and
# ensure that users always use the most up-to-date state.
transferState: true
# When a different REST base URL is used for the server-side application, the generated state contains references to
# REST resources with the internal URL configured. By default, these internal URLs are replaced with public URLs.
# Disable this setting to avoid URL replacement during SSR. In this the state is not transferred to avoid security issues.
replaceRestUrl: true
# Enable request performance profiling data collection and printing the results in the server console.
# Defaults to false. Enabling in production is NOT recommended
#enablePerformanceProfiler: false
# The REST API server settings # The REST API server settings
# NOTE: these settings define which (publicly available) REST API to use. They are usually # NOTE: these settings define which (publicly available) REST API to use. They are usually
@@ -45,6 +58,9 @@ rest:
port: 443 port: 443
# NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript # NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
nameSpace: /server nameSpace: /server
# Provide a different REST url to be used during SSR execution. It must contain the whole url including protocol, server port and
# server namespace (uncomment to use it).
#ssrBaseUrl: http://localhost:8080/server
# Caching settings # Caching settings
cache: cache:

View File

@@ -12,6 +12,13 @@ describe('Community List Page', () => {
cy.get('[data-test="expand-button"]').click({ multiple: true }); cy.get('[data-test="expand-button"]').click({ multiple: true });
// Analyze <ds-community-list-page> for accessibility issues // Analyze <ds-community-list-page> for accessibility issues
testA11y('ds-community-list-page'); testA11y('ds-community-list-page', {
rules: {
// When expanding a cdk node on the community-list page, the 'aria-posinset' property becomes 0.
// 0 is not a valid value for 'aria-posinset' so the test fails.
// see https://github.com/DSpace/dspace-angular/issues/4068
'aria-valid-attr-value': { enabled: false },
},
});
}); });
}); });

View File

@@ -217,7 +217,7 @@ describe('New Submission page', () => {
}); });
// Close popup window // Close popup window
cy.get('ds-dynamic-lookup-relation-modal button.close').click(); cy.get('ds-dynamic-lookup-relation-modal button.btn-close').click();
// Back on the form, click the discard button to remove new submission // Back on the form, click the discard button to remove new submission
// Clicking it will display a confirmation, which we will confirm with another click // Clicking it will display a confirmation, which we will confirm with another click

11608
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -57,70 +57,66 @@
"private": true, "private": true,
"overrides": { "overrides": {
"@kolkov/ngx-gallery": { "@kolkov/ngx-gallery": {
"@angular/animations": "^17.3.11", "@angular/animations": "^18.2.12",
"@angular/common": "^17.3.11", "@angular/common": "^18.2.12",
"@angular/core": "^17.3.11" "@angular/core": "^18.2.12"
}, },
"@ng-bootstrap/ng-bootstrap": { "@ng-bootstrap/ng-bootstrap": {
"@angular/common": "^17.3.11", "@angular/common": "^18.2.12",
"@angular/core": "^17.3.11", "@angular/core": "^18.2.12",
"@angular/forms": "^17.3.11", "@angular/forms": "^18.2.12",
"@angular/localize": "^17.3.11" "@angular/localize": "^18.2.12"
}, },
"@ng-dynamic-forms/core": { "@ng-dynamic-forms/core": {
"@angular/common": "^17.3.11", "@angular/common": "^18.2.12",
"@angular/core": "^17.3.11", "@angular/core": "^18.2.12",
"@angular/forms": "^17.3.11" "@angular/forms": "^18.2.12"
}, },
"@ng-dynamic-forms/ui-ng-bootstrap": { "@ng-dynamic-forms/ui-ng-bootstrap": {
"ngx-mask": "14.2.4" "ngx-mask": "14.2.4",
}, "@ng-bootstrap/ng-bootstrap": "^12.0.0",
"@ngtools/webpack": { "bootstrap": "^5.3"
"@angular/compiler-cli": "^17.3.11",
"typescript": "~5.4.5"
}, },
"@nicky-lenaers/ngx-scroll-to": { "@nicky-lenaers/ngx-scroll-to": {
"@angular/common": "^17.3.11", "@angular/common": "^18.2.12",
"@angular/core": "^17.3.11" "@angular/core": "^18.2.12"
}, },
"eslint-plugin-unused-imports": { "eslint-plugin-unused-imports": {
"@typescript-eslint/eslint-plugin": "^7.2.0" "@typescript-eslint/eslint-plugin": "^7.2.0"
}, },
"ng2-file-upload": {
"@angular/common": "^17.3.11",
"@angular/core": "^17.3.11"
},
"ngx-infinite-scroll": { "ngx-infinite-scroll": {
"@angular/common": "^17.3.11", "@angular/common": "^18.2.12",
"@angular/core": "^17.3.11" "@angular/core": "^18.2.12"
} },
"notistack": "3.0.1"
}, },
"dependencies": { "dependencies": {
"@angular/animations": "^17.3.12", "@angular/animations": "^18.2.12",
"@angular/cdk": "^17.3.10", "@angular/cdk": "^18.2.12",
"@angular/common": "^17.3.12", "@angular/common": "^18.2.12",
"@angular/compiler": "^17.3.12", "@angular/compiler": "^18.2.12",
"@angular/core": "^17.3.12", "@angular/core": "^18.2.12",
"@angular/forms": "^17.3.12", "@angular/forms": "^18.2.12",
"@angular/localize": "^17.3.12", "@angular/localize": "^18.2.12",
"@angular/platform-browser": "^17.3.12", "@angular/platform-browser": "^18.2.12",
"@angular/platform-browser-dynamic": "^17.3.12", "@angular/platform-browser-dynamic": "^18.2.12",
"@angular/platform-server": "^17.3.12", "@angular/platform-server": "^18.2.12",
"@angular/router": "^17.3.12", "@angular/router": "^18.2.12",
"@angular/ssr": "^17.3.11", "@angular/ssr": "^18.2.12",
"@babel/runtime": "7.26.0", "@babel/runtime": "7.26.0",
"@kolkov/ngx-gallery": "^2.0.1", "@kolkov/ngx-gallery": "^2.0.1",
"@ng-bootstrap/ng-bootstrap": "^11.0.0", "@ng-bootstrap/ng-bootstrap": "^12.0.0",
"@ng-dynamic-forms/core": "^16.0.0", "@ng-dynamic-forms/core": "^16.0.0",
"@ng-dynamic-forms/ui-ng-bootstrap": "^16.0.0", "@ng-dynamic-forms/ui-ng-bootstrap": "^16.0.0",
"@ngrx/effects": "^17.1.1", "@ngrx/effects": "^18.1.1",
"@ngrx/router-store": "^17.1.1", "@ngrx/operators": "^18.0.0",
"@ngrx/store": "^17.1.1", "@ngrx/router-store": "^18.1.1",
"@ngx-translate/core": "^14.0.0", "@ngrx/store": "^18.1.1",
"@ngx-translate/core": "^16.0.3",
"@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.7.9",
"bootstrap": "^4.6.1", "bootstrap": "^5.3",
"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",
@@ -137,7 +133,7 @@
"filesize": "^6.1.0", "filesize": "^6.1.0",
"http-proxy-middleware": "^2.0.7", "http-proxy-middleware": "^2.0.7",
"http-terminator": "^3.2.0", "http-terminator": "^3.2.0",
"isbot": "^5.1.21", "isbot": "^5.1.22",
"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",
@@ -150,13 +146,13 @@
"mirador-dl-plugin": "^0.13.0", "mirador-dl-plugin": "^0.13.0",
"mirador-share-plugin": "^0.16.0", "mirador-share-plugin": "^0.16.0",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"ng2-file-upload": "5.0.0", "ng2-file-upload": "7.0.1",
"ng2-nouislider": "^2.0.0", "ng2-nouislider": "^2.0.0",
"ngx-infinite-scroll": "^16.0.0", "ngx-infinite-scroll": "^18.0.0",
"ngx-matomo-client": "^6.4.1", "ngx-matomo-client": "^6.4.1",
"ngx-pagination": "6.0.3", "ngx-pagination": "6.0.3",
"ngx-skeleton-loader": "^9.0.0", "ngx-skeleton-loader": "^9.0.0",
"ngx-ui-switch": "^14.1.0", "ngx-ui-switch": "^15.0.0",
"nouislider": "^15.7.1", "nouislider": "^15.7.1",
"orejime": "^2.3.1", "orejime": "^2.3.1",
"pem": "1.14.8", "pem": "1.14.8",
@@ -166,29 +162,29 @@
"zone.js": "~0.14.10" "zone.js": "~0.14.10"
}, },
"devDependencies": { "devDependencies": {
"@angular-builders/custom-webpack": "~17.0.2", "@angular-builders/custom-webpack": "~18.0.0",
"@angular-devkit/build-angular": "^17.3.11", "@angular-devkit/build-angular": "^18.2.12",
"@angular-eslint/builder": "^17.5.3", "@angular-eslint/builder": "^18.4.1",
"@angular-eslint/bundled-angular-compiler": "^17.5.3", "@angular-eslint/bundled-angular-compiler": "^18.4.1",
"@angular-eslint/eslint-plugin": "^17.5.3", "@angular-eslint/eslint-plugin": "^18.4.1",
"@angular-eslint/eslint-plugin-template": "^17.5.3", "@angular-eslint/eslint-plugin-template": "^18.4.1",
"@angular-eslint/schematics": "^17.5.3", "@angular-eslint/schematics": "^18.4.1",
"@angular-eslint/template-parser": "^17.5.3", "@angular-eslint/template-parser": "^18.4.1",
"@angular-eslint/utils": "^17.5.3", "@angular-eslint/utils": "^18.4.1",
"@angular/cli": "^17.3.11", "@angular/cli": "^18.2.12",
"@angular/compiler-cli": "^17.3.11", "@angular/compiler-cli": "^18.2.12",
"@angular/language-service": "^17.3.12", "@angular/language-service": "^18.2.12",
"@cypress/schematic": "^1.5.0", "@cypress/schematic": "^1.5.0",
"@fortawesome/fontawesome-free": "^6.7.2", "@fortawesome/fontawesome-free": "^6.7.2",
"@ngrx/store-devtools": "^17.1.1", "@ngrx/store-devtools": "^18.1.1",
"@ngtools/webpack": "^16.2.16", "@ngtools/webpack": "^18.2.12",
"@types/deep-freeze": "0.1.5", "@types/deep-freeze": "0.1.5",
"@types/ejs": "^3.1.2", "@types/ejs": "^3.1.2",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@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.14", "@types/lodash": "^4.17.15",
"@types/node": "^14.14.9", "@types/node": "^14.14.9",
"@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0", "@typescript-eslint/parser": "^7.18.0",
@@ -199,7 +195,7 @@
"copy-webpack-plugin": "^6.4.1", "copy-webpack-plugin": "^6.4.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"cypress": "^13.17.0", "cypress": "^13.17.0",
"cypress-axe": "^1.5.0", "cypress-axe": "^1.6.0",
"deep-freeze": "0.0.1", "deep-freeze": "0.0.1",
"eslint": "^8.39.0", "eslint": "^8.39.0",
"eslint-plugin-deprecation": "^1.4.1", "eslint-plugin-deprecation": "^1.4.1",
@@ -208,7 +204,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.18.2", "eslint-plugin-jsonc": "^2.19.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",
@@ -231,7 +227,7 @@
"postcss-loader": "^4.0.3", "postcss-loader": "^4.0.3",
"postcss-preset-env": "^7.4.2", "postcss-preset-env": "^7.4.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"sass": "~1.83.4", "sass": "~1.84.0",
"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",

View File

@@ -20,10 +20,10 @@ import 'reflect-metadata';
/* eslint-disable import/no-namespace */ /* eslint-disable import/no-namespace */
import * as morgan from 'morgan'; import * as morgan from 'morgan';
import * as express from 'express'; import express from 'express';
import * as ejs from 'ejs'; import * as ejs from 'ejs';
import * as compression from 'compression'; import * as compression from 'compression';
import * as expressStaticGzip from 'express-static-gzip'; import expressStaticGzip from 'express-static-gzip';
/* eslint-enable import/no-namespace */ /* eslint-enable import/no-namespace */
import axios from 'axios'; import axios from 'axios';
import LRU from 'lru-cache'; import LRU from 'lru-cache';
@@ -81,6 +81,9 @@ let anonymousCache: LRU<string, any>;
// extend environment with app config for server // extend environment with app config for server
extendEnvironmentWithAppConfig(environment, appConfig); extendEnvironmentWithAppConfig(environment, appConfig);
// The REST server base URL
const REST_BASE_URL = environment.rest.ssrBaseUrl || environment.rest.baseUrl;
// The Express app is exported so that it can be used by serverless Functions. // The Express app is exported so that it can be used by serverless Functions.
export function app() { export function app() {
@@ -156,7 +159,7 @@ export function app() {
* Proxy the sitemaps * Proxy the sitemaps
*/ */
router.use('/sitemap**', createProxyMiddleware({ router.use('/sitemap**', createProxyMiddleware({
target: `${environment.rest.baseUrl}/sitemaps`, target: `${REST_BASE_URL}/sitemaps`,
pathRewrite: path => path.replace(environment.ui.nameSpace, '/'), pathRewrite: path => path.replace(environment.ui.nameSpace, '/'),
changeOrigin: true, changeOrigin: true,
})); }));
@@ -165,7 +168,7 @@ export function app() {
* Proxy the linksets * Proxy the linksets
*/ */
router.use('/signposting**', createProxyMiddleware({ router.use('/signposting**', createProxyMiddleware({
target: `${environment.rest.baseUrl}`, target: `${REST_BASE_URL}`,
pathRewrite: path => path.replace(environment.ui.nameSpace, '/'), pathRewrite: path => path.replace(environment.ui.nameSpace, '/'),
changeOrigin: true, changeOrigin: true,
})); }));
@@ -266,6 +269,11 @@ function serverSideRender(req, res, next, sendToUser: boolean = true) {
}) })
.then((html) => { .then((html) => {
if (hasValue(html)) { if (hasValue(html)) {
// Replace REST URL with UI URL
if (environment.ssr.replaceRestUrl && REST_BASE_URL !== environment.rest.baseUrl) {
html = html.replace(new RegExp(REST_BASE_URL, 'g'), environment.rest.baseUrl);
}
// save server side rendered page to cache (if any are enabled) // save server side rendered page to cache (if any are enabled)
saveToCache(req, html); saveToCache(req, html);
if (sendToUser) { if (sendToUser) {
@@ -623,7 +631,7 @@ function start() {
* The callback function to serve health check requests * The callback function to serve health check requests
*/ */
function healthCheck(req, res) { function healthCheck(req, res) {
const baseUrl = `${environment.rest.baseUrl}${environment.actuators.endpointPath}`; const baseUrl = `${REST_BASE_URL}${environment.actuators.endpointPath}`;
axios.get(baseUrl) axios.get(baseUrl)
.then((response) => { .then((response) => {
res.status(response.status).send(response.data); res.status(response.status).send(response.data);

View File

@@ -1,19 +1,13 @@
<ngb-accordion #acc="ngbAccordion" [activeIds]="'browse'"> <ngb-accordion #acc="ngbAccordion" [activeIds]="'browse'">
<ngb-panel [id]="'browse'"> <ngb-panel [id]="'browse'">
<ng-template ngbPanelHeader> <ng-template ngbPanelTitle>
<div class="w-100 d-flex gap-3 justify-content-between collapse-toggle" ngbPanelToggle (click)="acc.toggle('browse')" <div class="w-100 d-flex gap-3 justify-content-between collapse-toggle" (click)="acc.toggle('browse')"
data-test="browse"> data-test="browse">
<button type="button" class="btn btn-link p-0" (click)="$event.preventDefault()" <button type="button" class="btn btn-link p-0" (click)="$event.preventDefault()"
[attr.aria-expanded]="acc.isExpanded('browse')" [attr.aria-expanded]="acc.isExpanded('browse')"
aria-controls="bulk-access-browse-panel-content"> aria-controls="bulk-access-browse-panel-content">
{{ 'admin.access-control.bulk-access-browse.header' | translate }} {{ 'admin.access-control.bulk-access-browse.header' | translate }}
</button> </button>
<div class="text-right d-flex gap-2">
<div class="d-flex my-auto">
<span *ngIf="acc.isExpanded('browse')" class="fas fa-chevron-up fa-fw"></span>
<span *ngIf="!acc.isExpanded('browse')" class="fas fa-chevron-down fa-fw"></span>
</div>
</div>
</div> </div>
</ng-template> </ng-template>
<ng-template ngbPanelContent> <ng-template ngbPanelContent>
@@ -22,7 +16,7 @@
<li [ngbNavItem]="'search'" role="presentation"> <li [ngbNavItem]="'search'" role="presentation">
<a ngbNavLink>{{'admin.access-control.bulk-access-browse.search.header' | translate}}</a> <a ngbNavLink>{{'admin.access-control.bulk-access-browse.search.header' | translate}}</a>
<ng-template ngbNavContent> <ng-template ngbNavContent>
<div class="mx-n3"> <div class="bulk-access-search">
<ds-search [configuration]="'administrativeBulkAccess'" <ds-search [configuration]="'administrativeBulkAccess'"
[selectable]="true" [selectable]="true"
[selectionConfig]="{ repeatable: true, listId: listId }" [selectionConfig]="{ repeatable: true, listId: listId }"
@@ -42,7 +36,7 @@
[showPaginator]="false" [showPaginator]="false"
(prev)="pagePrev()" (prev)="pagePrev()"
(next)="pageNext()"> (next)="pageNext()">
<ul *ngIf="(objectsSelected$|async)?.hasSucceeded" class="list-unstyled ml-4"> <ul *ngIf="(objectsSelected$|async)?.hasSucceeded" class="list-unstyled ms-4">
<li *ngFor='let object of (objectsSelected$|async)?.payload?.page | paginate: { itemsPerPage: (paginationOptions$ | async).pageSize, <li *ngFor='let object of (objectsSelected$|async)?.payload?.page | paginate: { itemsPerPage: (paginationOptions$ | async).pageSize,
currentPage: (paginationOptions$ | async).currentPage, totalItems: (objectsSelected$|async)?.payload?.page.length }; let i = index; let last = last ' currentPage: (paginationOptions$ | async).currentPage, totalItems: (objectsSelected$|async)?.payload?.page.length }; let i = index; let last = last '
class="mt-4 mb-4 d-flex" class="mt-4 mb-4 d-flex"

View File

@@ -0,0 +1,4 @@
.bulk-access-search {
margin-right: calc(var(--bs-gutter-x, 1.5rem) / -2);
margin-left: calc(var(--bs-gutter-x, 1.5rem) / -2);
}

View File

@@ -7,7 +7,7 @@
<hr> <hr>
<div class="d-flex justify-content-end"> <div class="d-flex justify-content-end">
<button class="btn btn-outline-primary mr-3" (click)="reset()"> <button class="btn btn-outline-primary me-3" (click)="reset()">
{{ 'access-control-cancel' | translate }} {{ 'access-control-cancel' | translate }}
</button> </button>
<button class="btn btn-primary" [dsBtnDisabled]="!canExport()" (click)="submit()"> <button class="btn btn-primary" [dsBtnDisabled]="!canExport()" (click)="submit()">

View File

@@ -1,17 +1,11 @@
<ngb-accordion #acc="ngbAccordion" [activeIds]="'settings'"> <ngb-accordion #acc="ngbAccordion" [activeIds]="'settings'">
<ngb-panel [id]="'settings'"> <ngb-panel [id]="'settings'">
<ng-template ngbPanelHeader> <ng-template ngbPanelTitle>
<div class="w-100 d-flex gap-3 justify-content-between collapse-toggle" ngbPanelToggle (click)="acc.toggle('settings')" data-test="settings"> <div class="w-100 d-flex gap-3 justify-content-between collapse-toggle" ngbPanelToggle (click)="acc.toggle('settings')" data-test="settings">
<button type="button" class="btn btn-link p-0" (click)="$event.preventDefault()" [attr.aria-expanded]="acc.isExpanded('settings')" <button type="button" class="btn btn-link p-0" (click)="$event.preventDefault()" [attr.aria-expanded]="acc.isExpanded('settings')"
aria-controls="bulk-access-settings-panel-content"> aria-controls="bulk-access-settings-panel-content">
{{ 'admin.access-control.bulk-access-settings.header' | translate }} {{ 'admin.access-control.bulk-access-settings.header' | translate }}
</button> </button>
<div class="text-right d-flex gap-2">
<div class="d-flex my-auto">
<span *ngIf="acc.isExpanded('settings')" class="fas fa-chevron-up fa-fw"></span>
<span *ngIf="!acc.isExpanded('settings')" class="fas fa-chevron-down fa-fw"></span>
</div>
</div>
</div> </div>
</ng-template> </ng-template>
<ng-template ngbPanelContent> <ng-template ngbPanelContent>

View File

@@ -5,10 +5,10 @@
<h1 id="header" class="pb-2">{{labelPrefix + 'head' | translate}}</h1> <h1 id="header" class="pb-2">{{labelPrefix + 'head' | translate}}</h1>
<div> <div>
<button class="mr-auto btn btn-success addEPerson-button" <button class="me-auto btn btn-success addEPerson-button"
[routerLink]="'create'"> [routerLink]="'create'">
<i class="fas fa-plus"></i> <i class="fas fa-plus"></i>
<span class="d-none d-sm-inline ml-1">{{labelPrefix + 'button.add' | translate}}</span> <span class="d-none d-sm-inline ms-1">{{labelPrefix + 'button.add' | translate}}</span>
</button> </button>
</div> </div>
</div> </div>
@@ -18,17 +18,17 @@
</h2> </h2>
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between"> <form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
<div> <div>
<select name="scope" id="scope" formControlName="scope" class="form-control" aria-label="Search scope"> <select name="scope" id="scope" formControlName="scope" class="form-select" aria-label="Search scope">
<option value="metadata">{{labelPrefix + 'search.scope.metadata' | translate}}</option> <option value="metadata">{{labelPrefix + 'search.scope.metadata' | translate}}</option>
<option value="email">{{labelPrefix + 'search.scope.email' | translate}}</option> <option value="email">{{labelPrefix + 'search.scope.email' | translate}}</option>
</select> </select>
</div> </div>
<div class="flex-grow-1 mr-3 ml-3"> <div class="flex-grow-1 me-3 ms-3">
<div class="form-group input-group"> <div class="mb-3 input-group">
<input type="text" name="query" id="query" formControlName="query" <input type="text" name="query" id="query" formControlName="query"
class="form-control" [attr.aria-label]="labelPrefix + 'search.placeholder' | translate" class="form-control" [attr.aria-label]="labelPrefix + 'search.placeholder' | translate"
[placeholder]="(labelPrefix + 'search.placeholder' | translate)"> [placeholder]="(labelPrefix + 'search.placeholder' | translate)">
<span class="input-group-append"> <span class="input-group-append">
<button type="submit" class="search-button btn btn-primary"> <button type="submit" class="search-button btn btn-primary">
<i class="fas fa-search"></i> {{ labelPrefix + 'search.button' | translate }} <i class="fas fa-search"></i> {{ labelPrefix + 'search.button' | translate }}
</button> </button>

View File

@@ -29,7 +29,7 @@
<i class="fa fa-key"></i> {{'admin.access-control.epeople.actions.reset' | translate}} <i class="fa fa-key"></i> {{'admin.access-control.epeople.actions.reset' | translate}}
</button> </button>
</div> </div>
<div *ngIf="canImpersonate$ | async" between class="btn-group"> <div *ngIf="canImpersonate$ | async" between class="btn-group ms-1">
<button *ngIf="!isImpersonated" class="btn btn-primary" type="button" (click)="impersonate()"> <button *ngIf="!isImpersonated" class="btn btn-primary" type="button" (click)="impersonate()">
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.impersonate' | translate}} <i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.impersonate' | translate}}
</button> </button>

View File

@@ -75,8 +75,8 @@
</h3> </h3>
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between"> <form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
<div class="flex-grow-1 mr-3"> <div class="flex-grow-1 me-3">
<div class="form-group input-group mr-3"> <div class="form-group input-group me-3">
<input type="text" name="query" id="query" formControlName="query" <input type="text" name="query" id="query" formControlName="query"
class="form-control" aria-label="Search input"> class="form-control" aria-label="Search input">
<span class="input-group-append"> <span class="input-group-append">

View File

@@ -62,8 +62,8 @@
</h4> </h4>
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between"> <form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
<div class="flex-grow-1 mr-3"> <div class="flex-grow-1 me-3">
<div class="form-group input-group mr-3"> <div class="mb-3 input-group me-3">
<input type="text" name="query" id="query" formControlName="query" <input type="text" name="query" id="query" formControlName="query"
class="form-control" aria-label="Search input"> class="form-control" aria-label="Search input">
<span class="input-group-append"> <span class="input-group-append">
@@ -75,7 +75,7 @@
</div> </div>
</div> </div>
<div> <div>
<button (click)="clearFormAndResetResult();" class="btn btn-secondary float-right"> <button (click)="clearFormAndResetResult();" class="btn btn-secondary float-end">
{{messagePrefix + '.button.see-all' | translate}} {{messagePrefix + '.button.see-all' | translate}}
</button> </button>
</div> </div>

View File

@@ -4,18 +4,18 @@
<div class="d-flex justify-content-between border-bottom mb-3"> <div class="d-flex justify-content-between border-bottom mb-3">
<h1 id="header" class="pb-2">{{messagePrefix + 'head' | translate}}</h1> <h1 id="header" class="pb-2">{{messagePrefix + 'head' | translate}}</h1>
<div> <div>
<button class="mr-auto btn btn-success" <button class="me-auto btn btn-success"
[routerLink]="'create'"> [routerLink]="'create'">
<i class="fas fa-plus"></i> <i class="fas fa-plus"></i>
<span class="d-none d-sm-inline ml-1">{{messagePrefix + 'button.add' | translate}}</span> <span class="d-none d-sm-inline ms-1">{{messagePrefix + 'button.add' | translate}}</span>
</button> </button>
</div> </div>
</div> </div>
<h2 id="search" class="border-bottom pb-2">{{messagePrefix + 'search.head' | translate}}</h2> <h2 id="search" class="border-bottom pb-2">{{messagePrefix + 'search.head' | translate}}</h2>
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between"> <form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
<div class="flex-grow-1 mr-3"> <div class="flex-grow-1 me-3">
<div class="form-group input-group"> <div class="mb-3 input-group">
<input type="text" name="query" id="query" formControlName="query" <input type="text" name="query" id="query" formControlName="query"
class="form-control" [attr.aria-label]="messagePrefix + 'search.placeholder' | translate" class="form-control" [attr.aria-label]="messagePrefix + 'search.placeholder' | translate"
[placeholder]="(messagePrefix + 'search.placeholder' | translate)" > [placeholder]="(messagePrefix + 'search.placeholder' | translate)" >

View File

@@ -8,7 +8,7 @@
<p> <p>
<button class="btn btn-primary" (click)="this.selectCollection();">{{'admin.metadata-import.page.button.select-collection' | translate}}</button> <button class="btn btn-primary" (click)="this.selectCollection();">{{'admin.metadata-import.page.button.select-collection' | translate}}</button>
</p> </p>
<div class="form-group"> <div class="mb-3">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="validateOnly" [(ngModel)]="validateOnly"> <input class="form-check-input" type="checkbox" id="validateOnly" [(ngModel)]="validateOnly">
<label class="form-check-label" for="validateOnly"> <label class="form-check-label" for="validateOnly">
@@ -25,7 +25,7 @@
[uncheckedLabel]="'admin.metadata-import.page.toggle.url' | translate" [uncheckedLabel]="'admin.metadata-import.page.toggle.url' | translate"
[checked]="isUpload" [checked]="isUpload"
(change)="toggleUpload()" ></ui-switch> (change)="toggleUpload()" ></ui-switch>
<small class="form-text text-muted"> <small class="form-text text-muted d-block">
{{'admin.batch-import.page.toggle.help' | translate}} {{'admin.batch-import.page.toggle.help' | translate}}
</small> </small>
@@ -38,7 +38,7 @@
[dropMessageLabelReplacement]="'admin.batch-import.page.dropMsgReplace'"> [dropMessageLabelReplacement]="'admin.batch-import.page.dropMsgReplace'">
</ds-file-dropzone-no-uploader> </ds-file-dropzone-no-uploader>
<div class="form-group mt-2" *ngIf="!isUpload"> <div class="mb-3 mt-2" *ngIf="!isUpload">
<input class="form-control" type="text" placeholder="{{'admin.metadata-import.page.urlMsg' | translate}}" <input class="form-control" type="text" placeholder="{{'admin.metadata-import.page.urlMsg' | translate}}"
data-test="file-url-input" [(ngModel)]="fileURL"> data-test="file-url-input" [(ngModel)]="fileURL">
</div> </div>

View File

@@ -1,7 +1,7 @@
<div class="container"> <div class="container">
<h1 id="header">{{'admin.metadata-import.page.header' | translate}}</h1> <h1 id="header">{{'admin.metadata-import.page.header' | translate}}</h1>
<p>{{'admin.metadata-import.page.help' | translate}}</p> <p>{{'admin.metadata-import.page.help' | translate}}</p>
<div class="form-group"> <div class="mb-3">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="validateOnly" [(ngModel)]="validateOnly"> <input class="form-check-input" type="checkbox" id="validateOnly" [(ngModel)]="validateOnly">
<label class="form-check-label" for="validateOnly"> <label class="form-check-label" for="validateOnly">

View File

@@ -37,7 +37,7 @@
<div class="mb-5 mt-5"> <div class="mb-5 mt-5">
<!-- In the url section --> <!-- In the url section -->
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<div class="d-flex flex-column w-50 mr-2"> <div class="d-flex flex-column w-50 me-2">
<label for="url" class="font-weight-bold">{{ 'ldn-new-service.form.label.url' | translate }}</label> <label for="url" class="font-weight-bold">{{ 'ldn-new-service.form.label.url' | translate }}</label>
<input [class.invalid-field]="formModel.get('url').invalid && formModel.get('url').touched" <input [class.invalid-field]="formModel.get('url').invalid && formModel.get('url').touched"
[placeholder]="'ldn-new-service.form.placeholder.url' | translate" class="form-control" [placeholder]="'ldn-new-service.form.placeholder.url' | translate" class="form-control"
@@ -73,7 +73,7 @@
<label for="lowerIp" class="font-weight-bold">{{ 'ldn-new-service.form.label.ip-range' | translate }}</label> <label for="lowerIp" class="font-weight-bold">{{ 'ldn-new-service.form.label.ip-range' | translate }}</label>
<div class="d-flex"> <div class="d-flex">
<input [class.invalid-field]="formModel.get('lowerIp').invalid && formModel.get('lowerIp').touched" <input [class.invalid-field]="formModel.get('lowerIp').invalid && formModel.get('lowerIp').touched"
[placeholder]="'ldn-new-service.form.placeholder.lowerIp' | translate" class="form-control mr-2" [placeholder]="'ldn-new-service.form.placeholder.lowerIp' | translate" class="form-control me-2"
formControlName="lowerIp" formControlName="lowerIp"
id="lowerIp" id="lowerIp"
name="lowerIp" name="lowerIp"
@@ -298,7 +298,7 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<div *ngIf="!isNewService"> <div *ngIf="!isNewService">
<button (click)="closeModal()" class="btn btn-outline-secondary mr-2" <button (click)="closeModal()" class="btn btn-outline-secondary me-2"
id="delete-confirm-edit">{{ 'service.detail.return' | translate }} id="delete-confirm-edit">{{ 'service.detail.return' | translate }}
</button> </button>
<button *ngIf="!isNewService" (click)="patchService()" <button *ngIf="!isNewService" (click)="patchService()"
@@ -306,7 +306,7 @@
</button> </button>
</div> </div>
<div *ngIf="isNewService"> <div *ngIf="isNewService">
<button (click)="closeModal()" class="btn btn-outline-secondary mr-2 " <button (click)="closeModal()" class="btn btn-outline-secondary me-2 "
id="delete-confirm-new">{{ 'service.refuse.create' | translate }} id="delete-confirm-new">{{ 'service.refuse.create' | translate }}
</button> </button>
<button (click)="createService()" <button (click)="createService()"

View File

@@ -4,7 +4,7 @@
</div> </div>
<div class="d-flex justify-content-end"> <div class="d-flex justify-content-end">
<button class="btn btn-success" routerLink="/admin/ldn/services/new"><i <button class="btn btn-success" routerLink="/admin/ldn/services/new"><i
class="fas fa-plus pr-2"></i>{{ 'process.overview.new' | translate }}</button> class="fas fa-plus pe-2"></i>{{ 'process.overview.new' | translate }}</button>
</div> </div>
<ds-pagination *ngIf="(ldnServicesRD$ | async)?.payload?.totalElements > 0" <ds-pagination *ngIf="(ldnServicesRD$ | async)?.payload?.totalElements > 0"
[collectionSize]="(ldnServicesRD$ | async)?.payload?.totalElements" [collectionSize]="(ldnServicesRD$ | async)?.payload?.totalElements"
@@ -85,7 +85,7 @@
<div class="mt-4 text-right"> <div class="mt-4 text-right">
<button (click)="closeModal()" <button (click)="closeModal()"
[attr.aria-label]="'ldn-service-overview-close-modal' | translate" [attr.aria-label]="'ldn-service-overview-close-modal' | translate"
class="btn btn-outline-secondary mr-2">{{ 'service.detail.delete.cancel' | translate }}</button> class="btn btn-outline-secondary me-2">{{ 'service.detail.delete.cancel' | translate }}</button>
<button (click)="deleteSelected(this.selectedServiceId.toString(), ldnServicesService)" <button (click)="deleteSelected(this.selectedServiceId.toString(), ldnServicesService)"
class="btn btn-danger" class="btn btn-danger"
[attr.aria-label]="'ldn-service-overview-select-delete' | translate" [attr.aria-label]="'ldn-service-overview-select-delete' | translate"

View File

@@ -3,7 +3,7 @@
<div class="col-12 col-md-3 text-left h4">{{((isInbound$ | async) ? 'admin.notify.dashboard.inbound' : 'admin.notify.dashboard.outbound') | translate}}</div> <div class="col-12 col-md-3 text-left h4">{{((isInbound$ | async) ? 'admin.notify.dashboard.inbound' : 'admin.notify.dashboard.outbound') | translate}}</div>
<div class="col-md-9"> <div class="col-md-9">
<div class="h4"> <div class="h4">
<button (click)="resetDefaultConfiguration()" *ngIf="(selectedSearchConfig$ | async) !== defaultConfiguration" class="badge badge-primary mr-1 mb-1"> <button (click)="resetDefaultConfiguration()" *ngIf="(selectedSearchConfig$ | async) !== defaultConfiguration" class="badge bg-primary me-1 mb-1">
{{ 'admin-notify-logs.' + (selectedSearchConfig$ | async) | translate}} {{ 'admin-notify-logs.' + (selectedSearchConfig$ | async) | translate}}
<span> ×</span> <span> ×</span>
</button> </button>

View File

@@ -28,7 +28,7 @@
<tbody> <tbody>
<tr *ngFor="let bitstreamFormat of (bitstreamFormats$ | async)?.payload?.page"> <tr *ngFor="let bitstreamFormat of (bitstreamFormats$ | async)?.payload?.page">
<td> <td>
<label class="mb-0"> <label class="form-label mb-0">
<input type="checkbox" <input type="checkbox"
[attr.aria-label]="'admin.registries.bitstream-formats.select' | translate" [attr.aria-label]="'admin.registries.bitstream-formats.select' | translate"
[checked]="(selectedBitstreamFormatIDs$ | async)?.includes(bitstreamFormat.id)" [checked]="(selectedBitstreamFormatIDs$ | async)?.includes(bitstreamFormat.id)"
@@ -52,7 +52,7 @@
<div> <div>
<button *ngIf="(bitstreamFormats$ | async)?.payload?.page?.length > 0" class="btn btn-primary deselect" (click)="deselectAll()">{{'admin.registries.bitstream-formats.table.deselect-all' | translate}}</button> <button *ngIf="(bitstreamFormats$ | async)?.payload?.page?.length > 0" class="btn btn-primary deselect" (click)="deselectAll()">{{'admin.registries.bitstream-formats.table.deselect-all' | translate}}</button>
<button *ngIf="(bitstreamFormats$ | async)?.payload?.page?.length > 0" type="submit" class="btn btn-danger float-right" (click)="deleteFormats()">{{'admin.registries.bitstream-formats.table.delete' | translate}}</button> <button *ngIf="(bitstreamFormats$ | async)?.payload?.page?.length > 0" type="submit" class="btn btn-danger float-end" (click)="deleteFormats()">{{'admin.registries.bitstream-formats.table.delete' | translate}}</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -64,7 +64,7 @@ export class FormatFormComponent implements OnInit {
*/ */
arrayElementLayout: DynamicFormControlLayout = { arrayElementLayout: DynamicFormControlLayout = {
grid: { grid: {
group: 'form-row', group: 'row',
}, },
}; };

View File

@@ -29,7 +29,7 @@
<tr *ngFor="let schema of (metadataSchemas | async)?.payload?.page" <tr *ngFor="let schema of (metadataSchemas | async)?.payload?.page"
[ngClass]="{'table-primary' : (activeMetadataSchema$ | async)?.id === schema.id}"> [ngClass]="{'table-primary' : (activeMetadataSchema$ | async)?.id === schema.id}">
<td> <td>
<label class="mb-0"> <label class="form-label mb-0">
<input type="checkbox" <input type="checkbox"
[checked]="(selectedMetadataSchemaIDs$ | async)?.includes(schema.id)" [checked]="(selectedMetadataSchemaIDs$ | async)?.includes(schema.id)"
(change)="selectMetadataSchema(schema, $event)" (change)="selectMetadataSchema(schema, $event)"
@@ -52,7 +52,7 @@
</div> </div>
<div> <div>
<button *ngIf="(metadataSchemas | async)?.payload?.page?.length > 0" type="submit" class="btn btn-danger float-right" (click)="deleteSchemas()">{{'admin.registries.metadata.schemas.table.delete' | translate}}</button> <button *ngIf="(metadataSchemas | async)?.payload?.page?.length > 0" type="submit" class="btn btn-danger float-end" (click)="deleteSchemas()">{{'admin.registries.metadata.schemas.table.delete' | translate}}</button>
</div> </div>
</div> </div>

View File

@@ -53,7 +53,7 @@
<div> <div>
<button [routerLink]="['/admin/registries/metadata']" class="btn btn-primary">{{'admin.registries.schema.return' | translate}}</button> <button [routerLink]="['/admin/registries/metadata']" class="btn btn-primary">{{'admin.registries.schema.return' | translate}}</button>
<button *ngIf="fields?.page?.length > 0" type="submit" class="btn btn-danger float-right" (click)="deleteFields()">{{'admin.registries.schema.fields.table.delete' | translate}}</button> <button *ngIf="fields?.page?.length > 0" type="submit" class="btn btn-danger float-end" (click)="deleteFields()">{{'admin.registries.schema.fields.table.delete' | translate}}</button>
</div> </div>
</ng-container> </ng-container>

View File

@@ -1,4 +1,8 @@
import { HttpClientTestingModule } from '@angular/common/http/testing'; import {
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { import {
ComponentFixture, ComponentFixture,
@@ -39,22 +43,21 @@ describe('FiltersComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ schemas: [NO_ERRORS_SCHEMA],
NgbAccordionModule, imports: [NgbAccordionModule,
TranslateModule.forRoot({ TranslateModule.forRoot({
loader: { loader: {
provide: TranslateLoader, provide: TranslateLoader,
useClass: TranslateLoaderMock, useClass: TranslateLoaderMock,
}, },
}), }),
HttpClientTestingModule, FilteredCollectionsComponent],
FilteredCollectionsComponent,
],
providers: [ providers: [
FormBuilder, FormBuilder,
DspaceRestService, DspaceRestService,
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
], ],
schemas: [NO_ERRORS_SCHEMA],
}); });
})); }));

View File

@@ -13,7 +13,7 @@
<legend>{{group.key | translate}}</legend> <legend>{{group.key | translate}}</legend>
<ng-container [formGroup]="filtersForm"> <ng-container [formGroup]="filtersForm">
<div *ngFor="let filter of group.filters" class="col-6"> <div *ngFor="let filter of group.filters" class="col-6">
<input type="checkbox" id="flt-{{filter.id}}" value="{{filter.id}}" class="form-check-input col-1 align-baseline" formControlName="{{filter.id}}"><label for="flt-{{filter.id}}" class="col-11 align-middle" title="{{filter.tooltipKey | translate}}">{{filter.key | translate}}</label> <input type="checkbox" id="flt-{{filter.id}}" value="{{filter.id}}" class="form-check-input col-1" formControlName="{{filter.id}}"><label for="flt-{{filter.id}}" class="col-11 align-middle" title="{{filter.tooltipKey | translate}}">{{filter.key | translate}}</label>
</div> </div>
</ng-container> </ng-container>
</fieldset> </fieldset>

View File

@@ -0,0 +1,8 @@
.col-6 > label.col-11.align-middle {
padding-left: 15px;
padding-right: 15px;
}
fieldset.row-cols-2 > legend {
float: none !important;
}

View File

@@ -1,14 +1,13 @@
<div> <div>
<div class="modal-header">{{'supervision-group-selector.header' | translate}} <div class="modal-header">{{'supervision-group-selector.header' | translate}}
<button type="button" class="close" (click)="close()" aria-label="Close"> <button type="button" class="btn-close" (click)="close()" aria-label="Close">
<span aria-hidden="true">×</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row">
<div class="control-group col-sm-12"> <div class="control-group col-sm-12">
<label for="supervisionOrder">{{'supervision-group-selector.select.type-of-order.label' | translate}}</label> <label for="supervisionOrder" class="form-label">{{'supervision-group-selector.select.type-of-order.label' | translate}}</label>
<select name="supervisionOrder" id="supervisionOrder" class="form-control" <select name="supervisionOrder" id="supervisionOrder" class="form-select"
[(ngModel)]="selectedOrderType" [(ngModel)]="selectedOrderType"
attr.aria-label="{{'supervision-group-selector.select.type-of-order.label' | translate}}"> attr.aria-label="{{'supervision-group-selector.select.type-of-order.label' | translate}}">
<option value="EDITOR">{{'supervision-group-selector.select.type-of-order.option.editor' | translate}}</option> <option value="EDITOR">{{'supervision-group-selector.select.type-of-order.option.editor' | translate}}</option>
@@ -20,7 +19,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="control-group col-sm-12"> <div class="control-group col-sm-12">
<label for="supervisionGroup">{{'supervision-group-selector.select.group.label' | translate}}</label> <label for="supervisionGroup" class="form-label">{{'supervision-group-selector.select.group.label' | translate}}</label>
<ng-container class="mb-3"> <ng-container class="mb-3">
<input id="supervisionGroup" class="form-control" type="text" [value]="selectedGroup ? dsoNameService.getName(selectedGroup) : ''" disabled> <input id="supervisionGroup" class="form-control" type="text" [value]="selectedGroup ? dsoNameService.getName(selectedGroup) : ''" disabled>
<ds-error *ngIf="isSubmitted && !selectedGroup" message="{{'supervision-group-selector.select.group.error' | translate}}"></ds-error> <ds-error *ngIf="isSubmitted && !selectedGroup" message="{{'supervision-group-selector.select.group.error' | translate}}"></ds-error>

View File

@@ -4,7 +4,7 @@
<span>{{'workflow-item.search.result.list.element.supervised-by' | translate}} </span> <span>{{'workflow-item.search.result.list.element.supervised-by' | translate}} </span>
</div> </div>
<div> <div>
<a class="badge badge-primary mr-1 mb-1 text-capitalize mw-100 text-truncate" *ngFor="let supervisionOrder of supervisionOrders" data-test="soBadge" <a class="badge bg-primary me-1 mb-1 text-capitalize mw-100 text-truncate" *ngFor="let supervisionOrder of supervisionOrders" data-test="soBadge"
[ngbTooltip]="'workflow-item.search.result.list.element.supervised.remove-tooltip' | translate" [ngbTooltip]="'workflow-item.search.result.list.element.supervised.remove-tooltip' | translate"
(click)="$event.preventDefault(); $event.stopImmediatePropagation(); deleteSupervisionOrder(supervisionOrder)" aria-label="Close"> (click)="$event.preventDefault(); $event.stopImmediatePropagation(); deleteSupervisionOrder(supervisionOrder)" aria-label="Close">
{{ dsoNameService.getName(supervisionOrder.group) }} {{ dsoNameService.getName(supervisionOrder.group) }}

View File

@@ -1,8 +1,8 @@
<ng-template dsDynamicComponentLoader> <ng-template dsDynamicComponentLoader>
</ng-template> </ng-template>
<div #badges class="position-absolute ml-1"> <div #badges class="position-absolute ms-1">
<div class="workflow-badge"> <div class="workflow-badge">
<span class="badge badge-info">{{ "admin.workflow.item.workflow" | translate }}</span> <span class="badge bg-info">{{ "admin.workflow.item.workflow" | translate }}</span>
</div> </div>
</div> </div>
<ul #buttons class="list-group list-group-flush"> <ul #buttons class="list-group list-group-flush">

View File

@@ -1,8 +1,8 @@
<ng-template dsDynamicComponentLoader> <ng-template dsDynamicComponentLoader>
</ng-template> </ng-template>
<div #badges class="position-absolute ml-1"> <div #badges class="position-absolute ms-1">
<div class="workflow-badge"> <div class="workflow-badge">
<span class="badge badge-info">{{ "admin.workflow.item.workspace" | translate }}</span> <span class="badge bg-info">{{ "admin.workflow.item.workspace" | translate }}</span>
</div> </div>
</div> </div>
<ul #buttons class="list-group list-group-flush"> <ul #buttons class="list-group list-group-flush">

View File

@@ -1,5 +1,5 @@
<div class="workflow-badge"> <div class="workflow-badge">
<span class="badge badge-info">{{ "admin.workflow.item.workflow" | translate }}</span> <span class="badge bg-info">{{ "admin.workflow.item.workflow" | translate }}</span>
</div> </div>
<ds-listable-object-component-loader *ngIf="item$ | async" <ds-listable-object-component-loader *ngIf="item$ | async"
[object]="item$ | async" [object]="item$ | async"

View File

@@ -1,4 +1,4 @@
<span class="badge badge-info">{{ "admin.workflow.item.workspace" | translate }}</span> <span class="badge bg-info">{{ "admin.workflow.item.workspace" | translate }}</span>
<ds-listable-object-component-loader *ngIf="item$ | async" <ds-listable-object-component-loader *ngIf="item$ | async"
[object]="item$ | async" [object]="item$ | async"
[viewMode]="viewModes.ListElement" [viewMode]="viewModes.ListElement"

View File

@@ -108,7 +108,7 @@ export const APP_ROUTES: Route[] = [
path: COLLECTION_MODULE_PATH, path: COLLECTION_MODULE_PATH,
loadChildren: () => import('./collection-page/collection-page-routes') loadChildren: () => import('./collection-page/collection-page-routes')
.then((m) => m.ROUTES), .then((m) => m.ROUTES),
data: { showBreadcrumbs: false, enableRSS: true }, data: { enableRSS: true },
canActivate: [endUserAgreementCurrentUserGuard], canActivate: [endUserAgreementCurrentUserGuard],
}, },
{ {

View File

@@ -54,6 +54,7 @@ import {
} from './app-routes'; } from './app-routes';
import { BROWSE_BY_DECORATOR_MAP } from './browse-by/browse-by-switcher/browse-by-decorator'; import { BROWSE_BY_DECORATOR_MAP } from './browse-by/browse-by-switcher/browse-by-decorator';
import { AuthInterceptor } from './core/auth/auth.interceptor'; import { AuthInterceptor } from './core/auth/auth.interceptor';
import { DspaceRestInterceptor } from './core/dspace-rest/dspace-rest.interceptor';
import { LocaleInterceptor } from './core/locale/locale.interceptor'; import { LocaleInterceptor } from './core/locale/locale.interceptor';
import { LogInterceptor } from './core/log/log.interceptor'; import { LogInterceptor } from './core/log/log.interceptor';
import { import {
@@ -148,6 +149,11 @@ export const commonAppConfig: ApplicationConfig = {
useClass: LogInterceptor, useClass: LogInterceptor,
multi: true, multi: true,
}, },
{
provide: HTTP_INTERCEPTORS,
useClass: DspaceRestInterceptor,
multi: true,
},
// register the dynamic matcher used by form. MUST be provided by the app module // register the dynamic matcher used by form. MUST be provided by the app module
...DYNAMIC_MATCHER_PROVIDERS, ...DYNAMIC_MATCHER_PROVIDERS,
provideCore(), provideCore(),

View File

@@ -1,8 +1,8 @@
<div class="container"> <div class="container">
<ds-resource-policies [resourceType]="'bitstream'" [resourceUUID]="(dsoRD$ | async)?.payload?.id"></ds-resource-policies> <ds-resource-policies [resourceType]="'bitstream'" [resourceUUID]="(dsoRD$ | async)?.payload?.id"></ds-resource-policies>
<div class="button-row bottom"> <div class="button-row bottom">
<div class="text-right"> <div class="text-end">
<a [routerLink]="['/bitstreams', (dsoRD$ | async)?.payload?.id, 'edit']" role="button" class="btn btn-outline-secondary mr-1"> <a [routerLink]="['/bitstreams', (dsoRD$ | async)?.payload?.id, 'edit']" role="button" class="btn btn-outline-secondary me-1">
<i class="fas fa-arrow-left"></i> {{'bitstream.edit.return' | translate}} <i class="fas fa-arrow-left"></i> {{'bitstream.edit.return' | translate}}
</a> </a>
</div> </div>

View File

@@ -255,7 +255,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
group: [this.iiifLabelModel], group: [this.iiifLabelModel],
}, { }, {
grid: { grid: {
host: 'form-row', host: 'row',
}, },
}); });
@@ -273,7 +273,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
group: [this.iiifTocModel], group: [this.iiifTocModel],
}, { }, {
grid: { grid: {
host: 'form-row', host: 'row',
}, },
}); });
@@ -291,7 +291,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
group: [this.iiifWidthModel], group: [this.iiifWidthModel],
}, { }, {
grid: { grid: {
host: 'form-row', host: 'row',
}, },
}); });
@@ -309,7 +309,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
group: [this.iiifHeightModel], group: [this.iiifHeightModel],
}, { }, {
grid: { grid: {
host: 'form-row', host: 'row',
}, },
}); });
@@ -332,7 +332,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
], ],
}, { }, {
grid: { grid: {
host: 'form-row', host: 'row',
}, },
}), }),
new DynamicFormGroupModel({ new DynamicFormGroupModel({

View File

@@ -7,6 +7,8 @@
padding-bottom: calc(var(--ds-content-spacing) / 2); padding-bottom: calc(var(--ds-content-spacing) / 2);
padding-top: calc(var(--ds-content-spacing) / 2); padding-top: calc(var(--ds-content-spacing) / 2);
background-color: var(--ds-breadcrumb-bg); background-color: var(--ds-breadcrumb-bg);
padding-left: calc(var(--bs-spacer) *.75);
padding-right: calc(var(--bs-spacer) *.75);
} }
li.breadcrumb-item { li.breadcrumb-item {

View File

@@ -5,7 +5,7 @@
<div *ngIf="collectionRD?.payload as collection"> <div *ngIf="collectionRD?.payload as collection">
<ds-view-tracker [object]="collection"></ds-view-tracker> <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 me-auto">
<!-- Collection Name --> <!-- Collection Name -->
<ds-comcol-page-header <ds-comcol-page-header
[name]="dsoNameService.getName(collection)"> [name]="dsoNameService.getName(collection)">

View File

@@ -4,8 +4,8 @@
<div class="col-12 pb-4"> <div class="col-12 pb-4">
<h1 id="header" class="border-bottom pb-2">{{ 'collection.delete.head' | translate}}</h1> <h1 id="header" class="border-bottom pb-2">{{ 'collection.delete.head' | translate}}</h1>
<p class="pb-2">{{ 'collection.delete.text' | translate:{ dso: dsoNameService.getName(dso) } }}</p> <p class="pb-2">{{ 'collection.delete.text' | translate:{ dso: dsoNameService.getName(dso) } }}</p>
<div class="form-group row"> <div class="mb-3 row">
<div class="col text-right space-children-mr"> <div class="col text-end space-children-mr">
<button class="btn btn-outline-secondary" (click)="onCancel(dso)" [dsBtnDisabled]="(processing$ | async)"> <button class="btn btn-outline-secondary" (click)="onCancel(dso)" [dsBtnDisabled]="(processing$ | async)">
<i class="fas fa-times"></i> {{'collection.delete.cancel' | translate}} <i class="fas fa-times"></i> {{'collection.delete.cancel' | translate}}
</button> </button>

View File

@@ -1,6 +1,8 @@
<ds-comcol-role <div class="custom-alignment">
*ngFor="let comcolRole of comcolRoles$ | async" <ds-comcol-role
[dso]="collection$ | async" *ngFor="let comcolRole of comcolRoles$ | async"
[comcolRole]="comcolRole" [dso]="collection$ | async"
> [comcolRole]="comcolRole"
</ds-comcol-role> >
</ds-comcol-role>
</div>

View File

@@ -2,19 +2,19 @@
<div class="container-fluid space-children-mr" *ngIf="shouldShow"> <div class="container-fluid space-children-mr" *ngIf="shouldShow">
<h3>{{ 'collection.source.controls.head' | translate }}</h3> <h3>{{ 'collection.source.controls.head' | translate }}</h3>
<div> <div>
<span class="font-weight-bold">{{'collection.source.controls.harvest.status' | translate}}</span> <span class="fw-bold">{{'collection.source.controls.harvest.status' | translate}}</span>
<span>{{contentSource?.harvestStatus}}</span> <span>{{contentSource?.harvestStatus}}</span>
</div> </div>
<div> <div>
<span class="font-weight-bold">{{'collection.source.controls.harvest.start' | translate}}</span> <span class="fw-bold">{{'collection.source.controls.harvest.start' | translate}}</span>
<span>{{contentSource?.harvestStartTime ? contentSource?.harvestStartTime : 'collection.source.controls.harvest.no-information'|translate }}</span> <span>{{contentSource?.harvestStartTime ? contentSource?.harvestStartTime : 'collection.source.controls.harvest.no-information'|translate }}</span>
</div> </div>
<div> <div>
<span class="font-weight-bold">{{'collection.source.controls.harvest.last' | translate}}</span> <span class="fw-bold">{{'collection.source.controls.harvest.last' | translate}}</span>
<span>{{contentSource?.lastHarvested ? contentSource?.lastHarvested : 'collection.source.controls.harvest.no-information'|translate }}</span> <span>{{contentSource?.lastHarvested ? contentSource?.lastHarvested : 'collection.source.controls.harvest.no-information'|translate }}</span>
</div> </div>
<div> <div>
<span class="font-weight-bold">{{'collection.source.controls.harvest.message' | translate}}</span> <span class="fw-bold">{{'collection.source.controls.harvest.message' | translate}}</span>
<span>{{contentSource?.message ? contentSource?.message: 'collection.source.controls.harvest.no-information'|translate }}</span> <span>{{contentSource?.message ? contentSource?.message: 'collection.source.controls.harvest.no-information'|translate }}</span>
</div> </div>

View File

@@ -1,5 +1,5 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="d-inline-block float-right space-children-mr"> <div class="d-inline-block float-end space-children-mr">
<button class=" btn btn-danger" *ngIf="(isReinstatable$ | async) !== true" <button class=" btn btn-danger" *ngIf="(isReinstatable$ | async) !== true"
[dsBtnDisabled]="(hasChanges$ | async) !== true" [dsBtnDisabled]="(hasChanges$ | async) !== true"
(click)="discard()"><i (click)="discard()"><i
@@ -43,7 +43,7 @@
<div class="container mt-2" *ngIf="(contentSource?.harvestType !== harvestTypeNone)"> <div class="container mt-2" *ngIf="(contentSource?.harvestType !== harvestTypeNone)">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="d-inline-block float-right ml-1 space-children-mr"> <div class="d-inline-block float-end ms-1 space-children-mr">
<button class=" btn btn-danger" *ngIf="(isReinstatable$ | async) !== true" <button class=" btn btn-danger" *ngIf="(isReinstatable$ | async) !== true"
[dsBtnDisabled]="(hasChanges$ | async) !== true" [dsBtnDisabled]="(hasChanges$ | async) !== true"
(click)="discard()"><i (click)="discard()"><i

View File

@@ -0,0 +1,9 @@
:host ::ng-deep #oaiSetContainer {
> :first-child {
padding-left: 0;
}
> :last-child {
padding-right: 0;
}
}

View File

@@ -23,6 +23,7 @@ import { getCollectionPageRoute } from '../collection-page-routing-paths';
@Component({ @Component({
selector: 'ds-edit-collection', selector: 'ds-edit-collection',
templateUrl: '../../shared/comcol/comcol-forms/edit-comcol-page/edit-comcol-page.component.html', templateUrl: '../../shared/comcol/comcol-forms/edit-comcol-page/edit-comcol-page.component.html',
styleUrls: ['./edit-collection-page.component.scss'],
imports: [ imports: [
RouterLink, RouterLink,
TranslateModule, TranslateModule,

View File

@@ -24,7 +24,7 @@
<cdk-tree-node *cdkTreeNodeDef="let node; when: hasChild" cdkTreeNodePadding <cdk-tree-node *cdkTreeNodeDef="let node; when: hasChild" cdkTreeNodePadding
class="example-tree-node expandable-node"> class="example-tree-node expandable-node">
<div class="btn-group"> <div class="btn-group">
<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 btn-transparent" 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">
@@ -39,8 +39,8 @@
<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">{{ dsoNameService.getName(node.payload) }}</a>
<span class="pr-2">&nbsp;</span> <span class="pe-2">&nbsp;</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 rounded-pill bg-secondary align-top archived-items-lead my-auto ps-2 pe-2">{{node.payload.archivedItemsCount}}</span>
</span> </span>
</div> </div>
</div> </div>

View File

@@ -3,7 +3,7 @@
<div *ngIf="communityRD?.payload; let communityPayload"> <div *ngIf="communityRD?.payload; let communityPayload">
<ds-view-tracker [object]="communityPayload"></ds-view-tracker> <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 me-auto">
<!-- Community name --> <!-- Community name -->
<ds-comcol-page-header [name]="dsoNameService.getName(communityPayload)"></ds-comcol-page-header> <ds-comcol-page-header [name]="dsoNameService.getName(communityPayload)"></ds-comcol-page-header>
<!-- Community logo --> <!-- Community logo -->

View File

@@ -4,8 +4,8 @@
<div class="col-12 pb-4"> <div class="col-12 pb-4">
<h1 id="header" class="border-bottom pb-2">{{ 'community.delete.head' | translate}}</h1> <h1 id="header" class="border-bottom pb-2">{{ 'community.delete.head' | translate}}</h1>
<p class="pb-2">{{ 'community.delete.text' | translate:{ dso: dsoNameService.getName(dso) } }}</p> <p class="pb-2">{{ 'community.delete.text' | translate:{ dso: dsoNameService.getName(dso) } }}</p>
<div class="form-group row"> <div class="mb-3 row">
<div class="col text-right space-children-mr"> <div class="col text-end space-children-mr">
<button class="btn btn-outline-secondary" (click)="onCancel(dso)" [dsBtnDisabled]="(processing$ | async)"> <button class="btn btn-outline-secondary" (click)="onCancel(dso)" [dsBtnDisabled]="(processing$ | async)">
<i class="fas fa-times" aria-hidden="true"></i> {{'community.delete.cancel' | translate}} <i class="fas fa-times" aria-hidden="true"></i> {{'community.delete.cancel' | translate}}
</button> </button>

View File

@@ -1,6 +1,8 @@
<ds-comcol-role <div class="custom-alignment">
*ngFor="let comcolRole of comcolRoles$ | async" <ds-comcol-role
[dso]="community$ | async" *ngFor="let comcolRole of comcolRoles$ | async"
[comcolRole]="comcolRole" [dso]="community$ | async"
> [comcolRole]="comcolRole"
</ds-comcol-role> >
</ds-comcol-role>
</div>

View File

@@ -1,7 +1,11 @@
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { import {
HttpClientTestingModule, HTTP_INTERCEPTORS,
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http';
import {
HttpTestingController, HttpTestingController,
provideHttpClientTesting,
} from '@angular/common/http/testing'; } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
@@ -28,7 +32,7 @@ describe(`AuthInterceptor`, () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [HttpClientTestingModule], imports: [],
providers: [ providers: [
DspaceRestService, DspaceRestService,
{ provide: AuthService, useValue: authServiceStub }, { provide: AuthService, useValue: authServiceStub },
@@ -39,6 +43,8 @@ describe(`AuthInterceptor`, () => {
multi: true, multi: true,
}, },
{ provide: Store, useValue: store }, { provide: Store, useValue: store },
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
], ],
}); });

View File

@@ -5,5 +5,6 @@ export enum AuthMethodType {
Ip = 'ip', Ip = 'ip',
X509 = 'x509', X509 = 'x509',
Oidc = 'oidc', Oidc = 'oidc',
Orcid = 'orcid' Orcid = 'orcid',
Saml = 'saml'
} }

View File

@@ -40,6 +40,11 @@ export class AuthMethod {
this.location = location; this.location = location;
break; break;
} }
case 'saml': {
this.authMethodType = AuthMethodType.Saml;
this.location = location;
break;
}
default: { default: {
break; break;

View File

@@ -1,5 +1,5 @@
// eslint-disable-next-line import/no-namespace // eslint-disable-next-line import/no-namespace
import * as deepFreeze from 'deep-freeze'; import deepFreeze from 'deep-freeze';
import { Operation } from 'fast-json-patch'; import { Operation } from 'fast-json-patch';
import { Item } from '../shared/item.model'; import { Item } from '../shared/item.model';

View File

@@ -1,5 +1,5 @@
// eslint-disable-next-line import/no-namespace // eslint-disable-next-line import/no-namespace
import * as deepFreeze from 'deep-freeze'; import deepFreeze from 'deep-freeze';
import { RestRequestMethod } from '../data/rest-request-method'; import { RestRequestMethod } from '../data/rest-request-method';
import { RemoveFromObjectCacheAction } from './object-cache.actions'; import { RemoveFromObjectCacheAction } from './object-cache.actions';

View File

@@ -1,5 +1,5 @@
// eslint-disable-next-line import/no-namespace // eslint-disable-next-line import/no-namespace
import * as deepFreeze from 'deep-freeze'; import deepFreeze from 'deep-freeze';
import { Relationship } from '../../shared/item-relationships/relationship.model'; import { Relationship } from '../../shared/item-relationships/relationship.model';
import { FieldChangeType } from './field-change-type.model'; import { FieldChangeType } from './field-change-type.model';

View File

@@ -1,5 +1,5 @@
// eslint-disable-next-line import/no-namespace // eslint-disable-next-line import/no-namespace
import * as deepFreeze from 'deep-freeze'; import deepFreeze from 'deep-freeze';
import { import {
RequestConfigureAction, RequestConfigureAction,

View File

@@ -0,0 +1,194 @@
import {
HTTP_INTERCEPTORS,
HttpClient,
} from '@angular/common/http';
import {
HttpClientTestingModule,
HttpTestingController,
} from '@angular/common/http/testing';
import { PLATFORM_ID } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import {
APP_CONFIG,
AppConfig,
} from '../../../config/app-config.interface';
import { DspaceRestInterceptor } from './dspace-rest.interceptor';
import { DspaceRestService } from './dspace-rest.service';
describe('DspaceRestInterceptor', () => {
let httpMock: HttpTestingController;
let httpClient: HttpClient;
const appConfig: Partial<AppConfig> = {
rest: {
ssl: false,
host: 'localhost',
port: 8080,
nameSpace: '/server',
baseUrl: 'http://api.example.com/server',
},
};
const appConfigWithSSR: Partial<AppConfig> = {
rest: {
ssl: false,
host: 'localhost',
port: 8080,
nameSpace: '/server',
baseUrl: 'http://api.example.com/server',
ssrBaseUrl: 'http://ssr.example.com/server',
},
};
describe('When SSR base URL is not set ', () => {
describe('and it\'s in the browser', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
DspaceRestService,
{
provide: HTTP_INTERCEPTORS,
useClass: DspaceRestInterceptor,
multi: true,
},
{ provide: APP_CONFIG, useValue: appConfig },
{ provide: PLATFORM_ID, useValue: 'browser' },
],
});
httpMock = TestBed.inject(HttpTestingController);
httpClient = TestBed.inject(HttpClient);
});
it('should not modify the request', () => {
const url = 'http://api.example.com/server/items';
httpClient.get(url).subscribe((response) => {
expect(response).toBeTruthy();
});
const req = httpMock.expectOne(url);
expect(req.request.url).toBe(url);
req.flush({});
httpMock.verify();
});
});
describe('and it\'s in SSR mode', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
DspaceRestService,
{
provide: HTTP_INTERCEPTORS,
useClass: DspaceRestInterceptor,
multi: true,
},
{ provide: APP_CONFIG, useValue: appConfig },
{ provide: PLATFORM_ID, useValue: 'server' },
],
});
httpMock = TestBed.inject(HttpTestingController);
httpClient = TestBed.inject(HttpClient);
});
it('should not replace the base URL', () => {
const url = 'http://api.example.com/server/items';
httpClient.get(url).subscribe((response) => {
expect(response).toBeTruthy();
});
const req = httpMock.expectOne(url);
expect(req.request.url).toBe(url);
req.flush({});
httpMock.verify();
});
});
});
describe('When SSR base URL is set ', () => {
describe('and it\'s in the browser', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
DspaceRestService,
{
provide: HTTP_INTERCEPTORS,
useClass: DspaceRestInterceptor,
multi: true,
},
{ provide: APP_CONFIG, useValue: appConfigWithSSR },
{ provide: PLATFORM_ID, useValue: 'browser' },
],
});
httpMock = TestBed.inject(HttpTestingController);
httpClient = TestBed.inject(HttpClient);
});
it('should not modify the request', () => {
const url = 'http://api.example.com/server/items';
httpClient.get(url).subscribe((response) => {
expect(response).toBeTruthy();
});
const req = httpMock.expectOne(url);
expect(req.request.url).toBe(url);
req.flush({});
httpMock.verify();
});
});
describe('and it\'s in SSR mode', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
DspaceRestService,
{
provide: HTTP_INTERCEPTORS,
useClass: DspaceRestInterceptor,
multi: true,
},
{ provide: APP_CONFIG, useValue: appConfigWithSSR },
{ provide: PLATFORM_ID, useValue: 'server' },
],
});
httpMock = TestBed.inject(HttpTestingController);
httpClient = TestBed.inject(HttpClient);
});
it('should replace the base URL', () => {
const url = 'http://api.example.com/server/items';
const ssrBaseUrl = appConfigWithSSR.rest.ssrBaseUrl;
httpClient.get(url).subscribe((response) => {
expect(response).toBeTruthy();
});
const req = httpMock.expectOne(ssrBaseUrl + '/items');
expect(req.request.url).toBe(ssrBaseUrl + '/items');
req.flush({});
httpMock.verify();
});
it('should not replace any query param containing the base URL', () => {
const url = 'http://api.example.com/server/items?url=http://api.example.com/server/item/1';
const ssrBaseUrl = appConfigWithSSR.rest.ssrBaseUrl;
httpClient.get(url).subscribe((response) => {
expect(response).toBeTruthy();
});
const req = httpMock.expectOne(ssrBaseUrl + '/items?url=http://api.example.com/server/item/1');
expect(req.request.url).toBe(ssrBaseUrl + '/items?url=http://api.example.com/server/item/1');
req.flush({});
httpMock.verify();
});
});
});
});

View File

@@ -0,0 +1,52 @@
import { isPlatformBrowser } from '@angular/common';
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
} from '@angular/common/http';
import {
Inject,
Injectable,
PLATFORM_ID,
} from '@angular/core';
import { Observable } from 'rxjs';
import {
APP_CONFIG,
AppConfig,
} from '../../../config/app-config.interface';
import { isEmpty } from '../../shared/empty.util';
@Injectable()
/**
* This Interceptor is used to use the configured base URL for the request made during SSR execution
*/
export class DspaceRestInterceptor implements HttpInterceptor {
/**
* Contains the configured application base URL
* @protected
*/
protected baseUrl: string;
protected ssrBaseUrl: string;
constructor(
@Inject(APP_CONFIG) protected appConfig: AppConfig,
@Inject(PLATFORM_ID) private platformId: string,
) {
this.baseUrl = this.appConfig.rest.baseUrl;
this.ssrBaseUrl = this.appConfig.rest.ssrBaseUrl;
}
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
if (isPlatformBrowser(this.platformId) || isEmpty(this.ssrBaseUrl) || this.baseUrl === this.ssrBaseUrl) {
return next.handle(request);
}
// Different SSR Base URL specified so replace it in the current request url
const url = request.url.replace(this.baseUrl, this.ssrBaseUrl);
const newRequest: HttpRequest<any> = request.clone({ url });
return next.handle(newRequest);
}
}

View File

@@ -1,10 +1,12 @@
import { import {
HttpErrorResponse, HttpErrorResponse,
HttpHeaders, HttpHeaders,
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http'; } from '@angular/common/http';
import { import {
HttpClientTestingModule,
HttpTestingController, HttpTestingController,
provideHttpClientTesting,
} from '@angular/common/http/testing'; } from '@angular/common/http/testing';
import { import {
inject, inject,
@@ -33,8 +35,12 @@ describe('DspaceRestService', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [HttpClientTestingModule], imports: [],
providers: [DspaceRestService], providers: [
DspaceRestService,
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
],
}); });
dspaceRestService = TestBed.inject(DspaceRestService); dspaceRestService = TestBed.inject(DspaceRestService);

View File

@@ -1,7 +1,11 @@
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { import {
HttpClientTestingModule, HTTP_INTERCEPTORS,
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http';
import {
HttpTestingController, HttpTestingController,
provideHttpClientTesting,
} from '@angular/common/http/testing'; } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
@@ -21,7 +25,7 @@ describe('ForwardClientIpInterceptor', () => {
clientIp = '1.2.3.4'; clientIp = '1.2.3.4';
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [HttpClientTestingModule], imports: [],
providers: [ providers: [
DspaceRestService, DspaceRestService,
{ {
@@ -30,6 +34,8 @@ describe('ForwardClientIpInterceptor', () => {
multi: true, multi: true,
}, },
{ provide: REQUEST, useValue: { get: () => undefined, connection: { remoteAddress: clientIp } } }, { provide: REQUEST, useValue: { get: () => undefined, connection: { remoteAddress: clientIp } } },
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
], ],
}); });

View File

@@ -1,5 +1,5 @@
// eslint-disable-next-line import/no-namespace // eslint-disable-next-line import/no-namespace
import * as deepFreeze from 'deep-freeze'; import deepFreeze from 'deep-freeze';
import { import {
AddToIndexAction, AddToIndexAction,

View File

@@ -1,5 +1,5 @@
// eslint-disable-next-line import/no-namespace // eslint-disable-next-line import/no-namespace
import * as deepFreeze from 'deep-freeze'; import deepFreeze from 'deep-freeze';
import { import {
CommitPatchOperationsAction, CommitPatchOperationsAction,

View File

@@ -1,7 +1,11 @@
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { import {
HttpClientTestingModule, HTTP_INTERCEPTORS,
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http';
import {
HttpTestingController, HttpTestingController,
provideHttpClientTesting,
} from '@angular/common/http/testing'; } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
@@ -25,7 +29,7 @@ describe(`LocaleInterceptor`, () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [HttpClientTestingModule], imports: [],
providers: [ providers: [
DspaceRestService, DspaceRestService,
{ {
@@ -34,6 +38,8 @@ describe(`LocaleInterceptor`, () => {
multi: true, multi: true,
}, },
{ provide: LocaleService, useValue: mockLocaleService }, { provide: LocaleService, useValue: mockLocaleService },
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
], ],
}); });

View File

@@ -1,7 +1,11 @@
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { import {
HttpClientTestingModule, HTTP_INTERCEPTORS,
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http';
import {
HttpTestingController, HttpTestingController,
provideHttpClientTesting,
} from '@angular/common/http/testing'; } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
@@ -39,10 +43,7 @@ describe('LogInterceptor', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [StoreModule.forRoot(appReducers, storeModuleConfig)],
HttpClientTestingModule,
StoreModule.forRoot(appReducers, storeModuleConfig),
],
providers: [ providers: [
DspaceRestService, DspaceRestService,
// LogInterceptor, // LogInterceptor,
@@ -55,6 +56,8 @@ describe('LogInterceptor', () => {
{ provide: Router, useValue: router }, { provide: Router, useValue: router },
{ provide: CorrelationIdService, useClass: CorrelationIdService }, { provide: CorrelationIdService, useClass: CorrelationIdService },
{ provide: UUIDService, useClass: UUIDService }, { provide: UUIDService, useClass: UUIDService },
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
], ],
}); });

View File

@@ -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>
*/ */

View File

@@ -1,5 +1,6 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { environment } from '../../../environments/environment.test';
import { ServerHardRedirectService } from './server-hard-redirect.service'; import { ServerHardRedirectService } from './server-hard-redirect.service';
describe('ServerHardRedirectService', () => { describe('ServerHardRedirectService', () => {
@@ -7,7 +8,7 @@ describe('ServerHardRedirectService', () => {
const mockRequest = jasmine.createSpyObj(['get']); const mockRequest = jasmine.createSpyObj(['get']);
const mockResponse = jasmine.createSpyObj(['redirect', 'end']); const mockResponse = jasmine.createSpyObj(['redirect', 'end']);
const service: ServerHardRedirectService = new ServerHardRedirectService(mockRequest, mockResponse); let service: ServerHardRedirectService = new ServerHardRedirectService(environment, mockRequest, mockResponse);
const origin = 'https://test-host.com:4000'; const origin = 'https://test-host.com:4000';
beforeEach(() => { beforeEach(() => {
@@ -68,4 +69,23 @@ describe('ServerHardRedirectService', () => {
}); });
}); });
describe('when SSR base url is set', () => {
const redirect = 'https://private-url:4000/server/api/bitstreams/uuid';
const replacedUrl = 'https://public-url/server/api/bitstreams/uuid';
const environmentWithSSRUrl: any = { ...environment, ...{ ...environment.rest, rest: {
ssrBaseUrl: 'https://private-url:4000/server',
baseUrl: 'https://public-url/server',
} } };
service = new ServerHardRedirectService(environmentWithSSRUrl, mockRequest, mockResponse);
beforeEach(() => {
service.redirect(redirect);
});
it('should perform a 302 redirect', () => {
expect(mockResponse.redirect).toHaveBeenCalledWith(302, replacedUrl);
expect(mockResponse.end).toHaveBeenCalled();
});
});
}); });

View File

@@ -7,10 +7,15 @@ import {
Response, Response,
} from 'express'; } from 'express';
import {
APP_CONFIG,
AppConfig,
} from '../../../config/app-config.interface';
import { import {
REQUEST, REQUEST,
RESPONSE, RESPONSE,
} from '../../../express.tokens'; } from '../../../express.tokens';
import { isNotEmpty } from '../../shared/empty.util';
import { HardRedirectService } from './hard-redirect.service'; import { HardRedirectService } from './hard-redirect.service';
/** /**
@@ -20,6 +25,7 @@ import { HardRedirectService } from './hard-redirect.service';
export class ServerHardRedirectService extends HardRedirectService { export class ServerHardRedirectService extends HardRedirectService {
constructor( constructor(
@Inject(APP_CONFIG) protected appConfig: AppConfig,
@Inject(REQUEST) protected req: Request, @Inject(REQUEST) protected req: Request,
@Inject(RESPONSE) protected res: Response, @Inject(RESPONSE) protected res: Response,
) { ) {
@@ -35,17 +41,22 @@ export class ServerHardRedirectService extends HardRedirectService {
* optional HTTP status code to use for redirect (default = 302, which is a temporary redirect) * optional HTTP status code to use for redirect (default = 302, which is a temporary redirect)
*/ */
redirect(url: string, statusCode?: number) { redirect(url: string, statusCode?: number) {
if (url === this.req.url) { if (url === this.req.url) {
return; return;
} }
let redirectUrl = url;
// If redirect url contains SSR base url then replace with public base url
if (isNotEmpty(this.appConfig.rest.ssrBaseUrl) && this.appConfig.rest.baseUrl !== this.appConfig.rest.ssrBaseUrl) {
redirectUrl = url.replace(this.appConfig.rest.ssrBaseUrl, this.appConfig.rest.baseUrl);
}
if (this.res.finished) { if (this.res.finished) {
const req: any = this.req; const req: any = this.req;
req._r_count = (req._r_count || 0) + 1; req._r_count = (req._r_count || 0) + 1;
console.warn('Attempted to redirect on a finished response. From', console.warn('Attempted to redirect on a finished response. From',
this.req.url, 'to', url); this.req.url, 'to', redirectUrl);
if (req._r_count > 10) { if (req._r_count > 10) {
console.error('Detected a redirection loop. killing the nodejs process'); console.error('Detected a redirection loop. killing the nodejs process');
@@ -59,9 +70,9 @@ export class ServerHardRedirectService extends HardRedirectService {
status = 302; status = 302;
} }
console.log(`Redirecting from ${this.req.url} to ${url} with ${status}`); console.info(`Redirecting from ${this.req.url} to ${redirectUrl} with ${status}`);
this.res.redirect(status, url); this.res.redirect(status, redirectUrl);
this.res.end(); this.res.end();
// I haven't found a way to correctly stop Angular rendering. // I haven't found a way to correctly stop Angular rendering.
// So we just let it end its work, though we have already closed // So we just let it end its work, though we have already closed

View File

@@ -29,6 +29,9 @@ export enum Context {
SideBarSearchModal = 'sideBarSearchModal', SideBarSearchModal = 'sideBarSearchModal',
SideBarSearchModalCurrent = 'sideBarSearchModalCurrent', SideBarSearchModalCurrent = 'sideBarSearchModalCurrent',
ScopeSelectorModal = 'scopeSelectorModal',
ScopeSelectorModalCurrent = 'scopeSelectorModalCurrent',
/** The MyDSpace* Context values below are used for badge display in MyDSpace. */ /** The MyDSpace* Context values below are used for badge display in MyDSpace. */
MyDSpaceArchived = 'mydspaceArchived', MyDSpaceArchived = 'mydspaceArchived',
MyDSpaceWorkspace = 'mydspaceWorkspace', MyDSpaceWorkspace = 'mydspaceWorkspace',

View File

@@ -1,7 +1,11 @@
import { HttpClient } from '@angular/common/http';
import { import {
HttpClientTestingModule, HttpClient,
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http';
import {
HttpTestingController, HttpTestingController,
provideHttpClientTesting,
} from '@angular/common/http/testing'; } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
@@ -17,8 +21,12 @@ describe(`BrowserXSRFService`, () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ HttpClientTestingModule ], imports: [],
providers: [ BrowserXSRFService ], providers: [
BrowserXSRFService,
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
],
}); });
httpClient = TestBed.inject(HttpClient); httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController); httpTestingController = TestBed.inject(HttpTestingController);

View File

@@ -2,10 +2,12 @@ import {
HTTP_INTERCEPTORS, HTTP_INTERCEPTORS,
HttpHeaders, HttpHeaders,
HttpXsrfTokenExtractor, HttpXsrfTokenExtractor,
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http'; } from '@angular/common/http';
import { import {
HttpClientTestingModule,
HttpTestingController, HttpTestingController,
provideHttpClientTesting,
} from '@angular/common/http/testing'; } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
@@ -20,7 +22,7 @@ import { XsrfInterceptor } from './xsrf.interceptor';
describe(`XsrfInterceptor`, () => { describe(`XsrfInterceptor`, () => {
let service: DspaceRestService; let service: DspaceRestService;
let httpMock: HttpTestingController; let httpMock: HttpTestingController;
let cookieService: CookieService; let cookieService: CookieServiceMock;
// mock XSRF token // mock XSRF token
const testToken = 'test-token'; const testToken = 'test-token';
@@ -33,45 +35,52 @@ describe(`XsrfInterceptor`, () => {
const mockStatusCode = 200; const mockStatusCode = 200;
const mockStatusText = 'SUCCESS'; const mockStatusText = 'SUCCESS';
const testUrl = 'https://rest.com/server/api/core/items';
beforeEach(() => { beforeEach(() => {
const tokenExtractor = new HttpXsrfTokenExtractorMock(testToken);
cookieService = new CookieServiceMock();
const interceptor = new XsrfInterceptor(tokenExtractor,cookieService as any);
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [HttpClientTestingModule], imports: [],
providers: [ providers: [
DspaceRestService, DspaceRestService,
{ {
provide: HTTP_INTERCEPTORS, provide: HTTP_INTERCEPTORS,
useClass: XsrfInterceptor, useValue: interceptor,
multi: true, multi: true,
}, },
{ provide: HttpXsrfTokenExtractor, useValue: new HttpXsrfTokenExtractorMock(testToken) }, { provide: HttpXsrfTokenExtractor, useValue: tokenExtractor },
{ provide: CookieService, useValue: new CookieServiceMock() }, { provide: CookieService, useValue: cookieService },
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
], ],
}); });
service = TestBed.get(DspaceRestService); service = TestBed.get(DspaceRestService);
httpMock = TestBed.get(HttpTestingController); httpMock = TestBed.get(HttpTestingController);
cookieService = TestBed.get(CookieService);
}); });
it('should change withCredentials to true at all times', (done) => { it('should change withCredentials to true at all times', (done) => {
service.request(RestRequestMethod.POST, 'server/api/core/items', 'test', { withCredentials: false }).subscribe((response) => { service.request(RestRequestMethod.POST, testUrl, 'test', { withCredentials: false }).subscribe((response) => {
expect(response).toBeTruthy(); expect(response).toBeTruthy();
done(); done();
}); });
const httpRequest = httpMock.expectOne('server/api/core/items'); const httpRequest = httpMock.expectOne(testUrl);
expect(httpRequest.request.withCredentials).toBeTrue(); expect(httpRequest.request.withCredentials).toBeTrue();
httpRequest.flush(mockPayload, { status: mockStatusCode, statusText: mockStatusText }); httpRequest.flush(mockPayload, { status: mockStatusCode, statusText: mockStatusText });
}); });
it('should add an X-XSRF-TOKEN header when we are sending an HTTP POST request', (done) => { it('should add an X-XSRF-TOKEN header when we are sending an HTTP POST request', (done) => {
service.request(RestRequestMethod.POST, 'server/api/core/items', 'test').subscribe((response) => { service.request(RestRequestMethod.POST, testUrl, 'test').subscribe((response) => {
expect(response).toBeTruthy(); expect(response).toBeTruthy();
done(); done();
}); });
const httpRequest = httpMock.expectOne('server/api/core/items'); const httpRequest = httpMock.expectOne(testUrl);
expect(httpRequest.request.headers.has('X-XSRF-TOKEN')).toBeTrue(); expect(httpRequest.request.headers.has('X-XSRF-TOKEN')).toBeTrue();
expect(httpRequest.request.withCredentials).toBeTrue(); expect(httpRequest.request.withCredentials).toBeTrue();
@@ -83,12 +92,12 @@ describe(`XsrfInterceptor`, () => {
}); });
it('should NOT add an X-XSRF-TOKEN header when we are sending an HTTP GET request', (done) => { it('should NOT add an X-XSRF-TOKEN header when we are sending an HTTP GET request', (done) => {
service.request(RestRequestMethod.GET, 'server/api/core/items').subscribe((response) => { service.request(RestRequestMethod.GET, testUrl).subscribe((response) => {
expect(response).toBeTruthy(); expect(response).toBeTruthy();
done(); done();
}); });
const httpRequest = httpMock.expectOne('server/api/core/items'); const httpRequest = httpMock.expectOne(testUrl);
expect(httpRequest.request.headers.has('X-XSRF-TOKEN')).toBeFalse(); expect(httpRequest.request.headers.has('X-XSRF-TOKEN')).toBeFalse();
expect(httpRequest.request.withCredentials).toBeTrue(); expect(httpRequest.request.withCredentials).toBeTrue();
@@ -115,7 +124,7 @@ describe(`XsrfInterceptor`, () => {
// Create a mock XSRF token to be returned in response within DSPACE-XSRF-TOKEN header // Create a mock XSRF token to be returned in response within DSPACE-XSRF-TOKEN header
const mockNewXSRFToken = '123456789abcdefg'; const mockNewXSRFToken = '123456789abcdefg';
service.request(RestRequestMethod.GET, 'server/api/core/items').subscribe((response) => { service.request(RestRequestMethod.GET, testUrl).subscribe((response) => {
expect(response).toBeTruthy(); expect(response).toBeTruthy();
// ensure mock data (added in below flush() call) is returned. // ensure mock data (added in below flush() call) is returned.
@@ -135,7 +144,7 @@ describe(`XsrfInterceptor`, () => {
done(); done();
}); });
const httpRequest = httpMock.expectOne('server/api/core/items'); const httpRequest = httpMock.expectOne(testUrl);
// Flush & create mock response (including sending back a new XSRF token in header) // Flush & create mock response (including sending back a new XSRF token in header)
httpRequest.flush(mockPayload, { httpRequest.flush(mockPayload, {
@@ -153,7 +162,7 @@ describe(`XsrfInterceptor`, () => {
const mockErrorText = 'Forbidden'; const mockErrorText = 'Forbidden';
const mockErrorMessage = 'CSRF token mismatch'; const mockErrorMessage = 'CSRF token mismatch';
service.request(RestRequestMethod.GET, 'server/api/core/items').subscribe({ service.request(RestRequestMethod.GET, testUrl).subscribe({
error: (error: unknown) => { error: (error: unknown) => {
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error instanceof RequestError).toBeTrue(); expect(error instanceof RequestError).toBeTrue();
@@ -170,7 +179,7 @@ describe(`XsrfInterceptor`, () => {
}, },
}); });
const httpRequest = httpMock.expectOne('server/api/core/items'); const httpRequest = httpMock.expectOne(testUrl);
// Flush & create mock error response (including sending back a new XSRF token in header) // Flush & create mock error response (including sending back a new XSRF token in header)
httpRequest.flush(mockErrorMessage, { httpRequest.flush(mockErrorMessage, {

View File

@@ -1,16 +1,16 @@
<form [formGroup]="form" (ngSubmit)="submit()"> <form [formGroup]="form" (ngSubmit)="submit()">
<div class="form-group"> <div class="mb-3">
<div class="row mb-2"> <div class="row mb-2">
<div class="col-12 col-sm-6"> <div class="col-12 col-sm-6">
<label class="font-weight-bold" for="task">{{'curation.form.task-select.label' |translate }}</label> <label class="fw-bold form-label" for="task">{{'curation.form.task-select.label' |translate }}</label>
<select id="task" formControlName="task" class="form-control"> <select id="task" formControlName="task" class="form-select">
<option *ngFor="let task of tasks" [value]="task"> <option *ngFor="let task of tasks" [value]="task">
{{ 'curation-task.task.' + task + '.label' | translate }} {{ 'curation-task.task.' + task + '.label' | translate }}
</option> </option>
</select> </select>
</div> </div>
<div *ngIf="!hasHandleValue()" class="col-12 col-sm-6"> <div *ngIf="!hasHandleValue()" class="col-12 col-sm-6">
<label class="font-weight-bold" for="handle">{{'curation.form.handle.label' |translate }}</label> <label class="fw-bold form-label" for="handle">{{'curation.form.handle.label' |translate }}</label>
<input id="handle" class="form-control" formControlName="handle"> <input id="handle" class="form-control" formControlName="handle">
<small class="text-muted">{{'curation.form.handle.hint' |translate }}</small> <small class="text-muted">{{'curation.form.handle.hint' |translate }}</small>
</div> </div>

View File

@@ -24,26 +24,26 @@
} }
&.ds-warning { &.ds-warning {
background-color: var(--ds-warning-bg); background-color: var(--ds-warning-bg-subtle);
.ds-flex-cell { .ds-flex-cell {
border: 1px solid var(--bs-warning); border: 1px solid var(--bs-warning-border-subtle);
} }
} }
&.ds-danger { &.ds-danger {
background-color: var(--ds-danger-bg); background-color: var(--ds-danger-bg-subtle);
.ds-flex-cell { .ds-flex-cell {
border: 1px solid var(--bs-danger); border: 1px solid var(--bs-danger-border-subtle);
} }
} }
&.ds-success { &.ds-success {
background-color: var(--ds-success-bg); background-color: var(--ds-success-bg-subtle);
.ds-flex-cell { .ds-flex-cell {
border: 1px solid var(--bs-success); border: 1px solid var(--bs-success-border-subtle);
} }
} }
} }

View File

@@ -24,7 +24,7 @@
{{ (enabledFreeTextEditing ? dsoType + '.edit.metadata.edit.buttons.disable-free-text-editing' : dsoType + '.edit.metadata.edit.buttons.enable-free-text-editing') | translate }} {{ (enabledFreeTextEditing ? dsoType + '.edit.metadata.edit.buttons.disable-free-text-editing' : dsoType + '.edit.metadata.edit.buttons.enable-free-text-editing') | translate }}
</button> </button>
<div *ngIf="!isVirtual && !mdValue.editing && mdValue.newValue.authority && mdValue.newValue.confidence !== ConfidenceTypeEnum.CF_UNSET && mdValue.newValue.confidence !== ConfidenceTypeEnum.CF_NOVALUE"> <div *ngIf="!isVirtual && !mdValue.editing && mdValue.newValue.authority && mdValue.newValue.confidence !== ConfidenceTypeEnum.CF_UNSET && mdValue.newValue.confidence !== ConfidenceTypeEnum.CF_NOVALUE">
<span class="badge badge-light border" > <span class="badge bg-light border" >
<i dsAuthorityConfidenceState <i dsAuthorityConfidenceState
class="fas fa-fw p-0" class="fas fa-fw p-0"
aria-hidden="true" aria-hidden="true"
@@ -37,7 +37,7 @@
<div class="mt-2" *ngIf=" mdValue.editing && (isAuthorityControlled() | async) && (isSuggesterVocabulary() | async)"> <div class="mt-2" *ngIf=" mdValue.editing && (isAuthorityControlled() | async) && (isSuggesterVocabulary() | async)">
<div class="btn-group w-75"> <div class="btn-group w-75">
<i dsAuthorityConfidenceState <i dsAuthorityConfidenceState
class="fas fa-fw p-0 mr-1 mt-auto mb-auto" class="fas fa-fw p-0 me-1 mt-auto mb-auto"
aria-hidden="true" aria-hidden="true"
[authorityValue]="mdValue.newValue.confidence" [authorityValue]="mdValue.newValue.confidence"
[iconMode]="true" [iconMode]="true"
@@ -60,7 +60,7 @@
</div> </div>
</div> </div>
<div class="d-flex" *ngIf="mdRepresentation"> <div class="d-flex" *ngIf="mdRepresentation">
<a class="mr-2" target="_blank" [routerLink]="mdRepresentationItemRoute$ | async">{{ mdRepresentationName$ | async }}</a> <a class="me-2" target="_blank" [routerLink]="mdRepresentationItemRoute$ | async">{{ mdRepresentationName$ | async }}</a>
<ds-type-badge [object]="mdRepresentation"></ds-type-badge> <ds-type-badge [object]="mdRepresentation"></ds-type-badge>
</div> </div>
</div> </div>

View File

@@ -14,3 +14,6 @@
.cdk-drag-placeholder { .cdk-drag-placeholder {
opacity: 0; opacity: 0;
} }
.badge.bg-light {
color: var(--bs-gray-900);
}

View File

@@ -1,24 +1,24 @@
<div class="item-metadata" *ngIf="form"> <div class="item-metadata" *ngIf="form">
<div class="button-row top d-flex my-2 space-children-mr ml-gap"> <div class="button-row top d-flex my-2 space-children-mr ms-gap">
<button class="mr-auto btn btn-success" id="dso-add-btn" [dsBtnDisabled]="form.newValue || (saving$ | async)" <button class="me-auto btn btn-success" id="dso-add-btn" [dsBtnDisabled]="form.newValue || (saving$ | async)"
[attr.aria-label]="dsoType + '.edit.metadata.add-button' | translate" [attr.aria-label]="dsoType + '.edit.metadata.add-button' | translate"
[title]="dsoType + '.edit.metadata.add-button' | translate" [title]="dsoType + '.edit.metadata.add-button' | translate"
(click)="add()"><i class="fas fa-plus" aria-hidden="true"></i> (click)="add()"><i class="fas fa-plus" aria-hidden="true"></i>
<span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.add-button' | translate }}</span> <span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.add-button' | translate }}</span>
</button> </button>
<button class="btn btn-warning ml-1" id="dso-reinstate-btn" *ngIf="isReinstatable" [dsBtnDisabled]="(saving$ | async)" <button class="btn btn-warning ms-1" id="dso-reinstate-btn" *ngIf="isReinstatable" [dsBtnDisabled]="(saving$ | async)"
[attr.aria-label]="dsoType + '.edit.metadata.reinstate-button' | translate" [attr.aria-label]="dsoType + '.edit.metadata.reinstate-button' | translate"
[title]="dsoType + '.edit.metadata.reinstate-button' | translate" [title]="dsoType + '.edit.metadata.reinstate-button' | translate"
(click)="reinstate()"><i class="fas fa-undo-alt" aria-hidden="true"></i> (click)="reinstate()"><i class="fas fa-undo-alt" aria-hidden="true"></i>
<span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.reinstate-button' | translate }}</span> <span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.reinstate-button' | translate }}</span>
</button> </button>
<button class="btn btn-primary ml-1" id="dso-save-btn" [dsBtnDisabled]="!hasChanges || (saving$ | async)" <button class="btn btn-primary ms-1" id="dso-save-btn" [dsBtnDisabled]="!hasChanges || (saving$ | async)"
[attr.aria-label]="dsoType + '.edit.metadata.save-button' | translate" [attr.aria-label]="dsoType + '.edit.metadata.save-button' | translate"
[title]="dsoType + '.edit.metadata.save-button' | translate" [title]="dsoType + '.edit.metadata.save-button' | translate"
(click)="submit()"><i class="fas fa-save" aria-hidden="true"></i> (click)="submit()"><i class="fas fa-save" aria-hidden="true"></i>
<span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.save-button' | translate }}</span> <span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.save-button' | translate }}</span>
</button> </button>
<button class="btn btn-danger ml-1" id="dso-discard-btn" *ngIf="!isReinstatable" <button class="btn btn-danger ms-1" id="dso-discard-btn" *ngIf="!isReinstatable"
[attr.aria-label]="dsoType + '.edit.metadata.discard-button' | translate" [attr.aria-label]="dsoType + '.edit.metadata.discard-button' | translate"
[title]="dsoType + '.edit.metadata.discard-button' | translate" [title]="dsoType + '.edit.metadata.discard-button' | translate"
[dsBtnDisabled]="!hasChanges || (saving$ | async)" [dsBtnDisabled]="!hasChanges || (saving$ | async)"
@@ -76,7 +76,7 @@
<ds-alert [content]="dsoType + '.edit.metadata.empty'" [type]="AlertTypeEnum.Info"></ds-alert> <ds-alert [content]="dsoType + '.edit.metadata.empty'" [type]="AlertTypeEnum.Info"></ds-alert>
</div> </div>
<div class="button-row bottom d-inline-block w-100"> <div class="button-row bottom d-inline-block w-100">
<div class="mt-2 float-right space-children-mr ml-gap"> <div class="mt-2 float-end space-children-mr ms-gap">
<button class="btn btn-warning" *ngIf="isReinstatable" [dsBtnDisabled]="(saving$ | async)" <button class="btn btn-warning" *ngIf="isReinstatable" [dsBtnDisabled]="(saving$ | async)"
[attr.aria-label]="dsoType + '.edit.metadata.reinstate-button' | translate" [attr.aria-label]="dsoType + '.edit.metadata.reinstate-button' | translate"
[title]="dsoType + '.edit.metadata.reinstate-button' | translate" [title]="dsoType + '.edit.metadata.reinstate-button' | translate"

View File

@@ -1,6 +1,6 @@
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'"> <div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<ds-truncatable [id]="dso.id"> <ds-truncatable [id]="dso.id">
<div class="position-absolute ml-1"> <div class="position-absolute ms-1">
<ng-content></ng-content> <ng-content></ng-content>
</div> </div>
<a *ngIf="linkType !== linkTypes.None" <a *ngIf="linkType !== linkTypes.None"

View File

@@ -1,6 +1,6 @@
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'"> <div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<ds-truncatable [id]="dso.id"> <ds-truncatable [id]="dso.id">
<div class="position-absolute ml-1"> <div class="position-absolute ms-1">
<ng-content></ng-content> <ng-content></ng-content>
</div> </div>
<a *ngIf="linkType !== linkTypes.None" <a *ngIf="linkType !== linkTypes.None"

View File

@@ -1,6 +1,6 @@
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'"> <div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<ds-truncatable [id]="dso.id"> <ds-truncatable [id]="dso.id">
<div class="position-absolute ml-1"> <div class="position-absolute ms-1">
<ng-content></ng-content> <ng-content></ng-content>
</div> </div>
<a *ngIf="linkType !== linkTypes.None" <a *ngIf="linkType !== linkTypes.None"

View File

@@ -17,6 +17,8 @@ import { TruncatablePartComponent } from '../../../../../shared/truncatable/trun
@listableObjectComponent('JournalIssueSearchResult', ViewMode.ListElement, Context.SideBarSearchModal) @listableObjectComponent('JournalIssueSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
@listableObjectComponent('JournalIssueSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent) @listableObjectComponent('JournalIssueSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
@listableObjectComponent('JournalIssueSearchResult', ViewMode.ListElement, Context.ScopeSelectorModal)
@listableObjectComponent('JournalIssueSearchResult', ViewMode.ListElement, Context.ScopeSelectorModalCurrent)
@Component({ @Component({
selector: 'ds-journal-issue-sidebar-search-list-element', selector: 'ds-journal-issue-sidebar-search-list-element',
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html', templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html',

View File

@@ -17,6 +17,8 @@ import { TruncatablePartComponent } from '../../../../../shared/truncatable/trun
@listableObjectComponent('JournalVolumeSearchResult', ViewMode.ListElement, Context.SideBarSearchModal) @listableObjectComponent('JournalVolumeSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
@listableObjectComponent('JournalVolumeSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent) @listableObjectComponent('JournalVolumeSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
@listableObjectComponent('JournalVolumeSearchResult', ViewMode.ListElement, Context.ScopeSelectorModal)
@listableObjectComponent('JournalVolumeSearchResult', ViewMode.ListElement, Context.ScopeSelectorModalCurrent)
@Component({ @Component({
selector: 'ds-journal-volume-sidebar-search-list-element', selector: 'ds-journal-volume-sidebar-search-list-element',
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html', templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html',

View File

@@ -17,6 +17,8 @@ import { TruncatablePartComponent } from '../../../../../shared/truncatable/trun
@listableObjectComponent('JournalSearchResult', ViewMode.ListElement, Context.SideBarSearchModal) @listableObjectComponent('JournalSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
@listableObjectComponent('JournalSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent) @listableObjectComponent('JournalSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
@listableObjectComponent('JournalSearchResult', ViewMode.ListElement, Context.ScopeSelectorModal)
@listableObjectComponent('JournalSearchResult', ViewMode.ListElement, Context.ScopeSelectorModalCurrent)
@Component({ @Component({
selector: 'ds-journal-sidebar-search-list-element', selector: 'ds-journal-sidebar-search-list-element',
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html', templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html',

View File

@@ -1,6 +1,6 @@
<ds-results-back-button *ngIf="showBackButton$ | async" [back]="back"></ds-results-back-button> <ds-results-back-button *ngIf="showBackButton$ | async" [back]="back"></ds-results-back-button>
<div class="d-flex flex-row"> <div class="d-flex flex-row">
<ds-item-page-title-field [item]="object" class="mr-auto"> <ds-item-page-title-field [item]="object" class="me-auto">
</ds-item-page-title-field> </ds-item-page-title-field>
<ds-dso-edit-menu></ds-dso-edit-menu> <ds-dso-edit-menu></ds-dso-edit-menu>
</div> </div>

View File

@@ -1,6 +1,6 @@
<ds-results-back-button *ngIf="showBackButton$ | async" [back]="back"></ds-results-back-button> <ds-results-back-button *ngIf="showBackButton$ | async" [back]="back"></ds-results-back-button>
<div class="d-flex flex-row"> <div class="d-flex flex-row">
<ds-item-page-title-field [item]="object" class="mr-auto"> <ds-item-page-title-field [item]="object" class="me-auto">
</ds-item-page-title-field> </ds-item-page-title-field>
<ds-dso-edit-menu></ds-dso-edit-menu> <ds-dso-edit-menu></ds-dso-edit-menu>
</div> </div>

View File

@@ -1,6 +1,6 @@
<ds-results-back-button *ngIf="showBackButton$ | async" [back]="back"></ds-results-back-button> <ds-results-back-button *ngIf="showBackButton$ | async" [back]="back"></ds-results-back-button>
<div class="d-flex flex-row"> <div class="d-flex flex-row">
<ds-item-page-title-field [item]="object" class="mr-auto"> <ds-item-page-title-field [item]="object" class="me-auto">
</ds-item-page-title-field> </ds-item-page-title-field>
<ds-dso-edit-menu></ds-dso-edit-menu> <ds-dso-edit-menu></ds-dso-edit-menu>
</div> </div>
@@ -38,7 +38,7 @@
</a> </a>
</div> </div>
</div> </div>
<div class="mt-5 w-100"> <div class="mt-5 w-100 px-0">
<ds-tabbed-related-entities-search [item]="object" <ds-tabbed-related-entities-search [item]="object"
[relationTypes]="[{ [relationTypes]="[{
label: 'isJournalOfPublication', label: 'isJournalOfPublication',

View File

@@ -1,6 +1,6 @@
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'"> <div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<ds-truncatable [id]="dso.id"> <ds-truncatable [id]="dso.id">
<div class="position-absolute ml-1"> <div class="position-absolute ms-1">
<ng-content></ng-content> <ng-content></ng-content>
</div> </div>
<a *ngIf="linkType !== linkTypes.None" <a *ngIf="linkType !== linkTypes.None"

View File

@@ -1,6 +1,6 @@
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'"> <div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<ds-truncatable [id]="dso.id"> <ds-truncatable [id]="dso.id">
<div class="position-absolute ml-1"> <div class="position-absolute ms-1">
<ng-content></ng-content> <ng-content></ng-content>
</div> </div>
<a *ngIf="linkType !== linkTypes.None" <a *ngIf="linkType !== linkTypes.None"

View File

@@ -1,6 +1,6 @@
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'"> <div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<ds-truncatable [id]="dso.id"> <ds-truncatable [id]="dso.id">
<div class="position-absolute ml-1"> <div class="position-absolute ms-1">
<ng-content></ng-content> <ng-content></ng-content>
</div> </div>
<a *ngIf="linkType !== linkTypes.None" <a *ngIf="linkType !== linkTypes.None"

View File

@@ -16,6 +16,8 @@ import { TruncatablePartComponent } from '../../../../../shared/truncatable/trun
@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.SideBarSearchModal) @listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent) @listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.ScopeSelectorModal)
@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.ScopeSelectorModalCurrent)
@Component({ @Component({
selector: 'ds-org-unit-sidebar-search-list-element', selector: 'ds-org-unit-sidebar-search-list-element',
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html', templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html',

View File

@@ -23,6 +23,8 @@ import { TruncatablePartComponent } from '../../../../../shared/truncatable/trun
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModal) @listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent) @listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.ScopeSelectorModal)
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.ScopeSelectorModalCurrent)
@Component({ @Component({
selector: 'ds-person-sidebar-search-list-element', selector: 'ds-person-sidebar-search-list-element',
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html', templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html',

View File

@@ -16,6 +16,8 @@ import { TruncatablePartComponent } from '../../../../../shared/truncatable/trun
@listableObjectComponent('ProjectSearchResult', ViewMode.ListElement, Context.SideBarSearchModal) @listableObjectComponent('ProjectSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
@listableObjectComponent('ProjectSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent) @listableObjectComponent('ProjectSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
@listableObjectComponent('ProjectSearchResult', ViewMode.ListElement, Context.ScopeSelectorModal)
@listableObjectComponent('ProjectSearchResult', ViewMode.ListElement, Context.ScopeSelectorModalCurrent)
@Component({ @Component({
selector: 'ds-project-sidebar-search-list-element', selector: 'ds-project-sidebar-search-list-element',
templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html', templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html',

View File

@@ -1,6 +1,6 @@
<ds-results-back-button *ngIf="showBackButton$ | async" [back]="back"></ds-results-back-button> <ds-results-back-button *ngIf="showBackButton$ | async" [back]="back"></ds-results-back-button>
<div class="d-flex flex-row"> <div class="d-flex flex-row">
<ds-item-page-title-field [item]="object" class="mr-auto"> <ds-item-page-title-field [item]="object" class="me-auto">
</ds-item-page-title-field> </ds-item-page-title-field>
<ds-dso-edit-menu></ds-dso-edit-menu> <ds-dso-edit-menu></ds-dso-edit-menu>
</div> </div>
@@ -59,7 +59,7 @@
</a> </a>
</div> </div>
</div> </div>
<div class="mt-5 w-100"> <div class="mt-5 w-100 px-0">
<ds-tabbed-related-entities-search [item]="object" <ds-tabbed-related-entities-search [item]="object"
[relationTypes]="[{ [relationTypes]="[{
label: 'isOrgUnitOfPerson', label: 'isOrgUnitOfPerson',

View File

@@ -1,6 +1,6 @@
<ds-results-back-button *ngIf="showBackButton$ | async" [back]="back"></ds-results-back-button> <ds-results-back-button *ngIf="showBackButton$ | async" [back]="back"></ds-results-back-button>
<div class="d-flex flex-row"> <div class="d-flex flex-row">
<ds-item-page-title-field class="mr-auto" [item]="object"> <ds-item-page-title-field class="me-auto" [item]="object">
</ds-item-page-title-field> </ds-item-page-title-field>
<ds-dso-edit-menu></ds-dso-edit-menu> <ds-dso-edit-menu></ds-dso-edit-menu>
</div> </div>
@@ -55,7 +55,7 @@
</a> </a>
</div> </div>
</div> </div>
<div class="mt-5 w-100"> <div class="mt-5 w-100 px-0">
<ds-tabbed-related-entities-search [item]="object" <ds-tabbed-related-entities-search [item]="object"
[relationTypes]="[{ [relationTypes]="[{
label: 'isAuthorOfPublication', label: 'isAuthorOfPublication',

View File

@@ -1,6 +1,6 @@
<ds-results-back-button *ngIf="showBackButton$ | async" [back]="back"></ds-results-back-button> <ds-results-back-button *ngIf="showBackButton$ | async" [back]="back"></ds-results-back-button>
<div class="d-flex flex-row"> <div class="d-flex flex-row">
<ds-item-page-title-field [item]="object" class="mr-auto"> <ds-item-page-title-field [item]="object" class="me-auto">
</ds-item-page-title-field> </ds-item-page-title-field>
<ds-dso-edit-menu></ds-dso-edit-menu> <ds-dso-edit-menu></ds-dso-edit-menu>
</div> </div>

View File

@@ -1,5 +1,5 @@
<div class="d-flex"> <div class="d-flex">
<!-- <div class="person-thumbnail pr-2">--> <!-- <div class="person-thumbnail pe-2">-->
<!-- <ds-themed-thumbnail [thumbnail]="dso?.thumbnail | async" [defaultImage]="'assets/images/orgunit-placeholder.svg'"></ds-themed-thumbnail>--> <!-- <ds-themed-thumbnail [thumbnail]="dso?.thumbnail | async" [defaultImage]="'assets/images/orgunit-placeholder.svg'"></ds-themed-thumbnail>-->
<!-- </div>--> <!-- </div>-->
<div class="flex-grow-1"> <div class="flex-grow-1">

View File

@@ -1,7 +1,6 @@
<div class="modal-header"> <div class="modal-header">
<h4 class="modal-title">New name variant</h4> <h4 class="modal-title">New name variant</h4>
<button type="button" class="close" aria-label="Close" (click)="modal.dismiss('Cross click')"> <button type="button" class="btn-close" aria-label="Close" (click)="modal.dismiss('Cross click')">
<span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">

View File

@@ -43,7 +43,7 @@ describe('NameVariantModalComponent', () => {
}); });
it('when close button is clicked, dismiss should be called on the modal', () => { it('when close button is clicked, dismiss should be called on the modal', () => {
debugElement.query(By.css('button.close')).triggerEventHandler('click', {}); debugElement.query(By.css('button.btn-close')).triggerEventHandler('click', {});
expect(modal.dismiss).toHaveBeenCalled(); expect(modal.dismiss).toHaveBeenCalled();
}); });

View File

@@ -1,4 +1,4 @@
<footer class="text-lg-start"> <footer>
<div *ngIf="showTopFooter" class="top-footer"> <div *ngIf="showTopFooter" class="top-footer">
<!-- Grid container --> <!-- Grid container -->
<div class=" container p-4"> <div class=" container p-4">

View File

@@ -68,6 +68,10 @@
} }
} }
} }
.btn {
box-shadow: none;
}
} }
} }

View File

@@ -5,7 +5,7 @@
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<span class="font-weight-bold">{{'forgot-password.form.identification.email' | translate}} </span> <span class="fw-bold form-label">{{'forgot-password.form.identification.email' | translate}} </span>
<span [attr.data-test]="'email' | dsBrowserOnly">{{(registration$ |async).email}}</span> <span [attr.data-test]="'email' | dsBrowserOnly">{{(registration$ |async).email}}</span>
</div> </div>
</div> </div>

View File

@@ -56,11 +56,9 @@ describe('ContextHelpToggleComponent', () => {
}); });
it('clicking the button should toggle context help icon visibility', fakeAsync(() => { it('clicking the button should toggle context help icon visibility', fakeAsync(() => {
fixture.whenStable().then(() => { fixture.debugElement.query(By.css('a')).nativeElement.click();
fixture.debugElement.query(By.css('a')).nativeElement.click(); tick();
tick(); expect(contextHelpService.toggleIcons).toHaveBeenCalled();
expect(contextHelpService.toggleIcons).toHaveBeenCalled();
});
})); }));
}); });

Some files were not shown because too many files have changed in this diff Show More