diff --git a/config/config.example.yml b/config/config.example.yml index 9abf167b90..c548d6944a 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -310,3 +310,11 @@ info: markdown: enabled: false mathjax: false + +# Which vocabularies should be used for which search filters +# and whether to show the filter in the search sidebar +# Take a look at the filter-vocabulary-config.ts file for documentation on how the options are obtained +vocabularies: + - filter: 'subject' + vocabulary: 'srsc' + enabled: true diff --git a/package.json b/package.json index 52b089be37..f6ab1274e6 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,9 @@ "clean:log": "rimraf *.log*", "clean:json": "rimraf *.records.json", "clean:node": "rimraf node_modules", + "clean:cli": "rimraf .angular/cache", "clean:prod": "yarn run clean:dist && yarn run clean:log && yarn run clean:doc && yarn run clean:coverage && yarn run clean:json", - "clean": "yarn run clean:prod && yarn run clean:dev:config && yarn run clean:node", + "clean": "yarn run clean:prod && yarn run clean:dev:config && yarn run clean:cli && yarn run clean:node", "sync-i18n": "ts-node --project ./tsconfig.ts-node.json scripts/sync-i18n-files.ts", "build:mirador": "webpack --config webpack/webpack.mirador.config.ts", "merge-i18n": "ts-node --project ./tsconfig.ts-node.json scripts/merge-i18n-files.ts", @@ -99,7 +100,7 @@ "http-proxy-middleware": "^1.0.5", "js-cookie": "2.2.1", "js-yaml": "^4.1.0", - "json5": "^2.1.3", + "json5": "^2.2.2", "jsonschema": "1.4.0", "jwt-decode": "^3.1.2", "klaro": "^0.7.18", diff --git a/src/app/access-control/group-registry/group-form/group-form.component.spec.ts b/src/app/access-control/group-registry/group-form/group-form.component.spec.ts index 09487a7eaa..a7a7cb5be4 100644 --- a/src/app/access-control/group-registry/group-form/group-form.component.spec.ts +++ b/src/app/access-control/group-registry/group-form/group-form.component.spec.ts @@ -266,6 +266,43 @@ describe('GroupFormComponent', () => { fixture.detectChanges(); }); + it('should edit with name and description operations', () => { + const operations = [{ + op: 'add', + path: '/metadata/dc.description', + value: 'testDescription' + }, { + op: 'replace', + path: '/name', + value: 'newGroupName' + }]; + expect(groupsDataServiceStub.patch).toHaveBeenCalledWith(expected, operations); + }); + + it('should edit with description operations', () => { + component.groupName.value = null; + component.onSubmit(); + fixture.detectChanges(); + const operations = [{ + op: 'add', + path: '/metadata/dc.description', + value: 'testDescription' + }]; + expect(groupsDataServiceStub.patch).toHaveBeenCalledWith(expected, operations); + }); + + it('should edit with name operations', () => { + component.groupDescription.value = null; + component.onSubmit(); + fixture.detectChanges(); + const operations = [{ + op: 'replace', + path: '/name', + value: 'newGroupName' + }]; + expect(groupsDataServiceStub.patch).toHaveBeenCalledWith(expected, operations); + }); + it('should emit the existing group using the correct new values', waitForAsync(() => { fixture.whenStable().then(() => { expect(component.submitForm.emit).toHaveBeenCalledWith(expected2); diff --git a/src/app/access-control/group-registry/group-form/group-form.component.ts b/src/app/access-control/group-registry/group-form/group-form.component.ts index 584b28ba1e..4302d126ea 100644 --- a/src/app/access-control/group-registry/group-form/group-form.component.ts +++ b/src/app/access-control/group-registry/group-form/group-form.component.ts @@ -346,8 +346,8 @@ export class GroupFormComponent implements OnInit, OnDestroy { if (hasValue(this.groupDescription.value)) { operations = [...operations, { - op: 'replace', - path: '/metadata/dc.description/0/value', + op: 'add', + path: '/metadata/dc.description', value: this.groupDescription.value }]; } diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts index f84db92445..4b49cb4825 100644 --- a/src/app/app.reducer.ts +++ b/src/app/app.reducer.ts @@ -42,10 +42,6 @@ import { filterReducer, SearchFiltersState } from './shared/search/search-filters/search-filter/search-filter.reducer'; -import { - sidebarFilterReducer, - SidebarFiltersState -} from './shared/sidebar/filter/sidebar-filter.reducer'; import { sidebarReducer, SidebarState } from './shared/sidebar/sidebar.reducer'; import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer'; import { ThemeState, themeReducer } from './shared/theme-support/theme.reducer'; @@ -59,7 +55,6 @@ export interface AppState { metadataRegistry: MetadataRegistryState; notifications: NotificationsState; sidebar: SidebarState; - sidebarFilter: SidebarFiltersState; searchFilter: SearchFiltersState; truncatable: TruncatablesState; cssVariables: CSSVariablesState; @@ -81,7 +76,6 @@ export const appReducers: ActionReducerMap = { metadataRegistry: metadataRegistryReducer, notifications: notificationsReducer, sidebar: sidebarReducer, - sidebarFilter: sidebarFilterReducer, searchFilter: filterReducer, truncatable: truncatableReducer, cssVariables: cssVariablesReducer, diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.html b/src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.html similarity index 100% rename from src/app/shared/bitstream-download-page/bitstream-download-page.component.html rename to src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.html diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts b/src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.spec.ts similarity index 98% rename from src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts rename to src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.spec.ts index 4100653e0f..e84b254eae 100644 --- a/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts +++ b/src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.spec.ts @@ -6,7 +6,7 @@ import { Bitstream } from '../../core/shared/bitstream.model'; import { BitstreamDownloadPageComponent } from './bitstream-download-page.component'; import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; import { HardRedirectService } from '../../core/services/hard-redirect.service'; -import { createSuccessfulRemoteDataObject } from '../remote-data.utils'; +import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; import { ActivatedRoute, Router } from '@angular/router'; import { getForbiddenRoute } from '../../app-routing-paths'; import { TranslateModule } from '@ngx-translate/core'; diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts b/src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.ts similarity index 98% rename from src/app/shared/bitstream-download-page/bitstream-download-page.component.ts rename to src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.ts index 9dd8c7c723..51ec762ec3 100644 --- a/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts +++ b/src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { filter, map, switchMap, take } from 'rxjs/operators'; import { ActivatedRoute, Router } from '@angular/router'; -import { hasValue, isNotEmpty } from '../empty.util'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { getRemoteDataPayload} from '../../core/shared/operators'; import { Bitstream } from '../../core/shared/bitstream.model'; import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; diff --git a/src/app/bitstream-page/bitstream-page-routing.module.ts b/src/app/bitstream-page/bitstream-page-routing.module.ts index 0bdda29ddf..c2abe511a4 100644 --- a/src/app/bitstream-page/bitstream-page-routing.module.ts +++ b/src/app/bitstream-page/bitstream-page-routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule } from '@angular/router'; import { EditBitstreamPageComponent } from './edit-bitstream-page/edit-bitstream-page.component'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { BitstreamPageResolver } from './bitstream-page.resolver'; -import { BitstreamDownloadPageComponent } from '../shared/bitstream-download-page/bitstream-download-page.component'; +import { BitstreamDownloadPageComponent } from './bitstream-download-page/bitstream-download-page.component'; import { ResourcePolicyTargetResolver } from '../shared/resource-policies/resolvers/resource-policy-target.resolver'; import { ResourcePolicyCreateComponent } from '../shared/resource-policies/create/resource-policy-create.component'; import { ResourcePolicyResolver } from '../shared/resource-policies/resolvers/resource-policy.resolver'; diff --git a/src/app/bitstream-page/bitstream-page.module.ts b/src/app/bitstream-page/bitstream-page.module.ts index d168a06db2..992f714bf9 100644 --- a/src/app/bitstream-page/bitstream-page.module.ts +++ b/src/app/bitstream-page/bitstream-page.module.ts @@ -6,6 +6,7 @@ import { BitstreamPageRoutingModule } from './bitstream-page-routing.module'; import { BitstreamAuthorizationsComponent } from './bitstream-authorizations/bitstream-authorizations.component'; import { FormModule } from '../shared/form/form.module'; import { ResourcePoliciesModule } from '../shared/resource-policies/resource-policies.module'; +import { BitstreamDownloadPageComponent } from './bitstream-download-page/bitstream-download-page.component'; /** * This module handles all components that are necessary for Bitstream related pages @@ -20,7 +21,8 @@ import { ResourcePoliciesModule } from '../shared/resource-policies/resource-pol ], declarations: [ BitstreamAuthorizationsComponent, - EditBitstreamPageComponent + EditBitstreamPageComponent, + BitstreamDownloadPageComponent, ] }) export class BitstreamPageModule { diff --git a/src/app/breadcrumbs/breadcrumbs.component.scss b/src/app/breadcrumbs/breadcrumbs.component.scss index 412dca87db..a4d83b82ea 100644 --- a/src/app/breadcrumbs/breadcrumbs.component.scss +++ b/src/app/breadcrumbs/breadcrumbs.component.scss @@ -23,11 +23,14 @@ li.breadcrumb-item { } } -li.breadcrumb-item > a { - color: var(--ds-breadcrumb-link-color) !important; +li.breadcrumb-item { + a { + color: var(--ds-breadcrumb-link-color); + } } + li.breadcrumb-item.active { - color: var(--ds-breadcrumb-link-active-color) !important; + color: var(--ds-breadcrumb-link-active-color); } .breadcrumb-item+ .breadcrumb-item::before { diff --git a/src/app/community-list-page/community-list-page.module.ts b/src/app/community-list-page/community-list-page.module.ts index 18c28068be..15946b2e89 100644 --- a/src/app/community-list-page/community-list-page.module.ts +++ b/src/app/community-list-page/community-list-page.module.ts @@ -6,6 +6,7 @@ import { CommunityListPageRoutingModule } from './community-list-page.routing.mo import { CommunityListComponent } from './community-list/community-list.component'; import { ThemedCommunityListPageComponent } from './themed-community-list-page.component'; import { ThemedCommunityListComponent } from './community-list/themed-community-list.component'; +import { CdkTreeModule } from '@angular/cdk/tree'; const DECLARATIONS = [ @@ -21,13 +22,15 @@ const DECLARATIONS = [ imports: [ CommonModule, SharedModule, - CommunityListPageRoutingModule + CommunityListPageRoutingModule, + CdkTreeModule, ], declarations: [ ...DECLARATIONS ], exports: [ ...DECLARATIONS, + CdkTreeModule, ], }) export class CommunityListPageModule { diff --git a/src/app/core/auth/auth.interceptor.ts b/src/app/core/auth/auth.interceptor.ts index e55d0c0ff9..672879f436 100644 --- a/src/app/core/auth/auth.interceptor.ts +++ b/src/app/core/auth/auth.interceptor.ts @@ -196,7 +196,24 @@ export class AuthInterceptor implements HttpInterceptor { authStatus.token = new AuthTokenInfo(accessToken); } else { authStatus.authenticated = false; - authStatus.error = isNotEmpty(error) ? ((typeof error === 'string') ? JSON.parse(error) : error) : null; + if (isNotEmpty(error)) { + if (typeof error === 'string') { + try { + authStatus.error = JSON.parse(error); + } catch (e) { + console.error('Unknown auth error "', error, '" caused ', e); + authStatus.error = { + error: 'Unknown', + message: 'Unknown auth error', + status: 500, + timestamp: Date.now(), + path: '' + }; + } + } else { + authStatus.error = error; + } + } } return authStatus; } diff --git a/src/app/core/breadcrumbs/dso-name.service.ts b/src/app/core/breadcrumbs/dso-name.service.ts index d56f4a00eb..64f37baa65 100644 --- a/src/app/core/breadcrumbs/dso-name.service.ts +++ b/src/app/core/breadcrumbs/dso-name.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import { hasValue, isEmpty } from '../../shared/empty.util'; import { DSpaceObject } from '../shared/dspace-object.model'; import { TranslateService } from '@ngx-translate/core'; +import { Metadata } from '../shared/metadata.utils'; /** * Returns a name for a {@link DSpaceObject} based @@ -67,4 +68,45 @@ export class DSONameService { return name; } + /** + * Gets the Hit highlight + * + * @param object + * @param dso + * + * @returns {string} html embedded hit highlight. + */ + getHitHighlights(object: any, dso: DSpaceObject): string { + const types = dso.getRenderTypes(); + const entityType = types + .filter((type) => typeof type === 'string') + .find((type: string) => (['Person', 'OrgUnit']).includes(type)) as string; + if (entityType === 'Person') { + const familyName = this.firstMetadataValue(object, dso, 'person.familyName'); + const givenName = this.firstMetadataValue(object, dso, 'person.givenName'); + if (isEmpty(familyName) && isEmpty(givenName)) { + return this.firstMetadataValue(object, dso, 'dc.title') || dso.name; + } else if (isEmpty(familyName) || isEmpty(givenName)) { + return familyName || givenName; + } + return `${familyName}, ${givenName}`; + } else if (entityType === 'OrgUnit') { + return this.firstMetadataValue(object, dso, 'organization.legalName'); + } + return this.firstMetadataValue(object, dso, 'dc.title') || dso.name || this.translateService.instant('dso.name.untitled'); + } + + /** + * Gets the first matching metadata string value from hitHighlights or dso metadata, preferring hitHighlights. + * + * @param object + * @param dso + * @param {string|string[]} keyOrKeys The metadata key(s) in scope. Wildcards are supported; see [[Metadata]]. + * + * @returns {string} the first matching string value, or `undefined`. + */ + firstMetadataValue(object: any, dso: DSpaceObject, keyOrKeys: string | string[]): string { + return Metadata.firstValue([object.hitHighlights, dso.metadata], keyOrKeys); + } + } diff --git a/src/app/core/shared/media-viewer-item.model.ts b/src/app/core/shared/media-viewer-item.model.ts index cd3a31bd0b..1cf4948408 100644 --- a/src/app/core/shared/media-viewer-item.model.ts +++ b/src/app/core/shared/media-viewer-item.model.ts @@ -14,6 +14,11 @@ export class MediaViewerItem { */ format: string; + /** + * Incoming Bitsream format mime type + */ + mimetype: string; + /** * Incoming Bitsream thumbnail */ diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts index 7a38a02cf4..954f7bc591 100644 --- a/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component.ts @@ -61,7 +61,7 @@ export class OrgUnitSearchResultListSubmissionElementComponent extends SearchRes this.useNameVariants = this.context === Context.EntitySearchModalWithNameVariants; if (this.useNameVariants) { - const defaultValue = this.dsoTitle; + const defaultValue = this.dso ? this.dsoNameService.getName(this.dso) : undefined; const alternatives = this.allMetadataValues(this.alternativeField); this.allSuggestions = [defaultValue, ...alternatives]; diff --git a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts index 7d761c42dd..305407f8d2 100644 --- a/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts +++ b/src/app/entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component.ts @@ -55,7 +55,7 @@ export class PersonSearchResultListSubmissionElementComponent extends SearchResu ngOnInit() { super.ngOnInit(); - const defaultValue = this.dsoTitle; + const defaultValue = this.dso ? this.dsoNameService.getName(this.dso) : undefined; const alternatives = this.allMetadataValues(this.alternativeField); this.allSuggestions = [defaultValue, ...alternatives]; diff --git a/src/app/shared/item/item-alerts/item-alerts.component.html b/src/app/item-page/alerts/item-alerts.component.html similarity index 100% rename from src/app/shared/item/item-alerts/item-alerts.component.html rename to src/app/item-page/alerts/item-alerts.component.html diff --git a/src/app/shared/item/item-alerts/item-alerts.component.scss b/src/app/item-page/alerts/item-alerts.component.scss similarity index 100% rename from src/app/shared/item/item-alerts/item-alerts.component.scss rename to src/app/item-page/alerts/item-alerts.component.scss diff --git a/src/app/shared/item/item-alerts/item-alerts.component.spec.ts b/src/app/item-page/alerts/item-alerts.component.spec.ts similarity index 97% rename from src/app/shared/item/item-alerts/item-alerts.component.spec.ts rename to src/app/item-page/alerts/item-alerts.component.spec.ts index fed81199fd..a933eb6a58 100644 --- a/src/app/shared/item/item-alerts/item-alerts.component.spec.ts +++ b/src/app/item-page/alerts/item-alerts.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ItemAlertsComponent } from './item-alerts.component'; import { TranslateModule } from '@ngx-translate/core'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { Item } from '../../../core/shared/item.model'; +import { Item } from '../../core/shared/item.model'; import { By } from '@angular/platform-browser'; describe('ItemAlertsComponent', () => { diff --git a/src/app/shared/item/item-alerts/item-alerts.component.ts b/src/app/item-page/alerts/item-alerts.component.ts similarity index 80% rename from src/app/shared/item/item-alerts/item-alerts.component.ts rename to src/app/item-page/alerts/item-alerts.component.ts index 4c2d60d24c..d7a84db015 100644 --- a/src/app/shared/item/item-alerts/item-alerts.component.ts +++ b/src/app/item-page/alerts/item-alerts.component.ts @@ -1,6 +1,6 @@ import { Component, Input } from '@angular/core'; -import { Item } from '../../../core/shared/item.model'; -import { AlertType } from '../../alert/aletr-type'; +import { Item } from '../../core/shared/item.model'; +import { AlertType } from '../../shared/alert/aletr-type'; @Component({ selector: 'ds-item-alerts', diff --git a/src/app/shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component.html b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.html similarity index 100% rename from src/app/shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component.html rename to src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.html diff --git a/src/app/shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component.spec.ts b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.spec.ts similarity index 90% rename from src/app/shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component.spec.ts rename to src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.spec.ts index cc44ef8587..cbfbdf361f 100644 --- a/src/app/shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component.spec.ts +++ b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.spec.ts @@ -1,30 +1,30 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { AuthService } from '../../core/auth/auth.service'; +import { AuthService } from '../../../core/auth/auth.service'; import { of as observableOf } from 'rxjs'; -import { Bitstream } from '../../core/shared/bitstream.model'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; +import { Bitstream } from '../../../core/shared/bitstream.model'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ -} from '../remote-data.utils'; +} from '../../../shared/remote-data.utils'; import { ActivatedRoute, Router } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { BitstreamRequestACopyPageComponent } from './bitstream-request-a-copy-page.component'; import { By } from '@angular/platform-browser'; -import { RouterStub } from '../testing/router.stub'; +import { RouterStub } from '../../../shared/testing/router.stub'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { NotificationsServiceStub } from '../testing/notifications-service.stub'; -import { ItemRequestDataService } from '../../core/data/item-request-data.service'; -import { NotificationsService } from '../notifications/notifications.service'; -import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; -import { DSONameServiceMock } from '../mocks/dso-name.service.mock'; -import { Item } from '../../core/shared/item.model'; -import { EPerson } from '../../core/eperson/models/eperson.model'; -import { ItemRequest } from '../../core/shared/item-request.model'; +import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; +import { ItemRequestDataService } from '../../../core/data/item-request-data.service'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; +import { DSONameServiceMock } from '../../../shared/mocks/dso-name.service.mock'; +import { Item } from '../../../core/shared/item.model'; +import { EPerson } from '../../../core/eperson/models/eperson.model'; +import { ItemRequest } from '../../../core/shared/item-request.model'; import { Location } from '@angular/common'; -import { BitstreamDataService } from '../../core/data/bitstream-data.service'; +import { BitstreamDataService } from '../../../core/data/bitstream-data.service'; describe('BitstreamRequestACopyPageComponent', () => { diff --git a/src/app/shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component.ts b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.ts similarity index 85% rename from src/app/shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component.ts rename to src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.ts index 511079a701..59819a4a66 100644 --- a/src/app/shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component.ts +++ b/src/app/item-page/bitstreams/request-a-copy/bitstream-request-a-copy-page.component.ts @@ -1,25 +1,25 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { filter, map, switchMap, take } from 'rxjs/operators'; import { ActivatedRoute, Router } from '@angular/router'; -import { hasValue, isNotEmpty } from '../empty.util'; -import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; -import { Bitstream } from '../../core/shared/bitstream.model'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { FeatureID } from '../../core/data/feature-authorization/feature-id'; -import { AuthService } from '../../core/auth/auth.service'; +import { hasValue, isNotEmpty } from '../../../shared/empty.util'; +import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; +import { Bitstream } from '../../../core/shared/bitstream.model'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; +import { AuthService } from '../../../core/auth/auth.service'; import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; -import { getBitstreamDownloadRoute, getForbiddenRoute } from '../../app-routing-paths'; +import { getBitstreamDownloadRoute, getForbiddenRoute } from '../../../app-routing-paths'; import { TranslateService } from '@ngx-translate/core'; -import { EPerson } from '../../core/eperson/models/eperson.model'; +import { EPerson } from '../../../core/eperson/models/eperson.model'; import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; -import { ItemRequestDataService } from '../../core/data/item-request-data.service'; -import { ItemRequest } from '../../core/shared/item-request.model'; -import { Item } from '../../core/shared/item.model'; -import { NotificationsService } from '../notifications/notifications.service'; -import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { ItemRequestDataService } from '../../../core/data/item-request-data.service'; +import { ItemRequest } from '../../../core/shared/item-request.model'; +import { Item } from '../../../core/shared/item.model'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; import { Location } from '@angular/common'; -import { BitstreamDataService } from '../../core/data/bitstream-data.service'; -import { getItemPageRoute } from '../../item-page/item-page-routing-paths'; +import { BitstreamDataService } from '../../../core/data/bitstream-data.service'; +import { getItemPageRoute } from '../../item-page-routing-paths'; @Component({ selector: 'ds-bitstream-request-a-copy-page', diff --git a/src/app/item-page/edit-item-page/edit-item-page.module.ts b/src/app/item-page/edit-item-page/edit-item-page.module.ts index 3ed741bc1a..fafbae0bd4 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.module.ts @@ -36,6 +36,7 @@ import { ItemVersionHistoryComponent } from './item-version-history/item-version import { ItemAuthorizationsComponent } from './item-authorizations/item-authorizations.component'; import { ObjectValuesPipe } from '../../shared/utils/object-values-pipe'; import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-policies.module'; +import { ItemVersionsModule } from '../versions/item-versions.module'; /** @@ -50,7 +51,8 @@ import { ResourcePoliciesModule } from '../../shared/resource-policies/resource- SearchPageModule, DragDropModule, ResourcePoliciesModule, - NgbModule + NgbModule, + ItemVersionsModule, ], declarations: [ EditItemPageComponent, diff --git a/src/app/item-page/full/field-components/file-section/full-file-section.component.spec.ts b/src/app/item-page/full/field-components/file-section/full-file-section.component.spec.ts index 6f0e97513f..f8a8a83f29 100644 --- a/src/app/item-page/full/field-components/file-section/full-file-section.component.spec.ts +++ b/src/app/item-page/full/field-components/file-section/full-file-section.component.spec.ts @@ -7,7 +7,7 @@ import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.m import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { VarDirective } from '../../../../shared/utils/var.directive'; import { FileSizePipe } from '../../../../shared/utils/file-size-pipe'; -import { MetadataFieldWrapperComponent } from '../../../field-components/metadata-field-wrapper/metadata-field-wrapper.component'; +import { MetadataFieldWrapperComponent } from '../../../../shared/metadata-field-wrapper/metadata-field-wrapper.component'; import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { Bitstream } from '../../../../core/shared/bitstream.model'; diff --git a/src/app/item-page/item-page-routing.module.ts b/src/app/item-page/item-page-routing.module.ts index ac14ca8402..b713b8ed78 100644 --- a/src/app/item-page/item-page-routing.module.ts +++ b/src/app/item-page/item-page-routing.module.ts @@ -14,9 +14,7 @@ import { ThemedItemPageComponent } from './simple/themed-item-page.component'; import { ThemedFullItemPageComponent } from './full/themed-full-item-page.component'; import { MenuItemType } from '../shared/menu/menu-item-type.model'; import { VersionPageComponent } from './version-page/version-page/version-page.component'; -import { - BitstreamRequestACopyPageComponent -} from '../shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component'; +import { BitstreamRequestACopyPageComponent } from './bitstreams/request-a-copy/bitstream-request-a-copy-page.component'; import { REQUEST_COPY_MODULE_PATH } from '../app-routing-paths'; import { OrcidPageComponent } from './orcid-page/orcid-page.component'; import { OrcidPageGuard } from './orcid-page/orcid-page.guard'; diff --git a/src/app/item-page/item-page.module.ts b/src/app/item-page/item-page.module.ts index de9f2f60c5..39c2580921 100644 --- a/src/app/item-page/item-page.module.ts +++ b/src/app/item-page/item-page.module.ts @@ -47,6 +47,11 @@ import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; import { OrcidSyncSettingsComponent } from './orcid-page/orcid-sync-settings/orcid-sync-settings.component'; import { OrcidQueueComponent } from './orcid-page/orcid-queue/orcid-queue.component'; import { UploadModule } from '../shared/upload/upload.module'; +import { ItemAlertsComponent } from './alerts/item-alerts.component'; +import { ItemVersionsModule } from './versions/item-versions.module'; +import { BitstreamRequestACopyPageComponent } from './bitstreams/request-a-copy/bitstream-request-a-copy-page.component'; +import { FileSectionComponent } from './simple/field-components/file-section/file-section.component'; +import { ItemSharedModule } from './item-shared.module'; const ENTRY_COMPONENTS = [ @@ -56,6 +61,7 @@ const ENTRY_COMPONENTS = [ ]; const DECLARATIONS = [ + FileSectionComponent, ThemedFileSectionComponent, ItemPageComponent, ThemedItemPageComponent, @@ -82,7 +88,10 @@ const DECLARATIONS = [ OrcidPageComponent, OrcidAuthComponent, OrcidSyncSettingsComponent, - OrcidQueueComponent + OrcidQueueComponent, + ItemAlertsComponent, + VersionedItemComponent, + BitstreamRequestACopyPageComponent, ]; @NgModule({ @@ -91,6 +100,8 @@ const DECLARATIONS = [ SharedModule.withEntryComponents(), ItemPageRoutingModule, EditItemPageModule, + ItemVersionsModule, + ItemSharedModule, StatisticsModule.forRoot(), JournalEntitiesModule.withEntryComponents(), ResearchEntitiesModule.withEntryComponents(), @@ -100,10 +111,10 @@ const DECLARATIONS = [ ], declarations: [ ...DECLARATIONS, - VersionedItemComponent + ], exports: [ - ...DECLARATIONS + ...DECLARATIONS, ] }) export class ItemPageModule { diff --git a/src/app/item-page/item-shared.module.ts b/src/app/item-page/item-shared.module.ts index b191b6c4b3..c558b11692 100644 --- a/src/app/item-page/item-shared.module.ts +++ b/src/app/item-page/item-shared.module.ts @@ -7,10 +7,32 @@ import { TranslateModule } from '@ngx-translate/core'; import { DYNAMIC_FORM_CONTROL_MAP_FN } from '@ng-dynamic-forms/core'; import { dsDynamicFormControlMapFn } from '../shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component'; import { TabbedRelatedEntitiesSearchComponent } from './simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component'; +import { ItemVersionsDeleteModalComponent } from './versions/item-versions-delete-modal/item-versions-delete-modal.component'; +import { ItemVersionsSummaryModalComponent } from './versions/item-versions-summary-modal/item-versions-summary-modal.component'; +import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.component'; +import { DsoPageVersionButtonComponent } from '../shared/dso-page/dso-page-version-button/dso-page-version-button.component'; +import { PersonPageClaimButtonComponent } from '../shared/dso-page/person-page-claim-button/person-page-claim-button.component'; +import { GenericItemPageFieldComponent } from './simple/field-components/specific-field/generic/generic-item-page-field.component'; +import { MetadataRepresentationListComponent } from './simple/metadata-representation-list/metadata-representation-list.component'; +import { RelatedItemsComponent } from './simple/related-items/related-items-component'; +import { DsoPageOrcidButtonComponent } from '../shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component'; + +const ENTRY_COMPONENTS = [ + ItemVersionsDeleteModalComponent, + ItemVersionsSummaryModalComponent, +]; const COMPONENTS = [ + ...ENTRY_COMPONENTS, RelatedEntitiesSearchComponent, - TabbedRelatedEntitiesSearchComponent + TabbedRelatedEntitiesSearchComponent, + MetadataValuesComponent, + DsoPageVersionButtonComponent, + PersonPageClaimButtonComponent, + GenericItemPageFieldComponent, + MetadataRepresentationListComponent, + RelatedItemsComponent, + DsoPageOrcidButtonComponent ]; @NgModule({ @@ -30,7 +52,8 @@ const COMPONENTS = [ { provide: DYNAMIC_FORM_CONTROL_MAP_FN, useValue: dsDynamicFormControlMapFn - } + }, + ...ENTRY_COMPONENTS, ] }) export class ItemSharedModule { } diff --git a/src/app/item-page/media-viewer/media-viewer-video/caption-info.ts b/src/app/item-page/media-viewer/media-viewer-video/caption-info.ts new file mode 100644 index 0000000000..43996d096d --- /dev/null +++ b/src/app/item-page/media-viewer/media-viewer-video/caption-info.ts @@ -0,0 +1,11 @@ +/* + The class is designed to host information related to Video Captioning support + and used in HTML 5 video track + src: source vtt file + srclang: two letter language code + langLabel: language label + */ +export class CaptionInfo { + constructor(public src: string, public srclang: string, public langLabel: string ) { + } +} diff --git a/src/app/item-page/media-viewer/media-viewer-video/language-helper.ts b/src/app/item-page/media-viewer/media-viewer-video/language-helper.ts new file mode 100644 index 0000000000..b27ab9983f --- /dev/null +++ b/src/app/item-page/media-viewer/media-viewer-video/language-helper.ts @@ -0,0 +1,190 @@ +export const languageHelper = { + ab: 'Abkhazian', + aa: 'Afar', + af: 'Afrikaans', + ak: 'Akan', + sq: 'Albanian', + am: 'Amharic', + ar: 'Arabic', + an: 'Aragonese', + hy: 'Armenian', + as: 'Assamese', + av: 'Avaric', + ae: 'Avestan', + ay: 'Aymara', + az: 'Azerbaijani', + bm: 'Bambara', + ba: 'Bashkir', + eu: 'Basque', + be: 'Belarusian', + bn: 'Bengali (Bangla)', + bh: 'Bihari', + bi: 'Bislama', + bs: 'Bosnian', + br: 'Breton', + bg: 'Bulgarian', + my: 'Burmese', + ca: 'Catalan', + ch: 'Chamorro', + ce: 'Chechen', + ny: 'Chichewa, Chewa, Nyanja', + zh: 'Chinese', + cv: 'Chuvash', + kw: 'Cornish', + co: 'Corsican', + cr: 'Cree', + hr: 'Croatian', + cs: 'Czech', + da: 'Danish', + dv: 'Divehi, Dhivehi, Maldivian', + nl: 'Dutch', + dz: 'Dzongkha', + en: 'English', + eo: 'Esperanto', + et: 'Estonian', + ee: 'Ewe', + fo: 'Faroese', + fj: 'Fijian', + fi: 'Finnish', + fr: 'French', + ff: 'Fula, Fulah, Pulaar, Pular', + gl: 'Galician', + gd: 'Gaelic (Scottish)', + gv: 'Gaelic (Manx)', + ka: 'Georgian', + de: 'German', + el: 'Greek', + gn: 'Guarani', + gu: 'Gujarati', + ht: 'Haitian Creole', + ha: 'Hausa', + he: 'Hebrew', + hz: 'Herero', + hi: 'Hindi', + ho: 'Hiri Motu', + hu: 'Hungarian', + is: 'Icelandic', + io: 'Ido', + ig: 'Igbo', + in: 'Indonesian', + ia: 'Interlingua', + ie: 'Interlingue', + iu: 'Inuktitut', + ik: 'Inupiak', + ga: 'Irish', + it: 'Italian', + ja: 'Japanese', + jv: 'Javanese', + kl: 'Kalaallisut, Greenlandic', + kn: 'Kannada', + kr: 'Kanuri', + ks: 'Kashmiri', + kk: 'Kazakh', + km: 'Khmer', + ki: 'Kikuyu', + rw: 'Kinyarwanda (Rwanda)', + rn: 'Kirundi', + ky: 'Kyrgyz', + kv: 'Komi', + kg: 'Kongo', + ko: 'Korean', + ku: 'Kurdish', + kj: 'Kwanyama', + lo: 'Lao', + la: 'Latin', + lv: 'Latvian (Lettish)', + li: 'Limburgish ( Limburger)', + ln: 'Lingala', + lt: 'Lithuanian', + lu: 'Luga-Katanga', + lg: 'Luganda, Ganda', + lb: 'Luxembourgish', + mk: 'Macedonian', + mg: 'Malagasy', + ms: 'Malay', + ml: 'Malayalam', + mt: 'Maltese', + mi: 'Maori', + mr: 'Marathi', + mh: 'Marshallese', + mo: 'Moldavian', + mn: 'Mongolian', + na: 'Nauru', + nv: 'Navajo', + ng: 'Ndonga', + nd: 'Northern Ndebele', + ne: 'Nepali', + no: 'Norwegian', + nb: 'Norwegian bokmål', + nn: 'Norwegian nynorsk', + oc: 'Occitan', + oj: 'Ojibwe', + cu: 'Old Church Slavonic, Old Bulgarian', + or: 'Oriya', + om: 'Oromo (Afaan Oromo)', + os: 'Ossetian', + pi: 'Pāli', + ps: 'Pashto, Pushto', + fa: 'Persian (Farsi)', + pl: 'Polish', + pt: 'Portuguese', + pa: 'Punjabi (Eastern)', + qu: 'Quechua', + rm: 'Romansh', + ro: 'Romanian', + ru: 'Russian', + se: 'Sami', + sm: 'Samoan', + sg: 'Sango', + sa: 'Sanskrit', + sr: 'Serbian', + sh: 'Serbo-Croatian', + st: 'Sesotho', + tn: 'Setswana', + sn: 'Shona', + ii: 'Sichuan Yi, Nuosu', + sd: 'Sindhi', + si: 'Sinhalese', + ss: 'Siswati (Swati)', + sk: 'Slovak', + sl: 'Slovenian', + so: 'Somali', + nr: 'Southern Ndebele', + es: 'Spanish', + su: 'Sundanese', + sw: 'Swahili (Kiswahili)', + sv: 'Swedish', + tl: 'Tagalog', + ty: 'Tahitian', + tg: 'Tajik', + ta: 'Tamil', + tt: 'Tatar', + te: 'Telugu', + th: 'Thai', + bo: 'Tibetan', + ti: 'Tigrinya', + to: 'Tonga', + ts: 'Tsonga', + tr: 'Turkish', + tk: 'Turkmen', + tw: 'Twi', + ug: 'Uyghur', + uk: 'Ukrainian', + ur: 'Urdu', + uz: 'Uzbek', + ve: 'Venda', + vi: 'Vietnamese', + vo: 'Volapük', + wa: 'Wallon', + cy: 'Welsh', + wo: 'Wolof', + fy: 'Western Frisian', + xh: 'Xhosa', + yi: 'Yiddish', + yo: 'Yoruba', + za: 'Zhuang, Chuang', + zu: 'Zulu' +}; + + + diff --git a/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.html b/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.html index a4493e36fc..0cc854b272 100644 --- a/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.html +++ b/src/app/item-page/media-viewer/media-viewer-video/media-viewer-video.component.html @@ -1,4 +1,5 @@ +> + + + + + + +
diff --git a/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.scss b/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.scss similarity index 100% rename from src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.scss rename to src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.scss diff --git a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.spec.ts b/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.spec.ts similarity index 88% rename from src/app/shared/log-in/methods/orcid/log-in-orcid.component.spec.ts rename to src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.spec.ts index 001f0a4959..de4f62eb9e 100644 --- a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.spec.ts +++ b/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.spec.ts @@ -14,18 +14,17 @@ import { AuthServiceStub } from '../../../testing/auth-service.stub'; import { storeModuleConfig } from '../../../../app.reducer'; import { AuthMethod } from '../../../../core/auth/models/auth.method'; import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; -import { LogInOrcidComponent } from './log-in-orcid.component'; +import { LogInExternalProviderComponent } from './log-in-external-provider.component'; import { NativeWindowService } from '../../../../core/services/window.service'; import { RouterStub } from '../../../testing/router.stub'; import { ActivatedRouteStub } from '../../../testing/active-router.stub'; import { NativeWindowMockFactory } from '../../../mocks/mock-native-window-ref'; import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; +describe('LogInExternalProviderComponent', () => { -describe('LogInOrcidComponent', () => { - - let component: LogInOrcidComponent; - let fixture: ComponentFixture; + let component: LogInExternalProviderComponent; + let fixture: ComponentFixture; let page: Page; let user: EPerson; let componentAsAny: any; @@ -66,7 +65,7 @@ describe('LogInOrcidComponent', () => { TranslateModule.forRoot() ], declarations: [ - LogInOrcidComponent + LogInExternalProviderComponent ], providers: [ { provide: AuthService, useClass: AuthServiceStub }, @@ -88,7 +87,7 @@ describe('LogInOrcidComponent', () => { beforeEach(() => { // create component and test fixture - fixture = TestBed.createComponent(LogInOrcidComponent); + fixture = TestBed.createComponent(LogInExternalProviderComponent); // get test component from the fixture component = fixture.componentInstance; @@ -109,7 +108,7 @@ describe('LogInOrcidComponent', () => { expect(componentAsAny.injectedAuthMethodModel.location).toBe(location); expect(componentAsAny._window.nativeWindow.location.href).toBe(currentUrl); - component.redirectToOrcid(); + component.redirectToExternalProvider(); expect(setHrefSpy).toHaveBeenCalledWith(currentUrl); @@ -124,7 +123,7 @@ describe('LogInOrcidComponent', () => { expect(componentAsAny.injectedAuthMethodModel.location).toBe(location); expect(componentAsAny._window.nativeWindow.location.href).toBe(currentUrl); - component.redirectToOrcid(); + component.redirectToExternalProvider(); expect(setHrefSpy).toHaveBeenCalledWith(currentUrl); @@ -143,7 +142,7 @@ class Page { public navigateSpy: jasmine.Spy; public passwordInput: HTMLInputElement; - constructor(private component: LogInOrcidComponent, private fixture: ComponentFixture) { + constructor(private component: LogInExternalProviderComponent, private fixture: ComponentFixture) { // use injector to get services const injector = fixture.debugElement.injector; const store = injector.get(Store); diff --git a/src/app/shared/log-in/methods/log-in-external-provider.component.ts b/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.ts similarity index 73% rename from src/app/shared/log-in/methods/log-in-external-provider.component.ts rename to src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.ts index 037fc40e90..f182968457 100644 --- a/src/app/shared/log-in/methods/log-in-external-provider.component.ts +++ b/src/app/shared/log-in/methods/log-in-external-provider/log-in-external-provider.component.ts @@ -4,22 +4,27 @@ import { Observable } from 'rxjs'; import { take } from 'rxjs/operators'; import { select, Store } from '@ngrx/store'; -import { AuthMethod } from '../../../core/auth/models/auth.method'; +import { AuthMethod } from '../../../../core/auth/models/auth.method'; -import { isAuthenticated, isAuthenticationLoading } from '../../../core/auth/selectors'; -import { NativeWindowRef, NativeWindowService } from '../../../core/services/window.service'; -import { isEmpty, isNotNull } from '../../empty.util'; -import { AuthService } from '../../../core/auth/auth.service'; -import { HardRedirectService } from '../../../core/services/hard-redirect.service'; -import { URLCombiner } from '../../../core/url-combiner/url-combiner'; -import { CoreState } from '../../../core/core-state.model'; +import { isAuthenticated, isAuthenticationLoading } from '../../../../core/auth/selectors'; +import { NativeWindowRef, NativeWindowService } from '../../../../core/services/window.service'; +import { isEmpty, isNotNull } from '../../../empty.util'; +import { AuthService } from '../../../../core/auth/auth.service'; +import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; +import { URLCombiner } from '../../../../core/url-combiner/url-combiner'; +import { CoreState } from '../../../../core/core-state.model'; +import { renderAuthMethodFor } from '../log-in.methods-decorator'; +import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; @Component({ selector: 'ds-log-in-external-provider', - template: '' - + templateUrl: './log-in-external-provider.component.html', + styleUrls: ['./log-in-external-provider.component.scss'] }) -export abstract class LogInExternalProviderComponent implements OnInit { +@renderAuthMethodFor(AuthMethodType.Oidc) +@renderAuthMethodFor(AuthMethodType.Shibboleth) +@renderAuthMethodFor(AuthMethodType.Orcid) +export class LogInExternalProviderComponent implements OnInit { /** * The authentication method data. @@ -107,4 +112,7 @@ export abstract class LogInExternalProviderComponent implements OnInit { } + getButtonLabel() { + return `login.form.${this.authMethod.authMethodType}`; + } } diff --git a/src/app/shared/log-in/methods/oidc/log-in-oidc.component.html b/src/app/shared/log-in/methods/oidc/log-in-oidc.component.html deleted file mode 100644 index 7e78834305..0000000000 --- a/src/app/shared/log-in/methods/oidc/log-in-oidc.component.html +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/src/app/shared/log-in/methods/oidc/log-in-oidc.component.spec.ts b/src/app/shared/log-in/methods/oidc/log-in-oidc.component.spec.ts deleted file mode 100644 index 078a58dd5a..0000000000 --- a/src/app/shared/log-in/methods/oidc/log-in-oidc.component.spec.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; - -import { provideMockStore } from '@ngrx/store/testing'; -import { Store, StoreModule } from '@ngrx/store'; -import { TranslateModule } from '@ngx-translate/core'; - -import { EPerson } from '../../../../core/eperson/models/eperson.model'; -import { EPersonMock } from '../../../testing/eperson.mock'; -import { authReducer } from '../../../../core/auth/auth.reducer'; -import { AuthService } from '../../../../core/auth/auth.service'; -import { AuthServiceStub } from '../../../testing/auth-service.stub'; -import { storeModuleConfig } from '../../../../app.reducer'; -import { AuthMethod } from '../../../../core/auth/models/auth.method'; -import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; -import { LogInOidcComponent } from './log-in-oidc.component'; -import { NativeWindowService } from '../../../../core/services/window.service'; -import { RouterStub } from '../../../testing/router.stub'; -import { ActivatedRouteStub } from '../../../testing/active-router.stub'; -import { NativeWindowMockFactory } from '../../../mocks/mock-native-window-ref'; -import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; - - -describe('LogInOidcComponent', () => { - - let component: LogInOidcComponent; - let fixture: ComponentFixture; - let page: Page; - let user: EPerson; - let componentAsAny: any; - let setHrefSpy; - let oidcBaseUrl; - let location; - let initialState: any; - let hardRedirectService: HardRedirectService; - - beforeEach(() => { - user = EPersonMock; - oidcBaseUrl = 'dspace-rest.test/oidc?redirectUrl='; - location = oidcBaseUrl + 'http://dspace-angular.test/home'; - - hardRedirectService = jasmine.createSpyObj('hardRedirectService', { - getCurrentRoute: {}, - redirect: {} - }); - - initialState = { - core: { - auth: { - authenticated: false, - loaded: false, - blocking: false, - loading: false, - authMethods: [] - } - } - }; - }); - - beforeEach(waitForAsync(() => { - // refine the test module by declaring the test component - TestBed.configureTestingModule({ - imports: [ - StoreModule.forRoot({ auth: authReducer }, storeModuleConfig), - TranslateModule.forRoot() - ], - declarations: [ - LogInOidcComponent - ], - providers: [ - { provide: AuthService, useClass: AuthServiceStub }, - { provide: 'authMethodProvider', useValue: new AuthMethod(AuthMethodType.Oidc, location) }, - { provide: 'isStandalonePage', useValue: true }, - { provide: NativeWindowService, useFactory: NativeWindowMockFactory }, - { provide: Router, useValue: new RouterStub() }, - { provide: ActivatedRoute, useValue: new ActivatedRouteStub() }, - { provide: HardRedirectService, useValue: hardRedirectService }, - provideMockStore({ initialState }), - ], - schemas: [ - CUSTOM_ELEMENTS_SCHEMA - ] - }) - .compileComponents(); - - })); - - beforeEach(() => { - // create component and test fixture - fixture = TestBed.createComponent(LogInOidcComponent); - - // get test component from the fixture - component = fixture.componentInstance; - componentAsAny = component; - - // create page - page = new Page(component, fixture); - setHrefSpy = spyOnProperty(componentAsAny._window.nativeWindow.location, 'href', 'set').and.callThrough(); - - }); - - it('should set the properly a new redirectUrl', () => { - const currentUrl = 'http://dspace-angular.test/collections/12345'; - componentAsAny._window.nativeWindow.location.href = currentUrl; - - fixture.detectChanges(); - - expect(componentAsAny.injectedAuthMethodModel.location).toBe(location); - expect(componentAsAny._window.nativeWindow.location.href).toBe(currentUrl); - - component.redirectToOidc(); - - expect(setHrefSpy).toHaveBeenCalledWith(currentUrl); - - }); - - it('should not set a new redirectUrl', () => { - const currentUrl = 'http://dspace-angular.test/home'; - componentAsAny._window.nativeWindow.location.href = currentUrl; - - fixture.detectChanges(); - - expect(componentAsAny.injectedAuthMethodModel.location).toBe(location); - expect(componentAsAny._window.nativeWindow.location.href).toBe(currentUrl); - - component.redirectToOidc(); - - expect(setHrefSpy).toHaveBeenCalledWith(currentUrl); - - }); - -}); - -/** - * I represent the DOM elements and attach spies. - * - * @class Page - */ -class Page { - - public emailInput: HTMLInputElement; - public navigateSpy: jasmine.Spy; - public passwordInput: HTMLInputElement; - - constructor(private component: LogInOidcComponent, private fixture: ComponentFixture) { - // use injector to get services - const injector = fixture.debugElement.injector; - const store = injector.get(Store); - - // add spies - this.navigateSpy = spyOn(store, 'dispatch'); - } - -} diff --git a/src/app/shared/log-in/methods/oidc/log-in-oidc.component.ts b/src/app/shared/log-in/methods/oidc/log-in-oidc.component.ts deleted file mode 100644 index 882996b207..0000000000 --- a/src/app/shared/log-in/methods/oidc/log-in-oidc.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, } from '@angular/core'; - -import { renderAuthMethodFor } from '../log-in.methods-decorator'; -import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; -import { LogInExternalProviderComponent } from '../log-in-external-provider.component'; - -@Component({ - selector: 'ds-log-in-oidc', - templateUrl: './log-in-oidc.component.html', -}) -@renderAuthMethodFor(AuthMethodType.Oidc) -export class LogInOidcComponent extends LogInExternalProviderComponent { - - /** - * Redirect to orcid authentication url - */ - redirectToOidc() { - this.redirectToExternalProvider(); - } - -} diff --git a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.html b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.html deleted file mode 100644 index 6f5453fd60..0000000000 --- a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.html +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts b/src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts deleted file mode 100644 index e0b1da3db5..0000000000 --- a/src/app/shared/log-in/methods/orcid/log-in-orcid.component.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Component, } from '@angular/core'; - -import { renderAuthMethodFor } from '../log-in.methods-decorator'; -import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; -import { LogInExternalProviderComponent } from '../log-in-external-provider.component'; - -@Component({ - selector: 'ds-log-in-orcid', - templateUrl: './log-in-orcid.component.html', -}) -@renderAuthMethodFor(AuthMethodType.Orcid) -export class LogInOrcidComponent extends LogInExternalProviderComponent { - - /** - * Redirect to orcid authentication url - */ - redirectToOrcid() { - this.redirectToExternalProvider(); - } - -} diff --git a/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.html b/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.html deleted file mode 100644 index 3a3b935cfa..0000000000 --- a/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.html +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.spec.ts b/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.spec.ts deleted file mode 100644 index 075d33d98e..0000000000 --- a/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.spec.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; - -import { provideMockStore } from '@ngrx/store/testing'; -import { Store, StoreModule } from '@ngrx/store'; -import { TranslateModule } from '@ngx-translate/core'; - -import { EPerson } from '../../../../core/eperson/models/eperson.model'; -import { EPersonMock } from '../../../testing/eperson.mock'; -import { authReducer } from '../../../../core/auth/auth.reducer'; -import { AuthService } from '../../../../core/auth/auth.service'; -import { AuthServiceStub } from '../../../testing/auth-service.stub'; -import { storeModuleConfig } from '../../../../app.reducer'; -import { AuthMethod } from '../../../../core/auth/models/auth.method'; -import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; -import { LogInShibbolethComponent } from './log-in-shibboleth.component'; -import { NativeWindowService } from '../../../../core/services/window.service'; -import { RouterStub } from '../../../testing/router.stub'; -import { ActivatedRouteStub } from '../../../testing/active-router.stub'; -import { NativeWindowMockFactory } from '../../../mocks/mock-native-window-ref'; -import { HardRedirectService } from '../../../../core/services/hard-redirect.service'; - - -describe('LogInShibbolethComponent', () => { - - let component: LogInShibbolethComponent; - let fixture: ComponentFixture; - let page: Page; - let user: EPerson; - let componentAsAny: any; - let setHrefSpy; - let shibbolethBaseUrl; - let location; - let initialState: any; - let hardRedirectService: HardRedirectService; - - beforeEach(() => { - user = EPersonMock; - shibbolethBaseUrl = 'dspace-rest.test/shibboleth?redirectUrl='; - location = shibbolethBaseUrl + 'http://dspace-angular.test/home'; - - hardRedirectService = jasmine.createSpyObj('hardRedirectService', { - getCurrentRoute: {}, - redirect: {} - }); - - initialState = { - core: { - auth: { - authenticated: false, - loaded: false, - blocking: false, - loading: false, - authMethods: [] - } - } - }; - }); - - beforeEach(waitForAsync(() => { - // refine the test module by declaring the test component - TestBed.configureTestingModule({ - imports: [ - StoreModule.forRoot({ auth: authReducer }, storeModuleConfig), - TranslateModule.forRoot() - ], - declarations: [ - LogInShibbolethComponent - ], - providers: [ - { provide: AuthService, useClass: AuthServiceStub }, - { provide: 'authMethodProvider', useValue: new AuthMethod(AuthMethodType.Shibboleth, location) }, - { provide: 'isStandalonePage', useValue: true }, - { provide: NativeWindowService, useFactory: NativeWindowMockFactory }, - { provide: Router, useValue: new RouterStub() }, - { provide: ActivatedRoute, useValue: new ActivatedRouteStub() }, - { provide: HardRedirectService, useValue: hardRedirectService }, - provideMockStore({ initialState }), - ], - schemas: [ - CUSTOM_ELEMENTS_SCHEMA - ] - }) - .compileComponents(); - - })); - - beforeEach(() => { - // create component and test fixture - fixture = TestBed.createComponent(LogInShibbolethComponent); - - // get test component from the fixture - component = fixture.componentInstance; - componentAsAny = component; - - // create page - page = new Page(component, fixture); - setHrefSpy = spyOnProperty(componentAsAny._window.nativeWindow.location, 'href', 'set').and.callThrough(); - - }); - - it('should set the properly a new redirectUrl', () => { - const currentUrl = 'http://dspace-angular.test/collections/12345'; - componentAsAny._window.nativeWindow.location.href = currentUrl; - - fixture.detectChanges(); - - expect(componentAsAny.injectedAuthMethodModel.location).toBe(location); - expect(componentAsAny._window.nativeWindow.location.href).toBe(currentUrl); - - component.redirectToShibboleth(); - - expect(setHrefSpy).toHaveBeenCalledWith(currentUrl); - - }); - - it('should not set a new redirectUrl', () => { - const currentUrl = 'http://dspace-angular.test/home'; - componentAsAny._window.nativeWindow.location.href = currentUrl; - - fixture.detectChanges(); - - expect(componentAsAny.injectedAuthMethodModel.location).toBe(location); - expect(componentAsAny._window.nativeWindow.location.href).toBe(currentUrl); - - component.redirectToShibboleth(); - - expect(setHrefSpy).toHaveBeenCalledWith(currentUrl); - - }); - -}); - -/** - * I represent the DOM elements and attach spies. - * - * @class Page - */ -class Page { - - public emailInput: HTMLInputElement; - public navigateSpy: jasmine.Spy; - public passwordInput: HTMLInputElement; - - constructor(private component: LogInShibbolethComponent, private fixture: ComponentFixture) { - // use injector to get services - const injector = fixture.debugElement.injector; - const store = injector.get(Store); - - // add spies - this.navigateSpy = spyOn(store, 'dispatch'); - } - -} diff --git a/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.ts b/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.ts deleted file mode 100644 index dcfb3ccfc3..0000000000 --- a/src/app/shared/log-in/methods/shibboleth/log-in-shibboleth.component.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Component, } from '@angular/core'; - -import { renderAuthMethodFor } from '../log-in.methods-decorator'; -import { AuthMethodType } from '../../../../core/auth/models/auth.method-type'; -import { LogInExternalProviderComponent } from '../log-in-external-provider.component'; - -@Component({ - selector: 'ds-log-in-shibboleth', - templateUrl: './log-in-shibboleth.component.html', - styleUrls: ['./log-in-shibboleth.component.scss'], - -}) -@renderAuthMethodFor(AuthMethodType.Shibboleth) -export class LogInShibbolethComponent extends LogInExternalProviderComponent { - - /** - * Redirect to shibboleth authentication url - */ - redirectToShibboleth() { - this.redirectToExternalProvider(); - } - -} diff --git a/src/app/item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.html b/src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.html similarity index 100% rename from src/app/item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.html rename to src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.html diff --git a/src/app/item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.scss b/src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.scss similarity index 100% rename from src/app/item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.scss rename to src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.scss diff --git a/src/app/item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.spec.ts b/src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.spec.ts similarity index 100% rename from src/app/item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.spec.ts rename to src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.spec.ts diff --git a/src/app/item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.ts b/src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.ts similarity index 100% rename from src/app/item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component.ts rename to src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component.ts diff --git a/src/app/shared/mocks/dso-name.service.mock.ts b/src/app/shared/mocks/dso-name.service.mock.ts index f4947cc860..cf3cf5466b 100644 --- a/src/app/shared/mocks/dso-name.service.mock.ts +++ b/src/app/shared/mocks/dso-name.service.mock.ts @@ -6,4 +6,23 @@ export class DSONameServiceMock { public getName(dso: DSpaceObject) { return UNDEFINED_NAME; } + + public getHitHighlights(object: any, dso: DSpaceObject) { + if (object.hitHighlights && object.hitHighlights['dc.title']) { + return object.hitHighlights['dc.title'][0].value; + } else if (object.hitHighlights && object.hitHighlights['organization.legalName']) { + return object.hitHighlights['organization.legalName'][0].value; + } else if (object.hitHighlights && (object.hitHighlights['person.familyName'] || object.hitHighlights['person.givenName'])) { + if (object.hitHighlights['person.familyName'] && object.hitHighlights['person.givenName']) { + return `${object.hitHighlights['person.familyName'][0].value}, ${object.hitHighlights['person.givenName'][0].value}`; + } + if (object.hitHighlights['person.familyName']) { + return `${object.hitHighlights['person.familyName'][0].value}`; + } + if (object.hitHighlights['person.givenName']) { + return `${object.hitHighlights['person.givenName'][0].value}`; + } + } + return UNDEFINED_NAME; + } } diff --git a/src/app/shared/mydspace-actions/mydspace-actions.module.ts b/src/app/shared/mydspace-actions/mydspace-actions.module.ts new file mode 100644 index 0000000000..68e3a8fb58 --- /dev/null +++ b/src/app/shared/mydspace-actions/mydspace-actions.module.ts @@ -0,0 +1,59 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '../shared.module'; +import { ClaimedTaskActionsApproveComponent } from './claimed-task/approve/claimed-task-actions-approve.component'; +import { ClaimedTaskActionsRejectComponent } from './claimed-task/reject/claimed-task-actions-reject.component'; +import { ClaimedTaskActionsReturnToPoolComponent } from './claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component'; +import { ClaimedTaskActionsEditMetadataComponent } from './claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component'; +import { ClaimedTaskActionsComponent } from './claimed-task/claimed-task-actions.component'; +import { ClaimedTaskActionsLoaderComponent } from './claimed-task/switcher/claimed-task-actions-loader.component'; +import { ItemActionsComponent } from './item/item-actions.component'; +import { PoolTaskActionsComponent } from './pool-task/pool-task-actions.component'; +import { WorkflowitemActionsComponent } from './workflowitem/workflowitem-actions.component'; +import { WorkspaceitemActionsComponent } from './workspaceitem/workspaceitem-actions.component'; + +const ENTRY_COMPONENTS = [ + ClaimedTaskActionsApproveComponent, + ClaimedTaskActionsRejectComponent, + ClaimedTaskActionsReturnToPoolComponent, + ClaimedTaskActionsEditMetadataComponent, +]; + +const DECLARATIONS = [ + ...ENTRY_COMPONENTS, + ClaimedTaskActionsComponent, + ClaimedTaskActionsLoaderComponent, + ItemActionsComponent, + PoolTaskActionsComponent, + WorkflowitemActionsComponent, + WorkspaceitemActionsComponent, +]; + +/** + * This module contains Item actions used in MyDSpace + */ +@NgModule({ + imports: [ + CommonModule, + SharedModule, + ], + declarations: [ + ...DECLARATIONS, + ], + providers: [ + ...ENTRY_COMPONENTS, + ], + exports: [ + ...DECLARATIONS, + ], +}) +export class MyDSpaceActionsModule { + +} diff --git a/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component.spec.ts b/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component.spec.ts index 44e6a44b70..59fc29424d 100644 --- a/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component.spec.ts +++ b/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component.spec.ts @@ -55,34 +55,34 @@ describe('MyDSpaceItemStatusComponent', () => { component.status = MyDspaceItemStatusType.VALIDATION; fixture.detectChanges(); expect(component.badgeContent).toBe(MyDspaceItemStatusType.VALIDATION); - expect(component.badgeClass).toBe('text-light badge badge-warning'); + expect(component.badgeClass).toBe('text-light badge badge-validation'); }); it('should init badge content and class', () => { component.status = MyDspaceItemStatusType.WAITING_CONTROLLER; fixture.detectChanges(); expect(component.badgeContent).toBe(MyDspaceItemStatusType.WAITING_CONTROLLER); - expect(component.badgeClass).toBe('text-light badge badge-info'); + expect(component.badgeClass).toBe('text-light badge badge-waiting-controller'); }); it('should init badge content and class', () => { component.status = MyDspaceItemStatusType.WORKSPACE; fixture.detectChanges(); expect(component.badgeContent).toBe(MyDspaceItemStatusType.WORKSPACE); - expect(component.badgeClass).toBe('text-light badge badge-primary'); + expect(component.badgeClass).toBe('text-light badge badge-workspace'); }); it('should init badge content and class', () => { component.status = MyDspaceItemStatusType.ARCHIVED; fixture.detectChanges(); expect(component.badgeContent).toBe(MyDspaceItemStatusType.ARCHIVED); - expect(component.badgeClass).toBe('text-light badge badge-success'); + expect(component.badgeClass).toBe('text-light badge badge-archived'); }); it('should init badge content and class', () => { component.status = MyDspaceItemStatusType.WORKFLOW; fixture.detectChanges(); expect(component.badgeContent).toBe(MyDspaceItemStatusType.WORKFLOW); - expect(component.badgeClass).toBe('text-light badge badge-info'); + expect(component.badgeClass).toBe('text-light badge badge-workflow'); }); }); diff --git a/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component.ts b/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component.ts index 917dd45acc..83b2656fbd 100644 --- a/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component.ts +++ b/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component.ts @@ -34,19 +34,19 @@ export class MyDSpaceItemStatusComponent implements OnInit { this.badgeClass = 'text-light badge '; switch (this.status) { case MyDspaceItemStatusType.VALIDATION: - this.badgeClass += 'badge-warning'; + this.badgeClass += 'badge-validation'; break; case MyDspaceItemStatusType.WAITING_CONTROLLER: - this.badgeClass += 'badge-info'; + this.badgeClass += 'badge-waiting-controller'; break; case MyDspaceItemStatusType.WORKSPACE: - this.badgeClass += 'badge-primary'; + this.badgeClass += 'badge-workspace'; break; case MyDspaceItemStatusType.ARCHIVED: - this.badgeClass += 'badge-success'; + this.badgeClass += 'badge-archived'; break; case MyDspaceItemStatusType.WORKFLOW: - this.badgeClass += 'badge-info'; + this.badgeClass += 'badge-workflow'; break; } } diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec.ts b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec.ts index 57b863a1b1..dc42b033d8 100644 --- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec.ts +++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.spec.ts @@ -28,13 +28,19 @@ import { ItemSearchResultGridElementComponent } from './item-search-result-grid- const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult(); mockItemWithMetadata.hitHighlights = {}; +const dcTitle = 'This is just another title'; mockItemWithMetadata.indexableObject = Object.assign(new Item(), { + hitHighlights: { + 'dc.title': [{ + value: dcTitle + }], + }, bundles: createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])), metadata: { 'dc.title': [ { language: 'en_US', - value: 'This is just another title' + value: dcTitle } ], 'dc.contributor.author': [ @@ -57,6 +63,114 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), { ] } }); +const mockPerson: ItemSearchResult = Object.assign(new ItemSearchResult(), { + hitHighlights: { + 'person.familyName': [{ + value: 'Michel' + }], + }, + indexableObject: + Object.assign(new Item(), { + bundles: createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])), + entityType: 'Person', + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ], + 'dc.contributor.author': [ + { + language: 'en_US', + value: 'Smith, Donald' + } + ], + 'dc.publisher': [ + { + language: 'en_US', + value: 'a publisher' + } + ], + 'dc.date.issued': [ + { + language: 'en_US', + value: '2015-06-26' + } + ], + 'dc.description.abstract': [ + { + language: 'en_US', + value: 'This is the abstract' + } + ], + 'dspace.entity.type': [ + { + value: 'Person' + } + ], + 'person.familyName': [ + { + value: 'Michel' + } + ] + } + }) +}); +const mockOrgUnit: ItemSearchResult = Object.assign(new ItemSearchResult(), { + hitHighlights: { + 'organization.legalName': [{ + value: 'Science' + }], + }, + indexableObject: + Object.assign(new Item(), { + bundles: createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])), + entityType: 'OrgUnit', + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ], + 'dc.contributor.author': [ + { + language: 'en_US', + value: 'Smith, Donald' + } + ], + 'dc.publisher': [ + { + language: 'en_US', + value: 'a publisher' + } + ], + 'dc.date.issued': [ + { + language: 'en_US', + value: '2015-06-26' + } + ], + 'dc.description.abstract': [ + { + language: 'en_US', + value: 'This is the abstract' + } + ], + 'organization.legalName': [ + { + value: 'Science' + } + ], + 'dspace.entity.type': [ + { + value: 'OrgUnit' + } + ] + } + }) +}); const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult(); mockItemWithoutMetadata.hitHighlights = {}; @@ -154,6 +268,41 @@ export function getEntityGridElementTestComponent(component, searchResultWithMet expect(itemAuthorField).toBeNull(); }); }); + + describe('When the item has title', () => { + beforeEach(() => { + comp.object = mockItemWithMetadata; + fixture.detectChanges(); + }); + it('should show highlighted title', () => { + const titleField = fixture.debugElement.query(By.css('.card-title')); + expect(titleField.nativeNode.innerHTML).toEqual(dcTitle); + }); + }); + + describe('When the item is Person and has title', () => { + beforeEach(() => { + comp.object = mockPerson; + fixture.detectChanges(); + }); + + it('should show highlighted title', () => { + const titleField = fixture.debugElement.query(By.css('.card-title')); + expect(titleField.nativeNode.innerHTML).toEqual('Michel'); + }); + }); + + describe('When the item is orgUnit and has title', () => { + beforeEach(() => { + comp.object = mockOrgUnit; + fixture.detectChanges(); + }); + + it('should show highlighted title', () => { + const titleField = fixture.debugElement.query(By.css('.card-title')); + expect(titleField.nativeNode.innerHTML).toEqual('Science'); + }); + }); }); }; } diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.ts b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.ts index b5f9c016e4..303e4681a2 100644 --- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.ts +++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.ts @@ -42,6 +42,6 @@ export class ItemSearchResultGridElementComponent extends SearchResultGridElemen ngOnInit(): void { super.ngOnInit(); this.itemPageRoute = getItemPageRoute(this.dso); - this.dsoTitle = this.dsoNameService.getName(this.dso); + this.dsoTitle = this.dsoNameService.getHitHighlights(this.object, this.dso); } } diff --git a/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.ts index 04f1e24d7b..6b40678ded 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.ts @@ -55,7 +55,7 @@ export class ItemListPreviewComponent implements OnInit { ngOnInit(): void { this.showThumbnails = this.appConfig.browseBy.showThumbnails; - this.dsoTitle = this.dsoNameService.getName(this.item); + this.dsoTitle = this.dsoNameService.getHitHighlights(this.object, this.item); } diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.spec.ts b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.spec.ts index d1e6c27ba4..7665b7d64e 100644 --- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.spec.ts @@ -13,8 +13,13 @@ import { APP_CONFIG } from '../../../../../../../config/app-config.interface'; let publicationListElementComponent: ItemSearchResultListElementComponent; let fixture: ComponentFixture; - +const dcTitle = 'This is just another title'; const mockItemWithMetadata: ItemSearchResult = Object.assign(new ItemSearchResult(), { + hitHighlights: { + 'dc.title': [{ + value: dcTitle + }], + }, indexableObject: Object.assign(new Item(), { bundles: observableOf({}), @@ -22,7 +27,7 @@ const mockItemWithMetadata: ItemSearchResult = Object.assign(new ItemSearchResul 'dc.title': [ { language: 'en_US', - value: 'This is just another title' + value: dcTitle } ], 'dc.contributor.author': [ @@ -59,7 +64,114 @@ const mockItemWithoutMetadata: ItemSearchResult = Object.assign(new ItemSearchRe metadata: {} }) }); - +const mockPerson: ItemSearchResult = Object.assign(new ItemSearchResult(), { + hitHighlights: { + 'person.familyName': [{ + value: 'Michel' + }], + }, + indexableObject: + Object.assign(new Item(), { + bundles: observableOf({}), + entityType: 'Person', + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ], + 'dc.contributor.author': [ + { + language: 'en_US', + value: 'Smith, Donald' + } + ], + 'dc.publisher': [ + { + language: 'en_US', + value: 'a publisher' + } + ], + 'dc.date.issued': [ + { + language: 'en_US', + value: '2015-06-26' + } + ], + 'dc.description.abstract': [ + { + language: 'en_US', + value: 'This is the abstract' + } + ], + 'person.familyName': [ + { + value: 'Michel' + } + ], + 'dspace.entity.type': [ + { + value: 'Person' + } + ] + } + }) +}); +const mockOrgUnit: ItemSearchResult = Object.assign(new ItemSearchResult(), { + hitHighlights: { + 'organization.legalName': [{ + value: 'Science' + }], + }, + indexableObject: + Object.assign(new Item(), { + bundles: observableOf({}), + entityType: 'OrgUnit', + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ], + 'dc.contributor.author': [ + { + language: 'en_US', + value: 'Smith, Donald' + } + ], + 'dc.publisher': [ + { + language: 'en_US', + value: 'a publisher' + } + ], + 'dc.date.issued': [ + { + language: 'en_US', + value: '2015-06-26' + } + ], + 'dc.description.abstract': [ + { + language: 'en_US', + value: 'This is the abstract' + } + ], + 'organization.legalName': [ + { + value: 'Science' + } + ], + 'dspace.entity.type': [ + { + value: 'OrgUnit' + } + ] + } + }) +}); const environmentUseThumbs = { browseBy: { showThumbnails: true @@ -205,6 +317,42 @@ describe('ItemSearchResultListElementComponent', () => { }); }); + describe('When the item has title', () => { + beforeEach(() => { + publicationListElementComponent.object = mockItemWithMetadata; + fixture.detectChanges(); + }); + + it('should show highlighted title', () => { + const titleField = fixture.debugElement.query(By.css('.item-list-title')); + expect(titleField.nativeNode.innerHTML).toEqual(dcTitle); + }); + }); + + describe('When the item is Person and has title', () => { + beforeEach(() => { + publicationListElementComponent.object = mockPerson; + fixture.detectChanges(); + }); + + it('should show highlighted title', () => { + const titleField = fixture.debugElement.query(By.css('.item-list-title')); + expect(titleField.nativeNode.innerHTML).toEqual('Michel'); + }); + }); + + describe('When the item is orgUnit and has title', () => { + beforeEach(() => { + publicationListElementComponent.object = mockOrgUnit; + fixture.detectChanges(); + }); + + it('should show highlighted title', () => { + const titleField = fixture.debugElement.query(By.css('.item-list-title')); + expect(titleField.nativeNode.innerHTML).toEqual('Science'); + }); + }); + describe('When the item has no title', () => { beforeEach(() => { publicationListElementComponent.object = mockItemWithoutMetadata; diff --git a/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts index 72120a6b68..e56b7e970a 100644 --- a/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts +++ b/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts @@ -33,7 +33,7 @@ export class SearchResultListElementComponent, K exten ngOnInit(): void { if (hasValue(this.object)) { this.dso = this.object.indexableObject; - this.dsoTitle = this.dsoNameService.getName(this.dso); + this.dsoTitle = this.dsoNameService.getHitHighlights(this.object, this.dso); } } diff --git a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html index 49ca6fe3fd..eb49235641 100644 --- a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html @@ -29,3 +29,10 @@ ngDefaultControl >
+ + + {{'search.filters.filter.show-tree' | translate: {name: ('search.filters.filter.' + filterConfig.name + '.head' | translate | lowercase )} }} + diff --git a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts index 9302e66d98..e6c74d8047 100644 --- a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts @@ -1,155 +1,155 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; import { SearchHierarchyFilterComponent } from './search-hierarchy-filter.component'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { DebugElement, EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { VocabularyService } from '../../../../../core/submission/vocabularies/vocabulary.service'; +import { of as observableOf, BehaviorSubject } from 'rxjs'; +import { RemoteData } from '../../../../../core/data/remote-data'; +import { RequestEntryState } from '../../../../../core/data/request-entry-state.model'; +import { TranslateModule } from '@ngx-translate/core'; +import { RouterStub } from '../../../../testing/router.stub'; +import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; +import { PageInfo } from '../../../../../core/shared/page-info.model'; +import { CommonModule } from '@angular/common'; import { SearchService } from '../../../../../core/shared/search/search.service'; import { FILTER_CONFIG, IN_PLACE_SEARCH, - REFRESH_FILTER, - SearchFilterService + SearchFilterService, + REFRESH_FILTER } from '../../../../../core/shared/search/search-filter.service'; import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service'; -import { SearchFiltersComponent } from '../../search-filters.component'; import { Router } from '@angular/router'; -import { RouterStub } from '../../../../testing/router.stub'; -import { SearchServiceStub } from '../../../../testing/search-service.stub'; -import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; +import { NgbModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { SEARCH_CONFIG_SERVICE } from '../../../../../my-dspace-page/my-dspace-page.component'; import { SearchConfigurationServiceStub } from '../../../../testing/search-configuration-service.stub'; +import { VocabularyEntryDetail } from '../../../../../core/submission/vocabularies/models/vocabulary-entry-detail.model'; +import { FacetValue} from '../../../models/facet-value.model'; import { SearchFilterConfig } from '../../../models/search-filter-config.model'; -import { TranslateModule } from '@ngx-translate/core'; -import { - FilterInputSuggestionsComponent -} from '../../../../input-suggestions/filter-suggestions/filter-input-suggestions.component'; -import { FormsModule } from '@angular/forms'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; -import { createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils'; -import { FacetValue } from '../../../models/facet-value.model'; -import { FilterType } from '../../../models/filter-type.model'; -import { createPaginatedList } from '../../../../testing/utils.test'; -import { RemoteData } from '../../../../../core/data/remote-data'; -import { PaginatedList } from '../../../../../core/data/paginated-list.model'; describe('SearchHierarchyFilterComponent', () => { - let comp: SearchHierarchyFilterComponent; + let fixture: ComponentFixture; - let searchService: SearchService; - let router; + let showVocabularyTreeLink: DebugElement; - const value1 = 'testvalue1'; - const value2 = 'test2'; - const value3 = 'another value3'; - const values: FacetValue[] = [ - { - label: value1, - value: value1, - count: 52, - _links: { - self: { - href: '' - }, - search: { - href: '' - } - } - }, { - label: value2, - value: value2, - count: 20, - _links: { - self: { - href: '' - }, - search: { - href: '' - } - } - }, { - label: value3, - value: value3, - count: 5, - _links: { - self: { - href: '' - }, - search: { - href: '' - } - } - } - ]; - const mockValues = createSuccessfulRemoteDataObject$(createPaginatedList(values)); - - const searchFilterServiceStub = { - getSelectedValuesForFilter(_filterConfig: SearchFilterConfig): Observable { - return observableOf(values.map((value: FacetValue) => value.value)); - }, - getPage(_paramName: string): Observable { - return observableOf(0); - }, - resetPage(_filterName: string): void { - // empty - } + const testSearchLink = 'test-search'; + const testSearchFilter = 'test-search-filter'; + const VocabularyTreeViewComponent = { + select: new EventEmitter(), }; - const remoteDataBuildServiceStub = { - aggregate(_input: Observable>[]): Observable[]>> { - return createSuccessfulRemoteDataObject$([createPaginatedList(values)]); + const searchService = { + getSearchLink: () => testSearchLink, + getFacetValuesFor: () => observableOf([]), + }; + const searchFilterService = { + getPage: () => observableOf(0), + }; + const router = new RouterStub(); + const ngbModal = jasmine.createSpyObj('modal', { + open: { + componentInstance: VocabularyTreeViewComponent, } + }); + const vocabularyService = { + searchTopEntries: () => undefined, }; - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule], + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + CommonModule, + NgbModule, + TranslateModule.forRoot(), + ], declarations: [ SearchHierarchyFilterComponent, - SearchFiltersComponent, - FilterInputSuggestionsComponent ], providers: [ - { provide: SearchService, useValue: new SearchServiceStub() }, - { provide: SearchFilterService, useValue: searchFilterServiceStub }, - { provide: RemoteDataBuildService, useValue: remoteDataBuildServiceStub }, - { provide: Router, useValue: new RouterStub() }, + { provide: SearchService, useValue: searchService }, + { provide: SearchFilterService, useValue: searchFilterService }, + { provide: RemoteDataBuildService, useValue: {} }, + { provide: Router, useValue: router }, + { provide: NgbModal, useValue: ngbModal }, + { provide: VocabularyService, useValue: vocabularyService }, { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() }, { provide: IN_PLACE_SEARCH, useValue: false }, - { provide: FILTER_CONFIG, useValue: new SearchFilterConfig() }, - { provide: REFRESH_FILTER, useValue: new BehaviorSubject(false) } + { provide: FILTER_CONFIG, useValue: Object.assign(new SearchFilterConfig(), { name: testSearchFilter }) }, + { provide: REFRESH_FILTER, useValue: new BehaviorSubject(false)} ], - schemas: [NO_ERRORS_SCHEMA] - }).overrideComponent(SearchHierarchyFilterComponent, { - set: { changeDetection: ChangeDetectionStrategy.Default } + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); - }) - ; - const mockFilterConfig: SearchFilterConfig = Object.assign(new SearchFilterConfig(), { - name: 'filterName1', - filterType: FilterType.text, - hasFacets: false, - isOpenByDefault: false, - pageSize: 2 }); - beforeEach(async () => { + function init() { fixture = TestBed.createComponent(SearchHierarchyFilterComponent); - comp = fixture.componentInstance; // SearchHierarchyFilterComponent test instance - comp.filterConfig = mockFilterConfig; - searchService = (comp as any).searchService; - // @ts-ignore - spyOn(searchService, 'getFacetValuesFor').and.returnValue(mockValues); - router = (comp as any).router; fixture.detectChanges(); + showVocabularyTreeLink = fixture.debugElement.query(By.css('a#show-test-search-filter-tree')); + } + + describe('if the vocabulary doesn\'t exist', () => { + + beforeEach(() => { + spyOn(vocabularyService, 'searchTopEntries').and.returnValue(observableOf(new RemoteData( + undefined, 0, 0, RequestEntryState.Error, undefined, undefined, 404 + ))); + init(); + }); + + it('should not show the vocabulary tree link', () => { + expect(showVocabularyTreeLink).toBeNull(); + }); }); - it('should navigate to the correct filter with the query operator', () => { - expect((comp as any).searchService.getFacetValuesFor).toHaveBeenCalledWith(comp.filterConfig, 0, {}, null, true); + describe('if the vocabulary exists', () => { - const searchQuery = 'MARVEL'; - comp.onSubmit(searchQuery); + beforeEach(() => { + spyOn(vocabularyService, 'searchTopEntries').and.returnValue(observableOf(new RemoteData( + undefined, 0, 0, RequestEntryState.Success, undefined, buildPaginatedList(new PageInfo(), []), 200 + ))); + init(); + }); - expect(router.navigate).toHaveBeenCalledWith(['', 'search'], Object({ - queryParams: Object({ [mockFilterConfig.paramName]: [...values.map((value: FacetValue) => `${value.value},equals`), `${searchQuery},query`] }), - queryParamsHandling: 'merge' - })); + it('should show the vocabulary tree link', () => { + expect(showVocabularyTreeLink).toBeTruthy(); + }); + + describe('when clicking the vocabulary tree link', () => { + + const alreadySelectedValues = [ + 'already-selected-value-1', + 'already-selected-value-2', + ]; + const newSelectedValue = 'new-selected-value'; + + beforeEach(async () => { + showVocabularyTreeLink.nativeElement.click(); + fixture.componentInstance.selectedValues$ = observableOf( + alreadySelectedValues.map(value => Object.assign(new FacetValue(), { value })) + ); + VocabularyTreeViewComponent.select.emit(Object.assign(new VocabularyEntryDetail(), { + value: newSelectedValue, + })); + }); + + it('should open the vocabulary tree modal', () => { + expect(ngbModal.open).toHaveBeenCalled(); + }); + + describe('when selecting a value from the vocabulary tree', () => { + + it('should add a new search filter to the existing search filters', () => { + waitForAsync(() => expect(router.navigate).toHaveBeenCalledWith([testSearchLink], { + queryParams: { + [`f.${testSearchFilter}`]: [ + ...alreadySelectedValues, + newSelectedValue, + ].map((value => `${value},equals`)), + }, + queryParamsHandling: 'merge', + })); + }); + }); + }); }); }); diff --git a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.ts index b3349a5dd9..8504237f09 100644 --- a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.ts @@ -1,7 +1,30 @@ -import { Component, OnInit } from '@angular/core'; -import { FilterType } from '../../../models/filter-type.model'; +import { Component, Inject, OnInit } from '@angular/core'; import { renderFacetFor } from '../search-filter-type-decorator'; +import { FilterType } from '../../../models/filter-type.model'; import { facetLoad, SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component'; +import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { VocabularyTreeviewComponent } from '../../../../form/vocabulary-treeview/vocabulary-treeview.component'; +import { + VocabularyEntryDetail +} from '../../../../../core/submission/vocabularies/models/vocabulary-entry-detail.model'; +import { SearchService } from '../../../../../core/shared/search/search.service'; +import { + FILTER_CONFIG, + IN_PLACE_SEARCH, + SearchFilterService, REFRESH_FILTER +} from '../../../../../core/shared/search/search-filter.service'; +import { Router } from '@angular/router'; +import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service'; +import { SEARCH_CONFIG_SERVICE } from '../../../../../my-dspace-page/my-dspace-page.component'; +import { SearchConfigurationService } from '../../../../../core/shared/search/search-configuration.service'; +import { SearchFilterConfig } from '../../../models/search-filter-config.model'; +import { FacetValue } from '../../../models/facet-value.model'; +import { getFacetValueForType } from '../../../search.utils'; +import { filter, map, take } from 'rxjs/operators'; +import { VocabularyService } from '../../../../../core/submission/vocabularies/vocabulary.service'; +import { Observable, BehaviorSubject } from 'rxjs'; +import { PageInfo } from '../../../../../core/shared/page-info.model'; +import { environment } from '../../../../../../environments/environment'; import { addOperatorToFilterValue } from '../../../search.utils'; @Component({ @@ -16,6 +39,23 @@ import { addOperatorToFilterValue } from '../../../search.utils'; */ @renderFacetFor(FilterType.hierarchy) export class SearchHierarchyFilterComponent extends SearchFacetFilterComponent implements OnInit { + + constructor(protected searchService: SearchService, + protected filterService: SearchFilterService, + protected rdbs: RemoteDataBuildService, + protected router: Router, + protected modalService: NgbModal, + protected vocabularyService: VocabularyService, + @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService, + @Inject(IN_PLACE_SEARCH) public inPlaceSearch: boolean, + @Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig, + @Inject(REFRESH_FILTER) public refreshFilters: BehaviorSubject + ) { + super(searchService, filterService, rdbs, router, searchConfigService, inPlaceSearch, filterConfig, refreshFilters); + } + + vocabularyExists$: Observable; + /** * Submits a new active custom value to the filter from the input field * Overwritten method from parent component, adds the "query" operator to the received data before passing it on @@ -24,4 +64,59 @@ export class SearchHierarchyFilterComponent extends SearchFacetFilterComponent i onSubmit(data: any) { super.onSubmit(addOperatorToFilterValue(data, 'query')); } + + ngOnInit() { + super.ngOnInit(); + this.vocabularyExists$ = this.vocabularyService.searchTopEntries( + this.getVocabularyEntry(), new PageInfo(), true, false, + ).pipe( + filter(rd => rd.hasCompleted), + take(1), + map(rd => { + return rd.hasSucceeded; + }), + ); + } + + /** + * Open the vocabulary tree modal popup. + * When an entry is selected, add the filter query to the search options. + */ + showVocabularyTree() { + const modalRef: NgbModalRef = this.modalService.open(VocabularyTreeviewComponent, { + size: 'lg', + windowClass: 'treeview' + }); + modalRef.componentInstance.vocabularyOptions = { + name: this.getVocabularyEntry(), + closed: true + }; + modalRef.componentInstance.select.subscribe((detail: VocabularyEntryDetail) => { + this.selectedValues$ + .pipe(take(1)) + .subscribe((selectedValues) => { + this.router.navigate( + [this.searchService.getSearchLink()], + { + queryParams: { + [this.filterConfig.paramName]: [...selectedValues, {value: detail.value}] + .map((facetValue: FacetValue) => getFacetValueForType(facetValue, this.filterConfig)), + }, + queryParamsHandling: 'merge', + }, + ); + }); + }); + } + + /** + * Returns the matching vocabulary entry for the given search filter. + * These are configurable in the config file. + */ + getVocabularyEntry() { + const foundVocabularyConfig = environment.vocabularies.filter((v) => v.filter === this.filterConfig.name); + if (foundVocabularyConfig.length > 0 && foundVocabularyConfig[0].enabled === true) { + return foundVocabularyConfig[0].vocabulary; + } + } } diff --git a/src/app/shared/search/search.module.ts b/src/app/shared/search/search.module.ts index 426ed82aef..713b9925a6 100644 --- a/src/app/shared/search/search.module.ts +++ b/src/app/shared/search/search.module.ts @@ -31,6 +31,7 @@ import { SearchComponent } from './search.component'; import { ThemedSearchComponent } from './themed-search.component'; import { ThemedSearchResultsComponent } from './search-results/themed-search-results.component'; import { ThemedSearchSettingsComponent } from './search-settings/themed-search-settings.component'; +import { NouisliderModule } from 'ng2-nouislider'; const COMPONENTS = [ SearchComponent, @@ -91,7 +92,8 @@ export const MODELS = [ missingTranslationHandler: { provide: MissingTranslationHandler, useClass: MissingTranslationHelper }, useDefaultLang: true }), - SharedModule.withEntryComponents() + SharedModule.withEntryComponents(), + NouisliderModule, ], exports: [ ...COMPONENTS diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 2ec8fb1553..777ad03c1d 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -2,21 +2,15 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { CdkTreeModule } from '@angular/cdk/tree'; import { DragDropModule } from '@angular/cdk/drag-drop'; - -import { NouisliderModule } from 'ng2-nouislider'; import { - NgbDatepickerModule, NgbDropdownModule, NgbNavModule, NgbPaginationModule, - NgbTimepickerModule, NgbTooltipModule, NgbTypeaheadModule, } from '@ng-bootstrap/ng-bootstrap'; import { MissingTranslationHandler, TranslateModule } from '@ngx-translate/core'; -import { NgxPaginationModule } from 'ngx-pagination'; import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { ConfirmationModalComponent } from './confirmation-modal/confirmation-modal.component'; import { @@ -75,25 +69,7 @@ import { AlertComponent } from './alert/alert.component'; import { SearchResultDetailElementComponent } from './object-detail/my-dspace-result-detail-element/search-result-detail-element.component'; -import { ClaimedTaskActionsComponent } from './mydspace-actions/claimed-task/claimed-task-actions.component'; -import { PoolTaskActionsComponent } from './mydspace-actions/pool-task/pool-task-actions.component'; import { ObjectDetailComponent } from './object-detail/object-detail.component'; -import { - ItemDetailPreviewComponent -} from './object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component'; -import { - MyDSpaceItemStatusComponent -} from './object-collection/shared/mydspace-item-status/my-dspace-item-status.component'; -import { WorkspaceitemActionsComponent } from './mydspace-actions/workspaceitem/workspaceitem-actions.component'; -import { WorkflowitemActionsComponent } from './mydspace-actions/workflowitem/workflowitem-actions.component'; -import { ItemSubmitterComponent } from './object-collection/shared/mydspace-item-submitter/item-submitter.component'; -import { ItemActionsComponent } from './mydspace-actions/item/item-actions.component'; -import { - ClaimedTaskActionsApproveComponent -} from './mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component'; -import { - ClaimedTaskActionsRejectComponent -} from './mydspace-actions/claimed-task/reject/claimed-task-actions-reject.component'; import { ObjNgFor } from './utils/object-ngfor.pipe'; import { BrowseByComponent } from './browse-by/browse-by.component'; import { @@ -163,21 +139,8 @@ import { import { ThemedEditCollectionSelectorComponent } from './dso-selector/modal-wrappers/edit-collection-selector/themed-edit-collection-selector.component'; -import { - ItemListPreviewComponent -} from './object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component'; -import { - MetadataFieldWrapperComponent -} from '../item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component'; -import { MetadataValuesComponent } from '../item-page/field-components/metadata-values/metadata-values.component'; import { RoleDirective } from './roles/role.directive'; import { UserMenuComponent } from './auth-nav-menu/user-menu/user-menu.component'; -import { - ClaimedTaskActionsReturnToPoolComponent -} from './mydspace-actions/claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component'; -import { - ItemDetailPreviewFieldComponent -} from './object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview-field/item-detail-preview-field.component'; import { CollectionSearchResultGridElementComponent } from './object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component'; @@ -216,35 +179,22 @@ import { } from './object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component'; import { PageWithSidebarComponent } from './sidebar/page-with-sidebar.component'; import { SidebarDropdownComponent } from './sidebar/sidebar-dropdown.component'; -import { SidebarFilterComponent } from './sidebar/filter/sidebar-filter.component'; -import { SidebarFilterSelectedOptionComponent } from './sidebar/filter/sidebar-filter-selected-option.component'; import { SelectableListItemControlComponent } from './object-collection/shared/selectable-list-item-control/selectable-list-item-control.component'; import { ImportableListItemControlComponent } from './object-collection/shared/importable-list-item-control/importable-list-item-control.component'; -import { ItemVersionsComponent } from './item/item-versions/item-versions.component'; import { LogInContainerComponent } from './log-in/container/log-in-container.component'; -import { LogInShibbolethComponent } from './log-in/methods/shibboleth/log-in-shibboleth.component'; import { LogInPasswordComponent } from './log-in/methods/password/log-in-password.component'; import { LogInComponent } from './log-in/log-in.component'; -import { BundleListElementComponent } from './object-list/bundle-list-element/bundle-list-element.component'; import { MissingTranslationHelper } from './translate/missing-translation.helper'; -import { ItemVersionsNoticeComponent } from './item/item-versions/notice/item-versions-notice.component'; import { FileValidator } from './utils/require-file.validator'; import { FileValueAccessorDirective } from './utils/file-value-accessor.directive'; -import { FileSectionComponent } from '../item-page/simple/field-components/file-section/file-section.component'; import { ModifyItemOverviewComponent } from '../item-page/edit-item-page/modify-item-overview/modify-item-overview.component'; -import { - ClaimedTaskActionsLoaderComponent -} from './mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component'; import { ClaimedTaskActionsDirective } from './mydspace-actions/claimed-task/switcher/claimed-task-actions.directive'; -import { - ClaimedTaskActionsEditMetadataComponent -} from './mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component'; import { ImpersonateNavbarComponent } from './impersonate-navbar/impersonate-navbar.component'; import { NgForTrackByIdDirective } from './ng-for-track-by-id.directive'; import { FileDownloadLinkComponent } from './file-download-link/file-download-link.component'; @@ -267,44 +217,20 @@ import { AuthorizedCollectionSelectorComponent } from './dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component'; import { DsoPageEditButtonComponent } from './dso-page/dso-page-edit-button/dso-page-edit-button.component'; -import { DsoPageVersionButtonComponent } from './dso-page/dso-page-version-button/dso-page-version-button.component'; import { HoverClassDirective } from './hover-class.directive'; import { ValidationSuggestionsComponent } from './input-suggestions/validation-suggestions/validation-suggestions.component'; -import { ItemAlertsComponent } from './item/item-alerts/item-alerts.component'; import { ItemSearchResultGridElementComponent } from './object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component'; -import { BitstreamDownloadPageComponent } from './bitstream-download-page/bitstream-download-page.component'; -import { - GenericItemPageFieldComponent -} from '../item-page/simple/field-components/specific-field/generic/generic-item-page-field.component'; -import { - MetadataRepresentationListComponent -} from '../item-page/simple/metadata-representation-list/metadata-representation-list.component'; -import { RelatedItemsComponent } from '../item-page/simple/related-items/related-items-component'; import { SearchNavbarComponent } from '../search-navbar/search-navbar.component'; import { ThemedSearchNavbarComponent } from '../search-navbar/themed-search-navbar.component'; -import { - ItemVersionsSummaryModalComponent -} from './item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component'; -import { - ItemVersionsDeleteModalComponent -} from './item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component'; import { ScopeSelectorModalComponent } from './search-form/scope-selector-modal/scope-selector-modal.component'; -import { - BitstreamRequestACopyPageComponent -} from './bitstream-request-a-copy-page/bitstream-request-a-copy-page.component'; import { DsSelectComponent } from './ds-select/ds-select.component'; -import { LogInOidcComponent } from './log-in/methods/oidc/log-in-oidc.component'; -import { ThemedItemListPreviewComponent } from './object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component'; import { RSSComponent } from './rss-feed/rss.component'; -import { DsoPageOrcidButtonComponent } from './dso-page/dso-page-orcid-button/dso-page-orcid-button.component'; -import { LogInOrcidComponent } from './log-in/methods/orcid/log-in-orcid.component'; import { BrowserOnlyPipe } from './utils/browser-only.pipe'; import { ThemedLoadingComponent } from './loading/themed-loading.component'; -import { PersonPageClaimButtonComponent } from './dso-page/person-page-claim-button/person-page-claim-button.component'; import { SearchExportCsvComponent } from './search/search-export-csv/search-export-csv.component'; import { ItemPageTitleFieldComponent @@ -316,24 +242,22 @@ import { ListableNotificationObjectComponent } from './object-list/listable-notification-object/listable-notification-object.component'; import { ThemedCollectionDropdownComponent } from './collection-dropdown/themed-collection-dropdown.component'; +import { MetadataFieldWrapperComponent } from './metadata-field-wrapper/metadata-field-wrapper.component'; +import { LogInExternalProviderComponent } from './log-in/methods/log-in-external-provider/log-in-external-provider.component'; + const MODULES = [ CommonModule, FormsModule, InfiniteScrollModule, NgbNavModule, - NgbDatepickerModule, - NgbTimepickerModule, NgbTypeaheadModule, - NgxPaginationModule, NgbPaginationModule, NgbDropdownModule, NgbTooltipModule, ReactiveFormsModule, RouterModule, - NouisliderModule, DragDropModule, - CdkTreeModule, GoogleRecaptchaModule, MenuModule, ]; @@ -369,7 +293,6 @@ const COMPONENTS = [ UserMenuComponent, DsSelectComponent, ErrorComponent, - FileSectionComponent, LangSwitchComponent, LoadingComponent, ThemedLoadingComponent, @@ -386,21 +309,7 @@ const COMPONENTS = [ SearchFormComponent, PageWithSidebarComponent, SidebarDropdownComponent, - SidebarFilterComponent, - SidebarFilterSelectedOptionComponent, ThumbnailComponent, - ItemListPreviewComponent, - ThemedItemListPreviewComponent, - MyDSpaceItemStatusComponent, - ItemSubmitterComponent, - ItemDetailPreviewComponent, - ItemDetailPreviewFieldComponent, - ClaimedTaskActionsComponent, - ClaimedTaskActionsLoaderComponent, - ItemActionsComponent, - PoolTaskActionsComponent, - WorkflowitemActionsComponent, - WorkspaceitemActionsComponent, ViewModeSwitchComponent, TruncatableComponent, TruncatablePartComponent, @@ -423,8 +332,6 @@ const COMPONENTS = [ SelectableListItemControlComponent, ImportableListItemControlComponent, LogInContainerComponent, - ItemVersionsComponent, - ItemVersionsNoticeComponent, ModifyItemOverviewComponent, ImpersonateNavbarComponent, EntityDropdownComponent, @@ -437,6 +344,8 @@ const COMPONENTS = [ ItemPageTitleFieldComponent, ThemedSearchNavbarComponent, ListableNotificationObjectComponent, + DsoPageEditButtonComponent, + MetadataFieldWrapperComponent, ]; const ENTRY_COMPONENTS = [ @@ -476,19 +385,10 @@ const ENTRY_COMPONENTS = [ MetadataRepresentationListElementComponent, ItemMetadataRepresentationListElementComponent, LogInPasswordComponent, - LogInShibbolethComponent, - LogInOidcComponent, - LogInOrcidComponent, - BundleListElementComponent, - ClaimedTaskActionsApproveComponent, - ClaimedTaskActionsRejectComponent, - ClaimedTaskActionsReturnToPoolComponent, - ClaimedTaskActionsEditMetadataComponent, + LogInExternalProviderComponent, CollectionDropdownComponent, ThemedCollectionDropdownComponent, FileDownloadLinkComponent, - BitstreamDownloadPageComponent, - BitstreamRequestACopyPageComponent, CurationFormComponent, ExportMetadataSelectorComponent, ImportBatchSelectorComponent, @@ -502,20 +402,6 @@ const ENTRY_COMPONENTS = [ ListableNotificationObjectComponent, ]; -const SHARED_ITEM_PAGE_COMPONENTS = [ - MetadataFieldWrapperComponent, - MetadataValuesComponent, - DsoPageEditButtonComponent, - DsoPageVersionButtonComponent, - PersonPageClaimButtonComponent, - ItemAlertsComponent, - GenericItemPageFieldComponent, - MetadataRepresentationListComponent, - RelatedItemsComponent, - DsoPageOrcidButtonComponent - -]; - const PROVIDERS = [ TruncatableService, MockAdminGuard, @@ -551,9 +437,6 @@ const DIRECTIVES = [ ...COMPONENTS, ...ENTRY_COMPONENTS, ...DIRECTIVES, - ...SHARED_ITEM_PAGE_COMPONENTS, - ItemVersionsSummaryModalComponent, - ItemVersionsDeleteModalComponent, ], providers: [ ...PROVIDERS @@ -563,7 +446,6 @@ const DIRECTIVES = [ ...PIPES, ...COMPONENTS, ...ENTRY_COMPONENTS, - ...SHARED_ITEM_PAGE_COMPONENTS, ...DIRECTIVES, TranslateModule, ] diff --git a/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.html b/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.html deleted file mode 100644 index bbe0b93566..0000000000 --- a/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.html +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.scss b/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.scss deleted file mode 100644 index 30ab8912d3..0000000000 --- a/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.scss +++ /dev/null @@ -1,11 +0,0 @@ -a { - color: var(--bs-body-color); - - &:hover, &focus { - text-decoration: none; - } - - span.badge { - vertical-align: text-top; - } -} diff --git a/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.ts b/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.ts deleted file mode 100644 index 4f1d2415ae..0000000000 --- a/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; - -@Component({ - selector: 'ds-sidebar-filter-selected-option', - styleUrls: ['./sidebar-filter-selected-option.component.scss'], - templateUrl: './sidebar-filter-selected-option.component.html', -}) - -/** - * Represents a single selected option in a sidebar filter - */ -export class SidebarFilterSelectedOptionComponent { - @Input() label: string; - @Output() click: EventEmitter = new EventEmitter(); -} diff --git a/src/app/shared/sidebar/filter/sidebar-filter.actions.ts b/src/app/shared/sidebar/filter/sidebar-filter.actions.ts deleted file mode 100644 index 5dab677c5c..0000000000 --- a/src/app/shared/sidebar/filter/sidebar-filter.actions.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* eslint-disable max-classes-per-file */ -import { Action } from '@ngrx/store'; - -import { type } from '../../ngrx/type'; - -/** - * For each action type in an action group, make a simple - * enum object for all of this group's action types. - * - * The 'type' utility function coerces strings into string - * literal types and runs a simple check to guarantee all - * action types in the application are unique. - */ -export const SidebarFilterActionTypes = { - INITIALIZE: type('dspace/sidebar-filter/INITIALIZE'), - COLLAPSE: type('dspace/sidebar-filter/COLLAPSE'), - EXPAND: type('dspace/sidebar-filter/EXPAND'), - TOGGLE: type('dspace/sidebar-filter/TOGGLE'), -}; - -export class SidebarFilterAction implements Action { - /** - * Name of the filter the action is performed on, used to identify the filter - */ - filterName: string; - - /** - * Type of action that will be performed - */ - type; - - /** - * Initialize with the filter's name - * @param {string} name of the filter - */ - constructor(name: string) { - this.filterName = name; - } -} - -/** - * Used to initialize a filter - */ -export class FilterInitializeAction extends SidebarFilterAction { - type = SidebarFilterActionTypes.INITIALIZE; - initiallyExpanded; - - constructor(name: string, initiallyExpanded: boolean) { - super(name); - this.initiallyExpanded = initiallyExpanded; - } -} - -/** - * Used to collapse a filter - */ -export class FilterCollapseAction extends SidebarFilterAction { - type = SidebarFilterActionTypes.COLLAPSE; -} - -/** - * Used to expand a filter - */ -export class FilterExpandAction extends SidebarFilterAction { - type = SidebarFilterActionTypes.EXPAND; -} - -/** - * Used to collapse a filter when it's expanded and expand it when it's collapsed - */ -export class FilterToggleAction extends SidebarFilterAction { - type = SidebarFilterActionTypes.TOGGLE; -} diff --git a/src/app/shared/sidebar/filter/sidebar-filter.component.html b/src/app/shared/sidebar/filter/sidebar-filter.component.html deleted file mode 100644 index 79afaa7583..0000000000 --- a/src/app/shared/sidebar/filter/sidebar-filter.component.html +++ /dev/null @@ -1,26 +0,0 @@ -
-
-
- {{ label | translate }} -
- - -
- -
diff --git a/src/app/shared/sidebar/filter/sidebar-filter.component.scss b/src/app/shared/sidebar/filter/sidebar-filter.component.scss deleted file mode 100644 index bf7a089cb1..0000000000 --- a/src/app/shared/sidebar/filter/sidebar-filter.component.scss +++ /dev/null @@ -1,12 +0,0 @@ -:host .facet-filter { - border: 1px solid var(--bs-light); - cursor: pointer; - - .sidebar-filter-wrapper.closed { - overflow: hidden; - } - - .filter-toggle { - line-height: var(--bs-line-height-base); - } -} diff --git a/src/app/shared/sidebar/filter/sidebar-filter.component.ts b/src/app/shared/sidebar/filter/sidebar-filter.component.ts deleted file mode 100644 index 5a019d41df..0000000000 --- a/src/app/shared/sidebar/filter/sidebar-filter.component.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { Observable } from 'rxjs'; -import { SidebarFilterService } from './sidebar-filter.service'; -import { slide } from '../../animations/slide'; - -@Component({ - selector: 'ds-sidebar-filter', - styleUrls: ['./sidebar-filter.component.scss'], - templateUrl: './sidebar-filter.component.html', - animations: [slide], -}) -/** - * This components renders a sidebar filter including the label and the selected values. - * The filter input itself should still be provided in the content. - */ -export class SidebarFilterComponent implements OnInit { - - @Input() name: string; - @Input() type: string; - @Input() label: string; - @Input() expanded = true; - @Input() singleValue = false; - @Input() selectedValues: Observable; - @Output() removeValue: EventEmitter = new EventEmitter(); - - /** - * True when the filter is 100% collapsed in the UI - */ - closed = true; - - /** - * Emits true when the filter is currently collapsed in the store - */ - collapsed$: Observable; - - constructor( - protected filterService: SidebarFilterService, - ) { - } - - /** - * Changes the state for this filter to collapsed when it's expanded and to expanded it when it's collapsed - */ - toggle() { - this.filterService.toggle(this.name); - } - - /** - * Method to change this.collapsed to false when the slide animation ends and is sliding open - * @param event The animation event - */ - finishSlide(event: any): void { - if (event.fromState === 'collapsed') { - this.closed = false; - } - } - - /** - * Method to change this.collapsed to true when the slide animation starts and is sliding closed - * @param event The animation event - */ - startSlide(event: any): void { - if (event.toState === 'collapsed') { - this.closed = true; - } - } - - ngOnInit(): void { - this.closed = !this.expanded; - this.initializeFilter(); - this.collapsed$ = this.isCollapsed(); - } - - /** - * Sets the initial state of the filter - */ - initializeFilter() { - this.filterService.initializeFilter(this.name, this.expanded); - } - - /** - * Checks if the filter is currently collapsed - * @returns {Observable} Emits true when the current state of the filter is collapsed, false when it's expanded - */ - private isCollapsed(): Observable { - return this.filterService.isCollapsed(this.name); - } - -} diff --git a/src/app/shared/sidebar/filter/sidebar-filter.reducer.ts b/src/app/shared/sidebar/filter/sidebar-filter.reducer.ts deleted file mode 100644 index b7784dd12b..0000000000 --- a/src/app/shared/sidebar/filter/sidebar-filter.reducer.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - FilterInitializeAction, - SidebarFilterAction, - SidebarFilterActionTypes -} from './sidebar-filter.actions'; - -/** - * Interface that represents the state for a single filters - */ -export interface SidebarFilterState { - filterCollapsed: boolean; -} - -/** - * Interface that represents the state for all available filters - */ -export interface SidebarFiltersState { - [name: string]: SidebarFilterState; -} - -const initialState: SidebarFiltersState = Object.create(null); - -/** - * Performs a filter action on the current state - * @param {SidebarFiltersState} state The state before the action is performed - * @param {SidebarFilterAction} action The action that should be performed - * @returns {SidebarFiltersState} The state after the action is performed - */ -export function sidebarFilterReducer(state = initialState, action: SidebarFilterAction): SidebarFiltersState { - - switch (action.type) { - - case SidebarFilterActionTypes.INITIALIZE: { - const initAction = (action as FilterInitializeAction); - return Object.assign({}, state, { - [action.filterName]: { - filterCollapsed: !initAction.initiallyExpanded, - } - }); - } - - case SidebarFilterActionTypes.COLLAPSE: { - return Object.assign({}, state, { - [action.filterName]: { - filterCollapsed: true, - } - }); - } - - case SidebarFilterActionTypes.EXPAND: { - return Object.assign({}, state, { - [action.filterName]: { - filterCollapsed: false, - } - }); - } - - case SidebarFilterActionTypes.TOGGLE: { - return Object.assign({}, state, { - [action.filterName]: { - filterCollapsed: !state[action.filterName].filterCollapsed, - } - }); - } - - default: { - return state; - } - } -} diff --git a/src/app/shared/sidebar/filter/sidebar-filter.service.spec.ts b/src/app/shared/sidebar/filter/sidebar-filter.service.spec.ts deleted file mode 100644 index 49192a2006..0000000000 --- a/src/app/shared/sidebar/filter/sidebar-filter.service.spec.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { TestBed, waitForAsync } from '@angular/core/testing'; -import { Store, StoreModule } from '@ngrx/store'; -import { provideMockStore } from '@ngrx/store/testing'; -import { cold } from 'jasmine-marbles'; - -import { sidebarFilterReducer } from './sidebar-filter.reducer'; -import { SidebarFilterService } from './sidebar-filter.service'; -import { - FilterCollapseAction, - FilterExpandAction, - FilterInitializeAction, - FilterToggleAction -} from './sidebar-filter.actions'; -import { storeModuleConfig } from '../../../app.reducer'; - -describe('SidebarFilterService', () => { - let service: SidebarFilterService; - let store: any; - let initialState; - - function init() { - - initialState = { - sidebarFilter: { - filter_1: { - filterCollapsed: true - }, - filter_2: { - filterCollapsed: false - }, - filter_3: { - filterCollapsed: true - } - } - }; - - } - - beforeEach(waitForAsync(() => { - init(); - TestBed.configureTestingModule({ - imports: [ - StoreModule.forRoot({ sidebarFilter: sidebarFilterReducer }, storeModuleConfig) - ], - providers: [ - provideMockStore({ initialState }), - { provide: SidebarFilterService, useValue: service } - ] - }).compileComponents(); - })); - - beforeEach(() => { - store = TestBed.inject(Store); - service = new SidebarFilterService(store); - spyOn(store, 'dispatch'); - }); - - describe('initializeFilter', () => { - it('should dispatch an FilterInitializeAction with the correct arguments', () => { - service.initializeFilter('fakeFilter', true); - expect(store.dispatch).toHaveBeenCalledWith(new FilterInitializeAction('fakeFilter', true)); - }); - }); - - describe('collapse', () => { - it('should dispatch an FilterInitializeAction with the correct arguments', () => { - service.collapse('fakeFilter'); - expect(store.dispatch).toHaveBeenCalledWith(new FilterCollapseAction('fakeFilter')); - }); - }); - - describe('expand', () => { - it('should dispatch an FilterInitializeAction with the correct arguments', () => { - service.expand('fakeFilter'); - expect(store.dispatch).toHaveBeenCalledWith(new FilterExpandAction('fakeFilter')); - }); - }); - - describe('toggle', () => { - it('should dispatch an FilterInitializeAction with the correct arguments', () => { - service.toggle('fakeFilter'); - expect(store.dispatch).toHaveBeenCalledWith(new FilterToggleAction('fakeFilter')); - }); - }); - - describe('isCollapsed', () => { - it('should return true', () => { - - const result = service.isCollapsed('filter_1'); - const expected = cold('b', { - b: true - }); - - expect(result).toBeObservable(expected); - }); - - it('should return false', () => { - - const result = service.isCollapsed('filter_2'); - const expected = cold('b', { - b: false - }); - - expect(result).toBeObservable(expected); - }); - }); -}); diff --git a/src/app/shared/sidebar/filter/sidebar-filter.service.ts b/src/app/shared/sidebar/filter/sidebar-filter.service.ts deleted file mode 100644 index b67de24f9e..0000000000 --- a/src/app/shared/sidebar/filter/sidebar-filter.service.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Injectable } from '@angular/core'; -import { - FilterCollapseAction, - FilterExpandAction, FilterInitializeAction, - FilterToggleAction -} from './sidebar-filter.actions'; -import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; -import { SidebarFiltersState, SidebarFilterState } from './sidebar-filter.reducer'; -import { Observable } from 'rxjs'; -import { distinctUntilChanged, map } from 'rxjs/operators'; -import { hasValue } from '../../empty.util'; - -/** - * Service that performs all actions that have to do with sidebar filters like collapsing or expanding them. - */ -@Injectable() -export class SidebarFilterService { - - constructor(private store: Store) { - } - - /** - * Dispatches an initialize action to the store for a given filter - * @param {string} filter The filter for which the action is dispatched - * @param {boolean} expanded If the filter should be open from the start - */ - public initializeFilter(filter: string, expanded: boolean): void { - this.store.dispatch(new FilterInitializeAction(filter, expanded)); - } - - /** - * Dispatches a collapse action to the store for a given filter - * @param {string} filterName The filter for which the action is dispatched - */ - public collapse(filterName: string): void { - this.store.dispatch(new FilterCollapseAction(filterName)); - } - - /** - * Dispatches an expand action to the store for a given filter - * @param {string} filterName The filter for which the action is dispatched - */ - public expand(filterName: string): void { - this.store.dispatch(new FilterExpandAction(filterName)); - } - - /** - * Dispatches a toggle action to the store for a given filter - * @param {string} filterName The filter for which the action is dispatched - */ - public toggle(filterName: string): void { - this.store.dispatch(new FilterToggleAction(filterName)); - } - - /** - * Checks if the state of a given filter is currently collapsed or not - * @param {string} filterName The filtername for which the collapsed state is checked - * @returns {Observable} Emits the current collapsed state of the given filter, if it's unavailable, return false - */ - isCollapsed(filterName: string): Observable { - return this.store.pipe( - select(filterByNameSelector(filterName)), - map((object: SidebarFilterState) => { - if (object) { - return object.filterCollapsed; - } else { - return false; - } - }), - distinctUntilChanged() - ); - } - -} - -const filterStateSelector = (state: SidebarFiltersState) => state.sidebarFilter; - -function filterByNameSelector(name: string): MemoizedSelector { - return keySelector(name); -} - -export function keySelector(key: string): MemoizedSelector { - return createSelector(filterStateSelector, (state: SidebarFilterState) => { - if (hasValue(state)) { - return state[key]; - } else { - return undefined; - } - }); -} diff --git a/src/app/statistics/angulartics/dspace-provider.spec.ts b/src/app/statistics/angulartics/dspace-provider.spec.ts index 8491d8e80c..73c2419ce6 100644 --- a/src/app/statistics/angulartics/dspace-provider.spec.ts +++ b/src/app/statistics/angulartics/dspace-provider.spec.ts @@ -11,7 +11,7 @@ describe('Angulartics2DSpace', () => { beforeEach(() => { angulartics2 = { - eventTrack: observableOf({action: 'pageView', properties: {object: 'mock-object'}}), + eventTrack: observableOf({action: 'page_view', properties: {object: 'mock-object'}}), filterDeveloperMode: () => filter(() => true) } as any; statisticsService = jasmine.createSpyObj('statisticsService', {trackViewEvent: null}); diff --git a/src/app/statistics/angulartics/dspace-provider.ts b/src/app/statistics/angulartics/dspace-provider.ts index cd1aab94bd..6efa67f92a 100644 --- a/src/app/statistics/angulartics/dspace-provider.ts +++ b/src/app/statistics/angulartics/dspace-provider.ts @@ -24,7 +24,7 @@ export class Angulartics2DSpace { } private eventTrack(event) { - if (event.action === 'pageView') { + if (event.action === 'page_view') { this.statisticsService.trackViewEvent(event.properties.object); } else if (event.action === 'search') { this.statisticsService.trackSearchEvent( diff --git a/src/app/statistics/angulartics/dspace/view-tracker.component.ts b/src/app/statistics/angulartics/dspace/view-tracker.component.ts index 85588aeb97..d12b6c2f69 100644 --- a/src/app/statistics/angulartics/dspace/view-tracker.component.ts +++ b/src/app/statistics/angulartics/dspace/view-tracker.component.ts @@ -20,7 +20,7 @@ export class ViewTrackerComponent implements OnInit { ngOnInit(): void { this.angulartics2.eventTrack.next({ - action: 'pageView', + action: 'page_view', properties: {object: this.object}, }); } diff --git a/src/app/statistics/google-analytics.service.spec.ts b/src/app/statistics/google-analytics.service.spec.ts index 9e2b1c7edf..2465e4db0e 100644 --- a/src/app/statistics/google-analytics.service.spec.ts +++ b/src/app/statistics/google-analytics.service.spec.ts @@ -1,4 +1,7 @@ -import { Angulartics2GoogleAnalytics, Angulartics2GoogleTagManager } from 'angulartics2'; +import { + Angulartics2GoogleAnalytics, + Angulartics2GoogleGlobalSiteTag, +} from 'angulartics2'; import { of } from 'rxjs'; import { GoogleAnalyticsService } from './google-analytics.service'; @@ -16,7 +19,7 @@ describe('GoogleAnalyticsService', () => { const srcTestValue = 'mock-script-src'; let service: GoogleAnalyticsService; let googleAnalyticsSpy: Angulartics2GoogleAnalytics; - let googleTagManagerSpy: Angulartics2GoogleTagManager; + let googleTagManagerSpy: Angulartics2GoogleGlobalSiteTag; let configSpy: ConfigurationDataService; let klaroServiceSpy: jasmine.SpyObj; let scriptElementMock: any; @@ -37,7 +40,7 @@ describe('GoogleAnalyticsService', () => { googleAnalyticsSpy = jasmine.createSpyObj('Angulartics2GoogleAnalytics', [ 'startTracking', ]); - googleTagManagerSpy = jasmine.createSpyObj('Angulartics2GoogleTagManager', [ + googleTagManagerSpy = jasmine.createSpyObj('Angulartics2GoogleGlobalSiteTag', [ 'startTracking', ]); diff --git a/src/app/statistics/google-analytics.service.ts b/src/app/statistics/google-analytics.service.ts index 9c5883d183..9d32a61093 100644 --- a/src/app/statistics/google-analytics.service.ts +++ b/src/app/statistics/google-analytics.service.ts @@ -1,7 +1,10 @@ import { DOCUMENT } from '@angular/common'; import { Inject, Injectable } from '@angular/core'; -import { Angulartics2GoogleAnalytics, Angulartics2GoogleTagManager } from 'angulartics2'; +import { + Angulartics2GoogleAnalytics, + Angulartics2GoogleGlobalSiteTag, +} from 'angulartics2'; import { combineLatest } from 'rxjs'; import { ConfigurationDataService } from '../core/data/configuration-data.service'; @@ -19,7 +22,7 @@ export class GoogleAnalyticsService { constructor( private googleAnalytics: Angulartics2GoogleAnalytics, - private googleTagManager: Angulartics2GoogleTagManager, + private googleGlobalSiteTag: Angulartics2GoogleGlobalSiteTag, private klaroService: KlaroService, private configService: ConfigurationDataService, @Inject(DOCUMENT) private document: any, @@ -70,7 +73,7 @@ export class GoogleAnalyticsService { this.document.body.appendChild(libScript); // start tracking - this.googleTagManager.startTracking(); + this.googleGlobalSiteTag.startTracking(); } else { // add trackingId snippet to page const keyScript = this.document.createElement('script'); diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 07d05a9994..26844bb1d6 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3640,6 +3640,7 @@ "search.filters.filter.submitter.label": "Search submitter", + "search.filters.filter.show-tree": "Browse {{ name }} tree", "search.filters.entityType.JournalIssue": "Journal Issue", @@ -3836,6 +3837,8 @@ "submission.import-external.source.crossref": "CrossRef", + "submission.import-external.source.datacite": "DataCite", + "submission.import-external.source.scielo": "SciELO", "submission.import-external.source.scopus": "Scopus", @@ -4512,7 +4515,7 @@ "vocabulary-treeview.tree.description.srsc": "Research Subject Categories", - + "vocabulary-treeview.info": "Select a subject to add as search filter", "uploader.browse": "browse", diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts index ce9c8b3bf7..d62b9e5bcb 100644 --- a/src/config/app-config.interface.ts +++ b/src/config/app-config.interface.ts @@ -20,6 +20,7 @@ import { InfoConfig } from './info-config.interface'; 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'; interface AppConfig extends Config { ui: UIServerConfig; @@ -44,6 +45,7 @@ interface AppConfig extends Config { actuators: ActuatorsConfig info: InfoConfig; markdown: MarkdownConfig; + vocabularies: FilterVocabularyConfig[]; } /** diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 276d2d7150..d7b3efc2ed 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -20,6 +20,7 @@ import { InfoConfig } from './info-config.interface'; 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'; export class DefaultAppConfig implements AppConfig { production = false; @@ -385,4 +386,15 @@ export class DefaultAppConfig implements AppConfig { enabled: false, mathjax: false, }; + + // Which vocabularies should be used for which search filters + // and whether to show the filter in the search sidebar + // Take a look at the filter-vocabulary-config.ts file for documentation on how the options are obtained + vocabularies: FilterVocabularyConfig[] = [ + { + filter: 'subject', + vocabulary: 'srsc', + enabled: false + } + ]; } diff --git a/src/config/filter-vocabulary-config.ts b/src/config/filter-vocabulary-config.ts new file mode 100644 index 0000000000..54e57090c8 --- /dev/null +++ b/src/config/filter-vocabulary-config.ts @@ -0,0 +1,22 @@ +import { Config } from './config.interface'; + +/** + * Configuration that can be used to enable a vocabulary tree to be used as search filter + */ +export interface FilterVocabularyConfig extends Config { + /** + * The name of the filter where the vocabulary tree should be used + * This is the name of the filter as it's configured in the facet in discovery.xml + * (can also be seen on the /server/api/discover/facets endpoint) + */ + filter: string; + /** + * name of the vocabulary tree to use + * ( name of the file as stored in the dspace/config/controlled-vocabularies folder without file extension ) + */ + vocabulary: string; + /** + * Whether to show the vocabulary tree in the sidebar + */ + enabled: boolean; +} diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 19eec26a14..b323fa464d 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -283,4 +283,12 @@ export const environment: BuildConfig = { enabled: false, mathjax: false, }, + + vocabularies: [ + { + filter: 'subject', + vocabulary: 'srsc', + enabled: true + } + ] }; diff --git a/src/modules/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts index 6baf339003..4cdf7fbe2f 100644 --- a/src/modules/app/browser-app.module.ts +++ b/src/modules/app/browser-app.module.ts @@ -31,6 +31,7 @@ import { GoogleAnalyticsService } from '../../app/statistics/google-analytics.se import { AuthRequestService } from '../../app/core/auth/auth-request.service'; import { BrowserAuthRequestService } from '../../app/core/auth/browser-auth-request.service'; import { BrowserInitService } from './browser-init.service'; +import { VocabularyTreeviewService } from 'src/app/shared/form/vocabulary-treeview/vocabulary-treeview.service'; export const REQ_KEY = makeStateKey('req'); @@ -111,6 +112,10 @@ export function getRequest(transferState: TransferState): any { provide: LocationToken, useFactory: locationProvider, }, + { + provide: VocabularyTreeviewService, + useClass: VocabularyTreeviewService, + } ] }) export class BrowserAppModule { diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts index 17e394ede8..81426e7fcc 100644 --- a/src/modules/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -6,7 +6,11 @@ import { ServerModule, ServerTransferStateModule } from '@angular/platform-serve import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { Angulartics2, Angulartics2GoogleAnalytics, Angulartics2GoogleTagManager } from 'angulartics2'; +import { + Angulartics2, + Angulartics2GoogleAnalytics, + Angulartics2GoogleGlobalSiteTag +} from 'angulartics2'; import { AppComponent } from '../../app/app.component'; @@ -63,7 +67,7 @@ export function createTranslateLoader(transferState: TransferState) { useClass: AngularticsProviderMock }, { - provide: Angulartics2GoogleTagManager, + provide: Angulartics2GoogleGlobalSiteTag, useClass: AngularticsProviderMock }, { diff --git a/src/styles/_global-styles.scss b/src/styles/_global-styles.scss index 930384cf64..1bc0c8c435 100644 --- a/src/styles/_global-styles.scss +++ b/src/styles/_global-styles.scss @@ -204,3 +204,27 @@ ds-dynamic-form-control-container.d-none { } + +.badge-validation { + background-color: #{map-get($theme-colors, warning)}; +} + +.badge-waiting-controller { + background-color: #{map-get($theme-colors, info)}; +} + +.badge-workspace { + background-color: #{map-get($theme-colors, primary)}; +} + +.badge-archived { + background-color: #{map-get($theme-colors, success)}; +} + +.badge-workflow { + background-color: #{map-get($theme-colors, info)}; +} + +.badge-item-type { + background-color: #{map-get($theme-colors, info)}; +} diff --git a/src/themes/custom/eager-theme.module.ts b/src/themes/custom/eager-theme.module.ts index 6bca518092..50b56252d3 100644 --- a/src/themes/custom/eager-theme.module.ts +++ b/src/themes/custom/eager-theme.module.ts @@ -1,13 +1,11 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; import { SharedModule } from '../../app/shared/shared.module'; import { HomeNewsComponent } from './app/home-page/home-news/home-news.component'; import { NavbarComponent } from './app/navbar/navbar.component'; import { SearchNavbarComponent } from './app/search-navbar/search-navbar.component'; import { HeaderComponent } from './app/header/header.component'; import { HeaderNavbarWrapperComponent } from './app/header-nav-wrapper/header-navbar-wrapper.component'; -import { SearchModule } from '../../app/shared/search/search.module'; import { RootModule } from '../../app/root.module'; import { NavbarModule } from '../../app/navbar/navbar.module'; import { PublicationComponent } from './app/item-page/simple/item-types/publication/publication.component'; @@ -42,7 +40,7 @@ import { } from './app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component'; import { CommunityListElementComponent } from './app/shared/object-list/community-list-element/community-list-element.component'; -import { CollectionListElementComponent} from './app/shared/object-list/collection-list-element/collection-list-element.component'; +import { CollectionListElementComponent } from './app/shared/object-list/collection-list-element/collection-list-element.component'; import { CollectionDropdownComponent } from './app/shared/collection-dropdown/collection-dropdown.component'; @@ -82,8 +80,6 @@ const DECLARATIONS = [ imports: [ CommonModule, SharedModule, - SearchModule, - FormsModule, RootModule, NavbarModule, ItemPageModule, diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index 3738467a3a..b80e841d34 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -117,6 +117,8 @@ import { BrowseByTitlePageComponent } from './app/browse-by/browse-by-title-page import { ExternalSourceEntryImportModalComponent } from './app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/external-source-entry-import-modal/external-source-entry-import-modal.component'; +import { ItemVersionsModule } from '../../app/item-page/versions/item-versions.module'; +import { ItemSharedModule } from '../../app/item-page/item-shared.module'; import { SystemWideAlertModule } from '../../app/system-wide-alert/system-wide-alert.module'; const DECLARATIONS = [ @@ -194,8 +196,10 @@ const DECLARATIONS = [ CommunityPageModule, CoreModule, DragDropModule, + ItemSharedModule, ItemPageModule, EditItemPageModule, + ItemVersionsModule, FormsModule, HomePageModule, HttpClientModule, diff --git a/src/themes/dspace/app/navbar/navbar.component.scss b/src/themes/dspace/app/navbar/navbar.component.scss index 2859ec6d6c..9079470b94 100644 --- a/src/themes/dspace/app/navbar/navbar.component.scss +++ b/src/themes/dspace/app/navbar/navbar.component.scss @@ -1,6 +1,6 @@ nav.navbar { border-top: 1px var(--ds-header-navbar-border-top-color) solid; - border-bottom: 5px var(--bs-green) solid; + border-bottom: 5px var(--ds-header-navbar-border-bottom-color) solid; align-items: baseline; color: var(--ds-header-icon-color); } diff --git a/src/themes/dspace/eager-theme.module.ts b/src/themes/dspace/eager-theme.module.ts index 5dd114cd72..584c83a617 100644 --- a/src/themes/dspace/eager-theme.module.ts +++ b/src/themes/dspace/eager-theme.module.ts @@ -1,12 +1,10 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; import { SharedModule } from '../../app/shared/shared.module'; import { HomeNewsComponent } from './app/home-page/home-news/home-news.component'; import { NavbarComponent } from './app/navbar/navbar.component'; import { HeaderComponent } from './app/header/header.component'; import { HeaderNavbarWrapperComponent } from './app/header-nav-wrapper/header-navbar-wrapper.component'; -import { SearchModule } from '../../app/shared/search/search.module'; import { RootModule } from '../../app/root.module'; import { NavbarModule } from '../../app/navbar/navbar.module'; @@ -29,8 +27,6 @@ const DECLARATIONS = [ imports: [ CommonModule, SharedModule, - SearchModule, - FormsModule, RootModule, NavbarModule, ], diff --git a/src/themes/dspace/lazy-theme.module.ts b/src/themes/dspace/lazy-theme.module.ts index a4e8027a15..6ece4d9755 100644 --- a/src/themes/dspace/lazy-theme.module.ts +++ b/src/themes/dspace/lazy-theme.module.ts @@ -55,6 +55,8 @@ import { } from '../../app/shared/resource-policies/resource-policies.module'; import { ComcolModule } from '../../app/shared/comcol/comcol.module'; import { RootModule } from '../../app/root.module'; +import { ItemVersionsModule } from '../../app/item-page/versions/item-versions.module'; +import { ItemSharedModule } from 'src/app/item-page/item-shared.module'; const DECLARATIONS = [ ]; @@ -76,8 +78,10 @@ const DECLARATIONS = [ CommunityPageModule, CoreModule, DragDropModule, + ItemSharedModule, ItemPageModule, EditItemPageModule, + ItemVersionsModule, FormsModule, HomePageModule, HttpClientModule, diff --git a/src/themes/dspace/styles/_theme_css_variable_overrides.scss b/src/themes/dspace/styles/_theme_css_variable_overrides.scss index e4b4b61f45..516eff9f7e 100644 --- a/src/themes/dspace/styles/_theme_css_variable_overrides.scss +++ b/src/themes/dspace/styles/_theme_css_variable_overrides.scss @@ -6,5 +6,6 @@ --ds-banner-background-gradient-width: 300px; --ds-home-news-link-color: #{$green}; --ds-home-news-link-hover-color: #{darken($green, 15%)}; + --ds-header-navbar-border-bottom-color: #{$green}; } diff --git a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss index 9257bc46dd..b5799c9749 100644 --- a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss +++ b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss @@ -10,7 +10,7 @@ $font-family-sans-serif: 'Nunito', -apple-system, BlinkMacSystemFont, "Segoe UI" $navbar-dark-color: #FFFFFF; /* Reassign color vars to semantic color scheme */ -$blue: #43515f !default; +$blue: #2b4e72 !default; $green: #92C642 !default; $cyan: #207698 !default; $yellow: #ec9433 !default; @@ -18,6 +18,7 @@ $red: #CF4444 !default; $dark: #43515f !default; $gray-800: #343a40 !default; +$gray-700: #495057 !default; $gray-400: #ced4da !default; $gray-100: #f8f9fa !default; @@ -27,3 +28,14 @@ $table-accent-bg: $gray-100 !default; // Bootstrap $gray-100 $table-hover-bg: $gray-400 !default; // Bootstrap $gray-400 $yiq-contrasted-threshold: 170 !default; + +$theme-colors: ( + primary: $dark, + secondary: $gray-700, + success: $green, + info: $cyan, + warning: $yellow, + danger: $red, + light: $gray-100, + dark: $dark +) !default; diff --git a/yarn.lock b/yarn.lock index e92c5bf954..22eeefb309 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7005,10 +7005,10 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.2, json5@^2.1.3, json5@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== +json5@^2.1.2, json5@^2.2.1, json5@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.2.tgz#64471c5bdcc564c18f7c1d4df2e2297f2457c5ab" + integrity sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ== jsonc-parser@3.0.0: version "3.0.0"