diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.scss b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.scss
new file mode 100644
index 0000000000..65f38247c8
--- /dev/null
+++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.scss
@@ -0,0 +1,7 @@
+:host ::ng-deep {
+ .reviewersListWithGroup {
+ #search, #search + form, #search + form + ds-pagination {
+ display: none !important;
+ }
+ }
+}
diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts
new file mode 100644
index 0000000000..71952ce9c7
--- /dev/null
+++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts
@@ -0,0 +1,165 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { Location } from '@angular/common';
+import {
+ AdvancedWorkflowActionSelectReviewerComponent,
+ ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER,
+} from './advanced-workflow-action-select-reviewer.component';
+import { ActivatedRoute, Router } from '@angular/router';
+import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service';
+import { WorkflowItemDataServiceStub } from '../../../shared/testing/workflow-item-data-service.stub';
+import { WorkflowActionDataServiceStub } from '../../../shared/testing/workflow-action-data-service.stub';
+import { WorkflowActionDataService } from '../../../core/data/workflow-action-data.service';
+import { RouteService } from '../../../core/services/route.service';
+import { routeServiceStub } from '../../../shared/testing/route-service.stub';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
+import { TranslateModule } from '@ngx-translate/core';
+import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service';
+import { ClaimedTaskDataServiceStub } from '../../../shared/testing/claimed-task-data-service.stub';
+import { of as observableOf } from 'rxjs';
+import { WorkflowItem } from '../../../core/submission/models/workflowitem.model';
+import { createSuccessfulRemoteDataObject$, createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
+import { Item } from '../../../core/shared/item.model';
+import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock';
+import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response';
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { RequestService } from '../../../core/data/request.service';
+import { RequestServiceStub } from '../../../shared/testing/request-service.stub';
+import { RouterStub } from '../../../shared/testing/router.stub';
+import { LocationStub } from '../../../shared/testing/location.stub';
+
+const claimedTaskId = '2';
+const workflowId = '1';
+
+describe('AdvancedWorkflowActionSelectReviewerComponent', () => {
+ const workflowItem: WorkflowItem = new WorkflowItem();
+ workflowItem.item = createSuccessfulRemoteDataObject$(new Item());
+ let component: AdvancedWorkflowActionSelectReviewerComponent;
+ let fixture: ComponentFixture;
+
+ let claimedTaskDataService: ClaimedTaskDataServiceStub;
+ let location: LocationStub;
+ let notificationService: NotificationsServiceStub;
+ let router: RouterStub;
+ let workflowActionDataService: WorkflowItemDataServiceStub;
+ let workflowItemDataService: WorkflowItemDataServiceStub;
+
+ beforeEach(async () => {
+ claimedTaskDataService = new ClaimedTaskDataServiceStub();
+ location = new LocationStub();
+ notificationService = new NotificationsServiceStub();
+ router = new RouterStub();
+ workflowActionDataService = new WorkflowActionDataServiceStub();
+ workflowItemDataService = new WorkflowItemDataServiceStub();
+
+ await TestBed.configureTestingModule({
+ imports: [
+ TranslateModule.forRoot(),
+ ],
+ declarations: [
+ AdvancedWorkflowActionSelectReviewerComponent,
+ ],
+ providers: [
+ {
+ provide: ActivatedRoute,
+ useValue: {
+ data: observableOf({
+ id: workflowId,
+ wfi: createSuccessfulRemoteDataObject(workflowItem),
+ }),
+ snapshot: {
+ queryParams: {
+ claimedTask: claimedTaskId,
+ workflow: 'testaction',
+ previousSearchQuery: 'Thor%20Love%20and%20Thunder',
+ },
+ },
+ },
+ },
+ { provide: ClaimedTaskDataService, useValue: claimedTaskDataService },
+ { provide: Location, useValue: location },
+ { provide: NotificationsService, useValue: notificationService },
+ { provide: Router, useValue: router },
+ { provide: RouteService, useValue: routeServiceStub },
+ { provide: WorkflowActionDataService, useValue: workflowActionDataService },
+ { provide: WorkflowItemDataService, useValue: workflowItemDataService },
+ { provide: RequestService, useClass: RequestServiceStub },
+ ],
+ schemas: [NO_ERRORS_SCHEMA],
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(AdvancedWorkflowActionSelectReviewerComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ afterEach(() => {
+ fixture.debugElement.nativeElement.remove();
+ });
+
+ describe('previousPage', () => {
+ it('should navigate back to the Workflow tasks page with the previous query', () => {
+ spyOn(location, 'getState').and.returnValue({
+ previousQueryParams: {
+ configuration: 'workflow',
+ query: 'Thor Love and Thunder',
+ },
+ });
+
+ component.ngOnInit();
+ component.previousPage();
+
+ expect(router.navigate).toHaveBeenCalledWith(['/mydspace'], {
+ queryParams: {
+ configuration: 'workflow',
+ query: 'Thor Love and Thunder',
+ },
+ });
+ });
+ });
+
+ describe('performAction', () => {
+ beforeEach(() => {
+ spyOn(component, 'previousPage');
+ });
+
+ it('should call the claimedTaskDataService with the list of selected ePersons', () => {
+ spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true)));
+ component.selectedReviewers = [EPersonMock, EPersonMock2];
+
+ component.performAction();
+
+ expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, {
+ [ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER]: true,
+ eperson: [EPersonMock.id, EPersonMock2.id],
+ });
+ expect(notificationService.success).toHaveBeenCalled();
+ expect(component.previousPage).toHaveBeenCalled();
+ });
+
+ it('should not call the claimedTaskDataService with the list of selected ePersons when it\'s empty', () => {
+ spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true)));
+ component.selectedReviewers = [];
+
+ component.performAction();
+
+ expect(claimedTaskDataService.submitTask).not.toHaveBeenCalled();
+ });
+
+ it('should not call the return to mydspace page when the request failed', () => {
+ spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(false)));
+ component.selectedReviewers = [EPersonMock, EPersonMock2];
+
+ component.performAction();
+
+ expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, {
+ [ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER]: true,
+ eperson: [EPersonMock.id, EPersonMock2.id],
+ });
+ expect(notificationService.error).toHaveBeenCalled();
+ expect(component.previousPage).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts
new file mode 100644
index 0000000000..329af73351
--- /dev/null
+++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts
@@ -0,0 +1,152 @@
+import { Component, OnInit, OnDestroy } from '@angular/core';
+import { Location } from '@angular/common';
+import {
+ rendersAdvancedWorkflowTaskOption
+} from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator';
+import { AdvancedWorkflowActionComponent } from '../advanced-workflow-action/advanced-workflow-action.component';
+import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model';
+import {
+ SelectReviewerAdvancedWorkflowInfo
+} from '../../../core/tasks/models/select-reviewer-advanced-workflow-info.model';
+import {
+ EPersonListActionConfig
+} from '../../../access-control/group-registry/group-form/members-list/members-list.component';
+import { Subscription } from 'rxjs';
+import { EPerson } from '../../../core/eperson/models/eperson.model';
+import { ActivatedRoute, Params, Router } from '@angular/router';
+import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service';
+import { RouteService } from '../../../core/services/route.service';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { TranslateService } from '@ngx-translate/core';
+import { WorkflowActionDataService } from '../../../core/data/workflow-action-data.service';
+import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service';
+import { RequestService } from '../../../core/data/request.service';
+import { hasValue } from '../../../shared/empty.util';
+
+export const ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER = 'submit_select_reviewer';
+export const ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER = 'selectrevieweraction';
+
+/**
+ * The page on which Review Managers can assign Reviewers to review an item.
+ */
+@rendersAdvancedWorkflowTaskOption(ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER)
+@Component({
+ selector: 'ds-advanced-workflow-action-select-reviewer',
+ templateUrl: './advanced-workflow-action-select-reviewer.component.html',
+ styleUrls: ['./advanced-workflow-action-select-reviewer.component.scss'],
+})
+export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkflowActionComponent implements OnInit, OnDestroy {
+
+ multipleReviewers = true;
+
+ selectedReviewers: EPerson[] = [];
+
+ reviewersListActionConfig: EPersonListActionConfig;
+
+ /**
+ * When the component is created the value is `undefined`, afterwards it will be set to either the group id or `null`.
+ * It needs to be subscribed in the **ngOnInit()** because otherwise some unnecessary request will be made.
+ */
+ groupId?: string | null;
+
+ subs: Subscription[] = [];
+
+ displayError = false;
+
+ constructor(
+ protected route: ActivatedRoute,
+ protected workflowItemService: WorkflowItemDataService,
+ protected router: Router,
+ protected routeService: RouteService,
+ protected notificationsService: NotificationsService,
+ protected translationService: TranslateService,
+ protected workflowActionService: WorkflowActionDataService,
+ protected claimedTaskDataService: ClaimedTaskDataService,
+ protected requestService: RequestService,
+ protected location: Location,
+ ) {
+ super(route, workflowItemService, router, routeService, notificationsService, translationService, workflowActionService, claimedTaskDataService, requestService, location);
+ }
+
+ ngOnDestroy(): void {
+ this.subs.forEach((subscription: Subscription) => subscription.unsubscribe());
+ }
+
+ ngOnInit(): void {
+ super.ngOnInit();
+ if (this.multipleReviewers) {
+ this.reviewersListActionConfig = {
+ add: {
+ css: 'btn-outline-primary',
+ disabled: false,
+ icon: 'fas fa-plus',
+ },
+ remove: {
+ css: 'btn-outline-danger',
+ disabled: false,
+ icon: 'fas fa-minus'
+ },
+ };
+ } else {
+ this.reviewersListActionConfig = {
+ add: {
+ css: 'btn-outline-primary',
+ disabled: false,
+ icon: 'fas fa-check',
+ },
+ remove: {
+ css: 'btn-primary',
+ disabled: true,
+ icon: 'fas fa-check'
+ },
+ };
+ }
+ this.subs.push(this.workflowAction$.subscribe((workflowAction: WorkflowAction) => {
+ if (workflowAction) {
+ this.groupId = (workflowAction.advancedInfo as SelectReviewerAdvancedWorkflowInfo[])[0].group;
+ } else {
+ this.groupId = null;
+ }
+ }));
+ }
+
+ getType(): string {
+ return ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER;
+ }
+
+ /**
+ * Only performs the action when some reviewers have been selected.
+ */
+ performAction(): void {
+ if (this.selectedReviewers.length > 0) {
+ super.performAction();
+ } else {
+ this.displayError = true;
+ }
+ }
+
+ /**
+ * Returns the task option and the selected {@link EPerson} id(s)
+ */
+ createBody(): any {
+ return {
+ [ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER]: true,
+ eperson: this.selectedReviewers.map((ePerson: EPerson) => ePerson.id),
+ };
+ }
+
+ /**
+ * Hardcoded the previous page url because the {@link ReviewersListComponent} changes the previous route when
+ * switching between the different pages
+ */
+ previousPage(): void {
+ let queryParams: Params = this.previousQueryParameters;
+ if (!hasValue(queryParams)) {
+ queryParams = {
+ configuration: 'workflow',
+ };
+ }
+ void this.router.navigate(['/mydspace'], { queryParams: queryParams });
+ }
+
+}
diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.html b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.html
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts
new file mode 100644
index 0000000000..7c8db782ce
--- /dev/null
+++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts
@@ -0,0 +1,252 @@
+import { CommonModule } from '@angular/common';
+import { NO_ERRORS_SCHEMA, SimpleChange, DebugElement } from '@angular/core';
+import { ComponentFixture, fakeAsync, flush, TestBed, waitForAsync } from '@angular/core/testing';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { BrowserModule, By } from '@angular/platform-browser';
+import { Router } from '@angular/router';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
+import { Observable, of as observableOf } from 'rxjs';
+import { RestResponse } from '../../../../core/cache/response.models';
+import { buildPaginatedList, PaginatedList } from '../../../../core/data/paginated-list.model';
+import { RemoteData } from '../../../../core/data/remote-data';
+import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
+import { GroupDataService } from '../../../../core/eperson/group-data.service';
+import { EPerson } from '../../../../core/eperson/models/eperson.model';
+import { Group } from '../../../../core/eperson/models/group.model';
+import { PageInfo } from '../../../../core/shared/page-info.model';
+import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
+import { NotificationsService } from '../../../../shared/notifications/notifications.service';
+import { GroupMock, GroupMock2 } from '../../../../shared/testing/group-mock';
+import { ReviewersListComponent } from './reviewers-list.component';
+import { EPersonMock, EPersonMock2 } from '../../../../shared/testing/eperson.mock';
+import {
+ createSuccessfulRemoteDataObject$,
+ createNoContentRemoteDataObject$
+} from '../../../../shared/remote-data.utils';
+import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock';
+import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock';
+import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock';
+import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
+import { RouterMock } from '../../../../shared/mocks/router.mock';
+import { PaginationService } from '../../../../core/pagination/pagination.service';
+import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub';
+import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model';
+
+describe('ReviewersListComponent', () => {
+ let component: ReviewersListComponent;
+ let fixture: ComponentFixture;
+ let translateService: TranslateService;
+ let builderService: FormBuilderService;
+ let ePersonDataServiceStub: any;
+ let groupsDataServiceStub: any;
+ let activeGroup;
+ let allEPersons;
+ let allGroups;
+ let epersonMembers;
+ let subgroupMembers;
+ let paginationService;
+ let ePersonDtoModel1: EpersonDtoModel;
+ let ePersonDtoModel2: EpersonDtoModel;
+
+ beforeEach(waitForAsync(() => {
+ activeGroup = GroupMock;
+ epersonMembers = [EPersonMock2];
+ subgroupMembers = [GroupMock2];
+ allEPersons = [EPersonMock, EPersonMock2];
+ allGroups = [GroupMock, GroupMock2];
+ ePersonDataServiceStub = {
+ activeGroup: activeGroup,
+ epersonMembers: epersonMembers,
+ subgroupMembers: subgroupMembers,
+ findListByHref(_href: string): Observable>> {
+ return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), groupsDataServiceStub.getEPersonMembers()));
+ },
+ searchByScope(scope: string, query: string): Observable>> {
+ if (query === '') {
+ return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), allEPersons));
+ }
+ return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []));
+ },
+ clearEPersonRequests() {
+ // empty
+ },
+ clearLinkRequests() {
+ // empty
+ },
+ getEPeoplePageRouterLink(): string {
+ return '/access-control/epeople';
+ }
+ };
+ groupsDataServiceStub = {
+ activeGroup: activeGroup,
+ epersonMembers: epersonMembers,
+ subgroupMembers: subgroupMembers,
+ allGroups: allGroups,
+ getActiveGroup(): Observable {
+ return observableOf(activeGroup);
+ },
+ getEPersonMembers() {
+ return this.epersonMembers;
+ },
+ searchGroups(query: string): Observable>> {
+ if (query === '') {
+ return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), this.allGroups));
+ }
+ return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []));
+ },
+ addMemberToGroup(parentGroup, eperson: EPerson): Observable {
+ this.epersonMembers = [...this.epersonMembers, eperson];
+ return observableOf(new RestResponse(true, 200, 'Success'));
+ },
+ clearGroupsRequests() {
+ // empty
+ },
+ clearGroupLinkRequests() {
+ // empty
+ },
+ getGroupEditPageRouterLink(group: Group): string {
+ return '/access-control/groups/' + group.id;
+ },
+ deleteMemberFromGroup(parentGroup, epersonToDelete: EPerson): Observable {
+ this.epersonMembers = this.epersonMembers.find((eperson: EPerson) => {
+ if (eperson.id !== epersonToDelete.id) {
+ return eperson;
+ }
+ });
+ if (this.epersonMembers === undefined) {
+ this.epersonMembers = [];
+ }
+ return observableOf(new RestResponse(true, 200, 'Success'));
+ },
+ findById(id: string) {
+ for (const group of allGroups) {
+ if (group.id === id) {
+ return createSuccessfulRemoteDataObject$(group);
+ }
+ }
+ return createNoContentRemoteDataObject$();
+ },
+ editGroup() {
+ // empty
+ }
+ };
+ builderService = getMockFormBuilderService();
+ translateService = getMockTranslateService();
+
+ paginationService = new PaginationServiceStub();
+ TestBed.configureTestingModule({
+ imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
+ TranslateModule.forRoot({
+ loader: {
+ provide: TranslateLoader,
+ useClass: TranslateLoaderMock
+ }
+ }),
+ ],
+ declarations: [ReviewersListComponent],
+ providers: [ReviewersListComponent,
+ { provide: EPersonDataService, useValue: ePersonDataServiceStub },
+ { provide: GroupDataService, useValue: groupsDataServiceStub },
+ { provide: NotificationsService, useValue: new NotificationsServiceStub() },
+ { provide: FormBuilderService, useValue: builderService },
+ { provide: Router, useValue: new RouterMock() },
+ { provide: PaginationService, useValue: paginationService },
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ReviewersListComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+ afterEach(fakeAsync(() => {
+ fixture.destroy();
+ flush();
+ component = null;
+ fixture.debugElement.nativeElement.remove();
+ }));
+
+ beforeEach(() => {
+ ePersonDtoModel1 = new EpersonDtoModel();
+ ePersonDtoModel1.eperson = EPersonMock;
+ ePersonDtoModel2 = new EpersonDtoModel();
+ ePersonDtoModel2.eperson = EPersonMock2;
+ });
+
+ describe('when no group is selected', () => {
+ beforeEach(() => {
+ component.ngOnChanges({
+ groupId: new SimpleChange(undefined, null, true)
+ });
+ fixture.detectChanges();
+ });
+
+ it('should show no ePersons because no group is selected', () => {
+ const ePersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child'));
+ expect(ePersonIdsFound.length).toEqual(0);
+ epersonMembers.map((ePerson: EPerson) => {
+ expect(ePersonIdsFound.find((foundEl) => {
+ return (foundEl.nativeElement.textContent.trim() === ePerson.uuid);
+ })).not.toBeTruthy();
+ });
+ });
+ });
+
+ describe('when a group is selected', () => {
+ beforeEach(() => {
+ component.ngOnChanges({
+ groupId: new SimpleChange(undefined, GroupMock.id, true)
+ });
+ fixture.detectChanges();
+ });
+
+ it('should show all ePerson members of group', () => {
+ const ePersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child'));
+ expect(ePersonIdsFound.length).toEqual(1);
+ epersonMembers.map((ePerson: EPerson) => {
+ expect(ePersonIdsFound.find((foundEl: DebugElement) => {
+ return (foundEl.nativeElement.textContent.trim() === ePerson.uuid);
+ })).toBeTruthy();
+ });
+ });
+ });
+
+
+ it('should replace the value when a new member is added when multipleReviewers is false', () => {
+ spyOn(component.selectedReviewersUpdated, 'emit');
+ component.multipleReviewers = false;
+ component.selectedReviewers = [ePersonDtoModel1];
+
+ component.addMemberToGroup(ePersonDtoModel2);
+
+ expect(component.selectedReviewers).toEqual([ePersonDtoModel2]);
+ expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([ePersonDtoModel2.eperson]);
+ });
+
+ it('should add the value when a new member is added when multipleReviewers is true', () => {
+ spyOn(component.selectedReviewersUpdated, 'emit');
+ component.multipleReviewers = true;
+ component.selectedReviewers = [ePersonDtoModel1];
+
+ component.addMemberToGroup(ePersonDtoModel2);
+
+ expect(component.selectedReviewers).toEqual([ePersonDtoModel1, ePersonDtoModel2]);
+ expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([ePersonDtoModel1.eperson, ePersonDtoModel2.eperson]);
+ });
+
+ it('should delete the member when present', () => {
+ spyOn(component.selectedReviewersUpdated, 'emit');
+ ePersonDtoModel1.memberOfGroup = true;
+ component.selectedReviewers = [ePersonDtoModel1];
+
+ component.deleteMemberFromGroup(ePersonDtoModel1);
+
+ expect(component.selectedReviewers).toEqual([]);
+ expect(ePersonDtoModel1.memberOfGroup).toBeFalse();
+ expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([]);
+ });
+
+});
diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts
new file mode 100644
index 0000000000..7112a30543
--- /dev/null
+++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts
@@ -0,0 +1,149 @@
+import { Component, OnDestroy, OnInit, Input, OnChanges, SimpleChanges, EventEmitter, Output } from '@angular/core';
+import { FormBuilder } from '@angular/forms';
+import { Router } from '@angular/router';
+import { TranslateService } from '@ngx-translate/core';
+import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
+import { GroupDataService } from '../../../../core/eperson/group-data.service';
+import { NotificationsService } from '../../../../shared/notifications/notifications.service';
+import { PaginationService } from '../../../../core/pagination/pagination.service';
+import { Group } from '../../../../core/eperson/models/group.model';
+import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators';
+import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model';
+import { EPerson } from '../../../../core/eperson/models/eperson.model';
+import { Observable, of as observableOf } from 'rxjs';
+import { hasValue } from '../../../../shared/empty.util';
+import { PaginatedList } from '../../../../core/data/paginated-list.model';
+import {
+ MembersListComponent,
+ EPersonListActionConfig,
+} from '../../../../access-control/group-registry/group-form/members-list/members-list.component';
+
+/**
+ * Keys to keep track of specific subscriptions
+ */
+enum SubKey {
+ ActiveGroup,
+ MembersDTO,
+ SearchResultsDTO,
+}
+
+/**
+ * A custom {@link MembersListComponent} for the advanced SelectReviewer workflow.
+ */
+@Component({
+ selector: 'ds-reviewers-list',
+ // templateUrl: './reviewers-list.component.html',
+ templateUrl: '../../../../access-control/group-registry/group-form/members-list/members-list.component.html',
+})
+export class ReviewersListComponent extends MembersListComponent implements OnInit, OnChanges, OnDestroy {
+
+ @Input()
+ groupId: string | null;
+
+ @Input()
+ actionConfig: EPersonListActionConfig;
+
+ @Input()
+ multipleReviewers: boolean;
+
+ @Output()
+ selectedReviewersUpdated: EventEmitter = new EventEmitter();
+
+ selectedReviewers: EpersonDtoModel[] = [];
+
+ constructor(protected groupService: GroupDataService,
+ public ePersonDataService: EPersonDataService,
+ translateService: TranslateService,
+ notificationsService: NotificationsService,
+ formBuilder: FormBuilder,
+ paginationService: PaginationService,
+ router: Router) {
+ super(groupService, ePersonDataService, translateService, notificationsService, formBuilder, paginationService, router);
+ }
+
+ ngOnInit() {
+ this.searchForm = this.formBuilder.group(({
+ scope: 'metadata',
+ query: '',
+ }));
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ this.groupId = changes.groupId.currentValue;
+ if (changes.groupId.currentValue !== changes.groupId.previousValue) {
+ if (this.groupId === null) {
+ this.retrieveMembers(this.config.currentPage);
+ } else {
+ this.subs.set(SubKey.ActiveGroup, this.groupService.findById(this.groupId).pipe(
+ getFirstSucceededRemoteDataPayload(),
+ ).subscribe((activeGroup: Group) => {
+ if (activeGroup != null) {
+ this.groupDataService.editGroup(activeGroup);
+ this.groupBeingEdited = activeGroup;
+ this.retrieveMembers(this.config.currentPage);
+ }
+ }));
+ }
+ }
+ }
+
+ /**
+ * Sets the list of currently selected members, when no group is defined the list of {@link selectedReviewers}
+ * will be set.
+ *
+ * @param page The number of the page to retrieve
+ */
+ retrieveMembers(page: number): void {
+ this.config.currentPage = page;
+ if (this.groupId === null) {
+ this.unsubFrom(SubKey.MembersDTO);
+ const paginatedListOfDTOs: PaginatedList = new PaginatedList();
+ paginatedListOfDTOs.page = this.selectedReviewers;
+ this.ePeopleMembersOfGroupDtos.next(paginatedListOfDTOs);
+ } else {
+ super.retrieveMembers(page);
+ }
+ }
+
+ /**
+ * Checks whether the given {@link possibleMember} is part of the {@link selectedReviewers}.
+ *
+ * @param possibleMember The {@link EPerson} that needs to be checked
+ */
+ isMemberOfGroup(possibleMember: EPerson): Observable {
+ return observableOf(hasValue(this.selectedReviewers.find((reviewer: EpersonDtoModel) => reviewer.eperson.id === possibleMember.id)));
+ }
+
+ /**
+ * Removes the {@link ePerson} from the {@link selectedReviewers}
+ *
+ * @param ePerson The {@link EpersonDtoModel} containg the {@link EPerson} to remove
+ */
+ deleteMemberFromGroup(ePerson: EpersonDtoModel) {
+ ePerson.memberOfGroup = false;
+ const index = this.selectedReviewers.indexOf(ePerson);
+ if (index !== -1) {
+ this.selectedReviewers.splice(index, 1);
+ }
+ this.selectedReviewersUpdated.emit(this.selectedReviewers.map((ePersonDtoModel: EpersonDtoModel) => ePersonDtoModel.eperson));
+ }
+
+ /**
+ * Adds the {@link ePerson} to the {@link selectedReviewers} (or replaces it when {@link multipleReviewers} is
+ * `false`). Afterwards it will emit the list.
+ *
+ * @param ePerson The {@link EPerson} to add to the list
+ */
+ addMemberToGroup(ePerson: EpersonDtoModel) {
+ ePerson.memberOfGroup = true;
+ if (!this.multipleReviewers) {
+ for (const selectedReviewer of this.selectedReviewers) {
+ selectedReviewer.memberOfGroup = false;
+ }
+ this.selectedReviewers = [];
+ }
+ this.selectedReviewers.push(ePerson);
+ this.selectedReviewersUpdated.emit(this.selectedReviewers.map((epersonDtoModel: EpersonDtoModel) => epersonDtoModel.eperson));
+ }
+
+}
diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts
new file mode 100644
index 0000000000..f1beb86b98
--- /dev/null
+++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts
@@ -0,0 +1,134 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { Location } from '@angular/common';
+import { AdvancedWorkflowActionComponent } from './advanced-workflow-action.component';
+import { Component } from '@angular/core';
+import { MockComponent } from 'ng-mocks';
+import { DSOSelectorComponent } from '../../../shared/dso-selector/dso-selector/dso-selector.component';
+import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service';
+import { ClaimedTaskDataServiceStub } from '../../../shared/testing/claimed-task-data-service.stub';
+import { ActivatedRoute } from '@angular/router';
+import { of as observableOf } from 'rxjs';
+import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service';
+import { RouterTestingModule } from '@angular/router/testing';
+import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
+import { WorkflowActionDataService } from '../../../core/data/workflow-action-data.service';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { RouteService } from '../../../core/services/route.service';
+import { routeServiceStub } from '../../../shared/testing/route-service.stub';
+import { TranslateModule } from '@ngx-translate/core';
+import { WorkflowActionDataServiceStub } from '../../../shared/testing/workflow-action-data-service.stub';
+import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response';
+import { WorkflowItemDataServiceStub } from '../../../shared/testing/workflow-item-data-service.stub';
+import { RequestService } from '../../../core/data/request.service';
+import { RequestServiceStub } from '../../../shared/testing/request-service.stub';
+import { LocationStub } from '../../../shared/testing/location.stub';
+
+const workflowId = '1';
+
+describe('AdvancedWorkflowActionComponent', () => {
+ let component: AdvancedWorkflowActionComponent;
+ let fixture: ComponentFixture;
+
+ let claimedTaskDataService: ClaimedTaskDataServiceStub;
+ let location: LocationStub;
+ let notificationService: NotificationsServiceStub;
+ let workflowActionDataService: WorkflowActionDataServiceStub;
+ let workflowItemDataService: WorkflowItemDataServiceStub;
+
+ beforeEach(async () => {
+ claimedTaskDataService = new ClaimedTaskDataServiceStub();
+ location = new LocationStub();
+ notificationService = new NotificationsServiceStub();
+ workflowActionDataService = new WorkflowActionDataServiceStub();
+ workflowItemDataService = new WorkflowItemDataServiceStub();
+
+ await TestBed.configureTestingModule({
+ imports: [
+ TranslateModule.forRoot(),
+ RouterTestingModule,
+ ],
+ declarations: [
+ TestComponent,
+ MockComponent(DSOSelectorComponent),
+ ],
+ providers: [
+ {
+ provide: ActivatedRoute,
+ useValue: {
+ data: observableOf({
+ id: workflowId,
+ }),
+ snapshot: {
+ queryParams: {
+ workflow: 'testaction',
+ },
+ },
+ },
+ },
+ { provide: ClaimedTaskDataService, useValue: claimedTaskDataService },
+ { provide: Location, useValue: location },
+ { provide: NotificationsService, useValue: notificationService },
+ { provide: RouteService, useValue: routeServiceStub },
+ { provide: WorkflowActionDataService, useValue: workflowActionDataService },
+ { provide: WorkflowItemDataService, useValue: workflowItemDataService },
+ { provide: RequestService, useClass: RequestServiceStub },
+ ],
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(TestComponent);
+ component = fixture.componentInstance;
+ component.ngOnInit();
+ fixture.detectChanges();
+ });
+
+ describe('sendRequest', () => {
+ it('should return true if the request succeeded', () => {
+ spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true, 200)));
+ spyOn(workflowActionDataService, 'findById');
+
+ const result = component.sendRequest(workflowId);
+
+ expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith('1', {
+ 'submit_test': true,
+ });
+ result.subscribe((value: boolean) => {
+ expect(value).toBeTrue();
+ });
+ });
+
+ it('should return false if the request didn\'t succeeded', () => {
+ spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(false, 404)));
+ spyOn(workflowActionDataService, 'findById');
+
+ const result = component.sendRequest(workflowId);
+
+ expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith('1', {
+ 'submit_test': true,
+ });
+ result.subscribe((value: boolean) => {
+ expect(value).toBeFalse();
+ });
+ });
+ });
+});
+
+@Component({
+ // eslint-disable-next-line @angular-eslint/component-selector
+ selector: '',
+ template: ''
+})
+class TestComponent extends AdvancedWorkflowActionComponent {
+
+ createBody(): any {
+ return {
+ 'submit_test': true,
+ };
+ }
+
+ getType(): string {
+ return 'testaction';
+ }
+
+}
diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts
new file mode 100644
index 0000000000..73fd6dc63e
--- /dev/null
+++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts
@@ -0,0 +1,88 @@
+import { Component, OnInit } from '@angular/core';
+import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model';
+import { WorkflowActionDataService } from '../../../core/data/workflow-action-data.service';
+import { ActivatedRoute, Router } from '@angular/router';
+import { Observable } from 'rxjs';
+import { WorkflowItemActionPageComponent } from '../../workflow-item-action-page.component';
+import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service';
+import { RouteService } from '../../../core/services/route.service';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { TranslateService } from '@ngx-translate/core';
+import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators';
+import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service';
+import { map } from 'rxjs/operators';
+import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response';
+import { RequestService } from '../../../core/data/request.service';
+import { Location } from '@angular/common';
+
+/**
+ * Abstract component for rendering an advanced claimed task's workflow page
+ * To create a child-component for a new option:
+ * - Set the "{@link getType}()" of the component
+ * - Implement the {@link createBody}, should always contain at least the ADVANCED_WORKFLOW_TASK_OPTION_*
+ */
+@Component({
+ selector: 'ds-advanced-workflow-action',
+ template: '',
+})
+export abstract class AdvancedWorkflowActionComponent extends WorkflowItemActionPageComponent implements OnInit {
+
+ workflowAction$: Observable;
+
+ constructor(
+ protected route: ActivatedRoute,
+ protected workflowItemService: WorkflowItemDataService,
+ protected router: Router,
+ protected routeService: RouteService,
+ protected notificationsService: NotificationsService,
+ protected translationService: TranslateService,
+ protected workflowActionService: WorkflowActionDataService,
+ protected claimedTaskDataService: ClaimedTaskDataService,
+ protected requestService: RequestService,
+ protected location: Location,
+ ) {
+ super(route, workflowItemService, router, routeService, notificationsService, translationService, requestService, location);
+ }
+
+ ngOnInit(): void {
+ super.ngOnInit();
+ this.workflowAction$ = this.workflowActionService.findById(this.route.snapshot.queryParams.workflow).pipe(
+ getFirstSucceededRemoteDataPayload(),
+ );
+ }
+
+ /**
+ * Performs the action and shows a notification based on the outcome of the action
+ */
+ performAction(): void {
+ this.sendRequest(this.route.snapshot.queryParams.claimedTask).subscribe((successful: boolean) => {
+ if (successful) {
+ const title = this.translationService.get('workflow-item.' + this.type + '.notification.success.title');
+ const content = this.translationService.get('workflow-item.' + this.type + '.notification.success.content');
+ this.notificationsService.success(title, content);
+ this.previousPage();
+ } else {
+ const title = this.translationService.get('workflow-item.' + this.type + '.notification.error.title');
+ const content = this.translationService.get('workflow-item.' + this.type + '.notification.error.content');
+ this.notificationsService.error(title, content);
+ }
+ });
+ }
+
+ /**
+ * Submits the task with the given {@link createBody}.
+ *
+ * @param id The task id
+ */
+ sendRequest(id: string): Observable {
+ return this.claimedTaskDataService.submitTask(id, this.createBody()).pipe(
+ map((processTaskResponse: ProcessTaskResponse) => processTaskResponse.hasSucceeded),
+ );
+ }
+
+ /**
+ * The body that needs to be passed to the {@link ClaimedTaskDataService}.submitTask().
+ */
+ abstract createBody(): any;
+
+}
diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html
new file mode 100644
index 0000000000..0904d0fcde
--- /dev/null
+++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html
@@ -0,0 +1 @@
+
diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.scss b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts
new file mode 100644
index 0000000000..2c12b07589
--- /dev/null
+++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts
@@ -0,0 +1,83 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { AdvancedWorkflowActionsLoaderComponent } from './advanced-workflow-actions-loader.component';
+import { Router } from '@angular/router';
+import { RouterStub } from '../../../shared/testing/router.stub';
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { AdvancedWorkflowActionsDirective } from './advanced-workflow-actions.directive';
+import {
+ rendersAdvancedWorkflowTaskOption
+} from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator';
+import { By } from '@angular/platform-browser';
+import { PAGE_NOT_FOUND_PATH } from '../../../app-routing-paths';
+
+const ADVANCED_WORKFLOW_ACTION_TEST = 'testaction';
+
+describe('AdvancedWorkflowActionsLoaderComponent', () => {
+ let component: AdvancedWorkflowActionsLoaderComponent;
+ let fixture: ComponentFixture;
+
+ let router: RouterStub;
+
+ beforeEach(async () => {
+ router = new RouterStub();
+
+ await TestBed.configureTestingModule({
+ declarations: [
+ AdvancedWorkflowActionsDirective,
+ AdvancedWorkflowActionsLoaderComponent,
+ ],
+ providers: [
+ { provide: Router, useValue: router },
+ ],
+ }).overrideComponent(AdvancedWorkflowActionsLoaderComponent, {
+ set: {
+ changeDetection: ChangeDetectionStrategy.Default,
+ entryComponents: [AdvancedWorkflowActionTestComponent],
+ },
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(AdvancedWorkflowActionsLoaderComponent);
+ component = fixture.componentInstance;
+ component.type = ADVANCED_WORKFLOW_ACTION_TEST;
+ fixture.detectChanges();
+ });
+
+ afterEach(() => {
+ fixture.debugElement.nativeElement.remove();
+ });
+
+ describe('When the component is rendered', () => {
+ it('should display the AdvancedWorkflowActionTestComponent when the type has been defined in a rendersAdvancedWorkflowTaskOption', () => {
+ spyOn(component, 'getComponentByWorkflowTaskOption').and.returnValue(AdvancedWorkflowActionTestComponent);
+
+ component.ngOnInit();
+ fixture.detectChanges();
+
+ expect(component.getComponentByWorkflowTaskOption).toHaveBeenCalledWith(ADVANCED_WORKFLOW_ACTION_TEST);
+ expect(fixture.debugElement.query(By.css('#AdvancedWorkflowActionsLoaderComponent'))).not.toBeNull();
+ expect(router.navigate).not.toHaveBeenCalled();
+ });
+
+ it('should redirect to page not found when the type has not been defined in a rendersAdvancedWorkflowTaskOption', () => {
+ spyOn(component, 'getComponentByWorkflowTaskOption').and.returnValue(undefined);
+ component.type = 'nonexistingaction';
+
+ component.ngOnInit();
+ fixture.detectChanges();
+
+ expect(component.getComponentByWorkflowTaskOption).toHaveBeenCalledWith('nonexistingaction');
+ expect(router.navigate).toHaveBeenCalledWith([PAGE_NOT_FOUND_PATH]);
+ });
+ });
+});
+
+@rendersAdvancedWorkflowTaskOption(ADVANCED_WORKFLOW_ACTION_TEST)
+@Component({
+ // eslint-disable-next-line @angular-eslint/component-selector
+ selector: '',
+ template: '',
+})
+class AdvancedWorkflowActionTestComponent {
+}
diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts
new file mode 100644
index 0000000000..32f14c015d
--- /dev/null
+++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts
@@ -0,0 +1,57 @@
+import { Component, Input, ViewChild, ComponentFactoryResolver, OnInit } from '@angular/core';
+import { hasValue } from '../../../shared/empty.util';
+import {
+ getAdvancedComponentByWorkflowTaskOption
+} from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator';
+import { AdvancedWorkflowActionsDirective } from './advanced-workflow-actions.directive';
+import { Router } from '@angular/router';
+import { PAGE_NOT_FOUND_PATH } from '../../../app-routing-paths';
+
+/**
+ * Component for loading a {@link AdvancedWorkflowActionComponent} depending on the "{@link type}" input
+ */
+@Component({
+ selector: 'ds-advanced-workflow-actions-loader',
+ templateUrl: './advanced-workflow-actions-loader.component.html',
+ styleUrls: ['./advanced-workflow-actions-loader.component.scss'],
+})
+export class AdvancedWorkflowActionsLoaderComponent implements OnInit {
+
+ /**
+ * The name of the type to render
+ * Passed on to the decorator to fetch the relevant component for this option
+ */
+ @Input() type: string;
+
+ /**
+ * Directive to determine where the dynamic child component is located
+ */
+ @ViewChild(AdvancedWorkflowActionsDirective, { static: true }) claimedTaskActionsDirective: AdvancedWorkflowActionsDirective;
+
+ constructor(
+ private componentFactoryResolver: ComponentFactoryResolver,
+ private router: Router,
+ ) {
+ }
+
+ /**
+ * Fetch, create and initialize the relevant component
+ */
+ ngOnInit(): void {
+ const comp = this.getComponentByWorkflowTaskOption(this.type);
+ if (hasValue(comp)) {
+ const componentFactory = this.componentFactoryResolver.resolveComponentFactory(comp);
+
+ const viewContainerRef = this.claimedTaskActionsDirective.viewContainerRef;
+ viewContainerRef.clear();
+ viewContainerRef.createComponent(componentFactory);
+ } else {
+ void this.router.navigate([PAGE_NOT_FOUND_PATH]);
+ }
+ }
+
+ getComponentByWorkflowTaskOption(type: string): any {
+ return getAdvancedComponentByWorkflowTaskOption(type);
+ }
+
+}
diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive.ts
new file mode 100644
index 0000000000..e569f6cc6f
--- /dev/null
+++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive.ts
@@ -0,0 +1,16 @@
+import { Directive, ViewContainerRef } from '@angular/core';
+
+@Directive({
+ selector: '[dsAdvancedWorkflowActions]',
+})
+/**
+ * Directive used as a hook to know where to inject the dynamic Advanced Claimed Task Actions component
+ */
+export class AdvancedWorkflowActionsDirective {
+
+ constructor(
+ public viewContainerRef: ViewContainerRef,
+ ) {
+ }
+
+}
diff --git a/src/app/workflowitems-edit-page/workflow-item-action-page.component.spec.ts b/src/app/workflowitems-edit-page/workflow-item-action-page.component.spec.ts
index c7b9858836..c4dea0f30c 100644
--- a/src/app/workflowitems-edit-page/workflow-item-action-page.component.spec.ts
+++ b/src/app/workflowitems-edit-page/workflow-item-action-page.component.spec.ts
@@ -16,6 +16,10 @@ import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock';
import { ActivatedRouteStub } from '../shared/testing/active-router.stub';
import { RouterStub } from '../shared/testing/router.stub';
import { NotificationsServiceStub } from '../shared/testing/notifications-service.stub';
+import { RequestService } from '../core/data/request.service';
+import { RequestServiceStub } from '../shared/testing/request-service.stub';
+import { Location } from '@angular/common';
+import { LocationStub } from '../shared/testing/location.stub';
const type = 'testType';
describe('WorkflowItemActionPageComponent', () => {
@@ -50,8 +54,10 @@ describe('WorkflowItemActionPageComponent', () => {
{ provide: ActivatedRoute, useValue: new ActivatedRouteStub({}, { wfi: createSuccessfulRemoteDataObject(wfi) }) },
{ provide: Router, useClass: RouterStub },
{ provide: RouteService, useValue: {} },
+ { provide: Location, useValue: new LocationStub() },
{ provide: NotificationsService, useClass: NotificationsServiceStub },
{ provide: WorkflowItemDataService, useValue: wfiService },
+ { provide: RequestService, useClass: RequestServiceStub },
],
schemas: [NO_ERRORS_SCHEMA]
})
@@ -110,8 +116,11 @@ class TestComponent extends WorkflowItemActionPageComponent {
protected router: Router,
protected routeService: RouteService,
protected notificationsService: NotificationsService,
- protected translationService: TranslateService) {
- super(route, workflowItemService, router, routeService, notificationsService, translationService);
+ protected translationService: TranslateService,
+ protected requestService: RequestService,
+ protected location: Location,
+ ) {
+ super(route, workflowItemService, router, routeService, notificationsService, translationService, requestService, location);
}
getType(): string {
diff --git a/src/app/workflowitems-edit-page/workflow-item-action-page.component.ts b/src/app/workflowitems-edit-page/workflow-item-action-page.component.ts
index 7f09f3a3d2..b8998a6dd7 100644
--- a/src/app/workflowitems-edit-page/workflow-item-action-page.component.ts
+++ b/src/app/workflowitems-edit-page/workflow-item-action-page.component.ts
@@ -1,16 +1,18 @@
import { Component, OnInit } from '@angular/core';
-import { Observable } from 'rxjs';
+import { Location } from '@angular/common';
+import { Observable, forkJoin } from 'rxjs';
import { map, switchMap, take } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { WorkflowItem } from '../core/submission/models/workflowitem.model';
import { Item } from '../core/shared/item.model';
-import { ActivatedRoute, Data, Router } from '@angular/router';
+import { ActivatedRoute, Data, Router, Params } from '@angular/router';
import { WorkflowItemDataService } from '../core/submission/workflowitem-data.service';
import { RouteService } from '../core/services/route.service';
import { NotificationsService } from '../shared/notifications/notifications.service';
import { RemoteData } from '../core/data/remote-data';
import { getAllSucceededRemoteData, getRemoteDataPayload } from '../core/shared/operators';
import { isEmpty } from '../shared/empty.util';
+import { RequestService } from '../core/data/request.service';
/**
* Abstract component representing a page to perform an action on a workflow item
@@ -23,13 +25,17 @@ export abstract class WorkflowItemActionPageComponent implements OnInit {
public type;
public wfi$: Observable;
public item$: Observable;
+ protected previousQueryParameters?: Params;
constructor(protected route: ActivatedRoute,
protected workflowItemService: WorkflowItemDataService,
protected router: Router,
protected routeService: RouteService,
protected notificationsService: NotificationsService,
- protected translationService: TranslateService) {
+ protected translationService: TranslateService,
+ protected requestService: RequestService,
+ protected location: Location,
+ ) {
}
/**
@@ -39,15 +45,16 @@ export abstract class WorkflowItemActionPageComponent implements OnInit {
this.type = this.getType();
this.wfi$ = this.route.data.pipe(map((data: Data) => data.wfi as RemoteData), getRemoteDataPayload());
this.item$ = this.wfi$.pipe(switchMap((wfi: WorkflowItem) => (wfi.item as Observable>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload())));
+ this.previousQueryParameters = (this.location.getState() as { [key: string]: any }).previousQueryParams;
}
/**
* Performs the action and shows a notification based on the outcome of the action
*/
performAction() {
- this.wfi$.pipe(
+ forkJoin([this.wfi$, this.requestService.removeByHrefSubstring('/discover')]).pipe(
take(1),
- switchMap((wfi: WorkflowItem) => this.sendRequest(wfi.id))
+ switchMap(([wfi]) => this.sendRequest(wfi.id))
).subscribe((successful: boolean) => {
if (successful) {
const title = this.translationService.get('workflow-item.' + this.type + '.notification.success.title');
@@ -69,10 +76,17 @@ export abstract class WorkflowItemActionPageComponent implements OnInit {
previousPage() {
this.routeService.getPreviousUrl().pipe(take(1))
.subscribe((url) => {
+ let params: Params = {};
if (isEmpty(url)) {
url = '/mydspace';
+ params = this.previousQueryParameters;
}
- this.router.navigateByUrl(url);
+ if (url.split('?').length > 1) {
+ for (const param of url.split('?')[1].split('&')) {
+ params[param.split('=')[0]] = decodeURIComponent(param.split('=')[1]);
+ }
+ }
+ void this.router.navigate([url.split('?')[0]], { queryParams: params });
}
);
}
diff --git a/src/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component.spec.ts b/src/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component.spec.ts
index a4e8f7d849..89a7029b4a 100644
--- a/src/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component.spec.ts
+++ b/src/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component.spec.ts
@@ -1,5 +1,5 @@
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
-
+import { Location } from '@angular/common';
import { WorkflowItemDeleteComponent } from './workflow-item-delete.component';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { ActivatedRoute, Router } from '@angular/router';
@@ -17,6 +17,7 @@ import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
import { RouterStub } from '../../shared/testing/router.stub';
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
+import { LocationStub } from '../../shared/testing/location.stub';
describe('WorkflowItemDeleteComponent', () => {
let component: WorkflowItemDeleteComponent;
@@ -50,6 +51,7 @@ describe('WorkflowItemDeleteComponent', () => {
{ provide: ActivatedRoute, useValue: new ActivatedRouteStub({}, { wfi: createSuccessfulRemoteDataObject(wfi) }) },
{ provide: Router, useClass: RouterStub },
{ provide: RouteService, useValue: {} },
+ { provide: Location, useValue: new LocationStub() },
{ provide: NotificationsService, useClass: NotificationsServiceStub },
{ provide: WorkflowItemDataService, useValue: wfiService },
{ provide: RequestService, useValue: getMockRequestService() },
diff --git a/src/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component.ts b/src/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component.ts
index 451469b5e4..011398369d 100644
--- a/src/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component.ts
+++ b/src/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component.ts
@@ -11,6 +11,7 @@ import { map } from 'rxjs/operators';
import { RemoteData } from '../../core/data/remote-data';
import { NoContent } from '../../core/shared/NoContent.model';
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
+import { Location } from '@angular/common';
@Component({
selector: 'ds-workflow-item-delete',
@@ -26,8 +27,10 @@ export class WorkflowItemDeleteComponent extends WorkflowItemActionPageComponent
protected routeService: RouteService,
protected notificationsService: NotificationsService,
protected translationService: TranslateService,
- protected requestService: RequestService) {
- super(route, workflowItemService, router, routeService, notificationsService, translationService);
+ protected requestService: RequestService,
+ protected location: Location,
+ ) {
+ super(route, workflowItemService, router, routeService, notificationsService, translationService, requestService, location);
}
/**
@@ -42,7 +45,6 @@ export class WorkflowItemDeleteComponent extends WorkflowItemActionPageComponent
* @param id The id of the WorkflowItem
*/
sendRequest(id: string): Observable {
- this.requestService.removeByHrefSubstring('/discover');
return this.workflowItemService.delete(id).pipe(
getFirstCompletedRemoteData(),
map((response: RemoteData) => response.hasSucceeded)
diff --git a/src/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.spec.ts b/src/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.spec.ts
index d9dde8244c..1196e05593 100644
--- a/src/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.spec.ts
+++ b/src/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.spec.ts
@@ -1,5 +1,5 @@
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
-
+import { Location } from '@angular/common';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { ActivatedRoute, Router } from '@angular/router';
import { RouteService } from '../../core/services/route.service';
@@ -17,6 +17,7 @@ import { RouterStub } from '../../shared/testing/router.stub';
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
+import { LocationStub } from '../../shared/testing/location.stub';
describe('WorkflowItemSendBackComponent', () => {
let component: WorkflowItemSendBackComponent;
@@ -50,6 +51,7 @@ describe('WorkflowItemSendBackComponent', () => {
{ provide: ActivatedRoute, useValue: new ActivatedRouteStub({}, { wfi: createSuccessfulRemoteDataObject(wfi) }) },
{ provide: Router, useClass: RouterStub },
{ provide: RouteService, useValue: {} },
+ { provide: Location, useValue: new LocationStub() },
{ provide: NotificationsService, useClass: NotificationsServiceStub },
{ provide: WorkflowItemDataService, useValue: wfiService },
{ provide: RequestService, useValue: getMockRequestService() },
diff --git a/src/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.ts b/src/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.ts
index 002e5dcc9a..a3c03bcfb1 100644
--- a/src/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.ts
+++ b/src/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.ts
@@ -7,6 +7,7 @@ import { RouteService } from '../../core/services/route.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { RequestService } from '../../core/data/request.service';
+import { Location } from '@angular/common';
@Component({
selector: 'ds-workflow-item-send-back',
@@ -22,8 +23,10 @@ export class WorkflowItemSendBackComponent extends WorkflowItemActionPageCompone
protected routeService: RouteService,
protected notificationsService: NotificationsService,
protected translationService: TranslateService,
- protected requestService: RequestService) {
- super(route, workflowItemService, router, routeService, notificationsService, translationService);
+ protected requestService: RequestService,
+ protected location: Location,
+ ) {
+ super(route, workflowItemService, router, routeService, notificationsService, translationService, requestService, location);
}
/**
@@ -38,7 +41,6 @@ export class WorkflowItemSendBackComponent extends WorkflowItemActionPageCompone
* @param id The id of the WorkflowItem
*/
sendRequest(id: string): Observable {
- this.requestService.removeByHrefSubstring('/discover');
return this.workflowItemService.sendBack(id);
}
}
diff --git a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing-paths.ts b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing-paths.ts
index e2d969a872..ece61f0321 100644
--- a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing-paths.ts
+++ b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing-paths.ts
@@ -20,7 +20,12 @@ export function getWorkflowItemSendBackRoute(wfiId: string) {
return new URLCombiner(getWorkflowItemModuleRoute(), wfiId, WORKFLOW_ITEM_SEND_BACK_PATH).toString();
}
+export function getAdvancedWorkflowRoute(wfiId: string) {
+ return new URLCombiner(getWorkflowItemModuleRoute(), wfiId, ADVANCED_WORKFLOW_PATH).toString();
+}
+
export const WORKFLOW_ITEM_EDIT_PATH = 'edit';
export const WORKFLOW_ITEM_DELETE_PATH = 'delete';
export const WORKFLOW_ITEM_VIEW_PATH = 'view';
export const WORKFLOW_ITEM_SEND_BACK_PATH = 'sendback';
+export const ADVANCED_WORKFLOW_PATH = 'advanced';
diff --git a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts
index 9c24bacb98..06536d5816 100644
--- a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts
+++ b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts
@@ -7,7 +7,8 @@ import {
WORKFLOW_ITEM_DELETE_PATH,
WORKFLOW_ITEM_EDIT_PATH,
WORKFLOW_ITEM_SEND_BACK_PATH,
- WORKFLOW_ITEM_VIEW_PATH
+ WORKFLOW_ITEM_VIEW_PATH,
+ ADVANCED_WORKFLOW_PATH,
} from './workflowitems-edit-page-routing-paths';
import { ThemedSubmissionEditComponent } from '../submission/edit/themed-submission-edit.component';
import { ThemedWorkflowItemDeleteComponent } from './workflow-item-delete/themed-workflow-item-delete.component';
@@ -15,6 +16,9 @@ import { ThemedWorkflowItemSendBackComponent } from './workflow-item-send-back/t
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
import { ItemFromWorkflowResolver } from './item-from-workflow.resolver';
import { ThemedFullItemPageComponent } from '../item-page/full/themed-full-item-page.component';
+import {
+ AdvancedWorkflowActionPageComponent
+} from './advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component';
@NgModule({
imports: [
@@ -59,7 +63,16 @@ import { ThemedFullItemPageComponent } from '../item-page/full/themed-full-item-
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'workflow-item.send-back.title', breadcrumbKey: 'workflow-item.edit' }
- }
+ },
+ {
+ canActivate: [AuthenticatedGuard],
+ path: ADVANCED_WORKFLOW_PATH,
+ component: AdvancedWorkflowActionPageComponent,
+ resolve: {
+ breadcrumb: I18nBreadcrumbResolver
+ },
+ data: { title: 'workflow-item.advanced.title', breadcrumbKey: 'workflow-item.edit' }
+ },
]
}]
)
diff --git a/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts b/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts
index e99a3ca958..cf998c5274 100644
--- a/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts
+++ b/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts
@@ -6,9 +6,32 @@ import { SubmissionModule } from '../submission/submission.module';
import { WorkflowItemDeleteComponent } from './workflow-item-delete/workflow-item-delete.component';
import { WorkflowItemSendBackComponent } from './workflow-item-send-back/workflow-item-send-back.component';
import { ThemedWorkflowItemDeleteComponent } from './workflow-item-delete/themed-workflow-item-delete.component';
-import { ThemedWorkflowItemSendBackComponent } from './workflow-item-send-back/themed-workflow-item-send-back.component';
+import {
+ ThemedWorkflowItemSendBackComponent
+} from './workflow-item-send-back/themed-workflow-item-send-back.component';
import { StatisticsModule } from '../statistics/statistics.module';
import { ItemPageModule } from '../item-page/item-page.module';
+import {
+ AdvancedWorkflowActionsLoaderComponent
+} from './advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component';
+import {
+ AdvancedWorkflowActionRatingComponent
+} from './advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component';
+import {
+ AdvancedWorkflowActionSelectReviewerComponent
+} from './advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component';
+import {
+ AdvancedWorkflowActionPageComponent
+} from './advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component';
+import {
+ AdvancedWorkflowActionsDirective
+} from './advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive';
+import { AccessControlModule } from '../access-control/access-control.module';
+import {
+ ReviewersListComponent
+} from './advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component';
+import { FormModule } from '../shared/form/form.module';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
@NgModule({
imports: [
@@ -17,13 +40,22 @@ import { ItemPageModule } from '../item-page/item-page.module';
SharedModule,
SubmissionModule,
StatisticsModule,
- ItemPageModule
+ ItemPageModule,
+ AccessControlModule,
+ FormModule,
+ NgbModule,
],
declarations: [
WorkflowItemDeleteComponent,
ThemedWorkflowItemDeleteComponent,
WorkflowItemSendBackComponent,
- ThemedWorkflowItemSendBackComponent
+ ThemedWorkflowItemSendBackComponent,
+ AdvancedWorkflowActionsLoaderComponent,
+ AdvancedWorkflowActionRatingComponent,
+ AdvancedWorkflowActionSelectReviewerComponent,
+ AdvancedWorkflowActionPageComponent,
+ AdvancedWorkflowActionsDirective,
+ ReviewersListComponent,
]
})
/**
diff --git a/src/assets/i18n/ar.json5 b/src/assets/i18n/ar.json5
index 24e6e14603..1edbdf7981 100644
--- a/src/assets/i18n/ar.json5
+++ b/src/assets/i18n/ar.json5
@@ -6459,9 +6459,9 @@
// TODO New key - Add a translation
"submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
// TODO New key - Add a translation
- "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
// "submission.sections.upload.no-entry": "No",
// TODO New key - Add a translation
diff --git a/src/assets/i18n/bn.json5 b/src/assets/i18n/bn.json5
index 55ec1820dd..8b903c4a65 100644
--- a/src/assets/i18n/bn.json5
+++ b/src/assets/i18n/bn.json5
@@ -5803,7 +5803,7 @@
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
"submission.sections.upload.header.policy.default.withlist": "অনুগ্রহ করে মনে রাখবেন যে {{collectionName}} সংগ্রহে আপলোড করা ফাইলগুলি অ্যাক্সেসযোগ্য হবে, একক ফাইলের জন্য স্পষ্টভাবে যা নির্ধারণ করা হয়েছে তা ছাড়াও, নিম্নলিখিত গ্রুপ(গুলি) সহ:",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
"submission.sections.upload.info": "এখানে আপনি বর্তমানে আইটেমটিতে সমস্ত ফাইল পাবেন। আপনি ফাইল মেটাডেটা এবং অ্যাক্সেস শর্তাদি আপডেট করতে পারেন অথবা অতিরিক্ত ফাইল আপলোড করুন - পৃষ্ঠাটিতে সর্বত্র ড্র্যাগ করুন এবং ড্রপ করুন ",
// "submission.sections.upload.no-entry": "No",
diff --git a/src/assets/i18n/ca.json5 b/src/assets/i18n/ca.json5
index 24698baac0..246a92061c 100644
--- a/src/assets/i18n/ca.json5
+++ b/src/assets/i18n/ca.json5
@@ -6353,7 +6353,7 @@
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
"submission.sections.upload.header.policy.default.withlist": "Tingueu en compte que els fitxers carregats a la col·lecció {{ collectionName }} seran accessibles, a més del que es decideixi explícitament per al fitxer, per als grups següents:",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
"submission.sections.upload.info": "Aquí trobareu tots els fitxers que es troben actualment a l'ítem. Podeu actualitzar les metadades del fitxer i les condicions d'accés o incloure fitxers addicionals simplement arrossegant i deixant anar a qualsevol lloc de la pàgina",
// "submission.sections.upload.no-entry": "No",
diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5
index a04e89b2c3..5ed1474384 100644
--- a/src/assets/i18n/cs.json5
+++ b/src/assets/i18n/cs.json5
@@ -6327,9 +6327,9 @@
// TODO New key - Add a translation
"submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
// TODO New key - Add a translation
- "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
// "submission.sections.upload.no-entry": "No",
// TODO New key - Add a translation
diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5
index c1979be6b6..df4c2cb4ee 100644
--- a/src/assets/i18n/de.json5
+++ b/src/assets/i18n/de.json5
@@ -1982,8 +1982,8 @@
// "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.",
- "forgot-password.form.info": "Bitte geben Sie ein neues Passwort (mindestens sechs Zeichen) ein und bestätigen Sie es erneut.",
+ // "forgot-password.form.info": "Enter a new password in the box below, and confirm it by typing it again into the second box.",
+ "forgot-password.form.info": "Bitte geben Sie ein neues Passwort ein und bestätigen Sie es erneut.",
// "forgot-password.form.card.security": "Security",
"forgot-password.form.card.security": "Sicherheit",
@@ -3838,17 +3838,17 @@
// "profile.security.form.error.matching-passwords": "The passwords do not match.",
"profile.security.form.error.matching-passwords": "Die Passwörter stimmen nicht überein.",
- // "profile.security.form.error.password-length": "The password should be at least 6 characters long.",
- "profile.security.form.error.password-length": "Das Passwort sollte mindestens 6 Zeichen lang sein.",
-
- // "profile.security.form.info": "Optionally, you can 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.",
- "profile.security.form.info": "Optional können Sie ein neues Passwort in das Feld darunter eingeben und es durch erneute Eingabe in das zweite Feld bestätigen. Es sollte mindestens sechs Zeichen lang sein.",
+ // "profile.security.form.info": "Optionally, you can enter a new password in the box below, and confirm it by typing it again into the second box.",
+ "profile.security.form.info": "Im nachfolgenden Feld können Sie bei Bedarf ein neues Passwort eingeben und es durch erneute Eingabe in das zweite Feld bestätigen.",
// "profile.security.form.label.password": "Password",
"profile.security.form.label.password": "Passwort",
// "profile.security.form.label.passwordrepeat": "Retype to confirm",
- "profile.security.form.label.passwordrepeat": "Zum Bestätigen erneut eingeben",
+ "profile.security.form.label.passwordrepeat": "Zum Bestätigen das Passwort erneut eingeben",
+
+ // "profile.security.form.label.current-password": "Current password",
+ "profile.security.form.label.current-password": "Ihr aktuelles Passwort",
// "profile.security.form.notifications.success.content": "Your changes to the password were saved.",
"profile.security.form.notifications.success.content": "Ihr geändertes Passwort wurde gespeichert.",
@@ -3862,8 +3862,14 @@
// "profile.security.form.notifications.error.not-long-enough": "The password has to be at least 6 characters long.",
"profile.security.form.notifications.error.not-long-enough": "Das Passwort sollte mindestens 6 Zeichen lang sein.",
+ // "profile.security.form.notifications.error.change-failed": "An error occurred while trying to change the password. Please check if the current password is correct.",
+ "profile.security.form.notifications.error.change-failed": "Es ist ein Fehler beim Ändern des Passworts aufgetreten. Bitte überprüfen Sie, ob Ihr aktuelles Passwort korrekt eingegeben wurde.",
+
// "profile.security.form.notifications.error.not-same": "The provided passwords are not the same.",
"profile.security.form.notifications.error.not-same": "Die angegebenen Passwörter sind nicht identisch.",
+
+ // "profile.security.form.notifications.error.general": "Please fill required fields of security form.",
+ "profile.security.form.notifications.error.general": "Bitte füllen Sie alle Pflichtfelder im Formular aus.",
// "profile.title": "Update Profile",
"profile.title": "Profil aktualisieren",
@@ -3969,8 +3975,8 @@
// "register-page.create-profile.security.header": "Security",
"register-page.create-profile.security.header": "Sicherheit",
- // "register-page.create-profile.security.info": "Please enter a password in the box below, and confirm it by typing it again into the second box. It should be at least six characters long.",
- "register-page.create-profile.security.info": "Bitte geben Sie ein Passwort in das unten stehende Feld ein und bestätigen Sie es, indem Sie es in das zweite Feld erneut eingeben. Es sollte mindestens sechs Zeichen lang sein.",
+ // "register-page.create-profile.security.info": "Please enter a password in the box below, and confirm it by typing it again into the second box.",
+ "register-page.create-profile.security.info": "Bitte geben Sie ein Passwort in das unten stehende Feld ein und bestätigen Sie es, indem Sie es in das zweite Feld erneut eingeben.",
// "register-page.create-profile.security.label.password": "Password *",
"register-page.create-profile.security.label.password": "Passwort *",
@@ -5203,7 +5209,7 @@
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
"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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
"submission.sections.upload.info": "Hier finden Sie alle Dateien, die aktuell zum Item gehören. Sie können ihre Metadaten und Zugriffsrechte bearbeiten oder weitere Dateien per Drag & Drop hinzufügen.",
// "submission.sections.upload.no-entry": "No",
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5
index 99949c4378..fc4c6aa74d 100644
--- a/src/assets/i18n/en.json5
+++ b/src/assets/i18n/en.json5
@@ -540,10 +540,16 @@
"admin.workflow.item.workflow": "Workflow",
+ "admin.workflow.item.workspace": "Workspace",
+
"admin.workflow.item.delete": "Delete",
"admin.workflow.item.send-back": "Send back",
+ "admin.workflow.item.policies": "Policies",
+
+ "admin.workflow.item.supervision": "Supervision",
+
"admin.metadata-import.breadcrumbs": "Import Metadata",
@@ -584,6 +590,70 @@
"admin.metadata-import.page.validateOnly.hint": "When selected, the uploaded CSV will be validated. You will receive a report of detected changes, but no changes will be saved.",
+ "advanced-workflow-action.rating.form.rating.label": "Rating",
+
+ "advanced-workflow-action.rating.form.rating.error": "You must rate the item",
+
+ "advanced-workflow-action.rating.form.review.label": "Review",
+
+ "advanced-workflow-action.rating.form.review.error": "You must enter a review to submit this rating",
+
+ "advanced-workflow-action.rating.description": "Please select a rating below",
+
+ "advanced-workflow-action.rating.description-requiredDescription": "Please select a rating below and also add a review",
+
+
+ "advanced-workflow-action.select-reviewer.description-single": "Please select a single reviewer below before submitting",
+
+ "advanced-workflow-action.select-reviewer.description-multiple": "Please select one or more reviewers below before submitting",
+
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.head": "EPeople",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.search.head": "Add EPeople",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.button.see-all": "Browse All",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.headMembers": "Current Members",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.search.scope.metadata": "Metadata",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.search.scope.email": "E-mail (exact)",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.search.button": "Search",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.id": "ID",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.name": "Name",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.identity": "Identity",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.email": "Email",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.netid": "NetID",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.edit": "Remove / Add",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.edit.buttons.remove": "Remove member with name \"{{name}}\"",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.notification.success.addMember": "Successfully added member: \"{{name}}\"",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.notification.failure.addMember": "Failed to add member: \"{{name}}\"",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.notification.success.deleteMember": "Successfully deleted member: \"{{name}}\"",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.notification.failure.deleteMember": "Failed to delete member: \"{{name}}\"",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.edit.buttons.add": "Add member with name \"{{name}}\"",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.notification.failure.noActiveGroup": "No current active group, submit a name first.",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.no-members-yet": "No members in group yet, search and add.",
+
+ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.no-items": "No EPeople found in that search",
+
+ "advanced-workflow-action.select-reviewer.no-reviewer-selected.error": "No reviewer selected.",
+
"admin.batch-import.page.validateOnly.hint": "When selected, the uploaded ZIP will be validated. You will receive a report of detected changes, but no changes will be saved.",
"admin.batch-import.page.remove": "remove",
@@ -790,6 +860,12 @@
"chips.remove": "Remove chip",
+ "claimed-approved-search-result-list-element.title": "Approved",
+
+ "claimed-declined-search-result-list-element.title": "Rejected, sent back to submitter",
+
+ "claimed-declined-task-search-result-list-element.title": "Declined, sent back to Review Manager's workflow",
+
"collection.create.head": "Create a Collection",
@@ -1200,6 +1276,11 @@
"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.scorereviewers.name": "Score Reviewers",
+
+ "comcol-role.edit.scorereviewers.description": "Reviewers are able to give a score to incoming submissions, this will define whether the submission will be rejected or not.",
+
+
"community.form.abstract": "Short Description",
@@ -1327,7 +1408,7 @@
"curation-task.task.vscan.label": "Virus Scan",
- "curation-task.task.registerdoi.label": "Register DOI",
+ "curation-task.task.register-doi.label": "Register DOI",
@@ -1425,6 +1506,32 @@
"dso-selector.results-could-not-be-retrieved": "Something went wrong, please refresh again ↻",
+ "supervision-group-selector.header": "Supervision Group Selector",
+
+ "supervision-group-selector.select.type-of-order.label": "Select a type of Order",
+
+ "supervision-group-selector.select.type-of-order.option.none": "NONE",
+
+ "supervision-group-selector.select.type-of-order.option.editor": "EDITOR",
+
+ "supervision-group-selector.select.type-of-order.option.observer": "OBSERVER",
+
+ "supervision-group-selector.select.group.label": "Select a Group",
+
+ "supervision-group-selector.button.cancel": "Cancel",
+
+ "supervision-group-selector.button.save": "Save",
+
+ "supervision-group-selector.select.type-of-order.error": "Please select a type of order",
+
+ "supervision-group-selector.select.group.error": "Please select a group",
+
+ "supervision-group-selector.notification.create.success.title": "Successfully created supervision order for group {{ name }}",
+
+ "supervision-group-selector.notification.create.failure.title": "Error",
+
+ "supervision-group-selector.notification.create.already-existing" : "A supervision order already exists on this item for selected group",
+
"confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}",
"confirmation-modal.export-metadata.info": "Are you sure you want to export metadata for {{ dsoName }}",
@@ -1457,6 +1564,13 @@
"confirmation-modal.delete-profile.confirm": "Delete",
+ "confirmation-modal.delete-subscription.header": "Delete Subscription",
+
+ "confirmation-modal.delete-subscription.info": "Are you sure you want to delete subscription for \"{{ dsoName }}\"",
+
+ "confirmation-modal.delete-subscription.cancel": "Cancel",
+
+ "confirmation-modal.delete-subscription.confirm": "Delete",
"error.bitstream": "Error fetching bitstream",
@@ -1918,6 +2032,48 @@
"item.edit.tabs.item-mapper.title": "Item Edit - Collection Mapper",
+ "item.edit.identifiers.doi.status.UNKNOWN": "Unknown",
+
+ "item.edit.identifiers.doi.status.TO_BE_REGISTERED": "Queued for registration",
+
+ "item.edit.identifiers.doi.status.TO_BE_RESERVED": "Queued for reservation",
+
+ "item.edit.identifiers.doi.status.IS_REGISTERED": "Registered",
+
+ "item.edit.identifiers.doi.status.IS_RESERVED": "Reserved",
+
+ "item.edit.identifiers.doi.status.UPDATE_RESERVED": "Reserved (update queued)",
+
+ "item.edit.identifiers.doi.status.UPDATE_REGISTERED": "Registered (update queued)",
+
+ "item.edit.identifiers.doi.status.UPDATE_BEFORE_REGISTRATION": "Queued for update and registration",
+
+ "item.edit.identifiers.doi.status.TO_BE_DELETED": "Queued for deletion",
+
+ "item.edit.identifiers.doi.status.DELETED": "Deleted",
+
+ "item.edit.identifiers.doi.status.PENDING": "Pending (not registered)",
+
+ "item.edit.identifiers.doi.status.MINTED": "Minted (not registered)",
+
+ "item.edit.tabs.status.buttons.register-doi.label": "Register a new or pending DOI",
+
+ "item.edit.tabs.status.buttons.register-doi.button": "Register DOI...",
+
+ "item.edit.register-doi.header": "Register a new or pending DOI",
+
+ "item.edit.register-doi.description": "Review any pending identifiers and item metadata below and click Confirm to proceed with DOI registration, or Cancel to back out",
+
+ "item.edit.register-doi.confirm": "Confirm",
+
+ "item.edit.register-doi.cancel": "Cancel",
+
+ "item.edit.register-doi.success": "DOI queued for registration successfully.",
+
+ "item.edit.register-doi.error": "Error registering DOI",
+
+ "item.edit.register-doi.to-update": "The following DOI has already been minted and will be queued for registration online",
+
"item.edit.item-mapper.buttons.add": "Map item to selected collections",
"item.edit.item-mapper.buttons.remove": "Remove item's mapping for selected collections",
@@ -2239,6 +2395,22 @@
"item.truncatable-part.show-less": "Collapse",
+ "workflow-item.search.result.delete-supervision.modal.header": "Delete Supervision Order",
+
+ "workflow-item.search.result.delete-supervision.modal.info": "Are you sure you want to delete Supervision Order",
+
+ "workflow-item.search.result.delete-supervision.modal.cancel": "Cancel",
+
+ "workflow-item.search.result.delete-supervision.modal.confirm": "Delete",
+
+ "workflow-item.search.result.notification.deleted.success": "Successfully deleted supervision order \"{{name}}\"",
+
+ "workflow-item.search.result.notification.deleted.failure": "Failed to delete supervision order \"{{name}}\"",
+
+ "workflow-item.search.result.list.element.supervised-by": "Supervised by:",
+
+ "workflow-item.search.result.list.element.supervised.remove-tooltip": "Remove supervision group",
+
"item.page.abstract": "Abstract",
@@ -2924,6 +3096,8 @@
"mydspace.show.workspace": "Your Submissions",
+ "mydspace.show.supervisedWorkspace": "Supervised items",
+
"mydspace.status.archived": "Archived",
"mydspace.status.validation": "Validation",
@@ -2974,6 +3148,8 @@
"nav.stop-impersonating": "Stop impersonating EPerson",
+ "nav.subscriptions" : "Subscriptions",
+
"nav.toggle" : "Toggle navigation",
"nav.user.description" : "User profile bar",
@@ -3578,6 +3754,8 @@
"search.filters.applied.f.birthDate.min": "Start birth date",
+ "search.filters.applied.f.supervisedBy": "Supervised by",
+
"search.filters.applied.f.withdrawn": "Withdrawn",
@@ -3722,6 +3900,13 @@
"search.filters.filter.show-tree": "Browse {{ name }} tree",
+ "search.filters.filter.supervisedBy.head": "Supervised By",
+
+ "search.filters.filter.supervisedBy.placeholder": "Supervised By",
+
+ "search.filters.filter.supervisedBy.label": "Search Supervised By",
+
+
"search.filters.entityType.JournalIssue": "Journal Issue",
@@ -4287,7 +4472,17 @@
"submission.sections.general.sections_not_valid": "There are incomplete sections.",
+ "submission.sections.identifiers.info": "The following identifiers will be created for your item:",
+ "submission.sections.identifiers.no_handle": "No handles have been minted for this item.",
+
+ "submission.sections.identifiers.no_doi": "No DOIs have been minted for this item.",
+
+ "submission.sections.identifiers.handle_label": "Handle: ",
+
+ "submission.sections.identifiers.doi_label": "DOI: ",
+
+ "submission.sections.identifiers.otherIdentifiers_label": "Other identifiers: ",
"submission.sections.submit.progressbar.accessCondition": "Item access conditions",
@@ -4303,6 +4498,8 @@
"submission.sections.submit.progressbar.detect-duplicate": "Potential duplicates",
+ "submission.sections.submit.progressbar.identifiers": "Identifiers",
+
"submission.sections.submit.progressbar.license": "Deposit license",
"submission.sections.submit.progressbar.sherpapolicy": "Sherpa policies",
@@ -4384,7 +4581,7 @@
"submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
- "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
"submission.sections.upload.no-entry": "No",
@@ -4508,6 +4705,15 @@
"submission.workflow.generic.view-help": "Select this option to view the item's metadata.",
+ "submission.workflow.generic.submit_select_reviewer": "Select Reviewer",
+
+ "submission.workflow.generic.submit_select_reviewer-help": "",
+
+
+ "submission.workflow.generic.submit_score": "Rate",
+
+ "submission.workflow.generic.submit_score-help": "",
+
"submission.workflow.tasks.claimed.approve": "Approve",
@@ -4517,6 +4723,10 @@
"submission.workflow.tasks.claimed.edit_help": "Select this option to change the item's metadata.",
+ "submission.workflow.tasks.claimed.decline": "Decline",
+
+ "submission.workflow.tasks.claimed.decline_help": "",
+
"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.placeholder": "Describe the reason of reject",
@@ -4559,6 +4769,77 @@
"submission.workspace.generic.view-help": "Select this option to view the item's metadata.",
+ "subscriptions.title": "Subscriptions",
+
+ "subscriptions.item": "Subscriptions for items",
+
+ "subscriptions.collection": "Subscriptions for collections",
+
+ "subscriptions.community": "Subscriptions for communities",
+
+ "subscriptions.subscription_type": "Subscription type",
+
+ "subscriptions.frequency": "Subscription frequency",
+
+ "subscriptions.frequency.D": "Daily",
+
+ "subscriptions.frequency.M": "Monthly",
+
+ "subscriptions.frequency.W": "Weekly",
+
+ "subscriptions.tooltip": "Subscribe",
+
+ "subscriptions.modal.title": "Subscriptions",
+
+ "subscriptions.modal.type-frequency": "Type and frequency",
+
+ "subscriptions.modal.close": "Close",
+
+ "subscriptions.modal.delete-info": "To remove this subscription, please visit the \"Subscriptions\" page under your user profile",
+
+ "subscriptions.modal.new-subscription-form.type.content": "Content",
+
+ "subscriptions.modal.new-subscription-form.frequency.D": "Daily",
+
+ "subscriptions.modal.new-subscription-form.frequency.W": "Weekly",
+
+ "subscriptions.modal.new-subscription-form.frequency.M": "Monthly",
+
+ "subscriptions.modal.new-subscription-form.submit": "Submit",
+
+ "subscriptions.modal.new-subscription-form.processing": "Processing...",
+
+ "subscriptions.modal.create.success": "Subscribed to {{ type }} successfully.",
+
+ "subscriptions.modal.delete.success": "Subscription deleted successfully",
+
+ "subscriptions.modal.update.success": "Subscription to {{ type }} updated successfully",
+
+ "subscriptions.modal.create.error": "An error occurs during the subscription creation",
+
+ "subscriptions.modal.delete.error": "An error occurs during the subscription delete",
+
+ "subscriptions.modal.update.error": "An error occurs during the subscription update",
+
+ "subscriptions.table.dso": "Subject",
+
+ "subscriptions.table.subscription_type": "Subscription Type",
+
+ "subscriptions.table.subscription_frequency": "Subscription Frequency",
+
+ "subscriptions.table.action": "Action",
+
+ "subscriptions.table.edit": "Edit",
+
+ "subscriptions.table.delete": "Delete",
+
+ "subscriptions.table.not-available": "Not available",
+
+ "subscriptions.table.not-available-message": "The subscribed item has been deleted, or you don't currently have the permission to view it",
+
+ "subscriptions.table.empty.message": "You do not have any subscriptions at this time. To subscribe to email updates for a Community or Collection, use the subscription button on the object's page.",
+
+
"thumbnail.default.alt": "Thumbnail Image",
"thumbnail.default.placeholder": "No Thumbnail Available",
@@ -4617,12 +4898,16 @@
+ "supervisedWorkspace.search.results.head": "Supervised Items",
+
"workspace.search.results.head": "Your submissions",
"workflowAdmin.search.results.head": "Administer Workflow",
"workflow.search.results.head": "Workflow tasks",
+ "supervision.search.results.head": "Workflow and Workspace tasks",
+
"workflow-item.edit.breadcrumbs": "Edit workflowitem",
@@ -4668,6 +4953,43 @@
"workspace-item.view.title": "Workspace View",
+
+ "workflow-item.advanced.title": "Advanced workflow",
+
+
+ "workflow-item.selectrevieweraction.notification.success.title": "Selected reviewer",
+
+ "workflow-item.selectrevieweraction.notification.success.content": "The reviewer for this workflow item has been successfully selected",
+
+ "workflow-item.selectrevieweraction.notification.error.title": "Something went wrong",
+
+ "workflow-item.selectrevieweraction.notification.error.content": "Couldn't select the reviewer for this workflow item",
+
+ "workflow-item.selectrevieweraction.title": "Select Reviewer",
+
+ "workflow-item.selectrevieweraction.header": "Select Reviewer",
+
+ "workflow-item.selectrevieweraction.button.cancel": "Cancel",
+
+ "workflow-item.selectrevieweraction.button.confirm": "Confirm",
+
+
+ "workflow-item.scorereviewaction.notification.success.title": "Rating review",
+
+ "workflow-item.scorereviewaction.notification.success.content": "The rating for this item workflow item has been successfully submitted",
+
+ "workflow-item.scorereviewaction.notification.error.title": "Something went wrong",
+
+ "workflow-item.scorereviewaction.notification.error.content": "Couldn't rate this item",
+
+ "workflow-item.scorereviewaction.title": "Rate this item",
+
+ "workflow-item.scorereviewaction.header": "Rate this item",
+
+ "workflow-item.scorereviewaction.button.cancel": "Cancel",
+
+ "workflow-item.scorereviewaction.button.confirm": "Confirm",
+
"idle-modal.header": "Session will expire soon",
"idle-modal.info": "For security reasons, user sessions expire after {{ timeToExpire }} minutes of inactivity. Your session will expire soon. Would you like to extend it or log out?",
@@ -4922,4 +5244,53 @@
"home.recent-submissions.head": "Recent Submissions",
"listable-notification-object.default-message": "This object couldn't be retrieved",
+
+
+ "system-wide-alert-banner.retrieval.error": "Something went wrong retrieving the system-wide alert banner",
+
+ "system-wide-alert-banner.countdown.prefix": "In",
+
+ "system-wide-alert-banner.countdown.days": "{{days}} day(s),",
+
+ "system-wide-alert-banner.countdown.hours": "{{hours}} hour(s) and",
+
+ "system-wide-alert-banner.countdown.minutes": "{{minutes}} minute(s):",
+
+
+
+ "menu.section.system-wide-alert": "System-wide Alert",
+
+ "system-wide-alert.form.header": "System-wide Alert",
+
+ "system-wide-alert-form.retrieval.error": "Something went wrong retrieving the system-wide alert",
+
+ "system-wide-alert.form.cancel": "Cancel",
+
+ "system-wide-alert.form.save": "Save",
+
+ "system-wide-alert.form.label.active": "ACTIVE",
+
+ "system-wide-alert.form.label.inactive": "INACTIVE",
+
+ "system-wide-alert.form.error.message": "The system wide alert must have a message",
+
+ "system-wide-alert.form.label.message": "Alert message",
+
+ "system-wide-alert.form.label.countdownTo.enable": "Enable a countdown timer",
+
+ "system-wide-alert.form.label.countdownTo.hint": "Hint: Set a countdown timer. When enabled, a date can be set in the future and the system-wide alert banner will perform a countdown to the set date. When this timer ends, it will disappear from the alert. The server will NOT be automatically stopped.",
+
+ "system-wide-alert.form.label.preview": "System-wide alert preview",
+
+ "system-wide-alert.form.update.success": "The system-wide alert was successfully updated",
+
+ "system-wide-alert.form.update.error": "Something went wrong when updating the system-wide alert",
+
+ "system-wide-alert.form.create.success": "The system-wide alert was successfully created",
+
+ "system-wide-alert.form.create.error": "Something went wrong when creating the system-wide alert",
+
+ "admin.system-wide-alert.breadcrumbs": "System-wide Alerts",
+
+ "admin.system-wide-alert.title": "System-wide Alerts",
}
diff --git a/src/assets/i18n/es.json5 b/src/assets/i18n/es.json5
index ae41bfdcb5..3184e4f22b 100644
--- a/src/assets/i18n/es.json5
+++ b/src/assets/i18n/es.json5
@@ -6353,7 +6353,7 @@
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
"submission.sections.upload.header.policy.default.withlist": "Tenga en cuenta que los archivos cargados en la colección {{ collectionName }} serán accesibles, además de lo que se decida explícitamente para el fichero, para los siguientes grupos:",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
"submission.sections.upload.info": "Aquí encontrará todos los archivos que se encuentran actualmente en el ítem. Puede actualizar los metadatos del fichero y las condiciones de acceso e incluir ficheros adicionales simplemente arrastrando-y-soltando en cualquier lugar de la página",
// "submission.sections.upload.no-entry": "No",
diff --git a/src/assets/i18n/fi.json5 b/src/assets/i18n/fi.json5
index 2a077006cc..05ae06d46b 100644
--- a/src/assets/i18n/fi.json5
+++ b/src/assets/i18n/fi.json5
@@ -4914,7 +4914,7 @@
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
"submission.sections.upload.header.policy.default.withlist": "Yksittäisten tiedostojen pääsyrajoitusten lisäksi {{collectionName}}-kokoelmaan ladatut tiedostot ovat seuraavien ryhmien saatavilla:",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
"submission.sections.upload.info": "Tietueen kaikki tiedostot on lueteltu tässä. Voit päivittää tiedoston metadataa ja pääsyehtoja tai ladata lisää tiedostoja raahaamalla ne mihin hyvänsä sivun kohtaan",
// "submission.sections.upload.no-entry": "No",
diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5
index 381e13a743..1e042c81fd 100644
--- a/src/assets/i18n/fr.json5
+++ b/src/assets/i18n/fr.json5
@@ -5670,7 +5670,7 @@
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
"submission.sections.upload.header.policy.default.withlist": "Veuillez noter que, en complément des accès spécifiquement accordés pour le fichier, les fichiers téléchargés dans la collection {{collectionName}} seront accessibles par défaut au(x) groupe(s) suivant(s) :",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
"submission.sections.upload.info": "Vous trouverez ici tous les fichiers actuellement associés à l'Item. Vous pouvez éditer les métadonnés et les niveaux d'accès de ce(s) fichier(s) ou télécharger des fichiers complémentaires simplement en les glissant n'importe où sur cette page",
// "submission.sections.upload.no-entry": "No",
diff --git a/src/assets/i18n/gd.json5 b/src/assets/i18n/gd.json5
index 083de54781..4584145452 100644
--- a/src/assets/i18n/gd.json5
+++ b/src/assets/i18n/gd.json5
@@ -5801,7 +5801,7 @@
// TODO New key - Add a translation
"submission.sections.upload.header.policy.default.withlist": "Thoir fa-near gum bi cothrom air faidhlichean a chaidh a luchdachadh gu cruinneachadh {{collectionName}}, cho math ris na chaidh a cho-dhùnadh airson an fhaidhle air leth, aig a' bhuidheann/na buidhnean a leanas:",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
"submission.sections.upload.info": "An seo gheibh thu na faidhlichean gu lèir san nì an-dràsta. Is urrainn dhut metadata an fhaidhle ùrachadh agus cothrom fhaighinn air cumhachan no faidhlichean eile a luchdachadh suas le bhith gan slaodadh agus gan leigeil às air feadh na duilleig",
// "submission.sections.upload.no-entry": "No",
diff --git a/src/assets/i18n/hu.json5 b/src/assets/i18n/hu.json5
index 0e73e9dbae..bfa4a53ccf 100644
--- a/src/assets/i18n/hu.json5
+++ b/src/assets/i18n/hu.json5
@@ -4924,7 +4924,7 @@
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
"submission.sections.upload.header.policy.default.withlist": "Kérjük, vegye figyelembe, hogy a {{collectionName}} gyűjteménybe feltöltött állományok elérhetők lesznek, azon kívül amit az egyedi állományokról kifejezetten eldöntött, a következő csoport(ok)ban:",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
"submission.sections.upload.info": "Itt megtalálja a tárgyban lévő valamennyi állományt. Frissítheti az állomány metaadatait és hozzáférési feltételeit vagy feltölthet további állományokat azzal, hogy behúzza azokat bárhova az oldalra",
// "submission.sections.upload.no-entry": "No",
diff --git a/src/assets/i18n/ja.json5 b/src/assets/i18n/ja.json5
index 71b32cf70b..83a755c923 100644
--- a/src/assets/i18n/ja.json5
+++ b/src/assets/i18n/ja.json5
@@ -6459,9 +6459,9 @@
// TODO New key - Add a translation
"submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
// TODO New key - Add a translation
- "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
// "submission.sections.upload.no-entry": "No",
// TODO New key - Add a translation
diff --git a/src/assets/i18n/kk.json5 b/src/assets/i18n/kk.json5
index b661ce21d3..0fd092df02 100644
--- a/src/assets/i18n/kk.json5
+++ b/src/assets/i18n/kk.json5
@@ -6283,7 +6283,7 @@
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
"submission.sections.upload.header.policy.default.withlist": "{{collectionName}} жинағына жүктелген файлдар жеке файл үшін нақты анықталғаннан басқа, келесі топпен (топтармен) қол жетімді болатындығын ескеріңіз:",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
"submission.sections.upload.info": "Мұнда сіз осы элементтегі барлық файлдарды таба аласыз. Файл метадеректерін және кіру шарттарын жаңартуға немесе қосымша файлдарды олардыбетінде сүйреп апару арқылы жүктеуге болады.",
// "submission.sections.upload.no-entry": "No",
diff --git a/src/assets/i18n/lv.json5 b/src/assets/i18n/lv.json5
index e00ad3517b..79ff8ce855 100644
--- a/src/assets/i18n/lv.json5
+++ b/src/assets/i18n/lv.json5
@@ -5379,7 +5379,7 @@
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
"submission.sections.upload.header.policy.default.withlist": "Lūdzu, ņemiet vērā, ka augšupielādētie faili kolekcijā {{collectionName}} būs pieejami papildus tam, kas ir skaidri noteikts par atsevišķu failu, ar šādām grupām:",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
"submission.sections.upload.info": "Šeit atradīsit visus failus, kas pašlaik atrodas materiālā. Varat atjaunināt failu metadatus un piekļuves nosacījumus vai augšupielādēt papildu failus, vienkārši ievelkot un atstājot tos visur lapā",
// "submission.sections.upload.no-entry": "No",
diff --git a/src/assets/i18n/nl.json5 b/src/assets/i18n/nl.json5
index b02e3a5276..1ebdeaa566 100644
--- a/src/assets/i18n/nl.json5
+++ b/src/assets/i18n/nl.json5
@@ -5752,7 +5752,7 @@
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
"submission.sections.upload.header.policy.default.withlist": "Let op: bestanden in de collectie {{collectionName}} zullen niet alleen toegankelijk zijn volgens de expliciet toegekende rechten per bestand, maar ook volgens de volgende groep(en):",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
"submission.sections.upload.info": "Hier vindt u alle bestanden van het item. U kunt de metadata en toegangsrechten bewerken of meer bestanden toevoegen door ze naar deze pagina te slepen.",
// "submission.sections.upload.no-entry": "No",
diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5
index f68436d00b..7dca26aa4e 100644
--- a/src/assets/i18n/pt-BR.json5
+++ b/src/assets/i18n/pt-BR.json5
@@ -6066,7 +6066,7 @@
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
"submission.sections.upload.header.policy.default.withlist": "Por favor note que arquivos enviados a coleção {{collectionName}} serão acessíveis, de acordo com o que está explicitamente definido no arquivo, no(s) seguinte(s) grupo(s):",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
"submission.sections.upload.info": "Aqui vocẽ encontra todos os arquivos que estão atualmente no item. Você pode atualizar os metadados do arquivo e condições de acesso ou enviar arquivos adicionais apenas arrastando os arquivos em qualquer lugar da página",
// "submission.sections.upload.no-entry": "No",
diff --git a/src/assets/i18n/pt-PT.json5 b/src/assets/i18n/pt-PT.json5
index b277e866c0..62e7c53e36 100644
--- a/src/assets/i18n/pt-PT.json5
+++ b/src/assets/i18n/pt-PT.json5
@@ -5117,7 +5117,7 @@
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
"submission.sections.upload.header.policy.default.withlist": "Por favor note que os ficheiros enviados à coleção {{collectionName}} serão acessíveis, de acordo com o que está explicitamente definido no ficheiro, no(s) seguinte(s) grupo(s):",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
"submission.sections.upload.info": "Aqui encontra todos os ficheiros que estão atualmente no item. Pode atualizar os metadados do ficheiro e condições de acesso ou carregar ficheiros adicionais arrastando os ficheiros em qualquer lugar desta página",
// "submission.sections.upload.no-entry": "No",
diff --git a/src/assets/i18n/sv.json5 b/src/assets/i18n/sv.json5
index d04b95e144..4c9db83b16 100644
--- a/src/assets/i18n/sv.json5
+++ b/src/assets/i18n/sv.json5
@@ -5954,7 +5954,7 @@
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
"submission.sections.upload.header.policy.default.withlist": "Notera att uppladdade filer i samlingen {{collectionName}} också kommer att vara åtkompliga för följande grupp(er):",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
"submission.sections.upload.info": "Här visas samtliga filer som ingår i posten. Du kan uppdatera filernas metadata och villkor för åtkomst, samt ladda upp nya filer genom att dra och släppa dem här",
// "submission.sections.upload.no-entry": "No",
diff --git a/src/assets/i18n/sw.json5 b/src/assets/i18n/sw.json5
index 5e00b54599..4f68655019 100644
--- a/src/assets/i18n/sw.json5
+++ b/src/assets/i18n/sw.json5
@@ -6459,9 +6459,9 @@
// TODO New key - Add a translation
"submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
// TODO New key - Add a translation
- "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
// "submission.sections.upload.no-entry": "No",
// TODO New key - Add a translation
diff --git a/src/assets/i18n/tr.json5 b/src/assets/i18n/tr.json5
index fff02c5967..92e0c5b6e2 100644
--- a/src/assets/i18n/tr.json5
+++ b/src/assets/i18n/tr.json5
@@ -4905,7 +4905,7 @@
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
"submission.sections.upload.header.policy.default.withlist": "{{collectionName}} koleksiyonuna yüklenen dosyalara, tek dosya için açıkça karar verilenlere ek olarak aşağıdaki gruplarla erişilebilir olacağını lütfen unutmayın:",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
"submission.sections.upload.info": "Burada, o anda öğede bulunan tüm dosyaları bulacaksınız. Dosya metadatalarını güncelleyebilir ve koşullara erişebilir veya sayfanın her yerine sürükleyip bırakarak ek dosyalar yükleyebilirsiniz",
// "submission.sections.upload.no-entry": "No",
diff --git a/src/assets/i18n/uk.json5 b/src/assets/i18n/uk.json5
index 0ba5b0109a..6ee2bd9f1c 100644
--- a/src/assets/i18n/uk.json5
+++ b/src/assets/i18n/uk.json5
@@ -5225,7 +5225,7 @@
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
"submission.sections.upload.header.policy.default.withlist": "Зверніть увагу, що завантажені файли в {{collectionName}} зібрання будуть доступні, для користувачів, що входять у групу(и):",
- // "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": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.",
"submission.sections.upload.info": "Тут ви знайдете всі файли, які зараз містяться в документі. Ви можете оновити метадані файлу та умови доступу або завантажити додаткові файли, просто перетягнувши їх сюди на сторінці",
// "submission.sections.upload.no-entry": "No",
diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts
index d62b9e5bcb..e3f8988744 100644
--- a/src/config/app-config.interface.ts
+++ b/src/config/app-config.interface.ts
@@ -21,6 +21,7 @@ import { CommunityListConfig } from './community-list-config.interface';
import { HomeConfig } from './homepage-config.interface';
import { MarkdownConfig } from './markdown-config.interface';
import { FilterVocabularyConfig } from './filter-vocabulary-config';
+import { DiscoverySortConfig } from './discovery-sort.config';
interface AppConfig extends Config {
ui: UIServerConfig;
@@ -46,6 +47,7 @@ interface AppConfig extends Config {
info: InfoConfig;
markdown: MarkdownConfig;
vocabularies: FilterVocabularyConfig[];
+ comcolSelectionSort: DiscoverySortConfig;
}
/**
diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts
index e7851d4b34..67c2feada8 100644
--- a/src/config/default-app-config.ts
+++ b/src/config/default-app-config.ts
@@ -21,6 +21,7 @@ import { CommunityListConfig } from './community-list-config.interface';
import { HomeConfig } from './homepage-config.interface';
import { MarkdownConfig } from './markdown-config.interface';
import { FilterVocabularyConfig } from './filter-vocabulary-config';
+import { DiscoverySortConfig } from './discovery-sort.config';
export class DefaultAppConfig implements AppConfig {
production = false;
@@ -421,4 +422,10 @@ export class DefaultAppConfig implements AppConfig {
enabled: false
}
];
+
+ // Configuration that determines the metadata sorting of community and collection edition and creation when there are not a search query.
+ comcolSelectionSort: DiscoverySortConfig = {
+ sortField:'dc.title',
+ sortDirection:'ASC',
+ };
}
diff --git a/src/config/discovery-sort.config.ts b/src/config/discovery-sort.config.ts
new file mode 100644
index 0000000000..c0d5e5e391
--- /dev/null
+++ b/src/config/discovery-sort.config.ts
@@ -0,0 +1,14 @@
+import { Config } from './config.interface';
+
+/**
+ * Config that determines a metadata sorting config.
+ * It's created mainly to sort by metadata community and collection edition and creation
+ */
+export class DiscoverySortConfig implements Config {
+
+ public sortField: string;
+ /**
+ * ASC / DESC values expected
+ */
+ public sortDirection: string;
+}
diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts
index 0bb36da61f..08d7739f7d 100644
--- a/src/environments/environment.test.ts
+++ b/src/environments/environment.test.ts
@@ -297,6 +297,10 @@ export const environment: BuildConfig = {
enabled: false,
mathjax: false,
},
+ comcolSelectionSort: {
+ sortField:'dc.title',
+ sortDirection:'ASC',
+ },
vocabularies: [
{
diff --git a/src/index.csr.html b/src/index.csr.html
deleted file mode 100644
index b1ef4343b1..0000000000
--- a/src/index.csr.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
- DSpace
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main.browser.ts b/src/main.browser.ts
index d5efe828c3..68debfb355 100644
--- a/src/main.browser.ts
+++ b/src/main.browser.ts
@@ -2,21 +2,27 @@ import 'zone.js';
import 'reflect-metadata';
import 'core-js/es/reflect';
-import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { load as loadWebFont } from 'webfontloader';
-import { hasValue } from './app/shared/empty.util';
-
import { BrowserAppModule } from './modules/app/browser-app.module';
import { environment } from './environments/environment';
import { AppConfig } from './config/app-config.interface';
import { extendEnvironmentWithAppConfig } from './config/config.util';
+import { enableProdMode } from '@angular/core';
const bootstrap = () => platformBrowserDynamic()
.bootstrapModule(BrowserAppModule, {});
+/**
+ * We use this to determine have been serven SSR HTML or not.
+ *
+ * At this point, {@link environment} may not be in sync with the configuration.
+ * Therefore, we cannot depend on it to determine how to bootstrap the app.
+ */
+const hasTransferState = document.querySelector('script#dspace-angular-state') !== null;
+
const main = () => {
// Load fonts async
// https://github.com/typekit/webfontloader#configuration
@@ -30,22 +36,23 @@ const main = () => {
enableProdMode();
}
- if (hasValue(environment.universal) && environment.universal.preboot) {
+ if (hasTransferState) {
+ // Configuration will be taken from transfer state during initialization
return bootstrap();
} else {
+ // Configuration must be fetched explicitly
return fetch('assets/config.json')
.then((response) => response.json())
.then((appConfig: AppConfig) => {
// extend environment with app config for browser when not prerendered
extendEnvironmentWithAppConfig(environment, appConfig);
-
return bootstrap();
});
}
};
// support async tag or hmr
-if (document.readyState === 'complete' && hasValue(environment.universal) && !environment.universal.preboot) {
+if (document.readyState === 'complete' && !hasTransferState) {
main();
} else {
document.addEventListener('DOMContentLoaded', main);
diff --git a/src/modules/app/browser-init.service.ts b/src/modules/app/browser-init.service.ts
index 1135de5e93..687ecf0547 100644
--- a/src/modules/app/browser-init.service.ts
+++ b/src/modules/app/browser-init.service.ts
@@ -29,6 +29,7 @@ import { coreSelector } from '../../app/core/core.selectors';
import { find, map } from 'rxjs/operators';
import { isNotEmpty } from '../../app/shared/empty.util';
import { logStartupMessage } from '../../../startup-message';
+import { MenuService } from '../../app/shared/menu/menu.service';
/**
* Performs client-side initialization.
@@ -49,6 +50,7 @@ export class BrowserInitService extends InitService {
protected klaroService: KlaroService,
protected authService: AuthService,
protected themeService: ThemeService,
+ protected menuService: MenuService,
) {
super(
store,
@@ -60,6 +62,7 @@ export class BrowserInitService extends InitService {
metadata,
breadcrumbsService,
themeService,
+ menuService,
);
}
diff --git a/src/modules/app/server-init.service.ts b/src/modules/app/server-init.service.ts
index 903bd91b7c..d909bb0e8d 100644
--- a/src/modules/app/server-init.service.ts
+++ b/src/modules/app/server-init.service.ts
@@ -20,6 +20,7 @@ import { MetadataService } from '../../app/core/metadata/metadata.service';
import { BreadcrumbsService } from '../../app/breadcrumbs/breadcrumbs.service';
import { ThemeService } from '../../app/shared/theme-support/theme.service';
import { take } from 'rxjs/operators';
+import { MenuService } from '../../app/shared/menu/menu.service';
/**
* Performs server-side initialization.
@@ -37,6 +38,7 @@ export class ServerInitService extends InitService {
protected metadata: MetadataService,
protected breadcrumbsService: BreadcrumbsService,
protected themeService: ThemeService,
+ protected menuService: MenuService,
) {
super(
store,
@@ -48,6 +50,7 @@ export class ServerInitService extends InitService {
metadata,
breadcrumbsService,
themeService,
+ menuService,
);
}
diff --git a/src/styles/_global-styles.scss b/src/styles/_global-styles.scss
index bbb4bac298..b26e200875 100644
--- a/src/styles/_global-styles.scss
+++ b/src/styles/_global-styles.scss
@@ -240,3 +240,14 @@ ds-dynamic-form-control-container.d-none {
white-space: nowrap !important;
border: 0 !important;
}
+
+ul.dso-edit-menu-dropdown > li .nav-item.nav-link {
+ // ensure that links in DSO edit menu dropdowns are unstyled (li elements are styled instead to support icons)
+ padding: 0;
+ display: inline;
+}
+
+.table th,
+.table td {
+ vertical-align: middle;
+}
diff --git a/src/themes/custom/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.html b/src/themes/custom/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.html
index 85d8797e66..54044f5d79 100644
--- a/src/themes/custom/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.html
+++ b/src/themes/custom/app/shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component.html
@@ -6,6 +6,6 @@