Merge branch 'main' into DURACOM-195

This commit is contained in:
Davide Negretti
2024-02-02 10:41:47 +01:00
81 changed files with 513 additions and 316 deletions

View File

@@ -1,4 +1,5 @@
<div class="container"> <div class="container">
<h1>{{ 'admin.access-control.bulk-access.title' | translate }}</h1>
<ds-bulk-access-browse [listId]="listId"></ds-bulk-access-browse> <ds-bulk-access-browse [listId]="listId"></ds-bulk-access-browse>
<div class="clearfix mb-3"></div> <div class="clearfix mb-3"></div>
<ds-bulk-access-settings #dsBulkSettings ></ds-bulk-access-settings> <ds-bulk-access-settings #dsBulkSettings ></ds-bulk-access-settings>

View File

@@ -201,7 +201,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
deleteEPerson(ePerson: EPerson) { deleteEPerson(ePerson: EPerson) {
if (hasValue(ePerson.id)) { if (hasValue(ePerson.id)) {
const modalRef = this.modalService.open(ConfirmationModalComponent); const modalRef = this.modalService.open(ConfirmationModalComponent);
modalRef.componentInstance.dso = ePerson; modalRef.componentInstance.name = this.dsoNameService.getName(ePerson);
modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-eperson.header'; modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-eperson.header';
modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-eperson.info'; modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-eperson.info';
modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-eperson.cancel'; modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-eperson.cancel';

View File

@@ -478,7 +478,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
take(1), take(1),
switchMap((eperson: EPerson) => { switchMap((eperson: EPerson) => {
const modalRef = this.modalService.open(ConfirmationModalComponent); const modalRef = this.modalService.open(ConfirmationModalComponent);
modalRef.componentInstance.dso = eperson; modalRef.componentInstance.name = this.dsoNameService.getName(eperson);
modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-eperson.header'; modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-eperson.header';
modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-eperson.info'; modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-eperson.info';
modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-eperson.cancel'; modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-eperson.cancel';

View File

@@ -420,7 +420,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
delete() { delete() {
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((group: Group) => { this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((group: Group) => {
const modalRef = this.modalService.open(ConfirmationModalComponent); const modalRef = this.modalService.open(ConfirmationModalComponent);
modalRef.componentInstance.dso = group; modalRef.componentInstance.name = this.dsoNameService.getName(group);
modalRef.componentInstance.headerLabel = this.messagePrefix + '.delete-group.modal.header'; modalRef.componentInstance.headerLabel = this.messagePrefix + '.delete-group.modal.header';
modalRef.componentInstance.infoLabel = this.messagePrefix + '.delete-group.modal.info'; modalRef.componentInstance.infoLabel = this.messagePrefix + '.delete-group.modal.info';
modalRef.componentInstance.cancelLabel = this.messagePrefix + '.delete-group.modal.cancel'; modalRef.componentInstance.cancelLabel = this.messagePrefix + '.delete-group.modal.cancel';

View File

@@ -1,5 +1,5 @@
<div class="container"> <div class="container">
<h2 id="header">{{'admin.batch-import.page.header' | translate}}</h2> <h1 id="header">{{'admin.batch-import.page.header' | translate}}</h1>
<p>{{'admin.batch-import.page.help' | translate}}</p> <p>{{'admin.batch-import.page.help' | translate}}</p>
<p *ngIf="dso"> <p *ngIf="dso">
selected collection: <b>{{getDspaceObjectName()}}</b>&nbsp; selected collection: <b>{{getDspaceObjectName()}}</b>&nbsp;

View File

@@ -19,7 +19,7 @@
<table id="formats" class="table table-striped table-hover"> <table id="formats" class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th scope="col" [attr.aria-label]="'admin.registries.bitstream-formats.select' | translate"></th> <th scope="col"><span class="sr-only">{{'admin.registries.bitstream-formats.table.selected' | translate}}</span></th>
<th scope="col">{{'admin.registries.bitstream-formats.table.id' | translate}}</th> <th scope="col">{{'admin.registries.bitstream-formats.table.id' | translate}}</th>
<th scope="col">{{'admin.registries.bitstream-formats.table.name' | translate}}</th> <th scope="col">{{'admin.registries.bitstream-formats.table.name' | translate}}</th>
<th scope="col">{{'admin.registries.bitstream-formats.table.mimetype' | translate}}</th> <th scope="col">{{'admin.registries.bitstream-formats.table.mimetype' | translate}}</th>
@@ -35,6 +35,7 @@
[checked]="isSelected(bitstreamFormat) | async" [checked]="isSelected(bitstreamFormat) | async"
(change)="selectBitStreamFormat(bitstreamFormat, $event)" (change)="selectBitStreamFormat(bitstreamFormat, $event)"
> >
<span class="sr-only">{{'admin.registries.bitstream-formats.select' | translate}}}</span>
</label> </label>
</td> </td>
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.id}}</a></td> <td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.id}}</a></td>

View File

@@ -2,7 +2,7 @@
<div class="metadata-registry row"> <div class="metadata-registry row">
<div class="col-12"> <div class="col-12">
<h2 id="header" class="border-bottom pb-2">{{'admin.registries.metadata.head' | translate}}</h2> <h1 id="header" class="border-bottom pb-2">{{'admin.registries.metadata.head' | translate}}</h1>
<p id="description" class="pb-2">{{'admin.registries.metadata.description' | translate}}</p> <p id="description" class="pb-2">{{'admin.registries.metadata.description' | translate}}</p>
@@ -19,7 +19,7 @@
<table id="metadata-schemas" class="table table-striped table-hover"> <table id="metadata-schemas" class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th scope="col"></th> <th scope="col"><span class="sr-only">{{'admin.registries.metadata.schemas.table.selected' | translate}}</span></th>
<th scope="col">{{'admin.registries.metadata.schemas.table.id' | translate}}</th> <th scope="col">{{'admin.registries.metadata.schemas.table.id' | translate}}</th>
<th scope="col">{{'admin.registries.metadata.schemas.table.namespace' | translate}}</th> <th scope="col">{{'admin.registries.metadata.schemas.table.namespace' | translate}}</th>
<th scope="col">{{'admin.registries.metadata.schemas.table.name' | translate}}</th> <th scope="col">{{'admin.registries.metadata.schemas.table.name' | translate}}</th>
@@ -34,6 +34,7 @@
[checked]="isSelected(schema) | async" [checked]="isSelected(schema) | async"
(change)="selectMetadataSchema(schema, $event)" (change)="selectMetadataSchema(schema, $event)"
> >
<span class="sr-only">{{((isSelected(schema) | async) ? 'admin.registries.metadata.schemas.deselect' : 'admin.registries.metadata.schemas.select') | translate}}</span>
</label> </label>
</td> </td>
<td class="selectable-row" (click)="editSchema(schema)"><a [routerLink]="[schema.prefix]">{{schema.id}}</a></td> <td class="selectable-row" (click)="editSchema(schema)"><a [routerLink]="[schema.prefix]">{{schema.id}}</a></td>

View File

@@ -1,11 +1,11 @@
<div *ngIf="registryService.getActiveMetadataSchema() | async; then editheader; else createHeader"></div> <div *ngIf="registryService.getActiveMetadataSchema() | async; then editheader; else createHeader"></div>
<ng-template #createHeader> <ng-template #createHeader>
<h4>{{messagePrefix + '.create' | translate}}</h4> <h2>{{messagePrefix + '.create' | translate}}</h2>
</ng-template> </ng-template>
<ng-template #editheader> <ng-template #editheader>
<h4>{{messagePrefix + '.edit' | translate}}</h4> <h2>{{messagePrefix + '.edit' | translate}}</h2>
</ng-template> </ng-template>
<ds-form [formId]="formId" <ds-form [formId]="formId"

View File

@@ -1,11 +1,11 @@
<div *ngIf="registryService.getActiveMetadataField() | async; then editheader; else createHeader"></div> <div *ngIf="registryService.getActiveMetadataField() | async; then editheader; else createHeader"></div>
<ng-template #createHeader> <ng-template #createHeader>
<h4>{{messagePrefix + '.create' | translate}}</h4> <h2>{{messagePrefix + '.create' | translate}}</h2>
</ng-template> </ng-template>
<ng-template #editheader> <ng-template #editheader>
<h4>{{messagePrefix + '.edit' | translate}}</h4> <h2>{{messagePrefix + '.edit' | translate}}</h2>
</ng-template> </ng-template>
<ds-form [formId]="formId" <ds-form [formId]="formId"

View File

@@ -2,7 +2,7 @@
<div class="metadata-schema row"> <div class="metadata-schema row">
<div class="col-12" *ngVar="(metadataSchema$ | async) as schema"> <div class="col-12" *ngVar="(metadataSchema$ | async) as schema">
<h2 id="header" class="border-bottom pb-2">{{'admin.registries.schema.head' | translate}}: "{{schema?.prefix}}"</h2> <h1 id="header" class="border-bottom pb-2">{{'admin.registries.schema.head' | translate}}: "{{schema?.prefix}}"</h1>
<p id="description" class="pb-2">{{'admin.registries.schema.description' | translate:{ namespace: schema?.namespace } }}</p> <p id="description" class="pb-2">{{'admin.registries.schema.description' | translate:{ namespace: schema?.namespace } }}</p>
@@ -10,7 +10,7 @@
[metadataSchema]="schema" [metadataSchema]="schema"
(submitForm)="forceUpdateFields()"></ds-metadata-field-form> (submitForm)="forceUpdateFields()"></ds-metadata-field-form>
<h3>{{'admin.registries.schema.fields.head' | translate}}</h3> <h2>{{'admin.registries.schema.fields.head' | translate}}</h2>
<ng-container *ngVar="(metadataFields$ | async)?.payload as fields"> <ng-container *ngVar="(metadataFields$ | async)?.payload as fields">
<ds-pagination <ds-pagination
@@ -24,7 +24,7 @@
<table id="metadata-fields" class="table table-striped table-hover"> <table id="metadata-fields" class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th></th> <th><span class="sr-only">{{'admin.registries.schema.fields.table.selected' | translate}}</span></th>
<th scope="col">{{'admin.registries.schema.fields.table.id' | translate}}</th> <th scope="col">{{'admin.registries.schema.fields.table.id' | translate}}</th>
<th scope="col">{{'admin.registries.schema.fields.table.field' | translate}}</th> <th scope="col">{{'admin.registries.schema.fields.table.field' | translate}}</th>
<th scope="col">{{'admin.registries.schema.fields.table.scopenote' | translate}}</th> <th scope="col">{{'admin.registries.schema.fields.table.scopenote' | translate}}</th>
@@ -33,12 +33,11 @@
<tbody> <tbody>
<tr *ngFor="let field of fields?.page" <tr *ngFor="let field of fields?.page"
[ngClass]="{'table-primary' : isActive(field) | async}"> [ngClass]="{'table-primary' : isActive(field) | async}">
<td> <td *ngVar="(isSelected(field) | async) as selected">
<label class="mb-0"> <input type="checkbox"
<input type="checkbox" [attr.aria-label]="(selected ? 'admin.registries.schema.fields.deselect' : 'admin.registries.schema.fields.select') | translate"
[checked]="isSelected(field) | async" [checked]="selected"
(change)="selectMetadataField(field, $event)"> (change)="selectMetadataField(field, $event)">
</label>
</td> </td>
<td class="selectable-row" (click)="editField(field)">{{field.id}}</td> <td class="selectable-row" (click)="editField(field)">{{field.id}}</td>
<td class="selectable-row" (click)="editField(field)">{{schema?.prefix}}.{{field.element}}{{field.qualifier ? '.' + field.qualifier : ''}}</td> <td class="selectable-row" (click)="editField(field)">{{schema?.prefix}}.{{field.element}}{{field.qualifier ? '.' + field.qualifier : ''}}</td>

View File

@@ -124,7 +124,7 @@ export class WorkspaceItemAdminWorkflowActionsComponent implements OnInit {
*/ */
deleteSupervisionOrder(supervisionOrderEntry: SupervisionOrderListEntry) { deleteSupervisionOrder(supervisionOrderEntry: SupervisionOrderListEntry) {
const modalRef = this.modalService.open(ConfirmationModalComponent); const modalRef = this.modalService.open(ConfirmationModalComponent);
modalRef.componentInstance.dso = supervisionOrderEntry.group; modalRef.componentInstance.name = this.dsoNameService.getName(supervisionOrderEntry.group);
modalRef.componentInstance.headerLabel = this.messagePrefix + '.delete-supervision.modal.header'; modalRef.componentInstance.headerLabel = this.messagePrefix + '.delete-supervision.modal.header';
modalRef.componentInstance.infoLabel = this.messagePrefix + '.delete-supervision.modal.info'; modalRef.componentInstance.infoLabel = this.messagePrefix + '.delete-supervision.modal.info';
modalRef.componentInstance.cancelLabel = this.messagePrefix + '.delete-supervision.modal.cancel'; modalRef.componentInstance.cancelLabel = this.messagePrefix + '.delete-supervision.modal.cancel';

View File

@@ -13,7 +13,7 @@
<!-- Collection logo --> <!-- Collection logo -->
<ds-comcol-page-logo *ngIf="logoRD$" <ds-comcol-page-logo *ngIf="logoRD$"
[logo]="(logoRD$ | async)?.payload" [logo]="(logoRD$ | async)?.payload"
[alternateText]="'Collection Logo'"> [alternateText]="'collection.logo' | translate">
</ds-comcol-page-logo> </ds-comcol-page-logo>
<!-- Handle --> <!-- Handle -->

View File

@@ -1,17 +1,17 @@
<div class="container-fluid mb-2" *ngVar="(itemTemplateRD$ | async) as itemTemplateRD"> <div class="container-fluid mb-2" *ngVar="(itemTemplateRD$ | async) as itemTemplateRD">
<label>{{ 'collection.edit.template.label' | translate}}</label> <span class="d-inline-block mb-2">{{ 'collection.edit.template.label' | translate}}</span>
<div class="button-row space-children-mr"> <div class="button-row space-children-mr">
<button *ngIf="!itemTemplateRD?.payload" class="btn btn-success" (click)="addItemTemplate()"> <button *ngIf="!itemTemplateRD?.payload" class="btn btn-success" (click)="addItemTemplate()">
<i class="fas fa-plus"></i> <i class="fas fa-plus" aria-hidden="true"></i>
<span class="d-none d-sm-inline">&nbsp;{{"collection.edit.template.add-button" | translate}}</span> <span class="d-none d-sm-inline">&nbsp;{{"collection.edit.template.add-button" | translate}}</span>
</button> </button>
<button *ngIf="itemTemplateRD?.payload" class="btn btn-danger" (click)="deleteItemTemplate()"> <button *ngIf="itemTemplateRD?.payload" class="btn btn-danger" (click)="deleteItemTemplate()">
<i class="fas fa-trash-alt"></i> <i class="fas fa-trash-alt" aria-hidden="true"></i>
<span class="d-none d-sm-inline">&nbsp;{{"collection.edit.template.delete-button" | translate}}</span> <span class="d-none d-sm-inline">&nbsp;{{"collection.edit.template.delete-button" | translate}}</span>
</button> </button>
<button *ngIf="itemTemplateRD?.payload" class="btn btn-primary" <button *ngIf="itemTemplateRD?.payload" class="btn btn-primary"
[routerLink]="'/collections/' + (dsoRD$ | async)?.payload.uuid + '/itemtemplate'"> [routerLink]="'/collections/' + (dsoRD$ | async)?.payload.uuid + '/itemtemplate'">
<i class="fas fa-edit"></i> <i class="fas fa-edit" aria-hidden="true"></i>
<span class="d-none d-sm-inline">&nbsp;{{"collection.edit.template.edit-button" | translate}}</span> <span class="d-none d-sm-inline">&nbsp;{{"collection.edit.template.edit-button" | translate}}</span>
</button> </button>
</div> </div>

View File

@@ -4,9 +4,9 @@
<cdk-tree-node *cdkTreeNodeDef="let node; when: isShowMore" cdkTreeNodePadding <cdk-tree-node *cdkTreeNodeDef="let node; when: isShowMore" cdkTreeNodePadding
class="example-tree-node show-more-node"> class="example-tree-node show-more-node">
<div class="btn-group"> <div class="btn-group">
<button type="button" class="btn btn-default" cdkTreeNodeToggle> <span aria-hidden="true" class="btn btn-default invisible" cdkTreeNodeToggle>
<span class="fa fa-chevron-right invisible" aria-hidden="true"></span> <span class="fa fa-chevron-right"></span>
</button> </span>
<div class="align-middle pt-2"> <div class="align-middle pt-2">
<button *ngIf="!(dataSource.loading$ | async)" (click)="getNextPage(node)" <button *ngIf="!(dataSource.loading$ | async)" (click)="getNextPage(node)"
class="btn btn-outline-primary btn-sm" role="button"> class="btn btn-outline-primary btn-sm" role="button">
@@ -24,15 +24,18 @@
<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 type="button" class="btn btn-default" cdkTreeNodeToggle <button *ngIf="hasChild(null, node) | async" type="button" class="btn btn-default" cdkTreeNodeToggle
[title]="'toggle ' + dsoNameService.getName(node.payload)" [attr.aria-label]="(node.isExpanded ? 'communityList.collapse' : 'communityList.expand') | translate:{ name: dsoNameService.getName(node.payload) }"
[attr.aria-label]="'toggle ' + dsoNameService.getName(node.payload)"
(click)="toggleExpanded(node)" (click)="toggleExpanded(node)"
[ngClass]="(hasChild(null, node)| async) ? 'visible' : 'invisible'" data-test="expand-button">
[attr.data-test]="(hasChild(null, node)| async) ? 'expand-button' : ''">
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}" <span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
aria-hidden="true"></span> aria-hidden="true"></span>
<span class="sr-only">{{ (node.isExpanded ? 'communityList.collapse' : 'communityList.expand') | translate:{ name: dsoNameService.getName(node.payload) } }}</span>
</button> </button>
<!--Don't render the button when non-expandable otherwise it's still accessible, instead render this placeholder-->
<span *ngIf="!(hasChild(null, node) | async)" aria-hidden="true" class="btn btn-default invisible">
<span class="fa fa-chevron-right"></span>
</span>
<div class="d-flex flex-row"> <div class="d-flex flex-row">
<span class="align-middle pt-2 lead"> <span class="align-middle pt-2 lead">
<a [routerLink]="node.route" class="lead">{{ dsoNameService.getName(node.payload) }}</a> <a [routerLink]="node.route" class="lead">{{ dsoNameService.getName(node.payload) }}</a>
@@ -44,10 +47,9 @@
<ds-truncatable [id]="node.id"> <ds-truncatable [id]="node.id">
<div class="text-muted" cdkTreeNodePadding> <div class="text-muted" cdkTreeNodePadding>
<div class="d-flex" *ngIf="node.payload.shortDescription"> <div class="d-flex" *ngIf="node.payload.shortDescription">
<button type="button" class="btn btn-default invisible"> <span aria-hidden="true" class="btn btn-default invisible">
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}" <span class="fa fa-chevron-right"></span>
aria-hidden="true"></span> </span>
</button>
<ds-truncatable-part [id]="node.id" [minLines]="3"> <ds-truncatable-part [id]="node.id" [minLines]="3">
<span>{{node.payload.shortDescription}}</span> <span>{{node.payload.shortDescription}}</span>
</ds-truncatable-part> </ds-truncatable-part>
@@ -56,10 +58,9 @@
</ds-truncatable> </ds-truncatable>
<div class="d-flex" *ngIf="node===loadingNode && dataSource.loading$ | async" <div class="d-flex" *ngIf="node===loadingNode && dataSource.loading$ | async"
cdkTreeNodePadding> cdkTreeNodePadding>
<button type="button" class="btn btn-default invisible"> <span aria-hidden="true" class="btn btn-default invisible">
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}" <span class="fa fa-chevron-right"></span>
aria-hidden="true"></span> </span>
</button>
<ds-themed-loading class="ds-themed-loading"></ds-themed-loading> <ds-themed-loading class="ds-themed-loading"></ds-themed-loading>
</div> </div>
</cdk-tree-node> </cdk-tree-node>
@@ -67,9 +68,9 @@
<cdk-tree-node *cdkTreeNodeDef="let node; when: !(hasChild && isShowMore)" cdkTreeNodePadding <cdk-tree-node *cdkTreeNodeDef="let node; when: !(hasChild && isShowMore)" cdkTreeNodePadding
class="example-tree-node childless-node"> class="example-tree-node childless-node">
<div class="btn-group"> <div class="btn-group">
<button type="button" class="btn btn-default" cdkTreeNodeToggle> <span aria-hidden="true" class="btn btn-default invisible" cdkTreeNodeToggle>
<span class="fa fa-chevron-right invisible" aria-hidden="true"></span> <span class="fa fa-chevron-right"></span>
</button> </span>
<h6 class="align-middle pt-2"> <h6 class="align-middle pt-2">
<a [routerLink]="node.route" class="lead">{{ dsoNameService.getName(node.payload) }}</a> <a [routerLink]="node.route" class="lead">{{ dsoNameService.getName(node.payload) }}</a>
</h6> </h6>
@@ -77,10 +78,9 @@
<ds-truncatable [id]="node.id"> <ds-truncatable [id]="node.id">
<div class="text-muted" cdkTreeNodePadding> <div class="text-muted" cdkTreeNodePadding>
<div class="d-flex" *ngIf="node.payload.shortDescription"> <div class="d-flex" *ngIf="node.payload.shortDescription">
<button type="button" class="btn btn-default invisible"> <span aria-hidden="true" class="btn btn-default invisible">
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}" <span class="fa fa-chevron-right"></span>
aria-hidden="true"></span> </span>
</button>
<ds-truncatable-part [id]="node.id" [minLines]="3"> <ds-truncatable-part [id]="node.id" [minLines]="3">
<span>{{node.payload.shortDescription}}</span> <span>{{node.payload.shortDescription}}</span>
</ds-truncatable-part> </ds-truncatable-part>

View File

@@ -0,0 +1,4 @@
::ng-deep .fa-chevron-right::before {
display: block;
width: 16px;
}

View File

@@ -5,7 +5,7 @@ import { CommunityListService, showMoreFlatNode, toFlatNode } from '../community
import { CdkTreeModule } from '@angular/cdk/tree'; import { CdkTreeModule } from '@angular/cdk/tree';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { Community } from '../../core/shared/community.model'; import { Community } from '../../core/shared/community.model';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
@@ -300,12 +300,14 @@ describe('CommunityListComponent', () => {
describe('second top community node is expanded and has more children (collections) than page size of collection', () => { describe('second top community node is expanded and has more children (collections) than page size of collection', () => {
describe('children of second top com are added (page-limited pageSize 2)', () => { describe('children of second top com are added (page-limited pageSize 2)', () => {
let allNodes; let allNodes: DebugElement[];
beforeEach(fakeAsync(() => { beforeEach(fakeAsync(() => {
const chevronExpand = fixture.debugElement.queryAll(By.css('.expandable-node button')); const toggleButtons: DebugElement[] = fixture.debugElement.queryAll(By.css('.expandable-node button'));
const chevronExpandSpan = fixture.debugElement.queryAll(By.css('.expandable-node button span')); const toggleButtonText: DebugElement = toggleButtons[1].query(By.css('span'));
if (chevronExpandSpan[1].nativeElement.classList.contains('fa-chevron-right')) { expect(toggleButtonText).not.toBeNull();
chevronExpand[1].nativeElement.click();
if (toggleButtonText.nativeElement.classList.contains('fa-chevron-right')) {
toggleButtons[1].nativeElement.click();
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
} }
@@ -315,17 +317,18 @@ describe('CommunityListComponent', () => {
allNodes = [...expandableNodesFound, ...childlessNodesFound]; allNodes = [...expandableNodesFound, ...childlessNodesFound];
})); }));
it('tree contains 2 (page-limited) top com, 2 (page-limited) coll of 2nd top com, a show more for those page-limited coll and show more for page-limited top com', () => { it('tree contains 2 (page-limited) top com, 2 (page-limited) coll of 2nd top com, a show more for those page-limited coll and show more for page-limited top com', () => {
mockTopFlatnodesUnexpanded.slice(0, 2).map((topFlatnode: FlatNode) => { const allNodeNames: string[] = allNodes.map((node: DebugElement) => node.nativeElement.innerText.trim());
expect(allNodes.find((foundEl) => {
return (foundEl.nativeElement.textContent.trim() === topFlatnode.name);
})).toBeTruthy();
});
mockCollectionsPage1.map((coll) => {
expect(allNodes.find((foundEl) => {
return (foundEl.nativeElement.textContent.trim() === coll.name);
})).toBeTruthy();
});
expect(allNodes.length).toEqual(4); expect(allNodes.length).toEqual(4);
const flatNodes: string[] = mockTopFlatnodesUnexpanded.slice(0, 2).map((flatNode: FlatNode) => flatNode.name);
for (const flatNode of flatNodes) {
expect(allNodeNames).toContain(flatNode);
}
expect(flatNodes.length).toBe(2);
const page1CollectionNames: string[] = mockCollectionsPage1.map((collection: Collection) => collection.name);
for (const collectionName of page1CollectionNames) {
expect(allNodeNames).toContain(collectionName);
}
expect(page1CollectionNames.length).toBe(2);
const showMoreEl = fixture.debugElement.queryAll(By.css('.show-more-node')); const showMoreEl = fixture.debugElement.queryAll(By.css('.show-more-node'));
expect(showMoreEl.length).toEqual(2); expect(showMoreEl.length).toEqual(2);
}); });

View File

@@ -19,6 +19,7 @@ import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
@Component({ @Component({
selector: 'ds-community-list', selector: 'ds-community-list',
templateUrl: './community-list.component.html', templateUrl: './community-list.component.html',
styleUrls: ['./community-list.component.scss'],
}) })
export class CommunityListComponent implements OnInit, OnDestroy { export class CommunityListComponent implements OnInit, OnDestroy {

View File

@@ -7,7 +7,7 @@
<!-- 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 -->
<ds-comcol-page-logo *ngIf="logoRD$" [logo]="(logoRD$ | async)?.payload" [alternateText]="'Community Logo'"> <ds-comcol-page-logo *ngIf="logoRD$" [logo]="(logoRD$ | async)?.payload" [alternateText]="'community.logo' | translate">
</ds-comcol-page-logo> </ds-comcol-page-logo>
<!-- Handle --> <!-- Handle -->
<ds-themed-comcol-page-handle [content]="communityPayload.handle" [title]="'community.page.handle'"> <ds-themed-comcol-page-handle [content]="communityPayload.handle" [title]="'community.page.handle'">

View File

@@ -7,11 +7,11 @@
<div class="form-group row"> <div class="form-group row">
<div class="col text-right space-children-mr"> <div class="col text-right space-children-mr">
<button class="btn btn-outline-secondary" (click)="onCancel(dso)" [disabled]="(processing$ | async)"> <button class="btn btn-outline-secondary" (click)="onCancel(dso)" [disabled]="(processing$ | async)">
<i class="fas fa-times"></i> {{'community.delete.cancel' | translate}} <i class="fas fa-times" aria-hidden="true"></i> {{'community.delete.cancel' | translate}}
</button> </button>
<button class="btn btn-danger" (click)="onConfirm(dso)" [disabled]="(processing$ | async)"> <button class="btn btn-danger" (click)="onConfirm(dso)" [disabled]="(processing$ | async)">
<span *ngIf="processing$ | async"><i class='fas fa-circle-notch fa-spin'></i> {{'community.delete.processing' | translate}}</span> <span *ngIf="processing$ | async"><i class='fas fa-circle-notch fa-spin' aria-hidden="true"></i> {{'community.delete.processing' | translate}}</span>
<span *ngIf="!(processing$ | async)"><i class="fas fa-trash"></i> {{'community.delete.confirm' | translate}}</span> <span *ngIf="!(processing$ | async)"><i class="fas fa-trash" aria-hidden="true"></i> {{'community.delete.confirm' | translate}}</span>
</button> </button>
</div> </div>
</div> </div>

View File

@@ -351,7 +351,28 @@ function addOperationToList(body: JsonPatchOperationObject[], actionType, target
newBody.push(makeOperationEntry({ op: JsonPatchOperationType.move, from: fromPath, path: targetPath })); newBody.push(makeOperationEntry({ op: JsonPatchOperationType.move, from: fromPath, path: targetPath }));
break; break;
} }
return newBody; return dedupeOperationEntries(newBody);
}
/**
* Dedupe operation entries by op and path. This prevents processing unnecessary patches in a single PATCH request.
*
* @param body JSON patch operation object entries
* @returns deduped JSON patch operation object entries
*/
function dedupeOperationEntries(body: JsonPatchOperationObject[]): JsonPatchOperationObject[] {
const ops = new Map<string, any>();
for (let i = body.length - 1; i >= 0; i--) {
const patch = body[i].operation;
const key = `${patch.op}-${patch.path}`;
if (!ops.has(key)) {
ops.set(key, patch);
} else {
body.splice(i, 1);
}
}
return body;
} }
function makeOperationEntry(operation) { function makeOperationEntry(operation) {

View File

@@ -2,23 +2,23 @@
<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 ml-gap">
<button class="mr-auto btn btn-success" id="dso-add-btn" [disabled]="form.newValue || (saving$ | async)" <button class="mr-auto btn btn-success" id="dso-add-btn" [disabled]="form.newValue || (saving$ | async)"
[title]="dsoType + '.edit.metadata.add-button' | translate" [title]="dsoType + '.edit.metadata.add-button' | translate"
(click)="add()"><i class="fas fa-plus"></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" [disabled]="(saving$ | async)" <button class="btn btn-warning ml-1" id="dso-reinstate-btn" *ngIf="isReinstatable" [disabled]="(saving$ | async)"
[title]="dsoType + '.edit.metadata.reinstate-button' | translate" [title]="dsoType + '.edit.metadata.reinstate-button' | translate"
(click)="reinstate()"><i class="fas fa-undo-alt"></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" [disabled]="!hasChanges || (saving$ | async)" <button class="btn btn-primary ml-1" id="dso-save-btn" [disabled]="!hasChanges || (saving$ | async)"
[title]="dsoType + '.edit.metadata.save-button' | translate" [title]="dsoType + '.edit.metadata.save-button' | translate"
(click)="submit()"><i class="fas fa-save"></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 ml-1" id="dso-discard-btn" *ngIf="!isReinstatable"
[title]="dsoType + '.edit.metadata.discard-button' | translate" [title]="dsoType + '.edit.metadata.discard-button' | translate"
[disabled]="!hasChanges || (saving$ | async)" [disabled]="!hasChanges || (saving$ | async)"
(click)="discard()"><i class="fas fa-times"></i> (click)="discard()"><i class="fas fa-times" aria-hidden="true"></i>
<span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.discard-button' | translate }}</span> <span class="d-none d-sm-inline">&nbsp;{{ dsoType + '.edit.metadata.discard-button' | translate }}</span>
</button> </button>
</div> </div>
@@ -74,16 +74,19 @@
<div class="mt-2 float-right space-children-mr ml-gap"> <div class="mt-2 float-right space-children-mr ml-gap">
<button class="btn btn-warning" *ngIf="isReinstatable" [disabled]="(saving$ | async)" <button class="btn btn-warning" *ngIf="isReinstatable" [disabled]="(saving$ | async)"
[title]="dsoType + '.edit.metadata.reinstate-button' | translate" [title]="dsoType + '.edit.metadata.reinstate-button' | translate"
(click)="reinstate()"><i class="fas fa-undo-alt"></i> {{ dsoType + '.edit.metadata.reinstate-button' | translate }} (click)="reinstate()">
<i class="fas fa-undo-alt" aria-hidden="true"></i> {{ dsoType + '.edit.metadata.reinstate-button' | translate }}
</button> </button>
<button class="btn btn-primary" [disabled]="!hasChanges || (saving$ | async)" <button class="btn btn-primary" [disabled]="!hasChanges || (saving$ | async)"
[title]="dsoType + '.edit.metadata.save-button' | translate" [title]="dsoType + '.edit.metadata.save-button' | translate"
(click)="submit()"><i class="fas fa-save"></i> {{ dsoType + '.edit.metadata.save-button' | translate }} (click)="submit()">
<i class="fas fa-save" aria-hidden="true"></i> {{ dsoType + '.edit.metadata.save-button' | translate }}
</button> </button>
<button class="btn btn-danger" *ngIf="!isReinstatable" <button class="btn btn-danger" *ngIf="!isReinstatable"
[title]="dsoType + '.edit.metadata.discard-button' | translate" [title]="dsoType + '.edit.metadata.discard-button' | translate"
[disabled]="!hasChanges || (saving$ | async)" [disabled]="!hasChanges || (saving$ | async)"
(click)="discard()"><i class="fas fa-times"></i> {{ dsoType + '.edit.metadata.discard-button' | translate }} (click)="discard()">
<i class="fas fa-times" aria-hidden="true"></i> {{ dsoType + '.edit.metadata.discard-button' | translate }}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,7 @@
<div class="w-100 position-relative"> <div class="w-100 position-relative">
<input type="text" #mdFieldInput <input type="text" #mdFieldInput
class="form-control" [ngClass]="{ 'is-invalid': showInvalid }" class="form-control" [ngClass]="{ 'is-invalid': showInvalid }"
[attr.aria-label]="(dsoType + '.edit.metadata.metadatafield') | translate"
[value]="mdField" [value]="mdField"
[formControl]="input" [formControl]="input"
(focusin)="query$.next(mdField)" (focusin)="query$.next(mdField)"
@@ -10,9 +11,9 @@
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (mdFieldOptions$ | async)?.length > 0}"> <div class="autocomplete dropdown-menu" [ngClass]="{'show': (mdFieldOptions$ | async)?.length > 0}">
<div class="dropdown-list"> <div class="dropdown-list">
<div *ngFor="let mdFieldOption of (mdFieldOptions$ | async)"> <div *ngFor="let mdFieldOption of (mdFieldOptions$ | async)">
<a href="javascript:void(0);" class="d-block dropdown-item" (click)="select(mdFieldOption)"> <button class="d-block dropdown-item" (click)="select(mdFieldOption)">
<span [innerHTML]="mdFieldOption"></span> <span [innerHTML]="mdFieldOption"></span>
</a> </button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -11,7 +11,9 @@
[dsDebounce]="debounceTime" (onDebounce)="find($event)" [dsDebounce]="debounceTime" (onDebounce)="find($event)"
[placeholder]="placeholder" [placeholder]="placeholder"
[ngModelOptions]="{standalone: true}" autocomplete="off"/> [ngModelOptions]="{standalone: true}" autocomplete="off"/>
<input type="submit" class="d-none"/> <button class="sr-only" type="submit">
{{'search.filters.search.submit' | translate}}
</button>
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}"> <div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}">
<div class="dropdown-list"> <div class="dropdown-list">
<div *ngFor="let suggestionOption of suggestions"> <div *ngFor="let suggestionOption of suggestions">

View File

@@ -2,6 +2,7 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { OrgUnitInputSuggestionsComponent } from './org-unit-input-suggestions.component'; import { OrgUnitInputSuggestionsComponent } from './org-unit-input-suggestions.component';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
let component: OrgUnitInputSuggestionsComponent; let component: OrgUnitInputSuggestionsComponent;
let fixture: ComponentFixture<OrgUnitInputSuggestionsComponent>; let fixture: ComponentFixture<OrgUnitInputSuggestionsComponent>;
@@ -21,6 +22,7 @@ describe('OrgUnitInputSuggestionsComponent', () => {
declarations: [OrgUnitInputSuggestionsComponent], declarations: [OrgUnitInputSuggestionsComponent],
imports: [ imports: [
FormsModule, FormsModule,
TranslateModule.forRoot(),
], ],
providers: [ providers: [
], ],

View File

@@ -12,7 +12,9 @@
[dsDebounce]="debounceTime" (onDebounce)="find($event)" [dsDebounce]="debounceTime" (onDebounce)="find($event)"
[placeholder]="placeholder" [placeholder]="placeholder"
[ngModelOptions]="{standalone: true}" autocomplete="off"/> [ngModelOptions]="{standalone: true}" autocomplete="off"/>
<input type="submit" class="d-none"/> <button class="sr-only" type="submit">
{{'search.filters.search.submit' | translate}}
</button>
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}"> <div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}">
<div class="dropdown-list"> <div class="dropdown-list">
<div *ngFor="let suggestionOption of suggestions"> <div *ngFor="let suggestionOption of suggestions">

View File

@@ -1,13 +1,13 @@
<div class="container" *ngIf="(registration$ |async)"> <div class="container" *ngIf="(registration$ |async)">
<h3 class="mb-4">{{'forgot-password.form.head' | translate}}</h3> <h1 class="mb-4">{{'forgot-password.form.head' | translate}}</h1>
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header">{{'forgot-password.form.identification.header' | translate}}</div> <div class="card-header">{{'forgot-password.form.identification.header' | translate}}</div>
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<label class="font-weight-bold" <span class="font-weight-bold">{{'forgot-password.form.identification.email' | translate}} </span>
for="email">{{'forgot-password.form.identification.email' | translate}}</label> <span [attr.data-test]="'email' | dsBrowserOnly">{{(registration$ |async).email}}</span>
<span id="email">{{(registration$ |async).email}}</span></div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -21,6 +21,7 @@ import {
createSuccessfulRemoteDataObject$ createSuccessfulRemoteDataObject$
} from '../../shared/remote-data.utils'; } from '../../shared/remote-data.utils';
import { CoreState } from '../../core/core-state.model'; import { CoreState } from '../../core/core-state.model';
import { BrowserOnlyPipe } from '../../shared/utils/browser-only.pipe';
describe('ForgotPasswordFormComponent', () => { describe('ForgotPasswordFormComponent', () => {
let comp: ForgotPasswordFormComponent; let comp: ForgotPasswordFormComponent;
@@ -54,7 +55,10 @@ describe('ForgotPasswordFormComponent', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), ReactiveFormsModule], imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), ReactiveFormsModule],
declarations: [ForgotPasswordFormComponent], declarations: [
BrowserOnlyPipe,
ForgotPasswordFormComponent,
],
providers: [ providers: [
{provide: Router, useValue: router}, {provide: Router, useValue: router},
{provide: ActivatedRoute, useValue: route}, {provide: ActivatedRoute, useValue: route},
@@ -75,7 +79,7 @@ describe('ForgotPasswordFormComponent', () => {
describe('init', () => { describe('init', () => {
it('should initialise mail address', () => { it('should initialise mail address', () => {
const elem = fixture.debugElement.queryAll(By.css('span#email'))[0].nativeElement; const elem = fixture.debugElement.queryAll(By.css('span[data-test="email"]'))[0].nativeElement;
expect(elem.innerHTML).toContain('test@email.org'); expect(elem.innerHTML).toContain('test@email.org');
}); });
}); });

View File

@@ -1,5 +1,5 @@
<div class="container" *ngIf="(healthResponseInitialised | async) && (healthInfoResponseInitialised | async)"> <div class="container" *ngIf="(healthResponseInitialised | async) && (healthInfoResponseInitialised | async)">
<h2>{{'health-page.heading' | translate}}</h2> <h1>{{'health-page.heading' | translate}}</h1>
<div *ngIf="(healthResponse | async) && (healthInfoResponse | async)"> <div *ngIf="(healthResponse | async) && (healthInfoResponse | async)">
<ul ngbNav #nav="ngbNav" [activeId]="'status'" class="nav-tabs"> <ul ngbNav #nav="ngbNav" [activeId]="'status'" class="nav-tabs">
<li [ngbNavItem]="'status'" role="presentation"> <li [ngbNavItem]="'status'" role="presentation">

View File

@@ -1,45 +1,43 @@
<div class="row row-offcanvas row-offcanvas-right"> <div class="row row-offcanvas row-offcanvas-right">
<div class="col-xs-12 col-sm-12 col-md-9"> <div class="col-xs-12 col-sm-12 col-md-9">
<form class="primary" [formGroup]="feedbackForm" (ngSubmit)="createFeedback()"> <h1>{{ 'info.feedback.head' | translate }}</h1>
<h1>{{ 'info.feedback.head' | translate }}</h1> <p>{{ 'info.feedback.info' | translate }}</p>
<p>{{ 'info.feedback.info' | translate }}</p> <form [formGroup]="feedbackForm" (ngSubmit)="createFeedback()" class="col p-0">
<fieldset class="col p-0"> <div class="row">
<div class="row"> <div class="control-group col-sm-12">
<div class="control-group col-sm-12"> <label class="control-label" for="email">{{ 'info.feedback.email-label' | translate }}&nbsp;</label>
<label class="control-label" for="email">{{ 'info.feedback.email-label' | translate }}&nbsp;</label> <input id="email" class="form-control" name="email" type="text" value="" formControlName="email" autofocus="autofocus" title="{{ 'info.feedback.email_help' | translate }}">
<input id="email" class="form-control" name="email" type="text" value="" formControlName="email" autofocus="autofocus" title="{{ 'info.feedback.email_help' | translate }}"> <small class="text-muted">{{ 'info.feedback.email_help' | translate }}</small>
<small class="text-muted">{{ 'info.feedback.email_help' | translate }}</small>
</div>
</div> </div>
</div>
<ng-container *ngIf="feedbackForm.controls.email.invalid && (feedbackForm.controls.email.dirty || feedbackForm.controls.email.touched)" <ng-container *ngIf="feedbackForm.controls.email.invalid && (feedbackForm.controls.email.dirty || feedbackForm.controls.email.touched)"
class="alert"> class="alert">
<ds-error *ngIf="feedbackForm.controls.email.errors?.required" message="{{'info.feedback.error.email.required' | translate}}"></ds-error> <ds-error *ngIf="feedbackForm.controls.email.errors?.required" message="{{'info.feedback.error.email.required' | translate}}"></ds-error>
<ds-error *ngIf="feedbackForm.controls.email.errors?.pattern" message="{{'info.feedback.error.email.required' | translate}}"></ds-error> <ds-error *ngIf="feedbackForm.controls.email.errors?.pattern" message="{{'info.feedback.error.email.required' | translate}}"></ds-error>
</ng-container> </ng-container>
<div class="row"> <div class="row">
<div class="control-group col-sm-12"> <div class="control-group col-sm-12">
<label class="control-label" for="comments">{{ 'info.feedback.comments' | translate }}:&nbsp;</label> <label class="control-label" for="comments">{{ 'info.feedback.comments' | translate }}:&nbsp;</label>
<textarea id="comments" formControlName="message" class="form-control" name="message" cols="20" rows="5"> </textarea> <textarea id="comments" formControlName="message" class="form-control" name="message" cols="20" rows="5"> </textarea>
</div>
</div> </div>
<ng-container *ngIf="feedbackForm.controls.message.invalid && (feedbackForm.controls.message.dirty || feedbackForm.controls.message.touched)" </div>
class="alert"> <ng-container *ngIf="feedbackForm.controls.message.invalid && (feedbackForm.controls.message.dirty || feedbackForm.controls.message.touched)"
<ds-error *ngIf="feedbackForm.controls.message.errors?.required" message="{{'info.feedback.error.message.required' | translate}}"></ds-error> class="alert">
</ng-container> <ds-error *ngIf="feedbackForm.controls.message.errors?.required" message="{{'info.feedback.error.message.required' | translate}}"></ds-error>
<div class="row"> </ng-container>
<div class="control-group col-sm-12"> <div class="row">
<label class="control-label" for="page">{{ 'info.feedback.page-label' | translate }}&nbsp;</label> <div class="control-group col-sm-12">
<input id="page" readonly class="form-control" name="page" type="text" value="" formControlName="page" autofocus="autofocus" title="{{ 'info.feedback.page_help' | translate }}"> <label class="control-label" for="page">{{ 'info.feedback.page-label' | translate }}&nbsp;</label>
<small class="text-muted">{{ 'info.feedback.page_help' | translate }}</small> <input id="page" readonly class="form-control" name="page" type="text" value="" formControlName="page" autofocus="autofocus" title="{{ 'info.feedback.page_help' | translate }}">
</div> <small class="text-muted">{{ 'info.feedback.page_help' | translate }}</small>
</div> </div>
<div class="row py-2"> </div>
<div class="control-group col-sm-12 text-right"> <div class="row py-2">
<button [disabled]="!feedbackForm.valid" class="btn btn-primary" name="submit" type="submit">{{ 'info.feedback.send' | translate }}</button> <div class="control-group col-sm-12 text-right">
</div> <button [disabled]="!feedbackForm.valid" class="btn btn-primary" name="submit" type="submit">{{ 'info.feedback.send' | translate }}</button>
</div> </div>
</fieldset> </div>
</form> </form>
</div> </div>
</div> </div>

View File

@@ -15,10 +15,10 @@
[attr.data-test]="'import-dropdown' | dsBrowserOnly" [attr.data-test]="'import-dropdown' | dsBrowserOnly"
title="{{'mydspace.new-submission-external' | translate}}"> title="{{'mydspace.new-submission-external' | translate}}">
<i class="fa fa-file-import" aria-hidden="true"></i> <i class="fa fa-file-import" aria-hidden="true"></i>
<span class="caret"></span> <span class="caret" aria-hidden="true"></span>
</button> </button>
<div ngbDropdownMenu <div ngbDropdownMenu
class="dropdown-menu" class="dropdown-menu p-0"
id="importControlsDropdownMenu" id="importControlsDropdownMenu"
aria-labelledby="dropdownImport"> aria-labelledby="dropdownImport">
<ds-entity-dropdown [isSubmission]="false" (selectionChange)="openPage($event)"></ds-entity-dropdown> <ds-entity-dropdown [isSubmission]="false" (selectionChange)="openPage($event)"></ds-entity-dropdown>

View File

@@ -13,10 +13,10 @@
[attr.data-test]="'submission-dropdown' | dsBrowserOnly" [attr.data-test]="'submission-dropdown' | dsBrowserOnly"
title="{{'mydspace.new-submission' | translate}}"> title="{{'mydspace.new-submission' | translate}}">
<i class="fa fa-plus-circle" aria-hidden="true"></i> <i class="fa fa-plus-circle" aria-hidden="true"></i>
<span class="caret"></span> <span class="caret" aria-hidden="true"></span>
</button> </button>
<div ngbDropdownMenu <div ngbDropdownMenu
class="dropdown-menu" class="dropdown-menu p-0"
id="entityControlsDropdownMenu" id="entityControlsDropdownMenu"
aria-labelledby="dropdownSubmission"> aria-labelledby="dropdownSubmission">
<ds-entity-dropdown [isSubmission]="true" (selectionChange)="openDialog($event)"></ds-entity-dropdown> <ds-entity-dropdown [isSubmission]="true" (selectionChange)="openDialog($event)"></ds-entity-dropdown>

View File

@@ -43,7 +43,8 @@ export class ProfilePageMetadataFormComponent implements OnInit {
new DynamicInputModel({ new DynamicInputModel({
id: 'email', id: 'email',
name: 'email', name: 'email',
readOnly: true readOnly: true,
disabled: true,
}), }),
new DynamicInputModel({ new DynamicInputModel({
id: 'firstname', id: 'firstname',
@@ -55,6 +56,7 @@ export class ProfilePageMetadataFormComponent implements OnInit {
errorMessages: { errorMessages: {
required: 'This field is required' required: 'This field is required'
}, },
autoComplete: 'given-name',
}), }),
new DynamicInputModel({ new DynamicInputModel({
id: 'lastname', id: 'lastname',
@@ -66,10 +68,12 @@ export class ProfilePageMetadataFormComponent implements OnInit {
errorMessages: { errorMessages: {
required: 'This field is required' required: 'This field is required'
}, },
autoComplete: 'family-name',
}), }),
new DynamicInputModel({ new DynamicInputModel({
id: 'phone', id: 'phone',
name: 'eperson.phone' name: 'eperson.phone',
autoComplete: 'tel',
}), }),
new DynamicSelectModel<string>({ new DynamicSelectModel<string>({
id: 'language', id: 'language',

View File

@@ -39,12 +39,14 @@ export class ProfilePageSecurityFormComponent implements OnInit {
new DynamicInputModel({ new DynamicInputModel({
id: 'password', id: 'password',
name: 'password', name: 'password',
inputType: 'password' inputType: 'password',
autoComplete: 'new-password',
}), }),
new DynamicInputModel({ new DynamicInputModel({
id: 'passwordrepeat', id: 'passwordrepeat',
name: 'passwordrepeat', name: 'passwordrepeat',
inputType: 'password' inputType: 'password',
autoComplete: 'new-password',
}) })
]; ];
@@ -79,7 +81,8 @@ export class ProfilePageSecurityFormComponent implements OnInit {
id: 'current-password', id: 'current-password',
name: 'current-password', name: 'current-password',
inputType: 'password', inputType: 'password',
required: true required: true,
autoComplete: 'current-password',
})); }));
} }
if (this.passwordCanBeEmpty) { if (this.passwordCanBeEmpty) {

View File

@@ -1,7 +1,7 @@
<ng-container *ngVar="(user$ | async) as user"> <ng-container *ngVar="(user$ | async) as user">
<div class="container" *ngIf="user"> <div class="container" *ngIf="user">
<ng-container *ngIf="isResearcherProfileEnabled() | async"> <h1>{{'profile.title' | translate}}</h1>
<h2 class="mb-4">{{'profile.head' | translate}}</h2> <ng-container>
<div class="card mb-4"> <div class="card mb-4">
<div class="card-header">{{'profile.card.researcher' | translate}}</div> <div class="card-header">{{'profile.card.researcher' | translate}}</div>
<div class="card-body"> <div class="card-body">

View File

@@ -88,7 +88,7 @@
</div> </div>
<div> <div>
<label>&nbsp;</label> <span aria-hidden="true" class="mb-2 d-inline-block">&nbsp;</span>
<div class="input-group"> <div class="input-group">
<button type="button" class="btn btn-outline-danger" <button type="button" class="btn btn-outline-danger"

View File

@@ -23,10 +23,10 @@
</ui-switch> </ui-switch>
</div> </div>
<div class="row mt-3"> <fieldset class="row mt-3">
<div class="col-12 col-md-3"> <legend class="h4 col-12 col-md-3">
{{ 'access-control-mode' | translate }} {{ 'access-control-mode' | translate }}
</div> </legend>
<div class="col-12 col-md-8"> <div class="col-12 col-md-8">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="radio" <input class="form-check-input" type="radio"
@@ -47,7 +47,7 @@
</label> </label>
</div> </div>
</div> </div>
</div> </fieldset>
</div> </div>
<div> <div>
@@ -78,10 +78,10 @@
</div> </div>
<div *ngIf="showLimitToSpecificBitstreams" class="row mt-3"> <div *ngIf="showLimitToSpecificBitstreams" class="row mt-3">
<div class="col-12"> <fieldset class="col-12">
{{'access-control-limit-to-specific' | translate}} <legend class="h4">
</div> {{'access-control-limit-to-specific' | translate}}
<div class="col-12"> </legend>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="radio" <input class="form-check-input" type="radio"
name="changesLimit" id="processAll" value="all" name="changesLimit" id="processAll" value="all"
@@ -111,14 +111,14 @@
</label> </label>
</div> </div>
</div> </fieldset>
</div> </div>
</div> </div>
<div class="row mt-3"> <fieldset class="row mt-3">
<div class="col-12 col-md-3"> <legend class="h4 col-12 col-md-3">
{{'access-control-mode' | translate}} {{'access-control-mode' | translate}}
</div> </legend>
<div class="col-12 col-md-8"> <div class="col-12 col-md-8">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="radio" <input class="form-check-input" type="radio"
@@ -139,7 +139,7 @@
</label> </label>
</div> </div>
</div> </div>
</div> </fieldset>
<div> <div>
<h3 class="h4 mt-3">{{'access-control-access-conditions' | translate}}</h3> <h3 class="h4 mt-3">{{'access-control-access-conditions' | translate}}</h3>

View File

@@ -1,4 +1,4 @@
<div *ngIf="searchField" class="form-group w-100 pr-2 pl-2"> <div *ngIf="searchField" class="form-group w-100 pr-2 pl-2 my-2">
<input type="search" <input type="search"
class="form-control w-100" class="form-control w-100"
(click)="$event.stopPropagation();" (click)="$event.stopPropagation();"
@@ -6,9 +6,9 @@
[formControl]="searchField" [formControl]="searchField"
#searchFieldEl> #searchFieldEl>
</div> </div>
<div class="dropdown-divider"></div> <div class="dropdown-divider m-0"></div>
<ul class="scrollable-menu p-0" <ul class="scrollable-menu p-0 m-0"
aria-labelledby="dropdownMenuButton" role="menu"
(scroll)="onScroll($event)" (scroll)="onScroll($event)"
infiniteScroll infiniteScroll
[infiniteScrollDistance]="1.5" [infiniteScrollDistance]="1.5"
@@ -18,12 +18,13 @@
[scrollWindow]="false" [scrollWindow]="false"
(scrolled)="onScrollDown()"> (scrolled)="onScrollDown()">
<li class="dropdown-item disabled" *ngIf="searchListCollection?.length == 0 && !(isLoading | async)"> <li class="dropdown-item disabled" role="menuitem" *ngIf="searchListCollection?.length == 0 && !(isLoading | async)">
{{'submission.sections.general.no-collection' | translate}} {{'submission.sections.general.no-collection' | translate}}
</li> </li>
<ng-container *ngIf="searchListCollection?.length > 0"> <ng-container *ngIf="searchListCollection?.length > 0">
<li *ngFor="let listItem of searchListCollection" <li *ngFor="let listItem of searchListCollection"
class="dropdown-item collection-item" class="dropdown-item collection-item"
role="menuitem"
title="{{ listItem.collection.name }}" title="{{ listItem.collection.name }}"
(click)="onSelect(listItem)"> (click)="onSelect(listItem)">
<div class="list-unstyled mb-0"> <div class="list-unstyled mb-0">
@@ -34,9 +35,10 @@
</div> </div>
</li> </li>
</ng-container> </ng-container>
<button class="dropdown-item disabled" *ngIf="(isLoading | async)"> <li *ngIf="(isLoading | async)">
<ds-themed-loading message="{{'loading.default' | translate}}"> <button class="dropdown-item disabled">
</ds-themed-loading> <ds-themed-loading message="{{'loading.default' | translate}}">
</button> </ds-themed-loading>
</button>
</li>
</ul> </ul>

View File

@@ -4,7 +4,8 @@
overflow-x: hidden; overflow-x: hidden;
} }
.collection-item { li:not(:last-of-type), .dropdown-divider {
border-top: none;
border-bottom: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color); border-bottom: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);
} }

View File

@@ -1,13 +1,13 @@
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-12 d-inline-block"> <div class="col-12 d-inline-block mb-1">
<label>{{type.value + '.edit.logo.label' | translate}}</label> <span>{{type.value + '.edit.logo.label' | translate}}</span>
</div> </div>
<ng-container *ngVar="(dso?.logo | async)?.payload as logo"> <ng-container *ngVar="(dso?.logo | async)?.payload as logo">
<div class="col-12 d-inline-block alert" [ngClass]="{'alert-danger': markLogoForDeletion}" id="logo-section" *ngIf="logo"> <div class="col-12 d-inline-block alert" [ngClass]="{'alert-danger': markLogoForDeletion}" id="logo-section" *ngIf="logo">
<div class="row"> <div class="row">
<div class="col-8 d-inline-block"> <div class="col-8 d-inline-block">
<ds-comcol-page-logo [logo]="logo"></ds-comcol-page-logo> <ds-comcol-page-logo [alternateText]="type.value + '.logo.alt'" [logo]="logo"></ds-comcol-page-logo>
</div> </div>
<div class="col-4 d-inline-block"> <div class="col-4 d-inline-block">
<div *ngIf="logo" class="btn-group btn-group-sm float-right" role="group"> <div *ngIf="logo" class="btn-group btn-group-sm float-right" role="group">
@@ -27,6 +27,7 @@
</div> </div>
<div *ngIf="!logo" class="col-12 d-inline-block"> <div *ngIf="!logo" class="col-12 d-inline-block">
<ds-uploader *ngIf="initializedUploaderOptions | async" <ds-uploader *ngIf="initializedUploaderOptions | async"
[ariaLabel]="type.value + '.browse.logo'"
[dropMsg]="type.value + '.edit.logo.upload'" [dropMsg]="type.value + '.edit.logo.upload'"
[dropOverDocumentMsg]="type.value + '.edit.logo.upload'" [dropOverDocumentMsg]="type.value + '.edit.logo.upload'"
[enableDragOverDocument]="true" [enableDragOverDocument]="true"
@@ -43,6 +44,6 @@
[displayCancel]="false" [displayCancel]="false"
(submitForm)="onSubmit()"> (submitForm)="onSubmit()">
<button before (click)="back.emit()" class="btn btn-outline-secondary" type="button"> <button before (click)="back.emit()" class="btn btn-outline-secondary" type="button">
<i class="fas fa-arrow-left"></i> {{ type.value + '.edit.return' | translate }} <i class="fas fa-arrow-left" aria-hidden="true"></i> {{ type.value + '.edit.return' | translate }}
</button> </button>
</ds-form> </ds-form>

View File

@@ -7,7 +7,7 @@
<a class="btn btn-danger" <a class="btn btn-danger"
[routerLink]="((type === 'community') ? '/communities/' : '/collections/') + (dsoRD$ | async)?.payload.uuid + '/delete'" [routerLink]="((type === 'community') ? '/communities/' : '/collections/') + (dsoRD$ | async)?.payload.uuid + '/delete'"
data-test="delete-button"> data-test="delete-button">
<i class="fas fa-trash"></i> {{type + '.edit.delete' | translate}}</a> <i class="fas fa-trash" aria-hidden="true"></i> {{type + '.edit.delete' | translate}}</a>
</div> </div>
</div> </div>
<div class="pt-2"> <div class="pt-2">
@@ -27,7 +27,7 @@
</div> </div>
<div class="col-12 text-right"> <div class="col-12 text-right">
<a *ngIf="!hideReturnButton" [routerLink]="getPageUrl((dsoRD$ | async)?.payload)" class="btn btn-outline-secondary"> <a *ngIf="!hideReturnButton" [routerLink]="getPageUrl((dsoRD$ | async)?.payload)" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left"></i> {{ type + '.edit.return' | translate }} <i class="fas fa-arrow-left" aria-hidden="true"></i> {{ type + '.edit.return' | translate }}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,3 +1,3 @@
<div *ngIf="logo" class="dso-logo mb-3"> <div *ngIf="logo" class="dso-logo mb-3">
<img [src]="logo._links.content.href" class="img-fluid" [attr.alt]="alternateText ? alternateText : null" (error)="errorHandler($event)"/> <img [src]="logo._links.content.href" class="w-100 img-fluid" [attr.alt]="alternateText ? alternateText : null" (error)="errorHandler($event)"/>
</div> </div>

View File

@@ -0,0 +1,4 @@
img {
max-width: var(--ds-comcol-logo-max-width);
max-height: var(--ds-comcol-logo-max-height);
}

View File

@@ -1,18 +1,18 @@
<div> <div>
<div class="modal-header">{{ headerLabel | translate:{ dsoName: dsoNameService.getName(dso) } }} <div class="modal-header">{{ headerLabel | translate:{ dsoName: name } }}
<button type="button" class="close" (click)="close()" aria-label="Close"> <button type="button" class="close" (click)="close()" aria-label="Close">
<span aria-hidden="true">×</span> <span aria-hidden="true">×</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>{{ infoLabel | translate:{ dsoName: dsoNameService.getName(dso) } }}</p> <p>{{ infoLabel | translate:{ dsoName: name } }}</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="cancel btn btn-outline-secondary" (click)="cancelPressed()" aria-label="Cancel"> <button type="button" class="cancel btn btn-outline-secondary" (click)="cancelPressed()" aria-label="Cancel">
<i class="fas fa-times"></i> {{ cancelLabel | translate:{ dsoName: dsoNameService.getName(dso) } }} <i class="fas fa-times"></i> {{ cancelLabel | translate:{ dsoName: name } }}
</button> </button>
<button type="button" class="confirm btn btn-{{brandColor}}" (click)="confirmPressed()" aria-label="Confirm" ngbAutofocus> <button type="button" class="confirm btn btn-{{brandColor}}" (click)="confirmPressed()" aria-label="Confirm" ngbAutofocus>
<i *ngIf="confirmIcon" class="{{confirmIcon}}"></i> {{ confirmLabel | translate:{ dsoName: dsoNameService.getName(dso) } }} <i *ngIf="confirmIcon" class="{{confirmIcon}}"></i> {{ confirmLabel | translate:{ dsoName: name } }}
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,7 +1,5 @@
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
@Component({ @Component({
selector: 'ds-confirmation-modal', selector: 'ds-confirmation-modal',
@@ -18,7 +16,7 @@ export class ConfirmationModalComponent {
*/ */
@Input() brandColor = 'primary'; @Input() brandColor = 'primary';
@Input() dso: DSpaceObject; @Input() name: string;
/** /**
* An event fired when the cancel or confirm button is clicked, with respectively false or true * An event fired when the cancel or confirm button is clicked, with respectively false or true
@@ -28,7 +26,6 @@ export class ConfirmationModalComponent {
constructor( constructor(
protected activeModal: NgbActiveModal, protected activeModal: NgbActiveModal,
public dsoNameService: DSONameService,
) { ) {
} }

View File

@@ -22,7 +22,7 @@
class="dropdown-menu" class="dropdown-menu"
id="dsSelectDropdownMenu" id="dsSelectDropdownMenu"
aria-labelledby="dsSelectMenuButton"> aria-labelledby="dsSelectMenuButton">
<div aria-labelledby="dropdownMenuButton"> <div>
<ng-content select=".menu"></ng-content> <ng-content select=".menu"></ng-content>
</div> </div>
</div> </div>

View File

@@ -1,25 +1,21 @@
<div *ngIf="!canActivate" class="dso-button-menu mb-1" <div *ngIf="!canActivate" class="dso-button-menu mb-1"
[title]="itemModel.text | translate"
[ngbTooltip]="itemModel.text | translate"> [ngbTooltip]="itemModel.text | translate">
<a *ngIf="!section.model.disabled" class="d-flex flex-row flex-nowrap" <a *ngIf="!section.model.disabled" class="btn btn-dark btn-sm"
[routerLink]="itemModel.link" [routerLink]="itemModel.link">
href="javascript:void(0);"> <i class="fas fa-{{section.icon}} fa-fw" aria-hidden="true"></i>
<button [attr.aria-label]="itemModel.text | translate" [title]="itemModel.text | translate" class="btn btn-dark btn-sm" [disabled]="section.model.disabled"> <span class="sr-only">{{itemModel.text | translate}}</span>
<i class="fas fa-{{section.icon}} fa-fw"></i>
</button>
</a> </a>
<div *ngIf="section.model.disabled" class="d-flex flex-row flex-nowrap"> <button *ngIf="section.model.disabled" class="btn btn-dark btn-sm" [disabled]="true">
<button [attr.aria-label]="itemModel.text | translate" [title]="itemModel.text | translate" class="btn btn-dark btn-sm" [disabled]="section.model.disabled"> <i class="fas fa-{{section.icon}} fa-fw" aria-hidden="true"></i>
<i class="fas fa-{{section.icon}} fa-fw"></i> <span class="sr-only">{{itemModel.text | translate}}</span>
</button> </button>
</div>
</div> </div>
<div *ngIf="canActivate" class="dso-button-menu mb-1" <div *ngIf="canActivate" class="dso-button-menu mb-1"
[title]="itemModel.text | translate"
[ngbTooltip]="itemModel.text | translate"> [ngbTooltip]="itemModel.text | translate">
<button [attr.aria-label]="itemModel.text | translate" [title]="itemModel.text | translate" class="btn btn-dark btn-sm" [disabled]="section.model.disabled" <button class="btn btn-dark btn-sm" [disabled]="section.model.disabled"
(click)="activate($event)"> (click)="activate($event)">
<i class="fas fa-{{section.icon}} fa-fw"></i> <i class="fas fa-{{section.icon}} fa-fw" aria-hidden="true"></i>
<span class="sr-only">{{itemModel.text | translate}}</span>
</button> </button>
</div> </div>

View File

@@ -96,7 +96,7 @@ describe('DsoEditMenuSectionComponent', () => {
}); });
describe('when the section model in a disabled link or text', () => { describe('when the section model in a disabled link or text', () => {
it('should show just the button', () => { it('should show just the button', () => {
const textButton = fixture.debugElement.query(By.css('div div button')); const textButton = fixture.debugElement.query(By.css('div a'));
expect(textButton.nativeElement.innerHTML).toContain('fa-' + iconString); expect(textButton.nativeElement.innerHTML).toContain('fa-' + iconString);
}); });
}); });
@@ -144,7 +144,7 @@ describe('DsoEditMenuSectionComponent', () => {
}); });
describe('link model', () => { describe('when the section model in a non disabled link', () => {
initAsync(dummySectionLink, menuService); initAsync(dummySectionLink, menuService);
beforeEach(() => { beforeEach(() => {
spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(observableOf([])); spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(observableOf([]));
@@ -154,11 +154,8 @@ describe('DsoEditMenuSectionComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
}); });
describe('when the section model in a non disabled link', () => { it('should show the link element', () => {
it('should show a link element with the button in it', () => { expect(fixture.debugElement.query(By.css('a'))).not.toBeNull();
const link = fixture.debugElement.query(By.css('a'));
expect(link.nativeElement.innerHTML).toContain('button');
});
}); });
}); });

View File

@@ -2,6 +2,7 @@
<input type="search" <input type="search"
class="form-control" class="form-control"
(click)="$event.stopPropagation();" (click)="$event.stopPropagation();"
[attr.aria-label]="'dso-selector.placeholder' | translate: { type: typesString }"
placeholder="{{'dso-selector.placeholder' | translate: { type: typesString } }}" placeholder="{{'dso-selector.placeholder' | translate: { type: typesString } }}"
[formControl]="input" ngbAutofocus (keyup.enter)="selectSingleResult()"> [formControl]="input" ngbAutofocus (keyup.enter)="selectSingleResult()">
</div> </div>

View File

@@ -75,6 +75,11 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
*/ */
@Input() sort: SortOptions; @Input() sort: SortOptions;
/**
* The id that should be given to the input box, this is required for accessibility reasons
*/
@Input() searchBoxId: string | null = null;
// list of allowed selectable dsoTypes // list of allowed selectable dsoTypes
typesString: string; typesString: string;

View File

@@ -6,14 +6,14 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<button class="btn btn-outline-primary btn-lg btn-block" (click)="selectObject(undefined)">{{'dso-selector.create.community.top-level' | translate}}</button> <button class="btn btn-outline-primary btn-lg btn-block" (click)="selectObject(undefined)">{{'dso-selector.create.community.top-level' | translate}}</button>
<h3 class="position-relative py-1 my-3 font-weight-normal"> <div class="h3 position-relative py-1 my-3 font-weight-normal">
<hr> <hr>
<div id="create-community-or-separator" class="text-center position-absolute w-100"> <div id="create-community-or-separator" class="text-center position-absolute w-100">
<span class="px-4 bg-white">{{'dso-selector.create.community.or-divider' | translate}}</span> <span class="px-4 bg-white">{{'dso-selector.create.community.or-divider' | translate}}</span>
</div> </div>
</h3> </div>
<h5 class="px-2">{{'dso-selector.create.community.sub-level' | translate}}</h5> <span class="h5 px-2">{{'dso-selector.create.community.sub-level' | translate}}</span>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" [sort]="defaultSort" (onSelect)="selectObject($event)"></ds-dso-selector> <ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" [sort]="defaultSort" (onSelect)="selectObject($event)"></ds-dso-selector>
</div> </div>
</div> </div>

View File

@@ -5,7 +5,7 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<h5 *ngIf="header" class="px-2">{{header | translate}}</h5> <span *ngIf="header" class="h5 px-2">{{header | translate}}</span>
<ds-authorized-collection-selector [currentDSOId]="dsoRD?.payload.uuid" <ds-authorized-collection-selector [currentDSOId]="dsoRD?.payload.uuid"
[entityType]="entityType" [entityType]="entityType"
[types]="selectorTypes" [types]="selectorTypes"

View File

@@ -5,7 +5,7 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<h5 *ngIf="header" class="px-2">{{header | translate}}</h5> <span *ngIf="header" class="h5 px-2">{{header | translate}}</span>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" [sort]="defaultSort" (onSelect)="selectObject($event)"></ds-dso-selector> <ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" [sort]="defaultSort" (onSelect)="selectObject($event)"></ds-dso-selector>
</div> </div>
</div> </div>

View File

@@ -5,7 +5,7 @@
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<h5 *ngIf="header" class="px-2">{{header | translate}}</h5> <span *ngIf="header" class="h5 px-2">{{header | translate}}</span>
<ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" (onSelect)="selectObject($event)"></ds-dso-selector> <ds-dso-selector [currentDSOId]="dsoRD?.payload.uuid" [types]="selectorTypes" (onSelect)="selectObject($event)"></ds-dso-selector>
</div> </div>
</div> </div>

View File

@@ -20,6 +20,7 @@ import { RemoteData } from '../../../../core/data/remote-data';
import { getProcessDetailRoute } from '../../../../process-page/process-page-routing.paths'; import { getProcessDetailRoute } from '../../../../process-page/process-page-routing.paths';
import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service'; import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../../../../core/data/feature-authorization/feature-id'; import { FeatureID } from '../../../../core/data/feature-authorization/feature-id';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
/** /**
* Component to wrap a list of existing dso's inside a modal * Component to wrap a list of existing dso's inside a modal
@@ -38,6 +39,7 @@ export class ExportBatchSelectorComponent extends DSOSelectorModalWrapperCompone
protected notificationsService: NotificationsService, protected translationService: TranslateService, protected notificationsService: NotificationsService, protected translationService: TranslateService,
protected scriptDataService: ScriptDataService, protected scriptDataService: ScriptDataService,
protected authorizationDataService: AuthorizationDataService, protected authorizationDataService: AuthorizationDataService,
protected dsoNameService: DSONameService,
private modalService: NgbModal) { private modalService: NgbModal) {
super(activeModal, route); super(activeModal, route);
} }
@@ -49,7 +51,7 @@ export class ExportBatchSelectorComponent extends DSOSelectorModalWrapperCompone
navigate(dso: DSpaceObject): Observable<boolean> { navigate(dso: DSpaceObject): Observable<boolean> {
if (dso instanceof Collection) { if (dso instanceof Collection) {
const modalRef = this.modalService.open(ConfirmationModalComponent); const modalRef = this.modalService.open(ConfirmationModalComponent);
modalRef.componentInstance.dso = dso; modalRef.componentInstance.name = this.dsoNameService.getName(dso);
modalRef.componentInstance.headerLabel = 'confirmation-modal.export-batch.header'; modalRef.componentInstance.headerLabel = 'confirmation-modal.export-batch.header';
modalRef.componentInstance.infoLabel = 'confirmation-modal.export-batch.info'; modalRef.componentInstance.infoLabel = 'confirmation-modal.export-batch.info';
modalRef.componentInstance.cancelLabel = 'confirmation-modal.export-batch.cancel'; modalRef.componentInstance.cancelLabel = 'confirmation-modal.export-batch.cancel';

View File

@@ -21,6 +21,7 @@ import { RemoteData } from '../../../../core/data/remote-data';
import { getProcessDetailRoute } from '../../../../process-page/process-page-routing.paths'; import { getProcessDetailRoute } from '../../../../process-page/process-page-routing.paths';
import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service'; import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../../../../core/data/feature-authorization/feature-id'; import { FeatureID } from '../../../../core/data/feature-authorization/feature-id';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
/** /**
* Component to wrap a list of existing dso's inside a modal * Component to wrap a list of existing dso's inside a modal
@@ -39,6 +40,7 @@ export class ExportMetadataSelectorComponent extends DSOSelectorModalWrapperComp
protected notificationsService: NotificationsService, protected translationService: TranslateService, protected notificationsService: NotificationsService, protected translationService: TranslateService,
protected scriptDataService: ScriptDataService, protected scriptDataService: ScriptDataService,
protected authorizationDataService: AuthorizationDataService, protected authorizationDataService: AuthorizationDataService,
protected dsoNameService: DSONameService,
private modalService: NgbModal) { private modalService: NgbModal) {
super(activeModal, route); super(activeModal, route);
} }
@@ -50,7 +52,7 @@ export class ExportMetadataSelectorComponent extends DSOSelectorModalWrapperComp
navigate(dso: DSpaceObject): Observable<boolean> { navigate(dso: DSpaceObject): Observable<boolean> {
if (dso instanceof Collection || dso instanceof Community) { if (dso instanceof Collection || dso instanceof Community) {
const modalRef = this.modalService.open(ConfirmationModalComponent); const modalRef = this.modalService.open(ConfirmationModalComponent);
modalRef.componentInstance.dso = dso; modalRef.componentInstance.name = this.dsoNameService.getName(dso);
modalRef.componentInstance.headerLabel = 'confirmation-modal.export-metadata.header'; modalRef.componentInstance.headerLabel = 'confirmation-modal.export-metadata.header';
modalRef.componentInstance.infoLabel = 'confirmation-modal.export-metadata.info'; modalRef.componentInstance.infoLabel = 'confirmation-modal.export-metadata.info';
modalRef.componentInstance.cancelLabel = 'confirmation-modal.export-metadata.cancel'; modalRef.componentInstance.cancelLabel = 'confirmation-modal.export-metadata.cancel';

View File

@@ -1,6 +1,5 @@
<div <ul class="scrollable-menu list-unstyled mb-0"
class="scrollable-menu" role="menu"
aria-labelledby="dropdownMenuButton"
(scroll)="onScroll($event)" (scroll)="onScroll($event)"
infiniteScroll infiniteScroll
[infiniteScrollDistance]="5" [infiniteScrollDistance]="5"
@@ -9,20 +8,23 @@
[fromRoot]="true" [fromRoot]="true"
[scrollWindow]="false" [scrollWindow]="false"
(scrolled)="onScrollDown()"> (scrolled)="onScrollDown()">
<button class="dropdown-item disabled" *ngIf="searchListEntity?.length == 0 && !(isLoadingList | async)"> <li *ngIf="searchListEntity?.length == 0 && !(isLoadingList | async)">
<button class="dropdown-item disabled" role="menuitem">
{{'submission.sections.general.no-entity' | translate}} {{'submission.sections.general.no-entity' | translate}}
</button> </button>
<button *ngFor="let listItem of searchListEntity" </li>
class="dropdown-item entity-item" <li *ngFor="let listItem of searchListEntity" class="entity-item text-primary">
<button class="dropdown-item"
role="menuitem"
title="{{ listItem.label }}" title="{{ listItem.label }}"
(click)="onSelect(listItem)"> (click)="onSelect(listItem)">
<ul class="list-unstyled mb-0"> <span class="text-truncate font-weight-bold">{{ listItem.label.toLowerCase() + '.listelement.badge' | translate }}</span>
<li class="list-item text-truncate text-primary font-weight-bold">{{ listItem.label.toLowerCase() + '.listelement.badge' | translate }}</li>
</ul>
</button> </button>
<button class="dropdown-item disabled" *ngIf="(isLoadingList | async)" > </li>
<li *ngIf="(isLoadingList | async)">
<button class="dropdown-item disabled" role="menuitem">
<ds-themed-loading message="{{'loading.default' | translate}}"> <ds-themed-loading message="{{'loading.default' | translate}}">
</ds-themed-loading> </ds-themed-loading>
</button> </button>
</li>
</div> </ul>

View File

@@ -1,5 +1,9 @@
.list-item:active { .dropdown-item {
color: white !important; padding: 0.35rem 1rem;
&:active {
color: white !important;
}
} }
.scrollable-menu { .scrollable-menu {
@@ -8,7 +12,7 @@
overflow-x: hidden; overflow-x: hidden;
} }
.entity-item { li:not(:last-of-type) .dropdown-item {
border-bottom: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color); border-bottom: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);
} }

View File

@@ -123,7 +123,7 @@ describe('EntityDropdownComponent', () => {
scheduler.flush(); scheduler.flush();
spyOn(component, 'onSelect'); spyOn(component, 'onSelect');
const entityItem = fixture.debugElement.query(By.css('.entity-item:nth-child(2)')); const entityItem = fixture.debugElement.query(By.css('.entity-item:nth-child(2) button'));
entityItem.triggerEventHandler('click', null); entityItem.triggerEventHandler('click', null);
scheduler.schedule(() => fixture.detectChanges()); scheduler.schedule(() => fixture.detectChanges());

View File

@@ -88,7 +88,6 @@
aria-expanded="false" aria-expanded="false"
[attr.aria-labelledby]="'label_' + model.id"> [attr.aria-labelledby]="'label_' + model.id">
<div class="scrollable-menu" <div class="scrollable-menu"
aria-labelledby="scrollableDropdownMenuButton"
infiniteScroll infiniteScroll
[infiniteScrollDistance]="2" [infiniteScrollDistance]="2"
[infiniteScrollThrottle]="50" [infiniteScrollThrottle]="50"

View File

@@ -27,7 +27,7 @@
(keydown)="selectOnKeyDown($event, sdRef)"> (keydown)="selectOnKeyDown($event, sdRef)">
</div> </div>
<div ngbDropdownMenu <div #dropdownMenu ngbDropdownMenu
class="dropdown-menu scrollable-dropdown-menu w-100" class="dropdown-menu scrollable-dropdown-menu w-100"
[attr.aria-label]="model.placeholder"> [attr.aria-label]="model.placeholder">
<div class="scrollable-menu" <div class="scrollable-menu"
@@ -41,7 +41,8 @@
[scrollWindow]="false"> [scrollWindow]="false">
<button class="dropdown-item disabled" *ngIf="optionsList && optionsList.length == 0">{{'form.no-results' | translate}}</button> <button class="dropdown-item disabled" *ngIf="optionsList && optionsList.length == 0">{{'form.no-results' | translate}}</button>
<button class="dropdown-item collection-item text-truncate" *ngFor="let listEntry of optionsList" <button class="dropdown-item collection-item text-truncate" *ngFor="let listEntry of optionsList; let i = index"
[class.active]="i === selectedIndex"
(keydown.enter)="onSelect(listEntry); sdRef.close()" (mousedown)="onSelect(listEntry); sdRef.close()" (keydown.enter)="onSelect(listEntry); sdRef.close()" (mousedown)="onSelect(listEntry); sdRef.close()"
title="{{ listEntry.display }}" role="option" title="{{ listEntry.display }}" role="option"
[attr.id]="listEntry.display == (currentValue|async) ? ('combobox_' + id + '_selected') : null"> [attr.id]="listEntry.display == (currentValue|async) ? ('combobox_' + id + '_selected') : null">

View File

@@ -1,8 +1,17 @@
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import {
ChangeDetectorRef,
Component,
EventEmitter,
Input,
OnInit,
Output,
ViewChild,
ElementRef
} from '@angular/core';
import { UntypedFormGroup } from '@angular/forms'; import { UntypedFormGroup } from '@angular/forms';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { catchError, distinctUntilChanged, map, tap } from 'rxjs/operators'; import { catchError, map, tap } from 'rxjs/operators';
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'; import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap';
import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core'; import { DynamicFormLayoutService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
@@ -28,6 +37,8 @@ import { FormFieldMetadataValueObject } from '../../../models/form-field-metadat
templateUrl: './dynamic-scrollable-dropdown.component.html' templateUrl: './dynamic-scrollable-dropdown.component.html'
}) })
export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyComponent implements OnInit { export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyComponent implements OnInit {
@ViewChild('dropdownMenu', { read: ElementRef }) dropdownMenu: ElementRef;
@Input() bindId = true; @Input() bindId = true;
@Input() group: UntypedFormGroup; @Input() group: UntypedFormGroup;
@Input() model: DynamicScrollableDropdownModel; @Input() model: DynamicScrollableDropdownModel;
@@ -40,6 +51,9 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
public loading = false; public loading = false;
public pageInfo: PageInfo; public pageInfo: PageInfo;
public optionsList: any; public optionsList: any;
public inputText: string = null;
public selectedIndex = 0;
public acceptableKeys = ['Space', 'NumpadMultiply', 'NumpadAdd', 'NumpadSubtract', 'NumpadDecimal', 'Semicolon', 'Equal', 'Comma', 'Minus', 'Period', 'Quote', 'Backquote'];
constructor(protected vocabularyService: VocabularyService, constructor(protected vocabularyService: VocabularyService,
protected cdr: ChangeDetectorRef, protected cdr: ChangeDetectorRef,
@@ -54,32 +68,26 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
*/ */
ngOnInit() { ngOnInit() {
this.updatePageInfo(this.model.maxOptions, 1); this.updatePageInfo(this.model.maxOptions, 1);
this.vocabularyService.getVocabularyEntries(this.model.vocabularyOptions, this.pageInfo).pipe( this.loadOptions();
}
loadOptions() {
this.loading = true;
this.vocabularyService.getVocabularyEntriesByValue(this.inputText, false, this.model.vocabularyOptions, this.pageInfo).pipe(
getFirstSucceededRemoteDataPayload(), getFirstSucceededRemoteDataPayload(),
catchError(() => observableOf(buildPaginatedList( catchError(() => observableOf(buildPaginatedList(new PageInfo(), []))),
new PageInfo(), tap(() => this.loading = false)
[] ).subscribe((list: PaginatedList<VocabularyEntry>) => {
)) this.optionsList = list.page;
)) this.updatePageInfo(
.subscribe((list: PaginatedList<VocabularyEntry>) => { list.pageInfo.elementsPerPage,
this.optionsList = list.page; list.pageInfo.currentPage,
if (this.model.value) { list.pageInfo.totalElements,
this.setCurrentValue(this.model.value, true); list.pageInfo.totalPages
} );
this.selectedIndex = 0;
this.updatePageInfo( this.cdr.detectChanges();
list.pageInfo.elementsPerPage, });
list.pageInfo.currentPage,
list.pageInfo.totalElements,
list.pageInfo.totalPages
);
this.cdr.detectChanges();
});
this.group.get(this.model.id).valueChanges.pipe(distinctUntilChanged())
.subscribe((value) => {
this.setCurrentValue(value);
});
} }
/** /**
@@ -94,10 +102,30 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
openDropdown(sdRef: NgbDropdown) { openDropdown(sdRef: NgbDropdown) {
if (!this.model.readOnly) { if (!this.model.readOnly) {
this.group.markAsUntouched(); this.group.markAsUntouched();
this.inputText = null;
this.updatePageInfo(this.model.maxOptions, 1);
this.loadOptions();
sdRef.open(); sdRef.open();
} }
} }
navigateDropdown(event: KeyboardEvent) {
if (event.key === 'ArrowDown') {
this.selectedIndex = Math.min(this.selectedIndex + 1, this.optionsList.length - 1);
} else if (event.key === 'ArrowUp') {
this.selectedIndex = Math.max(this.selectedIndex - 1, 0);
}
this.scrollToSelected();
}
scrollToSelected() {
const dropdownItems = this.dropdownMenu.nativeElement.querySelectorAll('.dropdown-item');
const selectedItem = dropdownItems[this.selectedIndex];
if (selectedItem) {
selectedItem.scrollIntoView({ block: 'nearest' });
}
}
/** /**
* KeyDown handler to allow toggling the dropdown via keyboard * KeyDown handler to allow toggling the dropdown via keyboard
* @param event KeyboardEvent * @param event KeyboardEvent
@@ -106,15 +134,56 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
selectOnKeyDown(event: KeyboardEvent, sdRef: NgbDropdown) { selectOnKeyDown(event: KeyboardEvent, sdRef: NgbDropdown) {
const keyName = event.key; const keyName = event.key;
if (keyName === ' ' || keyName === 'Enter') { if (keyName === 'Enter') {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
sdRef.toggle(); if (sdRef.isOpen()) {
this.onSelect(this.optionsList[this.selectedIndex]);
sdRef.close();
} else {
sdRef.open();
}
} else if (keyName === 'ArrowDown' || keyName === 'ArrowUp') { } else if (keyName === 'ArrowDown' || keyName === 'ArrowUp') {
this.openDropdown(sdRef); event.preventDefault();
event.stopPropagation();
this.navigateDropdown(event);
} else if (keyName === 'Backspace') {
this.removeKeyFromInput();
} else if (this.isAcceptableKey(keyName)) {
this.addKeyToInput(keyName);
} }
} }
addKeyToInput(keyName: string) {
if (this.inputText === null) {
this.inputText = '';
}
this.inputText += keyName;
// When a new key is added, we need to reset the page info
this.updatePageInfo(this.model.maxOptions, 1);
this.loadOptions();
}
removeKeyFromInput() {
if (this.inputText !== null) {
this.inputText = this.inputText.slice(0, -1);
if (this.inputText === '') {
this.inputText = null;
}
this.loadOptions();
}
}
isAcceptableKey(keyPress: string): boolean {
// allow all letters and numbers
if (keyPress.length === 1 && keyPress.match(/^[a-zA-Z0-9]*$/)) {
return true;
}
// Some other characters like space, dash, etc should be allowed as well
return this.acceptableKeys.includes(keyPress);
}
/** /**
* Loads any new entries * Loads any new entries
*/ */
@@ -127,7 +196,7 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
this.pageInfo.totalElements, this.pageInfo.totalElements,
this.pageInfo.totalPages this.pageInfo.totalPages
); );
this.vocabularyService.getVocabularyEntries(this.model.vocabularyOptions, this.pageInfo).pipe( this.vocabularyService.getVocabularyEntriesByValue(this.inputText, false, this.model.vocabularyOptions, this.pageInfo).pipe(
getFirstSucceededRemoteDataPayload(), getFirstSucceededRemoteDataPayload(),
catchError(() => observableOf(buildPaginatedList( catchError(() => observableOf(buildPaginatedList(
new PageInfo(), new PageInfo(),

View File

@@ -58,11 +58,12 @@
<div class="col text-right space-children-mr"> <div class="col text-right space-children-mr">
<ng-content select="[before]"></ng-content> <ng-content select="[before]"></ng-content>
<button *ngIf="displayCancel" type="reset" class="btn btn-outline-secondary" (click)="reset()"> <button *ngIf="displayCancel" type="reset" class="btn btn-outline-secondary" (click)="reset()">
<i class="fa fa-times"></i> {{cancelLabel | translate}} <i class="fa fa-times" aria-hidden="true"></i> {{cancelLabel | translate}}
</button> </button>
<ng-content select="[between]"></ng-content> <ng-content select="[between]"></ng-content>
<button *ngIf="displaySubmit" type="submit" class="btn btn-primary" (click)="onSubmit()" <button *ngIf="displaySubmit" type="submit" class="btn btn-primary" (click)="onSubmit()"
[disabled]="!(isValid() | async)"><i class="fas fa-save"></i> {{submitLabel | translate}} [disabled]="!(isValid() | async)">
<i class="fas fa-save" aria-hidden="true"></i> {{submitLabel | translate}}
</button> </button>
<ng-content select="[after]"></ng-content> <ng-content select="[after]"></ng-content>
</div> </div>

View File

@@ -9,7 +9,9 @@
[dsDebounce]="debounceTime" (onDebounce)="find($event)" [dsDebounce]="debounceTime" (onDebounce)="find($event)"
[placeholder]="placeholder" [placeholder]="placeholder"
[ngModelOptions]="{standalone: true}" autocomplete="off"/> [ngModelOptions]="{standalone: true}" autocomplete="off"/>
<input type="submit" class="d-none"/> <button class="sr-only" type="submit">
{{'search.filters.search.submit' | translate}}
</button>
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}"> <div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}">
<div class="dropdown-list"> <div class="dropdown-list">
<div *ngFor="let suggestionOption of suggestions"> <div *ngFor="let suggestionOption of suggestions">

View File

@@ -20,10 +20,9 @@
[ngModelOptions]="{standalone: true}" autocomplete="off" [ngModelOptions]="{standalone: true}" autocomplete="off"
/> />
</ng-template> </ng-template>
<label class="d-none"> <button class="sr-only" type="submit">
<input type="submit"/> {{'search.filters.search.submit' | translate}}
<span>{{'search.filters.search.submit' | translate}}</span> </button>
</label>
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}"> <div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}">
<div class="dropdown-list"> <div class="dropdown-list">
<div *ngFor="let suggestionOption of suggestions"> <div *ngFor="let suggestionOption of suggestions">

View File

@@ -9,7 +9,9 @@
[dsDebounce]="debounceTime" (onDebounce)="find($event)" [dsDebounce]="debounceTime" (onDebounce)="find($event)"
[placeholder]="placeholder" [placeholder]="placeholder"
[ngModelOptions]="{standalone: true}" autocomplete="off"/> [ngModelOptions]="{standalone: true}" autocomplete="off"/>
<input type="submit" class="d-none"/> <button class="sr-only" type="submit">
{{'search.filters.search.submit' | translate}}
</button>
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}"> <div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}">
<div class="dropdown-list"> <div class="dropdown-list">
<div *ngFor="let suggestionOption of suggestions"> <div *ngFor="let suggestionOption of suggestions">

View File

@@ -10,7 +10,9 @@
[placeholder]="placeholder" [placeholder]="placeholder"
ng-model-options="{standalone: true}" ng-model-options="{standalone: true}"
autocomplete="off"> autocomplete="off">
<input type="submit" class="d-none"/> <button class="sr-only" type="submit">
{{'search.filters.search.submit' | translate}}
</button>
<div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}"> <div class="autocomplete dropdown-menu" [ngClass]="{'show': (show | async) && isNotEmpty(suggestions)}">
<div class="dropdown-list"> <div class="dropdown-list">
<div *ngFor="let suggestionOption of suggestions"> <div *ngFor="let suggestionOption of suggestions">

View File

@@ -1,9 +1,8 @@
<form class="form-login" <form class="form-login"
(ngSubmit)="submit()" (ngSubmit)="submit()"
[formGroup]="form" novalidate> [formGroup]="form" novalidate>
<label class="sr-only">{{"login.form.email" | translate}}</label>
<input [attr.aria-label]="'login.form.email' |translate" <input [attr.aria-label]="'login.form.email' |translate"
autocomplete="off" autocomplete="username"
autofocus autofocus
class="form-control form-control-lg position-relative" class="form-control form-control-lg position-relative"
formControlName="email" formControlName="email"
@@ -11,9 +10,8 @@
required required
type="email" type="email"
[attr.data-test]="'email' | dsBrowserOnly"> [attr.data-test]="'email' | dsBrowserOnly">
<label class="sr-only">{{"login.form.password" | translate}}</label>
<input [attr.aria-label]="'login.form.password' |translate" <input [attr.aria-label]="'login.form.password' |translate"
autocomplete="off" autocomplete="current-password"
class="form-control form-control-lg position-relative mb-3" class="form-control form-control-lg position-relative mb-3"
placeholder="{{'login.form.password' | translate}}" placeholder="{{'login.form.password' | translate}}"
formControlName="password" formControlName="password"

View File

@@ -11,7 +11,7 @@
<table id="collection-select" class="table table-striped table-hover"> <table id="collection-select" class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th aria-hidden="true"></th> <th><span class="sr-only">{{'collection.select.table.selected' | translate}}</span></th>
<th scope="col">{{'collection.select.table.title' | translate}}</th> <th scope="col">{{'collection.select.table.title' | translate}}</th>
</tr> </tr>
</thead> </thead>

View File

@@ -11,7 +11,7 @@
<table id="item-select" class="table table-striped table-hover"> <table id="item-select" class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th aria-hidden="true"></th> <th><span class="sr-only">{{'item.select.table.selected' | translate}}</span></th>
<th *ngIf="!hideCollection" scope="col">{{'item.select.table.collection' | translate}}</th> <th *ngIf="!hideCollection" scope="col">{{'item.select.table.collection' | translate}}</th>
<th scope="col">{{'item.select.table.author' | translate}}</th> <th scope="col">{{'item.select.table.author' | translate}}</th>
<th scope="col">{{'item.select.table.title' | translate}}</th> <th scope="col">{{'item.select.table.title' | translate}}</th>

View File

@@ -7,15 +7,29 @@
</div> </div>
<div class="col"> <div class="col">
<div *ngIf="!hideGear" ngbDropdown #paginationControls="ngbDropdown" placement="bottom-right" class="d-inline-block float-right"> <div *ngIf="!hideGear" ngbDropdown #paginationControls="ngbDropdown" placement="bottom-right" class="d-inline-block float-right">
<button class="btn btn-secondary" id="paginationControls" ngbDropdownToggle [title]="'pagination.options.description' | translate" [attr.aria-label]="'pagination.options.description' | translate"><i class="fas fa-cog" aria-hidden="true"></i></button> <button class="btn btn-secondary" id="paginationControls" ngbDropdownToggle [title]="'pagination.options.description' | translate" [attr.aria-label]="'pagination.options.description' | translate" aria-haspopup="true" aria-expanded="false"><i class="fas fa-cog" aria-hidden="true"></i></button>
<div id="paginationControlsDropdownMenu" aria-labelledby="paginationControls" ngbDropdownMenu> <ul id="paginationControlsDropdownMenu" aria-labelledby="paginationControls" role="menu" ngbDropdownMenu>
<h6 class="dropdown-header">{{ 'pagination.results-per-page' | translate}}</h6> <li role="menuitem">
<button class="dropdown-item" *ngFor="let item of pageSizeOptions" (click)="doPageSizeChange(item)"><i [ngClass]="{'invisible': item != (pageSize$|async)}" class="fas fa-check" aria-hidden="true"></i> {{item}} </button> <span class="dropdown-header" id="pagination-control_results-per-page" role="heading">{{ 'pagination.results-per-page' | translate}}</span>
<ng-container *ngIf="!hideSortOptions"> <ul aria-labelledby="pagination-control_results-per-page" class="list-unstyled" role="listbox">
<h6 class="dropdown-header">{{ 'pagination.sort-direction' | translate}}</h6> <li *ngFor="let item of pageSizeOptions" role="option" [attr.aria-selected]="item === (pageSize$ | async)">
<button class="dropdown-item" *ngFor="let direction of (sortDirections | dsKeys)" (click)="doSortDirectionChange(direction.value)"><i [ngClass]="{'invisible': direction.value !== (sortDirection$ |async)}" class="fas fa-check" aria-hidden="true"></i> {{'sorting.' + direction.key | translate}} </button> <button (click)="doPageSizeChange(item)" class="dropdown-item">
</ng-container> <i [ngClass]="{'invisible': item !== (pageSize$ | async) }" class="fas fa-check" aria-hidden="true"></i> {{item}}
</div> </button>
</li>
</ul>
</li>
<li *ngIf="!hideSortOptions" role="menuitem">
<span class="dropdown-header" id="pagination-control_sort-direction" role="heading">{{ 'pagination.sort-direction' | translate}}</span>
<ul aria-labelledby="pagination-control_sort-direction" class="list-unstyled" role="listbox">
<li *ngFor="let direction of (sortDirections | dsKeys)" [attr.aria-selected]="direction.value === (sortDirection$ | async)" role="option">
<button class="dropdown-item" (click)="doSortDirectionChange(direction.value)">
<i [ngClass]="{'invisible': direction.value !== (sortDirection$ |async)}" class="fas fa-check" aria-hidden="true"></i> {{'sorting.' + direction.key | translate}}
</button>
</li>
</ul>
</li>
</ul>
</div> </div>
<ds-rss></ds-rss> <ds-rss></ds-rss>
</div> </div>

View File

@@ -167,7 +167,7 @@ export class PaginationComponent implements OnDestroy, OnInit {
/** /**
* Number of items per page. * Number of items per page.
*/ */
public pageSize$; public pageSize$: Observable<number>;
/** /**
* Declare SortDirection enumeration to use it in the template * Declare SortDirection enumeration to use it in the template
@@ -188,7 +188,7 @@ export class PaginationComponent implements OnDestroy, OnInit {
/** /**
* Name of the field that's used to sort by * Name of the field that's used to sort by
*/ */
public sortField$; public sortField$: Observable<string>;
public defaultSortField = 'name'; public defaultSortField = 'name';
/** /**

View File

@@ -5,8 +5,8 @@
[id]="entry.id" [id]="entry.id"
[ngModel]="entry.checked" [ngModel]="entry.checked"
(ngModelChange)="this.toggleCheckbox.emit($event);"> (ngModelChange)="this.toggleCheckbox.emit($event);">
<label class="custom-control-label" [for]="entry.id" <label class="custom-control-label" [for]="entry.id">
[attr.aria-label]="(entry.checked ? 'resource-policies.table.headers.deselect' : 'resource-policies.table.headers.select') | translate"> <span class="sr-only">{{(entry.checked ? 'resource-policies.table.headers.deselect' : 'resource-policies.table.headers.select') | translate}}</span>
</label> </label>
</div> </div>
</td> </td>
@@ -30,12 +30,12 @@
<button class="btn btn-outline-primary btn-sm" <button class="btn btn-outline-primary btn-sm"
[title]="'resource-policies.table.headers.edit.policy' | translate" [title]="'resource-policies.table.headers.edit.policy' | translate"
(click)="redirectToResourcePolicyEditPage()"> (click)="redirectToResourcePolicyEditPage()">
<i class="fas fa-edit fa-fw"></i> <i class="fas fa-edit fa-fw" aria-hidden="true"></i>
</button> </button>
<button *ngIf="(groupName$ | async) !== undefined" class="btn btn-outline-primary btn-sm" <button *ngIf="(groupName$ | async) !== undefined" class="btn btn-outline-primary btn-sm"
[title]="'resource-policies.table.headers.edit.group' | translate" [title]="'resource-policies.table.headers.edit.group' | translate"
(click)="redirectToGroupEditPage()"> (click)="redirectToGroupEditPage()">
<i class="fas fa-users fa-fw"></i> <i class="fas fa-users fa-fw" aria-hidden="true"></i>
</button> </button>
</div> </div>
</td> </td>

View File

@@ -17,10 +17,10 @@
[title]="'resource-policies.delete.btn.title' | translate" [title]="'resource-policies.delete.btn.title' | translate"
(click)="deleteSelectedResourcePolicies()"> (click)="deleteSelectedResourcePolicies()">
<span *ngIf="(isProcessingDelete() | async)"> <span *ngIf="(isProcessingDelete() | async)">
<i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}} <i class='fas fa-circle-notch fa-spin' aria-hidden="true"></i> {{'submission.workflow.tasks.generic.processing' | translate}}
</span> </span>
<span *ngIf="!(isProcessingDelete() | async)"> <span *ngIf="!(isProcessingDelete() | async)">
<i class='fas fa-trash-alt fa-fw'></i> <i class='fas fa-trash-alt fa-fw' aria-hidden="true"></i>
{{'resource-policies.delete.btn' | translate}} {{'resource-policies.delete.btn' | translate}}
</span> </span>
</button> </button>
@@ -28,7 +28,7 @@
[disabled]="(isProcessingDelete() | async)" [disabled]="(isProcessingDelete() | async)"
[title]="'resource-policies.add.for.' + resourceType | translate" [title]="'resource-policies.add.for.' + resourceType | translate"
(click)="redirectToResourcePolicyCreatePage()"> (click)="redirectToResourcePolicyCreatePage()">
<i class='fas fa-plus'></i> <i class='fas fa-plus' aria-hidden="true"></i>
{{'resource-policies.add.button' | translate}} {{'resource-policies.add.button' | translate}}
</button> </button>
</div> </div>
@@ -42,8 +42,8 @@
class="custom-control-input" class="custom-control-input"
[id]="'selectAll_' + resourceUUID" [id]="'selectAll_' + resourceUUID"
(change)="selectAllCheckbox($event)"> (change)="selectAllCheckbox($event)">
<label class="custom-control-label" [for]="'selectAll_' + resourceUUID" <label class="custom-control-label" [for]="'selectAll_' + resourceUUID">
[attr.aria-label]="(selectAllBtn.checked ? 'resource-policies.table.headers.deselect-all' : 'resource-policies.table.headers.select-all') | translate"> <span class="sr-only">{{(selectAllBtn.checked ? 'resource-policies.table.headers.deselect-all' : 'resource-policies.table.headers.select-all') | translate}}</span>
</label> </label>
</div> </div>
</th> </th>

View File

@@ -26,10 +26,9 @@
/> />
</label> </label>
</div> </div>
<label class="d-none"> <button class="sr-only" type="submit">
<input type="submit" class="d-none"/> {{'search.filters.search.submit' | translate}}
<span>{{'search.filters.search.submit' | translate}}</span> </button>
</label>
</form> </form>
<ng-container *ngIf="shouldShowSlider()"> <ng-container *ngIf="shouldShowSlider()">

View File

@@ -82,7 +82,7 @@ export class SubscriptionViewComponent {
deleteSubscriptionPopup(subscription: Subscription) { deleteSubscriptionPopup(subscription: Subscription) {
if (hasValue(subscription.id)) { if (hasValue(subscription.id)) {
const modalRef = this.modalService.open(ConfirmationModalComponent); const modalRef = this.modalService.open(ConfirmationModalComponent);
modalRef.componentInstance.dso = this.dso; modalRef.componentInstance.name = this.dsoNameService.getName(this.dso);
modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-subscription.header'; modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-subscription.header';
modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-subscription.info'; modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-subscription.info';
modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-subscription.cancel'; modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-subscription.cancel';

View File

@@ -23,7 +23,7 @@
<i class="fas fa-upload" aria-hidden="true"></i> <i class="fas fa-upload" aria-hidden="true"></i>
{{dropMsg | translate}}{{'uploader.or' | translate}} {{dropMsg | translate}}{{'uploader.or' | translate}}
<label for="inputFileUploader-{{uploaderId}}" class="btn btn-link m-0 p-0 ml-1" tabindex="0" (keyup.enter)="$event.stopImmediatePropagation(); fileInput.click()"> <label for="inputFileUploader-{{uploaderId}}" class="btn btn-link m-0 p-0 ml-1" tabindex="0" (keyup.enter)="$event.stopImmediatePropagation(); fileInput.click()">
<span role="button" [attr.aria-label]="'uploader.browse' | translate">{{'uploader.browse' | translate}}</span> <span role="button" [attr.aria-label]="ariaLabel | translate">{{'uploader.browse' | translate}}</span>
</label> </label>
<input #fileInput id="inputFileUploader-{{uploaderId}}" class="d-none" type="file" ng2FileSelect [uploader]="uploader" multiple tabindex="0" /> <input #fileInput id="inputFileUploader-{{uploaderId}}" class="d-none" type="file" ng2FileSelect [uploader]="uploader" multiple tabindex="0" />
</span> </span>

View File

@@ -53,6 +53,11 @@ export class UploaderComponent {
*/ */
@Input() uploadProperties: UploaderProperties; @Input() uploadProperties: UploaderProperties;
/**
* The aria label to describe what kind of files need to be uploaded
*/
@Input() ariaLabel: string;
/** /**
* The function to call when upload is completed * The function to call when upload is completed
*/ */

View File

@@ -32,7 +32,7 @@
</button> </button>
<div ngbDropdownMenu <div ngbDropdownMenu
class="dropdown-menu" class="dropdown-menu p-0"
id="collectionControlsDropdownMenu" id="collectionControlsDropdownMenu"
aria-labelledby="collectionControlsMenuButton"> aria-labelledby="collectionControlsMenuButton">
<ds-themed-collection-dropdown <ds-themed-collection-dropdown

View File

@@ -1,25 +1,28 @@
<div *ngIf="(isXsOrSm$ | async)" class="input-group mb-2"> <div *ngIf="(isXsOrSm$ | async)" class="input-group mb-2">
<input type="text" class="form-control" (keyup.enter)="(searchString === '')?null:search()" [(ngModel)]="searchString" placeholder="{{'submission.import-external.search.placeholder' |translate}}" aria-label="" aria-describedby=""> <input type="text" class="form-control" (keyup.enter)="(searchString === '')?null:search()" [(ngModel)]="searchString" placeholder="{{'submission.import-external.search.placeholder' | translate}}" [attr.aria-label]="'submission.import-external.search.placeholder' | translate">
</div> </div>
<div class="input-group mb-5"> <div class="input-group mb-5">
<input *ngIf="!(isXsOrSm$ | async)" type="text" class="form-control" (keyup.enter)="(searchString === '')?null:search()" [(ngModel)]="searchString" placeholder="{{'submission.import-external.search.placeholder' |translate}}" aria-label="" aria-describedby=""> <input *ngIf="!(isXsOrSm$ | async)" type="text" class="form-control" (keyup.enter)="(searchString === '')?null:search()" [(ngModel)]="searchString" placeholder="{{'submission.import-external.search.placeholder' | translate}}" [attr.aria-label]="'submission.import-external.search.placeholder' | translate">
<div [ngClass]="{'input-group-append': !(isXsOrSm$ | async)}" ngbDropdown role="group" aria-label=""> <div [ngClass]="{'input-group-append': !(isXsOrSm$ | async)}" ngbDropdown role="group">
<button class="btn btn-outline-secondary w-fx" title="{{'submission.import-external.search.source.hint' |translate}}" ngbDropdownToggle>{{'submission.import-external.source.' + selectedElement?.name | translate}}</button> <button [attr.aria-label]="'submission.import-external.search.source.hint' |translate"
<div ngbDropdownMenu class="dropdown-menu scrollable-dropdown-menu w-100" class="btn btn-outline-secondary w-fx"
title="{{'submission.import-external.search.source.hint' |translate}}"
ngbDropdownToggle>
{{'submission.import-external.source.' + selectedElement?.name | translate}}
</button>
<ul ngbDropdownMenu class="dropdown-menu scrollable-dropdown-menu w-100"
aria-haspopup="true" aria-haspopup="true"
aria-expanded="false" aria-expanded="false">
aria-labelledby="scrollableDropdownMenuButton"> <li class="scrollable-menu"
<div class="scrollable-menu"
aria-labelledby="scrollableDropdownMenuButton"
infiniteScroll infiniteScroll
[infiniteScrollDistance]="2" [infiniteScrollDistance]="2"
[infiniteScrollThrottle]="50" [infiniteScrollThrottle]="50"
(scrolled)="onScroll()" (scrolled)="onScroll()"
[scrollWindow]="false"> [scrollWindow]="false">
<button ngbDropdownItem class="dropdown-item text-truncate" title="{{'submission.import-external.source.' + source?.name | translate}}" (click)="makeSourceSelection(source)" *ngFor="let source of sourceList">{{'submission.import-external.source.' + source?.name | translate}}</button> <button ngbDropdownItem class="dropdown-item text-truncate" (click)="makeSourceSelection(source)" *ngFor="let source of sourceList">{{'submission.import-external.source.' + source?.name | translate}}</button>
<div ngbDropdownItem class="scrollable-dropdown-loading text-center" *ngIf="sourceListLoading"><p>{{'submission.import-external.source.loading' | translate}}</p></div> <div ngbDropdownItem class="scrollable-dropdown-loading text-center" *ngIf="sourceListLoading"><p>{{'submission.import-external.source.loading' | translate}}</p></div>
</div> </li>
</div> </ul>
<button type="button" class="btn btn-primary" [title]="(searchString === '')?('submission.import-external.search.button.hint' | translate):('submission.import-external.search.button' | translate)" [disabled]="searchString === ''" (click)="search()">{{'submission.import-external.search.button' | translate}}</button> <button type="button" class="btn btn-primary" [title]="(searchString === '')?('submission.import-external.search.button.hint' | translate):''" [disabled]="searchString === ''" (click)="search()">{{'submission.import-external.search.button' | translate}}</button>
</div> </div>
</div> </div>

View File

@@ -128,6 +128,8 @@
"admin.registries.bitstream-formats.table.name": "Name", "admin.registries.bitstream-formats.table.name": "Name",
"admin.registries.bitstream-formats.table.selected": "Selected bitstream formats",
"admin.registries.bitstream-formats.table.id": "ID", "admin.registries.bitstream-formats.table.id": "ID",
"admin.registries.bitstream-formats.table.return": "Back", "admin.registries.bitstream-formats.table.return": "Back",
@@ -142,7 +144,9 @@
"admin.registries.bitstream-formats.title": "Bitstream Format Registry", "admin.registries.bitstream-formats.title": "Bitstream Format Registry",
"admin.registries.bitstream-formats.select": "Select bitstream format", "admin.registries.bitstream-formats.select": "Select",
"admin.registries.bitstream-formats.deselect": "Deselect",
"admin.registries.metadata.breadcrumbs": "Metadata registry", "admin.registries.metadata.breadcrumbs": "Metadata registry",
@@ -160,8 +164,14 @@
"admin.registries.metadata.schemas.no-items": "No metadata schemas to show.", "admin.registries.metadata.schemas.no-items": "No metadata schemas to show.",
"admin.registries.metadata.schemas.select": "Select",
"admin.registries.metadata.schemas.deselect": "Deselect",
"admin.registries.metadata.schemas.table.delete": "Delete selected", "admin.registries.metadata.schemas.table.delete": "Delete selected",
"admin.registries.metadata.schemas.table.selected": "Selected schemas",
"admin.registries.metadata.schemas.table.id": "ID", "admin.registries.metadata.schemas.table.id": "ID",
"admin.registries.metadata.schemas.table.name": "Name", "admin.registries.metadata.schemas.table.name": "Name",
@@ -174,6 +184,10 @@
"admin.registries.schema.description": "This is the metadata schema for \"{{namespace}}\".", "admin.registries.schema.description": "This is the metadata schema for \"{{namespace}}\".",
"admin.registries.schema.fields.select": "Select",
"admin.registries.schema.fields.deselect": "Deselect",
"admin.registries.schema.fields.head": "Schema metadata fields", "admin.registries.schema.fields.head": "Schema metadata fields",
"admin.registries.schema.fields.no-items": "No metadata fields to show.", "admin.registries.schema.fields.no-items": "No metadata fields to show.",
@@ -182,6 +196,8 @@
"admin.registries.schema.fields.table.field": "Field", "admin.registries.schema.fields.table.field": "Field",
"admin.registries.schema.fields.table.selected": "Selected metadata fields",
"admin.registries.schema.fields.table.id": "ID", "admin.registries.schema.fields.table.id": "ID",
"admin.registries.schema.fields.table.scopenote": "Scope Note", "admin.registries.schema.fields.table.scopenote": "Scope Note",
@@ -892,6 +908,8 @@
"claimed-declined-task-search-result-list-element.title": "Declined, sent back to Review Manager's workflow", "claimed-declined-task-search-result-list-element.title": "Declined, sent back to Review Manager's workflow",
"collection.browse.logo": "Browse for a collection logo",
"collection.create.head": "Create a Collection", "collection.create.head": "Create a Collection",
"collection.create.notifications.success": "Successfully created the Collection", "collection.create.notifications.success": "Successfully created the Collection",
@@ -1088,6 +1106,8 @@
"collection.listelement.badge": "Collection", "collection.listelement.badge": "Collection",
"collection.logo": "Collection logo",
"collection.page.browse.recent.head": "Recent Submissions", "collection.page.browse.recent.head": "Recent Submissions",
"collection.page.browse.recent.empty": "No items to show", "collection.page.browse.recent.empty": "No items to show",
@@ -1104,6 +1124,8 @@
"collection.select.empty": "No collections to show", "collection.select.empty": "No collections to show",
"collection.select.table.selected": "Selected collections",
"collection.select.table.select": "Select collection", "collection.select.table.select": "Select collection",
"collection.select.table.deselect": "Deselect collection", "collection.select.table.deselect": "Deselect collection",
@@ -1168,6 +1190,12 @@
"communityList.showMore": "Show More", "communityList.showMore": "Show More",
"communityList.expand": "Expand {{ name }}",
"communityList.collapse": "Collapse {{ name }}",
"community.browse.logo": "Browse for a community logo",
"community.create.head": "Create a Community", "community.create.head": "Create a Community",
"community.create.notifications.success": "Successfully created the Community", "community.create.notifications.success": "Successfully created the Community",
@@ -1244,6 +1272,8 @@
"community.listelement.badge": "Community", "community.listelement.badge": "Community",
"community.logo": "Community logo",
"comcol-role.edit.no-group": "None", "comcol-role.edit.no-group": "None",
"comcol-role.edit.create": "Create", "comcol-role.edit.create": "Create",
@@ -1690,7 +1720,7 @@
"forgot-password.form.label.passwordrepeat": "Retype to confirm", "forgot-password.form.label.passwordrepeat": "Retype to confirm",
"forgot-password.form.error.empty-password": "Please enter a password in the box below.", "forgot-password.form.error.empty-password": "Please enter a password in the boxes above.",
"forgot-password.form.error.matching-passwords": "The passwords do not match.", "forgot-password.form.error.matching-passwords": "The passwords do not match.",
@@ -1914,7 +1944,7 @@
"info.feedback.page-label": "Page", "info.feedback.page-label": "Page",
"info.feedback.page_help": "Tha page related to your feedback", "info.feedback.page_help": "The page related to your feedback",
"item.alerts.private": "This item is non-discoverable", "item.alerts.private": "This item is non-discoverable",
@@ -2142,6 +2172,8 @@
"item.edit.metadata.headers.value": "Value", "item.edit.metadata.headers.value": "Value",
"item.edit.metadata.metadatafield": "Edit field",
"item.edit.metadata.metadatafield.error": "An error occurred validating the metadata field", "item.edit.metadata.metadatafield.error": "An error occurred validating the metadata field",
"item.edit.metadata.metadatafield.invalid": "Please choose a valid metadata field", "item.edit.metadata.metadatafield.invalid": "Please choose a valid metadata field",
@@ -2534,6 +2566,8 @@
"item.select.empty": "No items to show", "item.select.empty": "No items to show",
"item.select.table.selected": "Selected items",
"item.select.table.select": "Select item", "item.select.table.select": "Select item",
"item.select.table.deselect": "Deselect item", "item.select.table.deselect": "Deselect item",
@@ -2664,6 +2698,8 @@
"itemtemplate.edit.metadata.headers.value": "Value", "itemtemplate.edit.metadata.headers.value": "Value",
"itemtemplate.edit.metadata.metadatafield": "Edit field",
"itemtemplate.edit.metadata.metadatafield.error": "An error occurred validating the metadata field", "itemtemplate.edit.metadata.metadatafield.error": "An error occurred validating the metadata field",
"itemtemplate.edit.metadata.metadatafield.invalid": "Please choose a valid metadata field", "itemtemplate.edit.metadata.metadatafield.invalid": "Please choose a valid metadata field",
@@ -3458,8 +3494,6 @@
"profile.special.groups.head": "Authorization special groups you belong to", "profile.special.groups.head": "Authorization special groups you belong to",
"profile.head": "Update Profile",
"profile.metadata.form.error.firstname.required": "First Name is required", "profile.metadata.form.error.firstname.required": "First Name is required",
"profile.metadata.form.error.lastname.required": "Last Name is required", "profile.metadata.form.error.lastname.required": "Last Name is required",
@@ -4570,6 +4604,8 @@
"submission.sections.general.no-collection": "No collection found", "submission.sections.general.no-collection": "No collection found",
"submission.sections.general.no-entity": "No entity types found",
"submission.sections.general.no-sections": "No options available", "submission.sections.general.no-sections": "No options available",
"submission.sections.general.save_error_notice": "There was an issue when saving the item, please try again later.", "submission.sections.general.save_error_notice": "There was an issue when saving the item, please try again later.",

View File

@@ -106,4 +106,7 @@
--ds-dso-edit-lang-width: 90px; --ds-dso-edit-lang-width: 90px;
--ds-dso-edit-actions-width: 173px; --ds-dso-edit-actions-width: 173px;
--ds-dso-edit-virtual-tooltip-min-width: 300px; --ds-dso-edit-virtual-tooltip-min-width: 300px;
--ds-comcol-logo-max-width: 500px;
--ds-comcol-logo-max-height: 500px;
} }