mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-13 21:13:07 +00:00
Merge remote-tracking branch 'lyrasis/dspace-7_x' into task/dspace-7_x/CST-14904
# Conflicts: # src/app/shared/shared.module.ts
This commit is contained in:
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
# Create a matrix of Node versions to test against (in parallel)
|
# Create a matrix of Node versions to test against (in parallel)
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [16.x, 18.x]
|
node-version: [18.x, 20.x]
|
||||||
# Do NOT exit immediately if one matrix job fails
|
# Do NOT exit immediately if one matrix job fails
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
# These are the actual CI steps to perform per job
|
# These are the actual CI steps to perform per job
|
||||||
|
@@ -396,3 +396,17 @@ vocabularies:
|
|||||||
comcolSelectionSort:
|
comcolSelectionSort:
|
||||||
sortField: 'dc.title'
|
sortField: 'dc.title'
|
||||||
sortDirection: 'ASC'
|
sortDirection: 'ASC'
|
||||||
|
|
||||||
|
# Live Region configuration
|
||||||
|
# Live Region as defined by w3c, https://www.w3.org/TR/wai-aria-1.1/#terms:
|
||||||
|
# Live regions are perceivable regions of a web page that are typically updated as a
|
||||||
|
# result of an external event when user focus may be elsewhere.
|
||||||
|
#
|
||||||
|
# The DSpace live region is a component present at the bottom of all pages that is invisible by default, but is useful
|
||||||
|
# for screen readers. Any message pushed to the live region will be announced by the screen reader. These messages
|
||||||
|
# usually contain information about changes on the page that might not be in focus.
|
||||||
|
liveRegion:
|
||||||
|
# The duration after which messages disappear from the live region in milliseconds
|
||||||
|
messageTimeOutDurationMs: 30000
|
||||||
|
# The visibility of the live region. Setting this to true is only useful for debugging purposes.
|
||||||
|
isVisible: false
|
||||||
|
@@ -100,7 +100,7 @@
|
|||||||
"filesize": "^6.1.0",
|
"filesize": "^6.1.0",
|
||||||
"http-proxy-middleware": "^1.0.5",
|
"http-proxy-middleware": "^1.0.5",
|
||||||
"http-terminator": "^3.2.0",
|
"http-terminator": "^3.2.0",
|
||||||
"isbot": "^3.6.10",
|
"isbot": "^5.1.17",
|
||||||
"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",
|
||||||
|
@@ -28,7 +28,7 @@ import * as 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';
|
||||||
import isbot from 'isbot';
|
import { isbot } from 'isbot';
|
||||||
import { createCertificate } from 'pem';
|
import { createCertificate } from 'pem';
|
||||||
import { createServer } from 'https';
|
import { createServer } from 'https';
|
||||||
import { json } from 'body-parser';
|
import { json } from 'body-parser';
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
<ng-container *ngIf="relationTypes.length > 1">
|
<ng-container *ngIf="relationTypes.length > 1">
|
||||||
<ul ngbNav #tabs="ngbNav" [destroyOnHide]="true" [activeId]="activeTab$ | async" (navChange)="onTabChange($event)" class="nav-tabs">
|
<ul ngbNav #tabs="ngbNav" [destroyOnHide]="true" [activeId]="activeTab$ | async" (navChange)="onTabChange($event)" class="nav-tabs" role="tablist">
|
||||||
<li *ngFor="let relationType of relationTypes" [ngbNavItem]="relationType.filter" rel="presentation">
|
<li *ngFor="let relationType of relationTypes" [ngbNavItem]="relationType.filter" role="presentation">
|
||||||
<a ngbNavLink>{{'item.page.relationships.' + relationType.label | translate}}</a>
|
<a ngbNavLink role="tab">
|
||||||
|
{{'item.page.relationships.' + relationType.label | translate}}
|
||||||
|
</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<ds-related-entities-search [item]="item"
|
<ds-related-entities-search [item]="item"
|
||||||
|
@@ -15,8 +15,7 @@
|
|||||||
[attr.aria-expanded]="isActive"
|
[attr.aria-expanded]="isActive"
|
||||||
[attr.aria-controls]="expandableNavbarSectionId(section.id)"
|
[attr.aria-controls]="expandableNavbarSectionId(section.id)"
|
||||||
class="d-flex flex-row flex-nowrap align-items-center gapx-1 ds-menu-toggler-wrapper"
|
class="d-flex flex-row flex-nowrap align-items-center gapx-1 ds-menu-toggler-wrapper"
|
||||||
[class.disabled]="section.model?.disabled"
|
[class.disabled]="section.model?.disabled">
|
||||||
id="browseDropdown">
|
|
||||||
<span class="flex-fill">
|
<span class="flex-fill">
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngComponentOutlet="(sectionMap$ | async).get(section.id).component; injector: (sectionMap$ | async).get(section.id).injector;"></ng-container>
|
*ngComponentOutlet="(sectionMap$ | async).get(section.id).component; injector: (sectionMap$ | async).get(section.id).injector;"></ng-container>
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
/** Mobile menu styling **/
|
/** Mobile menu styling **/
|
||||||
@media screen and (max-width: map-get($grid-breakpoints, md)-0.02) {
|
@media screen and (max-width: map-get($grid-breakpoints, md)-0.02) {
|
||||||
.navbar {
|
.navbar {
|
||||||
width: 100%;
|
width: 100vw;
|
||||||
background-color: var(--bs-white);
|
background-color: var(--bs-white);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@@ -31,3 +31,5 @@
|
|||||||
<div class="ds-full-screen-loader" *ngIf="shouldShowFullscreenLoader">
|
<div class="ds-full-screen-loader" *ngIf="shouldShowFullscreenLoader">
|
||||||
<ds-themed-loading [showMessage]="false"></ds-themed-loading>
|
<ds-themed-loading [showMessage]="false"></ds-themed-loading>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ds-live-region></ds-live-region>
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control" [(ngModel)]="searchText" (keyup.enter)="search()"
|
<input type="text" class="form-control" [(ngModel)]="searchText" (keyup.enter)="search()"
|
||||||
|
[attr.aria-label]="'vocabulary-treeview.search.form.search-placeholder' | translate"
|
||||||
[placeholder]="'vocabulary-treeview.search.form.search-placeholder' | translate">
|
[placeholder]="'vocabulary-treeview.search.form.search-placeholder' | translate">
|
||||||
<div class="input-group-append" id="button-addon4">
|
<div class="input-group-append" id="button-addon4">
|
||||||
<button class="btn btn-outline-primary" type="button" (click)="search()" [disabled]="!isSearchEnabled()">
|
<button class="btn btn-outline-primary" type="button" (click)="search()" [disabled]="!isSearchEnabled()">
|
||||||
|
3
src/app/shared/live-region/live-region.component.html
Normal file
3
src/app/shared/live-region/live-region.component.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<div class="live-region" [ngClass]="{'visually-hidden': !isVisible }" aria-live="assertive" role="log" aria-relevant="additions" aria-atomic="true">
|
||||||
|
<div class="live-region-message" *ngFor="let message of (messages$ | async)">{{ message }}</div>
|
||||||
|
</div>
|
13
src/app/shared/live-region/live-region.component.scss
Normal file
13
src/app/shared/live-region/live-region.component.scss
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
.live-region {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding-left: 60px;
|
||||||
|
height: 90px;
|
||||||
|
line-height: 18px;
|
||||||
|
color: var(--bs-white);
|
||||||
|
background-color: var(--bs-dark);
|
||||||
|
opacity: 0.94;
|
||||||
|
z-index: var(--ds-live-region-z-index);
|
||||||
|
}
|
57
src/app/shared/live-region/live-region.component.spec.ts
Normal file
57
src/app/shared/live-region/live-region.component.spec.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { LiveRegionComponent } from './live-region.component';
|
||||||
|
import { ComponentFixture, waitForAsync, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { LiveRegionService } from './live-region.service';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
describe('liveRegionComponent', () => {
|
||||||
|
let fixture: ComponentFixture<LiveRegionComponent>;
|
||||||
|
let liveRegionService: LiveRegionService;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
liveRegionService = jasmine.createSpyObj('liveRegionService', {
|
||||||
|
getMessages$: of(['message1', 'message2']),
|
||||||
|
getLiveRegionVisibility: false,
|
||||||
|
setLiveRegionVisibility: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
void TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
],
|
||||||
|
declarations: [LiveRegionComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: LiveRegionService, useValue: liveRegionService },
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(LiveRegionComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain the current live region messages', () => {
|
||||||
|
const messages = fixture.debugElement.queryAll(By.css('.live-region-message'));
|
||||||
|
|
||||||
|
expect(messages.length).toEqual(2);
|
||||||
|
expect(messages[0].nativeElement.textContent).toEqual('message1');
|
||||||
|
expect(messages[1].nativeElement.textContent).toEqual('message2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should respect the live region visibility', () => {
|
||||||
|
const liveRegion = fixture.debugElement.query(By.css('.live-region'));
|
||||||
|
expect(liveRegion).toBeDefined();
|
||||||
|
|
||||||
|
const liveRegionHidden = fixture.debugElement.query(By.css('.visually-hidden'));
|
||||||
|
expect(liveRegionHidden).toBeDefined();
|
||||||
|
|
||||||
|
liveRegionService.getLiveRegionVisibility = jasmine.createSpy('getLiveRegionVisibility').and.returnValue(true);
|
||||||
|
fixture = TestBed.createComponent(LiveRegionComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const liveRegionVisible = fixture.debugElement.query(By.css('.visually-hidden'));
|
||||||
|
expect(liveRegionVisible).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
31
src/app/shared/live-region/live-region.component.ts
Normal file
31
src/app/shared/live-region/live-region.component.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { LiveRegionService } from './live-region.service';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Live Region Component is an accessibility tool for screenreaders. When a change occurs on a page when the changed
|
||||||
|
* section is not in focus, a message should be displayed by this component so it can be announced by a screen reader.
|
||||||
|
*
|
||||||
|
* This component should not be used directly. Use the {@link LiveRegionService} to add messages.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: `ds-live-region`,
|
||||||
|
templateUrl: './live-region.component.html',
|
||||||
|
styleUrls: ['./live-region.component.scss'],
|
||||||
|
})
|
||||||
|
export class LiveRegionComponent implements OnInit {
|
||||||
|
|
||||||
|
protected isVisible: boolean;
|
||||||
|
|
||||||
|
protected messages$: Observable<string[]>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected liveRegionService: LiveRegionService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.isVisible = this.liveRegionService.getLiveRegionVisibility();
|
||||||
|
this.messages$ = this.liveRegionService.getMessages$();
|
||||||
|
}
|
||||||
|
}
|
9
src/app/shared/live-region/live-region.config.ts
Normal file
9
src/app/shared/live-region/live-region.config.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Config } from '../../../config/config.interface';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration interface used by the LiveRegionService
|
||||||
|
*/
|
||||||
|
export class LiveRegionConfig implements Config {
|
||||||
|
messageTimeOutDurationMs: number;
|
||||||
|
isVisible: boolean;
|
||||||
|
}
|
170
src/app/shared/live-region/live-region.service.spec.ts
Normal file
170
src/app/shared/live-region/live-region.service.spec.ts
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
import { LiveRegionService } from './live-region.service';
|
||||||
|
import { fakeAsync, tick, flush } from '@angular/core/testing';
|
||||||
|
import { UUIDService } from '../../core/shared/uuid.service';
|
||||||
|
|
||||||
|
describe('liveRegionService', () => {
|
||||||
|
let service: LiveRegionService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
service = new LiveRegionService(
|
||||||
|
new UUIDService(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('addMessage', () => {
|
||||||
|
it('should correctly add messages', () => {
|
||||||
|
expect(service.getMessages().length).toEqual(0);
|
||||||
|
|
||||||
|
service.addMessage('Message One');
|
||||||
|
expect(service.getMessages().length).toEqual(1);
|
||||||
|
expect(service.getMessages()[0]).toEqual('Message One');
|
||||||
|
|
||||||
|
service.addMessage('Message Two');
|
||||||
|
expect(service.getMessages().length).toEqual(2);
|
||||||
|
expect(service.getMessages()[1]).toEqual('Message Two');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('clearMessages', () => {
|
||||||
|
it('should clear the messages', () => {
|
||||||
|
expect(service.getMessages().length).toEqual(0);
|
||||||
|
|
||||||
|
service.addMessage('Message One');
|
||||||
|
service.addMessage('Message Two');
|
||||||
|
expect(service.getMessages().length).toEqual(2);
|
||||||
|
|
||||||
|
service.clear();
|
||||||
|
expect(service.getMessages().length).toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('messages$', () => {
|
||||||
|
it('should emit when a message is added and when a message is removed after the timeOut', fakeAsync(() => {
|
||||||
|
const results: string[][] = [];
|
||||||
|
|
||||||
|
service.getMessages$().subscribe((messages) => {
|
||||||
|
results.push(messages);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(results.length).toEqual(1);
|
||||||
|
expect(results[0]).toEqual([]);
|
||||||
|
|
||||||
|
service.addMessage('message');
|
||||||
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(results.length).toEqual(2);
|
||||||
|
expect(results[1]).toEqual(['message']);
|
||||||
|
|
||||||
|
tick(service.getMessageTimeOutMs());
|
||||||
|
|
||||||
|
expect(results.length).toEqual(3);
|
||||||
|
expect(results[2]).toEqual([]);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should only emit once when the messages are cleared', fakeAsync(() => {
|
||||||
|
const results: string[][] = [];
|
||||||
|
|
||||||
|
service.getMessages$().subscribe((messages) => {
|
||||||
|
results.push(messages);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(results.length).toEqual(1);
|
||||||
|
expect(results[0]).toEqual([]);
|
||||||
|
|
||||||
|
service.addMessage('Message One');
|
||||||
|
service.addMessage('Message Two');
|
||||||
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
expect(results.length).toEqual(3);
|
||||||
|
expect(results[2]).toEqual(['Message One', 'Message Two']);
|
||||||
|
|
||||||
|
service.clear();
|
||||||
|
flush();
|
||||||
|
|
||||||
|
expect(results.length).toEqual(4);
|
||||||
|
expect(results[3]).toEqual([]);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not pop messages added after clearing within timeOut period', fakeAsync(() => {
|
||||||
|
const results: string[][] = [];
|
||||||
|
|
||||||
|
service.getMessages$().subscribe((messages) => {
|
||||||
|
results.push(messages);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(results.length).toEqual(1);
|
||||||
|
expect(results[0]).toEqual([]);
|
||||||
|
|
||||||
|
service.addMessage('Message One');
|
||||||
|
tick(10000);
|
||||||
|
service.clear();
|
||||||
|
tick(15000);
|
||||||
|
service.addMessage('Message Two');
|
||||||
|
|
||||||
|
// Message Two should not be cleared after 5 more seconds
|
||||||
|
tick(5000);
|
||||||
|
|
||||||
|
expect(results.length).toEqual(4);
|
||||||
|
expect(results[3]).toEqual(['Message Two']);
|
||||||
|
|
||||||
|
// But should be cleared 30 seconds after it was added
|
||||||
|
tick(25000);
|
||||||
|
expect(results.length).toEqual(5);
|
||||||
|
expect(results[4]).toEqual([]);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should respect configured timeOut', fakeAsync(() => {
|
||||||
|
const results: string[][] = [];
|
||||||
|
|
||||||
|
service.getMessages$().subscribe((messages) => {
|
||||||
|
results.push(messages);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(results.length).toEqual(1);
|
||||||
|
expect(results[0]).toEqual([]);
|
||||||
|
|
||||||
|
const timeOutMs = 500;
|
||||||
|
service.setMessageTimeOutMs(timeOutMs);
|
||||||
|
|
||||||
|
service.addMessage('Message One');
|
||||||
|
tick(timeOutMs - 1);
|
||||||
|
|
||||||
|
expect(results.length).toEqual(2);
|
||||||
|
expect(results[1]).toEqual(['Message One']);
|
||||||
|
|
||||||
|
tick(1);
|
||||||
|
|
||||||
|
expect(results.length).toEqual(3);
|
||||||
|
expect(results[2]).toEqual([]);
|
||||||
|
|
||||||
|
const timeOutMsTwo = 50000;
|
||||||
|
service.setMessageTimeOutMs(timeOutMsTwo);
|
||||||
|
|
||||||
|
service.addMessage('Message Two');
|
||||||
|
tick(timeOutMsTwo - 1);
|
||||||
|
|
||||||
|
expect(results.length).toEqual(4);
|
||||||
|
expect(results[3]).toEqual(['Message Two']);
|
||||||
|
|
||||||
|
tick(1);
|
||||||
|
|
||||||
|
expect(results.length).toEqual(5);
|
||||||
|
expect(results[4]).toEqual([]);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('liveRegionVisibility', () => {
|
||||||
|
it('should be false by default', () => {
|
||||||
|
expect(service.getLiveRegionVisibility()).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly update', () => {
|
||||||
|
service.setLiveRegionVisibility(true);
|
||||||
|
expect(service.getLiveRegionVisibility()).toBeTrue();
|
||||||
|
service.setLiveRegionVisibility(false);
|
||||||
|
expect(service.getLiveRegionVisibility()).toBeFalse();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
132
src/app/shared/live-region/live-region.service.ts
Normal file
132
src/app/shared/live-region/live-region.service.ts
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
|
import { UUIDService } from '../../core/shared/uuid.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The LiveRegionService is responsible for handling the messages that are shown by the {@link LiveRegionComponent}.
|
||||||
|
* Use this service to add or remove messages to the Live Region.
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class LiveRegionService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected uuidService: UUIDService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The duration after which the messages disappear in milliseconds
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected messageTimeOutDurationMs: number = environment.liveRegion.messageTimeOutDurationMs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array containing the messages that should be shown in the live region,
|
||||||
|
* together with a uuid, so they can be uniquely identified
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected messages: { message: string, uuid: string }[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BehaviorSubject emitting the array with messages every time the array updates
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected messages$: BehaviorSubject<string[]> = new BehaviorSubject([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the live region should be visible
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected liveRegionIsVisible: boolean = environment.liveRegion.isVisible;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of the array with the current live region messages
|
||||||
|
*/
|
||||||
|
getMessages(): string[] {
|
||||||
|
return this.messages.map(messageObj => messageObj.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the BehaviorSubject emitting the array with messages every time the array updates
|
||||||
|
*/
|
||||||
|
getMessages$(): BehaviorSubject<string[]> {
|
||||||
|
return this.messages$;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a message to the live-region messages array
|
||||||
|
* @param message
|
||||||
|
* @return The uuid of the message
|
||||||
|
*/
|
||||||
|
addMessage(message: string): string {
|
||||||
|
const uuid = this.uuidService.generate();
|
||||||
|
this.messages.push({ message, uuid });
|
||||||
|
setTimeout(() => this.clearMessageByUUID(uuid), this.messageTimeOutDurationMs);
|
||||||
|
this.emitCurrentMessages();
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the live-region messages array
|
||||||
|
*/
|
||||||
|
clear() {
|
||||||
|
this.messages = [];
|
||||||
|
this.emitCurrentMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the message with the given UUID from the messages array
|
||||||
|
* @param uuid The uuid of the message to clear
|
||||||
|
*/
|
||||||
|
clearMessageByUUID(uuid: string) {
|
||||||
|
const index = this.messages.findIndex(messageObj => messageObj.uuid === uuid);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
this.messages.splice(index, 1);
|
||||||
|
this.emitCurrentMessages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the messages$ BehaviorSubject emit the current messages array
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected emitCurrentMessages() {
|
||||||
|
this.messages$.next(this.getMessages());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a boolean specifying whether the live region should be visible.
|
||||||
|
* Returns 'true' if the region should be visible and false otherwise.
|
||||||
|
*/
|
||||||
|
getLiveRegionVisibility(): boolean {
|
||||||
|
return this.liveRegionIsVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the visibility of the live region.
|
||||||
|
* Setting this to true will make the live region visible which is useful for debugging purposes.
|
||||||
|
* @param isVisible
|
||||||
|
*/
|
||||||
|
setLiveRegionVisibility(isVisible: boolean) {
|
||||||
|
this.liveRegionIsVisible = isVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current message timeOut duration in milliseconds
|
||||||
|
*/
|
||||||
|
getMessageTimeOutMs(): number {
|
||||||
|
return this.messageTimeOutDurationMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the message timeOut duration
|
||||||
|
* @param timeOutMs the message timeOut duration in milliseconds
|
||||||
|
*/
|
||||||
|
setMessageTimeOutMs(timeOutMs: number) {
|
||||||
|
this.messageTimeOutDurationMs = timeOutMs;
|
||||||
|
}
|
||||||
|
}
|
@@ -285,6 +285,7 @@ import { NgxPaginationModule } from 'ngx-pagination';
|
|||||||
import { ThemedLangSwitchComponent } from './lang-switch/themed-lang-switch.component';
|
import { ThemedLangSwitchComponent } from './lang-switch/themed-lang-switch.component';
|
||||||
import {ThemedUserMenuComponent} from './auth-nav-menu/user-menu/themed-user-menu.component';
|
import {ThemedUserMenuComponent} from './auth-nav-menu/user-menu/themed-user-menu.component';
|
||||||
import { OrcidBadgeAndTooltipComponent } from './orcid-badge-and-tooltip/orcid-badge-and-tooltip.component';
|
import { OrcidBadgeAndTooltipComponent } from './orcid-badge-and-tooltip/orcid-badge-and-tooltip.component';
|
||||||
|
import { LiveRegionComponent } from './live-region/live-region.component';
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@@ -469,7 +470,8 @@ const ENTRY_COMPONENTS = [
|
|||||||
AdvancedClaimedTaskActionRatingComponent,
|
AdvancedClaimedTaskActionRatingComponent,
|
||||||
EpersonGroupListComponent,
|
EpersonGroupListComponent,
|
||||||
EpersonSearchBoxComponent,
|
EpersonSearchBoxComponent,
|
||||||
GroupSearchBoxComponent
|
GroupSearchBoxComponent,
|
||||||
|
LiveRegionComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
const PROVIDERS = [
|
const PROVIDERS = [
|
||||||
|
@@ -9,8 +9,6 @@
|
|||||||
// "401.unauthorized": "unauthorized",
|
// "401.unauthorized": "unauthorized",
|
||||||
"401.unauthorized": "unautorisiert",
|
"401.unauthorized": "unautorisiert",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// "403.help": "You don't have permission to access this page. You can use the button below to get back to the home page.",
|
// "403.help": "You don't have permission to access this page. You can use the button below to get back to the home page.",
|
||||||
"403.help": "Sie sind nicht berechtigt, auf diese Seite zuzugreifen. Über den Button unten auf der Seite gelangen Sie zurück zur Startseite.",
|
"403.help": "Sie sind nicht berechtigt, auf diese Seite zuzugreifen. Über den Button unten auf der Seite gelangen Sie zurück zur Startseite.",
|
||||||
|
|
||||||
@@ -20,8 +18,6 @@
|
|||||||
// "403.forbidden": "forbidden",
|
// "403.forbidden": "forbidden",
|
||||||
"403.forbidden": "verboten",
|
"403.forbidden": "verboten",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// "404.help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ",
|
// "404.help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ",
|
||||||
"404.help": "Die Seite konnte nicht gefunden werden. Eventuell wurde sie verschoben oder gelöscht. Über den Button unten auf der Seite gelangen Sie zurück zur Startseite.",
|
"404.help": "Die Seite konnte nicht gefunden werden. Eventuell wurde sie verschoben oder gelöscht. Über den Button unten auf der Seite gelangen Sie zurück zur Startseite.",
|
||||||
|
|
||||||
@@ -31,6 +27,16 @@
|
|||||||
// "404.page-not-found": "page not found",
|
// "404.page-not-found": "page not found",
|
||||||
"404.page-not-found": "Seite nicht gefunden",
|
"404.page-not-found": "Seite nicht gefunden",
|
||||||
|
|
||||||
|
// "500.page-internal-server-error": "Service unavailable",
|
||||||
|
"500.page-internal-server-error": "Dienst nicht verfügbar",
|
||||||
|
|
||||||
|
// "500.help": "The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.",
|
||||||
|
"500.help": "Der Dienst steht momentan nicht zur Verfügung. Bitte versuchen Sie es später noch einmal.",
|
||||||
|
|
||||||
|
// "500.link.home-page": "Take me to the home page",
|
||||||
|
"500.link.home-page": "Zur Startseite",
|
||||||
|
|
||||||
|
|
||||||
// "admin.access-control.epeople.breadcrumbs": "EPeople",
|
// "admin.access-control.epeople.breadcrumbs": "EPeople",
|
||||||
"admin.access-control.epeople.breadcrumbs": "Personen suchen",
|
"admin.access-control.epeople.breadcrumbs": "Personen suchen",
|
||||||
|
|
||||||
@@ -5143,10 +5149,10 @@
|
|||||||
"submission.sections.general.deposit_error_notice": "Beim Einreichen des Items ist ein Fehler aufgetreten. Bitte versuchen Sie es später noch einmal.",
|
"submission.sections.general.deposit_error_notice": "Beim Einreichen des Items ist ein Fehler aufgetreten. Bitte versuchen Sie es später noch einmal.",
|
||||||
|
|
||||||
// "submission.sections.general.deposit_success_notice": "Submission deposited successfully.",
|
// "submission.sections.general.deposit_success_notice": "Submission deposited successfully.",
|
||||||
"submission.sections.general.deposit_success_notice": "Veröffentlichung erfolgreich eingereicht",
|
"submission.sections.general.deposit_success_notice": "Veröffentlichung erfolgreich eingereicht.",
|
||||||
|
|
||||||
// "submission.sections.general.discard_error_notice": "There was an issue when discarding the item, please try again later.",
|
// "submission.sections.general.discard_error_notice": "There was an issue when discarding the item, please try again later.",
|
||||||
"submission.sections.general.discard_error_notice": "Beim Verwerfen der Einreichung ist ein Fehler aufgetreten. Bitte versuchen Sie es später noch einmal",
|
"submission.sections.general.discard_error_notice": "Beim Verwerfen der Einreichung ist ein Fehler aufgetreten. Bitte versuchen Sie es später noch einmal.",
|
||||||
|
|
||||||
// "submission.sections.general.discard_success_notice": "Submission discarded successfully.",
|
// "submission.sections.general.discard_success_notice": "Submission discarded successfully.",
|
||||||
"submission.sections.general.discard_success_notice": "Einreichung erfolgreich verworfen.",
|
"submission.sections.general.discard_success_notice": "Einreichung erfolgreich verworfen.",
|
||||||
|
@@ -1240,7 +1240,7 @@
|
|||||||
|
|
||||||
"community.edit.notifications.unauthorized": "You do not have privileges to make this change",
|
"community.edit.notifications.unauthorized": "You do not have privileges to make this change",
|
||||||
|
|
||||||
"community.edit.notifications.error": "An error occured while editing the community",
|
"community.edit.notifications.error": "An error occurred while editing the community",
|
||||||
|
|
||||||
"community.edit.return": "Back",
|
"community.edit.return": "Back",
|
||||||
|
|
||||||
@@ -1448,7 +1448,7 @@
|
|||||||
|
|
||||||
"curation.form.submit.error.head": "Running the curation task failed",
|
"curation.form.submit.error.head": "Running the curation task failed",
|
||||||
|
|
||||||
"curation.form.submit.error.content": "An error occured when trying to start the curation task.",
|
"curation.form.submit.error.content": "An error occurred when trying to start the curation task.",
|
||||||
|
|
||||||
"curation.form.submit.error.invalid-handle": "Couldn't determine the handle for this object",
|
"curation.form.submit.error.invalid-handle": "Couldn't determine the handle for this object",
|
||||||
|
|
||||||
@@ -1700,7 +1700,7 @@
|
|||||||
|
|
||||||
"forgot-email.form.error.head": "Error when trying to reset password",
|
"forgot-email.form.error.head": "Error when trying to reset password",
|
||||||
|
|
||||||
"forgot-email.form.error.content": "An error occured when attempting to reset the password for the account associated with the following email address: {{ email }}",
|
"forgot-email.form.error.content": "An error occurred when attempting to reset the password for the account associated with the following email address: {{ email }}",
|
||||||
|
|
||||||
"forgot-password.title": "Forgot Password",
|
"forgot-password.title": "Forgot Password",
|
||||||
|
|
||||||
@@ -3518,7 +3518,7 @@
|
|||||||
|
|
||||||
"register-page.registration.error.head": "Error when trying to register email",
|
"register-page.registration.error.head": "Error when trying to register email",
|
||||||
|
|
||||||
"register-page.registration.error.content": "An error occured when registering the following email address: {{ email }}",
|
"register-page.registration.error.content": "An error occurred when registering the following email address: {{ email }}",
|
||||||
|
|
||||||
"register-page.registration.error.recaptcha": "Error when trying to authenticate with recaptcha",
|
"register-page.registration.error.recaptcha": "Error when trying to authenticate with recaptcha",
|
||||||
|
|
||||||
|
@@ -22,6 +22,7 @@ import { HomeConfig } from './homepage-config.interface';
|
|||||||
import { MarkdownConfig } from './markdown-config.interface';
|
import { MarkdownConfig } from './markdown-config.interface';
|
||||||
import { FilterVocabularyConfig } from './filter-vocabulary-config';
|
import { FilterVocabularyConfig } from './filter-vocabulary-config';
|
||||||
import { DiscoverySortConfig } from './discovery-sort.config';
|
import { DiscoverySortConfig } from './discovery-sort.config';
|
||||||
|
import { LiveRegionConfig } from '../app/shared/live-region/live-region.config';
|
||||||
|
|
||||||
interface AppConfig extends Config {
|
interface AppConfig extends Config {
|
||||||
ui: UIServerConfig;
|
ui: UIServerConfig;
|
||||||
@@ -48,6 +49,7 @@ interface AppConfig extends Config {
|
|||||||
markdown: MarkdownConfig;
|
markdown: MarkdownConfig;
|
||||||
vocabularies: FilterVocabularyConfig[];
|
vocabularies: FilterVocabularyConfig[];
|
||||||
comcolSelectionSort: DiscoverySortConfig;
|
comcolSelectionSort: DiscoverySortConfig;
|
||||||
|
liveRegion: LiveRegionConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -22,6 +22,7 @@ import { HomeConfig } from './homepage-config.interface';
|
|||||||
import { MarkdownConfig } from './markdown-config.interface';
|
import { MarkdownConfig } from './markdown-config.interface';
|
||||||
import { FilterVocabularyConfig } from './filter-vocabulary-config';
|
import { FilterVocabularyConfig } from './filter-vocabulary-config';
|
||||||
import { DiscoverySortConfig } from './discovery-sort.config';
|
import { DiscoverySortConfig } from './discovery-sort.config';
|
||||||
|
import { LiveRegionConfig } from '../app/shared/live-region/live-region.config';
|
||||||
|
|
||||||
export class DefaultAppConfig implements AppConfig {
|
export class DefaultAppConfig implements AppConfig {
|
||||||
production = false;
|
production = false;
|
||||||
@@ -435,4 +436,10 @@ export class DefaultAppConfig implements AppConfig {
|
|||||||
sortField:'dc.title',
|
sortField:'dc.title',
|
||||||
sortDirection:'ASC',
|
sortDirection:'ASC',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Live Region configuration, used by the LiveRegionService
|
||||||
|
liveRegion: LiveRegionConfig = {
|
||||||
|
messageTimeOutDurationMs: 30000,
|
||||||
|
isVisible: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@@ -314,5 +314,10 @@ export const environment: BuildConfig = {
|
|||||||
vocabulary: 'srsc',
|
vocabulary: 'srsc',
|
||||||
enabled: true
|
enabled: true
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
|
||||||
|
liveRegion: {
|
||||||
|
messageTimeOutDurationMs: 30000,
|
||||||
|
isVisible: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@@ -13,6 +13,7 @@
|
|||||||
--ds-login-logo-width:72px;
|
--ds-login-logo-width:72px;
|
||||||
--ds-submission-header-z-index: 1001;
|
--ds-submission-header-z-index: 1001;
|
||||||
--ds-submission-footer-z-index: 999;
|
--ds-submission-footer-z-index: 999;
|
||||||
|
--ds-live-region-z-index: 1030;
|
||||||
|
|
||||||
--ds-main-z-index: 1;
|
--ds-main-z-index: 1;
|
||||||
--ds-nav-z-index: 10;
|
--ds-nav-z-index: 10;
|
||||||
|
@@ -7188,10 +7188,10 @@ isbinaryfile@^4.0.8:
|
|||||||
resolved "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz"
|
resolved "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz"
|
||||||
integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==
|
integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==
|
||||||
|
|
||||||
isbot@^3.6.10:
|
isbot@^5.1.17:
|
||||||
version "3.6.10"
|
version "5.1.17"
|
||||||
resolved "https://registry.yarnpkg.com/isbot/-/isbot-3.6.10.tgz#7b66334e81794f0461794debb567975cf08eaf2b"
|
resolved "https://registry.yarnpkg.com/isbot/-/isbot-5.1.17.tgz#ad7da5690a61bbb19056a069975c9a73182682a0"
|
||||||
integrity sha512-+I+2998oyP4oW9+OTQD8TS1r9P6wv10yejukj+Ksj3+UR5pUhsZN3f8W7ysq0p1qxpOVNbl5mCuv0bCaF8y5iQ==
|
integrity sha512-/wch8pRKZE+aoVhRX/hYPY1C7dMCeeMyhkQLNLNlYAbGQn9bkvMB8fOUXNnk5I0m4vDYbBJ9ciVtkr9zfBJ7qA==
|
||||||
|
|
||||||
isexe@^2.0.0:
|
isexe@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
|
Reference in New Issue
Block a user