From 565c4106f9cfa55d01cda77a43980d9ff3715dbf Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Fri, 31 Dec 2021 10:26:28 +0530 Subject: [PATCH 001/160] [DSC-389] add pagination. --- .../browse-by-metadata-page.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html index 2321da0204..1ec224e6b7 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html @@ -31,7 +31,7 @@ [sortConfig]="(currentSort$ |async)" [type]="startsWithType" [startsWithOptions]="startsWithOptions" - [enableArrows]="true" + [enableArrows]="false" (prev)="goPrev()" (next)="goNext()"> From e4d099df43189d9abb9e6c787918549dd5fb4132 Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Wed, 2 Feb 2022 14:05:10 +0530 Subject: [PATCH 002/160] [DSC-389] pagination added. --- .../browse-by-metadata-page.component.html | 2 +- .../shared/browse-by/browse-by.component.html | 36 +++---------- .../browse-by/browse-by.component.spec.ts | 8 +-- .../shared/browse-by/browse-by.component.ts | 12 ++--- .../object-collection.component.html | 5 ++ .../object-collection.component.ts | 29 ++++++++++ .../object-detail.component.html | 7 ++- .../object-detail/object-detail.component.ts | 29 ++++++++++ .../object-grid/object-grid.component.html | 8 ++- .../object-grid/object-grid.component.ts | 30 +++++++++++ .../object-list/object-list.component.html | 7 ++- .../object-list/object-list.component.ts | 29 ++++++++++ .../pagination/pagination.component.html | 39 +++++++++----- .../shared/pagination/pagination.component.ts | 53 +++++++++++++++++++ 14 files changed, 239 insertions(+), 55 deletions(-) diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html index 1ec224e6b7..b38fcb0235 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html @@ -31,7 +31,7 @@ [sortConfig]="(currentSort$ |async)" [type]="startsWithType" [startsWithOptions]="startsWithOptions" - [enableArrows]="false" + [showPaginator]="true" (prev)="goPrev()" (next)="goNext()"> diff --git a/src/app/shared/browse-by/browse-by.component.html b/src/app/shared/browse-by/browse-by.component.html index 6d1422293d..cc126768e4 100644 --- a/src/app/shared/browse-by/browse-by.component.html +++ b/src/app/shared/browse-by/browse-by.component.html @@ -2,37 +2,14 @@

{{title | translate}}

-
+ [config]="paginationConfig" + [sortConfig]="sortConfig" + [showPaginator]="showPaginator" + [objects]="objects" + (prev)="goPrev()" + (next)="goNext()"> -
-
-
-
-
- -
- - - - -
-
-
-
-
    -
  • - -
  • -
-
- - -
-
@@ -40,3 +17,4 @@ {{'browse.empty' | translate}} + diff --git a/src/app/shared/browse-by/browse-by.component.spec.ts b/src/app/shared/browse-by/browse-by.component.spec.ts index 896537b63a..a39a207c77 100644 --- a/src/app/shared/browse-by/browse-by.component.spec.ts +++ b/src/app/shared/browse-by/browse-by.component.spec.ts @@ -129,9 +129,9 @@ describe('BrowseByComponent', () => { expect(fixture.debugElement.query(By.css('ds-viewable-collection'))).toBeDefined(); }); - describe('when enableArrows is true and objects are defined', () => { + describe('when showPaginator is true and objects are defined', () => { beforeEach(() => { - comp.enableArrows = true; + comp.showPaginator = true; comp.objects$ = mockItemsRD$; comp.paginationConfig = paginationConfig; @@ -188,7 +188,7 @@ describe('BrowseByComponent', () => { }); }); - describe('when enableArrows is true and browseEntries are provided', () => { + describe('when showPaginator is true and browseEntries are provided', () => { let browseEntries; beforeEach(() => { @@ -209,7 +209,7 @@ describe('BrowseByComponent', () => { }), ]; - comp.enableArrows = true; + comp.showPaginator = true; comp.objects$ = createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), browseEntries)); comp.paginationConfig = paginationConfig; comp.sortConfig = Object.assign(new SortOptions('dc.title', SortDirection.ASC)); diff --git a/src/app/shared/browse-by/browse-by.component.ts b/src/app/shared/browse-by/browse-by.component.ts index 861e431635..8245897609 100644 --- a/src/app/shared/browse-by/browse-by.component.ts +++ b/src/app/shared/browse-by/browse-by.component.ts @@ -67,30 +67,30 @@ export class BrowseByComponent implements OnInit { /** * Whether or not the pagination should be rendered as simple previous and next buttons instead of the normal pagination */ - @Input() enableArrows = false; + @Input() showPaginator = true; /** - * If enableArrows is set to true, should it hide the options gear? + * If showPaginator is set to true, should it hide the options gear? */ @Input() hideGear = false; /** - * If enableArrows is set to true, emit when the previous button is clicked + * If showPaginator is set to true, emit when the previous button is clicked */ @Output() prev = new EventEmitter(); /** - * If enableArrows is set to true, emit when the next button is clicked + * If showPaginator is set to true, emit when the next button is clicked */ @Output() next = new EventEmitter(); /** - * If enableArrows is set to true, emit when the page size is changed + * If showPaginator is set to true, emit when the page size is changed */ @Output() pageSizeChange = new EventEmitter(); /** - * If enableArrows is set to true, emit when the sort direction is changed + * If showPaginator is set to true, emit when the sort direction is changed */ @Output() sortDirectionChange = new EventEmitter(); diff --git a/src/app/shared/object-collection/object-collection.component.html b/src/app/shared/object-collection/object-collection.component.html index f2778757ef..999ae9a120 100644 --- a/src/app/shared/object-collection/object-collection.component.html +++ b/src/app/shared/object-collection/object-collection.component.html @@ -6,6 +6,7 @@ [linkType]="linkType" [context]="context" [hidePaginationDetail]="hidePaginationDetail" + [showPaginator]="showPaginator" (paginationChange)="onPaginationChange($event)" (pageChange)="onPageChange($event)" (pageSizeChange)="onPageSizeChange($event)" @@ -19,6 +20,8 @@ [importConfig]="importConfig" (importObject)="importObject.emit($event)" (contentChange)="contentChange.emit()" + (prev)="goPrev()" + (next)="goNext()" *ngIf="(currentMode$ | async) === viewModeEnum.ListElement"> @@ -29,6 +32,7 @@ [linkType]="linkType" [context]="context" [hidePaginationDetail]="hidePaginationDetail" + [showPaginator]="showPaginator" (paginationChange)="onPaginationChange($event)" (pageChange)="onPageChange($event)" (pageSizeChange)="onPageSizeChange($event)" @@ -44,6 +48,7 @@ [linkType]="linkType" [context]="context" [hidePaginationDetail]="hidePaginationDetail" + [showPaginator]="showPaginator" *ngIf="(currentMode$ | async) === viewModeEnum.DetailedListElement"> diff --git a/src/app/shared/object-collection/object-collection.component.ts b/src/app/shared/object-collection/object-collection.component.ts index 29fdb37ea1..f2706f1a4c 100644 --- a/src/app/shared/object-collection/object-collection.component.ts +++ b/src/app/shared/object-collection/object-collection.component.ts @@ -88,6 +88,11 @@ export class ObjectCollectionComponent implements OnInit { */ @Input() hidePaginationDetail = false; + /** + * Whether or not the pagination should be rendered as simple previous and next buttons instead of the normal pagination + */ + @Input() showPaginator = true; + /** * the page info of the list */ @@ -122,6 +127,16 @@ export class ObjectCollectionComponent implements OnInit { */ @Output() sortFieldChange: EventEmitter = new EventEmitter(); + /** + * If showPaginator is set to true, emit when the previous button is clicked + */ + @Output() prev = new EventEmitter(); + + /** + * If showPaginator is set to true, emit when the next button is clicked + */ + @Output() next = new EventEmitter(); + /** * Emits the current view mode */ @@ -192,4 +207,18 @@ export class ObjectCollectionComponent implements OnInit { this.paginationChange.emit(event); } + /** + * Go to the previous page + */ + goPrev() { + this.prev.emit(true); + } + + /** + * Go to the next page + */ + goNext() { + this.next.emit(true); + } + } diff --git a/src/app/shared/object-detail/object-detail.component.html b/src/app/shared/object-detail/object-detail.component.html index 7fef7d9689..32728d92e4 100644 --- a/src/app/shared/object-detail/object-detail.component.html +++ b/src/app/shared/object-detail/object-detail.component.html @@ -3,14 +3,19 @@ [pageInfoState]="objects?.payload" [collectionSize]="objects?.payload?.totalElements" [sortOptions]="sortConfig" + [objects]="objects" [hideGear]="hideGear" [hidePaginationDetail]="hidePaginationDetail" [hidePagerWhenSinglePage]="hidePagerWhenSinglePage" + [showPaginator]="showPaginator" (pageChange)="onPageChange($event)" (pageSizeChange)="onPageSizeChange($event)" (sortDirectionChange)="onSortDirectionChange($event)" (sortFieldChange)="onSortFieldChange($event)" - (paginationChange)="onPaginationChange($event)"> + (paginationChange)="onPaginationChange($event)" + (prev)="goPrev()" + (next)="goNext()" + >
diff --git a/src/app/shared/object-detail/object-detail.component.ts b/src/app/shared/object-detail/object-detail.component.ts index 8b94746fbd..05d2ca4b5b 100644 --- a/src/app/shared/object-detail/object-detail.component.ts +++ b/src/app/shared/object-detail/object-detail.component.ts @@ -66,6 +66,21 @@ export class ObjectDetailComponent { */ @Input() context: Context; + /** + * Whether or not the pagination should be rendered as simple previous and next buttons instead of the normal pagination + */ + @Input() showPaginator = true; + + /** + * If showPaginator is set to true, emit when the previous button is clicked + */ + @Output() prev = new EventEmitter(); + + /** + * If showPaginator is set to true, emit when the next button is clicked + */ + @Output() next = new EventEmitter(); + /** * The list of objects to paginate */ @@ -168,4 +183,18 @@ export class ObjectDetailComponent { this.paginationChange.emit(event); } + /** + * Go to the previous page + */ + goPrev() { + this.prev.emit(true); + } + + /** + * Go to the next page + */ + goNext() { + this.next.emit(true); + } + } diff --git a/src/app/shared/object-grid/object-grid.component.html b/src/app/shared/object-grid/object-grid.component.html index 0afd623d86..0961aa2989 100644 --- a/src/app/shared/object-grid/object-grid.component.html +++ b/src/app/shared/object-grid/object-grid.component.html @@ -4,13 +4,18 @@ [collectionSize]="objects?.payload?.totalElements" [sortOptions]="sortConfig" [hideGear]="hideGear" + [objects]="objects" [hidePagerWhenSinglePage]="hidePagerWhenSinglePage" [hidePaginationDetail]="hidePaginationDetail" + [showPaginator]="showPaginator" (pageChange)="onPageChange($event)" (pageSizeChange)="onPageSizeChange($event)" (sortDirectionChange)="onSortDirectionChange($event)" (sortFieldChange)="onSortFieldChange($event)" - (paginationChange)="onPaginationChange($event)"> + (paginationChange)="onPaginationChange($event)" + (prev)="goPrev()" + (next)="goNext()" + >
@@ -22,3 +27,4 @@ + diff --git a/src/app/shared/object-grid/object-grid.component.ts b/src/app/shared/object-grid/object-grid.component.ts index 39c3d56f7f..1b5ab075e2 100644 --- a/src/app/shared/object-grid/object-grid.component.ts +++ b/src/app/shared/object-grid/object-grid.component.ts @@ -49,6 +49,11 @@ export class ObjectGridComponent implements OnInit { */ @Input() sortConfig: SortOptions; + /** + * Whether or not the pagination should be rendered as simple previous and next buttons instead of the normal pagination + */ + @Input() showPaginator = true; + /** * The whether or not the gear is hidden */ @@ -134,6 +139,17 @@ export class ObjectGridComponent implements OnInit { * Event's payload equals to the newly selected sort field. */ @Output() sortFieldChange: EventEmitter = new EventEmitter(); + + /** + * If showPaginator is set to true, emit when the previous button is clicked + */ + @Output() prev = new EventEmitter(); + + /** + * If showPaginator is set to true, emit when the next button is clicked + */ + @Output() next = new EventEmitter(); + data: any = {}; columns$: Observable; @@ -225,4 +241,18 @@ export class ObjectGridComponent implements OnInit { this.paginationChange.emit(event); } + /** + * Go to the previous page + */ + goPrev() { + this.prev.emit(true); + } + + /** + * Go to the next page + */ + goNext() { + this.next.emit(true); + } + } diff --git a/src/app/shared/object-list/object-list.component.html b/src/app/shared/object-list/object-list.component.html index 331ff1cb28..1c6d01ba11 100644 --- a/src/app/shared/object-list/object-list.component.html +++ b/src/app/shared/object-list/object-list.component.html @@ -2,15 +2,20 @@ [paginationOptions]="config" [pageInfoState]="objects?.payload" [collectionSize]="objects?.payload?.totalElements" + [objects]="objects" [sortOptions]="sortConfig" [hideGear]="hideGear" [hidePagerWhenSinglePage]="hidePagerWhenSinglePage" [hidePaginationDetail]="hidePaginationDetail" + [showPaginator]="showPaginator" (pageChange)="onPageChange($event)" (pageSizeChange)="onPageSizeChange($event)" (sortDirectionChange)="onSortDirectionChange($event)" (sortFieldChange)="onSortFieldChange($event)" - (paginationChange)="onPaginationChange($event)"> + (paginationChange)="onPaginationChange($event)" + (prev)="goPrev()" + (next)="goNext()" + >
  • (); + /** + * If showPaginator is set to true, emit when the previous button is clicked + */ + @Output() prev = new EventEmitter(); + + /** + * If showPaginator is set to true, emit when the next button is clicked + */ + @Output() next = new EventEmitter(); + /** * The current listable objects */ @@ -192,4 +207,18 @@ export class ObjectListComponent { onPaginationChange(event) { this.paginationChange.emit(event); } + + /** + * Go to the previous page + */ + goPrev() { + this.prev.emit(true); + } + + /** + * Go to the next page + */ + goNext() { + this.next.emit(true); + } } diff --git a/src/app/shared/pagination/pagination.component.html b/src/app/shared/pagination/pagination.component.html index 2a9aa1a062..785d38af96 100644 --- a/src/app/shared/pagination/pagination.component.html +++ b/src/app/shared/pagination/pagination.component.html @@ -1,7 +1,7 @@
    -
    +
    {{ 'pagination.showing.label' | translate }} {{ 'pagination.showing.detail' | translate:(getShowingDetails(collectionSize)|async)}}
    @@ -20,16 +20,31 @@
    - diff --git a/src/app/shared/pagination/pagination.component.ts b/src/app/shared/pagination/pagination.component.ts index 8f1c6bf26f..21f495956d 100644 --- a/src/app/shared/pagination/pagination.component.ts +++ b/src/app/shared/pagination/pagination.component.ts @@ -20,6 +20,10 @@ import { hasValue } from '../empty.util'; import { PageInfo } from '../../core/shared/page-info.model'; import { PaginationService } from '../../core/pagination/pagination.service'; import { map } from 'rxjs/operators'; +import { RemoteData } from 'src/app/core/data/remote-data'; +import { PaginatedList } from 'src/app/core/data/paginated-list.model'; +import { ListableObject } from '../object-collection/shared/listable-object.model'; +import { ViewMode } from 'src/app/core/shared/view-mode.model'; /** * The default pagination controls component. @@ -33,6 +37,11 @@ import { map } from 'rxjs/operators'; encapsulation: ViewEncapsulation.Emulated }) export class PaginationComponent implements OnDestroy, OnInit { + /** + * ViewMode that should be passed to {@link ListableObjectComponentLoaderComponent}. + */ + viewMode: ViewMode = ViewMode.ListElement; + /** * Number of items in collection. */ @@ -53,6 +62,26 @@ export class PaginationComponent implements OnDestroy, OnInit { */ @Input() sortOptions: SortOptions; + /** + * Whether or not the pagination should be rendered as simple previous and next buttons instead of the normal pagination + */ + @Input() showPaginator = true; + + /** + * The current pagination configuration + */ + @Input() config?: PaginationComponentOptions; + + /** + * The list of listable objects to render in this component + */ + @Input() objects: RemoteData>; + + /** + * The current sorting configuration + */ + @Input() sortConfig: SortOptions; + /** * An event fired when the page is changed. * Event's payload equals to the newly selected page. @@ -163,6 +192,15 @@ export class PaginationComponent implements OnDestroy, OnInit { */ private subs: Subscription[] = []; + /** + * If showPaginator is set to true, emit when the previous button is clicked + */ + @Output() prev = new EventEmitter(); + + /** + * If showPaginator is set to true, emit when the next button is clicked + */ + @Output() next = new EventEmitter(); /** * Method provided by Angular. Invoked after the constructor. */ @@ -347,4 +385,19 @@ export class PaginationComponent implements OnDestroy, OnInit { map((hasMultiplePages) => hasMultiplePages || !this.hidePagerWhenSinglePage) ); } + + /** + * Go to the previous page + */ + goPrev() { + this.prev.emit(true); + } + + /** + * Go to the next page + */ + goNext() { + this.next.emit(true); + } + } From 57ff37ec7f1b1caeb9860b9b3a87489b5402d542 Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Wed, 2 Feb 2022 19:11:06 +0530 Subject: [PATCH 003/160] [DSC-389] merged with main branch --- config/config.example.yml | 3 + src/app/app-routing-paths.ts | 6 + .../edit-bitstream-page.component.html | 2 +- .../edit-bitstream-page.component.scss | 4 + .../edit-bitstream-page.component.spec.ts | 539 +- .../edit-bitstream-page.component.ts | 264 +- src/app/core/auth/models/auth.method-type.ts | 3 +- src/app/core/auth/models/auth.method.ts | 5 + ...onfig-accesses-conditions-options.model.ts | 45 + .../models/config-submission-access.model.ts | 42 + .../config-submission-accesses.model.ts | 10 + src/app/core/config/models/config-type.ts | 2 + .../submission-accesses-config.service.ts | 42 + src/app/core/core.module.ts | 14 +- .../data/feature-authorization/feature-id.ts | 1 + .../feedback/feedback-data.service.spec.ts | 88 + .../core/feedback/feedback-data.service.ts | 49 + src/app/core/feedback/feedback.guard.ts | 20 + .../core/feedback/models/feedback.model.ts | 34 + .../feedback/models/feedback.resource-type.ts | 9 + .../models/access-condition.model.ts | 25 + .../submission-accesses.resource-type.ts | 9 + .../submission-item-access-condition.model.ts | 8 + ...sion-upload-file-access-condition.model.ts | 23 +- .../workspaceitem-section-accesses.model.ts | 21 + .../models/workspaceitem-sections.model.ts | 2 + src/app/footer/footer.component.html | 4 + .../feedback-form.component.html | 45 + .../feedback-form.component.scss | 3 + .../feedback-form.component.spec.ts | 97 + .../feedback-form/feedback-form.component.ts | 83 + src/app/info/feedback/feedback.component.html | 3 + src/app/info/feedback/feedback.component.scss | 0 .../info/feedback/feedback.component.spec.ts | 27 + src/app/info/feedback/feedback.component.ts | 12 + .../feedback/themed-feedback.component.ts | 26 + src/app/info/info-routing-paths.ts | 5 + src/app/info/info-routing.module.ts | 14 +- src/app/info/info.module.ts | 15 +- .../item-delete/item-delete.component.spec.ts | 72 +- .../item-delete/item-delete.component.ts | 59 +- ...amic-form-control-container.component.html | 3 +- .../dynamic-form-array.component.html | 4 +- .../dynamic-form-array.component.scss | 10 +- .../methods/oidc/log-in-oidc.component.html | 3 + .../oidc/log-in-oidc.component.spec.ts | 155 + .../methods/oidc/log-in-oidc.component.ts | 110 + .../shared/mocks/mock-native-window-ref.ts | 3 +- .../section-accesses-config.service.mock.ts | 87 + .../mocks/section-accesses.service.mock.ts | 13 + src/app/shared/mocks/submission.mock.ts | 93 +- src/app/shared/shared.module.ts | 3 + src/app/shared/testing/form-event.stub.ts | 100 + src/app/shared/testing/route-service.stub.ts | 5 +- .../form/submission-form.component.ts | 2 +- .../objects/submission-objects.effects.ts | 6 +- .../accesses/section-accesses.component.html | 7 + .../accesses/section-accesses.component.scss | 5 + .../section-accesses.component.spec.ts | 204 + .../accesses/section-accesses.component.ts | 379 + .../accesses/section-accesses.model.ts | 123 + .../accesses/section-accesses.service.ts | 42 + .../container/section-container.component.ts | 6 +- .../form/section-form.component.spec.ts | 16 +- .../sections/form/section-form.component.ts | 2 +- .../sections/models/section.model.ts | 4 +- src/app/submission/sections/sections-type.ts | 3 +- .../edit/section-upload-file-edit.model.ts | 5 +- .../file/section-upload-file.component.ts | 12 +- src/app/submission/selectors.ts | 11 +- src/app/submission/submission.module.ts | 58 +- src/assets/i18n/en.json5 | 84 + src/assets/i18n/gd.json5 | 6076 +++++++++++++++++ src/config/default-app-config.ts | 1 + .../app/info/feedback/feedback.component.html | 0 .../app/info/feedback/feedback.component.scss | 0 .../app/info/feedback/feedback.component.ts | 15 + src/themes/custom/theme.module.ts | 4 +- 78 files changed, 9098 insertions(+), 291 deletions(-) create mode 100644 src/app/core/config/models/config-accesses-conditions-options.model.ts create mode 100644 src/app/core/config/models/config-submission-access.model.ts create mode 100644 src/app/core/config/models/config-submission-accesses.model.ts create mode 100644 src/app/core/config/submission-accesses-config.service.ts create mode 100644 src/app/core/feedback/feedback-data.service.spec.ts create mode 100644 src/app/core/feedback/feedback-data.service.ts create mode 100644 src/app/core/feedback/feedback.guard.ts create mode 100644 src/app/core/feedback/models/feedback.model.ts create mode 100644 src/app/core/feedback/models/feedback.resource-type.ts create mode 100644 src/app/core/submission/models/access-condition.model.ts create mode 100644 src/app/core/submission/models/submission-accesses.resource-type.ts create mode 100644 src/app/core/submission/models/submission-item-access-condition.model.ts create mode 100644 src/app/core/submission/models/workspaceitem-section-accesses.model.ts create mode 100644 src/app/info/feedback/feedback-form/feedback-form.component.html create mode 100644 src/app/info/feedback/feedback-form/feedback-form.component.scss create mode 100644 src/app/info/feedback/feedback-form/feedback-form.component.spec.ts create mode 100644 src/app/info/feedback/feedback-form/feedback-form.component.ts create mode 100644 src/app/info/feedback/feedback.component.html create mode 100644 src/app/info/feedback/feedback.component.scss create mode 100644 src/app/info/feedback/feedback.component.spec.ts create mode 100644 src/app/info/feedback/feedback.component.ts create mode 100644 src/app/info/feedback/themed-feedback.component.ts create mode 100644 src/app/shared/log-in/methods/oidc/log-in-oidc.component.html create mode 100644 src/app/shared/log-in/methods/oidc/log-in-oidc.component.spec.ts create mode 100644 src/app/shared/log-in/methods/oidc/log-in-oidc.component.ts create mode 100644 src/app/shared/mocks/section-accesses-config.service.mock.ts create mode 100644 src/app/shared/mocks/section-accesses.service.mock.ts create mode 100644 src/app/shared/testing/form-event.stub.ts create mode 100644 src/app/submission/sections/accesses/section-accesses.component.html create mode 100644 src/app/submission/sections/accesses/section-accesses.component.scss create mode 100644 src/app/submission/sections/accesses/section-accesses.component.spec.ts create mode 100644 src/app/submission/sections/accesses/section-accesses.component.ts create mode 100644 src/app/submission/sections/accesses/section-accesses.model.ts create mode 100644 src/app/submission/sections/accesses/section-accesses.service.ts create mode 100644 src/assets/i18n/gd.json5 create mode 100644 src/themes/custom/app/info/feedback/feedback.component.html create mode 100644 src/themes/custom/app/info/feedback/feedback.component.scss create mode 100644 src/themes/custom/app/info/feedback/feedback.component.ts diff --git a/config/config.example.yml b/config/config.example.yml index 1e035889a5..ecb2a3cfb9 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -127,6 +127,9 @@ languages: - code: fr label: Français active: true + - code: gd + label: Gàidhlig + active: true - code: lv label: Latviešu active: true diff --git a/src/app/app-routing-paths.ts b/src/app/app-routing-paths.ts index d9c3410931..57767b6f3e 100644 --- a/src/app/app-routing-paths.ts +++ b/src/app/app-routing-paths.ts @@ -32,6 +32,12 @@ export function getBitstreamRequestACopyRoute(item, bitstream): { routerLink: st }; } +export const HOME_PAGE_PATH = 'admin'; + +export function getHomePageRoute() { + return `/${HOME_PAGE_PATH}`; +} + export const ADMIN_MODULE_PATH = 'admin'; export function getAdminModuleRoute() { diff --git a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.html b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.html index cbb587cca4..4d3b948a58 100644 --- a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.html +++ b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.html @@ -12,7 +12,7 @@
    - div > label { + margin-top: 1.75rem; +} + diff --git a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts index 67f8866e6d..44e48182fd 100644 --- a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts +++ b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts @@ -22,6 +22,8 @@ import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } f import { getEntityEditRoute } from '../../item-page/item-page-routing-paths'; import { createPaginatedList } from '../../shared/testing/utils.test'; import { Item } from '../../core/shared/item.model'; +import { MetadataValueFilter } from '../../core/shared/metadata.models'; +import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; const infoNotification: INotification = new Notification('id', NotificationType.Info, 'info'); const warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning'); @@ -31,24 +33,27 @@ let notificationsService: NotificationsService; let formService: DynamicFormService; let bitstreamService: BitstreamDataService; let bitstreamFormatService: BitstreamFormatDataService; +let dsoNameService: DSONameService; let bitstream: Bitstream; let selectedFormat: BitstreamFormat; let allFormats: BitstreamFormat[]; let router: Router; -describe('EditBitstreamPageComponent', () => { - let comp: EditBitstreamPageComponent; - let fixture: ComponentFixture; +let comp: EditBitstreamPageComponent; +let fixture: ComponentFixture; - beforeEach(waitForAsync(() => { +describe('EditBitstreamPageComponent', () => { + + beforeEach(() => { allFormats = [ Object.assign({ id: '1', shortDescription: 'Unknown', description: 'Unknown format', supportLevel: BitstreamFormatSupportLevel.Unknown, + mimetype: 'application/octet-stream', _links: { - self: { href: 'format-selflink-1' } + self: {href: 'format-selflink-1'} } }), Object.assign({ @@ -56,8 +61,9 @@ describe('EditBitstreamPageComponent', () => { shortDescription: 'PNG', description: 'Portable Network Graphics', supportLevel: BitstreamFormatSupportLevel.Known, + mimetype: 'image/png', _links: { - self: { href: 'format-selflink-2' } + self: {href: 'format-selflink-2'} } }), Object.assign({ @@ -65,19 +71,14 @@ describe('EditBitstreamPageComponent', () => { shortDescription: 'GIF', description: 'Graphics Interchange Format', supportLevel: BitstreamFormatSupportLevel.Known, + mimetype: 'image/gif', _links: { - self: { href: 'format-selflink-3' } + self: {href: 'format-selflink-3'} } }) ] as BitstreamFormat[]; selectedFormat = allFormats[1]; - notificationsService = jasmine.createSpyObj('notificationsService', - { - info: infoNotification, - warning: warningNotification, - success: successNotification - } - ); + formService = Object.assign({ createFormGroup: (fModel: DynamicFormControlModel[]) => { const controls = {}; @@ -90,156 +91,418 @@ describe('EditBitstreamPageComponent', () => { return undefined; } }); - bitstream = Object.assign(new Bitstream(), { - metadata: { - 'dc.description': [ - { - value: 'Bitstream description' - } - ], - 'dc.title': [ - { - value: 'Bitstream title' - } - ] - }, - format: createSuccessfulRemoteDataObject$(selectedFormat), - _links: { - self: 'bitstream-selflink' - }, - bundle: createSuccessfulRemoteDataObject$({ - item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), { - uuid: 'some-uuid' - })) - }) - }); - bitstreamService = jasmine.createSpyObj('bitstreamService', { - findById: createSuccessfulRemoteDataObject$(bitstream), - update: createSuccessfulRemoteDataObject$(bitstream), - updateFormat: createSuccessfulRemoteDataObject$(bitstream), - commitUpdates: {}, - patch: {} - }); + bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', { findAll: createSuccessfulRemoteDataObject$(createPaginatedList(allFormats)) }); - TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), RouterTestingModule], - declarations: [EditBitstreamPageComponent, FileSizePipe, VarDirective], - providers: [ - { provide: NotificationsService, useValue: notificationsService }, - { provide: DynamicFormService, useValue: formService }, - { provide: ActivatedRoute, useValue: { data: observableOf({ bitstream: createSuccessfulRemoteDataObject(bitstream) }), snapshot: { queryParams: {} } } }, - { provide: BitstreamDataService, useValue: bitstreamService }, - { provide: BitstreamFormatDataService, useValue: bitstreamFormatService }, - ChangeDetectorRef - ], - schemas: [NO_ERRORS_SCHEMA] - }).compileComponents(); - - })); - - beforeEach(() => { - fixture = TestBed.createComponent(EditBitstreamPageComponent); - comp = fixture.componentInstance; - fixture.detectChanges(); - router = TestBed.inject(Router); - spyOn(router, 'navigate'); + notificationsService = jasmine.createSpyObj('notificationsService', + { + info: infoNotification, + warning: warningNotification, + success: successNotification + } + ); }); - describe('on startup', () => { - let rawForm; + describe('EditBitstreamPageComponent no IIIF fields', () => { + + beforeEach(waitForAsync(() => { + + const bundleName = 'ORIGINAL'; + + bitstream = Object.assign(new Bitstream(), { + metadata: { + 'dc.description': [ + { + value: 'Bitstream description' + } + ], + 'dc.title': [ + { + value: 'Bitstream title' + } + ] + }, + format: createSuccessfulRemoteDataObject$(selectedFormat), + _links: { + self: 'bitstream-selflink' + }, + bundle: createSuccessfulRemoteDataObject$({ + item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), { + uuid: 'some-uuid', + firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string { + return undefined; + }, + })) + }) + }); + bitstreamService = jasmine.createSpyObj('bitstreamService', { + findById: createSuccessfulRemoteDataObject$(bitstream), + update: createSuccessfulRemoteDataObject$(bitstream), + updateFormat: createSuccessfulRemoteDataObject$(bitstream), + commitUpdates: {}, + patch: {} + }); + bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', { + findAll: createSuccessfulRemoteDataObject$(createPaginatedList(allFormats)) + }); + dsoNameService = jasmine.createSpyObj('dsoNameService', { + getName: bundleName + }); + + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), RouterTestingModule], + declarations: [EditBitstreamPageComponent, FileSizePipe, VarDirective], + providers: [ + {provide: NotificationsService, useValue: notificationsService}, + {provide: DynamicFormService, useValue: formService}, + {provide: ActivatedRoute, + useValue: { + data: observableOf({bitstream: createSuccessfulRemoteDataObject(bitstream)}), + snapshot: {queryParams: {}} + } + }, + {provide: BitstreamDataService, useValue: bitstreamService}, + {provide: DSONameService, useValue: dsoNameService}, + {provide: BitstreamFormatDataService, useValue: bitstreamFormatService}, + ChangeDetectorRef + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + + })); beforeEach(() => { - rawForm = comp.formGroup.getRawValue(); + fixture = TestBed.createComponent(EditBitstreamPageComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + router = TestBed.inject(Router); + spyOn(router, 'navigate'); }); - it('should fill in the bitstream\'s title', () => { - expect(rawForm.fileNamePrimaryContainer.fileName).toEqual(bitstream.name); - }); + describe('on startup', () => { + let rawForm; - it('should fill in the bitstream\'s description', () => { - expect(rawForm.descriptionContainer.description).toEqual(bitstream.firstMetadataValue('dc.description')); - }); - - it('should select the correct format', () => { - expect(rawForm.formatContainer.selectedFormat).toEqual(selectedFormat.id); - }); - - it('should put the \"New Format\" input on invisible', () => { - expect(comp.formLayout.newFormat.grid.host).toContain('invisible'); - }); - }); - - describe('when an unknown format is selected', () => { - beforeEach(() => { - comp.updateNewFormatLayout(allFormats[0].id); - }); - - it('should remove the invisible class from the \"New Format\" input', () => { - expect(comp.formLayout.newFormat.grid.host).not.toContain('invisible'); - }); - }); - - describe('onSubmit', () => { - describe('when selected format hasn\'t changed', () => { beforeEach(() => { - comp.onSubmit(); + rawForm = comp.formGroup.getRawValue(); }); - it('should call update', () => { - expect(bitstreamService.update).toHaveBeenCalled(); + it('should fill in the bitstream\'s title', () => { + expect(rawForm.fileNamePrimaryContainer.fileName).toEqual(bitstream.name); }); - it('should commit the updates', () => { - expect(bitstreamService.commitUpdates).toHaveBeenCalled(); + it('should fill in the bitstream\'s description', () => { + expect(rawForm.descriptionContainer.description).toEqual(bitstream.firstMetadataValue('dc.description')); + }); + + it('should select the correct format', () => { + expect(rawForm.formatContainer.selectedFormat).toEqual(selectedFormat.id); + }); + + it('should put the \"New Format\" input on invisible', () => { + expect(comp.formLayout.newFormat.grid.host).toContain('invisible'); }); }); - describe('when selected format has changed', () => { + describe('when an unknown format is selected', () => { beforeEach(() => { - comp.formGroup.patchValue({ - formatContainer: { - selectedFormat: allFormats[2].id - } + comp.updateNewFormatLayout(allFormats[0].id); + }); + + it('should remove the invisible class from the \"New Format\" input', () => { + expect(comp.formLayout.newFormat.grid.host).not.toContain('invisible'); + }); + }); + + describe('onSubmit', () => { + describe('when selected format hasn\'t changed', () => { + beforeEach(() => { + comp.onSubmit(); }); + + it('should call update', () => { + expect(bitstreamService.update).toHaveBeenCalled(); + }); + + it('should commit the updates', () => { + expect(bitstreamService.commitUpdates).toHaveBeenCalled(); + }); + }); + + describe('when selected format has changed', () => { + beforeEach(() => { + comp.formGroup.patchValue({ + formatContainer: { + selectedFormat: allFormats[2].id + } + }); + fixture.detectChanges(); + comp.onSubmit(); + }); + + it('should call update', () => { + expect(bitstreamService.update).toHaveBeenCalled(); + }); + + it('should call updateFormat', () => { + expect(bitstreamService.updateFormat).toHaveBeenCalled(); + }); + + it('should commit the updates', () => { + expect(bitstreamService.commitUpdates).toHaveBeenCalled(); + }); + }); + }); + describe('when the cancel button is clicked', () => { + it('should call navigateToItemEditBitstreams method', () => { + spyOn(comp, 'navigateToItemEditBitstreams'); + comp.onCancel(); + expect(comp.navigateToItemEditBitstreams).toHaveBeenCalled(); + }); + }); + describe('when navigateToItemEditBitstreams is called, and the component has an itemId', () => { + it('should redirect to the item edit page on the bitstreams tab with the itemId from the component', () => { + comp.itemId = 'some-uuid1'; + comp.navigateToItemEditBitstreams(); + expect(router.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid1'), 'bitstreams']); + }); + }); + describe('when navigateToItemEditBitstreams is called, and the component does not have an itemId', () => { + it('should redirect to the item edit page on the bitstreams tab with the itemId from the bundle links ', () => { + comp.itemId = undefined; + comp.navigateToItemEditBitstreams(); + expect(router.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid'), 'bitstreams']); + }); + }); + }); + + describe('EditBitstreamPageComponent with IIIF fields', () => { + + const bundleName = 'ORIGINAL'; + + beforeEach(waitForAsync(() => { + + bitstream = Object.assign(new Bitstream(), { + metadata: { + 'dc.description': [ + { + value: 'Bitstream description' + } + ], + 'dc.title': [ + { + value: 'Bitstream title' + } + ], + 'iiif.label': [ + { + value: 'chapter one' + } + ], + 'iiif.toc': [ + { + value: 'chapter one' + } + ], + 'iiif.image.width': [ + { + value: '2400' + } + ], + 'iiif.image.height': [ + { + value: '2800' + } + ], + }, + format: createSuccessfulRemoteDataObject$(allFormats[1]), + _links: { + self: 'bitstream-selflink' + }, + bundle: createSuccessfulRemoteDataObject$({ + item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), { + uuid: 'some-uuid', + firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string { + return 'True'; + } + })) + }) + }); + bitstreamService = jasmine.createSpyObj('bitstreamService', { + findById: createSuccessfulRemoteDataObject$(bitstream), + update: createSuccessfulRemoteDataObject$(bitstream), + updateFormat: createSuccessfulRemoteDataObject$(bitstream), + commitUpdates: {}, + patch: {} + }); + + dsoNameService = jasmine.createSpyObj('dsoNameService', { + getName: bundleName + }); + + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), RouterTestingModule], + declarations: [EditBitstreamPageComponent, FileSizePipe, VarDirective], + providers: [ + {provide: NotificationsService, useValue: notificationsService}, + {provide: DynamicFormService, useValue: formService}, + { + provide: ActivatedRoute, + useValue: { + data: observableOf({bitstream: createSuccessfulRemoteDataObject(bitstream)}), + snapshot: {queryParams: {}} + } + }, + {provide: BitstreamDataService, useValue: bitstreamService}, + {provide: DSONameService, useValue: dsoNameService}, + {provide: BitstreamFormatDataService, useValue: bitstreamFormatService}, + ChangeDetectorRef + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EditBitstreamPageComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + router = TestBed.inject(Router); + spyOn(router, 'navigate'); + }); + + + describe('on startup', () => { + let rawForm; + + beforeEach(() => { + rawForm = comp.formGroup.getRawValue(); + }); + it('should set isIIIF to true', () => { + expect(comp.isIIIF).toBeTrue(); + }); + it('should fill in the iiif label', () => { + expect(rawForm.iiifLabelContainer.iiifLabel).toEqual('chapter one'); + }); + it('should fill in the iiif toc', () => { + expect(rawForm.iiifTocContainer.iiifToc).toEqual('chapter one'); + }); + it('should fill in the iiif width', () => { + expect(rawForm.iiifWidthContainer.iiifWidth).toEqual('2400'); + }); + it('should fill in the iiif height', () => { + expect(rawForm.iiifHeightContainer.iiifHeight).toEqual('2800'); + }); + }); + }); + + describe('ignore OTHERCONTENT bundle', () => { + + const bundleName = 'OTHERCONTENT'; + + beforeEach(waitForAsync(() => { + + bitstream = Object.assign(new Bitstream(), { + metadata: { + 'dc.description': [ + { + value: 'Bitstream description' + } + ], + 'dc.title': [ + { + value: 'Bitstream title' + } + ], + 'iiif.label': [ + { + value: 'chapter one' + } + ], + 'iiif.toc': [ + { + value: 'chapter one' + } + ], + 'iiif.image.width': [ + { + value: '2400' + } + ], + 'iiif.image.height': [ + { + value: '2800' + } + ], + }, + format: createSuccessfulRemoteDataObject$(allFormats[2]), + _links: { + self: 'bitstream-selflink' + }, + bundle: createSuccessfulRemoteDataObject$({ + item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), { + uuid: 'some-uuid', + firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string { + return 'True'; + } + })) + }) + }); + bitstreamService = jasmine.createSpyObj('bitstreamService', { + findById: createSuccessfulRemoteDataObject$(bitstream), + update: createSuccessfulRemoteDataObject$(bitstream), + updateFormat: createSuccessfulRemoteDataObject$(bitstream), + commitUpdates: {}, + patch: {} + }); + + dsoNameService = jasmine.createSpyObj('dsoNameService', { + getName: bundleName + }); + + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), RouterTestingModule], + declarations: [EditBitstreamPageComponent, FileSizePipe, VarDirective], + providers: [ + {provide: NotificationsService, useValue: notificationsService}, + {provide: DynamicFormService, useValue: formService}, + {provide: ActivatedRoute, + useValue: { + data: observableOf({bitstream: createSuccessfulRemoteDataObject(bitstream)}), + snapshot: {queryParams: {}} + } + }, + {provide: BitstreamDataService, useValue: bitstreamService}, + {provide: DSONameService, useValue: dsoNameService}, + {provide: BitstreamFormatDataService, useValue: bitstreamFormatService}, + ChangeDetectorRef + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EditBitstreamPageComponent); + comp = fixture.componentInstance; fixture.detectChanges(); - comp.onSubmit(); + router = TestBed.inject(Router); + spyOn(router, 'navigate'); }); - it('should call update', () => { - expect(bitstreamService.update).toHaveBeenCalled(); - }); + describe('EditBitstreamPageComponent with IIIF fields', () => { + let rawForm; - it('should call updateFormat', () => { - expect(bitstreamService.updateFormat).toHaveBeenCalled(); - }); + beforeEach(() => { + rawForm = comp.formGroup.getRawValue(); + }); - it('should commit the updates', () => { - expect(bitstreamService.commitUpdates).toHaveBeenCalled(); + it('should NOT set isIIIF to true', () => { + expect(comp.isIIIF).toBeFalse(); + }); + it('should put the \"IIIF Label\" input not to be shown', () => { + expect(rawForm.iiifLabelContainer).toBeFalsy(); + }); }); - }); - }); - describe('when the cancel button is clicked', () => { - it('should call navigateToItemEditBitstreams method', () => { - spyOn(comp, 'navigateToItemEditBitstreams'); - comp.onCancel(); - expect(comp.navigateToItemEditBitstreams).toHaveBeenCalled(); - }); - }); - describe('when navigateToItemEditBitstreams is called, and the component has an itemId', () => { - it('should redirect to the item edit page on the bitstreams tab with the itemId from the component', () => { - comp.itemId = 'some-uuid1'; - comp.navigateToItemEditBitstreams(); - expect(router.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid1'), 'bitstreams']); - }); - }); - describe('when navigateToItemEditBitstreams is called, and the component does not have an itemId', () => { - it('should redirect to the item edit page on the bitstreams tab with the itemId from the bundle links ', () => { - comp.itemId = undefined; - comp.navigateToItemEditBitstreams(); - expect(router.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid'), 'bitstreams']); - }); }); + }); diff --git a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts index f6ece7f4fa..9a59df4b95 100644 --- a/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts +++ b/src/app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts @@ -1,16 +1,27 @@ -import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnDestroy, + OnInit +} from '@angular/core'; import { Bitstream } from '../../core/shared/bitstream.model'; import { ActivatedRoute, Router } from '@angular/router'; import { map, mergeMap, switchMap } from 'rxjs/operators'; -import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; +import { + combineLatest, + combineLatest as observableCombineLatest, + Observable, + of as observableOf, + Subscription +} from 'rxjs'; import { DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, DynamicFormService, DynamicInputModel, - DynamicSelectModel, - DynamicTextAreaModel + DynamicSelectModel } from '@ng-dynamic-forms/core'; import { FormGroup } from '@angular/forms'; import { TranslateService } from '@ngx-translate/core'; @@ -28,13 +39,19 @@ import { NotificationsService } from '../../shared/notifications/notifications.s import { BitstreamFormatDataService } from '../../core/data/bitstream-format-data.service'; import { BitstreamFormat } from '../../core/shared/bitstream-format.model'; import { BitstreamFormatSupportLevel } from '../../core/shared/bitstream-format-support-level'; -import { hasValue, isNotEmpty } from '../../shared/empty.util'; +import { hasValue, isNotEmpty, isEmpty } from '../../shared/empty.util'; import { Metadata } from '../../core/shared/metadata.utils'; import { Location } from '@angular/common'; import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; import { getEntityEditRoute, getItemEditRoute } from '../../item-page/item-page-routing-paths'; import { Bundle } from '../../core/shared/bundle.model'; +import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { Item } from '../../core/shared/item.model'; +import { + DsDynamicInputModel +} from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model'; +import { DsDynamicTextAreaModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-textarea.model'; @Component({ selector: 'ds-edit-bitstream-page', @@ -94,6 +111,26 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { */ NOTIFICATIONS_PREFIX = 'bitstream.edit.notifications.'; + /** + * IIIF image width metadata key + */ + IMAGE_WIDTH_METADATA = 'iiif.image.width'; + + /** + * IIIF image height metadata key + */ + IMAGE_HEIGHT_METADATA = 'iiif.image.height'; + + /** + * IIIF table of contents metadata key + */ + IIIF_TOC_METADATA = 'iiif.toc'; + + /** + * IIIF label metadata key + */ + IIIF_LABEL_METADATA = 'iiif.label'; + /** * Options for fetching all bitstream formats */ @@ -102,7 +139,8 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { /** * The Dynamic Input Model for the file's name */ - fileNameModel = new DynamicInputModel({ + fileNameModel = new DsDynamicInputModel({ + hasSelectableMetadata: false, metadataFields: [], repeatable: false, submissionId: '', id: 'fileName', name: 'fileName', required: true, @@ -118,14 +156,16 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { * The Dynamic Switch Model for the file's name */ primaryBitstreamModel = new DynamicCustomSwitchModel({ - id: 'primaryBitstream', - name: 'primaryBitstream' - }); + id: 'primaryBitstream', + name: 'primaryBitstream' + } + ); /** * The Dynamic TextArea Model for the file's description */ - descriptionModel = new DynamicTextAreaModel({ + descriptionModel = new DsDynamicTextAreaModel({ + hasSelectableMetadata: false, metadataFields: [], repeatable: false, submissionId: '', id: 'description', name: 'description', rows: 10 @@ -147,10 +187,87 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { name: 'newFormat' }); + /** + * The Dynamic Input Model for the iiif label + */ + iiifLabelModel = new DsDynamicInputModel({ + hasSelectableMetadata: false, metadataFields: [], repeatable: false, submissionId: '', + id: 'iiifLabel', + name: 'iiifLabel' + }, + { + grid: { + host: 'col col-lg-6 d-inline-block' + } + }); + iiifLabelContainer = new DynamicFormGroupModel({ + id: 'iiifLabelContainer', + group: [this.iiifLabelModel] + },{ + grid: { + host: 'form-row' + } + }); + + iiifTocModel = new DsDynamicInputModel({ + hasSelectableMetadata: false, metadataFields: [], repeatable: false, submissionId: '', + id: 'iiifToc', + name: 'iiifToc', + },{ + grid: { + host: 'col col-lg-6 d-inline-block' + } + }); + iiifTocContainer = new DynamicFormGroupModel({ + id: 'iiifTocContainer', + group: [this.iiifTocModel] + },{ + grid: { + host: 'form-row' + } + }); + + iiifWidthModel = new DsDynamicInputModel({ + hasSelectableMetadata: false, metadataFields: [], repeatable: false, submissionId: '', + id: 'iiifWidth', + name: 'iiifWidth', + },{ + grid: { + host: 'col col-lg-6 d-inline-block' + } + }); + iiifWidthContainer = new DynamicFormGroupModel({ + id: 'iiifWidthContainer', + group: [this.iiifWidthModel] + },{ + grid: { + host: 'form-row' + } + }); + + iiifHeightModel = new DsDynamicInputModel({ + hasSelectableMetadata: false, metadataFields: [], repeatable: false, submissionId: '', + id: 'iiifHeight', + name: 'iiifHeight' + },{ + grid: { + host: 'col col-lg-6 d-inline-block' + } + }); + iiifHeightContainer = new DynamicFormGroupModel({ + id: 'iiifHeightContainer', + group: [this.iiifHeightModel] + },{ + grid: { + host: 'form-row' + } + }); + /** * All input models in a simple array for easier iterations */ - inputModels = [this.fileNameModel, this.primaryBitstreamModel, this.descriptionModel, this.selectedFormatModel, this.newFormatModel]; + inputModels = [this.fileNameModel, this.primaryBitstreamModel, this.descriptionModel, this.selectedFormatModel, + this.newFormatModel]; /** * The dynamic form fields used for editing the information of a bitstream @@ -163,7 +280,11 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { this.fileNameModel, this.primaryBitstreamModel ] - }), + },{ + grid: { + host: 'form-row' + } + }), new DynamicFormGroupModel({ id: 'descriptionContainer', group: [ @@ -254,18 +375,27 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { */ entityType: string; + /** + * Set to true when the parent item supports IIIF. + */ + isIIIF = false; + + /** * Array to track all subscriptions and unsubscribe them onDestroy * @type {Array} */ protected subs: Subscription[] = []; + constructor(private route: ActivatedRoute, private router: Router, + private changeDetectorRef: ChangeDetectorRef, private location: Location, private formService: DynamicFormService, private translate: TranslateService, private bitstreamService: BitstreamDataService, + private dsoNameService: DSONameService, private notificationsService: NotificationsService, private bitstreamFormatService: BitstreamFormatDataService) { } @@ -277,7 +407,6 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { * - Translate the form labels and hints */ ngOnInit(): void { - this.formGroup = this.formService.createFormGroup(this.formModel); this.itemId = this.route.snapshot.queryParams.itemId; this.entityType = this.route.snapshot.queryParams.entityType; @@ -301,13 +430,10 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { ).subscribe(([bitstream, allFormats]) => { this.bitstream = bitstream as Bitstream; this.formats = allFormats.page; - this.updateFormatModel(); - this.updateForm(this.bitstream); + this.setIiifStatus(this.bitstream); }) ); - this.updateFieldTranslations(); - this.subs.push( this.translate.onLangChange .subscribe(() => { @@ -316,6 +442,16 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { ); } + /** + * Initializes the form. + */ + setForm() { + this.formGroup = this.formService.createFormGroup(this.formModel); + this.updateFormatModel(); + this.updateForm(this.bitstream); + this.updateFieldTranslations(); + } + /** * Update the current form values with bitstream properties * @param bitstream @@ -333,6 +469,22 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { newFormat: hasValue(bitstream.firstMetadata('dc.format')) ? bitstream.firstMetadata('dc.format').value : undefined } }); + if (this.isIIIF) { + this.formGroup.patchValue({ + iiifLabelContainer: { + iiifLabel: bitstream.firstMetadataValue(this.IIIF_LABEL_METADATA) + }, + iiifTocContainer: { + iiifToc: bitstream.firstMetadataValue(this.IIIF_TOC_METADATA) + }, + iiifWidthContainer: { + iiifWidth: bitstream.firstMetadataValue(this.IMAGE_WIDTH_METADATA) + }, + iiifHeightContainer: { + iiifHeight: bitstream.firstMetadataValue(this.IMAGE_HEIGHT_METADATA) + } + }); + } this.bitstream.format.pipe( getAllSucceededRemoteDataPayload() ).subscribe((format: BitstreamFormat) => { @@ -467,6 +619,32 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { const primary = rawForm.fileNamePrimaryContainer.primaryBitstream; Metadata.setFirstValue(newMetadata, 'dc.title', rawForm.fileNamePrimaryContainer.fileName); Metadata.setFirstValue(newMetadata, 'dc.description', rawForm.descriptionContainer.description); + if (this.isIIIF) { + // It's helpful to remove these metadata elements entirely when the form value is empty. + // This avoids potential issues on the REST side and makes it possible to do things like + // remove an existing "table of contents" entry. + if (isEmpty(rawForm.iiifLabelContainer.iiifLabel)) { + + delete newMetadata[this.IIIF_LABEL_METADATA]; + } else { + Metadata.setFirstValue(newMetadata, this.IIIF_LABEL_METADATA, rawForm.iiifLabelContainer.iiifLabel); + } + if (isEmpty(rawForm.iiifTocContainer.iiifToc)) { + delete newMetadata[this.IIIF_TOC_METADATA]; + } else { + Metadata.setFirstValue(newMetadata, this.IIIF_TOC_METADATA, rawForm.iiifTocContainer.iiifToc); + } + if (isEmpty(rawForm.iiifWidthContainer.iiifWidth)) { + delete newMetadata[this.IMAGE_WIDTH_METADATA]; + } else { + Metadata.setFirstValue(newMetadata, this.IMAGE_WIDTH_METADATA, rawForm.iiifWidthContainer.iiifWidth); + } + if (isEmpty(rawForm.iiifHeightContainer.iiifHeight)) { + delete newMetadata[this.IMAGE_HEIGHT_METADATA]; + } else { + Metadata.setFirstValue(newMetadata, this.IMAGE_HEIGHT_METADATA, rawForm.iiifHeightContainer.iiifHeight); + } + } if (isNotEmpty(rawForm.formatContainer.newFormat)) { Metadata.setFirstValue(newMetadata, 'dc.format', rawForm.formatContainer.newFormat); } @@ -497,6 +675,58 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { } } + /** + * Verifies that the parent item is iiif-enabled. Checks bitstream mimetype to be + * sure it's an image, excluding bitstreams in the THUMBNAIL or OTHERCONTENT bundles. + * @param bitstream + */ + setIiifStatus(bitstream: Bitstream) { + + const regexExcludeBundles = /OTHERCONTENT|THUMBNAIL|LICENSE/; + const regexIIIFItem = /true|yes/i; + + const isImage$ = this.bitstream.format.pipe( + getFirstSucceededRemoteData(), + map((format: RemoteData) => format.payload.mimetype.includes('image/'))); + + const isIIIFBundle$ = this.bitstream.bundle.pipe( + getFirstSucceededRemoteData(), + map((bundle: RemoteData) => + this.dsoNameService.getName(bundle.payload).match(regexExcludeBundles) == null)); + + const isEnabled$ = this.bitstream.bundle.pipe( + getFirstSucceededRemoteData(), + map((bundle: RemoteData) => bundle.payload.item.pipe( + getFirstSucceededRemoteData(), + map((item: RemoteData) => + (item.payload.firstMetadataValue('dspace.iiif.enabled') && + item.payload.firstMetadataValue('dspace.iiif.enabled').match(regexIIIFItem) !== null) + )))); + + const iiifSub = combineLatest( + isImage$, + isIIIFBundle$, + isEnabled$ + ).subscribe(([isImage, isIIIFBundle, isEnabled]) => { + if (isImage && isIIIFBundle && isEnabled) { + this.isIIIF = true; + this.inputModels.push(this.iiifLabelModel); + this.formModel.push(this.iiifLabelContainer); + this.inputModels.push(this.iiifTocModel); + this.formModel.push(this.iiifTocContainer); + this.inputModels.push(this.iiifWidthModel); + this.formModel.push(this.iiifWidthContainer); + this.inputModels.push(this.iiifHeightModel); + this.formModel.push(this.iiifHeightContainer); + } + this.setForm(); + this.changeDetectorRef.detectChanges(); + }); + + this.subs.push(iiifSub); + + } + /** * Unsubscribe from open subscriptions */ diff --git a/src/app/core/auth/models/auth.method-type.ts b/src/app/core/auth/models/auth.method-type.ts index f053515065..9d999c4c3f 100644 --- a/src/app/core/auth/models/auth.method-type.ts +++ b/src/app/core/auth/models/auth.method-type.ts @@ -3,5 +3,6 @@ export enum AuthMethodType { Shibboleth = 'shibboleth', Ldap = 'ldap', Ip = 'ip', - X509 = 'x509' + X509 = 'x509', + Oidc = 'oidc' } diff --git a/src/app/core/auth/models/auth.method.ts b/src/app/core/auth/models/auth.method.ts index 617154080b..5a362e8606 100644 --- a/src/app/core/auth/models/auth.method.ts +++ b/src/app/core/auth/models/auth.method.ts @@ -29,6 +29,11 @@ export class AuthMethod { this.authMethodType = AuthMethodType.Password; break; } + case 'oidc': { + this.authMethodType = AuthMethodType.Oidc; + this.location = location; + break; + } default: { break; diff --git a/src/app/core/config/models/config-accesses-conditions-options.model.ts b/src/app/core/config/models/config-accesses-conditions-options.model.ts new file mode 100644 index 0000000000..244b501908 --- /dev/null +++ b/src/app/core/config/models/config-accesses-conditions-options.model.ts @@ -0,0 +1,45 @@ +/** + * Model class for an Item Access Condition + */ +export class AccessesConditionOption { + + /** + * The name for this Access Condition + */ + name: string; + + /** + * The groupName for this Access Condition + */ + groupName: string; + + /** + * A boolean representing if this Access Condition has a start date + */ + hasStartDate: boolean; + + /** + * A boolean representing if this Access Condition has an end date + */ + hasEndDate: boolean; + + /** + * Maximum value of the start date + */ + endDateLimit?: string; + + /** + * Maximum value of the end date + */ + startDateLimit?: string; + + /** + * Maximum value of the start date + */ + maxStartDate?: string; + + /** + * Maximum value of the end date + */ + maxEndDate?: string; +} diff --git a/src/app/core/config/models/config-submission-access.model.ts b/src/app/core/config/models/config-submission-access.model.ts new file mode 100644 index 0000000000..7db96acf2b --- /dev/null +++ b/src/app/core/config/models/config-submission-access.model.ts @@ -0,0 +1,42 @@ +import { autoserialize, deserialize, inheritSerialization } from 'cerialize'; +import { typedObject } from '../../cache/builders/build-decorators'; +import { ConfigObject } from './config.model'; +import { AccessesConditionOption } from './config-accesses-conditions-options.model'; +import { SUBMISSION_ACCESSES_TYPE } from './config-type'; +import { HALLink } from '../../shared/hal-link.model'; + +/** + * Class for the configuration describing the item accesses condition + */ +@typedObject +@inheritSerialization(ConfigObject) +export class SubmissionAccessModel extends ConfigObject { + static type = SUBMISSION_ACCESSES_TYPE; + + /** + * A list of available item access conditions + */ + @autoserialize + accessConditionOptions: AccessesConditionOption[]; + + /** + * Boolean that indicates whether the current item must be findable via search or browse. + */ + @autoserialize + discoverable: boolean; + + /** + * Boolean that indicates whether or not the user can change the discoverable flag. + */ + @autoserialize + canChangeDiscoverable: boolean; + + /** + * The links to all related resources returned by the rest api. + */ + @deserialize + _links: { + self: HALLink + }; + +} diff --git a/src/app/core/config/models/config-submission-accesses.model.ts b/src/app/core/config/models/config-submission-accesses.model.ts new file mode 100644 index 0000000000..3f8004928d --- /dev/null +++ b/src/app/core/config/models/config-submission-accesses.model.ts @@ -0,0 +1,10 @@ +import { inheritSerialization } from 'cerialize'; +import { typedObject } from '../../cache/builders/build-decorators'; +import { SUBMISSION_ACCESSES_TYPE } from './config-type'; +import { SubmissionAccessModel } from './config-submission-access.model'; + +@typedObject +@inheritSerialization(SubmissionAccessModel) +export class SubmissionAccessesModel extends SubmissionAccessModel { + static type = SUBMISSION_ACCESSES_TYPE; +} diff --git a/src/app/core/config/models/config-type.ts b/src/app/core/config/models/config-type.ts index 858ff19c91..19864536f0 100644 --- a/src/app/core/config/models/config-type.ts +++ b/src/app/core/config/models/config-type.ts @@ -15,3 +15,5 @@ export const SUBMISSION_SECTION_TYPE = new ResourceType('submissionsection'); export const SUBMISSION_UPLOADS_TYPE = new ResourceType('submissionuploads'); export const SUBMISSION_UPLOAD_TYPE = new ResourceType('submissionupload'); + +export const SUBMISSION_ACCESSES_TYPE = new ResourceType('submissionaccessoption'); diff --git a/src/app/core/config/submission-accesses-config.service.ts b/src/app/core/config/submission-accesses-config.service.ts new file mode 100644 index 0000000000..de9afc66ea --- /dev/null +++ b/src/app/core/config/submission-accesses-config.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@angular/core'; +import { ConfigService } from './config.service'; +import { RequestService } from '../data/request.service'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { dataService } from '../cache/builders/build-decorators'; +import { SUBMISSION_ACCESSES_TYPE } from './models/config-type'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { Store } from '@ngrx/store'; +import { CoreState } from '../core.reducers'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { HttpClient } from '@angular/common/http'; +import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service'; +import { ConfigObject } from './models/config.model'; +import { SubmissionAccessesModel } from './models/config-submission-accesses.model'; +import { RemoteData } from '../data/remote-data'; +import { Observable } from 'rxjs'; +import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; + +/** + * Provides methods to retrieve, from REST server, bitstream access conditions configurations applicable during the submission process. + */ +@Injectable() +@dataService(SUBMISSION_ACCESSES_TYPE) +export class SubmissionAccessesConfigService extends ConfigService { + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected store: Store, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparator: DefaultChangeAnalyzer + ) { + super(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator, 'submissionaccessoptions'); + } + + findByHref(href: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow): Observable> { + return super.findByHref(href, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow as FollowLinkConfig[]) as Observable>; + } +} diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 2c9a24cb80..8d8a614a89 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -2,11 +2,7 @@ import { CommonModule } from '@angular/common'; import { HttpClient } from '@angular/common/http'; import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core'; -import { - DynamicFormLayoutService, - DynamicFormService, - DynamicFormValidationService -} from '@ng-dynamic-forms/core'; +import { DynamicFormLayoutService, DynamicFormService, DynamicFormValidationService } from '@ng-dynamic-forms/core'; import { EffectsModule } from '@ngrx/effects'; import { Action, StoreConfig, StoreModule } from '@ngrx/store'; @@ -77,6 +73,7 @@ import { MetadataSchema } from './metadata/metadata-schema.model'; import { MetadataService } from './metadata/metadata.service'; import { RegistryService } from './registry/registry.service'; import { RoleService } from './roles/role.service'; +import { FeedbackDataService } from './feedback/feedback-data.service'; import { ApiService } from './services/api.service'; import { ServerResponseService } from './services/server-response.service'; @@ -164,6 +161,7 @@ import { Root } from './data/root.model'; import { SearchConfig } from './shared/search/search-filters/search-config.model'; import { SequenceService } from './shared/sequence.service'; import { GroupDataService } from './eperson/group-data.service'; +import { SubmissionAccessesModel } from './config/models/config-submission-accesses.model'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -286,7 +284,8 @@ const PROVIDERS = [ VocabularyService, VocabularyTreeviewService, SequenceService, - GroupDataService + GroupDataService, + FeedbackDataService, ]; /** @@ -345,7 +344,8 @@ export const models = Registration, UsageReport, Root, - SearchConfig + SearchConfig, + SubmissionAccessesModel ]; @NgModule({ diff --git a/src/app/core/data/feature-authorization/feature-id.ts b/src/app/core/data/feature-authorization/feature-id.ts index 01a482dc79..029c75d9cb 100644 --- a/src/app/core/data/feature-authorization/feature-id.ts +++ b/src/app/core/data/feature-authorization/feature-id.ts @@ -27,4 +27,5 @@ export enum FeatureID { CanDeleteVersion = 'canDeleteVersion', CanCreateVersion = 'canCreateVersion', CanViewUsageStatistics = 'canViewUsageStatistics', + CanSendFeedback = 'canSendFeedback', } diff --git a/src/app/core/feedback/feedback-data.service.spec.ts b/src/app/core/feedback/feedback-data.service.spec.ts new file mode 100644 index 0000000000..4bb5e642c2 --- /dev/null +++ b/src/app/core/feedback/feedback-data.service.spec.ts @@ -0,0 +1,88 @@ +import { FeedbackDataService } from './feedback-data.service'; +import { HALLink } from '../shared/hal-link.model'; +import { Item } from '../shared/item.model'; +import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { getMockRequestService } from '../../shared/mocks/request.service.mock'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { HttpClient } from '@angular/common/http'; +import { Store } from '@ngrx/store'; +import { CoreState } from '../core.reducers'; +import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service'; +import { Feedback } from './models/feedback.model'; + +describe('FeedbackDataService', () => { + let service: FeedbackDataService; + let requestService; + let halService; + let rdbService; + let notificationsService; + let http; + let comparator; + let objectCache; + let store; + let item; + let bundleLink; + let bundleHALLink; + + const feedbackPayload = Object.assign(new Feedback(), { + email: 'test@email.com', + message: 'message', + page: '/home' + }); + + + function initTestService(): FeedbackDataService { + bundleLink = '/items/0fdc0cd7-ff8c-433d-b33c-9b56108abc07/bundles'; + bundleHALLink = new HALLink(); + bundleHALLink.href = bundleLink; + item = new Item(); + item._links = { + bundles: bundleHALLink + }; + requestService = getMockRequestService(); + halService = new HALEndpointServiceStub('url') as any; + rdbService = {} as RemoteDataBuildService; + notificationsService = {} as NotificationsService; + http = {} as HttpClient; + comparator = new DSOChangeAnalyzer() as any; + objectCache = { + + addPatch: () => { + /* empty */ + }, + getObjectBySelfLink: () => { + /* empty */ + } + } as any; + store = {} as Store; + return new FeedbackDataService( + requestService, + rdbService, + store, + objectCache, + halService, + notificationsService, + http, + comparator, + ); + } + + + beforeEach(() => { + service = initTestService(); + }); + + + describe('getFeedback', () => { + beforeEach(() => { + spyOn(service, 'getFeedback'); + service.getFeedback('3'); + }); + + it('should call getFeedback with the feedback link', () => { + expect(service.getFeedback).toHaveBeenCalledWith('3'); + }); + }); + +}); diff --git a/src/app/core/feedback/feedback-data.service.ts b/src/app/core/feedback/feedback-data.service.ts new file mode 100644 index 0000000000..8f6d4e3ecf --- /dev/null +++ b/src/app/core/feedback/feedback-data.service.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { DataService } from '../data/data.service'; +import { Feedback } from './models/feedback.model'; +import { FEEDBACK } from './models/feedback.resource-type'; +import { dataService } from '../cache/builders/build-decorators'; +import { RequestService } from '../data/request.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { Store } from '@ngrx/store'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { HttpClient } from '@angular/common/http'; +import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service'; +import { getFirstSucceededRemoteData, getRemoteDataPayload } from '../shared/operators'; + +/** + * Service for checking and managing the feedback + */ +@Injectable() +@dataService(FEEDBACK) +export class FeedbackDataService extends DataService { + protected linkPath = 'feedbacks'; + + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected store: Store, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparator: DSOChangeAnalyzer, + ) { + super(); + } + + /** + * Get feedback from its id + * @param uuid string the id of the feedback + */ + getFeedback(uuid: string): Observable { + return this.findById(uuid).pipe( + getFirstSucceededRemoteData(), + getRemoteDataPayload(), + ); + } + +} diff --git a/src/app/core/feedback/feedback.guard.ts b/src/app/core/feedback/feedback.guard.ts new file mode 100644 index 0000000000..63353a60ff --- /dev/null +++ b/src/app/core/feedback/feedback.guard.ts @@ -0,0 +1,20 @@ +import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree } from '@angular/router'; +import { Observable } from 'rxjs'; +import { AuthorizationDataService } from '../data/feature-authorization/authorization-data.service'; +import { FeatureID } from '../data/feature-authorization/feature-id'; +import { Injectable } from '@angular/core'; + +/** + * An guard for redirecting users to the feedback page if user is authorized + */ +@Injectable() +export class FeedbackGuard implements CanActivate { + + constructor(private authorizationService: AuthorizationDataService) { + } + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + return this.authorizationService.isAuthorized(FeatureID.CanSendFeedback); + } + +} diff --git a/src/app/core/feedback/models/feedback.model.ts b/src/app/core/feedback/models/feedback.model.ts new file mode 100644 index 0000000000..fd98affba9 --- /dev/null +++ b/src/app/core/feedback/models/feedback.model.ts @@ -0,0 +1,34 @@ +import { autoserialize, inheritSerialization } from 'cerialize'; +import { typedObject } from '../../cache/builders/build-decorators'; + +import { DSpaceObject } from '../../shared/dspace-object.model'; +import { HALLink } from '../../shared/hal-link.model'; +import { FEEDBACK } from './feedback.resource-type'; + +@typedObject +@inheritSerialization(DSpaceObject) +export class Feedback extends DSpaceObject { + static type = FEEDBACK; + + /** + * The email address + */ + @autoserialize + public email: string; + + /** + * A string representing message the user inserted + */ + @autoserialize + public message: string; + /** + * A string representing the page from which the user came from + */ + @autoserialize + public page: string; + + _links: { + self: HALLink; + }; + +} diff --git a/src/app/core/feedback/models/feedback.resource-type.ts b/src/app/core/feedback/models/feedback.resource-type.ts new file mode 100644 index 0000000000..89b28c6cb0 --- /dev/null +++ b/src/app/core/feedback/models/feedback.resource-type.ts @@ -0,0 +1,9 @@ +import { ResourceType } from '../../shared/resource-type'; + +/** + * The resource type for Feedback + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const FEEDBACK = new ResourceType('feedback'); diff --git a/src/app/core/submission/models/access-condition.model.ts b/src/app/core/submission/models/access-condition.model.ts new file mode 100644 index 0000000000..5ec5c2c434 --- /dev/null +++ b/src/app/core/submission/models/access-condition.model.ts @@ -0,0 +1,25 @@ +/** + * An interface to represent an access condition. + */ +export class AccessConditionObject { + + /** + * The access condition id + */ + id: string; + + /** + * The access condition name + */ + name: string; + + /** + * Possible start date of the access condition + */ + startDate: string; + + /** + * Possible end date of the access condition + */ + endDate: string; +} diff --git a/src/app/core/submission/models/submission-accesses.resource-type.ts b/src/app/core/submission/models/submission-accesses.resource-type.ts new file mode 100644 index 0000000000..0634a63337 --- /dev/null +++ b/src/app/core/submission/models/submission-accesses.resource-type.ts @@ -0,0 +1,9 @@ +import { ResourceType } from '../../shared/resource-type'; + +/** + * The resource type for Accesses section + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const SUBMISSION_ACCESSES = new ResourceType('submissionaccesses'); diff --git a/src/app/core/submission/models/submission-item-access-condition.model.ts b/src/app/core/submission/models/submission-item-access-condition.model.ts new file mode 100644 index 0000000000..60e749b786 --- /dev/null +++ b/src/app/core/submission/models/submission-item-access-condition.model.ts @@ -0,0 +1,8 @@ +import { AccessConditionObject } from './access-condition.model'; + +/** + * An interface to represent item's access condition. + */ +export class SubmissionItemAccessConditionObject extends AccessConditionObject { + +} diff --git a/src/app/core/submission/models/submission-upload-file-access-condition.model.ts b/src/app/core/submission/models/submission-upload-file-access-condition.model.ts index fa4d9b9062..7be9d6f1f7 100644 --- a/src/app/core/submission/models/submission-upload-file-access-condition.model.ts +++ b/src/app/core/submission/models/submission-upload-file-access-condition.model.ts @@ -1,25 +1,8 @@ +import { AccessConditionObject } from './access-condition.model'; + /** * An interface to represent bitstream's access condition. */ -export class SubmissionUploadFileAccessConditionObject { +export class SubmissionUploadFileAccessConditionObject extends AccessConditionObject { - /** - * The access condition id - */ - id: string; - - /** - * The access condition name - */ - name: string; - - /** - * Possible start date of the access condition - */ - startDate: string; - - /** - * Possible end date of the access condition - */ - endDate: string; } diff --git a/src/app/core/submission/models/workspaceitem-section-accesses.model.ts b/src/app/core/submission/models/workspaceitem-section-accesses.model.ts new file mode 100644 index 0000000000..2d571f67f8 --- /dev/null +++ b/src/app/core/submission/models/workspaceitem-section-accesses.model.ts @@ -0,0 +1,21 @@ +import { SubmissionItemAccessConditionObject } from './submission-item-access-condition.model'; + +/** + * An interface to represent the submission's item accesses condition. + */ +export interface WorkspaceitemSectionAccessesObject { + /** + * The access condition id + */ + id: string; + + /** + * Boolean that indicates whether the current item must be findable via search or browse. + */ + discoverable: boolean; + + /** + * A list of available item access conditions + */ + accessConditions: SubmissionItemAccessConditionObject[]; +} diff --git a/src/app/core/submission/models/workspaceitem-sections.model.ts b/src/app/core/submission/models/workspaceitem-sections.model.ts index 6ff756a323..084da3f088 100644 --- a/src/app/core/submission/models/workspaceitem-sections.model.ts +++ b/src/app/core/submission/models/workspaceitem-sections.model.ts @@ -1,3 +1,4 @@ +import { WorkspaceitemSectionAccessesObject } from './workspaceitem-section-accesses.model'; import { WorkspaceitemSectionFormObject } from './workspaceitem-section-form.model'; import { WorkspaceitemSectionLicenseObject } from './workspaceitem-section-license.model'; import { WorkspaceitemSectionUploadObject } from './workspaceitem-section-upload.model'; @@ -19,4 +20,5 @@ export type WorkspaceitemSectionDataType | WorkspaceitemSectionFormObject | WorkspaceitemSectionLicenseObject | WorkspaceitemSectionCcLicenseObject + | WorkspaceitemSectionAccessesObject | string; diff --git a/src/app/footer/footer.component.html b/src/app/footer/footer.component.html index c8ee8a2f5b..2c1a34ccae 100644 --- a/src/app/footer/footer.component.html +++ b/src/app/footer/footer.component.html @@ -75,6 +75,10 @@ {{ 'footer.link.end-user-agreement' | translate}}
  • +
  • + {{ 'footer.link.feedback' | translate}} +
diff --git a/src/app/info/feedback/feedback-form/feedback-form.component.html b/src/app/info/feedback/feedback-form/feedback-form.component.html new file mode 100644 index 0000000000..02745f2580 --- /dev/null +++ b/src/app/info/feedback/feedback-form/feedback-form.component.html @@ -0,0 +1,45 @@ +
+
+
+

{{ 'info.feedback.head' | translate }}

+

{{ 'info.feedback.info' | translate }}

+
+
+
+ + + {{ 'info.feedback.email_help' | translate }} +
+
+ + + + + +
+
+ + +
+
+ + + +
+
+ + + {{ 'info.feedback.page_help' | translate }} +
+
+
+
+ +
+
+
+
+
+
diff --git a/src/app/info/feedback/feedback-form/feedback-form.component.scss b/src/app/info/feedback/feedback-form/feedback-form.component.scss new file mode 100644 index 0000000000..41d439e591 --- /dev/null +++ b/src/app/info/feedback/feedback-form/feedback-form.component.scss @@ -0,0 +1,3 @@ +ds-error{ + color:red; +} \ No newline at end of file diff --git a/src/app/info/feedback/feedback-form/feedback-form.component.spec.ts b/src/app/info/feedback/feedback-form/feedback-form.component.spec.ts new file mode 100644 index 0000000000..d6bedc46cf --- /dev/null +++ b/src/app/info/feedback/feedback-form/feedback-form.component.spec.ts @@ -0,0 +1,97 @@ +import { EPersonMock } from '../../../shared/testing/eperson.mock'; +import { FeedbackDataService } from '../../../core/feedback/feedback-data.service'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { FeedbackFormComponent } from './feedback-form.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { RouteService } from '../../../core/services/route.service'; +import { routeServiceStub } from '../../../shared/testing/route-service.stub'; +import { FormBuilder } from '@angular/forms'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; +import { AuthService } from '../../../core/auth/auth.service'; +import { AuthServiceStub } from '../../../shared/testing/auth-service.stub'; +import { of } from 'rxjs'; +import { Feedback } from '../../../core/feedback/models/feedback.model'; +import { Router } from '@angular/router'; +import { RouterMock } from '../../../shared/mocks/router.mock'; +import { NativeWindowService } from '../../../core/services/window.service'; +import { NativeWindowMockFactory } from '../../../shared/mocks/mock-native-window-ref'; + + +describe('FeedbackFormComponent', () => { + let component: FeedbackFormComponent; + let fixture: ComponentFixture; + let de: DebugElement; + const notificationService = new NotificationsServiceStub(); + const feedbackDataServiceStub = jasmine.createSpyObj('feedbackDataService', { + create: of(new Feedback()) + }); + const authService: AuthServiceStub = Object.assign(new AuthServiceStub(), { + getAuthenticatedUserFromStore: () => { + return of(EPersonMock); + } + }); + const routerStub = new RouterMock(); + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [FeedbackFormComponent], + providers: [ + { provide: RouteService, useValue: routeServiceStub }, + { provide: FormBuilder, useValue: new FormBuilder() }, + { provide: NotificationsService, useValue: notificationService }, + { provide: FeedbackDataService, useValue: feedbackDataServiceStub }, + { provide: AuthService, useValue: authService }, + { provide: NativeWindowService, useFactory: NativeWindowMockFactory }, + { provide: Router, useValue: routerStub }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FeedbackFormComponent); + component = fixture.componentInstance; + de = fixture.debugElement; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should have page value', () => { + expect(component.feedbackForm.controls.page.value).toEqual('http://localhost/home'); + }); + + it('should have email if ePerson', () => { + expect(component.feedbackForm.controls.email.value).toEqual('test@test.com'); + }); + + it('should have disabled button', () => { + expect(de.query(By.css('button')).nativeElement.disabled).toBeTrue(); + }); + + describe('when message is inserted', () => { + + beforeEach(() => { + component.feedbackForm.patchValue({ message: 'new feedback' }); + fixture.detectChanges(); + }); + + it('should not have disabled button', () => { + expect(de.query(By.css('button')).nativeElement.disabled).toBeFalse(); + }); + + it('on submit should call createFeedback of feedbackDataServiceStub service', () => { + component.createFeedback(); + fixture.detectChanges(); + expect(feedbackDataServiceStub.create).toHaveBeenCalled(); + }); + }); + + +}); diff --git a/src/app/info/feedback/feedback-form/feedback-form.component.ts b/src/app/info/feedback/feedback-form/feedback-form.component.ts new file mode 100644 index 0000000000..35cdd69cfc --- /dev/null +++ b/src/app/info/feedback/feedback-form/feedback-form.component.ts @@ -0,0 +1,83 @@ +import { RemoteData } from '../../../core/data/remote-data'; +import { NoContent } from '../../../core/shared/NoContent.model'; +import { FeedbackDataService } from '../../../core/feedback/feedback-data.service'; +import { Component, Inject, OnInit } from '@angular/core'; +import { RouteService } from '../../../core/services/route.service'; +import { FormBuilder, Validators } from '@angular/forms'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { AuthService } from '../../../core/auth/auth.service'; +import { EPerson } from '../../../core/eperson/models/eperson.model'; +import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; +import { Router } from '@angular/router'; +import { getHomePageRoute } from '../../../app-routing-paths'; +import { take } from 'rxjs/operators'; +import { NativeWindowRef, NativeWindowService } from '../../../core/services/window.service'; +import { URLCombiner } from '../../../core/url-combiner/url-combiner'; + +@Component({ + selector: 'ds-feedback-form', + templateUrl: './feedback-form.component.html', + styleUrls: ['./feedback-form.component.scss'] +}) +/** + * Component displaying the contents of the Feedback Statement + */ +export class FeedbackFormComponent implements OnInit { + + /** + * Form builder created used from the feedback from + */ + feedbackForm = this.fb.group({ + email: ['', [Validators.required, Validators.pattern('^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$')]], + message: ['', Validators.required], + page: [''], + }); + + constructor( + @Inject(NativeWindowService) protected _window: NativeWindowRef, + public routeService: RouteService, + private fb: FormBuilder, + protected notificationsService: NotificationsService, + protected translate: TranslateService, + private feedbackDataService: FeedbackDataService, + private authService: AuthService, + private router: Router) { + } + + /** + * On init check if user is logged in and use its email if so + */ + ngOnInit() { + + this.authService.getAuthenticatedUserFromStore().pipe(take(1)).subscribe((user: EPerson) => { + if (!!user) { + this.feedbackForm.patchValue({ email: user.email }); + } + }); + + this.routeService.getPreviousUrl().pipe(take(1)).subscribe((url: string) => { + if (!url) { + url = getHomePageRoute(); + } + const relatedUrl = new URLCombiner(this._window.nativeWindow.origin, url).toString(); + this.feedbackForm.patchValue({ page: relatedUrl }); + }); + + } + + /** + * Function to create the feedback from form values + */ + createFeedback(): void { + const url = this.feedbackForm.value.page.replace(this._window.nativeWindow.origin, ''); + this.feedbackDataService.create(this.feedbackForm.value).pipe(getFirstCompletedRemoteData()).subscribe((response: RemoteData) => { + if (response.isSuccess) { + this.notificationsService.success(this.translate.instant('info.feedback.create.success')); + this.feedbackForm.reset(); + this.router.navigateByUrl(url); + } + }); + } + +} diff --git a/src/app/info/feedback/feedback.component.html b/src/app/info/feedback/feedback.component.html new file mode 100644 index 0000000000..210bdcf1d7 --- /dev/null +++ b/src/app/info/feedback/feedback.component.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/src/app/info/feedback/feedback.component.scss b/src/app/info/feedback/feedback.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/info/feedback/feedback.component.spec.ts b/src/app/info/feedback/feedback.component.spec.ts new file mode 100644 index 0000000000..810c3b703f --- /dev/null +++ b/src/app/info/feedback/feedback.component.spec.ts @@ -0,0 +1,27 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { FeedbackComponent } from './feedback.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; + +describe('FeedbackComponent', () => { + let component: FeedbackComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [FeedbackComponent], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FeedbackComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/info/feedback/feedback.component.ts b/src/app/info/feedback/feedback.component.ts new file mode 100644 index 0000000000..3c0924b4de --- /dev/null +++ b/src/app/info/feedback/feedback.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ds-feedback', + templateUrl: './feedback.component.html', + styleUrls: ['./feedback.component.scss'] +}) +/** + * Component displaying the Feedback Statement + */ +export class FeedbackComponent { +} diff --git a/src/app/info/feedback/themed-feedback.component.ts b/src/app/info/feedback/themed-feedback.component.ts new file mode 100644 index 0000000000..68581c32fd --- /dev/null +++ b/src/app/info/feedback/themed-feedback.component.ts @@ -0,0 +1,26 @@ +import { Component } from '@angular/core'; +import { ThemedComponent } from '../../shared/theme-support/themed.component'; +import { FeedbackComponent } from './feedback.component'; + +/** + * Themed wrapper for FeedbackComponent + */ +@Component({ + selector: 'ds-themed-feedback', + styleUrls: [], + templateUrl: '../../shared/theme-support/themed.component.html', +}) +export class ThemedFeedbackComponent extends ThemedComponent { + protected getComponentName(): string { + return 'FeedbackComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../themes/${themeName}/app/info/feedback/feedback.component`); + } + + protected importUnthemedComponent(): Promise { + return import(`./feedback.component`); + } + +} diff --git a/src/app/info/info-routing-paths.ts b/src/app/info/info-routing-paths.ts index 8ec6dbcb8d..a18de2c611 100644 --- a/src/app/info/info-routing-paths.ts +++ b/src/app/info/info-routing-paths.ts @@ -2,6 +2,7 @@ import { getInfoModulePath } from '../app-routing-paths'; export const END_USER_AGREEMENT_PATH = 'end-user-agreement'; export const PRIVACY_PATH = 'privacy'; +export const FEEDBACK_PATH = 'feedback'; export function getEndUserAgreementPath() { return getSubPath(END_USER_AGREEMENT_PATH); @@ -11,6 +12,10 @@ export function getPrivacyPath() { return getSubPath(PRIVACY_PATH); } +export function getFeedbackPath() { + return getSubPath(FEEDBACK_PATH); +} + function getSubPath(path: string) { return `${getInfoModulePath()}/${path}`; } diff --git a/src/app/info/info-routing.module.ts b/src/app/info/info-routing.module.ts index f76fb47ff0..c746bebaaa 100644 --- a/src/app/info/info-routing.module.ts +++ b/src/app/info/info-routing.module.ts @@ -1,9 +1,12 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { PRIVACY_PATH, END_USER_AGREEMENT_PATH } from './info-routing-paths'; +import { PRIVACY_PATH, END_USER_AGREEMENT_PATH, FEEDBACK_PATH } from './info-routing-paths'; import { ThemedEndUserAgreementComponent } from './end-user-agreement/themed-end-user-agreement.component'; import { ThemedPrivacyComponent } from './privacy/themed-privacy.component'; +import { ThemedFeedbackComponent } from './feedback/themed-feedback.component'; +import { FeedbackGuard } from '../core/feedback/feedback.guard'; + @NgModule({ imports: [ @@ -22,6 +25,15 @@ import { ThemedPrivacyComponent } from './privacy/themed-privacy.component'; resolve: { breadcrumb: I18nBreadcrumbResolver }, data: { title: 'info.privacy.title', breadcrumbKey: 'info.privacy' } } + ]), + RouterModule.forChild([ + { + path: FEEDBACK_PATH, + component: ThemedFeedbackComponent, + resolve: { breadcrumb: I18nBreadcrumbResolver }, + data: { title: 'info.feedback.title', breadcrumbKey: 'info.feedback' }, + canActivate: [FeedbackGuard] + } ]) ] }) diff --git a/src/app/info/info.module.ts b/src/app/info/info.module.ts index 84ba5a62db..61eee71f3a 100644 --- a/src/app/info/info.module.ts +++ b/src/app/info/info.module.ts @@ -8,6 +8,11 @@ import { PrivacyComponent } from './privacy/privacy.component'; import { PrivacyContentComponent } from './privacy/privacy-content/privacy-content.component'; import { ThemedEndUserAgreementComponent } from './end-user-agreement/themed-end-user-agreement.component'; import { ThemedPrivacyComponent } from './privacy/themed-privacy.component'; +import { FeedbackComponent } from './feedback/feedback.component'; +import { FeedbackFormComponent } from './feedback/feedback-form/feedback-form.component'; +import { ThemedFeedbackComponent } from './feedback/themed-feedback.component'; +import { FeedbackGuard } from '../core/feedback/feedback.guard'; + const DECLARATIONS = [ EndUserAgreementComponent, @@ -15,21 +20,25 @@ const DECLARATIONS = [ EndUserAgreementContentComponent, PrivacyComponent, PrivacyContentComponent, - ThemedPrivacyComponent + ThemedPrivacyComponent, + FeedbackComponent, + FeedbackFormComponent, + ThemedFeedbackComponent ]; @NgModule({ imports: [ CommonModule, SharedModule, - InfoRoutingModule + InfoRoutingModule, ], declarations: [ ...DECLARATIONS ], exports: [ ...DECLARATIONS - ] + ], + providers: [FeedbackGuard] }) export class InfoModule { } diff --git a/src/app/item-page/edit-item-page/item-delete/item-delete.component.spec.ts b/src/app/item-page/edit-item-page/item-delete/item-delete.component.spec.ts index ea78767df5..2533de32b2 100644 --- a/src/app/item-page/edit-item-page/item-delete/item-delete.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-delete/item-delete.component.spec.ts @@ -3,7 +3,7 @@ import { ItemType } from '../../../core/shared/item-relationships/item-type.mode import { Relationship } from '../../../core/shared/item-relationships/relationship.model'; import { Item } from '../../../core/shared/item.model'; import { RouterStub } from '../../../shared/testing/router.stub'; -import { of as observableOf } from 'rxjs'; +import { of as observableOf, EMPTY } from 'rxjs'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; @@ -24,6 +24,8 @@ import { RelationshipType } from '../../../core/shared/item-relationships/relati import { EntityTypeService } from '../../../core/data/entity-type.service'; import { getItemEditRoute } from '../../item-page-routing-paths'; import { createPaginatedList } from '../../../shared/testing/utils.test'; +import { RelationshipTypeService } from '../../../core/data/relationship-type.service'; +import { LinkService } from '../../../core/cache/builders/link.service'; let comp: ItemDeleteComponent; let fixture: ComponentFixture; @@ -40,6 +42,7 @@ let mockItemDataService: ItemDataService; let routeStub; let objectUpdatesServiceStub; let relationshipService; +let linkService; let entityTypeService; let notificationsServiceStub; let typesSelection; @@ -52,7 +55,12 @@ describe('ItemDeleteComponent', () => { uuid: 'fake-uuid', handle: 'fake/handle', lastModified: '2018', - isWithdrawn: true + isWithdrawn: true, + metadata: { + 'dspace.entity.type': [ + { value: 'Person' } + ] + } }); itemType = Object.assign(new ItemType(), { @@ -129,6 +137,12 @@ describe('ItemDeleteComponent', () => { } ); + linkService = jasmine.createSpyObj('linkService', + { + resolveLinks: relationships[0], + } + ); + notificationsServiceStub = new NotificationsServiceStub(); TestBed.configureTestingModule({ @@ -142,6 +156,8 @@ describe('ItemDeleteComponent', () => { { provide: ObjectUpdatesService, useValue: objectUpdatesServiceStub }, { provide: RelationshipService, useValue: relationshipService }, { provide: EntityTypeService, useValue: entityTypeService }, + { provide: RelationshipTypeService, useValue: {} }, + { provide: LinkService, useValue: linkService }, ], schemas: [ CUSTOM_ELEMENTS_SCHEMA ] @@ -166,25 +182,45 @@ describe('ItemDeleteComponent', () => { }); describe('performAction', () => { - it('should call delete function from the ItemDataService', () => { - spyOn(comp, 'notify'); - comp.performAction(); - expect(mockItemDataService.delete) - .toHaveBeenCalledWith(mockItem.id, types.filter((type) => typesSelection[type]).map((type) => type.id)); - expect(comp.notify).toHaveBeenCalled(); + describe(`when there are entitytypes`, () => { + it('should call delete function from the ItemDataService', () => { + spyOn(comp, 'notify'); + comp.performAction(); + expect(mockItemDataService.delete) + .toHaveBeenCalledWith(mockItem.id, types.filter((type) => typesSelection[type]).map((type) => type.id)); + expect(comp.notify).toHaveBeenCalled(); + }); + + it('should call delete function from the ItemDataService with empty types', () => { + + spyOn(comp, 'notify'); + jasmine.getEnv().allowRespy(true); + spyOn(entityTypeService, 'getEntityTypeRelationships').and.returnValue([]); + comp.ngOnInit(); + + comp.performAction(); + + expect(mockItemDataService.delete).toHaveBeenCalledWith(mockItem.id, []); + expect(comp.notify).toHaveBeenCalled(); + }); }); - it('should call delete function from the ItemDataService with empty types', () => { + describe(`when there are no entity types`, () => { + beforeEach(() => { + (comp as any).entityTypeService = jasmine.createSpyObj('entityTypeService', + { + getEntityTypeByLabel: EMPTY, + } + ); + }); - spyOn(comp, 'notify'); - jasmine.getEnv().allowRespy(true); - spyOn(entityTypeService, 'getEntityTypeRelationships').and.returnValue([]); - comp.ngOnInit(); - - comp.performAction(); - - expect(mockItemDataService.delete).toHaveBeenCalledWith(mockItem.id, []); - expect(comp.notify).toHaveBeenCalled(); + it('should call delete function from the ItemDataService', () => { + spyOn(comp, 'notify'); + comp.performAction(); + expect(mockItemDataService.delete) + .toHaveBeenCalledWith(mockItem.id, types.filter((type) => typesSelection[type]).map((type) => type.id)); + expect(comp.notify).toHaveBeenCalled(); + }); }); }); describe('notify', () => { diff --git a/src/app/item-page/edit-item-page/item-delete/item-delete.component.ts b/src/app/item-page/edit-item-page/item-delete/item-delete.component.ts index 0249422c8e..7735fae0ea 100644 --- a/src/app/item-page/edit-item-page/item-delete/item-delete.component.ts +++ b/src/app/item-page/edit-item-page/item-delete/item-delete.component.ts @@ -1,12 +1,14 @@ -import { Component, Input, OnInit } from '@angular/core'; -import {defaultIfEmpty, filter, map, switchMap, take} from 'rxjs/operators'; -import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component'; +import { Component, Input, OnInit, OnDestroy } from '@angular/core'; +import { defaultIfEmpty, filter, map, switchMap, take } from 'rxjs/operators'; +import { + AbstractSimpleItemActionComponent +} from '../simple-item-action/abstract-simple-item-action.component'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { combineLatest as observableCombineLatest, combineLatest, Observable, - of as observableOf + of as observableOf, Subscription } from 'rxjs'; import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model'; import { VirtualMetadata } from '../virtual-metadata/virtual-metadata.component'; @@ -32,6 +34,7 @@ import { followLink } from '../../../shared/utils/follow-link-config.model'; import { getItemEditRoute } from '../../item-page-routing-paths'; import { RemoteData } from '../../../core/data/remote-data'; import { NoContent } from '../../../core/shared/NoContent.model'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; @Component({ selector: 'ds-item-delete', @@ -42,7 +45,7 @@ import { NoContent } from '../../../core/shared/NoContent.model'; */ export class ItemDeleteComponent extends AbstractSimpleItemActionComponent - implements OnInit { + implements OnInit, OnDestroy { /** * The current url of this page @@ -60,7 +63,7 @@ export class ItemDeleteComponent * A list of the relationship types for which this item has relations as an observable. * The list doesn't contain duplicates. */ - types$: Observable; + types$: BehaviorSubject = new BehaviorSubject([]); /** * A map which stores the relationships of this item for each type as observable lists @@ -84,6 +87,11 @@ export class ItemDeleteComponent */ public modalRef: NgbModalRef; + /** + * Array to track all subscriptions and unsubscribe them onDestroy + */ + private subs: Subscription[] = []; + constructor(protected route: ActivatedRoute, protected router: Router, protected notificationsService: NotificationsService, @@ -113,8 +121,8 @@ export class ItemDeleteComponent this.url = this.router.url; const label = this.item.firstMetadataValue('dspace.entity.type'); - if (label !== undefined) { - this.types$ = this.entityTypeService.getEntityTypeByLabel(label).pipe( + if (isNotEmpty(label)) { + this.subs.push(this.entityTypeService.getEntityTypeByLabel(label).pipe( getFirstSucceededRemoteData(), getRemoteDataPayload(), switchMap((entityType) => this.entityTypeService.getEntityTypeRelationships(entityType.id)), @@ -138,16 +146,14 @@ export class ItemDeleteComponent ), ); }) - ); - } else { - this.types$ = observableOf([]); + ).subscribe((types: RelationshipType[]) => this.types$.next(types))); } - this.types$.pipe( + this.subs.push(this.types$.pipe( take(1), ).subscribe((types) => this.objectUpdatesService.initialize(this.url, types, this.item.lastModified) - ); + )); } /** @@ -327,7 +333,7 @@ export class ItemDeleteComponent */ performAction() { - this.types$.pipe( + this.subs.push(this.types$.pipe( switchMap((types) => combineLatest( types.map((type) => this.isSelected(type)) @@ -339,13 +345,14 @@ export class ItemDeleteComponent map((selectedTypes) => selectedTypes.map((type) => type.id)), ) ), - ).subscribe((types) => { - this.itemDataService.delete(this.item.id, types).pipe(getFirstCompletedRemoteData()).subscribe( - (rd: RemoteData) => { - this.notify(rd.hasSucceeded); - } - ); - }); + switchMap((types) => + this.itemDataService.delete(this.item.id, types).pipe(getFirstCompletedRemoteData()) + ) + ).subscribe( + (rd: RemoteData) => { + this.notify(rd.hasSucceeded); + } + )); } /** @@ -361,4 +368,14 @@ export class ItemDeleteComponent this.router.navigate([getItemEditRoute(this.item)]); } } + + /** + * Unsubscribe from all subscriptions + */ + ngOnDestroy(): void { + this.subs + .filter((sub) => hasValue(sub)) + .forEach((sub) => sub.unsubscribe()); + } + } diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html index fc115e043a..55e354ea7a 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html @@ -14,7 +14,8 @@
-
-
- +
+
diff --git a/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.spec.ts b/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.spec.ts index 8f80cea1d3..e5420f7d4f 100644 --- a/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.spec.ts +++ b/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.spec.ts @@ -4,6 +4,7 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; import { Collection } from '../../../core/shared/collection.model'; import { LinkService } from '../../../core/cache/builders/link.service'; +import { TranslateModule } from '@ngx-translate/core'; let collectionGridElementComponent: CollectionGridElementComponent; let fixture: ComponentFixture; @@ -37,6 +38,9 @@ const linkService = jasmine.createSpyObj('linkService', { describe('CollectionGridElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot() + ], declarations: [CollectionGridElementComponent], providers: [ { provide: 'objectElementProvider', useValue: (mockCollectionWithAbstract) }, diff --git a/src/app/shared/object-grid/community-grid-element/community-grid-element.component.html b/src/app/shared/object-grid/community-grid-element/community-grid-element.component.html index 63097c4f57..c9833b8829 100644 --- a/src/app/shared/object-grid/community-grid-element/community-grid-element.component.html +++ b/src/app/shared/object-grid/community-grid-element/community-grid-element.component.html @@ -1,5 +1,5 @@
- + @@ -11,7 +11,7 @@

{{object.name}}

{{object.shortDescription}}

diff --git a/src/app/shared/object-grid/community-grid-element/community-grid-element.component.spec.ts b/src/app/shared/object-grid/community-grid-element/community-grid-element.component.spec.ts index c678dadd5c..2bf682350e 100644 --- a/src/app/shared/object-grid/community-grid-element/community-grid-element.component.spec.ts +++ b/src/app/shared/object-grid/community-grid-element/community-grid-element.component.spec.ts @@ -4,6 +4,7 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; import { Community } from '../../../core/shared/community.model'; import { LinkService } from '../../../core/cache/builders/link.service'; +import { TranslateModule } from '@ngx-translate/core'; let communityGridElementComponent: CommunityGridElementComponent; let fixture: ComponentFixture; @@ -37,6 +38,9 @@ const linkService = jasmine.createSpyObj('linkService', { describe('CommunityGridElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot() + ], declarations: [CommunityGridElementComponent], providers: [ { provide: 'objectElementProvider', useValue: (mockCommunityWithAbstract) }, diff --git a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html index 739fa6c7a8..b05bb4f7ba 100644 --- a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html @@ -1,5 +1,5 @@
- + @@ -12,7 +12,7 @@

{{dso.name}}

{{dso.shortDescription}}

diff --git a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.spec.ts b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.spec.ts index 065efbf379..15548d6608 100644 --- a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.spec.ts +++ b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.spec.ts @@ -20,6 +20,7 @@ import { TruncatePipe } from '../../../utils/truncate.pipe'; import { CollectionSearchResultGridElementComponent } from './collection-search-result-grid-element.component'; import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service'; import { LinkService } from '../../../../core/cache/builders/link.service'; +import { TranslateModule } from '@ngx-translate/core'; let collectionSearchResultGridElementComponent: CollectionSearchResultGridElementComponent; let fixture: ComponentFixture; @@ -60,6 +61,9 @@ const linkService = jasmine.createSpyObj('linkService', { describe('CollectionSearchResultGridElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot() + ], declarations: [CollectionSearchResultGridElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: truncatableServiceStub }, diff --git a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html index d8c253c8a9..d915cdb7a4 100644 --- a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html @@ -1,5 +1,5 @@
- + @@ -12,7 +12,7 @@

{{dso.name}}

{{dso.shortDescription}}

diff --git a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.spec.ts b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.spec.ts index 42005d745e..710fbf3f67 100644 --- a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.spec.ts +++ b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.spec.ts @@ -20,6 +20,7 @@ import { TruncatePipe } from '../../../utils/truncate.pipe'; import { CommunitySearchResultGridElementComponent } from './community-search-result-grid-element.component'; import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service'; import { LinkService } from '../../../../core/cache/builders/link.service'; +import { TranslateModule } from '@ngx-translate/core'; let communitySearchResultGridElementComponent: CommunitySearchResultGridElementComponent; let fixture: ComponentFixture; @@ -60,6 +61,9 @@ const linkService = jasmine.createSpyObj('linkService', { describe('CommunitySearchResultGridElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot() + ], declarations: [CommunitySearchResultGridElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: truncatableServiceStub }, diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html index d2454b28e6..7fdb505d43 100644 --- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html @@ -4,7 +4,7 @@
+ class="card-img-top full-width" [attr.title]="'search.results.view-result' | translate">
@@ -37,7 +37,7 @@

View + class="lead btn btn-primary viewButton">{{ 'search.results.view-result' | translate}}
diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec.ts b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec.ts index ca6614efbc..57b863a1b1 100644 --- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec.ts +++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec.ts @@ -4,6 +4,7 @@ import { TestBed, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { Store } from '@ngrx/store'; +import { TranslateModule } from '@ngx-translate/core'; import { Observable, of as observableOf } from 'rxjs'; import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../../../../../core/cache/object-cache.service'; @@ -99,7 +100,10 @@ export function getEntityGridElementTestComponent(component, searchResultWithMet beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [NoopAnimationsModule], + imports: [ + NoopAnimationsModule, + TranslateModule.forRoot() + ], declarations: [component, TruncatePipe], providers: [ { provide: TruncatableService, useValue: truncatableServiceStub }, diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index f33a195cfe..2af9d16b0c 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3408,6 +3408,8 @@ "search.results.empty": "Your search returned no results.", + "search.results.view-result": "View", + "default.search.results.head": "Search Results", From ba40cfe75ae971428890d96c0ac0f484ff39a4fb Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 4 Feb 2022 14:46:31 -0600 Subject: [PATCH 028/160] Enhance Search results tests (stabilize). Add more MyDSpace tests, including starting a submission --- cypress.json | 4 +- cypress/integration/my-dspace.spec.ts | 101 ++++++++++++++++++---- cypress/integration/search-navbar.spec.ts | 4 +- cypress/integration/search-page.spec.ts | 10 ++- cypress/support/index.ts | 2 + 5 files changed, 101 insertions(+), 20 deletions(-) diff --git a/cypress.json b/cypress.json index 549485eb9e..725f02fcb7 100644 --- a/cypress.json +++ b/cypress.json @@ -16,6 +16,8 @@ "DSPACE_TEST_ADMIN_PASSWORD": "dspace", "DSPACE_TEST_COMMUNITY": "0958c910-2037-42a9-81c7-dca80e3892b4", "DSPACE_TEST_COLLECTION": "282164f5-d325-4740-8dd1-fa4d6d3e7200", - "DSPACE_TEST_ENTITY_PUBLICATION": "e98b0f27-5c19-49a0-960d-eb6ad5287067" + "DSPACE_TEST_COLLECTION_NAME": "Sample Collection", + "DSPACE_TEST_ENTITY_PUBLICATION": "e98b0f27-5c19-49a0-960d-eb6ad5287067", + "DSPACE_TEST_SEARCH_TERM": "test" } } \ No newline at end of file diff --git a/cypress/integration/my-dspace.spec.ts b/cypress/integration/my-dspace.spec.ts index 6348185065..970255dd5a 100644 --- a/cypress/integration/my-dspace.spec.ts +++ b/cypress/integration/my-dspace.spec.ts @@ -1,29 +1,52 @@ import { Options } from 'cypress-axe'; -import { TEST_ADMIN_USER, TEST_ADMIN_PASSWORD } from 'cypress/support'; +import { TEST_ADMIN_USER, TEST_ADMIN_PASSWORD, TEST_COLLECTION_NAME } from 'cypress/support'; import { testA11y } from 'cypress/support/utils'; describe('My DSpace page', () => { - it('should display recent submissions', () => { + it('should display recent submissions and pass accessibility tests', () => { cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); - // This is the GET command that will automatically search for recent submissions - cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results'); - - cy.visit('/mydspace'); - - // Wait for search results to come back from the above GET command - cy.wait('@search-results'); - - // At least one recent submission should be displayed - cy.get('ds-item-search-result-list-element-submission').should('be.visible'); - }); - - it('should pass accessibility tests', () => { - cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); cy.visit('/mydspace'); cy.get('ds-my-dspace-page').should('exist'); + // At least one recent submission should be displayed + cy.get('ds-item-search-result-list-element-submission').should('be.visible'); + + // Click each filter toggle to open *every* filter + // (As we want to scan filter section for accessibility issues as well) + cy.get('.filter-toggle').click({ multiple: true }); + + // Analyze for accessibility issues + testA11y( + { + include: ['ds-my-dspace-page'], + exclude: [ + ['nouislider'] // Date filter slider is missing ARIA labels. Will be fixed by #1175 + ], + }, + { + rules: { + // Search filters fail these two "moderate" impact rules + 'heading-order': { enabled: false }, + 'landmark-unique': { enabled: false } + } + } as Options + ); + }); + + it('should have a working detailed view that passes accessibility tests', () => { + cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); + + cy.visit('/mydspace'); + + cy.get('ds-my-dspace-page').should('exist'); + + // Click button in sidebar to display detailed view + cy.get('ds-search-sidebar [data-e2e="detail-view"]').click(); + + cy.get('ds-object-detail').should('exist'); + // Analyze for accessibility issues testA11y('ds-my-dspace-page', { @@ -35,4 +58,50 @@ describe('My DSpace page', () => { } as Options ); }); + + + it('should let you start a new submission', () => { + cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); + cy.visit('/mydspace'); + + // Open the New Submission dropdown + cy.get('#dropdownSubmission').click(); + // Click on the "Item" type in that dropdown + cy.get('#entityControlsDropdownMenu button[title="none"]').click(); + + // This should display the (popup window) + cy.get('ds-create-item-parent-selector').should('be.visible'); + + // Type in a known Collection name in the search box + cy.get('ds-authorized-collection-selector input[type="search"]').type(TEST_COLLECTION_NAME); + + // Click on the button matching that known Collection name + cy.get('ds-authorized-collection-selector button[title="' + TEST_COLLECTION_NAME + '"]').click(); + + // New URL should include /workspaceitems, as we've started a new submission + cy.url().should('include', '/workspaceitems'); + + // The Submission edit form tag should be visible + cy.get('ds-submission-edit').should('be.visible'); + + // A Collection menu button should exist & it's value should be the selected collection + cy.get('#collectionControlsMenuButton span').should('have.text', TEST_COLLECTION_NAME); + }); + + it('should let you import from external sources', () => { + cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); + cy.visit('/mydspace'); + + // Open the New Import dropdown + cy.get('#dropdownImport').click(); + // Click on the "Item" type in that dropdown + cy.get('#importControlsDropdownMenu button[title="none"]').click(); + + // New URL should include /import-external, as we've moved to the import page + cy.url().should('include', '/import-external'); + + // The external import searchbox should be visible + cy.get('ds-submission-import-external-searchbar').should('be.visible'); + }); + }); diff --git a/cypress/integration/search-navbar.spec.ts b/cypress/integration/search-navbar.spec.ts index 53e713412e..3aa8e7248d 100644 --- a/cypress/integration/search-navbar.spec.ts +++ b/cypress/integration/search-navbar.spec.ts @@ -1,3 +1,5 @@ +import { TEST_SEARCH_TERM } from 'cypress/support'; + const page = { fillOutQueryInNavBar(query) { // Click the magnifying glass @@ -15,7 +17,7 @@ const page = { describe('Search from Navigation Bar', () => { // NOTE: these tests currently assume this query will return results! - const query = 'test'; + const query = TEST_SEARCH_TERM; it('should go to search page with correct query if submitted (from home)', () => { cy.visit('/'); diff --git a/cypress/integration/search-page.spec.ts b/cypress/integration/search-page.spec.ts index 0765884952..cbd518b97c 100644 --- a/cypress/integration/search-page.spec.ts +++ b/cypress/integration/search-page.spec.ts @@ -17,12 +17,15 @@ describe('Search Page', () => { cy.url().should('include', 'query=' + encodeURI(queryString)); }); - it('should pass accessibility tests', () => { + it('should load results and pass accessibility tests', () => { cy.visit('/search'); // tag must be loaded cy.get('ds-search-page').should('exist'); + // At least one search result should be displayed + cy.get('ds-item-search-result-list-element').should('be.visible'); + // Click each filter toggle to open *every* filter // (As we want to scan filter section for accessibility issues as well) cy.get('.filter-toggle').click({ multiple: true }); @@ -45,7 +48,7 @@ describe('Search Page', () => { ); }); - it('should pass accessibility tests in Grid view', () => { + it('should have a working grid view that passes accessibility tests', () => { cy.visit('/search'); // Click button in sidebar to display grid view @@ -54,6 +57,9 @@ describe('Search Page', () => { // tag must be loaded cy.get('ds-search-page').should('exist'); + // At least one grid element (card) should be displayed + cy.get('ds-item-search-result-grid-element').should('be.visible'); + // Analyze for accessibility issues testA11y('ds-search-page', { diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 442f00b9fe..0213cfe55b 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -37,5 +37,7 @@ export const TEST_REST_BASE_URL = Cypress.env('DSPACE_TEST_REST_BASE_URL') || 'h export const TEST_ADMIN_USER = Cypress.env('DSPACE_TEST_ADMIN_USER') || 'dspacedemo+admin@gmail.com'; export const TEST_ADMIN_PASSWORD = Cypress.env('DSPACE_TEST_ADMIN_PASSWORD') || 'dspace'; export const TEST_COLLECTION = Cypress.env('DSPACE_TEST_COLLECTION') || '282164f5-d325-4740-8dd1-fa4d6d3e7200'; +export const TEST_COLLECTION_NAME = Cypress.env('DSPACE_TEST_COLLECTION_NAME') || 'Sample Collection'; export const TEST_COMMUNITY = Cypress.env('DSPACE_TEST_COMMUNITY') || '0958c910-2037-42a9-81c7-dca80e3892b4'; export const TEST_ENTITY_PUBLICATION = Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION') || 'e98b0f27-5c19-49a0-960d-eb6ad5287067'; +export const TEST_SEARCH_TERM = Cypress.env('DSPACE_TEST_SEARCH_TERM') || 'test'; From 106a73f0d3c5bda2c1472591ed7fdd84cf325d2a Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Mon, 7 Feb 2022 17:05:14 -0600 Subject: [PATCH 029/160] Improve stability/consistency of e2e tests by visiting blank page between each test --- cypress/support/index.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 0213cfe55b..cf93b1fefb 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -27,17 +27,30 @@ before(() => { cy.setCookie('klaro-anonymous', '{%22authentication%22:true%2C%22preferences%22:true%2C%22acknowledgement%22:true%2C%22google-analytics%22:true}'); }); +// For better stability between tests, we visit "about:blank" (i.e. blank page) after each test. +// This ensures any remaining/outstanding XHR requests are killed, so they don't affect the next test. +// Borrowed from: https://glebbahmutov.com/blog/visit-blank-page-between-tests/ +afterEach(() => { + cy.window().then((win) => { + win.location.href = 'about:blank' + }) +}); + // Global constants used in tests // May be overridden in our cypress.json config file using specified environment variables. -// Default UUIDs listed here are all in the Demo Entities Data set available at +// Default values listed here are all valid for the Demo Entities Data set available at // https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data // (This is the data set used in our CI environment) export const TEST_REST_BASE_URL = Cypress.env('DSPACE_TEST_REST_BASE_URL') || 'http://localhost:8080'; export const TEST_ADMIN_USER = Cypress.env('DSPACE_TEST_ADMIN_USER') || 'dspacedemo+admin@gmail.com'; export const TEST_ADMIN_PASSWORD = Cypress.env('DSPACE_TEST_ADMIN_PASSWORD') || 'dspace'; +// Community/collection/publication used for view/edit tests export const TEST_COLLECTION = Cypress.env('DSPACE_TEST_COLLECTION') || '282164f5-d325-4740-8dd1-fa4d6d3e7200'; -export const TEST_COLLECTION_NAME = Cypress.env('DSPACE_TEST_COLLECTION_NAME') || 'Sample Collection'; export const TEST_COMMUNITY = Cypress.env('DSPACE_TEST_COMMUNITY') || '0958c910-2037-42a9-81c7-dca80e3892b4'; export const TEST_ENTITY_PUBLICATION = Cypress.env('DSPACE_TEST_ENTITY_PUBLICATION') || 'e98b0f27-5c19-49a0-960d-eb6ad5287067'; +// Search term (should return results) used in search tests export const TEST_SEARCH_TERM = Cypress.env('DSPACE_TEST_SEARCH_TERM') || 'test'; +// Collection used for submission tests +export const TEST_SUBMIT_COLLECTION_NAME = Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME') || 'Sample Collection'; +export const TEST_SUBMIT_COLLECTION_UUID = Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID') || '9d8334e9-25d3-4a67-9cea-3dffdef80144'; \ No newline at end of file From 9906d5fec02da8996a654795b2fad2a6c4f14e9e Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 3 Mar 2022 11:10:35 +0100 Subject: [PATCH 030/160] 88082: Revert navigateAction on redirectOn4xx --- src/app/core/shared/operators.ts | 14 +++----------- src/app/register-page/registration.guard.spec.ts | 4 +--- src/app/register-page/registration.guard.ts | 8 ++------ 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index c53ca20b1d..3be04447ab 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -197,27 +197,19 @@ export const getAllSucceededRemoteListPayload = () => * * @param router The router used to navigate to a new page * @param authService Service to check if the user is authenticated - * @param navigateAction Optional action to take with the Promise returned by navigateByUrl */ -// tslint:disable-next-line:no-empty -export const redirectOn4xx = (router: Router, authService: AuthService, navigateAction?: (nav: boolean) => void) => +export const redirectOn4xx = (router: Router, authService: AuthService) => (source: Observable>): Observable> => source.pipe( withLatestFrom(authService.isAuthenticated()), filter(([rd, isAuthenticated]: [RemoteData, boolean]) => { if (rd.hasFailed) { if (rd.statusCode === 404 || rd.statusCode === 422) { - const promise = router.navigateByUrl(getPageNotFoundRoute(), { skipLocationChange: true }); - if (hasValue(navigateAction)) { - promise.then(navigateAction); - } + router.navigateByUrl(getPageNotFoundRoute(), { skipLocationChange: true }); return false; } else if (rd.statusCode === 403 || rd.statusCode === 401) { if (isAuthenticated) { - const promise = router.navigateByUrl(getForbiddenRoute(), { skipLocationChange: true }); - if (hasValue(navigateAction)) { - promise.then(navigateAction); - } + router.navigateByUrl(getForbiddenRoute(), { skipLocationChange: true }); return false; } else { authService.setRedirectUrl(router.url); diff --git a/src/app/register-page/registration.guard.spec.ts b/src/app/register-page/registration.guard.spec.ts index 0dfa57c7bf..89eaff7a02 100644 --- a/src/app/register-page/registration.guard.spec.ts +++ b/src/app/register-page/registration.guard.spec.ts @@ -17,7 +17,6 @@ describe('RegistrationGuard', () => { let epersonRegistrationService: EpersonRegistrationService; let router: Router; let authService: AuthService; - let location: Location; let registration: Registration; let registrationRD: RemoteData; @@ -61,9 +60,8 @@ describe('RegistrationGuard', () => { isAuthenticated: observableOf(false), setRedirectUrl: {}, }); - location = jasmine.createSpyObj('location', ['replaceState']); - guard = new RegistrationGuard(epersonRegistrationService, router, authService, location); + guard = new RegistrationGuard(epersonRegistrationService, router, authService); }); describe('canActivate', () => { diff --git a/src/app/register-page/registration.guard.ts b/src/app/register-page/registration.guard.ts index 2be3f3fe53..36bbfa4252 100644 --- a/src/app/register-page/registration.guard.ts +++ b/src/app/register-page/registration.guard.ts @@ -18,8 +18,7 @@ import { Location } from '@angular/common'; export class RegistrationGuard implements CanActivate { constructor(private epersonRegistrationService: EpersonRegistrationService, private router: Router, - private authService: AuthService, - private location: Location) { + private authService: AuthService) { } /** @@ -33,10 +32,7 @@ export class RegistrationGuard implements CanActivate { const token = route.params.token; return this.epersonRegistrationService.searchByToken(token).pipe( getFirstCompletedRemoteData(), - // The replaceState call is an action taken by the Promise returned by navigateByUrl within redirectOn4xx - // It ensures the user stays on the correct URL when redirected to a 4xx page - // See this related issue's comment: https://github.com/angular/angular/issues/16981#issuecomment-549330207 - redirectOn4xx(this.router, this.authService, () => this.location.replaceState(state.url)), + redirectOn4xx(this.router, this.authService), map((rd) => { route.data = { ...route.data, registration: rd }; return rd.hasSucceeded; From 1f808f009685797ab107d51cb40a3733834b27c4 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Fri, 4 Mar 2022 10:19:09 +0100 Subject: [PATCH 031/160] 87242: Fix workspace/workflow confusion in spec names --- .../submission/sections/form/section-form.component.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/submission/sections/form/section-form.component.spec.ts b/src/app/submission/sections/form/section-form.component.spec.ts index 09fd6fa991..7dea922b6b 100644 --- a/src/app/submission/sections/form/section-form.component.spec.ts +++ b/src/app/submission/sections/form/section-form.component.spec.ts @@ -364,11 +364,11 @@ describe('SubmissionSectionFormComponent test suite', () => { expect((comp as any).inCurrentSubmissionScope('dc.title')).toBe(true); }); - it('should return true for fields scoped to workflow', () => { + it('should return true for fields scoped to workspace', () => { expect((comp as any).inCurrentSubmissionScope('scoped.workspace')).toBe(true); }); - it('should return false for fields scoped to workspace', () => { + it('should return false for fields scoped to workflow', () => { expect((comp as any).inCurrentSubmissionScope('scoped.workflow')).toBe(false); }); }); From fe0e414343aa7c16dc9599ae051d6f0b550908f6 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 8 Feb 2022 15:13:19 -0600 Subject: [PATCH 032/160] Upgrade Cypress --- package.json | 2 +- yarn.lock | 46 ++++++++++++++++++++-------------------------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 99ab1d2e07..5e98af53dd 100644 --- a/package.json +++ b/package.json @@ -147,7 +147,7 @@ "cross-env": "^7.0.3", "css-loader": "3.4.0", "cssnano": "^4.1.10", - "cypress": "8.6.0", + "cypress": "9.5.1", "cypress-axe": "^0.13.0", "debug-loader": "^0.0.1", "deep-freeze": "0.0.1", diff --git a/yarn.lock b/yarn.lock index 420ff76478..c1903163dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1451,7 +1451,7 @@ resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== -"@cypress/request@^2.88.6": +"@cypress/request@^2.88.10": version "2.88.10" resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce" integrity sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg== @@ -2190,10 +2190,10 @@ "@types/mime" "^1" "@types/node" "*" -"@types/sinonjs__fake-timers@^6.0.2": - version "6.0.4" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.4.tgz#0ecc1b9259b76598ef01942f547904ce61a6a77d" - integrity sha512-IFQTJARgMUBF+xVd2b+hIgXWrZEjND3vJtRCvIelcFB5SIXfjV4bOHbHJ0eXKh+0COrBRc8MqteKAz/j88rE0A== +"@types/sinonjs__fake-timers@8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3" + integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== "@types/sizzle@^2.3.2": version "2.3.3" @@ -3456,7 +3456,7 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.5.0: +buffer@^5.5.0, buffer@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -3883,15 +3883,14 @@ cli-spinners@^2.5.0: resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== -cli-table3@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee" - integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ== +cli-table3@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.1.tgz#36ce9b7af4847f288d3cdd081fbd09bf7bd237b8" + integrity sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA== dependencies: - object-assign "^4.1.0" string-width "^4.2.0" optionalDependencies: - colors "^1.1.2" + colors "1.4.0" cli-truncate@^2.1.0: version "2.1.0" @@ -4951,24 +4950,25 @@ cypress-axe@^0.13.0: resolved "https://registry.yarnpkg.com/cypress-axe/-/cypress-axe-0.13.0.tgz#3234e1a79a27701f2451fcf2f333eb74204c7966" integrity sha512-fCIy7RiDCm7t30U3C99gGwQrUO307EYE1QqXNaf9ToK4DVqW8y5on+0a/kUHMrHdlls2rENF6TN9ZPpPpwLrnw== -cypress@8.6.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-8.6.0.tgz#8d02fa58878b37cfc45bbfce393aa974fa8a8e22" - integrity sha512-F7qEK/6Go5FsqTueR+0wEw2vOVKNgk5847Mys8vsWkzPoEKdxs+7N9Y1dit+zhaZCLtMPyrMwjfA53ZFy+lSww== +cypress@9.5.1: + version "9.5.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.5.1.tgz#51162f3688cedf5ffce311b914ef49a7c1ece076" + integrity sha512-H7lUWB3Svr44gz1rNnj941xmdsCljXoJa2cDneAltjI9leKLMQLm30x6jLlpQ730tiVtIbW5HdUmBzPzwzfUQg== dependencies: - "@cypress/request" "^2.88.6" + "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" "@types/node" "^14.14.31" - "@types/sinonjs__fake-timers" "^6.0.2" + "@types/sinonjs__fake-timers" "8.1.1" "@types/sizzle" "^2.3.2" arch "^2.2.0" blob-util "^2.0.2" bluebird "^3.7.2" + buffer "^5.6.0" cachedir "^2.3.0" chalk "^4.1.0" check-more-types "^2.24.0" cli-cursor "^3.1.0" - cli-table3 "~0.6.0" + cli-table3 "~0.6.1" commander "^5.1.0" common-tags "^1.8.0" dayjs "^1.10.4" @@ -4991,12 +4991,11 @@ cypress@8.6.0: ospath "^1.2.2" pretty-bytes "^5.6.0" proxy-from-env "1.0.0" - ramda "~0.27.1" request-progress "^3.0.0" + semver "^7.3.2" supports-color "^8.1.1" tmp "~0.2.1" untildify "^4.0.0" - url "^0.11.0" yauzl "^2.10.0" d@1, d@^1.0.1: @@ -11611,11 +11610,6 @@ raf-schd@^4.0.2: resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a" integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ== -ramda@~0.27.1: - version "0.27.1" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" - integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== - randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" From 53042179f00d375465bb75ba03d3a7cb289127ed Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 9 Feb 2022 14:03:50 -0600 Subject: [PATCH 033/160] Add Submission e2e tests, enhance MyDSpace tests. Minor cleanup to support tests --- cypress.json | 5 +- cypress/integration/my-dspace.spec.ts | 52 +++++-- cypress/integration/submission.spec.ts | 134 ++++++++++++++++++ cypress/support/index.ts | 6 +- .../date-picker/date-picker.component.html | 3 + .../workspaceitem-actions.component.html | 8 +- .../number-picker.component.html | 3 +- .../number-picker.component.spec.ts | 1 + .../number-picker/number-picker.component.ts | 2 +- .../submission-form-footer.component.html | 6 +- 10 files changed, 200 insertions(+), 20 deletions(-) create mode 100644 cypress/integration/submission.spec.ts diff --git a/cypress.json b/cypress.json index 725f02fcb7..fe74ce3ff9 100644 --- a/cypress.json +++ b/cypress.json @@ -16,8 +16,9 @@ "DSPACE_TEST_ADMIN_PASSWORD": "dspace", "DSPACE_TEST_COMMUNITY": "0958c910-2037-42a9-81c7-dca80e3892b4", "DSPACE_TEST_COLLECTION": "282164f5-d325-4740-8dd1-fa4d6d3e7200", - "DSPACE_TEST_COLLECTION_NAME": "Sample Collection", "DSPACE_TEST_ENTITY_PUBLICATION": "e98b0f27-5c19-49a0-960d-eb6ad5287067", - "DSPACE_TEST_SEARCH_TERM": "test" + "DSPACE_TEST_SEARCH_TERM": "test", + "DSPACE_TEST_SUBMIT_COLLECTION_NAME": "Sample Collection", + "DSPACE_TEST_SUBMIT_COLLECTION_UUID": "9d8334e9-25d3-4a67-9cea-3dffdef80144" } } \ No newline at end of file diff --git a/cypress/integration/my-dspace.spec.ts b/cypress/integration/my-dspace.spec.ts index 970255dd5a..6222d30cfb 100644 --- a/cypress/integration/my-dspace.spec.ts +++ b/cypress/integration/my-dspace.spec.ts @@ -1,5 +1,5 @@ import { Options } from 'cypress-axe'; -import { TEST_ADMIN_USER, TEST_ADMIN_PASSWORD, TEST_COLLECTION_NAME } from 'cypress/support'; +import { TEST_ADMIN_USER, TEST_ADMIN_PASSWORD, TEST_SUBMIT_COLLECTION_NAME } from 'cypress/support'; import { testA11y } from 'cypress/support/utils'; describe('My DSpace page', () => { @@ -37,7 +37,7 @@ describe('My DSpace page', () => { it('should have a working detailed view that passes accessibility tests', () => { cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); - + cy.visit('/mydspace'); cy.get('ds-my-dspace-page').should('exist'); @@ -59,8 +59,8 @@ describe('My DSpace page', () => { ); }); - - it('should let you start a new submission', () => { + // NOTE: Deleting existing submissions is exercised by submission.spec.ts + it('should let you start a new submission & edit in-progress submissions', () => { cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); cy.visit('/mydspace'); @@ -73,10 +73,10 @@ describe('My DSpace page', () => { cy.get('ds-create-item-parent-selector').should('be.visible'); // Type in a known Collection name in the search box - cy.get('ds-authorized-collection-selector input[type="search"]').type(TEST_COLLECTION_NAME); + cy.get('ds-authorized-collection-selector input[type="search"]').type(TEST_SUBMIT_COLLECTION_NAME); // Click on the button matching that known Collection name - cy.get('ds-authorized-collection-selector button[title="' + TEST_COLLECTION_NAME + '"]').click(); + cy.get('ds-authorized-collection-selector button[title="' + TEST_SUBMIT_COLLECTION_NAME + '"]').click(); // New URL should include /workspaceitems, as we've started a new submission cy.url().should('include', '/workspaceitems'); @@ -84,8 +84,44 @@ describe('My DSpace page', () => { // The Submission edit form tag should be visible cy.get('ds-submission-edit').should('be.visible'); - // A Collection menu button should exist & it's value should be the selected collection - cy.get('#collectionControlsMenuButton span').should('have.text', TEST_COLLECTION_NAME); + // A Collection menu button should exist & its value should be the selected collection + cy.get('#collectionControlsMenuButton span').should('have.text', TEST_SUBMIT_COLLECTION_NAME); + + // Now that we've created a submission, we'll test that we can go back and Edit it. + // Get our Submission URL, to parse out the ID of this new submission + cy.location().then(fullUrl => { + // This will be the full path (/workspaceitems/[id]/edit) + const path = fullUrl.pathname; + // Split on the slashes + const subpaths = path.split('/'); + // Part 2 will be the [id] of the submission + const id = subpaths[2]; + + // Go back to the MyDSpace page + cy.visit('/mydspace'); + + // This is the GET command that will actually run the search + cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results'); + // On MyDSpace, find the submission we just created via its ID + cy.get('[data-e2e="search-box"]').type(id); + cy.get('[data-e2e="search-button"]').click(); + + // Wait for search results to come back from the above GET command + cy.wait('@search-results'); + + // Click the Edit button for this in-progress submission + cy.get('#edit_' + id).click(); + + // Should send us back to the submission form + cy.url().should('include', '/workspaceitems/' + id + '/edit'); + + // Discard our new submission by clicking Discard in Submission form & confirming + cy.get('button#discard').click(); + cy.get('button#discard_submit').click(); + + // Discarding should send us back to MyDSpace + cy.url().should('include', '/mydspace'); + }); }); it('should let you import from external sources', () => { diff --git a/cypress/integration/submission.spec.ts b/cypress/integration/submission.spec.ts new file mode 100644 index 0000000000..9469ee35e1 --- /dev/null +++ b/cypress/integration/submission.spec.ts @@ -0,0 +1,134 @@ +import { Options } from 'cypress-axe'; +import { TEST_ADMIN_USER, TEST_ADMIN_PASSWORD, TEST_SUBMIT_COLLECTION_NAME, TEST_SUBMIT_COLLECTION_UUID } from 'cypress/support'; +import { testA11y } from 'cypress/support/utils'; + +describe('New Submission page', () => { + // NOTE: We already test that new submissions can be started from MyDSpace in my-dspace.spec.ts + + it('should create a new submission when using /submit path', () => { + cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); + + // Test that calling /submit with collection & entityType will create a new submission + cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none'); + + // Should redirect to /workspaceitems, as we've started a new submission + cy.url().should('include', '/workspaceitems'); + + // The Submission edit form tag should be visible + cy.get('ds-submission-edit').should('be.visible'); + + // A Collection menu button should exist & it's value should be the selected collection + cy.get('#collectionControlsMenuButton span').should('have.text', TEST_SUBMIT_COLLECTION_NAME); + + // 4 sections should be visible by default + cy.get('div#section_traditionalpageone').should('be.visible'); + cy.get('div#section_traditionalpagetwo').should('be.visible'); + cy.get('div#section_upload').should('be.visible'); + cy.get('div#section_license').should('be.visible'); + + // Discard button should work + // Clicking it will display a confirmation, which we will confirm with another click + cy.get('button#discard').click(); + cy.get('button#discard_submit').click(); + }); + + it('should block submission & show errors if required fields are missing', () => { + cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); + + // Create a new submission + cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none'); + + // Attempt an immediate deposit without filling out any fields + cy.get('button#deposit').click(); + + // A warning alert should display. + cy.get('ds-notification div.alert-warning').should('be.visible'); + + // First section should have an exclamation error in the header + // (as it has required fields) + cy.get('div#traditionalpageone-header i.fa-exclamation-circle').should('be.visible'); + + // Title field should have class "is-invalid" applied, as it's required + cy.get('input#dc_title').should('have.class', 'is-invalid'); + + // Date Year field should also have "is-valid" class + cy.get('input#dc_date_issued_year').should('have.class', 'is-invalid'); + + // FINALLY, cleanup after ourselves. This also exercises the MyDSpace delete button. + // Get our Submission URL, to parse out the ID of this submission + cy.location().then(fullUrl => { + // This will be the full path (/workspaceitems/[id]/edit) + const path = fullUrl.pathname; + // Split on the slashes + const subpaths = path.split('/'); + // Part 2 will be the [id] of the submission + const id = subpaths[2]; + + // Even though form is incomplete, the "Save for Later" button should still work + cy.get('button#saveForLater').click(); + + // "Save for Later" should send us to MyDSpace + cy.url().should('include', '/mydspace'); + + // A success alert should be visible + cy.get('ds-notification div.alert-success').should('be.visible'); + // Now, dismiss any open alert boxes (may be multiple, as tests run quickly) + cy.get('[data-dismiss="alert"]').click({multiple: true}); + + // This is the GET command that will actually run the search + cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results'); + // On MyDSpace, find the submission we just saved via its ID + cy.get('[data-e2e="search-box"]').type(id); + cy.get('[data-e2e="search-button"]').click(); + + // Wait for search results to come back from the above GET command + cy.wait('@search-results'); + + // Delete our created submission & confirm deletion + cy.get('button#delete_' + id).click(); + cy.get('button#delete_confirm').click(); + }); + }); + + it('should allow for deposit if all required fields completed & file uploaded', () => { + cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); + + // Create a new submission + cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none'); + + // Fill out all required fields (Title, Date) + cy.get('input#dc_title').type('DSpace logo uploaded via e2e tests'); + cy.get('input#dc_date_issued_year').type('2022'); + + // Confirm the required license by checking checkbox + // (NOTE: requires "force:true" cause Cypress claims this checkbox is covered by its own ) + cy.get('input#granted').check( {force: true} ); + + // Before using Cypress drag & drop, we have to manually trigger the "dragover" event. + // This ensures our UI displays the dropzone that covers the entire submission page. + // (For some reason Cypress drag & drop doesn't trigger this even itself & upload won't work without this trigger) + cy.get('ds-uploader').trigger('dragover'); + + // This is the POST command that will upload the file + cy.intercept('POST', '/server/api/submission/workspaceitems/*').as('upload'); + + // Upload our DSpace logo via drag & drop onto submission form + // cy.get('div#section_upload') + cy.get('div.ds-document-drop-zone').selectFile('src/assets/images/dspace-logo.png', { + action: 'drag-drop' + }); + + // Wait for upload to complete before proceeding + cy.wait('@upload'); + // Close the upload success notice + cy.get('[data-dismiss="alert"]').click({multiple: true}); + + // Wait for deposit button to not be disabled & click it. + cy.get('button#deposit').should('not.be.disabled').click(); + + // No warnings should exist. Instead, just successful deposit alert is displayed + cy.get('ds-notification div.alert-warning').should('not.exist'); + cy.get('ds-notification div.alert-success').should('be.visible'); + }); + +}); diff --git a/cypress/support/index.ts b/cypress/support/index.ts index cf93b1fefb..a56ace7d38 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -32,8 +32,8 @@ before(() => { // Borrowed from: https://glebbahmutov.com/blog/visit-blank-page-between-tests/ afterEach(() => { cy.window().then((win) => { - win.location.href = 'about:blank' - }) + win.location.href = 'about:blank'; + }); }); @@ -53,4 +53,4 @@ export const TEST_ENTITY_PUBLICATION = Cypress.env('DSPACE_TEST_ENTITY_PUBLICATI export const TEST_SEARCH_TERM = Cypress.env('DSPACE_TEST_SEARCH_TERM') || 'test'; // Collection used for submission tests export const TEST_SUBMIT_COLLECTION_NAME = Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME') || 'Sample Collection'; -export const TEST_SUBMIT_COLLECTION_UUID = Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID') || '9d8334e9-25d3-4a67-9cea-3dffdef80144'; \ No newline at end of file +export const TEST_SUBMIT_COLLECTION_UUID = Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID') || '9d8334e9-25d3-4a67-9cea-3dffdef80144'; diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component.html index d5421a254f..1046dd6b2d 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.component.html @@ -5,6 +5,7 @@ @@ -6,6 +7,7 @@
@@ -24,7 +26,7 @@

{{'submission.general.discard.confirm.info' | translate}}

diff --git a/src/app/shared/number-picker/number-picker.component.html b/src/app/shared/number-picker/number-picker.component.html index 58b6def50b..9b2ef98925 100644 --- a/src/app/shared/number-picker/number-picker.component.html +++ b/src/app/shared/number-picker/number-picker.component.html @@ -9,6 +9,7 @@ Increment - + + From 8c865b758eba2847056154a3bb20fb38a5bb2523 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 9 Feb 2022 15:07:08 -0600 Subject: [PATCH 034/160] Fix bugs / stability issues with search e2e tests --- cypress/integration/search-navbar.spec.ts | 6 +++--- cypress/integration/search-page.spec.ts | 18 +++++++----------- .../object-grid/object-grid.component.html | 2 +- .../object-list/object-list.component.html | 2 +- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/cypress/integration/search-navbar.spec.ts b/cypress/integration/search-navbar.spec.ts index 3aa8e7248d..211b4836b1 100644 --- a/cypress/integration/search-navbar.spec.ts +++ b/cypress/integration/search-navbar.spec.ts @@ -31,7 +31,7 @@ describe('Search from Navigation Bar', () => { // Wait for search results to come back from the above GET command cy.wait('@search-results'); // At least one search result should be displayed - cy.get('ds-item-search-result-list-element').should('be.visible'); + cy.get('[data-e2e="list-object"]').should('be.visible'); }); it('should go to search page with correct query if submitted (from search)', () => { @@ -46,7 +46,7 @@ describe('Search from Navigation Bar', () => { // Wait for search results to come back from the above GET command cy.wait('@search-results'); // At least one search result should be displayed - cy.get('ds-item-search-result-list-element').should('be.visible'); + cy.get('[data-e2e="list-object"]').should('be.visible'); }); it('should allow user to also submit query by clicking icon', () => { @@ -61,6 +61,6 @@ describe('Search from Navigation Bar', () => { // Wait for search results to come back from the above GET command cy.wait('@search-results'); // At least one search result should be displayed - cy.get('ds-item-search-result-list-element').should('be.visible'); + cy.get('[data-e2e="list-object"]').should('be.visible'); }); }); diff --git a/cypress/integration/search-page.spec.ts b/cypress/integration/search-page.spec.ts index cbd518b97c..981c9365aa 100644 --- a/cypress/integration/search-page.spec.ts +++ b/cypress/integration/search-page.spec.ts @@ -1,13 +1,8 @@ import { Options } from 'cypress-axe'; +import { TEST_SEARCH_TERM } from 'cypress/support'; import { testA11y } from 'cypress/support/utils'; describe('Search Page', () => { - it('should contain query value when navigating to page with query parameter', () => { - const queryString = 'test query'; - cy.visit('/search?query=' + queryString); - cy.get('[data-e2e="search-box"]').should('have.value', queryString); - }); - it('should redirect to the correct url when query was set and submit button was triggered', () => { const queryString = 'Another interesting query string'; cy.visit('/search'); @@ -18,13 +13,14 @@ describe('Search Page', () => { }); it('should load results and pass accessibility tests', () => { - cy.visit('/search'); + cy.visit('/search?query=' + TEST_SEARCH_TERM); + cy.get('[data-e2e="search-box"]').should('have.value', TEST_SEARCH_TERM); // tag must be loaded cy.get('ds-search-page').should('exist'); // At least one search result should be displayed - cy.get('ds-item-search-result-list-element').should('be.visible'); + cy.get('[data-e2e="list-object"]').should('be.visible'); // Click each filter toggle to open *every* filter // (As we want to scan filter section for accessibility issues as well) @@ -49,7 +45,7 @@ describe('Search Page', () => { }); it('should have a working grid view that passes accessibility tests', () => { - cy.visit('/search'); + cy.visit('/search?query=' + TEST_SEARCH_TERM); // Click button in sidebar to display grid view cy.get('ds-search-sidebar [data-e2e="grid-view"]').click(); @@ -57,8 +53,8 @@ describe('Search Page', () => { // tag must be loaded cy.get('ds-search-page').should('exist'); - // At least one grid element (card) should be displayed - cy.get('ds-item-search-result-grid-element').should('be.visible'); + // At least one grid object (card) should be displayed + cy.get('[data-e2e="grid-object"]').should('be.visible'); // Analyze for accessibility issues testA11y('ds-search-page', diff --git a/src/app/shared/object-grid/object-grid.component.html b/src/app/shared/object-grid/object-grid.component.html index 0afd623d86..296f190d2d 100644 --- a/src/app/shared/object-grid/object-grid.component.html +++ b/src/app/shared/object-grid/object-grid.component.html @@ -13,7 +13,7 @@ (paginationChange)="onPaginationChange($event)">
-
+
diff --git a/src/app/shared/object-list/object-list.component.html b/src/app/shared/object-list/object-list.component.html index 331ff1cb28..53a62ca1f5 100644 --- a/src/app/shared/object-list/object-list.component.html +++ b/src/app/shared/object-list/object-list.component.html @@ -12,7 +12,7 @@ (sortFieldChange)="onSortFieldChange($event)" (paginationChange)="onPaginationChange($event)">
    -
  • +
  • Date: Mon, 14 Feb 2022 11:28:08 -0600 Subject: [PATCH 035/160] Update submission tests to use Demo Submitter. Switch Docker scripts to use updated `dspace7-entities-data.sql` which now includes demo in-progress submissions --- cypress.json | 4 +++- cypress/integration/my-dspace.spec.ts | 22 ++++++++++++++-------- cypress/integration/submission.spec.ts | 10 +++++----- cypress/support/index.ts | 2 ++ docker/cli.assetstore.yml | 2 +- docker/db.entities.yml | 2 +- docker/docker-compose-ci.yml | 2 +- 7 files changed, 27 insertions(+), 17 deletions(-) diff --git a/cypress.json b/cypress.json index fe74ce3ff9..c0b5a56a15 100644 --- a/cypress.json +++ b/cypress.json @@ -19,6 +19,8 @@ "DSPACE_TEST_ENTITY_PUBLICATION": "e98b0f27-5c19-49a0-960d-eb6ad5287067", "DSPACE_TEST_SEARCH_TERM": "test", "DSPACE_TEST_SUBMIT_COLLECTION_NAME": "Sample Collection", - "DSPACE_TEST_SUBMIT_COLLECTION_UUID": "9d8334e9-25d3-4a67-9cea-3dffdef80144" + "DSPACE_TEST_SUBMIT_COLLECTION_UUID": "9d8334e9-25d3-4a67-9cea-3dffdef80144", + "DSPACE_TEST_SUBMIT_USER": "dspacedemo+submit@gmail.com", + "DSPACE_TEST_SUBMIT_USER_PASSWORD": "dspace" } } \ No newline at end of file diff --git a/cypress/integration/my-dspace.spec.ts b/cypress/integration/my-dspace.spec.ts index 6222d30cfb..da78ff8da6 100644 --- a/cypress/integration/my-dspace.spec.ts +++ b/cypress/integration/my-dspace.spec.ts @@ -1,17 +1,17 @@ import { Options } from 'cypress-axe'; -import { TEST_ADMIN_USER, TEST_ADMIN_PASSWORD, TEST_SUBMIT_COLLECTION_NAME } from 'cypress/support'; +import { TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD, TEST_SUBMIT_COLLECTION_NAME } from 'cypress/support'; import { testA11y } from 'cypress/support/utils'; describe('My DSpace page', () => { it('should display recent submissions and pass accessibility tests', () => { - cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); + cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD); cy.visit('/mydspace'); cy.get('ds-my-dspace-page').should('exist'); // At least one recent submission should be displayed - cy.get('ds-item-search-result-list-element-submission').should('be.visible'); + cy.get('[data-e2e="list-object"]').should('be.visible'); // Click each filter toggle to open *every* filter // (As we want to scan filter section for accessibility issues as well) @@ -36,7 +36,7 @@ describe('My DSpace page', () => { }); it('should have a working detailed view that passes accessibility tests', () => { - cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); + cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD); cy.visit('/mydspace'); @@ -61,7 +61,7 @@ describe('My DSpace page', () => { // NOTE: Deleting existing submissions is exercised by submission.spec.ts it('should let you start a new submission & edit in-progress submissions', () => { - cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); + cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD); cy.visit('/mydspace'); // Open the New Submission dropdown @@ -97,8 +97,14 @@ describe('My DSpace page', () => { // Part 2 will be the [id] of the submission const id = subpaths[2]; - // Go back to the MyDSpace page - cy.visit('/mydspace'); + // Click the "Save for Later" button to save this submission + cy.get('button#saveForLater').click(); + + // "Save for Later" should send us to MyDSpace + cy.url().should('include', '/mydspace'); + + // Close any open notifications, to make sure they don't get in the way of next steps + cy.get('[data-dismiss="alert"]').click({multiple: true}); // This is the GET command that will actually run the search cy.intercept('GET', '/server/api/discover/search/objects*').as('search-results'); @@ -125,7 +131,7 @@ describe('My DSpace page', () => { }); it('should let you import from external sources', () => { - cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); + cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD); cy.visit('/mydspace'); // Open the New Import dropdown diff --git a/cypress/integration/submission.spec.ts b/cypress/integration/submission.spec.ts index 9469ee35e1..4c7758f8d6 100644 --- a/cypress/integration/submission.spec.ts +++ b/cypress/integration/submission.spec.ts @@ -1,12 +1,12 @@ import { Options } from 'cypress-axe'; -import { TEST_ADMIN_USER, TEST_ADMIN_PASSWORD, TEST_SUBMIT_COLLECTION_NAME, TEST_SUBMIT_COLLECTION_UUID } from 'cypress/support'; +import { TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD, TEST_SUBMIT_COLLECTION_NAME, TEST_SUBMIT_COLLECTION_UUID } from 'cypress/support'; import { testA11y } from 'cypress/support/utils'; describe('New Submission page', () => { // NOTE: We already test that new submissions can be started from MyDSpace in my-dspace.spec.ts - it('should create a new submission when using /submit path', () => { - cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); + it('should create a new submission when using /submit path & pass accessibility', () => { + cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD); // Test that calling /submit with collection & entityType will create a new submission cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none'); @@ -33,7 +33,7 @@ describe('New Submission page', () => { }); it('should block submission & show errors if required fields are missing', () => { - cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); + cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD); // Create a new submission cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none'); @@ -91,7 +91,7 @@ describe('New Submission page', () => { }); it('should allow for deposit if all required fields completed & file uploaded', () => { - cy.login(TEST_ADMIN_USER, TEST_ADMIN_PASSWORD); + cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD); // Create a new submission cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none'); diff --git a/cypress/support/index.ts b/cypress/support/index.ts index a56ace7d38..8edfdedde3 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -54,3 +54,5 @@ export const TEST_SEARCH_TERM = Cypress.env('DSPACE_TEST_SEARCH_TERM') || 'test' // Collection used for submission tests export const TEST_SUBMIT_COLLECTION_NAME = Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_NAME') || 'Sample Collection'; export const TEST_SUBMIT_COLLECTION_UUID = Cypress.env('DSPACE_TEST_SUBMIT_COLLECTION_UUID') || '9d8334e9-25d3-4a67-9cea-3dffdef80144'; +export const TEST_SUBMIT_USER = Cypress.env('DSPACE_TEST_SUBMIT_USER') || 'dspacedemo+submit@gmail.com'; +export const TEST_SUBMIT_USER_PASSWORD = Cypress.env('DSPACE_TEST_SUBMIT_USER_PASSWORD') || 'dspace'; diff --git a/docker/cli.assetstore.yml b/docker/cli.assetstore.yml index c2846286d7..40e4974c7c 100644 --- a/docker/cli.assetstore.yml +++ b/docker/cli.assetstore.yml @@ -35,6 +35,6 @@ services: tar xvfz /tmp/assetstore.tar.gz fi - /dspace/bin/dspace index-discovery + /dspace/bin/dspace index-discovery -b /dspace/bin/dspace oai import /dspace/bin/dspace oai clean-cache diff --git a/docker/db.entities.yml b/docker/db.entities.yml index 818d14877c..d1dfdf4a26 100644 --- a/docker/db.entities.yml +++ b/docker/db.entities.yml @@ -20,7 +20,7 @@ services: environment: # This LOADSQL should be kept in sync with the URL in DSpace/DSpace # This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data - - LOADSQL=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-2021-04-14.sql + - LOADSQL=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql dspace: ### OVERRIDE default 'entrypoint' in 'docker-compose-rest.yml' #### # Ensure that the database is ready BEFORE starting tomcat diff --git a/docker/docker-compose-ci.yml b/docker/docker-compose-ci.yml index a895314a17..3bd8f52630 100644 --- a/docker/docker-compose-ci.yml +++ b/docker/docker-compose-ci.yml @@ -63,7 +63,7 @@ services: # This LOADSQL should be kept in sync with the LOADSQL in # https://github.com/DSpace/DSpace/blob/main/dspace/src/main/docker-compose/db.entities.yml # This SQL is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data - LOADSQL: https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-2021-04-14.sql + LOADSQL: https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/dspace7-entities-data.sql PGDATA: /pgdata image: dspace/dspace-postgres-pgcrypto:loadsql networks: From 852da27c2c9e0ac8bebe0a4310d54e55bd934020 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 4 Mar 2022 12:49:55 -0600 Subject: [PATCH 036/160] Update Cypress to read REST API URL dynamically from config.json --- cypress.json | 1 - cypress/support/commands.ts | 69 +++++++++++++++++++++++-------------- cypress/support/index.ts | 7 +++- 3 files changed, 50 insertions(+), 27 deletions(-) diff --git a/cypress.json b/cypress.json index c0b5a56a15..80358eb6dd 100644 --- a/cypress.json +++ b/cypress.json @@ -11,7 +11,6 @@ "openMode": 0 }, "env": { - "DSPACE_TEST_REST_BASE_URL": "http://localhost:8080", "DSPACE_TEST_ADMIN_USER": "dspacedemo+admin@gmail.com", "DSPACE_TEST_ADMIN_PASSWORD": "dspace", "DSPACE_TEST_COMMUNITY": "0958c910-2037-42a9-81c7-dca80e3892b4", diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index dcac5aaaac..d66e1ef0bd 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -4,7 +4,7 @@ // *********************************************** import { AuthTokenInfo, TOKENITEM } from 'src/app/core/auth/models/auth-token-info.model'; -import { TEST_REST_BASE_URL } from '.'; +import { FALLBACK_TEST_REST_BASE_URL } from '.'; // Declare Cypress namespace to help with Intellisense & code completion in IDEs // ALL custom commands MUST be listed here for code completion to work @@ -30,34 +30,53 @@ declare global { * @param password password to login as */ function login(email: string, password: string): void { - // To login via REST, first we have to do a GET to obtain a valid CSRF token - cy.request( TEST_REST_BASE_URL + '/server/api/authn/status' ) - .then((response) => { - // We should receive a CSRF token returned in a response header - expect(response.headers).to.have.property('dspace-xsrf-token'); - const csrfToken = response.headers['dspace-xsrf-token']; + // Cypress doesn't have access to the running application in Node.js. + // So, it's not possible to inject or load the AppConfig or environment of the Angular UI. + // Instead, we'll read our running application's config.json, which contains the configs & + // is regenerated at runtime each time the Angular UI application starts up. + cy.readFile('./src/assets/config.json').then((str) => { + // Parse JSON file into a JSON object + const config = JSON.parse(JSON.stringify(str)); - // Now, send login POST request including that CSRF token - cy.request({ - method: 'POST', - url: TEST_REST_BASE_URL + '/server/api/authn/login', - headers: { 'X-XSRF-TOKEN' : csrfToken}, - form: true, // indicates the body should be form urlencoded - body: { user: email, password: password } - }).then((resp) => { - // We expect a successful login - expect(resp.status).to.eq(200); - // We expect to have a valid authorization header returned (with our auth token) - expect(resp.headers).to.have.property('authorization'); + // Find the URL of our REST API. Have a fallback ready, just in case 'rest.baseUrl' cannot be found. + let baseRestUrl = FALLBACK_TEST_REST_BASE_URL; + if (!config.rest.baseUrl) { + console.warn("Could not load 'rest.baseUrl' from config.json. Falling back to " + FALLBACK_TEST_REST_BASE_URL); + } else { + console.log("Found 'rest.baseUrl' in config.json. Using this REST API for login: " + config.rest.baseUrl); + baseRestUrl = config.rest.baseUrl; + } - // Initialize our AuthTokenInfo object from the authorization header. - const authheader = resp.headers.authorization as string; - const authinfo: AuthTokenInfo = new AuthTokenInfo(authheader); + // To login via REST, first we have to do a GET to obtain a valid CSRF token + cy.request( baseRestUrl + '/api/authn/status' ) + .then((response) => { + // We should receive a CSRF token returned in a response header + expect(response.headers).to.have.property('dspace-xsrf-token'); + const csrfToken = response.headers['dspace-xsrf-token']; - // Save our AuthTokenInfo object to our dsAuthInfo UI cookie - // This ensures the UI will recognize we are logged in on next "visit()" - cy.setCookie(TOKENITEM, JSON.stringify(authinfo)); + // Now, send login POST request including that CSRF token + cy.request({ + method: 'POST', + url: baseRestUrl + '/api/authn/login', + headers: { 'X-XSRF-TOKEN' : csrfToken}, + form: true, // indicates the body should be form urlencoded + body: { user: email, password: password } + }).then((resp) => { + // We expect a successful login + expect(resp.status).to.eq(200); + // We expect to have a valid authorization header returned (with our auth token) + expect(resp.headers).to.have.property('authorization'); + + // Initialize our AuthTokenInfo object from the authorization header. + const authheader = resp.headers.authorization as string; + const authinfo: AuthTokenInfo = new AuthTokenInfo(authheader); + + // Save our AuthTokenInfo object to our dsAuthInfo UI cookie + // This ensures the UI will recognize we are logged in on next "visit()" + cy.setCookie(TOKENITEM, JSON.stringify(authinfo)); + }); }); + }); } // Add as a Cypress command (i.e. assign to 'cy.login') diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 8edfdedde3..d9b6409a0d 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -42,7 +42,12 @@ afterEach(() => { // Default values listed here are all valid for the Demo Entities Data set available at // https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data // (This is the data set used in our CI environment) -export const TEST_REST_BASE_URL = Cypress.env('DSPACE_TEST_REST_BASE_URL') || 'http://localhost:8080'; + +// NOTE: FALLBACK_TEST_REST_BASE_URL is only used if Cypress cannot read the REST API BaseURL +// from the Angular UI's config.json. See 'getBaseRESTUrl()' in commands.ts +export const FALLBACK_TEST_REST_BASE_URL = 'http://localhost:8080/server'; + +// Admin account used for administrative tests export const TEST_ADMIN_USER = Cypress.env('DSPACE_TEST_ADMIN_USER') || 'dspacedemo+admin@gmail.com'; export const TEST_ADMIN_PASSWORD = Cypress.env('DSPACE_TEST_ADMIN_PASSWORD') || 'dspace'; // Community/collection/publication used for view/edit tests From a0e2ac3d122bab4bb3dd092aa73195db154ca930 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 4 Mar 2022 12:50:21 -0600 Subject: [PATCH 037/160] Update README with clearer instructions on running 'ng e2e' --- README.md | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 74010f3c5c..d9c911d30a 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ After you have installed all dependencies you can now run the app. Run `yarn run When building for production we're using Ahead of Time (AoT) compilation. With AoT, the browser downloads a pre-compiled version of the application, so it can render the application immediately, without waiting to compile the app first. The compiler is roughly half the size of Angular itself, so omitting it dramatically reduces the application payload. -To build the app for production and start the server run: +To build the app for production and start the server (in one command) run: ```bash yarn start @@ -207,6 +207,10 @@ yarn run build:prod ``` This will build the application and put the result in the `dist` folder. You can copy this folder to wherever you need it for your application server. If you will be using the built-in Express server, you'll also need a copy of the `node_modules` folder tucked inside your copy of `dist`. +After building the app for production, it can be started by running: +```bash +yarn run serve:ssr +``` ### Running the application with Docker NOTE: At this time, we do not have production-ready Docker images for DSpace. @@ -268,11 +272,29 @@ E2E tests (aka integration tests) use [Cypress.io](https://www.cypress.io/). Con The test files can be found in the `./cypress/integration/` folder. -Before you can run e2e tests, two things are required: -1. You MUST have a running backend (i.e. REST API). By default, the e2e tests look for this at http://localhost:8080/server/ or whatever `rest` backend is defined in your `config.prod.yml` or `config.yml`. You may override this using env variables, see [Configuring](#configuring). -2. Your backend MUST include our Entities Test Data set. Some tests run against a (currently hardcoded) Community/Collection/Item UUID. These UUIDs are all valid for our Entities Test Data set. The Entities Test Data set may be installed easily via Docker, see https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker-compose#ingest-option-2-ingest-entities-test-data +Before you can run e2e tests, two things are REQUIRED: +1. You MUST be running the DSpace backend (i.e. REST API) locally. The e2e tests will *NOT* succeed if run against our demo REST API (https://api7.dspace.org/server/), as that server is uncontrolled and may have content added/removed at any time. + * After starting up your backend on localhost, make sure either your `config.prod.yml` or `config.dev.yml` has its `rest` settings defined to use that localhost backend. + * If you'd prefer, you may instead use environment variables as described at [Configuring](#configuring). For example: + ``` + DSPACE_REST_SSL = false + DSPACE_REST_HOST = localhost + DSPACE_REST_PORT = 8080 + ``` +2. Your backend MUST include our [Entities Test Data set](https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data). Some tests run against a specific Community/Collection/Item UUID. These UUIDs are all valid for our Entities Test Data set. + * (Recommended) The Entities Test Data set may be installed easily via Docker, see https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker-compose#ingest-option-2-ingest-entities-test-data + * Alternatively, the Entities Test Data set may be installed via a simple SQL import (e. g. `psql -U dspace < dspace7-entities-data.sql`). See instructions in link above. -Run `ng e2e` to kick off the tests. This will start Cypress and allow you to select the browser you wish to use, as well as whether you wish to run all tests or an individual test file. Once you click run on test(s), this opens the [Cypress Test Runner](https://docs.cypress.io/guides/core-concepts/test-runner) to run your test(s) and show you the results. +After performing the above setup, you can run the e2e tests using +``` +ng e2e +```` +NOTE: By default these tests will run against the REST API backend configured via environment variables or in `config.prod.yml`. If you'd rather it use `config.dev.yml`, just set the NODE_ENV environment variable like this: +``` +NODE_ENV=development ng e2e +``` + +The `ng e2e` command will start Cypress and allow you to select the browser you wish to use, as well as whether you wish to run all tests or an individual test file. Once you click run on test(s), this opens the [Cypress Test Runner](https://docs.cypress.io/guides/core-concepts/test-runner) to run your test(s) and show you the results. #### Writing E2E Tests From bc705df1444b7d5c8de548b089bd55de06536ab1 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 4 Mar 2022 17:02:42 -0600 Subject: [PATCH 038/160] Check two locations for config.json, as it's in a different location in CI --- cypress/plugins/index.ts | 23 +++++++++++++++++++++-- cypress/support/commands.ts | 6 +++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/cypress/plugins/index.ts b/cypress/plugins/index.ts index c6eb874232..ead38afb92 100644 --- a/cypress/plugins/index.ts +++ b/cypress/plugins/index.ts @@ -1,15 +1,34 @@ +const fs = require('fs'); + // Plugins enable you to tap into, modify, or extend the internal behavior of Cypress // For more info, visit https://on.cypress.io/plugins-api module.exports = (on, config) => { - // Define "log" and "table" tasks, used for logging accessibility errors during CI - // Borrowed from https://github.com/component-driven/cypress-axe#in-cypress-plugins-file on('task', { + // Define "log" and "table" tasks, used for logging accessibility errors during CI + // Borrowed from https://github.com/component-driven/cypress-axe#in-cypress-plugins-file log(message: string) { console.log(message); return null; }, table(message: string) { console.table(message); + return null; + }, + // Cypress doesn't have access to the running application in Node.js. + // So, it's not possible to inject or load the AppConfig or environment of the Angular UI. + // Instead, we'll read our running application's config.json, which contains the configs & + // is regenerated at runtime each time the Angular UI application starts up. + readUIConfig() { + // Check if we have a config.json in the src/assets. If so, use that. + // This is where it's written when running "ng e2e" or "yarn serve" + if (fs.existsSync('./src/assets/config.json')) { + return fs.readFileSync('./src/assets/config.json', 'utf8'); + // Otherwise, check the dist/browser/assets + // This is where it's written when running "serve:ssr", which is what CI uses to start the frontend + } else if (fs.existsSync('./dist/browser/assets/config.json')) { + return fs.readFileSync('./dist/browser/assets/config.json', 'utf8'); + } + return null; } }); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index d66e1ef0bd..30951d46f1 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -34,9 +34,9 @@ function login(email: string, password: string): void { // So, it's not possible to inject or load the AppConfig or environment of the Angular UI. // Instead, we'll read our running application's config.json, which contains the configs & // is regenerated at runtime each time the Angular UI application starts up. - cy.readFile('./src/assets/config.json').then((str) => { - // Parse JSON file into a JSON object - const config = JSON.parse(JSON.stringify(str)); + cy.task('readUIConfig').then((str: string) => { + // Parse config into a JSON object + const config = JSON.parse(str); // Find the URL of our REST API. Have a fallback ready, just in case 'rest.baseUrl' cannot be found. let baseRestUrl = FALLBACK_TEST_REST_BASE_URL; From 59f03817b36d51a5be77df0508ec097926a3e9f8 Mon Sep 17 00:00:00 2001 From: YPaulsen-TLC Date: Mon, 20 Dec 2021 12:58:51 +0100 Subject: [PATCH 039/160] DSpace translation German translations of DSpace 7 message keys. --- src/assets/i18n/de.json5 | 100 +++++++++++++-------------------------- 1 file changed, 33 insertions(+), 67 deletions(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index b414852bcd..7edfd27993 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -768,31 +768,28 @@ // "bitstream.edit.form.embargo.hint": "The first day from which access is allowed. This date cannot be modified on this form. To set an embargo date for a bitstream, go to the Item Status tab, click Authorizations..., create or edit the bitstream's READ policy, and set the Start Date as desired.", // TODO New key - Add a translation "bitstream.edit.form.embargo.hint": "The first day from which access is allowed. This date cannot be modified on this form. To set an embargo date for a bitstream, go to the Item Status tab, click Authorizations..., create or edit the bitstream's READ policy, and set the Start Date as desired.", + // Der erste Tag, ab wann der Zugriff möglich ist. Dieses Datum kann in dieser Form nicht verändert werden. Um eine Embargo-Frist für eine Datei festzulegen, zu dem ()-Tab gehen, auf () klicken, die ()-Richtline für die Datei erstellen oder verändern und das Startdatum wie gewünscht einstellen. // "bitstream.edit.form.embargo.label": "Embargo until specific date", "bitstream.edit.form.embargo.label": "Embargo bis zu einem bestimmten Datum", // "bitstream.edit.form.fileName.hint": "Change the filename for the bitstream. Note that this will change the display bitstream URL, but old links will still resolve as long as the sequence ID does not change.", - // TODO New key - Add a translation - "bitstream.edit.form.fileName.hint": "Change the filename for the bitstream. Note that this will change the display bitstream URL, but old links will still resolve as long as the sequence ID does not change.", + "bitstream.edit.form.fileName.hint": "Ändern Sie den Dateinamen für die Datei. Beachten Sie, dass sich dadurch die angezeigte Datei-URL ändert, aber alte Links weiterhin funktionieren, solange sich die Sequenz-ID nicht ändert.", // "bitstream.edit.form.fileName.label": "Filename", "bitstream.edit.form.fileName.label": "Dateiname", // "bitstream.edit.form.newFormat.label": "Describe new format", - // TODO New key - Add a translation - "bitstream.edit.form.newFormat.label": "Describe new format", + "bitstream.edit.form.newFormat.label": "Neues Format beschreiben", // "bitstream.edit.form.newFormat.hint": "The application you used to create the file, and the version number (for example, \"ACMESoft SuperApp version 1.5\").", - // TODO New key - Add a translation - "bitstream.edit.form.newFormat.hint": "The application you used to create the file, and the version number (for example, \"ACMESoft SuperApp version 1.5\").", + "bitstream.edit.form.newFormat.hint": "Die Anwendung, mit der Sie die Datei erstellt haben, und die Versionsnummer (zum Beispiel, \"ACMESoft SuperApp version 1.5\").", // "bitstream.edit.form.primaryBitstream.label": "Primary bitstream", "bitstream.edit.form.primaryBitstream.label": "Primäre Datei", // "bitstream.edit.form.selectedFormat.hint": "If the format is not in the above list, select \"format not in list\" above and describe it under \"Describe new format\".", - // TODO New key - Add a translation - "bitstream.edit.form.selectedFormat.hint": "If the format is not in the above list, select \"format not in list\" above and describe it under \"Describe new format\".", + "bitstream.edit.form.selectedFormat.hint": "Wenn das Format nicht in der obigen Liste enthalten ist, wählen Sie \"Format in der Liste\" und beschreiben Sie es unter \"Neues Format beschreiben\".", // "bitstream.edit.form.selectedFormat.label": "Selected Format", "bitstream.edit.form.selectedFormat.label": "Ausgewähltes Format", @@ -1153,23 +1150,19 @@ // "collection.edit.template.head": "Edit Template Item for Collection \"{{ collection }}\"", // TODO New key - Add a translation - "collection.edit.template.head": "Edit Template Item for Collection \"{{ collection }}\"", + "collection.edit.template.head": "Itemvorlage für Collection \"{{ collection }}\" bearbeiten", // "collection.edit.template.label": "Template item", - // TODO New key - Add a translation - "collection.edit.template.label": "Template item", + "collection.edit.template.label": "Itemvorlage", // "collection.edit.template.notifications.delete.error": "Failed to delete the item template", - // TODO New key - Add a translation - "collection.edit.template.notifications.delete.error": "Failed to delete the item template", + "collection.edit.template.notifications.delete.error": "Löschen der Itemvorlage fehlgeschlagen", // "collection.edit.template.notifications.delete.success": "Successfully deleted the item template", - // TODO New key - Add a translation - "collection.edit.template.notifications.delete.success": "Successfully deleted the item template", + "collection.edit.template.notifications.delete.success": "Die Itemvorlage wurde erfolgreich gelöscht", // "collection.edit.template.title": "Edit Template Item", - // TODO New key - Add a translation - "collection.edit.template.title": "Edit Template Item", + "collection.edit.template.title": "Itemvorlage bearbeiten", @@ -1393,8 +1386,7 @@ // "comcol-role.edit.submitters.name": "Submitters", - // TODO New key - Add a translation - "comcol-role.edit.submitters.name": "Submitters", + "comcol-role.edit.submitters.name": "Einreichende", // "comcol-role.edit.submitters.description": "The E-People and Groups that have permission to submit new items to this collection.", // TODO New key - Add a translation @@ -1410,21 +1402,18 @@ "comcol-role.edit.item_read.description": "E-People and Groups that can read new items submitted to this collection. Changes to this role are not retroactive. Existing items in the system will still be viewable by those who had read access at the time of their addition.", // "comcol-role.edit.item_read.anonymous-group": "Default read for incoming items is currently set to Anonymous.", - // TODO New key - Add a translation - "comcol-role.edit.item_read.anonymous-group": "Default read for incoming items is currently set to Anonymous.", + "comcol-role.edit.item_read.anonymous-group": "Die Standardeinstellung für das Lesen eingehender Items ist derzeit auf Anonym eingestellt.", // "comcol-role.edit.bitstream_read.name": "Default bitstream read access", - // TODO New key - Add a translation - "comcol-role.edit.bitstream_read.name": "Default bitstream read access", + "comcol-role.edit.bitstream_read.name": "Standard-Lesezugriff Datei", // "comcol-role.edit.bitstream_read.description": "Community administrators can create sub-communities or collections, and manage or assign management for those sub-communities or collections. In addition, they decide who can submit items to any sub-collections, edit item metadata (after submission), and add (map) existing items from other collections (subject to authorization).", // TODO New key - Add a translation "comcol-role.edit.bitstream_read.description": "Community administrators can create sub-communities or collections, and manage or assign management for those sub-communities or collections. In addition, they decide who can submit items to any sub-collections, edit item metadata (after submission), and add (map) existing items from other collections (subject to authorization).", // "comcol-role.edit.bitstream_read.anonymous-group": "Default read for incoming bitstreams is currently set to Anonymous.", - // TODO New key - Add a translation - "comcol-role.edit.bitstream_read.anonymous-group": "Default read for incoming bitstreams is currently set to Anonymous.", + "comcol-role.edit.bitstream_read.anonymous-group": "Die Standardeinstellung für das Lesen eingehender Dateien ist derzeit auf Anonym eingestellt.", // "comcol-role.edit.editor.name": "Editors", @@ -1435,21 +1424,17 @@ // "comcol-role.edit.finaleditor.name": "Final editors", - // TODO New key - Add a translation - "comcol-role.edit.finaleditor.name": "Final editors", + "comcol-role.edit.finaleditor.name": "Endredakteure", // "comcol-role.edit.finaleditor.description": "Final editors are able to edit the metadata of incoming submissions, but will not be able to reject them.", - // TODO New key - Add a translation - "comcol-role.edit.finaleditor.description": "Final editors are able to edit the metadata of incoming submissions, but will not be able to reject them.", + "comcol-role.edit.finaleditor.description": "Die Endredakteure können die Metadaten der eingehenden Beiträge bearbeiten, sie können sie jedoch nicht ablehnen.", // "comcol-role.edit.reviewer.name": "Reviewers", - // TODO New key - Add a translation - "comcol-role.edit.reviewer.name": "Reviewers", + "comcol-role.edit.reviewer.name": "Prüfer", // "comcol-role.edit.reviewer.description": "Reviewers are able to accept or reject incoming submissions. However, they are not able to edit the submission's metadata.", - // TODO New key - Add a translation - "comcol-role.edit.reviewer.description": "Reviewers are able to accept or reject incoming submissions. However, they are not able to edit the submission's metadata.", + "comcol-role.edit.reviewer.description": "Die Prüfer können eingehende Beiträge annehmen oder ablehnen. Sie können jedoch nicht die Metadaten der Einreichung bearbeiten.", @@ -1509,16 +1494,13 @@ "cookies.consent.app.opt-out.title": "(opt-out)", // "cookies.consent.app.purpose": "purpose", - // TODO New key - Add a translation - "cookies.consent.app.purpose": "purpose", + "cookies.consent.app.purpose": "Verwendungszweck", // "cookies.consent.app.required.description": "This application is always required", - // TODO New key - Add a translation - "cookies.consent.app.required.description": "This application is always required", + "cookies.consent.app.required.description": "Dieser Eintrag ist immer erforderlich.", // "cookies.consent.app.required.title": "(always required)", - // TODO New key - Add a translation - "cookies.consent.app.required.title": "(always required)", + "cookies.consent.app.required.title": "(immer erforderlich)", // "cookies.consent.update": "There were changes since your last visit, please update your consent.", "cookies.consent.update": "Seit Ihrem letzten Besuch haben sich Änderungen ergeben, bitte aktualisieren Sie Ihr Einverständnis.", @@ -1569,33 +1551,27 @@ "cookies.consent.app.title.acknowledgement": "Bestätigung", // "cookies.consent.app.description.acknowledgement": "Required for saving your acknowledgements and consents", - // TODO New key - Add a translation "cookies.consent.app.description.acknowledgement": "Erforderlich für die Speicherung Ihrer Bestätigungen und Einwilligungen", // "cookies.consent.app.title.google-analytics": "Google Analytics", - // TODO New key - Add a translation "cookies.consent.app.title.google-analytics": "Google Analytics", // "cookies.consent.app.description.google-analytics": "Allows us to track statistical data", - // TODO New key - Add a translation - "cookies.consent.app.description.google-analytics": "Allows us to track statistical data", + "cookies.consent.app.description.google-analytics": "Erlaubt uns, statistische Daten zu erfassen", // "cookies.consent.purpose.functional": "Functional", - // TODO New key - Add a translation - "cookies.consent.purpose.functional": "Functional", + "cookies.consent.purpose.functional": "Funktionell", // "cookies.consent.purpose.statistical": "Statistical", - // TODO New key - Add a translation - "cookies.consent.purpose.statistical": "Statistical", + "cookies.consent.purpose.statistical": "Statistisch", // "curation-task.task.checklinks.label": "Check Links in Metadata", - // TODO New key - Add a translation - "curation-task.task.checklinks.label": "Check Links in Metadata", + "curation-task.task.checklinks.label": "Links in Metadaten prüfen", // "curation-task.task.noop.label": "NOOP", // TODO New key - Add a translation @@ -1606,45 +1582,35 @@ "curation-task.task.profileformats.label": "Profile Bitstream Formats", // "curation-task.task.requiredmetadata.label": "Check for Required Metadata", - // TODO New key - Add a translation - "curation-task.task.requiredmetadata.label": "Check for Required Metadata", + "curation-task.task.requiredmetadata.label": "Prüfung auf erforderliche Metadaten", // "curation-task.task.translate.label": "Microsoft Translator", - // TODO New key - Add a translation - "curation-task.task.translate.label": "Microsoft Translator", + "curation-task.task.translate.label": "Microsoft Übersetzer", // "curation-task.task.vscan.label": "Virus Scan", - // TODO New key - Add a translation - "curation-task.task.vscan.label": "Virus Scan", + "curation-task.task.vscan.label": "Viren-Scan", // "curation.form.task-select.label": "Task:", - // TODO New key - Add a translation - "curation.form.task-select.label": "Task:", + "curation.form.task-select.label": "Aufgabe:", // "curation.form.submit": "Start", - // TODO New key - Add a translation "curation.form.submit": "Start", // "curation.form.submit.success.head": "The curation task has been started successfully", - // TODO New key - Add a translation - "curation.form.submit.success.head": "The curation task has been started successfully", + "curation.form.submit.success.head": "Die Datenpflegeroutine wurde erfolgreich gestartet", // "curation.form.submit.success.content": "You will be redirected to the corresponding process page.", - // TODO New key - Add a translation - "curation.form.submit.success.content": "You will be redirected to the corresponding process page.", + "curation.form.submit.success.content": "Sie werden auf die entsprechende Prozessseite weitergeleitet.", // "curation.form.submit.error.head": "Running the curation task failed", - // TODO New key - Add a translation - "curation.form.submit.error.head": "Running the curation task failed", + "curation.form.submit.error.head": "Die Ausführung der Datenpflegeroutine ist fehlgeschlagen", // "curation.form.submit.error.content": "An error occured when trying to start the curation task.", - // TODO New key - Add a translation - "curation.form.submit.error.content": "An error occured when trying to start the curation task.", + "curation.form.submit.error.content": "Beim Versuch, die Datenpflegeroutine zu starten, ist ein Fehler aufgetreten.", // "curation.form.handle.label": "Handle:", - // TODO New key - Add a translation "curation.form.handle.label": "Handle:", // "curation.form.handle.hint": "Hint: Enter [your-handle-prefix]/0 to run a task across entire site (not all tasks may support this capability)", From 8b0c0348b2d582bb106ccc00ab67171017763b74 Mon Sep 17 00:00:00 2001 From: YPaulsen-TLC Date: Fri, 7 Jan 2022 14:34:18 +0100 Subject: [PATCH 040/160] New message key translations German translations of DSpace 7 message keys. --- src/assets/i18n/de.json5 | 400 +++++++++++++++------------------------ 1 file changed, 152 insertions(+), 248 deletions(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 7edfd27993..77105cc7aa 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -107,7 +107,7 @@ "admin.registries.bitstream-formats.edit.head": "Dateiformat: {{ format }}", // "admin.registries.bitstream-formats.edit.internal.hint": "Formats marked as internal are hidden from the user, and used for administrative purposes.", - "admin.registries.bitstream-formats.edit.internal.hint": "Dateiformate, die als intern gekennzeichnet sind, dienen administrativen Zwecken und bleiben dem Endnutzer verborgen.", + "admin.registries.bitstream-formats.edit.internal.hint": "Dateiformate, die als intern gekennzeichnet sind, dienen administrativen Zwecken und bleiben dem/der Endnutzer:in verborgen.", // "admin.registries.bitstream-formats.edit.internal.label": "Internal", "admin.registries.bitstream-formats.edit.internal.label": "Intern", @@ -812,7 +812,7 @@ // "browse.comcol.by.author": "By Author", - "browse.comcol.by.author": "Nach Autor/in", + "browse.comcol.by.author": "Nach Autor:in", // "browse.comcol.by.dateissued": "By Issue Date", "browse.comcol.by.dateissued": "Nach Erscheinungsjahr", @@ -830,7 +830,7 @@ "browse.empty": "Es gibt keine Dokumente, die angezeigt werden können.", // "browse.metadata.author": "Author", - "browse.metadata.author": "Autor/in", + "browse.metadata.author": "Autor:in", // "browse.metadata.dateissued": "Issue Date", "browse.metadata.dateissued": "Erscheinungsdatum", @@ -842,7 +842,7 @@ "browse.metadata.title": "Titel", // "browse.metadata.author.breadcrumbs": "Browse by Author", - "browse.metadata.author.breadcrumbs": "Auflistung nach Autor(in)", + "browse.metadata.author.breadcrumbs": "Auflistung nach Autor:in", // "browse.metadata.dateissued.breadcrumbs": "Browse by Date", "browse.metadata.dateissued.breadcrumbs": "Auflistung nach Datum", @@ -978,7 +978,7 @@ "collection.edit.item-mapper.confirm": "Ausgewählte Ressourcen spiegeln", // "collection.edit.item-mapper.description": "This is the item mapper tool that allows collection administrators to map items from other collections into this collection. You can search for items from other collections and map them, or browse the list of currently mapped items.", - "collection.edit.item-mapper.description": "Sammlungsadministratoren haben die Möglichkeit Ressourcen von einer Sammlung in eine andere zu spiegeln. Man kann nach Ressourcen in anderen Sammlungen suchen und diese spiegeln oder sich eine Liste der gespiegelten Ressourcen anzeigen lassen.", + "collection.edit.item-mapper.description": "Sammlungsadministrator:innen haben die Möglichkeit Ressourcen von einer Sammlung in eine andere zu spiegeln. Man kann nach Ressourcen in anderen Sammlungen suchen und diese spiegeln oder sich eine Liste der gespiegelten Ressourcen anzeigen lassen.", // "collection.edit.item-mapper.head": "Item Mapper - Map Items from Other Collections", "collection.edit.item-mapper.head": "Ressourcen spiegeln - Spiegelt Ressourcen aus anderen Sammlungen", @@ -1370,10 +1370,10 @@ // "comcol-role.edit.community-admin.name": "Administrators", - "comcol-role.edit.community-admin.name": "Administratoren", + "comcol-role.edit.community-admin.name": "Administrator:innen", // "comcol-role.edit.collection-admin.name": "Administrators", - "comcol-role.edit.collection-admin.name": "Administratoren", + "comcol-role.edit.collection-admin.name": "Administrator:innen", // "comcol-role.edit.community-admin.description": "Community administrators can create sub-communities or collections, and manage or assign management for those sub-communities or collections. In addition, they decide who can submit items to any sub-collections, edit item metadata (after submission), and add (map) existing items from other collections (subject to authorization).", @@ -1417,24 +1417,24 @@ // "comcol-role.edit.editor.name": "Editors", - "comcol-role.edit.editor.name": "Redakteure", + "comcol-role.edit.editor.name": "Redakteur:innen", // "comcol-role.edit.editor.description": "Editors are able to edit the metadata of incoming submissions, and then accept or reject them.", "comcol-role.edit.editor.description": "Die Redakteure können die Metadaten der eingehenden Beiträge bearbeiten und sie dann annehmen oder ablehnen.", // "comcol-role.edit.finaleditor.name": "Final editors", - "comcol-role.edit.finaleditor.name": "Endredakteure", + "comcol-role.edit.finaleditor.name": "Endredakteur:innen", // "comcol-role.edit.finaleditor.description": "Final editors are able to edit the metadata of incoming submissions, but will not be able to reject them.", - "comcol-role.edit.finaleditor.description": "Die Endredakteure können die Metadaten der eingehenden Beiträge bearbeiten, sie können sie jedoch nicht ablehnen.", + "comcol-role.edit.finaleditor.description": "Die Endredakteur:innen können die Metadaten der eingehenden Beiträge bearbeiten, sie können sie jedoch nicht ablehnen.", // "comcol-role.edit.reviewer.name": "Reviewers", - "comcol-role.edit.reviewer.name": "Prüfer", + "comcol-role.edit.reviewer.name": "Prüfer:innen", // "comcol-role.edit.reviewer.description": "Reviewers are able to accept or reject incoming submissions. However, they are not able to edit the submission's metadata.", - "comcol-role.edit.reviewer.description": "Die Prüfer können eingehende Beiträge annehmen oder ablehnen. Sie können jedoch nicht die Metadaten der Einreichung bearbeiten.", + "comcol-role.edit.reviewer.description": "Die Prüfer:innen können eingehende Beiträge annehmen oder ablehnen. Sie können jedoch nicht die Metadaten der Einreichung bearbeiten.", @@ -1623,8 +1623,7 @@ "dso-selector.create.collection.head": "Neue Sammlung", // "dso-selector.create.collection.sub-level": "Create a new collection in", - // TODO New key - Add a translation - "dso-selector.create.collection.sub-level": "Create a new collection in", + "dso-selector.create.collection.sub-level": "Eine neue Sammlung erstellen in", // "dso-selector.create.community.head": "New community", "dso-selector.create.community.head": "Neuer Bereich", @@ -1639,12 +1638,10 @@ "dso-selector.create.item.head": "Neue Ressource", // "dso-selector.create.item.sub-level": "Create a new item in", - // TODO New key - Add a translation - "dso-selector.create.item.sub-level": "Create a new item in", + "dso-selector.create.item.sub-level": "Ein neues Item erstellen in", // "dso-selector.create.submission.head": "New submission", - // TODO New key - Add a translation - "dso-selector.create.submission.head": "New submission", + "dso-selector.create.submission.head": "Neue Veröffentlichung", // "dso-selector.edit.collection.head": "Edit collection", "dso-selector.edit.collection.head": "Sammlung bearbeiten", @@ -1656,8 +1653,7 @@ "dso-selector.edit.item.head": "Ressource bearbeiten", // "dso-selector.export-metadata.dspaceobject.head": "Export metadata from", - // TODO New key - Add a translation - "dso-selector.export-metadata.dspaceobject.head": "Export metadata from", + "dso-selector.export-metadata.dspaceobject.head": "Exportieren der Metadaten aus", // "dso-selector.no-results": "No {{ type }} found", "dso-selector.no-results": "Kein(e) {{ type }} gefunden", @@ -1668,36 +1664,28 @@ // "confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}", - // TODO New key - Add a translation - "confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}", + "confirmation-modal.export-metadata.header": "Exportieren der Metadaten für {{ dsoName }}", // "confirmation-modal.export-metadata.info": "Are you sure you want to export metadata for {{ dsoName }}", - // TODO New key - Add a translation - "confirmation-modal.export-metadata.info": "Are you sure you want to export metadata for {{ dsoName }}", + "confirmation-modal.export-metadata.info": "Sind Sie sicher, dass Sie die Metadaten für {{ dsoName }} exportieren wollen", // "confirmation-modal.export-metadata.cancel": "Cancel", - // TODO New key - Add a translation - "confirmation-modal.export-metadata.cancel": "Cancel", + "confirmation-modal.export-metadata.cancel": "Abbrechen", // "confirmation-modal.export-metadata.confirm": "Export", - // TODO New key - Add a translation - "confirmation-modal.export-metadata.confirm": "Export", + "confirmation-modal.export-metadata.confirm": "Exportieren", // "confirmation-modal.delete-eperson.header": "Delete EPerson \"{{ dsoName }}\"", - // TODO New key - Add a translation - "confirmation-modal.delete-eperson.header": "Delete EPerson \"{{ dsoName }}\"", + "confirmation-modal.delete-eperson.header": "Person \"{{ dsoName }}\" löschen", // "confirmation-modal.delete-eperson.info": "Are you sure you want to delete EPerson \"{{ dsoName }}\"", - // TODO New key - Add a translation - "confirmation-modal.delete-eperson.info": "Are you sure you want to delete EPerson \"{{ dsoName }}\"", + "confirmation-modal.delete-eperson.info": "Sind Sie sicher, dass Sie die Person \"{{ dsoName }}\" löschen möchten", // "confirmation-modal.delete-eperson.cancel": "Cancel", - // TODO New key - Add a translation - "confirmation-modal.delete-eperson.cancel": "Cancel", + "confirmation-modal.delete-eperson.cancel": "Abbrechen", // "confirmation-modal.delete-eperson.confirm": "Delete", - // TODO New key - Add a translation - "confirmation-modal.delete-eperson.confirm": "Delete", + "confirmation-modal.delete-eperson.confirm": "Löschen", // "error.bitstream": "Error fetching bitstream", @@ -1726,7 +1714,7 @@ "error.item": "Fehler beim Laden der Ressource", // "error.items": "Error fetching items", - "error.items": "Fejöer beim Laden der Ressourcen", + "error.items": "Fehler beim Laden der Ressourcen", // "error.objects": "Error fetching objects", "error.objects": "Fehler beim Laden der Objekte", @@ -1761,8 +1749,7 @@ // "file-section.error.header": "Error obtaining files for this item", - // TODO New key - Add a translation - "file-section.error.header": "Error obtaining files for this item", + "file-section.error.header": "Fehler beim Abrufen der Dateien für dieses Item", @@ -1776,124 +1763,98 @@ "footer.link.lyrasis": "LYRASIS", // "footer.link.cookies": "Cookie settings", - // TODO New key - Add a translation - "footer.link.cookies": "Cookie settings", + "footer.link.cookies": "Cookie Einstellungen", // "footer.link.privacy-policy": "Privacy policy", - // TODO New key - Add a translation - "footer.link.privacy-policy": "Privacy policy", + "footer.link.privacy-policy": "Datenschutzbestimmungen", // "footer.link.end-user-agreement":"End User Agreement", - // TODO New key - Add a translation - "footer.link.end-user-agreement":"End User Agreement", - + "footer.link.end-user-agreement":"Endnutzervereinbarung", + // "forgot-email.form.header": "Forgot Password", - // TODO New key - Add a translation - "forgot-email.form.header": "Forgot Password", + "forgot-email.form.header": "Passwort vergessen", // "forgot-email.form.info": "Enter Register an account to subscribe to collections for email updates, and submit new items to DSpace.", // TODO New key - Add a translation "forgot-email.form.info": "Enter Register an account to subscribe to collections for email updates, and submit new items to DSpace.", // "forgot-email.form.email": "Email Address *", - // TODO New key - Add a translation - "forgot-email.form.email": "Email Address *", + "forgot-email.form.email": "E-Mail-Addresse *", // "forgot-email.form.email.error.required": "Please fill in an email address", - // TODO New key - Add a translation - "forgot-email.form.email.error.required": "Please fill in an email address", + "forgot-email.form.email.error.required": "Bitte geben Sie eine E-Mail-Adresse ein", // "forgot-email.form.email.error.pattern": "Please fill in a valid email address", - // TODO New key - Add a translation - "forgot-email.form.email.error.pattern": "Please fill in a valid email address", + "forgot-email.form.email.error.pattern": "Bitte geben Sie eine gültige E-Mail-Adresse ein", // "forgot-email.form.email.hint": "This address will be verified and used as your login name.", - // TODO New key - Add a translation - "forgot-email.form.email.hint": "This address will be verified and used as your login name.", + "forgot-email.form.email.hint": "Diese Adresse wird überprüft und als Ihr Anmeldename verwendet.", // "forgot-email.form.submit": "Submit", - // TODO New key - Add a translation - "forgot-email.form.submit": "Submit", + "forgot-email.form.submit": "Einreichen", // "forgot-email.form.success.head": "Verification email sent", - // TODO New key - Add a translation - "forgot-email.form.success.head": "Verification email sent", + "forgot-email.form.success.head": "Verifizierungs-E-Mail gesendet", // "forgot-email.form.success.content": "An email has been sent to {{ email }} containing a special URL and further instructions.", // TODO New key - Add a translation "forgot-email.form.success.content": "An email has been sent to {{ email }} containing a special URL and further instructions.", // "forgot-email.form.error.head": "Error when trying to register email", - // TODO New key - Add a translation - "forgot-email.form.error.head": "Error when trying to register email", + "forgot-email.form.error.head": "Fehler beim Versuch, die E-Mail zu registrieren", // "forgot-email.form.error.content": "An error occured when registering the following email address: {{ email }}", // TODO New key - Add a translation - "forgot-email.form.error.content": "An error occured when registering the following email address: {{ email }}", + "forgot-email.form.error.content": "Bei der Registrierung der folgenden E-Mail-Adresse ist ein Fehler aufgetreten: {{ email }}", // "forgot-password.title": "Forgot Password", - // TODO New key - Add a translation - "forgot-password.title": "Forgot Password", + "forgot-password.title": "Passwort vergessen", // "forgot-password.form.head": "Forgot Password", - // TODO New key - Add a translation - "forgot-password.form.head": "Forgot Password", + "forgot-password.form.head": "Passwort vergessen", // "forgot-password.form.info": "Enter a new password in the box below, and confirm it by typing it again into the second box. It should be at least six characters long.", - // TODO New key - Add a translation - "forgot-password.form.info": "Enter a new password in the box below, and confirm it by typing it again into the second box. It should be at least six characters long.", + "forgot-password.form.info": "Geben Sie ein neues Kennwort in das unten stehende Feld ein und bestätigen Sie es, indem Sie es erneut in das zweite Feld eingeben. Es sollte mindestens sechs Zeichen lang sein.", // "forgot-password.form.card.security": "Security", - // TODO New key - Add a translation - "forgot-password.form.card.security": "Security", + "forgot-password.form.card.security": "Sicherheit", // "forgot-password.form.identification.header": "Identify", - // TODO New key - Add a translation - "forgot-password.form.identification.header": "Identify", + "forgot-password.form.identification.header": "Identifizieren", // "forgot-password.form.identification.email": "Email address: ", - // TODO New key - Add a translation - "forgot-password.form.identification.email": "Email address: ", + "forgot-password.form.identification.email": "E-Mail-Addresse: ", // "forgot-password.form.label.password": "Password", - // TODO New key - Add a translation - "forgot-password.form.label.password": "Password", + "forgot-password.form.label.password": "Passwort", // "forgot-password.form.label.passwordrepeat": "Retype to confirm", - // TODO New key - Add a translation - "forgot-password.form.label.passwordrepeat": "Retype to confirm", + "forgot-password.form.label.passwordrepeat": "Zum Bestätigen wiederholen", // "forgot-password.form.error.empty-password": "Please enter a password in the box below.", - // TODO New key - Add a translation - "forgot-password.form.error.empty-password": "Please enter a password in the box below.", + "forgot-password.form.error.empty-password": "Bitte geben Sie in das unten stehende Feld ein Passwort ein.", // "forgot-password.form.error.matching-passwords": "The passwords do not match.", - // TODO New key - Add a translation - "forgot-password.form.error.matching-passwords": "The passwords do not match.", + "forgot-password.form.error.matching-passwords": "Die Passwörter stimmen nicht überein.", // "forgot-password.form.error.password-length": "The password should be at least 6 characters long.", - // TODO New key - Add a translation - "forgot-password.form.error.password-length": "The password should be at least 6 characters long.", + "forgot-password.form.error.password-length": "Das Passwort sollte mindestens 6 Zeichen lang sein.", // "forgot-password.form.notification.error.title": "Error when trying to submit new password", - // TODO New key - Add a translation - "forgot-password.form.notification.error.title": "Error when trying to submit new password", + "forgot-password.form.notification.error.title": "Fehler beim Versuch, ein neues Passwort zu übermitteln", // "forgot-password.form.notification.success.content": "The password reset was successful. You have been logged in as the created user.", - // TODO New key - Add a translation - "forgot-password.form.notification.success.content": "The password reset was successful. You have been logged in as the created user.", + "forgot-password.form.notification.success.content": "Das Zurücksetzen des Passworts war erfolgreich. Sie wurden in den erstellten Account eingeloggt.", // "forgot-password.form.notification.success.title": "Password reset completed", - // TODO New key - Add a translation - "forgot-password.form.notification.success.title": "Password reset completed", + "forgot-password.form.notification.success.title": "Passwort zurücksetzen abgeschlossen", // "forgot-password.form.submit": "Submit password", - // TODO New key - Add a translation - "forgot-password.form.submit": "Submit password", + "forgot-password.form.submit": "Passwort abschicken", @@ -1967,7 +1928,6 @@ "form.search": "Suche", // "form.search-help": "Click here to look for an existing correspondence", - // TODO Source message changed - Revise the translation "form.search-help": "Klicken Sie hier, um eine Übereinstimmung zu suchen", // "form.submit": "Submit", @@ -1979,8 +1939,7 @@ "home.description": "", // "home.breadcrumbs": "Home", - // TODO New key - Add a translation - "home.breadcrumbs": "Home", + "home.breadcrumbs": "Startseite", // "search.search-form.placeholder": "Search the repository ...", "home.search-form.placeholder": "Durchsuche Repositorium", @@ -2032,12 +1991,10 @@ // "item.alerts.private": "This item is private", - // TODO New key - Add a translation - "item.alerts.private": "This item is private", + "item.alerts.private": "Dieses Item ist privat", // "item.alerts.withdrawn": "This item has been withdrawn", - // TODO New key - Add a translation - "item.alerts.withdrawn": "This item has been withdrawn", + "item.alerts.withdrawn": "Dieses Item wurde zurückgezogen", @@ -2046,8 +2003,7 @@ "item.edit.authorizations.heading": "With this editor you can view and alter the policies of an item, plus alter policies of individual item components: bundles and bitstreams. Briefly, an item is a container of bundles, and bundles are containers of bitstreams. Containers usually have ADD/REMOVE/READ/WRITE policies, while bitstreams only have READ/WRITE policies.", // "item.edit.authorizations.title": "Edit item's Policies", - // TODO New key - Add a translation - "item.edit.authorizations.title": "Edit item's Policies", + "item.edit.authorizations.title": "Item-Richtlinien bearbeiten", @@ -2079,7 +2035,6 @@ "item.bitstreams.upload.drop-message": "Eine Datei zum Hochladen ablegen", // "item.bitstreams.upload.item": "Item: ", - // TODO New key - Add a translation "item.bitstreams.upload.item": "Item: ", // "item.bitstreams.upload.notifications.bundle.created.content": "Successfully created new bundle.", @@ -2092,8 +2047,7 @@ "item.bitstreams.upload.notifications.upload.failed": "Hochladen fehlgeschlagen. Bitte überprüfen Sie den Inhalt, bevor Sie es erneut versuchen.", // "item.bitstreams.upload.title": "Upload bitstream", - // TODO New key - Add a translation - "item.bitstreams.upload.title": "Upload bitstream", + "item.bitstreams.upload.title": "Datei hochladen", @@ -2105,8 +2059,7 @@ "item.edit.bitstreams.bundle.displaying": "Currently displaying {{ amount }} bitstreams of {{ total }}.", // "item.edit.bitstreams.bundle.load.all": "Load all ({{ total }})", - // TODO New key - Add a translation - "item.edit.bitstreams.bundle.load.all": "Load all ({{ total }})", + "item.edit.bitstreams.bundle.load.all": "Alle ({{ total }}) laden", // "item.edit.bitstreams.bundle.load.more": "Load more", "item.edit.bitstreams.bundle.load.more": "Mehr laden", @@ -2168,7 +2121,7 @@ "item.edit.bitstreams.notifications.move.saved.title": "Move changes saved", // "item.edit.bitstreams.notifications.outdated.content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts", - "item.edit.bitstreams.notifications.outdated.content": "Die Ressource, an der Sie gerade arbeiten, wurde von einem anderen Benutzer geändert. Ihre aktuellen Änderungen werden verworfen, um Konflikte zu vermeiden.", + "item.edit.bitstreams.notifications.outdated.content": "Die Ressource, an der Sie gerade arbeiten, wurde von einem anderen Account geändert. Ihre aktuellen Änderungen werden verworfen, um Konflikte zu vermeiden.", // "item.edit.bitstreams.notifications.outdated.title": "Changes outdated", "item.edit.bitstreams.notifications.outdated.title": "Veraltete Änderungen", @@ -2236,7 +2189,7 @@ "item.edit.item-mapper.cancel": "Abbrechen", // "item.edit.item-mapper.description": "This is the item mapper tool that allows administrators to map this item to other collections. You can search for collections and map them, or browse the list of collections the item is currently mapped to.", - "item.edit.item-mapper.description": "Sammlungsadministratoren haben die Möglichkeit Ressourcen von einer Sammlung in eine andere zu spiegeln. Man kann nach Ressourcen in anderen Sammlungen suchen und diese spiegeln oder sich eine Liste der gespiegelten Ressourcen anzeigen lassen.", + "item.edit.item-mapper.description": "Sammlungsadministrator:innen haben die Möglichkeit Ressourcen von einer Sammlung in eine andere zu spiegeln. Man kann nach Ressourcen in anderen Sammlungen suchen und diese spiegeln oder sich eine Liste der gespiegelten Ressourcen anzeigen lassen.", // "item.edit.item-mapper.head": "Item Mapper - Map Item to Collections", "item.edit.item-mapper.head": "Ressourcen spiegeln - Spiegelt eine Ressource in andere Sammlungen", @@ -2322,8 +2275,7 @@ "item.edit.metadata.notifications.discarded.title": "Änderungen verworfen", // "item.edit.metadata.notifications.error.title": "An error occurred", - // TODO New key - Add a translation - "item.edit.metadata.notifications.error.title": "An error occurred", + "item.edit.metadata.notifications.error.title": "Es ist ein Fehler aufgetreten", // "item.edit.metadata.notifications.invalid.content": "Your changes were not saved. Please make sure all fields are valid before you save.", "item.edit.metadata.notifications.invalid.content": "Ihre Änderungen wurden nicht gespeichert. Stellen Sie sicher, dass alle Felder gültig sind, bevor Sie Abspeichern.", @@ -2332,7 +2284,7 @@ "item.edit.metadata.notifications.invalid.title": "Metadaten ungültig", // "item.edit.metadata.notifications.outdated.content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts", - "item.edit.metadata.notifications.outdated.content": "Die Ressource, an der Sie gerade arbeiten, wurde von einem anderen Benutzer geändert. Ihre aktuellen Änderungen werden nicht angewandt, um Konflikte zu vermeiden.", + "item.edit.metadata.notifications.outdated.content": "Die Ressource, an der Sie gerade arbeiten, wurde von einem anderen Account geändert. Ihre aktuellen Änderungen werden nicht angewandt, um Konflikte zu vermeiden.", // "item.edit.metadata.notifications.outdated.title": "Changed outdated", "item.edit.metadata.notifications.outdated.title": "Änderung veraltet", @@ -2482,7 +2434,7 @@ "item.edit.relationships.notifications.failed.title": "Fehler beim Bearbeiten der Beziehung", // "item.edit.relationships.notifications.outdated.content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts", - "item.edit.relationships.notifications.outdated.content": "Die Ressource, die Sie gerade bearbeiten, wurde von einem anderen Benutzer geändert. Ihre aktuellen Änderungen werden verworfen, um Konflikte zu vermeiden.", + "item.edit.relationships.notifications.outdated.content": "Die Ressource, die Sie gerade bearbeiten, wurde von einem anderen Account geändert. Ihre aktuellen Änderungen werden verworfen, um Konflikte zu vermeiden.", // "item.edit.relationships.notifications.outdated.title": "Changes outdated", "item.edit.relationships.notifications.outdated.title": "Änderungen veraltet", @@ -2530,7 +2482,7 @@ "item.edit.tabs.relationships.title": "Ressource bearbeiten - Relationen", // "item.edit.tabs.status.buttons.authorizations.button": "Authorizations...", - "item.edit.tabs.status.buttons.authorizations.button": "Rechte...", + "item.edit.tabs.status.buttons.authorizations.button": "Berechtigungen...", // "item.edit.tabs.status.buttons.authorizations.label": "Edit item's authorization policies", "item.edit.tabs.status.buttons.authorizations.label": "Rechte der Ressource bearbeiten", @@ -2671,7 +2623,7 @@ "item.page.abstract": "Zusammenfassung", // "item.page.author": "Authors", - "item.page.author": "AutorInnen", + "item.page.author": "Autor:innen", // "item.page.citation": "Citation", "item.page.citation": "Zitierform", @@ -2713,7 +2665,7 @@ "item.page.link.simple": "Kurzanzeige", // "item.page.person.search.title": "Articles by this author", - "item.page.person.search.title": "Veröffentlichungen dieses/r Autor/in", + "item.page.person.search.title": "Veröffentlichungen dieses/dieser Autor:in", // "item.page.related-items.view-more": "Show {{ amount }} more", "item.page.related-items.view-more": "{{ amount }} mehr anzeigen", @@ -2728,7 +2680,7 @@ "item.page.relationships.isJournalOfPublication": "Veröffentlichungen", // "item.page.relationships.isOrgUnitOfPerson": "Authors", - "item.page.relationships.isOrgUnitOfPerson": "AutorInnen", + "item.page.relationships.isOrgUnitOfPerson": "Autor:innen", // "item.page.relationships.isOrgUnitOfProject": "Research Projects", "item.page.relationships.isOrgUnitOfProject": "Forschungsprojekte", @@ -2755,7 +2707,7 @@ "item.preview.dc.identifier.uri": "Identifikator:", // "item.preview.dc.contributor.author": "Authors:", - "item.preview.dc.contributor.author": "AutorInnen:", + "item.preview.dc.contributor.author": "Autor:innen:", // "item.preview.dc.date.issued": "Published date:", "item.preview.dc.date.issued": "Erscheinungsdatum:", @@ -2793,7 +2745,7 @@ "item.select.empty": "Es gibt keine Ressourcen dazu", // "item.select.table.author": "Author", - "item.select.table.author": "Autor/in", + "item.select.table.author": "Autor:in", // "item.select.table.collection": "Collection", "item.select.table.collection": "Sammlung", @@ -2809,7 +2761,6 @@ "item.version.history.head": "Versionsgeschichte", // "item.version.history.return": "Return", - // TODO New key - Add a translation "item.version.history.return": "Zurück", // "item.version.history.selected": "Selected version", @@ -2822,7 +2773,7 @@ "item.version.history.table.item": "Ressource", // "item.version.history.table.editor": "Editor", - "item.version.history.table.editor": "Herausgeber/in", + "item.version.history.table.editor": "Herausgeber:in", // "item.version.history.table.date": "Date", "item.version.history.table.date": "Datum", @@ -2848,7 +2799,7 @@ "journal.page.edit": "Diese Ressource bearbeiten", // "journal.page.editor": "Editor-in-Chief", - "journal.page.editor": "Chefredakteur", + "journal.page.editor": "Chefredakteur:in", // "journal.page.issn": "ISSN", "journal.page.issn": "ISSN", @@ -3038,7 +2989,7 @@ // "menu.section.admin_search": "Admin Search", - "menu.section.admin_search": "Administratoren Suche", + "menu.section.admin_search": "Administrator:innen Suche", @@ -3046,7 +2997,7 @@ "menu.section.browse_community": "Dieser Bereich", // "menu.section.browse_community_by_author": "By Author", - "menu.section.browse_community_by_author": "Nach Autor/in", + "menu.section.browse_community_by_author": "Nach Autor:in", // "menu.section.browse_community_by_issue_date": "By Issue Date", "menu.section.browse_community_by_issue_date": "Nach Erscheinungsdateum", @@ -3058,7 +3009,7 @@ "menu.section.browse_global": "Das gesamte Repositorium", // "menu.section.browse_global_by_author": "By Author", - "menu.section.browse_global_by_author": "Nach Autor/in", + "menu.section.browse_global_by_author": "Nach Autor:in", // "menu.section.browse_global_by_dateissued": "By Issue Date", "menu.section.browse_global_by_dateissued": "Nach Erscheinungsjahr", @@ -3260,8 +3211,7 @@ "mydspace.description": "", // "mydspace.general.text-here": "here", - // TODO New key - Add a translation - "mydspace.general.text-here": "here", + "mydspace.general.text-here": "hier", // "mydspace.messages.controller-help": "Select this option to send a message to item's submitter.", "mydspace.messages.controller-help": "Wählen Sie diese Option, um dem/derjenigen, die die Ressource eingereicht hat, eine Nachricht zu schicken.", @@ -3318,7 +3268,7 @@ "mydspace.results.no-abstract": "Keine Zusammenfassung", // "mydspace.results.no-authors": "No Authors", - "mydspace.results.no-authors": "Keine AutorInnen", + "mydspace.results.no-authors": "Keine Autor:innen", // "mydspace.results.no-collections": "No Collections", "mydspace.results.no-collections": "Keine Sammlungen", @@ -3587,14 +3537,12 @@ "process.detail.output-files" : "Output Files", // "process.detail.output-files.empty" : "This process doesn't contain any output files", - // TODO New key - Add a translation - "process.detail.output-files.empty" : "This process doesn't contain any output files", + "process.detail.output-files.empty" : "Dieser Prozess enthält keine Ausgabedateien", // "process.detail.script" : "Script", "process.detail.script" : "Skript", // "process.detail.title" : "Process: {{ id }} - {{ name }}", - // TODO New key - Add a translation "process.detail.title" : "Prozess: {{ id }} - {{ name }}", // "process.detail.start-time" : "Start time", @@ -3627,7 +3575,7 @@ "process.overview.table.status" : "Status", // "process.overview.table.user" : "User", - "process.overview.table.user" : "Benutzer", + "process.overview.table.user" : "Benutzer:in", // "process.overview.title": "Processes Overview", "process.overview.title": "Überblick der Prozesse", @@ -3652,8 +3600,7 @@ "profile.form.submit": "Profil aktualisieren", // "profile.groups.head": "Authorization groups you belong to", - // TODO New key - Add a translation - "profile.groups.head": "Authorization groups you belong to", + "profile.groups.head": "Berechtigungsgruppen, denen Sie angehören", // "profile.head": "Update Profile", "profile.head": "Profil aktualisieren", @@ -3793,7 +3740,7 @@ // "register-email.title": "New user registration", - "register-email.title": "Registrierung neuer Benutzer", + "register-email.title": "Registrierung neuer Benutzer:innen", // "register-page.create-profile.header": "Create Profile", "register-page.create-profile.header": "Profil erstellen", @@ -3847,20 +3794,20 @@ "register-page.create-profile.submit": "Vollständige Registrierung", // "register-page.create-profile.submit.error.content": "Something went wrong while registering a new user.", - "register-page.create-profile.submit.error.content": "Bei der Registrierung eines neuen Benutzers ist ein Fehler aufgetreten.", + "register-page.create-profile.submit.error.content": "Bei der Registrierung eines neuen Accounts ist ein Fehler aufgetreten.", // "register-page.create-profile.submit.error.head": "Registration failed", "register-page.create-profile.submit.error.head": "Registrierung fehlgeschlagen.", // "register-page.create-profile.submit.success.content": "The registration was successful. You have been logged in as the created user.", - "register-page.create-profile.submit.success.content": "Die Registrierung war erfolgreich. Sie wurden als der angelegte Benutzer angemeldet.", + "register-page.create-profile.submit.success.content": "Die Registrierung war erfolgreich. Sie wurden als der/die angelegte Benutzer:in angemeldet.", // "register-page.create-profile.submit.success.head": "Registration completed", "register-page.create-profile.submit.success.head": "Registrierung abgeschlossen", // "register-page.registration.header": "New user registration", - "register-page.registration.header": "Registrierung neuer Benutzer", + "register-page.registration.header": "Registrierung neuer Benutzer:innen", // "register-page.registration.info": "Register an account to subscribe to collections for email updates, and submit new items to DSpace.", "register-page.registration.info": "Registrieren Sie ein Konto, um Sammlungen für E-Mail-Updates zu abonnieren und neue Ressourcen in DSpace einzugeben.", @@ -3875,7 +3822,7 @@ "register-page.registration.email.error.pattern": "Bitte geben Sie eine gültige E-Mail Adresse an", // "register-page.registration.email.hint": "This address will be verified and used as your login name.", - "register-page.registration.email.hint": "Diese Adresse wird überprüft und als Ihr Benutzername verwendet..", + "register-page.registration.email.hint": "Diese Adresse wird überprüft und als Ihr Accountname verwendet..", // "register-page.registration.submit": "Register", "register-page.registration.submit": "Registrieren", @@ -3904,13 +3851,13 @@ "relationships.add.error.title": "Beziehung kann nicht hinzugefügt werden", // "relationships.isAuthorOf": "Authors", - "relationships.isAuthorOf": "AutorInnen", + "relationships.isAuthorOf": "Autor:innen", // "relationships.isAuthorOf.Person": "Authors (persons)", "relationships.isAuthorOf.Person": "AutorInnen (Personen)", // "relationships.isAuthorOf.OrgUnit": "Authors (organizational units)", - "relationships.isAuthorOf.OrgUnit": "AutorInnen (Organisationseinheiten)", + "relationships.isAuthorOf.OrgUnit": "Autor:innen (Organisationseinheiten)", // "relationships.isIssueOf": "Journal Issues", "relationships.isIssueOf": "Zeitschriftenausgaben", @@ -3925,7 +3872,7 @@ "relationships.isOrgUnitOf": "Organisationseinheiten", // "relationships.isPersonOf": "Authors", - "relationships.isPersonOf": "AutorInnen", + "relationships.isPersonOf": "Autor:innen", // "relationships.isProjectOf": "Research Projects", "relationships.isProjectOf": "Forschungsvorhaben", @@ -3957,24 +3904,19 @@ "resource-policies.add.for.": "Eine neue Richtlinie hinzufügen", // "resource-policies.add.for.bitstream": "Add a new Bitstream policy", - // TODO New key - Add a translation - "resource-policies.add.for.bitstream": "Add a new Bitstream policy", + "resource-policies.add.for.bitstream": "Eine neue Datei-Richtlinie hinzufügen", // "resource-policies.add.for.bundle": "Add a new Bundle policy", - // TODO New key - Add a translation - "resource-policies.add.for.bundle": "Add a new Bundle policy", + "resource-policies.add.for.bundle": "Eine neue Bündel-Richtlinie hinzufügen", // "resource-policies.add.for.item": "Add a new Item policy", - // TODO New key - Add a translation - "resource-policies.add.for.item": "Add a new Item policy", + "resource-policies.add.for.item": "Eine neue Item-Richtlinie hinzufügen", // "resource-policies.add.for.community": "Add a new Community policy", - // TODO New key - Add a translation - "resource-policies.add.for.community": "Add a new Community policy", + "resource-policies.add.for.community": "Eine neue Bereiche-Richtlinie hinzufügen", // "resource-policies.add.for.collection": "Add a new Collection policy", - // TODO New key - Add a translation - "resource-policies.add.for.collection": "Add a new Collection policy", + "resource-policies.add.for.collection": "Eine neue Sammlungen-Richtlinie hinzufügen", // "resource-policies.create.page.heading": "Create new resource policy for ", // TODO New key - Add a translation @@ -4021,15 +3963,13 @@ "resource-policies.edit.page.title": "Edit resource policy", // "resource-policies.form.action-type.label": "Select the action type", - // TODO New key - Add a translation - "resource-policies.form.action-type.label": "Select the action type", + "resource-policies.form.action-type.label": "Wählen Sie die Aktionsart", // "resource-policies.form.action-type.required": "You must select the resource policy action.", // TODO New key - Add a translation "resource-policies.form.action-type.required": "You must select the resource policy action.", // "resource-policies.form.eperson-group-list.label": "The eperson or group that will be granted the permission", - // TODO New key - Add a translation "resource-policies.form.eperson-group-list.label": "Die Person oder Gruppe, der die Genehmigung erteilt wird", // "resource-policies.form.eperson-group-list.select.btn": "Select", @@ -4064,16 +4004,14 @@ "resource-policies.form.name.label": "Name", // "resource-policies.form.policy-type.label": "Select the policy type", - // TODO New key - Add a translation - "resource-policies.form.policy-type.label": "Select the policy type", + "resource-policies.form.policy-type.label": "Wählen Sie den Richtlinientyp", // "resource-policies.form.policy-type.required": "You must select the resource policy type.", // TODO New key - Add a translation "resource-policies.form.policy-type.required": "You must select the resource policy type.", // "resource-policies.table.headers.action": "Action", - // TODO New key - Add a translation - "resource-policies.table.headers.action": "Action", + "resource-policies.table.headers.action": "Aktion", // "resource-policies.table.headers.date.end": "End Date", "resource-policies.table.headers.date.end": "Enddatum", @@ -4106,24 +4044,19 @@ "resource-policies.table.headers.policyType": "Typ", // "resource-policies.table.headers.title.for.bitstream": "Policies for Bitstream", - // TODO New key - Add a translation - "resource-policies.table.headers.title.for.bitstream": "Policies for Bitstream", + "resource-policies.table.headers.title.for.bitstream": "Richtlinien für Dateien", // "resource-policies.table.headers.title.for.bundle": "Policies for Bundle", - // TODO New key - Add a translation - "resource-policies.table.headers.title.for.bundle": "Policies for Bundle", + "resource-policies.table.headers.title.for.bundle": "Richtlinien für Bündel", // "resource-policies.table.headers.title.for.item": "Policies for Item", - // TODO New key - Add a translation - "resource-policies.table.headers.title.for.item": "Policies for Item", + "resource-policies.table.headers.title.for.item": "Richtlinien für Items", // "resource-policies.table.headers.title.for.community": "Policies for Community", - // TODO New key - Add a translation - "resource-policies.table.headers.title.for.community": "Policies for Community", + "resource-policies.table.headers.title.for.community": "Richtlinien für Bereiche", // "resource-policies.table.headers.title.for.collection": "Policies for Collection", - // TODO New key - Add a translation - "resource-policies.table.headers.title.for.collection": "Policies for Collection", + "resource-policies.table.headers.title.for.collection": "Richtlinien für Sammlungen", @@ -4143,7 +4076,7 @@ "search.search-form.placeholder": "Durchsuche Repositorium", // "search.filters.applied.f.author": "Author", - "search.filters.applied.f.author": "AutorIn", + "search.filters.applied.f.author": "Autor:in", // "search.filters.applied.f.dateIssued.max": "End date", "search.filters.applied.f.dateIssued.max": "Enddatum", @@ -4173,7 +4106,7 @@ "search.filters.applied.f.subject": "Thema", // "search.filters.applied.f.submitter": "Submitter", - "search.filters.applied.f.submitter": "Einreichende(r)", + "search.filters.applied.f.submitter": "Einreichende:r", // "search.filters.applied.f.jobTitle": "Job Title", "search.filters.applied.f.jobTitle": "Berufsbezeichnung", @@ -4190,10 +4123,10 @@ // "search.filters.filter.author.head": "Author", - "search.filters.filter.author.head": "AutorIn", + "search.filters.filter.author.head": "Autor:in", // "search.filters.filter.author.placeholder": "Author name", - "search.filters.filter.author.placeholder": "Name des/r AutorIn", + "search.filters.filter.author.placeholder": "Name des/der Autor:in", // "search.filters.filter.birthDate.head": "Birth Date", "search.filters.filter.birthDate.head": "Geburtsdatum", @@ -4208,10 +4141,10 @@ "search.filters.filter.creativeDatePublished.placeholder": "Erscheinungsdatum", // "search.filters.filter.creativeWorkEditor.head": "Editor", - "search.filters.filter.creativeWorkEditor.head": "Herausgeber", + "search.filters.filter.creativeWorkEditor.head": "Herausgeber:in", // "search.filters.filter.creativeWorkEditor.placeholder": "Editor", - "search.filters.filter.creativeWorkEditor.placeholder": "Herausgeber", + "search.filters.filter.creativeWorkEditor.placeholder": "Herausgeber:in", // "search.filters.filter.creativeWorkKeywords.head": "Subject", "search.filters.filter.creativeWorkKeywords.head": "Thema", @@ -4322,10 +4255,10 @@ "search.filters.filter.subject.placeholder": "Thema", // "search.filters.filter.submitter.head": "Submitter", - "search.filters.filter.submitter.head": "Einreichende(r)", + "search.filters.filter.submitter.head": "Einreichende:r", // "search.filters.filter.submitter.placeholder": "Submitter", - "search.filters.filter.submitter.placeholder": "Einreichende(r)", + "search.filters.filter.submitter.placeholder": "Einreichende:r", @@ -4591,10 +4524,10 @@ "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.title": "Import Remote Author", // "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.local-entity": "Successfully added local author to the selection", - "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.local-entity": "Erfolgreicher Import und Hinzufügen eines lokalen Autors zur Auswahl", + "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.local-entity": "Erfolgreicher Import und Hinzufügen eines/einer lokalen Autor:in zur Auswahl", // "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.new-entity": "Successfully imported and added external author to the selection", - "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.new-entity": "Erfolgreicher Import und Hinzufügen eines externen Autors zur Auswahl", + "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.new-entity": "Erfolgreicher Import und Hinzufügen eines/einer externen Autor:in zur Auswahl", // "submission.sections.describe.relationship-lookup.external-source.import-modal.authority": "Authority", "submission.sections.describe.relationship-lookup.external-source.import-modal.authority": "Referenz", @@ -4756,12 +4689,10 @@ "submission.sections.describe.relationship-lookup.search-tab.tab-title.arxiv": "arXiv ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isFundingAgencyOfPublication": "Search for Funding Agencies", - // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.isFundingAgencyOfPublication": "Suche nach Fördereinrichtungen", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isFundingOfPublication": "Search for Funding", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.search-tab.tab-title.isFundingOfPublication": "Search for Funding", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.isFundingOfPublication": "Suche nach Förderungen", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isChildOrgUnitOf": "Search for Organizational Units", "submission.sections.describe.relationship-lookup.search-tab.tab-title.isChildOrgUnitOf": "Suche nach Organisationseinheiten", @@ -4783,7 +4714,7 @@ "submission.sections.describe.relationship-lookup.title.isJournalOfPublication": "Zeitschriften", // "submission.sections.describe.relationship-lookup.title.isAuthorOfPublication": "Authors", - "submission.sections.describe.relationship-lookup.title.isAuthorOfPublication": "AutorInnen", + "submission.sections.describe.relationship-lookup.title.isAuthorOfPublication": "Autor:innen", // "submission.sections.describe.relationship-lookup.title.isFundingAgencyOfPublication": "Funding Agency", "submission.sections.describe.relationship-lookup.title.isFundingAgencyOfPublication": "Fördereinrichtung", @@ -4794,14 +4725,13 @@ "submission.sections.describe.relationship-lookup.title.Publication": "Publikationen", // "submission.sections.describe.relationship-lookup.title.Person": "Authors", - "submission.sections.describe.relationship-lookup.title.Person": "AutorInnen", + "submission.sections.describe.relationship-lookup.title.Person": "Autor:innen", // "submission.sections.describe.relationship-lookup.title.OrgUnit": "Organizational Units", "submission.sections.describe.relationship-lookup.title.OrgUnit": "Organisationseinheiten", // "submission.sections.describe.relationship-lookup.title.DataPackage": "Data Packages", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.title.DataPackage": "Data Packages", + "submission.sections.describe.relationship-lookup.title.DataPackage": "Datenpakete", // "submission.sections.describe.relationship-lookup.title.DataFile": "Data Files", // TODO New key - Add a translation @@ -4811,12 +4741,10 @@ "submission.sections.describe.relationship-lookup.title.Funding Agency": "Fördereinrichtung", // "submission.sections.describe.relationship-lookup.title.isFundingOfPublication": "Funding", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.title.isFundingOfPublication": "Funding", + "submission.sections.describe.relationship-lookup.title.isFundingOfPublication": "Förderung", // "submission.sections.describe.relationship-lookup.title.isChildOrgUnitOf": "Parent Organizational Unit", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.title.isChildOrgUnitOf": "Parent Organizational Unit", + "submission.sections.describe.relationship-lookup.title.isChildOrgUnitOf": "Übergeordnete Organisationeinheit", // "submission.sections.describe.relationship-lookup.search-tab.toggle-dropdown": "Toggle dropdown", "submission.sections.describe.relationship-lookup.search-tab.toggle-dropdown": "Liste umschalten", @@ -4828,13 +4756,14 @@ "submission.sections.describe.relationship-lookup.selection-tab.no-selection": "Ihre Auswahl ist momentan leer.", // "submission.sections.describe.relationship-lookup.selection-tab.title.isAuthorOfPublication": "Selected Authors", - "submission.sections.describe.relationship-lookup.selection-tab.title.isAuthorOfPublication": "Ausgewählte AutorInnen", + "submission.sections.describe.relationship-lookup.selection-tab.title.isAuthorOfPublication": "Ausgewählte Autor:innen", // "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalOfPublication": "Selected Journals", "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalOfPublication": "Ausgewählte Zeitschriften", // "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalVolumeOfPublication": "Selected Journal Volume", "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalVolumeOfPublication": "Ausgewählte Zeitschriftenbände", + // "submission.sections.describe.relationship-lookup.selection-tab.title.Project": "Selected Projects", "submission.sections.describe.relationship-lookup.selection-tab.title.Project": "Ausgewählte Projekte", @@ -4842,14 +4771,13 @@ "submission.sections.describe.relationship-lookup.selection-tab.title.Publication": "Ausgewählte Publikationen", // "submission.sections.describe.relationship-lookup.selection-tab.title.Person": "Selected Authors", - "submission.sections.describe.relationship-lookup.selection-tab.title.Person": "Ausgewählte AutorInnen", + "submission.sections.describe.relationship-lookup.selection-tab.title.Person": "Ausgewählte Autor:innen", // "submission.sections.describe.relationship-lookup.selection-tab.title.OrgUnit": "Selected Organizational Units", "submission.sections.describe.relationship-lookup.selection-tab.title.OrgUnit": "Ausgewählte Organisationseinheiten", // "submission.sections.describe.relationship-lookup.selection-tab.title.DataPackage": "Selected Data Packages", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.selection-tab.title.DataPackage": "Selected Data Packages", + "submission.sections.describe.relationship-lookup.selection-tab.title.DataPackage": "Ausgewählte Datenpakete", // "submission.sections.describe.relationship-lookup.selection-tab.title.DataFile": "Selected Data Files", // TODO New key - Add a translation @@ -4860,6 +4788,7 @@ // "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalIssueOfPublication": "Selected Issue", "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalIssueOfPublication": "Ausgewählte Ausgaben", + // "submission.sections.describe.relationship-lookup.selection-tab.title.JournalVolume": "Selected Journal Volume", "submission.sections.describe.relationship-lookup.selection-tab.title.JournalVolume": "Ausgewählte Zeitschriftenbände", @@ -4867,8 +4796,8 @@ "submission.sections.describe.relationship-lookup.selection-tab.title.isFundingAgencyOfPublication": "Ausgewählte Fördereinrichtung", // "submission.sections.describe.relationship-lookup.selection-tab.title.isFundingOfPublication": "Selected Funding", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.selection-tab.title.isFundingOfPublication": "Selected Funding", + "submission.sections.describe.relationship-lookup.selection-tab.title.isFundingOfPublication": "Ausgewählte Förderung", + // "submission.sections.describe.relationship-lookup.selection-tab.title.JournalIssue": "Selected Issue", "submission.sections.describe.relationship-lookup.selection-tab.title.JournalIssue": "Ausgewählte Ausgabe", @@ -5110,7 +5039,7 @@ "submission.workflow.tasks.claimed.edit_help": "Wählen Sie diese Option, um die Metadaten der Ressource zu bearbeiten.", // "submission.workflow.tasks.claimed.reject.reason.info": "Please enter your reason for rejecting the submission into the box below, indicating whether the submitter may fix a problem and resubmit.", - "submission.workflow.tasks.claimed.reject.reason.info": "Bitte geben Sie den Grund für die Ablehnung der eingereichten Ressource in das Feld unten ein. Bitte geben Sie an ob und wie der/die Einreichenden das Problem beheben und die Ressource erneut einreichen kann.", + "submission.workflow.tasks.claimed.reject.reason.info": "Bitte geben Sie den Grund für die Ablehnung der eingereichten Ressource in das Feld unten ein. Bitte geben Sie an ob und wie der/die Einreichende:n das Problem beheben und die Ressource erneut einreichen kann.", // "submission.workflow.tasks.claimed.reject.reason.placeholder": "Describe the reason of reject", "submission.workflow.tasks.claimed.reject.reason.placeholder": "Beschreiben Sie den Grund für die Ablehnung", @@ -5142,7 +5071,7 @@ "submission.workflow.tasks.generic.processing": "Verarbeitung läuft...", // "submission.workflow.tasks.generic.submitter": "Submitter", - "submission.workflow.tasks.generic.submitter": "Einreichende(r)", + "submission.workflow.tasks.generic.submitter": "Einreichende:r", // "submission.workflow.tasks.generic.success": "Operation successful", "submission.workflow.tasks.generic.success": "Aktion erfolgreich", @@ -5169,32 +5098,25 @@ // "vocabulary-treeview.header": "Hierarchical tree view", - // TODO New key - Add a translation - "vocabulary-treeview.header": "Hierarchical tree view", + "vocabulary-treeview.header": "Hierarchische Baumstruktur", // "vocabulary-treeview.load-more": "Load more", - // TODO New key - Add a translation - "vocabulary-treeview.load-more": "Load more", + "vocabulary-treeview.load-more": "Mehr laden", // "vocabulary-treeview.search.form.reset": "Reset", - // TODO New key - Add a translation - "vocabulary-treeview.search.form.reset": "Reset", + "vocabulary-treeview.search.form.reset": "Zurücksetzen", // "vocabulary-treeview.search.form.search": "Search", - // TODO New key - Add a translation - "vocabulary-treeview.search.form.search": "Search", + "vocabulary-treeview.search.form.search": "Suche", // "vocabulary-treeview.search.no-result": "There were no items to show", - // TODO New key - Add a translation - "vocabulary-treeview.search.no-result": "There were no items to show", + "vocabulary-treeview.search.no-result": "Es gibt keine Items zu zeigen", // "vocabulary-treeview.tree.description.nsi": "The Norwegian Science Index", - // TODO New key - Add a translation - "vocabulary-treeview.tree.description.nsi": "The Norwegian Science Index", + "vocabulary-treeview.tree.description.nsi": "Der norwegische Wissenschaftsindex", // "vocabulary-treeview.tree.description.srsc": "Research Subject Categories", - // TODO New key - Add a translation - "vocabulary-treeview.tree.description.srsc": "Research Subject Categories", + "vocabulary-treeview.tree.description.srsc": "Kategorien von Forschungsthemen", @@ -5205,8 +5127,7 @@ "uploader.drag-message": "Ziehen Sie Ihre Dateien hierhin", // "uploader.or": ", or ", - // TODO Source message changed - Revise the translation - "uploader.or": " oder", + "uploader.or": ", oder", // "uploader.processing": "Processing", "uploader.processing": "Bearbeitung läuft", @@ -5226,74 +5147,57 @@ // "workflowAdmin.search.results.head": "Administer Workflow", - // TODO New key - Add a translation - "workflowAdmin.search.results.head": "Administer Workflow", + "workflowAdmin.search.results.head": "Workflow verwalten", // "workflow-item.delete.notification.success.title": "Deleted", - // TODO New key - Add a translation - "workflow-item.delete.notification.success.title": "Deleted", + "workflow-item.delete.notification.success.title": "Gelöscht", // "workflow-item.delete.notification.success.content": "This workflow item was successfully deleted", - // TODO New key - Add a translation - "workflow-item.delete.notification.success.content": "This workflow item was successfully deleted", + "workflow-item.delete.notification.success.content": "Dieses Workflow-Item wurde erfolgreich gelöscht", // "workflow-item.delete.notification.error.title": "Something went wrong", - // TODO New key - Add a translation - "workflow-item.delete.notification.error.title": "Something went wrong", + "workflow-item.delete.notification.error.title": "Etwas ist schief gelaufen", // "workflow-item.delete.notification.error.content": "The workflow item could not be deleted", - // TODO New key - Add a translation - "workflow-item.delete.notification.error.content": "The workflow item could not be deleted", + "workflow-item.delete.notification.error.content": "Das Workflow-Item konnte nicht gelöscht werden", // "workflow-item.delete.title": "Delete workflow item", - // TODO New key - Add a translation - "workflow-item.delete.title": "Delete workflow item", + "workflow-item.delete.title": "Workflow-Item löschen", // "workflow-item.delete.header": "Delete workflow item", - // TODO New key - Add a translation - "workflow-item.delete.header": "Delete workflow item", + "workflow-item.delete.header": "Workflow-Item löschen", // "workflow-item.delete.button.cancel": "Cancel", - // TODO New key - Add a translation - "workflow-item.delete.button.cancel": "Cancel", + "workflow-item.delete.button.cancel": "Abbrechen", // "workflow-item.delete.button.confirm": "Delete", - // TODO New key - Add a translation - "workflow-item.delete.button.confirm": "Delete", + "workflow-item.delete.button.confirm": "Löschen", // "workflow-item.send-back.notification.success.title": "Sent back to submitter", - // TODO New key - Add a translation - "workflow-item.send-back.notification.success.title": "Sent back to submitter", + "workflow-item.send-back.notification.success.title": "An die/den Einreichende:n zurückgeschickt", // "workflow-item.send-back.notification.success.content": "This workflow item was successfully sent back to the submitter", - // TODO New key - Add a translation - "workflow-item.send-back.notification.success.content": "This workflow item was successfully sent back to the submitter", + "workflow-item.send-back.notification.success.content": "Dieses Workflow-Item wurde erfolgreich an die/den Einreichende:n zurückgeschickt", // "workflow-item.send-back.notification.error.title": "Something went wrong", - // TODO New key - Add a translation - "workflow-item.send-back.notification.error.title": "Something went wrong", + "workflow-item.send-back.notification.error.title": "Etwas ist schief gelaufen", // "workflow-item.send-back.notification.error.content": "The workflow item could not be sent back to the submitter", - // TODO New key - Add a translation - "workflow-item.send-back.notification.error.content": "The workflow item could not be sent back to the submitter", + "workflow-item.send-back.notification.error.content": "Das Workflow-Item konnte nicht an die/den Einreichende:n zurückgeschickt werden", // "workflow-item.send-back.title": "Send workflow item back to submitter", - // TODO New key - Add a translation - "workflow-item.send-back.title": "Send workflow item back to submitter", + "workflow-item.send-back.title": "Workflow-Item an die/den Einreichende:n zurücksenden", // "workflow-item.send-back.header": "Send workflow item back to submitter", - // TODO New key - Add a translation - "workflow-item.send-back.header": "Send workflow item back to submitter", + "workflow-item.send-back.header": "Workflow-Item an die/den Einreichende:n zurücksenden", // "workflow-item.send-back.button.cancel": "Cancel", - // TODO New key - Add a translation - "workflow-item.send-back.button.cancel": "Cancel", + "workflow-item.send-back.button.cancel": "Abbrechen", // "workflow-item.send-back.button.confirm": "Send back" - // TODO New key - Add a translation - "workflow-item.send-back.button.confirm": "Send back" + "workflow-item.send-back.button.confirm": "Zurücksenden" } From 3bbd2d9d6405b802b9a020533c6c9dc9e37ecaa5 Mon Sep 17 00:00:00 2001 From: YPaulsen-TLC Date: Fri, 14 Jan 2022 14:05:41 +0100 Subject: [PATCH 041/160] message key translations Final German message keys translations for DSpace 7, --- src/assets/i18n/de.json5 | 454 +++++++++++++++++---------------------- 1 file changed, 194 insertions(+), 260 deletions(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 77105cc7aa..d3fc1131aa 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -483,8 +483,7 @@ "admin.access-control.groups.form.alert.permanent": "Diese Gruppe ist permanent, das heißt sie kann nicht bearbeitet oder gelöscht werden. Sie können jedoch über diese Seite Gruppenmitglieder hinzufügen und entfernen.", // "admin.access-control.groups.form.alert.workflowGroup": "This group can’t be modified or deleted because it corresponds to a role in the submission and workflow process in the \"{{name}}\" {{comcol}}. You can delete it from the \"assign roles\" tab on the edit {{comcol}} page. You can still add and remove group members using this page.", - // TODO New key - Add a translation - "admin.access-control.groups.form.alert.workflowGroup": "This group can’t be modified or deleted because it corresponds to a role in the submission and workflow process in the \"{{name}}\" {{comcol}}. You can delete it from the \"assign roles\" tab on the edit {{comcol}} page. You can still add and remove group members using this page.", + "admin.access-control.groups.form.alert.workflowGroup": "Diese Gruppe kann nicht geändert oder gelöscht werden, da sie einer Rolle im Einreichungs- und Workflow-Prozess im \"{{name}}\" {{comcol}} zugeordnet ist. Sie können sie über die Registerkarte \"assign roles\" auf der Bearbeitungsseite {{comcol}} löschen. Sie können über diese Seite weiterhin Gruppenmitglieder hinzufügen und entfernen.", // "admin.access-control.groups.form.head.create": "Create group", "admin.access-control.groups.form.head.create": "Gruppe erstellen", @@ -766,9 +765,8 @@ "bitstream.edit.form.description.label": "Beschreibung", // "bitstream.edit.form.embargo.hint": "The first day from which access is allowed. This date cannot be modified on this form. To set an embargo date for a bitstream, go to the Item Status tab, click Authorizations..., create or edit the bitstream's READ policy, and set the Start Date as desired.", - // TODO New key - Add a translation - "bitstream.edit.form.embargo.hint": "The first day from which access is allowed. This date cannot be modified on this form. To set an embargo date for a bitstream, go to the Item Status tab, click Authorizations..., create or edit the bitstream's READ policy, and set the Start Date as desired.", - // Der erste Tag, ab wann der Zugriff möglich ist. Dieses Datum kann in dieser Form nicht verändert werden. Um eine Embargo-Frist für eine Datei festzulegen, zu dem ()-Tab gehen, auf () klicken, die ()-Richtline für die Datei erstellen oder verändern und das Startdatum wie gewünscht einstellen. + "bitstream.edit.form.embargo.hint": "Der erste Tag, ab dem der Zugang erlaubt ist. This date cannot be modified on this form. Um eine Embargo-Frist für eine Datei festzulegen, zu Item Status gehen, auf Authorizations... klicken, die Richtlinie READ für die Datei erstellen oder verändern und das Start Date wie gewünscht einstellen.", + // "bitstream.edit.form.embargo.label": "Embargo until specific date", "bitstream.edit.form.embargo.label": "Embargo bis zu einem bestimmten Datum", @@ -827,7 +825,7 @@ "browse.comcol.head": "Listen", // "browse.empty": "No items to show.", - "browse.empty": "Es gibt keine Dokumente, die angezeigt werden können.", + "browse.empty": "Es gibt keine Items, die angezeigt werden können.", // "browse.metadata.author": "Author", "browse.metadata.author": "Autor:in", @@ -963,10 +961,10 @@ // "collection.edit.tabs.mapper.head": "Item Mapper", - "collection.edit.tabs.mapper.head": "Ressource spiegeln", + "collection.edit.tabs.mapper.head": "Item spiegeln", // "collection.edit.tabs.item-mapper.title": "Collection Edit - Item Mapper", - "collection.edit.tabs.item-mapper.title": "Sammlung bearbeiten - Ressource spiegeln", + "collection.edit.tabs.item-mapper.title": "Sammlung bearbeiten - Item spiegeln", // "collection.edit.item-mapper.cancel": "Cancel", "collection.edit.item-mapper.cancel": "Abbrechen", @@ -975,13 +973,13 @@ "collection.edit.item-mapper.collection": "Sammlung: \"{{name}}\"", // "collection.edit.item-mapper.confirm": "Map selected items", - "collection.edit.item-mapper.confirm": "Ausgewählte Ressourcen spiegeln", + "collection.edit.item-mapper.confirm": "Ausgewählte Items spiegeln", // "collection.edit.item-mapper.description": "This is the item mapper tool that allows collection administrators to map items from other collections into this collection. You can search for items from other collections and map them, or browse the list of currently mapped items.", - "collection.edit.item-mapper.description": "Sammlungsadministrator:innen haben die Möglichkeit Ressourcen von einer Sammlung in eine andere zu spiegeln. Man kann nach Ressourcen in anderen Sammlungen suchen und diese spiegeln oder sich eine Liste der gespiegelten Ressourcen anzeigen lassen.", + "collection.edit.item-mapper.description": "Sammlungsadministrator:innen haben die Möglichkeit Items von einer Sammlung in eine andere zu spiegeln. Man kann nach Items in anderen Sammlungen suchen und diese spiegeln oder sich eine Liste der gespiegelten Items anzeigen lassen.", // "collection.edit.item-mapper.head": "Item Mapper - Map Items from Other Collections", - "collection.edit.item-mapper.head": "Ressourcen spiegeln - Spiegelt Ressourcen aus anderen Sammlungen", + "collection.edit.item-mapper.head": "Item spiegeln - Spiegelt Items aus anderen Sammlungen", // "collection.edit.item-mapper.no-search": "Please enter a query to search", "collection.edit.item-mapper.no-search": "Bitte geben Sie eine Suchanfrage ein.", @@ -993,13 +991,13 @@ "collection.edit.item-mapper.notifications.map.error.head": "Fehler beim Spiegeln", // "collection.edit.item-mapper.notifications.map.success.content": "Successfully mapped {{amount}} items.", - "collection.edit.item-mapper.notifications.map.success.content": "{{amount}} Ressource(n) erfolgreich gespiegelt.", + "collection.edit.item-mapper.notifications.map.success.content": "{{amount}} Item(s) erfolgreich gespiegelt.", // "collection.edit.item-mapper.notifications.map.success.head": "Mapping completed", "collection.edit.item-mapper.notifications.map.success.head": "Spiegelung abgeschlossen", // "collection.edit.item-mapper.notifications.unmap.error.content": "Errors occurred for removing the mappings of {{amount}} items.", - "collection.edit.item-mapper.notifications.unmap.error.content": "Beim Spiegeln von {{amount}} Ressource(n) ist ein Fehler aufgetreten.", + "collection.edit.item-mapper.notifications.unmap.error.content": "Beim Spiegeln von {{amount}} Item(s) ist ein Fehler aufgetreten.", // "collection.edit.item-mapper.notifications.unmap.error.head": "Remove mapping errors", "collection.edit.item-mapper.notifications.unmap.error.head": "Spiegeln Entfernen Fehler", @@ -1011,13 +1009,13 @@ "collection.edit.item-mapper.notifications.unmap.success.head": "Spiegelung entfernen abgeschlossen", // "collection.edit.item-mapper.remove": "Remove selected item mappings", - "collection.edit.item-mapper.remove": "Spiegelung der gewählten Ressourcen entfernen.", + "collection.edit.item-mapper.remove": "Spiegelung der gewählten Items entfernen.", // "collection.edit.item-mapper.tabs.browse": "Browse mapped items", - "collection.edit.item-mapper.tabs.browse": "Gespiegelte Ressourcen auflisten", + "collection.edit.item-mapper.tabs.browse": "Gespiegelte Items auflisten", // "collection.edit.item-mapper.tabs.map": "Map new items", - "collection.edit.item-mapper.tabs.map": "Neue Ressourcen spiegeln", + "collection.edit.item-mapper.tabs.map": "Neue Items spiegeln", @@ -1062,8 +1060,7 @@ "collection.edit.tabs.authorizations.head": "Berechtigungen", // "collection.edit.tabs.authorizations.title": "Collection Edit - Authorizations", - // TODO New key - Add a translation - "collection.edit.tabs.authorizations.title": "Collection Edit - Authorizations", + "collection.edit.tabs.authorizations.title": "Sammlung bearbeiten - Berechtigungen", // "collection.edit.tabs.metadata.head": "Edit Metadata", "collection.edit.tabs.metadata.head": "Metadaten bearbeiten", @@ -1149,20 +1146,19 @@ "collection.edit.template.edit-button": "Bearbeiten", // "collection.edit.template.head": "Edit Template Item for Collection \"{{ collection }}\"", - // TODO New key - Add a translation - "collection.edit.template.head": "Itemvorlage für Collection \"{{ collection }}\" bearbeiten", + "collection.edit.template.head": "Itemvorlage für Sammlung \"{{ collection }}\" bearbeiten", // "collection.edit.template.label": "Template item", "collection.edit.template.label": "Itemvorlage", // "collection.edit.template.notifications.delete.error": "Failed to delete the item template", - "collection.edit.template.notifications.delete.error": "Löschen der Itemvorlage fehlgeschlagen", + "collection.edit.template.notifications.delete.error": "Löschen der Metadatenvorlage fehlgeschlagen", // "collection.edit.template.notifications.delete.success": "Successfully deleted the item template", - "collection.edit.template.notifications.delete.success": "Die Itemvorlage wurde erfolgreich gelöscht", + "collection.edit.template.notifications.delete.success": "Die Metadatenvorlage wurde erfolgreich gelöscht", // "collection.edit.template.title": "Edit Template Item", - "collection.edit.template.title": "Itemvorlage bearbeiten", + "collection.edit.template.title": "Metadatenvorlage bearbeiten", @@ -1201,7 +1197,7 @@ "collection.page.browse.recent.head": "Neueste Veröffentlichungen", // "collection.page.browse.recent.empty": "No items to show", - "collection.page.browse.recent.empty": "Es gibt keine Ressourcen zum Anzeigen", + "collection.page.browse.recent.empty": "Es gibt keine Items zum Anzeigen", // "collection.page.edit": "Edit this collection", "collection.page.edit": "Diese Sammlung bearbeiten", @@ -1362,8 +1358,7 @@ "comcol-role.edit.create": "Erstellen", // "comcol-role.edit.restrict": "Restrict", - // TODO New key - Add a translation - "comcol-role.edit.restrict": "Restrict", + "comcol-role.edit.restrict": "Beschränken", // "comcol-role.edit.delete": "Delete", "comcol-role.edit.delete": "Löschen", @@ -1377,29 +1372,24 @@ // "comcol-role.edit.community-admin.description": "Community administrators can create sub-communities or collections, and manage or assign management for those sub-communities or collections. In addition, they decide who can submit items to any sub-collections, edit item metadata (after submission), and add (map) existing items from other collections (subject to authorization).", - // TODO New key - Add a translation - "comcol-role.edit.community-admin.description": "Community administrators can create sub-communities or collections, and manage or assign management for those sub-communities or collections. In addition, they decide who can submit items to any sub-collections, edit item metadata (after submission), and add (map) existing items from other collections (subject to authorization).", + "comcol-role.edit.community-admin.description": "Community-Administrator:innen können Teilbereiche oder Sammlungen erstellen, verwalten oder die Verwaltung anderen zuweisen. Darüber hinaus entscheiden sie, wer Items zu einer Teilsammlung einreichen, Item-Metadaten bearbeiten (nach der Einreichung) und bestehende Items von anderen Sammlungen verknüpfen kann (vorbehaltlich einer Berechtigung).", // "comcol-role.edit.collection-admin.description": "Collection administrators decide who can submit items to the collection, edit item metadata (after submission), and add (map) existing items from other collections to this collection (subject to authorization for that collection).", - // TODO New key - Add a translation - "comcol-role.edit.collection-admin.description": "Collection administrators decide who can submit items to the collection, edit item metadata (after submission), and add (map) existing items from other collections to this collection (subject to authorization for that collection).", + "comcol-role.edit.collection-admin.description": "Sammlungsadministrator:innen entscheiden, wer Items zu einer Sammlung einreichen, Item-Metadaten bearbeiten (nach der Einreichung) and und bestehende Items von anderen Sammlungen mit dieser Sammlung verknüpfen kann (vorbehaltlich einer Berechtigung für diese Sammlung).", // "comcol-role.edit.submitters.name": "Submitters", "comcol-role.edit.submitters.name": "Einreichende", // "comcol-role.edit.submitters.description": "The E-People and Groups that have permission to submit new items to this collection.", - // TODO New key - Add a translation - "comcol-role.edit.submitters.description": "The E-People and Groups that have permission to submit new items to this collection.", + "comcol-role.edit.submitters.description": "Die Personen und Gruppen, die die Berechtigungen haben, neue Items zu dieser Sammlung einzureichen.", // "comcol-role.edit.item_read.name": "Default item read access", - // TODO New key - Add a translation - "comcol-role.edit.item_read.name": "Default item read access", + "comcol-role.edit.item_read.name": "Standard-Lesezugriff Item", // "comcol-role.edit.item_read.description": "E-People and Groups that can read new items submitted to this collection. Changes to this role are not retroactive. Existing items in the system will still be viewable by those who had read access at the time of their addition.", - // TODO New key - Add a translation - "comcol-role.edit.item_read.description": "E-People and Groups that can read new items submitted to this collection. Changes to this role are not retroactive. Existing items in the system will still be viewable by those who had read access at the time of their addition.", + "comcol-role.edit.item_read.description": "Personen und Gruppen, die neu eingereichte Items dieser Sammlung lesen können. Änderungen an dieser Rolle sind nicht rückwirkend. Bestehende Items im System können weiterhin von denjenigen eingesehen werden, die zum Zeitpunkt des Hinzufügens Lesezugriff hatten.", // "comcol-role.edit.item_read.anonymous-group": "Default read for incoming items is currently set to Anonymous.", "comcol-role.edit.item_read.anonymous-group": "Die Standardeinstellung für das Lesen eingehender Items ist derzeit auf Anonym eingestellt.", @@ -1409,8 +1399,7 @@ "comcol-role.edit.bitstream_read.name": "Standard-Lesezugriff Datei", // "comcol-role.edit.bitstream_read.description": "Community administrators can create sub-communities or collections, and manage or assign management for those sub-communities or collections. In addition, they decide who can submit items to any sub-collections, edit item metadata (after submission), and add (map) existing items from other collections (subject to authorization).", - // TODO New key - Add a translation - "comcol-role.edit.bitstream_read.description": "Community administrators can create sub-communities or collections, and manage or assign management for those sub-communities or collections. In addition, they decide who can submit items to any sub-collections, edit item metadata (after submission), and add (map) existing items from other collections (subject to authorization).", + "comcol-role.edit.bitstream_read.description": "Community-Administrator:innen können Teilbereiche oder Sammlungen erstellen, verwalten oder die Verwaltung anderen zuweisen. Darüber hinaus entscheiden sie, wer Items zu einer Teilsammlung einreichen, Item-Metadaten bearbeiten (nach der Einreichung) und bestehende Items von anderen Sammlungen verknüpfen kann (vorbehaltlich einer Berechtigung).", // "comcol-role.edit.bitstream_read.anonymous-group": "Default read for incoming bitstreams is currently set to Anonymous.", "comcol-role.edit.bitstream_read.anonymous-group": "Die Standardeinstellung für das Lesen eingehender Dateien ist derzeit auf Anonym eingestellt.", @@ -1486,11 +1475,9 @@ "cookies.consent.accept-selected": "Ausgewählte annehmen", // "cookies.consent.app.opt-out.description": "This app is loaded by default (but you can opt out)", - // TODO New key - Add a translation - "cookies.consent.app.opt-out.description": "This app is loaded by default (but you can opt out)", + "cookies.consent.app.opt-out.description": "Diese App wird standardmäßig geladen (Sie können dies jedoch deaktivieren)", // "cookies.consent.app.opt-out.title": "(opt-out)", - // TODO New key - Add a translation "cookies.consent.app.opt-out.title": "(opt-out)", // "cookies.consent.app.purpose": "purpose", @@ -1512,8 +1499,7 @@ "cookies.consent.decline": "Ablehnen", // "cookies.consent.content-notice.description": "We collect and process your personal information for the following purposes: Authentication, Preferences, Acknowledgement and Statistics.
    To learn more, please read our {privacyPolicy}.", - // TODO New key - Add a translation - "cookies.consent.content-notice.description": "We collect and process your personal information for the following purposes: Authentication, Preferences, Acknowledgement and Statistics.
    To learn more, please read our {privacyPolicy}.", + "cookies.consent.content-notice.description": "Wir sammeln und verarbeiten Ihre personenbezogenen Daten für die folgenden Zwecke: Authentication, Preferences, Acknowledgement and Statistics.
    Um mehr zu erfahren, lesen Sie bitte unsere {privacyPolicy}.", // "cookies.consent.content-notice.learnMore": "Customize", "cookies.consent.content-notice.learnMore": "Anpassen", @@ -1574,12 +1560,10 @@ "curation-task.task.checklinks.label": "Links in Metadaten prüfen", // "curation-task.task.noop.label": "NOOP", - // TODO New key - Add a translation "curation-task.task.noop.label": "NOOP", // "curation-task.task.profileformats.label": "Profile Bitstream Formats", - // TODO New key - Add a translation - "curation-task.task.profileformats.label": "Profile Bitstream Formats", + "curation-task.task.profileformats.label": "Dateiformate auflisten", // "curation-task.task.requiredmetadata.label": "Check for Required Metadata", "curation-task.task.requiredmetadata.label": "Prüfung auf erforderliche Metadaten", @@ -1614,8 +1598,7 @@ "curation.form.handle.label": "Handle:", // "curation.form.handle.hint": "Hint: Enter [your-handle-prefix]/0 to run a task across entire site (not all tasks may support this capability)", - // TODO New key - Add a translation - "curation.form.handle.hint": "Hint: Enter [your-handle-prefix]/0 to run a task across entire site (not all tasks may support this capability)", + "curation.form.handle.hint": "Hinweis: Geben Sie [your-handle-prefix]/0 ein, um eine Aufgabe für die gesamte Seite durchzuführen (nicht alle Aufgaben unterstützen diese Funktion)", @@ -1635,7 +1618,7 @@ "dso-selector.create.community.top-level": "Einen neuen Bereich auf oberster Ebene anlgen", // "dso-selector.create.item.head": "New item", - "dso-selector.create.item.head": "Neue Ressource", + "dso-selector.create.item.head": "Neues Item", // "dso-selector.create.item.sub-level": "Create a new item in", "dso-selector.create.item.sub-level": "Ein neues Item erstellen in", @@ -1650,7 +1633,7 @@ "dso-selector.edit.community.head": "Bereich bearbeiten", // "dso-selector.edit.item.head": "Edit item", - "dso-selector.edit.item.head": "Ressource bearbeiten", + "dso-selector.edit.item.head": "Item bearbeiten", // "dso-selector.export-metadata.dspaceobject.head": "Export metadata from", "dso-selector.export-metadata.dspaceobject.head": "Exportieren der Metadaten aus", @@ -1689,11 +1672,10 @@ // "error.bitstream": "Error fetching bitstream", - // TODO New key - Add a translation - "error.bitstream": "Error fetching bitstream", + "error.bitstream": "Fehler beim Laden der Datei", // "error.browse-by": "Error fetching items", - "error.browse-by": "Fehler beim Laden der Ressourcen", + "error.browse-by": "Fehler beim Laden der Items", // "error.collection": "Error fetching collection", "error.collection": "Fehler beim Laden der Sammlung", @@ -1705,16 +1687,16 @@ "error.community": "Fehler beim Laden des Bereichs", // "error.identifier": "No item found for the identifier", - "error.identifier": "Zu dieser Kennung wurde keine Ressource gefunden", + "error.identifier": "Zu dieser Kennung wurde kein Item gefunden", // "error.default": "Error", "error.default": "Fehler", // "error.item": "Error fetching item", - "error.item": "Fehler beim Laden der Ressource", + "error.item": "Fehler beim Laden des Items", // "error.items": "Error fetching items", - "error.items": "Fehler beim Laden der Ressourcen", + "error.items": "Fehler beim Laden der Items", // "error.objects": "Error fetching objects", "error.objects": "Fehler beim Laden der Objekte", @@ -1777,8 +1759,7 @@ "forgot-email.form.header": "Passwort vergessen", // "forgot-email.form.info": "Enter Register an account to subscribe to collections for email updates, and submit new items to DSpace.", - // TODO New key - Add a translation - "forgot-email.form.info": "Enter Register an account to subscribe to collections for email updates, and submit new items to DSpace.", + "forgot-email.form.info": "Geben Sie einen Account ein, um Sammlungen für E-Mail-Updates zu abonnieren und neue Items an DSpace zu übermitteln.", // "forgot-email.form.email": "Email Address *", "forgot-email.form.email": "E-Mail-Addresse *", @@ -1799,14 +1780,12 @@ "forgot-email.form.success.head": "Verifizierungs-E-Mail gesendet", // "forgot-email.form.success.content": "An email has been sent to {{ email }} containing a special URL and further instructions.", - // TODO New key - Add a translation - "forgot-email.form.success.content": "An email has been sent to {{ email }} containing a special URL and further instructions.", + "forgot-email.form.success.content": "Es wurde eine E-Mail mit einer speziellen URL und weiteren Anweisungen an {{ email }} gesendet.", // "forgot-email.form.error.head": "Error when trying to register email", "forgot-email.form.error.head": "Fehler beim Versuch, die E-Mail zu registrieren", // "forgot-email.form.error.content": "An error occured when registering the following email address: {{ email }}", - // TODO New key - Add a translation "forgot-email.form.error.content": "Bei der Registrierung der folgenden E-Mail-Adresse ist ein Fehler aufgetreten: {{ email }}", @@ -1999,8 +1978,7 @@ // "item.edit.authorizations.heading": "With this editor you can view and alter the policies of an item, plus alter policies of individual item components: bundles and bitstreams. Briefly, an item is a container of bundles, and bundles are containers of bitstreams. Containers usually have ADD/REMOVE/READ/WRITE policies, while bitstreams only have READ/WRITE policies.", - // TODO New key - Add a translation - "item.edit.authorizations.heading": "With this editor you can view and alter the policies of an item, plus alter policies of individual item components: bundles and bitstreams. Briefly, an item is a container of bundles, and bundles are containers of bitstreams. Containers usually have ADD/REMOVE/READ/WRITE policies, while bitstreams only have READ/WRITE policies.", + "item.edit.authorizations.heading": "Mit diesem Editor können Sie die Richtlinien eines Items anzeigen und ändern sowie die Richtlinien der einzelnen Itemkomponenten, d. h. Bündel und Dateien, ändern. Kurz gesagt, ein Item ist ein Container von Bündeln, und Bündel sind Container von Dateien. Container haben normalerweise ADD/REMOVE/READ/WRITE-Richtlinien, während Dateien nur READ/WRITE-Richtlinien haben.", // "item.edit.authorizations.title": "Edit item's Policies", "item.edit.authorizations.title": "Item-Richtlinien bearbeiten", @@ -2025,8 +2003,7 @@ "item.bitstreams.upload.bundle.new": "Bündel erstellen", // "item.bitstreams.upload.bundles.empty": "This item doesn\'t contain any bundles to upload a bitstream to.", - // TODO New key - Add a translation - "item.bitstreams.upload.bundles.empty": "This item doesn\'t contain any bundles to upload a bitstream to.", + "item.bitstreams.upload.bundles.empty": "Dieses Item enthält keine Bündel, um eine Datei hochzuladen.", // "item.bitstreams.upload.cancel": "Cancel", "item.bitstreams.upload.cancel": "Abbrechen", @@ -2055,8 +2032,7 @@ "item.edit.bitstreams.bundle.edit.buttons.upload": "Hochladen", // "item.edit.bitstreams.bundle.displaying": "Currently displaying {{ amount }} bitstreams of {{ total }}.", - // TODO New key - Add a translation - "item.edit.bitstreams.bundle.displaying": "Currently displaying {{ amount }} bitstreams of {{ total }}.", + "item.edit.bitstreams.bundle.displaying": "Derzeit werden {{ amount }} Dateien von insgesamt {{ total }} angezeigt.", // "item.edit.bitstreams.bundle.load.all": "Load all ({{ total }})", "item.edit.bitstreams.bundle.load.all": "Alle ({{ total }}) laden", @@ -2086,7 +2062,7 @@ "item.edit.bitstreams.edit.buttons.undo": "Änderungen rückgängig machen", // "item.edit.bitstreams.empty": "This item doesn't contain any bitstreams. Click the upload button to create one.", - "item.edit.bitstreams.empty": "Diese Ressource enthält keine Dateien. Klicken Sie auf das Feld Hochladen, um eine zu erstellen.", + "item.edit.bitstreams.empty": "Dieses Item enthält keine Dateien. Klicken Sie auf das Feld Hochladen, um eine zu erstellen.", // "item.edit.bitstreams.headers.actions": "Actions", "item.edit.bitstreams.headers.actions": "Aktionen", @@ -2113,15 +2089,13 @@ "item.edit.bitstreams.notifications.move.failed.title": "Fehler beim Verschieben von Dateien", // "item.edit.bitstreams.notifications.move.saved.content": "Your move changes to this item's bitstreams and bundles have been saved.", - // TODO New key - Add a translation - "item.edit.bitstreams.notifications.move.saved.content": "Your move changes to this item's bitstreams and bundles have been saved.", + "item.edit.bitstreams.notifications.move.saved.content": "Die von Ihnen vorgenommenen Änderungen an den Dateien und Bündeln dieses Artikels wurden gespeichert.", // "item.edit.bitstreams.notifications.move.saved.title": "Move changes saved", - // TODO New key - Add a translation - "item.edit.bitstreams.notifications.move.saved.title": "Move changes saved", + "item.edit.bitstreams.notifications.move.saved.title": "Änderungen gespeichert", // "item.edit.bitstreams.notifications.outdated.content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts", - "item.edit.bitstreams.notifications.outdated.content": "Die Ressource, an der Sie gerade arbeiten, wurde von einem anderen Account geändert. Ihre aktuellen Änderungen werden verworfen, um Konflikte zu vermeiden.", + "item.edit.bitstreams.notifications.outdated.content": "Das Item, an der Sie gerade arbeiten, wurde von einem anderen Account geändert. Ihre aktuellen Änderungen werden verworfen, um Konflikte zu vermeiden.", // "item.edit.bitstreams.notifications.outdated.title": "Changes outdated", "item.edit.bitstreams.notifications.outdated.title": "Veraltete Änderungen", @@ -2130,12 +2104,10 @@ "item.edit.bitstreams.notifications.remove.failed.title": "Fehler beim Löschen der Datei", // "item.edit.bitstreams.notifications.remove.saved.content": "Your removal changes to this item's bitstreams have been saved.", - // TODO New key - Add a translation - "item.edit.bitstreams.notifications.remove.saved.content": "Your removal changes to this item's bitstreams have been saved.", + "item.edit.bitstreams.notifications.remove.saved.content": "Ihre Entfernungsänderungen an den Dateien dieses Items wurden gespeichert.", // "item.edit.bitstreams.notifications.remove.saved.title": "Removal changes saved", - // TODO New key - Add a translation - "item.edit.bitstreams.notifications.remove.saved.title": "Removal changes saved", + "item.edit.bitstreams.notifications.remove.saved.title": "Änderungen gespeichert", // "item.edit.bitstreams.reinstate-button": "Undo", "item.edit.bitstreams.reinstate-button": "Rückgängig", @@ -2155,59 +2127,59 @@ "item.edit.delete.confirm": "Löschen", // "item.edit.delete.description": "Are you sure this item should be completely deleted? Caution: At present, no tombstone would be left.", - "item.edit.delete.description": "Sind Sie sicher, dass diese Ressource komplett gelöscht werden soll. Warnung: Zur Zeit wird kein Grabstein hinterlassen", + "item.edit.delete.description": "Sind Sie sicher, dass dieses Item komplett gelöscht werden soll. Warnung: Zur Zeit wird kein Grabstein hinterlassen", // "item.edit.delete.error": "An error occurred while deleting the item", "item.edit.delete.error": "Beim Löschen der Ressource ist ein Fehler aufgetreten", // "item.edit.delete.header": "Delete item: {{ id }}", - "item.edit.delete.header": "Löschen der Ressource: {{ id }}", + "item.edit.delete.header": "Löschen des Items: {{ id }}", // "item.edit.delete.success": "The item has been deleted", - "item.edit.delete.success": "Die Ressource wurde gelöscht", + "item.edit.delete.success": "Das Item wurde gelöscht", // "item.edit.head": "Edit Item", - "item.edit.head": "Ressource bearbeiten", + "item.edit.head": "Item bearbeiten", // "item.edit.breadcrumbs": "Edit Item", - "item.edit.breadcrumbs": "Ressource bearbeiten", + "item.edit.breadcrumbs": "Item bearbeiten", // "item.edit.tabs.mapper.head": "Collection Mapper", "item.edit.tabs.mapper.head": "Spiegeln", // "item.edit.tabs.item-mapper.title": "Item Edit - Collection Mapper", - "item.edit.tabs.item-mapper.title": "Ressource bearbeiten - Spiegeln", + "item.edit.tabs.item-mapper.title": "Item bearbeiten - Spiegeln", // "item.edit.item-mapper.buttons.add": "Map item to selected collections", - "item.edit.item-mapper.buttons.add": "Ressource in die gewählte(n) Sammlung(en) spiegeln", + "item.edit.item-mapper.buttons.add": "Item in die gewählte(n) Sammlung(en) spiegeln", // "item.edit.item-mapper.buttons.remove": "Remove item's mapping for selected collections", - "item.edit.item-mapper.buttons.remove": "Spiegelung der Ressource aus der/den gewählten Sammlung(en) entfernen", + "item.edit.item-mapper.buttons.remove": "Spiegelung des Items aus der/den gewählten Sammlung(en) entfernen", // "item.edit.item-mapper.cancel": "Cancel", "item.edit.item-mapper.cancel": "Abbrechen", // "item.edit.item-mapper.description": "This is the item mapper tool that allows administrators to map this item to other collections. You can search for collections and map them, or browse the list of collections the item is currently mapped to.", - "item.edit.item-mapper.description": "Sammlungsadministrator:innen haben die Möglichkeit Ressourcen von einer Sammlung in eine andere zu spiegeln. Man kann nach Ressourcen in anderen Sammlungen suchen und diese spiegeln oder sich eine Liste der gespiegelten Ressourcen anzeigen lassen.", + "item.edit.item-mapper.description": "Sammlungsadministrator:innen haben die Möglichkeit Items von einer Sammlung in eine andere zu spiegeln. Man kann nach Items in anderen Sammlungen suchen und diese spiegeln oder sich eine Liste der gespiegelten Items anzeigen lassen.", // "item.edit.item-mapper.head": "Item Mapper - Map Item to Collections", - "item.edit.item-mapper.head": "Ressourcen spiegeln - Spiegelt eine Ressource in andere Sammlungen", + "item.edit.item-mapper.head": "Item spiegeln - Spiegelt ein Item in andere Sammlungen", // "item.edit.item-mapper.item": "Item: \"{{name}}\"", - "item.edit.item-mapper.item": "Ressource: \"{{name}}\"", + "item.edit.item-mapper.item": "Item: \"{{name}}\"", // "item.edit.item-mapper.no-search": "Please enter a query to search", "item.edit.item-mapper.no-search": "Bitte geben Sie einen Suchbegriff ein", // "item.edit.item-mapper.notifications.add.error.content": "Errors occurred for mapping of item to {{amount}} collections.", - "item.edit.item-mapper.notifications.add.error.content": "Beim Spiegeln der Ressource in {{amount}} Sammlung(en) ist ein Fehler aufgetreten.", + "item.edit.item-mapper.notifications.add.error.content": "Beim Spiegeln des Items in {{amount}} Sammlung(en) ist ein Fehler aufgetreten.", // "item.edit.item-mapper.notifications.add.error.head": "Mapping errors", "item.edit.item-mapper.notifications.add.error.head": "Fehler beim Spiegeln", // "item.edit.item-mapper.notifications.add.success.content": "Successfully mapped item to {{amount}} collections.", - "item.edit.item-mapper.notifications.add.success.content": "Ressource erfolgreich in {{amount}} Sammlung(en) gespiegelt.", + "item.edit.item-mapper.notifications.add.success.content": "Item erfolgreich in {{amount}} Sammlung(en) gespiegelt.", // "item.edit.item-mapper.notifications.add.success.head": "Mapping completed", "item.edit.item-mapper.notifications.add.success.head": "Spiegeln abgeschlossen", @@ -2216,10 +2188,10 @@ "item.edit.item-mapper.notifications.remove.error.content": "Beim Entfernen der Spiegelung in {{amount}} Sammlung(en) ist ein Fehler aufgetreten.", // "item.edit.item-mapper.notifications.remove.error.head": "Removal of mapping errors", - "item.edit.item-mapper.notifications.remove.error.head": "Fehler beim Entfernen von gespiegelten Ressourcen", + "item.edit.item-mapper.notifications.remove.error.head": "Fehler beim Entfernen von gespiegelten Items", // "item.edit.item-mapper.notifications.remove.success.content": "Successfully removed mapping of item to {{amount}} collections.", - "item.edit.item-mapper.notifications.remove.success.content": "Spiegelung der Ressource aus {{amount}} Sammlung(en) erfolgreich.", + "item.edit.item-mapper.notifications.remove.success.content": "Spiegelung des Items aus {{amount}} Sammlung(en) erfolgreich.", // "item.edit.item-mapper.notifications.remove.success.head": "Removal of mapping completed", "item.edit.item-mapper.notifications.remove.success.head": "Entfernen der Spiegelung abgeschlossen", @@ -2251,7 +2223,7 @@ "item.edit.metadata.edit.buttons.unedit": "Bearbeitung beenden", // "item.edit.metadata.empty": "The item currently doesn't contain any metadata. Click Add to start adding a metadata value.", - "item.edit.metadata.empty": "Die Ressource enthält derzeit keine Metadaten. Klicken Sie auf Hinzufügen, um einen Metadatenwert hinzuzufügen.", + "item.edit.metadata.empty": "Das Item enthält derzeit keine Metadaten. Klicken Sie auf Hinzufügen, um einen Metadatenwert hinzuzufügen.", // "item.edit.metadata.headers.edit": "Edit", "item.edit.metadata.headers.edit": "Bearbeiten", @@ -2284,13 +2256,13 @@ "item.edit.metadata.notifications.invalid.title": "Metadaten ungültig", // "item.edit.metadata.notifications.outdated.content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts", - "item.edit.metadata.notifications.outdated.content": "Die Ressource, an der Sie gerade arbeiten, wurde von einem anderen Account geändert. Ihre aktuellen Änderungen werden nicht angewandt, um Konflikte zu vermeiden.", + "item.edit.metadata.notifications.outdated.content": "Das Item, an dem Sie gerade arbeiten, wurde von einem anderen Account geändert. Ihre aktuellen Änderungen werden nicht angewandt, um Konflikte zu vermeiden.", // "item.edit.metadata.notifications.outdated.title": "Changed outdated", "item.edit.metadata.notifications.outdated.title": "Änderung veraltet", // "item.edit.metadata.notifications.saved.content": "Your changes to this item's metadata were saved.", - "item.edit.metadata.notifications.saved.content": "Ihre Änderungen an den Metadaten der Ressource wurden gespeichert.", + "item.edit.metadata.notifications.saved.content": "Ihre Änderungen an den Metadaten des Items wurden gespeichert.", // "item.edit.metadata.notifications.saved.title": "Metadata saved", "item.edit.metadata.notifications.saved.title": "Metadaten gespeichert", @@ -2321,10 +2293,10 @@ "item.edit.move.description": "Select the collection you wish to move this item to. To narrow down the list of displayed collections, you can enter a search query in the box.", // "item.edit.move.error": "An error occurred when attempting to move the item", - "item.edit.move.error": "Ein Fehler ist beim Verschieben der Ressource aufgetreten", + "item.edit.move.error": "Ein Fehler ist beim Verschieben des Items aufgetreten", // "item.edit.move.head": "Move item: {{id}}", - "item.edit.move.head": "Ressource verschieben: {{id}}", + "item.edit.move.head": "Item verschieben: {{id}}", // "item.edit.move.inheritpolicies.checkbox": "Inherit policies", "item.edit.move.inheritpolicies.checkbox": "Rechte erben", @@ -2342,10 +2314,10 @@ "item.edit.move.search.placeholder": "Geben Sie einen Begriff ein, um nach Sammlungen zu suchen", // "item.edit.move.success": "The item has been moved successfully", - "item.edit.move.success": "Die Ressource wurde erfolgreich verschoben", + "item.edit.move.success": "Das Item wurde erfolgreich verschoben", // "item.edit.move.title": "Move item", - "item.edit.move.title": "Ressource verschieben", + "item.edit.move.title": "Item verschieben", @@ -2356,16 +2328,16 @@ "item.edit.private.confirm": "Privat machen", // "item.edit.private.description": "Are you sure this item should be made private in the archive?", - "item.edit.private.description": "Wollen Sie diese Ressource als privat markieren.", + "item.edit.private.description": "Wollen Sie dieses Item als privat markieren.", // "item.edit.private.error": "An error occurred while making the item private", - "item.edit.private.error": "Ein Fehler ist aufgetreten bei dem Versuch, die Ressource privat zu machen", + "item.edit.private.error": "Ein Fehler ist aufgetreten bei dem Versuch, das Item privat zu machen", // "item.edit.private.header": "Make item private: {{ id }}", - "item.edit.private.header": "Ressource: {{ id }} privat machen", + "item.edit.private.header": "Item: {{ id }} privat machen", // "item.edit.private.success": "The item is now private", - "item.edit.private.success": "Diese Ressource ist nun privat", + "item.edit.private.success": "Dieses Item ist nun privat", @@ -2376,16 +2348,16 @@ "item.edit.public.confirm": "Öffentlich machen", // "item.edit.public.description": "Are you sure this item should be made public in the archive?", - "item.edit.public.description": "Sind Sie sicher, dass diese Ressource im Repositorium öffentlich gemacht werden soll?", + "item.edit.public.description": "Sind Sie sicher, dass dieses Item im Repositorium öffentlich gemacht werden soll?", // "item.edit.public.error": "An error occurred while making the item public", - "item.edit.public.error": "Ein Fehler ist aufgetreten, als die Ressource öffentlich gemacht werden sollte.", + "item.edit.public.error": "Ein Fehler ist aufgetreten, als das Item öffentlich gemacht werden sollte.", // "item.edit.public.header": "Make item public: {{ id }}", - "item.edit.public.header": "Ressource: {{ id }} öffentlich machen", + "item.edit.public.header": "Item: {{ id }} öffentlich machen", // "item.edit.public.success": "The item is now public", - "item.edit.public.success": "Die Ressource ist nun öffentlich", + "item.edit.public.success": "Das Item ist nun öffentlich", @@ -2396,16 +2368,16 @@ "item.edit.reinstate.confirm": "Reinstantiieren", // "item.edit.reinstate.description": "Are you sure this item should be reinstated to the archive?", - "item.edit.reinstate.description": "Sind Sie sicher, dass die Ressource reinstantiiert werden soll?", + "item.edit.reinstate.description": "Sind Sie sicher, dass das Item reinstantiiert werden soll?", // "item.edit.reinstate.error": "An error occurred while reinstating the item", - "item.edit.reinstate.error": "Ein Fehler ist bei der Reinstantiierung der Ressource aufgetreten.", + "item.edit.reinstate.error": "Ein Fehler ist bei der Reinstantiierung des Items aufgetreten.", // "item.edit.reinstate.header": "Reinstate item: {{ id }}", - "item.edit.reinstate.header": "Ressource: {{ id }} reinstantiieren", + "item.edit.reinstate.header": "Item: {{ id }} reinstantiieren", // "item.edit.reinstate.success": "The item was reinstated successfully", - "item.edit.reinstate.success": "Die Ressource wurde erfolgreich reinstantiiert", + "item.edit.reinstate.success": "Das Item wurde erfolgreich reinstantiiert", @@ -2434,13 +2406,13 @@ "item.edit.relationships.notifications.failed.title": "Fehler beim Bearbeiten der Beziehung", // "item.edit.relationships.notifications.outdated.content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts", - "item.edit.relationships.notifications.outdated.content": "Die Ressource, die Sie gerade bearbeiten, wurde von einem anderen Account geändert. Ihre aktuellen Änderungen werden verworfen, um Konflikte zu vermeiden.", + "item.edit.relationships.notifications.outdated.content": "Das Item, das Sie gerade bearbeiten, wurde von einem anderen Account geändert. Ihre aktuellen Änderungen werden verworfen, um Konflikte zu vermeiden.", // "item.edit.relationships.notifications.outdated.title": "Changes outdated", "item.edit.relationships.notifications.outdated.title": "Änderungen veraltet", // "item.edit.relationships.notifications.saved.content": "Your changes to this item's relationships were saved.", - "item.edit.relationships.notifications.saved.content": "Ihre Änderungen an den Beziehungen der Ressource wurden gespeichert.", + "item.edit.relationships.notifications.saved.content": "Ihre Änderungen an den Beziehungen des Items wurden gespeichert.", // "item.edit.relationships.notifications.saved.title": "Relationships saved", "item.edit.relationships.notifications.saved.title": "Beziehungen gespeichert", @@ -2452,40 +2424,39 @@ "item.edit.relationships.save-button": "Speichern", // "item.edit.relationships.no-entity-type": "Add 'dspace.entity.type' metadata to enable relationships for this item", - "item.edit.relationships.no-entity-type": "Füge 'dspace.entity.type' in die Metadaten hinzu, um Beziehungen für diese Ressource zu ermöglichen", + "item.edit.relationships.no-entity-type": "Füge 'dspace.entity.type' in die Metadaten hinzu, um Beziehungen für dieses Item zu ermöglichen", // "item.edit.tabs.bitstreams.head": "Bitstreams", - // TODO Source message changed - Revise the translation "item.edit.tabs.bitstreams.head": "Dateien", // "item.edit.tabs.bitstreams.title": "Item Edit - Bitstreams", - "item.edit.tabs.bitstreams.title": "Ressource bearbeiten - Dateien", + "item.edit.tabs.bitstreams.title": "Item bearbeiten - Dateien", // "item.edit.tabs.curate.head": "Curate", "item.edit.tabs.curate.head": "Pflegen", // "item.edit.tabs.curate.title": "Item Edit - Curate", - "item.edit.tabs.curate.title": "Ressource bearbeiten - Pflegen", + "item.edit.tabs.curate.title": "Item bearbeiten - Pflegen", // "item.edit.tabs.metadata.head": "Metadata", "item.edit.tabs.metadata.head": "Metadaten", // "item.edit.tabs.metadata.title": "Item Edit - Metadata", - "item.edit.tabs.metadata.title": "Ressource bearbeiten - Metadaten", + "item.edit.tabs.metadata.title": "Item bearbeiten - Metadaten", // "item.edit.tabs.relationships.head": "Relationships", "item.edit.tabs.relationships.head": "Beziehungen", // "item.edit.tabs.relationships.title": "Item Edit - Relationships", - "item.edit.tabs.relationships.title": "Ressource bearbeiten - Relationen", + "item.edit.tabs.relationships.title": "Item bearbeiten - Relationen", // "item.edit.tabs.status.buttons.authorizations.button": "Authorizations...", "item.edit.tabs.status.buttons.authorizations.button": "Berechtigungen...", // "item.edit.tabs.status.buttons.authorizations.label": "Edit item's authorization policies", - "item.edit.tabs.status.buttons.authorizations.label": "Rechte der Ressource bearbeiten", + "item.edit.tabs.status.buttons.authorizations.label": "Rechte des Items bearbeiten", // "item.edit.tabs.status.buttons.delete.button": "Permanently delete", "item.edit.tabs.status.buttons.delete.button": "Endgültig löschen", @@ -2503,34 +2474,34 @@ "item.edit.tabs.status.buttons.move.button": "Verschieben...", // "item.edit.tabs.status.buttons.move.label": "Move item to another collection", - "item.edit.tabs.status.buttons.move.label": "Ressource in eine andere Sammlung verschieben", + "item.edit.tabs.status.buttons.move.label": "Item in eine andere Sammlung verschieben", // "item.edit.tabs.status.buttons.private.button": "Make it private...", "item.edit.tabs.status.buttons.private.button": "Privat machen...", // "item.edit.tabs.status.buttons.private.label": "Make item private", - "item.edit.tabs.status.buttons.private.label": "Ressource privat machen", + "item.edit.tabs.status.buttons.private.label": "Item privat machen", // "item.edit.tabs.status.buttons.public.button": "Make it public...", "item.edit.tabs.status.buttons.public.button": "Öffentlich machen...", // "item.edit.tabs.status.buttons.public.label": "Make item public", - "item.edit.tabs.status.buttons.public.label": "Ressource öffentlich machen", + "item.edit.tabs.status.buttons.public.label": "Item öffentlich machen", // "item.edit.tabs.status.buttons.reinstate.button": "Reinstate...", "item.edit.tabs.status.buttons.reinstate.button": "Reinstantiieren...", // "item.edit.tabs.status.buttons.reinstate.label": "Reinstate item into the repository", - "item.edit.tabs.status.buttons.reinstate.label": "Ressource wieder ins Repositorium einsetzen", + "item.edit.tabs.status.buttons.reinstate.label": "Item wieder ins Repositorium einsetzen", // "item.edit.tabs.status.buttons.withdraw.button": "Withdraw...", "item.edit.tabs.status.buttons.withdraw.button": "Zurückziehen...", // "item.edit.tabs.status.buttons.withdraw.label": "Withdraw item from the repository", - "item.edit.tabs.status.buttons.withdraw.label": "Ressource aus dem Repositorium zurückziehen", + "item.edit.tabs.status.buttons.withdraw.label": "Item aus dem Repositorium zurückziehen", // "item.edit.tabs.status.description": "Welcome to the item management page. From here you can withdraw, reinstate, move or delete the item. You may also update or add new metadata / bitstreams on the other tabs.", - "item.edit.tabs.status.description": "Ressource bearbeiten. Von hier können Sie Ressourcen zurückziehen, wiedereinsetzen, verschieben oder Löschen. Des weiteren können die Metadaten und die zugehörigen Dateien bearbeitet werden.", + "item.edit.tabs.status.description": "Item bearbeiten. Von hier können Sie Items zurückziehen, wiedereinsetzen, verschieben oder Löschen. Des weiteren können die Metadaten und die zugehörigen Dateien bearbeitet werden.", // "item.edit.tabs.status.head": "Status", "item.edit.tabs.status.head": "Status", @@ -2539,31 +2510,31 @@ "item.edit.tabs.status.labels.handle": "Handle", // "item.edit.tabs.status.labels.id": "Item Internal ID", - "item.edit.tabs.status.labels.id": "Interne ID der Ressource", + "item.edit.tabs.status.labels.id": "Interne ID des Items", // "item.edit.tabs.status.labels.itemPage": "Item Page", - "item.edit.tabs.status.labels.itemPage": "Startseite der Ressource", + "item.edit.tabs.status.labels.itemPage": "Startseite des Items", // "item.edit.tabs.status.labels.lastModified": "Last Modified", "item.edit.tabs.status.labels.lastModified": "Zuletzt geändert", // "item.edit.tabs.status.title": "Item Edit - Status", - "item.edit.tabs.status.title": "Ressource bearbeiten - Status", + "item.edit.tabs.status.title": "Item bearbeiten - Status", // "item.edit.tabs.versionhistory.head": "Version History", "item.edit.tabs.versionhistory.head": "Versionsgeschichte", // "item.edit.tabs.versionhistory.title": "Item Edit - Version History", - "item.edit.tabs.versionhistory.title": "Ressource bearbeiten - Versionsgeschichte", + "item.edit.tabs.versionhistory.title": "Item bearbeiten - Versionsgeschichte", // "item.edit.tabs.versionhistory.under-construction": "Editing or adding new versions is not yet possible in this user interface.", "item.edit.tabs.versionhistory.under-construction": "Das Bearbeiten oder Hinzufügen neuer Versionen ist in dieser Benutzeroberfläche noch nicht möglich.", // "item.edit.tabs.view.head": "View Item", - "item.edit.tabs.view.head": "Ressource ansehen", + "item.edit.tabs.view.head": "Item ansehen", // "item.edit.tabs.view.title": "Item Edit - View", - "item.edit.tabs.view.title": "Ressource bearbeiten - Ansicht", + "item.edit.tabs.view.title": "Item bearbeiten - Ansicht", @@ -2574,27 +2545,27 @@ "item.edit.withdraw.confirm": "Zurückziehen", // "item.edit.withdraw.description": "Are you sure this item should be withdrawn from the archive?", - "item.edit.withdraw.description": "Sind Sie sicher, dass Sie diese Ressource aus dem Repositorium zurückziehen wollen?", + "item.edit.withdraw.description": "Sind Sie sicher, dass Sie dieses Item aus dem Repositorium zurückziehen wollen?", // "item.edit.withdraw.error": "An error occurred while withdrawing the item", - "item.edit.withdraw.error": "Ein Fehler ist beim Zurückziehen der Ressource aufgetreten", + "item.edit.withdraw.error": "Ein Fehler ist beim Zurückziehen des Items aufgetreten", // "item.edit.withdraw.header": "Withdraw item: {{ id }}", - "item.edit.withdraw.header": "Ressource: {{ id }} zurückziehen", + "item.edit.withdraw.header": "Item: {{ id }} zurückziehen", // "item.edit.withdraw.success": "The item was withdrawn successfully", - "item.edit.withdraw.success": "Die Ressource wurde erfolgreich zurückgezogen", + "item.edit.withdraw.success": "Das Item wurde erfolgreich zurückgezogen", // "item.listelement.badge": "Item", - "item.listelement.badge": "Ressource", + "item.listelement.badge": "Item", // "item.page.description": "Description", "item.page.description": "Beschreibung", // "item.page.edit": "Edit this item", - "item.page.edit": "Diese Ressource bearbeiten", + "item.page.edit": "Dieses Item bearbeiten", // "item.page.journal-issn": "Journal ISSN", "item.page.journal-issn": "ISSN der Zeitschrift", @@ -2606,16 +2577,16 @@ "item.page.publisher": "Verlag", // "item.page.titleprefix": "Item: ", - "item.page.titleprefix": "Ressource: ", + "item.page.titleprefix": "Item: ", // "item.page.volume-title": "Volume Title", "item.page.volume-title": "Bandtitel", // "item.search.results.head": "Item Search Results", - "item.search.results.head": "Ressourcen Suchergebnisse", + "item.search.results.head": "Item Suchergebnisse", // "item.search.title": "DSpace Angular :: Item Search", - "item.search.title": "DSpace Angular :: Ressourcen-Suche", + "item.search.title": "DSpace Angular :: Item-Suche", @@ -2635,7 +2606,7 @@ "item.page.date": "Datum", // "item.page.edit": "Edit this item", - "item.page.edit": "Diese Ressource bearbeiten", + "item.page.edit": "Dieses Item bearbeiten", // "item.page.files": "Files", "item.page.files": "Dateien", @@ -2728,7 +2699,6 @@ "item.preview.dc.title": "Titel:", // "item.preview.person.familyName": "Surname:", - // TODO New key - Add a translation "item.preview.person.familyName": "Nachname:", // "item.preview.person.givenName": "Name:", @@ -2742,7 +2712,7 @@ "item.select.confirm": "Auswahl bestätigen", // "item.select.empty": "No items to show", - "item.select.empty": "Es gibt keine Ressourcen dazu", + "item.select.empty": "Es gibt keine Items dazu", // "item.select.table.author": "Author", "item.select.table.author": "Autor:in", @@ -2755,7 +2725,7 @@ // "item.version.history.empty": "There are no other versions for this item yet.", - "item.version.history.empty": "Es gibt noch keine anderen Versionen für diese Ressource.", + "item.version.history.empty": "Es gibt noch keine anderen Versionen für dieses Item.", // "item.version.history.head": "Version History", "item.version.history.head": "Versionsgeschichte", @@ -2770,7 +2740,7 @@ "item.version.history.table.version": "Version", // "item.version.history.table.item": "Item", - "item.version.history.table.item": "Ressource", + "item.version.history.table.item": "Item", // "item.version.history.table.editor": "Editor", "item.version.history.table.editor": "Herausgeber:in", @@ -2784,8 +2754,7 @@ // "item.version.notice": "This is not the latest version of this item. The latest version can be found here.", - // TODO New key - Add a translation - "item.version.notice": "Dies ist nicht die neueste Version dieses Artikels. Die neueste Version finden Sie hier.", + "item.version.notice": "Dies ist nicht die neueste Version dieses Items. Die neueste Version finden Sie unter hier.", @@ -2796,7 +2765,7 @@ "journal.page.description": "Beschreibung", // "journal.page.edit": "Edit this item", - "journal.page.edit": "Diese Ressource bearbeiten", + "journal.page.edit": "Dieses Item bearbeiten", // "journal.page.editor": "Editor-in-Chief", "journal.page.editor": "Chefredakteur:in", @@ -2825,7 +2794,7 @@ "journalissue.page.description": "Beschreibeung", // "journalissue.page.edit": "Edit this item", - "journalissue.page.edit": "Diese Ressource bearbeiten", + "journalissue.page.edit": "Dieses Item bearbeiten", // "journalissue.page.issuedate": "Issue Date", "journalissue.page.issuedate": "Erscheinungsdatum", @@ -2854,7 +2823,7 @@ "journalvolume.page.description": "Beschreibung", // "journalvolume.page.edit": "Edit this item", - "journalvolume.page.edit": "Diese Ressource bearbeiten", + "journalvolume.page.edit": "Dieses Item bearbeiten", // "journalvolume.page.issuedate": "Issue Date", "journalvolume.page.issuedate": "Erscheinungsdatum", @@ -2874,7 +2843,7 @@ "loading.bitstreams": "Lade Datei...", // "loading.browse-by": "Loading items...", - "loading.browse-by": "Lade Ressourcen...", + "loading.browse-by": "Lade Items...", // "loading.browse-by-page": "Loading page...", "loading.browse-by-page": "Lade Seite...", @@ -2895,13 +2864,13 @@ "loading.default": "Lade...", // "loading.item": "Loading item...", - "loading.item": "Lade Ressource...", + "loading.item": "Lade Item...", // "loading.items": "Loading items...", - "loading.items": "Lade Ressourcen...", + "loading.items": "Lade Items...", // "loading.mydspace-results": "Loading items...", - "loading.mydspace-results": "Lade Ressourcen...", + "loading.mydspace-results": "Lade Items...", // "loading.objects": "Loading...", "loading.objects": "Lade...", @@ -3043,7 +3012,7 @@ "menu.section.edit_community": "Bereich", // "menu.section.edit_item": "Item", - "menu.section.edit_item": "Ressource", + "menu.section.edit_item": "Item", @@ -3057,7 +3026,7 @@ "menu.section.export_community": "Bereich", // "menu.section.export_item": "Item", - "menu.section.export_item": "Ressource", + "menu.section.export_item": "Item", // "menu.section.export_metadata": "Metadata", "menu.section.export_metadata": "Metadaten", @@ -3068,8 +3037,7 @@ "menu.section.icon.access_control": "Menübereich Zugriffskontrolle", // "menu.section.icon.admin_search": "Admin search menu section", - // TODO New key - Add a translation - "menu.section.icon.admin_search": "Admin search menu section", + "menu.section.icon.admin_search": "Menübereich Admin-Suche", // "menu.section.icon.control_panel": "Control Panel menu section", "menu.section.icon.control_panel": "Menübereich Kontrollfeld", @@ -3130,10 +3098,10 @@ "menu.section.new_community": "Bereich", // "menu.section.new_item": "Item", - "menu.section.new_item": "Ressource", + "menu.section.new_item": "Item", // "menu.section.new_item_version": "Item Version", - "menu.section.new_item_version": "Ressourcenversion", + "menu.section.new_item_version": "Item-Version", // "menu.section.new_process": "Process", "menu.section.new_process": "Prozess", @@ -3214,7 +3182,7 @@ "mydspace.general.text-here": "hier", // "mydspace.messages.controller-help": "Select this option to send a message to item's submitter.", - "mydspace.messages.controller-help": "Wählen Sie diese Option, um dem/derjenigen, die die Ressource eingereicht hat, eine Nachricht zu schicken.", + "mydspace.messages.controller-help": "Wählen Sie diese Option, um dem/derjenigen, die das Item eingereicht hat, eine Nachricht zu schicken.", // "mydspace.messages.description-placeholder": "Insert your message here...", "mydspace.messages.description-placeholder": "Geben Sie Ihre Nachricht hier ein...", @@ -3280,7 +3248,7 @@ "mydspace.results.no-files": "Keine Dateien", // "mydspace.results.no-results": "There were no items to show", - "mydspace.results.no-results": "Es gibt keine Ressourcen anzuzeigen", + "mydspace.results.no-results": "Es gibt keine Items anzuzeigen", // "mydspace.results.no-title": "No title", "mydspace.results.no-title": "Kein Titel", @@ -3322,10 +3290,10 @@ "mydspace.upload.upload-failed-moreonefile": "Unverarbeitbare Anfrage. Nur eine Datei ist erlaubt.", // "mydspace.upload.upload-multiple-successful": "{{qty}} new workspace items created.", - "mydspace.upload.upload-multiple-successful": "{{qty}} neue(s) Arbeitsbereichressource(n) angelegt.", + "mydspace.upload.upload-multiple-successful": "{{qty}} neue(s) Arbeitsbereichitem(s) angelegt.", // "mydspace.upload.upload-successful": "New workspace item created. Click {{here}} for edit it.", - "mydspace.upload.upload-successful": "Neue Arbeitsbereichressource angelegt. Klicken Sie {{here}}, um sie zu bearbeiten.", + "mydspace.upload.upload-successful": "Neues Arbeitsbereichitem angelegt. Klicken Sie {{here}}, um sie zu bearbeiten.", // "mydspace.view-btn": "View", "mydspace.view-btn": "Anzeige", @@ -3380,7 +3348,7 @@ "orgunit.page.description": "Beschreibung", // "orgunit.page.edit": "Edit this item", - "orgunit.page.edit": "Diese Ressourcen bearbeiten", + "orgunit.page.edit": "Dieses Item bearbeiten", // "orgunit.page.id": "ID", "orgunit.page.id": "ID", @@ -3414,7 +3382,7 @@ "person.page.birthdate": "Geburtsdatum", // "person.page.edit": "Edit this item", - "person.page.edit": "Diese Ressource bearbeiten", + "person.page.edit": "Dieses Item bearbeiten", // "person.page.email": "Email Address", "person.page.email": "E-Mail-Adresse", @@ -3517,24 +3485,19 @@ "process.detail.back" : "Zurück", // "process.detail.output" : "Process Output", - // TODO New key - Add a translation - "process.detail.output" : "Process Output", + "process.detail.output" : "Prozessausgabe", // "process.detail.logs.button": "Retrieve process output", - // TODO New key - Add a translation - "process.detail.logs.button": "Retrieve process output", + "process.detail.logs.button": "Abrufen der Prozessausgabe", // "process.detail.logs.loading": "Retrieving", - // TODO New key - Add a translation - "process.detail.logs.loading": "Retrieving", + "process.detail.logs.loading": "Abrufen", // "process.detail.logs.none": "This process has no output", - // TODO New key - Add a translation - "process.detail.logs.none": "This process has no output", + "process.detail.logs.none": "Dieser Prozess hat keine Ausgabe", // "process.detail.output-files" : "Output Files", - // TODO New key - Add a translation - "process.detail.output-files" : "Output Files", + "process.detail.output-files" : "Ausgabedateien", // "process.detail.output-files.empty" : "This process doesn't contain any output files", "process.detail.output-files.empty" : "Dieser Prozess enthält keine Ausgabedateien", @@ -3683,7 +3646,7 @@ "project.page.description": "Beschreibung", // "project.page.edit": "Edit this item", - "project.page.edit": "Diese Ressource bearbeiten", + "project.page.edit": "Dieses Item bearbeiten", // "project.page.expectedcompletion": "Expected Completion", "project.page.expectedcompletion": "Erwartetes Abschlussdatum", @@ -3715,7 +3678,7 @@ "publication.page.description": "Beschreibung", // "publication.page.edit": "Edit this item", - "publication.page.edit": "Diese Ressource bearbeiten", + "publication.page.edit": "Dieses Item bearbeiten", // "publication.page.journal-issn": "Journal ISSN", "publication.page.journal-issn": "ISSN der Zeitschrift", @@ -3810,7 +3773,7 @@ "register-page.registration.header": "Registrierung neuer Benutzer:innen", // "register-page.registration.info": "Register an account to subscribe to collections for email updates, and submit new items to DSpace.", - "register-page.registration.info": "Registrieren Sie ein Konto, um Sammlungen für E-Mail-Updates zu abonnieren und neue Ressourcen in DSpace einzugeben.", + "register-page.registration.info": "Registrieren Sie ein Konto, um Sammlungen für E-Mail-Updates zu abonnieren und neue Items in DSpace einzugeben.", // "register-page.registration.email": "Email Address *", "register-page.registration.email": "E-Mail-Adresse *", @@ -3842,7 +3805,7 @@ // "relationships.add.error.relationship-type.content": "No suitable match could be found for relationship type {{ type }} between the two items", - "relationships.add.error.relationship-type.content": "Für den Beziehungstyp {{ type }} zwischen den beiden Ressourcen konnte keine passende Übereinstimmung gefunden werden", + "relationships.add.error.relationship-type.content": "Für den Beziehungstyp {{ type }} zwischen den beiden Items konnte keine passende Übereinstimmung gefunden werden", // "relationships.add.error.server.content": "The server returned an error", "relationships.add.error.server.content": "Der Server hat einen Fehler zurückgegeben", @@ -3919,55 +3882,46 @@ "resource-policies.add.for.collection": "Eine neue Sammlungen-Richtlinie hinzufügen", // "resource-policies.create.page.heading": "Create new resource policy for ", - // TODO New key - Add a translation - "resource-policies.create.page.heading": "Create new resource policy for ", + "resource-policies.create.page.heading": "Eine neue Ressourcen-Richtlinie erstellen für ", // "resource-policies.create.page.failure.content": "An error occurred while creating the resource policy.", - // TODO New key - Add a translation - "resource-policies.create.page.failure.content": "An error occurred while creating the resource policy.", + "resource-policies.create.page.failure.content": "Bei der Erstellung der Ressourcen-Richtlinie ist ein Fehler aufgetreten.", // "resource-policies.create.page.success.content": "Operation successful", "resource-policies.create.page.success.content": "Vorgang erfolgreich", // "resource-policies.create.page.title": "Create new resource policy", - // TODO New key - Add a translation - "resource-policies.create.page.title": "Create new resource policy", + "resource-policies.create.page.title": "Eine neue Ressourcen-Richtlinie erstellen", // "resource-policies.delete.btn": "Delete selected", "resource-policies.delete.btn": "Ausgewählte löschen", // "resource-policies.delete.btn.title": "Delete selected resource policies", - // TODO New key - Add a translation - "resource-policies.delete.btn.title": "Delete selected resource policies", + "resource-policies.delete.btn.title": "Ausgewählte Ressourcen-Richtlinien löschen", // "resource-policies.delete.failure.content": "An error occurred while deleting selected resource policies.", - // TODO New key - Add a translation - "resource-policies.delete.failure.content": "An error occurred while deleting selected resource policies.", + "resource-policies.delete.failure.content": "Beim Löschen der ausgewählten Ressourcen-Richtlinien ist ein Fehler aufgetreten.", // "resource-policies.delete.success.content": "Operation successful", "resource-policies.delete.success.content": "Vorgang erfolgreich", // "resource-policies.edit.page.heading": "Edit resource policy ", - // TODO New key - Add a translation - "resource-policies.edit.page.heading": "Edit resource policy ", + "resource-policies.edit.page.heading": "Ressourcen-Richtlinie bearbeiten ", // "resource-policies.edit.page.failure.content": "An error occurred while editing the resource policy.", - // TODO New key - Add a translation - "resource-policies.edit.page.failure.content": "An error occurred while editing the resource policy.", + "resource-policies.edit.page.failure.content": "Beim Bearbeiten der Ressourcen-Richtlinie ist ein Fehler aufgetreten.", // "resource-policies.edit.page.success.content": "Operation successful", "resource-policies.edit.page.success.content": "Vorgang erfolgreich", // "resource-policies.edit.page.title": "Edit resource policy", - // TODO New key - Add a translation - "resource-policies.edit.page.title": "Edit resource policy", + "resource-policies.edit.page.title": "Ressourcen-Richtlinie bearbeiten", // "resource-policies.form.action-type.label": "Select the action type", "resource-policies.form.action-type.label": "Wählen Sie die Aktionsart", // "resource-policies.form.action-type.required": "You must select the resource policy action.", - // TODO New key - Add a translation - "resource-policies.form.action-type.required": "You must select the resource policy action.", + "resource-policies.form.action-type.required": "Sie müssen die Aktion der Ressourcen-Richtlinie auswählen.", // "resource-policies.form.eperson-group-list.label": "The eperson or group that will be granted the permission", "resource-policies.form.eperson-group-list.label": "Die Person oder Gruppe, der die Genehmigung erteilt wird", @@ -3982,8 +3936,7 @@ "resource-policies.form.eperson-group-list.tab.group": "Suche nach einer Gruppe", // "resource-policies.form.eperson-group-list.table.headers.action": "Action", - // TODO New key - Add a translation - "resource-policies.form.eperson-group-list.table.headers.action": "Action", + "resource-policies.form.eperson-group-list.table.headers.action": "Aktion", // "resource-policies.form.eperson-group-list.table.headers.id": "ID", "resource-policies.form.eperson-group-list.table.headers.id": "ID", @@ -4007,8 +3960,7 @@ "resource-policies.form.policy-type.label": "Wählen Sie den Richtlinientyp", // "resource-policies.form.policy-type.required": "You must select the resource policy type.", - // TODO New key - Add a translation - "resource-policies.form.policy-type.required": "You must select the resource policy type.", + "resource-policies.form.policy-type.required": "Sie müssen den Typ der Ressourcen-Richtlinie auswählen", // "resource-policies.table.headers.action": "Action", "resource-policies.table.headers.action": "Aktion", @@ -4091,7 +4043,7 @@ "search.filters.applied.f.discoverable": "Privat", // "search.filters.applied.f.entityType": "Item Type", - "search.filters.applied.f.entityType": "Art der Ressource", + "search.filters.applied.f.entityType": "Item-Typ", // "search.filters.applied.f.has_content_in_original_bundle": "Has files", "search.filters.applied.f.has_content_in_original_bundle": "Hat Dateien", @@ -4180,10 +4132,10 @@ "search.filters.filter.withdrawn.head": "Zurückgezogen", // "search.filters.filter.entityType.head": "Item Type", - "search.filters.filter.entityType.head": "Art der Ressource", + "search.filters.filter.entityType.head": "Item-Typ", // "search.filters.filter.entityType.placeholder": "Item Type", - "search.filters.filter.entityType.placeholder": "Art der Ressource", + "search.filters.filter.entityType.placeholder": "Item-Typ", // "search.filters.filter.has_content_in_original_bundle.head": "Has files", "search.filters.filter.has_content_in_original_bundle.head": "Enthält Dateien", @@ -4447,7 +4399,7 @@ "submission.import-external.title": "Importieren von Metadaten aus einer externen Quelle", // "submission.import-external.page.hint": "Enter a query above to find items from the web to import in to DSpace.", - "submission.import-external.page.hint": "Geben Sie oben eine Abfrage ein, um Ressourcen aus dem Internet zu finden, die in DSpace importiert werden sollen.", + "submission.import-external.page.hint": "Geben Sie oben eine Abfrage ein, um Items aus dem Internet zu finden, die in DSpace importiert werden sollen.", // "submission.import-external.back-to-my-dspace": "Back to MyDSpace", "submission.import-external.back-to-my-dspace": "Zurück zu MyDSpace", @@ -4486,7 +4438,7 @@ "submission.import-external.source.lcname": "Library of Congress Names", // "submission.import-external.preview.title": "Item Preview", - "submission.import-external.preview.title": "Ressourcen-Vorschau", + "submission.import-external.preview.title": "Item-Vorschau", // "submission.import-external.preview.subtitle": "The metadata below was imported from an external source. It will be pre-filled when you start the submission.", "submission.import-external.preview.subtitle": "Die folgenden Metadaten wurden aus einer externen Quelle importiert. Sie werden vorausgefüllt, wenn Sie die Eingabe starten.", @@ -4507,8 +4459,7 @@ "submission.sections.describe.relationship-lookup.external-source.added": "Der lokale Eintrage wurde erfolgreich zur Auswahl hinzugefügt.", // "submission.sections.describe.relationship-lookup.external-source.import-button-title.isAuthorOfPublication": "Import remote author", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.external-source.import-button-title.isAuthorOfPublication": "Import remote author", + "submission.sections.describe.relationship-lookup.external-source.import-button-title.isAuthorOfPublication": "Importiere Autor:innen-Metadaten", // "submission.sections.describe.relationship-lookup.external-source.import-button-title.Journal": "Import remote journal", "submission.sections.describe.relationship-lookup.external-source.import-button-title.Journal": "Zeitschrift importieren", @@ -4520,8 +4471,7 @@ "submission.sections.describe.relationship-lookup.external-source.import-button-title.Journal Volume": "Zeitschriftenband importieren", // "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.title": "Import Remote Author", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.title": "Import Remote Author", + "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.title": "Importiere Autor:innen-Metadaten", // "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.local-entity": "Successfully added local author to the selection", "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.local-entity": "Erfolgreicher Import und Hinzufügen eines/einer lokalen Autor:in zur Auswahl", @@ -4620,55 +4570,46 @@ "submission.sections.describe.relationship-lookup.search-tab.select-page": "Seite auswählen", // "submission.sections.describe.relationship-lookup.selected": "Selected {{ size }} items", - "submission.sections.describe.relationship-lookup.selected": "{{ size }} Ressourcen ausgewählt", + "submission.sections.describe.relationship-lookup.selected": "{{ size }} Items ausgewählt", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isAuthorOfPublication": "Local Authors ({{ count }})", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.search-tab.tab-title.isAuthorOfPublication": "Local Authors ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.isAuthorOfPublication": "Lokale Autoren ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalOfPublication": "Local Journals ({{ count }})", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalOfPublication": "Local Journals ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalOfPublication": "Lokale Zeitschriften ({{ count }})", + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.Project": "Local Projects ({{ count }})", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.search-tab.tab-title.Project": "Local Projects ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.Project": "Lokale Projekte ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.Publication": "Local Publications ({{ count }})", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.search-tab.tab-title.Publication": "Local Publications ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.Publication": "Lokale Publicationen ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.Person": "Local Authors ({{ count }})", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.search-tab.tab-title.Person": "Local Authors ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.Person": "Lokale Autoren ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.OrgUnit": "Local Organizational Units ({{ count }})", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.search-tab.tab-title.OrgUnit": "Local Organizational Units ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.OrgUnit": "Lokale Organisationseinheiten ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.DataPackage": "Local Data Packages ({{ count }})", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.search-tab.tab-title.DataPackage": "Local Data Packages ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.DataPackage": "Lokale Datenpakete ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.DataFile": "Local Data Files ({{ count }})", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.search-tab.tab-title.DataFile": "Local Data Files ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.DataFile": "Lokale Dateien ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal": "Local Journals ({{ count }})", "submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal": "Lokale Zeitschriften ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalIssueOfPublication": "Local Journal Issues ({{ count }})", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalIssueOfPublication": "Local Journal Issues ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalIssueOfPublication": "Lokale Zeitschriftenhefte ({{ count }})", + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.JournalIssue": "Local Journal Issues ({{ count }})", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.search-tab.tab-title.JournalIssue": "Local Journal Issues ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.JournalIssue": "Lokale Zeitschriftenhefte ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalVolumeOfPublication": "Local Journal Volumes ({{ count }})", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalVolumeOfPublication": "Local Journal Volumes ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalVolumeOfPublication": "Lokale Zeitschriftenbände ({{ count }})", + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.JournalVolume": "Local Journal Volumes ({{ count }})", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.search-tab.tab-title.JournalVolume": "Local Journal Volumes ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.JournalVolume": "Lokale Zeitschriftenbände ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaJournal": "Sherpa Journals ({{ count }})", "submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaJournal": "Sherpa Zeitschriften ({{ count }})", @@ -4734,8 +4675,7 @@ "submission.sections.describe.relationship-lookup.title.DataPackage": "Datenpakete", // "submission.sections.describe.relationship-lookup.title.DataFile": "Data Files", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.title.DataFile": "Data Files", + "submission.sections.describe.relationship-lookup.title.DataFile": "Dateien", // "submission.sections.describe.relationship-lookup.title.Funding Agency": "Funding Agency", "submission.sections.describe.relationship-lookup.title.Funding Agency": "Fördereinrichtung", @@ -4780,8 +4720,7 @@ "submission.sections.describe.relationship-lookup.selection-tab.title.DataPackage": "Ausgewählte Datenpakete", // "submission.sections.describe.relationship-lookup.selection-tab.title.DataFile": "Selected Data Files", - // TODO New key - Add a translation - "submission.sections.describe.relationship-lookup.selection-tab.title.DataFile": "Selected Data Files", + "submission.sections.describe.relationship-lookup.selection-tab.title.DataFile": "Ausgewählte Dateien", // "submission.sections.describe.relationship-lookup.selection-tab.title.Journal": "Selected Journals", "submission.sections.describe.relationship-lookup.selection-tab.title.Journal": "Ausgewählte Zeitschriften", @@ -4850,12 +4789,10 @@ "submission.sections.ccLicense.option.select": "Wählen Sie eine Option…", // "submission.sections.ccLicense.link": "You’ve selected the following license:", - // TODO New key - Add a translation - "submission.sections.ccLicense.link": "You’ve selected the following license:", + "submission.sections.ccLicense.link": "Sie haben die folgende Lizenz ausgewählt:", // "submission.sections.ccLicense.confirmation": "I grant the license above", - // TODO New key - Add a translation - "submission.sections.ccLicense.confirmation": "I grant the license above", + "submission.sections.ccLicense.confirmation": "Ich erteile die oben genannte Lizenz", // "submission.sections.general.add-more": "Add more", "submission.sections.general.add-more": "Mehr Hinzufügen", @@ -4864,7 +4801,7 @@ "submission.sections.general.collection": "Sammlung", // "submission.sections.general.deposit_error_notice": "There was an issue when submitting the item, please try again later.", - "submission.sections.general.deposit_error_notice": "Beim Einreichen der Ressource ist ein Fehler aufgetreten. Bitte versuchen Sie es später noch einmal.", + "submission.sections.general.deposit_error_notice": "Beim Einreichen des Items ist ein Fehler aufgetreten. Bitte versuchen Sie es später noch einmal.", // "submission.sections.general.deposit_success_notice": "Submission deposited successfully.", "submission.sections.general.deposit_success_notice": "Veröffentlichung erfolgreich eingereicht", @@ -4888,7 +4825,7 @@ "submission.sections.general.no-sections": "Es stehen keine Optionen zur Verfügung", // "submission.sections.general.save_error_notice": "There was an issue when saving the item, please try again later.", - "submission.sections.general.save_error_notice": "Beim Speichern der Ressource ist ein Fehler aufgetreten, bitte versuchen Sie es später noch einmal.", + "submission.sections.general.save_error_notice": "Beim Speichern des Items ist ein Fehler aufgetreten, bitte versuchen Sie es später noch einmal.", // "submission.sections.general.save_success_notice": "Submission saved successfully.", "submission.sections.general.save_success_notice": "Einreichung erfolgreich gespeichert.", @@ -4902,7 +4839,6 @@ // "submission.sections.submit.progressbar.CClicense": "Creative commons license", - // TODO New key - Add a translation "submission.sections.submit.progressbar.CClicense": "Creative commons license", // "submission.sections.submit.progressbar.describe.recycle": "Recycle", @@ -4944,7 +4880,7 @@ "submission.sections.upload.delete.submit": "Löschen", // "submission.sections.upload.drop-message": "Drop files to attach them to the item", - "submission.sections.upload.drop-message": "Dateien herüberziehen, um sie der Ressource hinzuzufügen", + "submission.sections.upload.drop-message": "Dateien herüberziehen, um sie dem Item hinzuzufügen", // "submission.sections.upload.form.access-condition-label": "Access condition type", "submission.sections.upload.form.access-condition-label": "Zugriffsbedingung Typ", @@ -4953,7 +4889,6 @@ "submission.sections.upload.form.date-required": "Datum erforderlich.", // "submission.sections.upload.form.from-label": "Grant access from", - // TODO Source message changed - Revise the translation "submission.sections.upload.form.from-label": "Zugriff gewährt ab", // "submission.sections.upload.form.from-placeholder": "From", @@ -4966,7 +4901,6 @@ "submission.sections.upload.form.group-required": "Gruppe ist erforderlich", // "submission.sections.upload.form.until-label": "Grant access until", - // TODO Source message changed - Revise the translation "submission.sections.upload.form.until-label": "Zugriff gewährt bis", // "submission.sections.upload.form.until-placeholder": "Until", @@ -4979,7 +4913,7 @@ "submission.sections.upload.header.policy.default.withlist": "Bitte beachten Sie, dass in diese Sammlung {{collectionName}} hochgeladene Dateien zugüglich zu dem, was für einzelne Dateien entschieden wurde, für folgende Gruppe(n) zugänglich sein:", // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", - "submission.sections.upload.info": "Hier finden Sie alle Dateien, die aktuell zur Ressource gehören. Sie können die Metadaten und Zugriffsrechte bearbeiten oder weitere Dateien hinzufügen, indem Sie sie einfach irgenwo auf diese Seite ziehen.", + "submission.sections.upload.info": "Hier finden Sie alle Dateien, die aktuell zum Item gehören. Sie können die Metadaten und Zugriffsrechte bearbeiten oder weitere Dateien hinzufügen, indem Sie sie einfach irgenwo auf diese Seite ziehen.", // "submission.sections.upload.no-entry": "No", "submission.sections.upload.no-entry": "Kein Eintrag", @@ -5010,19 +4944,19 @@ "submission.workflow.generic.delete": "Löschen", // "submission.workflow.generic.delete-help": "If you would to discard this item, select \"Delete\". You will then be asked to confirm it.", - "submission.workflow.generic.delete-help": "Wenn Sie die Ressource verwerfen möchten, wählen Sie \"Löschen\". Sie werden dies noch einmal gefragt, um die Aktion zu bestätigen.", + "submission.workflow.generic.delete-help": "Wenn Sie das Item verwerfen möchten, wählen Sie \"Löschen\". Sie werden dies noch einmal gefragt, um die Aktion zu bestätigen.", // "submission.workflow.generic.edit": "Edit", "submission.workflow.generic.edit": "Bearbeiten", // "submission.workflow.generic.edit-help": "Select this option to change the item's metadata.", - "submission.workflow.generic.edit-help": "Wählen Sie diese Option, um die Metadaten der Ressource zu bearbeiten.", + "submission.workflow.generic.edit-help": "Wählen Sie diese Option, um die Metadaten des Items zu bearbeiten.", // "submission.workflow.generic.view": "View", "submission.workflow.generic.view": "Anzeige", // "submission.workflow.generic.view-help": "Select this option to view the item's metadata.", - "submission.workflow.generic.view-help": "Wählen Sie diese Option, um die Metadaten der Ressourcen anzuzeigen", + "submission.workflow.generic.view-help": "Wählen Sie diese Option, um die Metadaten des Items anzuzeigen", @@ -5030,13 +4964,13 @@ "submission.workflow.tasks.claimed.approve": "Zustimmen", // "submission.workflow.tasks.claimed.approve_help": "If you have reviewed the item and it is suitable for inclusion in the collection, select \"Approve\".", - "submission.workflow.tasks.claimed.approve_help": "Wenn Sie die Ressource begutachtet haben und die Aufnahme in die Sammlung befürworten, wählen Sie \"Zustimmen\".", + "submission.workflow.tasks.claimed.approve_help": "Wenn Sie das Item begutachtet haben und die Aufnahme in die Sammlung befürworten, wählen Sie \"Zustimmen\".", // "submission.workflow.tasks.claimed.edit": "Edit", "submission.workflow.tasks.claimed.edit": "Bearbeiten", // "submission.workflow.tasks.claimed.edit_help": "Select this option to change the item's metadata.", - "submission.workflow.tasks.claimed.edit_help": "Wählen Sie diese Option, um die Metadaten der Ressource zu bearbeiten.", + "submission.workflow.tasks.claimed.edit_help": "Wählen Sie diese Option, um die Metadaten des Items zu bearbeiten.", // "submission.workflow.tasks.claimed.reject.reason.info": "Please enter your reason for rejecting the submission into the box below, indicating whether the submitter may fix a problem and resubmit.", "submission.workflow.tasks.claimed.reject.reason.info": "Bitte geben Sie den Grund für die Ablehnung der eingereichten Ressource in das Feld unten ein. Bitte geben Sie an ob und wie der/die Einreichende:n das Problem beheben und die Ressource erneut einreichen kann.", @@ -5045,7 +4979,7 @@ "submission.workflow.tasks.claimed.reject.reason.placeholder": "Beschreiben Sie den Grund für die Ablehnung", // "submission.workflow.tasks.claimed.reject.reason.submit": "Reject item", - "submission.workflow.tasks.claimed.reject.reason.submit": "Ressource ablehnen", + "submission.workflow.tasks.claimed.reject.reason.submit": "Item ablehnen", // "submission.workflow.tasks.claimed.reject.reason.title": "Reason", "submission.workflow.tasks.claimed.reject.reason.title": "Grund", @@ -5054,7 +4988,7 @@ "submission.workflow.tasks.claimed.reject.submit": "Ablehnen", // "submission.workflow.tasks.claimed.reject_help": "If you have reviewed the item and found it is not suitable for inclusion in the collection, select \"Reject\". You will then be asked to enter a message indicating why the item is unsuitable, and whether the submitter should change something and resubmit.", - "submission.workflow.tasks.claimed.reject_help": "Wenn Sie die Ressource begutachtet und als ungeeignet für die Aufnahme in die Sammlung befunden haben, wählen Sie \"Ablehnen\". Sie haben dann die Möglichkeit dem/der Einreichenden, den Grund für die Ablehnung zu erklären und ob es eine Möglichkeit gibt, durch entsprechenden Änderungen die Ressource erneut einzureichen.", + "submission.workflow.tasks.claimed.reject_help": "Wenn Sie das Item begutachtet und als ungeeignet für die Aufnahme in die Sammlung befunden haben, wählen Sie \"Ablehnen\". Sie haben dann die Möglichkeit dem/der Einreichenden, den Grund für die Ablehnung zu erklären und ob es eine Möglichkeit gibt, durch entsprechenden Änderungen das Item erneut einzureichen.", // "submission.workflow.tasks.claimed.return": "Return to pool", "submission.workflow.tasks.claimed.return": "Zurück in den gemeinsamen Aufgabenbereich", @@ -5142,7 +5076,7 @@ "virtual-metadata.delete-item.modal-head": "Virtuelle Metadaten dieser Relation", // "virtual-metadata.delete-relationship.modal-head": "Select the items for which you want to save the virtual metadata as real metadata", - "virtual-metadata.delete-relationship.modal-head": "Wählen Sie die Ressourcen für die Sie die virtuellen Metadaten als reelle Metadaten speichern wollen", + "virtual-metadata.delete-relationship.modal-head": "Wählen Sie die Items für die Sie die virtuellen Metadaten als reelle Metadaten speichern wollen", From 07000297183c5182326132624cb0948f252dd708 Mon Sep 17 00:00:00 2001 From: Pascal-Nicolas Becker Date: Wed, 9 Mar 2022 16:28:38 +0100 Subject: [PATCH 042/160] Working on inclusivness of the German translation --- src/assets/i18n/de.json5 | 42 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index d3fc1131aa..bbac4ccdef 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -107,7 +107,7 @@ "admin.registries.bitstream-formats.edit.head": "Dateiformat: {{ format }}", // "admin.registries.bitstream-formats.edit.internal.hint": "Formats marked as internal are hidden from the user, and used for administrative purposes.", - "admin.registries.bitstream-formats.edit.internal.hint": "Dateiformate, die als intern gekennzeichnet sind, dienen administrativen Zwecken und bleiben dem/der Endnutzer:in verborgen.", + "admin.registries.bitstream-formats.edit.internal.hint": "Dateiformate, die als intern gekennzeichnet sind, dienen administrativen Zwecken und bleiben dem:der Endnutzer:in verborgen.", // "admin.registries.bitstream-formats.edit.internal.label": "Internal", "admin.registries.bitstream-formats.edit.internal.label": "Intern", @@ -765,7 +765,7 @@ "bitstream.edit.form.description.label": "Beschreibung", // "bitstream.edit.form.embargo.hint": "The first day from which access is allowed. This date cannot be modified on this form. To set an embargo date for a bitstream, go to the Item Status tab, click Authorizations..., create or edit the bitstream's READ policy, and set the Start Date as desired.", - "bitstream.edit.form.embargo.hint": "Der erste Tag, ab dem der Zugang erlaubt ist. This date cannot be modified on this form. Um eine Embargo-Frist für eine Datei festzulegen, zu Item Status gehen, auf Authorizations... klicken, die Richtlinie READ für die Datei erstellen oder verändern und das Start Date wie gewünscht einstellen.", + "bitstream.edit.form.embargo.hint": "Der erste Tag, ab dem der Zugang erlaubt ist. Dieses Datum kann in diesem Formular nicht verändert werden. Um eine Embargo-Frist für eine Datei festzulegen, zu Item Status gehen, auf Authorizations... klicken, die Richtlinie READ für die Datei erstellen oder verändern und das Start Date wie gewünscht einstellen.", // "bitstream.edit.form.embargo.label": "Embargo until specific date", @@ -1499,7 +1499,7 @@ "cookies.consent.decline": "Ablehnen", // "cookies.consent.content-notice.description": "We collect and process your personal information for the following purposes: Authentication, Preferences, Acknowledgement and Statistics.
    To learn more, please read our {privacyPolicy}.", - "cookies.consent.content-notice.description": "Wir sammeln und verarbeiten Ihre personenbezogenen Daten für die folgenden Zwecke: Authentication, Preferences, Acknowledgement and Statistics.
    Um mehr zu erfahren, lesen Sie bitte unsere {privacyPolicy}.", + "cookies.consent.content-notice.description": "Wir sammeln und verarbeiten Ihre personenbezogenen Daten für die folgenden Zwecke: Authentifikation, Einstellungen, Zustimmungen und Statistiken.
    Um mehr zu erfahren, lesen Sie bitte unsere {privacyPolicy}.", // "cookies.consent.content-notice.learnMore": "Customize", "cookies.consent.content-notice.learnMore": "Anpassen", @@ -1598,7 +1598,7 @@ "curation.form.handle.label": "Handle:", // "curation.form.handle.hint": "Hint: Enter [your-handle-prefix]/0 to run a task across entire site (not all tasks may support this capability)", - "curation.form.handle.hint": "Hinweis: Geben Sie [your-handle-prefix]/0 ein, um eine Aufgabe für die gesamte Seite durchzuführen (nicht alle Aufgaben unterstützen diese Funktion)", + "curation.form.handle.hint": "Hinweis: Geben Sie [handle-prefix]/0 ein, um eine Aufgabe für die gesamte Seite durchzuführen (nicht alle Aufgaben unterstützen diese Funktion)", @@ -2636,7 +2636,7 @@ "item.page.link.simple": "Kurzanzeige", // "item.page.person.search.title": "Articles by this author", - "item.page.person.search.title": "Veröffentlichungen dieses/dieser Autor:in", + "item.page.person.search.title": "Veröffentlichungen dieses:dieser Autor:in", // "item.page.related-items.view-more": "Show {{ amount }} more", "item.page.related-items.view-more": "{{ amount }} mehr anzeigen", @@ -3182,7 +3182,7 @@ "mydspace.general.text-here": "hier", // "mydspace.messages.controller-help": "Select this option to send a message to item's submitter.", - "mydspace.messages.controller-help": "Wählen Sie diese Option, um dem/derjenigen, die das Item eingereicht hat, eine Nachricht zu schicken.", + "mydspace.messages.controller-help": "Wählen Sie diese Option, um dem:derjenigen, die das Item eingereicht hat, eine Nachricht zu schicken.", // "mydspace.messages.description-placeholder": "Insert your message here...", "mydspace.messages.description-placeholder": "Geben Sie Ihre Nachricht hier ein...", @@ -3290,10 +3290,10 @@ "mydspace.upload.upload-failed-moreonefile": "Unverarbeitbare Anfrage. Nur eine Datei ist erlaubt.", // "mydspace.upload.upload-multiple-successful": "{{qty}} new workspace items created.", - "mydspace.upload.upload-multiple-successful": "{{qty}} neue(s) Arbeitsbereichitem(s) angelegt.", + "mydspace.upload.upload-multiple-successful": "{{qty}} neue(s) Arbeitsbereichsitem(s) angelegt.", // "mydspace.upload.upload-successful": "New workspace item created. Click {{here}} for edit it.", - "mydspace.upload.upload-successful": "Neues Arbeitsbereichitem angelegt. Klicken Sie {{here}}, um sie zu bearbeiten.", + "mydspace.upload.upload-successful": "Neues Arbeitsbereichsitem angelegt. Klicken Sie {{here}}, um sie zu bearbeiten.", // "mydspace.view-btn": "View", "mydspace.view-btn": "Anzeige", @@ -3763,7 +3763,7 @@ "register-page.create-profile.submit.error.head": "Registrierung fehlgeschlagen.", // "register-page.create-profile.submit.success.content": "The registration was successful. You have been logged in as the created user.", - "register-page.create-profile.submit.success.content": "Die Registrierung war erfolgreich. Sie wurden als der/die angelegte Benutzer:in angemeldet.", + "register-page.create-profile.submit.success.content": "Die Registrierung war erfolgreich. Sie wurden als der:die angelegte Benutzer:in angemeldet.", // "register-page.create-profile.submit.success.head": "Registration completed", "register-page.create-profile.submit.success.head": "Registrierung abgeschlossen", @@ -4078,7 +4078,7 @@ "search.filters.filter.author.head": "Autor:in", // "search.filters.filter.author.placeholder": "Author name", - "search.filters.filter.author.placeholder": "Name des/der Autor:in", + "search.filters.filter.author.placeholder": "Name des:der Autor:in", // "search.filters.filter.birthDate.head": "Birth Date", "search.filters.filter.birthDate.head": "Geburtsdatum", @@ -4474,10 +4474,10 @@ "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.title": "Importiere Autor:innen-Metadaten", // "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.local-entity": "Successfully added local author to the selection", - "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.local-entity": "Erfolgreicher Import und Hinzufügen eines/einer lokalen Autor:in zur Auswahl", + "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.local-entity": "Erfolgreicher Import und Hinzufügen eines:einer lokalen Autor:in zur Auswahl", // "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.new-entity": "Successfully imported and added external author to the selection", - "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.new-entity": "Erfolgreicher Import und Hinzufügen eines/einer externen Autor:in zur Auswahl", + "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.new-entity": "Erfolgreicher Import und Hinzufügen eines:einer externen Autor:in zur Auswahl", // "submission.sections.describe.relationship-lookup.external-source.import-modal.authority": "Authority", "submission.sections.describe.relationship-lookup.external-source.import-modal.authority": "Referenz", @@ -4573,7 +4573,7 @@ "submission.sections.describe.relationship-lookup.selected": "{{ size }} Items ausgewählt", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isAuthorOfPublication": "Local Authors ({{ count }})", - "submission.sections.describe.relationship-lookup.search-tab.tab-title.isAuthorOfPublication": "Lokale Autoren ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.isAuthorOfPublication": "Lokale Autor:innen ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalOfPublication": "Local Journals ({{ count }})", "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalOfPublication": "Lokale Zeitschriften ({{ count }})", @@ -4585,7 +4585,7 @@ "submission.sections.describe.relationship-lookup.search-tab.tab-title.Publication": "Lokale Publicationen ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.Person": "Local Authors ({{ count }})", - "submission.sections.describe.relationship-lookup.search-tab.tab-title.Person": "Lokale Autoren ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.Person": "Lokale Autor:innen ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.OrgUnit": "Local Organizational Units ({{ count }})", "submission.sections.describe.relationship-lookup.search-tab.tab-title.OrgUnit": "Lokale Organisationseinheiten ({{ count }})", @@ -4973,7 +4973,7 @@ "submission.workflow.tasks.claimed.edit_help": "Wählen Sie diese Option, um die Metadaten des Items zu bearbeiten.", // "submission.workflow.tasks.claimed.reject.reason.info": "Please enter your reason for rejecting the submission into the box below, indicating whether the submitter may fix a problem and resubmit.", - "submission.workflow.tasks.claimed.reject.reason.info": "Bitte geben Sie den Grund für die Ablehnung der eingereichten Ressource in das Feld unten ein. Bitte geben Sie an ob und wie der/die Einreichende:n das Problem beheben und die Ressource erneut einreichen kann.", + "submission.workflow.tasks.claimed.reject.reason.info": "Bitte geben Sie den Grund für die Ablehnung der eingereichten Ressource in das Feld unten ein. Bitte geben Sie an ob und wie der:die Einreichende:n das Problem beheben und die Ressource erneut einreichen kann.", // "submission.workflow.tasks.claimed.reject.reason.placeholder": "Describe the reason of reject", "submission.workflow.tasks.claimed.reject.reason.placeholder": "Beschreiben Sie den Grund für die Ablehnung", @@ -4988,7 +4988,7 @@ "submission.workflow.tasks.claimed.reject.submit": "Ablehnen", // "submission.workflow.tasks.claimed.reject_help": "If you have reviewed the item and found it is not suitable for inclusion in the collection, select \"Reject\". You will then be asked to enter a message indicating why the item is unsuitable, and whether the submitter should change something and resubmit.", - "submission.workflow.tasks.claimed.reject_help": "Wenn Sie das Item begutachtet und als ungeeignet für die Aufnahme in die Sammlung befunden haben, wählen Sie \"Ablehnen\". Sie haben dann die Möglichkeit dem/der Einreichenden, den Grund für die Ablehnung zu erklären und ob es eine Möglichkeit gibt, durch entsprechenden Änderungen das Item erneut einzureichen.", + "submission.workflow.tasks.claimed.reject_help": "Wenn Sie das Item begutachtet und als ungeeignet für die Aufnahme in die Sammlung befunden haben, wählen Sie \"Ablehnen\". Sie haben dann die Möglichkeit dem:der Einreichenden, den Grund für die Ablehnung zu erklären und ob es eine Möglichkeit gibt, durch entsprechenden Änderungen das Item erneut einzureichen.", // "submission.workflow.tasks.claimed.return": "Return to pool", "submission.workflow.tasks.claimed.return": "Zurück in den gemeinsamen Aufgabenbereich", @@ -5111,22 +5111,22 @@ // "workflow-item.send-back.notification.success.title": "Sent back to submitter", - "workflow-item.send-back.notification.success.title": "An die/den Einreichende:n zurückgeschickt", + "workflow-item.send-back.notification.success.title": "An die:den Einreichende:n zurückgeschickt", // "workflow-item.send-back.notification.success.content": "This workflow item was successfully sent back to the submitter", - "workflow-item.send-back.notification.success.content": "Dieses Workflow-Item wurde erfolgreich an die/den Einreichende:n zurückgeschickt", + "workflow-item.send-back.notification.success.content": "Dieses Workflow-Item wurde erfolgreich an die:den Einreichende:n zurückgeschickt", // "workflow-item.send-back.notification.error.title": "Something went wrong", "workflow-item.send-back.notification.error.title": "Etwas ist schief gelaufen", // "workflow-item.send-back.notification.error.content": "The workflow item could not be sent back to the submitter", - "workflow-item.send-back.notification.error.content": "Das Workflow-Item konnte nicht an die/den Einreichende:n zurückgeschickt werden", + "workflow-item.send-back.notification.error.content": "Das Workflow-Item konnte nicht an die:den Einreichende:n zurückgeschickt werden", // "workflow-item.send-back.title": "Send workflow item back to submitter", - "workflow-item.send-back.title": "Workflow-Item an die/den Einreichende:n zurücksenden", + "workflow-item.send-back.title": "Workflow-Item an die:den Einreichende:n zurücksenden", // "workflow-item.send-back.header": "Send workflow item back to submitter", - "workflow-item.send-back.header": "Workflow-Item an die/den Einreichende:n zurücksenden", + "workflow-item.send-back.header": "Workflow-Item an die:den Einreichende:n zurücksenden", // "workflow-item.send-back.button.cancel": "Cancel", "workflow-item.send-back.button.cancel": "Abbrechen", From 4affbd8d57e2ceeda2877682ae25138b88221f5a Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 11 Mar 2022 12:18:14 +0100 Subject: [PATCH 043/160] [CST-5449] Fix failed test --- .../shared/browse-by/browse-by.component.html | 6 ++--- .../browse-by/browse-by.component.spec.ts | 26 +++++++------------ 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/app/shared/browse-by/browse-by.component.html b/src/app/shared/browse-by/browse-by.component.html index cc126768e4..3b72ce2d23 100644 --- a/src/app/shared/browse-by/browse-by.component.html +++ b/src/app/shared/browse-by/browse-by.component.html @@ -1,15 +1,15 @@ -

    {{title | translate}}

    +

    {{title | translate}}

    - - +
    diff --git a/src/app/shared/browse-by/browse-by.component.spec.ts b/src/app/shared/browse-by/browse-by.component.spec.ts index 1e739dafbf..601c352705 100644 --- a/src/app/shared/browse-by/browse-by.component.spec.ts +++ b/src/app/shared/browse-by/browse-by.component.spec.ts @@ -4,20 +4,17 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { By } from '@angular/platform-browser'; import { Component, NO_ERRORS_SCHEMA } from '@angular/core'; import { of as observableOf } from 'rxjs'; -import { SharedModule } from '../shared.module'; import { CommonModule } from '@angular/common'; import { Item } from '../../core/shared/item.model'; import { buildPaginatedList } from '../../core/data/paginated-list.model'; import { PageInfo } from '../../core/shared/page-info.model'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { StoreModule } from '@ngrx/store'; import { TranslateLoaderMock } from '../mocks/translate-loader.mock'; import { RouterTestingModule } from '@angular/router/testing'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils'; -import { storeModuleConfig } from '../../app.reducer'; import { PaginationService } from '../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../testing/pagination-service.stub'; import { ListableObjectComponentLoaderComponent } from '../object-collection/shared/listable-object/listable-object-component-loader.component'; @@ -30,17 +27,18 @@ import { import { BrowseEntry } from '../../core/shared/browse-entry.model'; import { ITEM } from '../../core/shared/item.resource-type'; import { ThemeService } from '../theme-support/theme.service'; -import SpyObj = jasmine.SpyObj; import { SelectableListService } from '../object-list/selectable-list/selectable-list.service'; +import { HostWindowServiceStub } from '../testing/host-window-service.stub'; import { HostWindowService } from '../host-window.service'; -import { CSSVariableService } from '../sass-helper/sass-helper.service'; +import SpyObj = jasmine.SpyObj; @listableObjectComponent(BrowseEntry, ViewMode.ListElement, DEFAULT_CONTEXT, 'custom') @Component({ selector: 'ds-browse-entry-list-element', template: '' }) -class MockThemedBrowseEntryListElementComponent {} +class MockThemedBrowseEntryListElementComponent { +} describe('BrowseByComponent', () => { let comp: BrowseByComponent; @@ -86,10 +84,7 @@ describe('BrowseByComponent', () => { TestBed.configureTestingModule({ imports: [ CommonModule, - TranslateModule.forRoot(), - SharedModule, NgbModule, - StoreModule.forRoot({}, storeModuleConfig), TranslateModule.forRoot({ loader: { provide: TranslateLoader, @@ -97,16 +92,15 @@ describe('BrowseByComponent', () => { } }), RouterTestingModule, - BrowserAnimationsModule + NoopAnimationsModule ], declarations: [], providers: [ - {provide: PaginationService, useValue: paginationService}, - {provide: MockThemedBrowseEntryListElementComponent}, + { provide: PaginationService, useValue: paginationService }, + { provide: MockThemedBrowseEntryListElementComponent }, { provide: ThemeService, useValue: themeService }, - SelectableListService, - HostWindowService, - CSSVariableService + { provide: SelectableListService, useValue: {} }, + { provide: HostWindowService, useValue: new HostWindowServiceStub(800) }, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); From d4ed4ca88350eccaab9f127644ff7843c7d61cfa Mon Sep 17 00:00:00 2001 From: reetagithub <51482276+reetagithub@users.noreply.github.com> Date: Mon, 14 Mar 2022 09:05:48 +0200 Subject: [PATCH 044/160] Corrected a typo --- src/assets/i18n/fi.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/i18n/fi.json5 b/src/assets/i18n/fi.json5 index 0348b906c2..02f020a45d 100644 --- a/src/assets/i18n/fi.json5 +++ b/src/assets/i18n/fi.json5 @@ -4120,7 +4120,7 @@ "search.filters.filter.dateSubmitted.placeholder": "Tallennnusajankohta", // "search.filters.filter.discoverable.head": "Private", - "search.filters.filter.discoverable.head": "Ykstyinen", + "search.filters.filter.discoverable.head": "Yksityinen", // "search.filters.filter.withdrawn.head": "Withdrawn", "search.filters.filter.withdrawn.head": "Poistettu käytöstä", From 3a4a10e45385b46ee977fd4282b86b8ed9b140f5 Mon Sep 17 00:00:00 2001 From: William Welling <8352733+wwelling@users.noreply.github.com> Date: Mon, 14 Mar 2022 08:26:22 -0500 Subject: [PATCH 045/160] set default env preboot to false leaving production env to true --- src/environments/environment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/environments/environment.ts b/src/environments/environment.ts index a64ccf2608..dc0e808be0 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -10,7 +10,7 @@ export const environment: Partial = { // Angular Universal settings universal: { - preboot: true, + preboot: false, async: true, time: false } From b21f76456d641192d1b9b65362c0c6a7396be5b1 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 14 Mar 2022 16:33:10 +0100 Subject: [PATCH 046/160] 88248: #346: Withdrawn item tombstone page --- .../full/full-item-page.component.html | 44 ++++++------ .../full/full-item-page.component.spec.ts | 67 ++++++++++++++++++- .../full/full-item-page.component.ts | 4 +- .../item-page/simple/item-page.component.html | 2 +- .../simple/item-page.component.spec.ts | 62 +++++++++++++++++ .../item-page/simple/item-page.component.ts | 12 +++- .../item-alerts/item-alerts.component.html | 17 +++-- 7 files changed, 178 insertions(+), 30 deletions(-) diff --git a/src/app/item-page/full/full-item-page.component.html b/src/app/item-page/full/full-item-page.component.html index d2655c4ad0..e71dd92f96 100644 --- a/src/app/item-page/full/full-item-page.component.html +++ b/src/app/item-page/full/full-item-page.component.html @@ -4,19 +4,21 @@ -
    - -
    - +
    +
    + +
    + +
    -
    - - - + +
    + @@ -24,14 +26,16 @@ - -
    {{mdEntry.key}}{{mdValue.language}}
    - - - -
    -
    - + + + + + +
    +
    + +
    diff --git a/src/app/item-page/full/full-item-page.component.spec.ts b/src/app/item-page/full/full-item-page.component.spec.ts index b4ab926667..c7643a940e 100644 --- a/src/app/item-page/full/full-item-page.component.spec.ts +++ b/src/app/item-page/full/full-item-page.component.spec.ts @@ -11,12 +11,15 @@ import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; import { VarDirective } from '../../shared/utils/var.directive'; import { RouterTestingModule } from '@angular/router/testing'; import { Item } from '../../core/shared/item.model'; -import { of as observableOf } from 'rxjs'; +import { BehaviorSubject, of as observableOf } from 'rxjs'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { By } from '@angular/platform-browser'; import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { AuthService } from '../../core/auth/auth.service'; import { createPaginatedList } from '../../shared/testing/utils.test'; +import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; +import { createRelationshipsObservable } from '../simple/item-types/shared/item.component.spec'; +import { RemoteData } from '../../core/data/remote-data'; const mockItem: Item = Object.assign(new Item(), { bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), @@ -30,6 +33,13 @@ const mockItem: Item = Object.assign(new Item(), { } }); +const mockWithdrawnItem: Item = Object.assign(new Item(), { + bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), + metadata: [], + relationships: createRelationshipsObservable(), + isWithdrawn: true +}); + const metadataServiceStub = { /* tslint:disable:no-empty */ processRemoteData: () => { @@ -44,6 +54,7 @@ describe('FullItemPageComponent', () => { let authService: AuthService; let routeStub: ActivatedRouteStub; let routeData; + let authorizationDataService: AuthorizationDataService; @@ -61,6 +72,10 @@ describe('FullItemPageComponent', () => { data: observableOf(routeData) }); + authorizationDataService = jasmine.createSpyObj('authorizationDataService', { + isAuthorized: observableOf(false), + }); + TestBed.configureTestingModule({ imports: [TranslateModule.forRoot({ loader: { @@ -74,6 +89,7 @@ describe('FullItemPageComponent', () => { { provide: ItemDataService, useValue: {} }, { provide: MetadataService, useValue: metadataServiceStub }, { provide: AuthService, useValue: authService }, + { provide: AuthorizationDataService, useValue: authorizationDataService }, ], schemas: [NO_ERRORS_SCHEMA] @@ -111,4 +127,53 @@ describe('FullItemPageComponent', () => { expect(simpleViewBtn).toBeFalsy(); }); })); + + describe('when the item is withdrawn and the user is an admin', () => { + beforeEach(() => { + comp.isAdmin$ = observableOf(true); + comp.itemRD$ = new BehaviorSubject>(createSuccessfulRemoteDataObject(mockWithdrawnItem)); + fixture.detectChanges(); + }); + + it('should display the item', () => { + const objectLoader = fixture.debugElement.query(By.css('.full-item-info')); + expect(objectLoader.nativeElement).toBeDefined(); + }); + }); + describe('when the item is withdrawn and the user is not an admin', () => { + beforeEach(() => { + comp.itemRD$ = new BehaviorSubject>(createSuccessfulRemoteDataObject(mockWithdrawnItem)); + fixture.detectChanges(); + }); + + it('should not display the item', () => { + const objectLoader = fixture.debugElement.query(By.css('.full-item-info')); + expect(objectLoader).toBeNull(); + }); + }); + + describe('when the item is not withdrawn and the user is an admin', () => { + beforeEach(() => { + comp.isAdmin$ = observableOf(true); + comp.itemRD$ = new BehaviorSubject>(createSuccessfulRemoteDataObject(mockItem)); + fixture.detectChanges(); + }); + + it('should display the item', () => { + const objectLoader = fixture.debugElement.query(By.css('.full-item-info')); + expect(objectLoader.nativeElement).toBeDefined(); + }); + }); + + describe('when the item is not withdrawn and the user is not an admin', () => { + beforeEach(() => { + comp.itemRD$ = new BehaviorSubject>(createSuccessfulRemoteDataObject(mockItem)); + fixture.detectChanges(); + }); + + it('should display the item', () => { + const objectLoader = fixture.debugElement.query(By.css('.full-item-info')); + expect(objectLoader.nativeElement).toBeDefined(); + }); + }); }); diff --git a/src/app/item-page/full/full-item-page.component.ts b/src/app/item-page/full/full-item-page.component.ts index 7f1b6de614..369769c77d 100644 --- a/src/app/item-page/full/full-item-page.component.ts +++ b/src/app/item-page/full/full-item-page.component.ts @@ -15,6 +15,7 @@ import { fadeInOut } from '../../shared/animations/fade'; import { hasValue } from '../../shared/empty.util'; import { AuthService } from '../../core/auth/auth.service'; import { Location } from '@angular/common'; +import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; /** @@ -46,8 +47,9 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit, router: Router, items: ItemDataService, authService: AuthService, + authorizationService: AuthorizationDataService, private _location: Location) { - super(route, router, items, authService); + super(route, router, items, authService, authorizationService); } /*** AoT inheritance fix, will hopefully be resolved in the near future **/ diff --git a/src/app/item-page/simple/item-page.component.html b/src/app/item-page/simple/item-page.component.html index e843155d10..3e834b53db 100644 --- a/src/app/item-page/simple/item-page.component.html +++ b/src/app/item-page/simple/item-page.component.html @@ -4,7 +4,7 @@ - +
    diff --git a/src/app/item-page/simple/item-page.component.spec.ts b/src/app/item-page/simple/item-page.component.spec.ts index ff5a1e38d5..2ad0bbb272 100644 --- a/src/app/item-page/simple/item-page.component.spec.ts +++ b/src/app/item-page/simple/item-page.component.spec.ts @@ -21,6 +21,7 @@ import { } from '../../shared/remote-data.utils'; import { AuthService } from '../../core/auth/auth.service'; import { createPaginatedList } from '../../shared/testing/utils.test'; +import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; const mockItem: Item = Object.assign(new Item(), { bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), @@ -28,10 +29,18 @@ const mockItem: Item = Object.assign(new Item(), { relationships: createRelationshipsObservable() }); +const mockWithdrawnItem: Item = Object.assign(new Item(), { + bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), + metadata: [], + relationships: createRelationshipsObservable(), + isWithdrawn: true +}); + describe('ItemPageComponent', () => { let comp: ItemPageComponent; let fixture: ComponentFixture; let authService: AuthService; + let authorizationDataService: AuthorizationDataService; const mockMetadataService = { /* tslint:disable:no-empty */ @@ -48,6 +57,9 @@ describe('ItemPageComponent', () => { isAuthenticated: observableOf(true), setRedirectUrl: {} }); + authorizationDataService = jasmine.createSpyObj('authorizationDataService', { + isAuthorized: observableOf(false), + }); TestBed.configureTestingModule({ imports: [TranslateModule.forRoot({ @@ -63,6 +75,7 @@ describe('ItemPageComponent', () => { { provide: MetadataService, useValue: mockMetadataService }, { provide: Router, useValue: {} }, { provide: AuthService, useValue: authService }, + { provide: AuthorizationDataService, useValue: authorizationDataService }, ], schemas: [NO_ERRORS_SCHEMA] @@ -102,4 +115,53 @@ describe('ItemPageComponent', () => { }); }); + describe('when the item is withdrawn and the user is an admin', () => { + beforeEach(() => { + comp.isAdmin$ = observableOf(true); + comp.itemRD$ = createSuccessfulRemoteDataObject$(mockWithdrawnItem); + fixture.detectChanges(); + }); + + it('should display the item', () => { + const objectLoader = fixture.debugElement.query(By.css('ds-listable-object-component-loader')); + expect(objectLoader.nativeElement).toBeDefined(); + }); + }); + describe('when the item is withdrawn and the user is not an admin', () => { + beforeEach(() => { + comp.itemRD$ = createSuccessfulRemoteDataObject$(mockWithdrawnItem); + fixture.detectChanges(); + }); + + it('should not display the item', () => { + const objectLoader = fixture.debugElement.query(By.css('ds-listable-object-component-loader')); + expect(objectLoader).toBeNull(); + }); + }); + + describe('when the item is not withdrawn and the user is an admin', () => { + beforeEach(() => { + comp.isAdmin$ = observableOf(true); + comp.itemRD$ = createSuccessfulRemoteDataObject$(mockItem); + fixture.detectChanges(); + }); + + it('should display the item', () => { + const objectLoader = fixture.debugElement.query(By.css('ds-listable-object-component-loader')); + expect(objectLoader.nativeElement).toBeDefined(); + }); + }); + + describe('when the item is not withdrawn and the user is not an admin', () => { + beforeEach(() => { + comp.itemRD$ = createSuccessfulRemoteDataObject$(mockItem); + fixture.detectChanges(); + }); + + it('should display the item', () => { + const objectLoader = fixture.debugElement.query(By.css('ds-listable-object-component-loader')); + expect(objectLoader.nativeElement).toBeDefined(); + }); + }); + }); diff --git a/src/app/item-page/simple/item-page.component.ts b/src/app/item-page/simple/item-page.component.ts index cc23ba86d5..0559dc2219 100644 --- a/src/app/item-page/simple/item-page.component.ts +++ b/src/app/item-page/simple/item-page.component.ts @@ -1,4 +1,4 @@ -import { map } from 'rxjs/operators'; +import { map, switchMap } from 'rxjs/operators'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; @@ -13,6 +13,8 @@ import { getAllSucceededRemoteDataPayload, redirectOn4xx } from '../../core/shar import { ViewMode } from '../../core/shared/view-mode.model'; import { AuthService } from '../../core/auth/auth.service'; import { getItemPageRoute } from '../item-page-routing-paths'; +import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from '../../core/data/feature-authorization/feature-id'; /** * This component renders a simple item page. @@ -48,11 +50,17 @@ export class ItemPageComponent implements OnInit { */ itemPageRoute$: Observable; + /** + * Whether the current user is an admin or not + */ + isAdmin$: Observable; + constructor( protected route: ActivatedRoute, private router: Router, private items: ItemDataService, private authService: AuthService, + private authorizationService: AuthorizationDataService ) { } /** @@ -67,5 +75,7 @@ export class ItemPageComponent implements OnInit { getAllSucceededRemoteDataPayload(), map((item) => getItemPageRoute(item)) ); + + this.isAdmin$ = this.authorizationService.isAuthorized(FeatureID.AdministratorOf); } } diff --git a/src/app/shared/item/item-alerts/item-alerts.component.html b/src/app/shared/item/item-alerts/item-alerts.component.html index fce5c038e5..740ea2595d 100644 --- a/src/app/shared/item/item-alerts/item-alerts.component.html +++ b/src/app/shared/item/item-alerts/item-alerts.component.html @@ -1,8 +1,13 @@
    -
    - -
    -
    - -
    +
    + +
    +
    + +
    + {{'item.alerts.withdrawn' | translate}} + {{"404.link.home-page" | translate}} +
    +
    +
    From 90e469ee167a3c37046825f0aea0f98ec9b7064f Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 14 Mar 2022 17:21:34 +0100 Subject: [PATCH 047/160] Fix LGTM issue --- src/app/item-page/simple/item-page.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/item-page/simple/item-page.component.ts b/src/app/item-page/simple/item-page.component.ts index 0559dc2219..95fbd7a2e0 100644 --- a/src/app/item-page/simple/item-page.component.ts +++ b/src/app/item-page/simple/item-page.component.ts @@ -1,4 +1,4 @@ -import { map, switchMap } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; From 97dc241c5208d949826a6ebd930615adb47c31c7 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 17 Mar 2022 13:00:00 +0100 Subject: [PATCH 048/160] 88300: Issue 1379 - Angular: Browse-by Subject does not allow to proceed to next pages once filtered --- .../shared/browse-by/browse-by.component.html | 23 +++- .../browse-by/browse-by.component.spec.ts | 20 +++- .../shared/browse-by/browse-by.component.ts | 19 ++- .../text/starts-with-text.component.html | 19 --- .../text/starts-with-text.component.spec.ts | 110 ------------------ src/app/shared/testing/route-service.stub.ts | 7 +- src/assets/i18n/en.json5 | 6 +- 7 files changed, 65 insertions(+), 139 deletions(-) diff --git a/src/app/shared/browse-by/browse-by.component.html b/src/app/shared/browse-by/browse-by.component.html index c133324681..4837c92db8 100644 --- a/src/app/shared/browse-by/browse-by.component.html +++ b/src/app/shared/browse-by/browse-by.component.html @@ -12,6 +12,10 @@
- - + +
-