Merge remote-tracking branch 'upstream/main' into w2p-97425_System-wide-alerts

This commit is contained in:
Yana De Pauw
2023-01-24 10:25:58 +01:00
128 changed files with 1393 additions and 1368 deletions

View File

@@ -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

View File

@@ -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",

View File

@@ -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);

View File

@@ -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
}];
}

View File

@@ -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<AppState> = {
metadataRegistry: metadataRegistryReducer,
notifications: notificationsReducer,
sidebar: sidebarReducer,
sidebarFilter: sidebarFilterReducer,
searchFilter: filterReducer,
truncatable: truncatableReducer,
cssVariables: cssVariablesReducer,

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -14,6 +14,11 @@ export class MediaViewerItem {
*/
format: string;
/**
* Incoming Bitsream format mime type
*/
mimetype: string;
/**
* Incoming Bitsream thumbnail
*/

View File

@@ -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];

View File

@@ -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];

View File

@@ -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', () => {

View File

@@ -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',

View File

@@ -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', () => {

View File

@@ -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',

View File

@@ -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,

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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 { }

View File

@@ -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 ) {
}
}

View File

@@ -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'
};

View File

@@ -1,4 +1,5 @@
<video
crossorigin="anonymous"
#media
[src]="filteredMedias[currentIndex].bitstream._links.content.href"
id="singleVideo"
@@ -8,7 +9,14 @@
"
preload="none"
controls
></video>
>
<ng-container *ngIf="getMediaCap(filteredMedias[currentIndex].bitstream.name) as capInfos">
<ng-container *ngFor="let capInfo of capInfos">
<track [src]="capInfo.src" [label]="capInfo.langLabel" [srclang]="capInfo.srclang" />
</ng-container>
</ng-container>
</video>
<div class="buttons" *ngIf="filteredMedias?.length > 1">
<button
class="btn btn-primary previous"

View File

@@ -8,7 +8,7 @@ import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock';
import { FileSizePipe } from '../../../shared/utils/file-size-pipe';
import { VarDirective } from '../../../shared/utils/var.directive';
import { MetadataFieldWrapperComponent } from '../../field-components/metadata-field-wrapper/metadata-field-wrapper.component';
import { MetadataFieldWrapperComponent } from '../../../shared/metadata-field-wrapper/metadata-field-wrapper.component';
import { MockBitstreamFormat1 } from '../../../shared/mocks/item.mock';
import { MediaViewerVideoComponent } from './media-viewer-video.component';
import { By } from '@angular/platform-browser';

View File

@@ -1,5 +1,7 @@
import { Component, Input, OnInit } from '@angular/core';
import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
import { languageHelper } from './language-helper';
import { CaptionInfo} from './caption-info';
/**
* This componenet renders a video viewer and playlist for the media viewer
@@ -26,9 +28,38 @@ export class MediaViewerVideoComponent implements OnInit {
ngOnInit() {
this.isCollapsed = false;
this.filteredMedias = this.medias.filter(
(media) => media.format === 'audio' || media.format === 'video'
);
this.filteredMedias = this.medias.filter((media) => media.format === 'audio' || media.format === 'video');
}
/**
* This method check if there is caption file for the media
* The caption file name is the media name plus "-" following two letter
* language code and .vtt suffix
*
* html5 video only support WEBVTT format
*
* Two letter language code reference
* https://www.w3schools.com/tags/ref_language_codes.asp
*/
getMediaCap(name: string): CaptionInfo[] {
let filteredCapMedias: MediaViewerItem[];
let capInfos: CaptionInfo[] = [];
filteredCapMedias = this.medias
.filter((media) => media.mimetype === 'text/vtt')
.filter((media) => media.bitstream.name.substring(0, (media.bitstream.name.length - 7) ).toLowerCase() === name.toLowerCase());
if (filteredCapMedias) {
filteredCapMedias
.forEach((media, index) => {
let srclang: string = media.bitstream.name.slice(-6, -4).toLowerCase();
capInfos.push(new CaptionInfo(
media.bitstream._links.content.href,
srclang,
languageHelper[srclang]
));
});
}
return capInfos;
}
/**

View File

@@ -13,7 +13,7 @@ import { BitstreamDataService } from '../../core/data/bitstream-data.service';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { MediaViewerItem } from '../../core/shared/media-viewer-item.model';
import { VarDirective } from '../../shared/utils/var.directive';
import { MetadataFieldWrapperComponent } from '../field-components/metadata-field-wrapper/metadata-field-wrapper.component';
import { MetadataFieldWrapperComponent } from '../../shared/metadata-field-wrapper/metadata-field-wrapper.component';
import { FileSizePipe } from '../../shared/utils/file-size-pipe';
describe('MediaViewerComponent', () => {

View File

@@ -108,6 +108,7 @@ export class MediaViewerComponent implements OnInit {
const mediaItem = new MediaViewerItem();
mediaItem.bitstream = original;
mediaItem.format = format.mimetype.split('/')[0];
mediaItem.mimetype = format.mimetype;
mediaItem.thumbnail = thumbnail ? thumbnail._links.content.href : null;
return mediaItem;
}

View File

@@ -13,7 +13,7 @@ import { of as observableOf } from 'rxjs';
import { MockBitstreamFormat1 } from '../../../../shared/mocks/item.mock';
import { FileSizePipe } from '../../../../shared/utils/file-size-pipe';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { MetadataFieldWrapperComponent } from '../../../field-components/metadata-field-wrapper/metadata-field-wrapper.component';
import { MetadataFieldWrapperComponent } from '../../../../shared/metadata-field-wrapper/metadata-field-wrapper.component';
import { createPaginatedList } from '../../../../shared/testing/utils.test';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';

View File

@@ -36,7 +36,7 @@ import { VersionDataService } from '../../../../core/data/version-data.service';
import { RouterTestingModule } from '@angular/router/testing';
import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service';
import { SearchService } from '../../../../core/shared/search/search.service';
import { ItemVersionsSharedService } from '../../../../shared/item/item-versions/item-versions-shared.service';
import { ItemVersionsSharedService } from '../../../versions/item-versions-shared.service';
const noMetadata = new MetadataMap();

View File

@@ -5,7 +5,7 @@ import { VersionHistoryDataService } from '../../../../core/data/version-history
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { VersionDataService } from '../../../../core/data/version-data.service';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { ItemVersionsSharedService } from '../../../../shared/item/item-versions/item-versions-shared.service';
import { ItemVersionsSharedService } from '../../../versions/item-versions-shared.service';
import { Item } from '../../../../core/shared/item.model';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
import { buildPaginatedList } from '../../../../core/data/paginated-list.model';
@@ -20,6 +20,7 @@ import { ItemDataService } from '../../../../core/data/item-data.service';
import { Version } from '../../../../core/shared/version.model';
import { RouteService } from '../../../../core/services/route.service';
import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock';
import { ItemSharedModule } from '../../../item-shared.module';
const mockItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])),
@@ -66,6 +67,7 @@ describe('VersionedItemComponent', () => {
useClass: TranslateLoaderMock,
}
}),
ItemSharedModule,
],
providers: [
{ provide: VersionHistoryDataService, useValue: versionHistoryServiceSpy },

View File

@@ -1,6 +1,6 @@
import { Component } from '@angular/core';
import { ItemComponent } from '../shared/item.component';
import { ItemVersionsSummaryModalComponent } from '../../../../shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component';
import { ItemVersionsSummaryModalComponent } from '../../../versions/item-versions-summary-modal/item-versions-summary-modal.component';
import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators';
import { RemoteData } from '../../../../core/data/remote-data';
import { Version } from '../../../../core/shared/version.model';
@@ -9,7 +9,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { VersionHistoryDataService } from '../../../../core/data/version-history-data.service';
import { TranslateService } from '@ngx-translate/core';
import { VersionDataService } from '../../../../core/data/version-data.service';
import { ItemVersionsSharedService } from '../../../../shared/item/item-versions/item-versions-shared.service';
import { ItemVersionsSharedService } from '../../../versions/item-versions-shared.service';
import { Router } from '@angular/router';
import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service';
import { SearchService } from '../../../../core/shared/search/search.service';

View File

@@ -2,15 +2,15 @@ import { TestBed } from '@angular/core/testing';
import { ItemVersionsSharedService } from './item-versions-shared.service';
import { ActivatedRoute } from '@angular/router';
import { VersionDataService } from '../../../core/data/version-data.service';
import { AuthService } from '../../../core/auth/auth.service';
import { NotificationsService } from '../../notifications/notifications.service';
import { VersionDataService } from '../../core/data/version-data.service';
import { AuthService } from '../../core/auth/auth.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { VersionHistoryDataService } from '../../../core/data/version-history-data.service';
import { WorkspaceitemDataService } from '../../../core/submission/workspaceitem-data.service';
import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service';
import { createFailedRemoteDataObject, createSuccessfulRemoteDataObject } from '../../remote-data.utils';
import { Version } from '../../../core/shared/version.model';
import { VersionHistoryDataService } from '../../core/data/version-history-data.service';
import { WorkspaceitemDataService } from '../../core/submission/workspaceitem-data.service';
import { WorkflowItemDataService } from '../../core/submission/workflowitem-data.service';
import { createFailedRemoteDataObject, createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
import { Version } from '../../core/shared/version.model';
describe('ItemVersionsSharedService', () => {
let service: ItemVersionsSharedService;

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@angular/core';
import { NotificationsService } from '../../notifications/notifications.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { RemoteData } from '../../../core/data/remote-data';
import { Version } from '../../../core/shared/version.model';
import { RemoteData } from '../../core/data/remote-data';
import { Version } from '../../core/shared/version.model';
@Injectable({
providedIn: 'root'

View File

@@ -1,7 +1,7 @@
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { BehaviorSubject } from 'rxjs';
import { ModalBeforeDismiss } from '../../../interfaces/modal-before-dismiss.interface';
import { ModalBeforeDismiss } from '../../../shared/interfaces/modal-before-dismiss.interface';
@Component({
selector: 'ds-item-versions-summary-modal',

View File

@@ -2,33 +2,33 @@ import { ItemVersionsComponent } from './item-versions.component';
import {
ComponentFixture, TestBed, waitForAsync
} from '@angular/core/testing';
import { VarDirective } from '../../utils/var.directive';
import { VarDirective } from '../../shared/utils/var.directive';
import { TranslateModule } from '@ngx-translate/core';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { Item } from '../../../core/shared/item.model';
import { Version } from '../../../core/shared/version.model';
import { VersionHistory } from '../../../core/shared/version-history.model';
import { VersionHistoryDataService } from '../../../core/data/version-history-data.service';
import { Item } from '../../core/shared/item.model';
import { Version } from '../../core/shared/version.model';
import { VersionHistory } from '../../core/shared/version-history.model';
import { VersionHistoryDataService } from '../../core/data/version-history-data.service';
import { BrowserModule, By } from '@angular/platform-browser';
import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils';
import { createPaginatedList } from '../../testing/utils.test';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { createPaginatedList } from '../../shared/testing/utils.test';
import { EMPTY, of, of as observableOf } from 'rxjs';
import { PaginationService } from '../../../core/pagination/pagination.service';
import { PaginationServiceStub } from '../../testing/pagination-service.stub';
import { AuthService } from '../../../core/auth/auth.service';
import { VersionDataService } from '../../../core/data/version-data.service';
import { ItemDataService } from '../../../core/data/item-data.service';
import { PaginationService } from '../../core/pagination/pagination.service';
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
import { AuthService } from '../../core/auth/auth.service';
import { VersionDataService } from '../../core/data/version-data.service';
import { ItemDataService } from '../../core/data/item-data.service';
import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NotificationsService } from '../../notifications/notifications.service';
import { NotificationsServiceStub } from '../../testing/notifications-service.stub';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
import { WorkspaceitemDataService } from '../../../core/submission/workspaceitem-data.service';
import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service';
import { ConfigurationDataService } from '../../../core/data/configuration-data.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
import { WorkspaceitemDataService } from '../../core/submission/workspaceitem-data.service';
import { WorkflowItemDataService } from '../../core/submission/workflowitem-data.service';
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
import { Router } from '@angular/router';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { CommonModule } from '@angular/common';
import { ItemSharedModule } from '../item-shared.module';
describe('ItemVersionsComponent', () => {
let component: ItemVersionsComponent;
@@ -137,7 +137,7 @@ describe('ItemVersionsComponent', () => {
TestBed.configureTestingModule({
declarations: [ItemVersionsComponent, VarDirective],
imports: [TranslateModule.forRoot(), CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule],
imports: [TranslateModule.forRoot(), CommonModule, FormsModule, ReactiveFormsModule, BrowserModule, ItemSharedModule],
providers: [
{provide: PaginationService, useValue: new PaginationServiceStub()},
{provide: FormBuilder, useValue: new FormBuilder()},

View File

@@ -1,7 +1,7 @@
import { Component, Input, OnInit } from '@angular/core';
import { Item } from '../../../core/shared/item.model';
import { Version } from '../../../core/shared/version.model';
import { RemoteData } from '../../../core/data/remote-data';
import { Item } from '../../core/shared/item.model';
import { Version } from '../../core/shared/version.model';
import { RemoteData } from '../../core/data/remote-data';
import {
BehaviorSubject,
combineLatest,
@@ -9,7 +9,7 @@ import {
of,
Subscription,
} from 'rxjs';
import { VersionHistory } from '../../../core/shared/version-history.model';
import { VersionHistory } from '../../core/shared/version-history.model';
import {
getAllSucceededRemoteData,
getAllSucceededRemoteDataPayload,
@@ -17,37 +17,37 @@ import {
getFirstSucceededRemoteData,
getFirstSucceededRemoteDataPayload,
getRemoteDataPayload
} from '../../../core/shared/operators';
} from '../../core/shared/operators';
import { map, mergeMap, startWith, switchMap, take, tap } from 'rxjs/operators';
import { PaginatedList } from '../../../core/data/paginated-list.model';
import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model';
import { VersionHistoryDataService } from '../../../core/data/version-history-data.service';
import { PaginatedSearchOptions } from '../../search/models/paginated-search-options.model';
import { AlertType } from '../../alert/aletr-type';
import { followLink } from '../../utils/follow-link-config.model';
import { hasValue, hasValueOperator } from '../../empty.util';
import { PaginationService } from '../../../core/pagination/pagination.service';
import { PaginatedList } from '../../core/data/paginated-list.model';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { VersionHistoryDataService } from '../../core/data/version-history-data.service';
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
import { AlertType } from '../../shared/alert/aletr-type';
import { followLink } from '../../shared/utils/follow-link-config.model';
import { hasValue, hasValueOperator } from '../../shared/empty.util';
import { PaginationService } from '../../core/pagination/pagination.service';
import {
getItemEditVersionhistoryRoute,
getItemPageRoute,
getItemVersionRoute
} from '../../../item-page/item-page-routing-paths';
} from '../item-page-routing-paths';
import { FormBuilder } from '@angular/forms';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { ItemVersionsSummaryModalComponent } from './item-versions-summary-modal/item-versions-summary-modal.component';
import { NotificationsService } from '../../notifications/notifications.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { ItemVersionsDeleteModalComponent } from './item-versions-delete-modal/item-versions-delete-modal.component';
import { VersionDataService } from '../../../core/data/version-data.service';
import { ItemDataService } from '../../../core/data/item-data.service';
import { VersionDataService } from '../../core/data/version-data.service';
import { ItemDataService } from '../../core/data/item-data.service';
import { Router } from '@angular/router';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
import { ItemVersionsSharedService } from './item-versions-shared.service';
import { WorkspaceItem } from '../../../core/submission/models/workspaceitem.model';
import { WorkspaceitemDataService } from '../../../core/submission/workspaceitem-data.service';
import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service';
import { ConfigurationDataService } from '../../../core/data/configuration-data.service';
import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model';
import { WorkspaceitemDataService } from '../../core/submission/workspaceitem-data.service';
import { WorkflowItemDataService } from '../../core/submission/workflowitem-data.service';
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
@Component({
selector: 'ds-item-versions',

View File

@@ -0,0 +1,32 @@
/**
* 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 { SharedModule } from '../../shared/shared.module';
import { ItemVersionsComponent } from './item-versions.component';
import { ItemVersionsNoticeComponent } from './notice/item-versions-notice.component';
const DECLARATIONS = [
ItemVersionsComponent,
ItemVersionsNoticeComponent,
];
@NgModule({
imports: [
SharedModule,
],
declarations: [
...DECLARATIONS,
],
exports: [
...DECLARATIONS,
],
})
export class ItemVersionsModule {
}

View File

@@ -3,13 +3,13 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { VersionHistory } from '../../../../core/shared/version-history.model';
import { Version } from '../../../../core/shared/version.model';
import { Item } from '../../../../core/shared/item.model';
import { VersionHistoryDataService } from '../../../../core/data/version-history-data.service';
import { VersionHistory } from '../../../core/shared/version-history.model';
import { Version } from '../../../core/shared/version.model';
import { Item } from '../../../core/shared/item.model';
import { VersionHistoryDataService } from '../../../core/data/version-history-data.service';
import { By } from '@angular/platform-browser';
import { createSuccessfulRemoteDataObject$ } from '../../../remote-data.utils';
import { createPaginatedList } from '../../../testing/utils.test';
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
import { createPaginatedList } from '../../../shared/testing/utils.test';
import { of } from 'rxjs';
import { take } from 'rxjs/operators';

View File

@@ -1,19 +1,19 @@
import { Component, Input, OnInit } from '@angular/core';
import { Item } from '../../../../core/shared/item.model';
import { Item } from '../../../core/shared/item.model';
import { Observable } from 'rxjs';
import { RemoteData } from '../../../../core/data/remote-data';
import { VersionHistory } from '../../../../core/shared/version-history.model';
import { Version } from '../../../../core/shared/version.model';
import { hasValue, hasValueOperator } from '../../../empty.util';
import { RemoteData } from '../../../core/data/remote-data';
import { VersionHistory } from '../../../core/shared/version-history.model';
import { Version } from '../../../core/shared/version.model';
import { hasValue, hasValueOperator } from '../../../shared/empty.util';
import {
getAllSucceededRemoteData,
getFirstSucceededRemoteDataPayload,
getRemoteDataPayload
} from '../../../../core/shared/operators';
} from '../../../core/shared/operators';
import { map, startWith, switchMap } from 'rxjs/operators';
import { VersionHistoryDataService } from '../../../../core/data/version-history-data.service';
import { AlertType } from '../../../alert/aletr-type';
import { getItemPageRoute } from '../../../../item-page/item-page-routing-paths';
import { VersionHistoryDataService } from '../../../core/data/version-history-data.service';
import { AlertType } from '../../../shared/alert/aletr-type';
import { getItemPageRoute } from '../../item-page-routing-paths';
@Component({
selector: 'ds-item-versions-notice',

View File

@@ -17,9 +17,16 @@ import { PoolSearchResultDetailElementComponent } from '../shared/object-detail/
import { ClaimedApprovedSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component';
import { ClaimedDeclinedSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declined-search-result-list-element.component';
import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module';
import { ItemSubmitterComponent } from '../shared/object-collection/shared/mydspace-item-submitter/item-submitter.component';
import { ItemDetailPreviewComponent } from '../shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component';
import { ItemDetailPreviewFieldComponent } from '../shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview-field/item-detail-preview-field.component';
import { ItemListPreviewComponent } from '../shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component';
import { ThemedItemListPreviewComponent } from '../shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component';
import { MyDSpaceItemStatusComponent } from '../shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component';
import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module';
import { MyDSpaceActionsModule } from '../shared/mydspace-actions/mydspace-actions.module';
const ENTRY_COMPONENTS = [
// put only entry components that use custom decorator
WorkspaceItemSearchResultListElementComponent,
WorkflowItemSearchResultListElementComponent,
ClaimedSearchResultListElementComponent,
@@ -31,7 +38,17 @@ const ENTRY_COMPONENTS = [
WorkflowItemSearchResultDetailElementComponent,
ClaimedTaskSearchResultDetailElementComponent,
PoolSearchResultDetailElementComponent,
ItemSearchResultListElementSubmissionComponent
ItemSearchResultListElementSubmissionComponent,
];
const DECLARATIONS = [
...ENTRY_COMPONENTS,
ItemSubmitterComponent,
ItemDetailPreviewComponent,
ItemDetailPreviewFieldComponent,
ItemListPreviewComponent,
ThemedItemListPreviewComponent,
MyDSpaceItemStatusComponent,
];
@NgModule({
@@ -39,10 +56,12 @@ const ENTRY_COMPONENTS = [
CommonModule,
SharedModule,
MyDspacePageRoutingModule,
ResearchEntitiesModule.withEntryComponents()
MyDSpaceActionsModule,
ResearchEntitiesModule.withEntryComponents(),
JournalEntitiesModule.withEntryComponents(),
],
declarations: [
...ENTRY_COMPONENTS
...DECLARATIONS,
]
})

View File

@@ -1,5 +1,5 @@
nav.navbar {
border-bottom: 1px var(--bs-gray-400) solid;
border-bottom: 1px var(--ds-header-navbar-border-bottom-color) solid;
align-items: baseline;
}

View File

@@ -7,7 +7,6 @@ import { ConfigurationSearchPageGuard } from './configuration-search-page.guard'
import { SearchTrackerComponent } from './search-tracker.component';
import { StatisticsModule } from '../statistics/statistics.module';
import { SearchPageComponent } from './search-page.component';
import { SidebarFilterService } from '../shared/sidebar/filter/sidebar-filter.service';
import { SearchFilterService } from '../core/shared/search/search-filter.service';
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module';
@@ -34,7 +33,6 @@ const components = [
declarations: components,
providers: [
SidebarService,
SidebarFilterService,
SearchFilterService,
ConfigurationSearchPageGuard,
SearchConfigurationService

View File

@@ -49,7 +49,7 @@ const LAZY_KLARO = new InjectionToken<Promise<any>>(
'Lazily loaded Klaro',
{
providedIn: 'root',
factory: async () => (await import('klaro')),
factory: async () => (await import('klaro/dist/klaro-no-translations')),
}
);
@@ -115,7 +115,6 @@ export class BrowserKlaroService extends KlaroService {
if (hideRegistrationVerification) {
servicesToHideArray.push(CAPTCHA_NAME);
}
console.log(servicesToHideArray);
return servicesToHideArray;
})
);

View File

@@ -38,6 +38,8 @@ import { DsDynamicTypeBindRelationService } from './builder/ds-dynamic-form-ui/d
import { FormService } from './form.service';
import { NgxMaskModule } from 'ngx-mask';
import { ThemedExternalSourceEntryImportModalComponent } from './builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/external-source-entry-import-modal/themed-external-source-entry-import-modal.component';
import { NgbDatepickerModule, NgbTimepickerModule } from '@ng-bootstrap/ng-bootstrap';
import { CdkTreeModule } from '@angular/cdk/tree';
const COMPONENTS = [
CustomSwitchComponent,
@@ -86,6 +88,9 @@ const DIRECTIVES = [
TranslateModule,
SortablejsModule,
NgxMaskModule.forRoot(),
NgbDatepickerModule,
NgbTimepickerModule,
CdkTreeModule,
],
exports: [
...COMPONENTS,

View File

@@ -141,7 +141,7 @@ describe('VocabularyTreeviewComponent test suite', () => {
comp.selectedItem = currentValue;
fixture.detectChanges();
expect(comp.dataSource.data).toEqual([]);
expect(vocabularyTreeviewServiceStub.initialize).toHaveBeenCalledWith(comp.vocabularyOptions, new PageInfo(), 'entryID');
expect(vocabularyTreeviewServiceStub.initialize).toHaveBeenCalledWith(comp.vocabularyOptions, new PageInfo(), null);
});
it('should should init component properly with init value as VocabularyEntry', () => {
@@ -153,7 +153,7 @@ describe('VocabularyTreeviewComponent test suite', () => {
comp.selectedItem = currentValue;
fixture.detectChanges();
expect(comp.dataSource.data).toEqual([]);
expect(vocabularyTreeviewServiceStub.initialize).toHaveBeenCalledWith(comp.vocabularyOptions, new PageInfo(), 'entryID');
expect(vocabularyTreeviewServiceStub.initialize).toHaveBeenCalledWith(comp.vocabularyOptions, new PageInfo(), null);
});
it('should call loadMore function', () => {

View File

@@ -2,14 +2,13 @@ import { FlatTreeControl } from '@angular/cdk/tree';
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { filter, find, startWith } from 'rxjs/operators';
import { map } from 'rxjs/operators';
import { Observable, Subscription } from 'rxjs';
import { select, Store } from '@ngrx/store';
import { Store } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import { VocabularyEntryDetail } from '../../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
import { hasValue, isEmpty, isNotEmpty } from '../../empty.util';
import { isAuthenticated } from '../../../core/auth/selectors';
import { VocabularyTreeviewService } from './vocabulary-treeview.service';
import { LOAD_MORE, LOAD_MORE_ROOT, TreeviewFlatNode, TreeviewNode } from './vocabulary-treeview-node.model';
import { VocabularyOptions } from '../../../core/submission/vocabularies/models/vocabulary-options.model';
@@ -18,6 +17,7 @@ import { VocabularyEntry } from '../../../core/submission/vocabularies/models/vo
import { VocabularyTreeFlattener } from './vocabulary-tree-flattener';
import { VocabularyTreeFlatDataSource } from './vocabulary-tree-flat-data-source';
import { CoreState } from '../../../core/core-state.model';
import { lowerCase } from 'lodash/string';
/**
* Component that show a hierarchical vocabulary in a tree view
@@ -203,23 +203,15 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit {
})
);
const descriptionLabel = 'vocabulary-treeview.tree.description.' + this.vocabularyOptions.name;
this.description = this.translate.get(descriptionLabel).pipe(
filter((msg) => msg !== descriptionLabel),
startWith('')
this.translate.get(`search.filters.filter.${this.vocabularyOptions.name}.head`).pipe(
map((type) => lowerCase(type)),
).subscribe(
(type) => this.description = this.translate.get('vocabulary-treeview.info', { type })
);
// set isAuthenticated
this.isAuthenticated = this.store.pipe(select(isAuthenticated));
this.loading = this.vocabularyTreeviewService.isLoading();
this.isAuthenticated.pipe(
find((isAuth) => isAuth)
).subscribe(() => {
const entryId: string = (this.selectedItem) ? this.getEntryId(this.selectedItem) : null;
this.vocabularyTreeviewService.initialize(this.vocabularyOptions, new PageInfo(), entryId);
});
this.vocabularyTreeviewService.initialize(this.vocabularyOptions, new PageInfo(), null);
}
/**

View File

@@ -1,6 +1,6 @@
<ds-themed-loading *ngIf="(loading | async) || (isAuthenticated | async)" class="m-5"></ds-themed-loading>
<div *ngIf="!(loading | async) && !(isAuthenticated | async)" class="px-4 py-3 login-container">
<ng-container *ngFor="let authMethod of (authMethods | async); let i = index">
<ng-container *ngFor="let authMethod of (authMethods); let i = index">
<div *ngIf="i === 1" class="text-center mt-2">
<span class="align-middle">{{"login.form.or-divider" | translate}}</span>
</div>

View File

@@ -14,6 +14,7 @@ import { AuthService } from '../../core/auth/auth.service';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
import { CoreState } from '../../core/core-state.model';
import { AuthMethodType } from '../../core/auth/models/auth.method-type';
/**
* /users/sign-in
@@ -36,7 +37,7 @@ export class LogInComponent implements OnInit {
* The list of authentication methods available
* @type {AuthMethod[]}
*/
public authMethods: Observable<AuthMethod[]>;
public authMethods: AuthMethod[];
/**
* Whether user is authenticated.
@@ -62,9 +63,12 @@ export class LogInComponent implements OnInit {
ngOnInit(): void {
this.authMethods = this.store.pipe(
this.store.pipe(
select(getAuthenticationMethods),
);
).subscribe(methods => {
// ignore the ip authentication method when it's returned by the backend
this.authMethods = methods.filter(a => a.authMethodType !== AuthMethodType.Ip);
});
// set loading
this.loading = this.store.pipe(select(isAuthenticationLoading));

View File

@@ -0,0 +1,3 @@
<button class="btn btn-lg btn-primary btn-block mt-2 text-white" (click)="redirectToExternalProvider()">
<i class="fas fa-sign-in-alt"></i> {{getButtonLabel() | translate}}
</button>

View File

@@ -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<LogInOrcidComponent>;
let component: LogInExternalProviderComponent;
let fixture: ComponentFixture<LogInExternalProviderComponent>;
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<LogInOrcidComponent>) {
constructor(private component: LogInExternalProviderComponent, private fixture: ComponentFixture<LogInExternalProviderComponent>) {
// use injector to get services
const injector = fixture.debugElement.injector;
const store = injector.get(Store);

View File

@@ -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}`;
}
}

View File

@@ -1,3 +0,0 @@
<button class="btn btn-lg btn-primary btn-block mt-2 text-white" (click)="redirectToOidc()">
<i class="fas fa-sign-in-alt"></i> {{"login.form.oidc" | translate}}
</button>

View File

@@ -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<LogInOidcComponent>;
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<LogInOidcComponent>) {
// use injector to get services
const injector = fixture.debugElement.injector;
const store = injector.get(Store);
// add spies
this.navigateSpy = spyOn(store, 'dispatch');
}
}

View File

@@ -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();
}
}

View File

@@ -1,3 +0,0 @@
<button class="btn btn-lg btn-primary btn-block mt-2 text-white" (click)="redirectToOrcid()">
<i class="fas fa-sign-in-alt"></i> {{"login.form.orcid" | translate}}
</button>

View File

@@ -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();
}
}

View File

@@ -1,3 +0,0 @@
<button class="btn btn-lg btn-primary btn-block mt-2 text-white" (click)="redirectToShibboleth()">
<i class="fas fa-sign-in-alt"></i> {{"login.form.shibboleth" | translate}}
</button>

View File

@@ -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<LogInShibbolethComponent>;
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<LogInShibbolethComponent>) {
// use injector to get services
const injector = fixture.debugElement.injector;
const store = injector.get(Store);
// add spies
this.navigateSpy = spyOn(store, 'dispatch');
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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 {
}

View File

@@ -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');
});
});

View File

@@ -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;
}
}

View File

@@ -28,13 +28,19 @@ import { ItemSearchResultGridElementComponent } from './item-search-result-grid-
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
const dcTitle = 'This is just another <em>title</em>';
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: '<em>Michel</em>'
}],
},
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: '<em>Science</em>'
}],
},
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('<em>Michel</em>');
});
});
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('<em>Science</em>');
});
});
});
};
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -13,8 +13,13 @@ import { APP_CONFIG } from '../../../../../../../config/app-config.interface';
let publicationListElementComponent: ItemSearchResultListElementComponent;
let fixture: ComponentFixture<ItemSearchResultListElementComponent>;
const dcTitle = 'This is just another <em>title</em>';
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: '<em>Michel</em>'
}],
},
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: '<em>Science</em>'
}],
},
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('<em>Michel</em>');
});
});
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('<em>Science</em>');
});
});
describe('When the item has no title', () => {
beforeEach(() => {
publicationListElementComponent.object = mockItemWithoutMetadata;

View File

@@ -33,7 +33,7 @@ export class SearchResultListElementComponent<T extends SearchResult<K>, 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);
}
}

View File

@@ -29,3 +29,10 @@
ngDefaultControl
></ds-filter-input-suggestions>
</div>
<a *ngIf="vocabularyExists$ | async"
href="javascript:void(0);"
id="show-{{filterConfig.name}}-tree"
(click)="showVocabularyTree()">
{{'search.filters.filter.show-tree' | translate: {name: ('search.filters.filter.' + filterConfig.name + '.head' | translate | lowercase )} }}
</a>

View File

@@ -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 { 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 { VocabularyEntryDetail } from '../../../../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
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';
import { SearchFilterConfig } from '../../../models/search-filter-config.model';
describe('SearchHierarchyFilterComponent', () => {
let comp: SearchHierarchyFilterComponent;
let fixture: ComponentFixture<SearchHierarchyFilterComponent>;
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<string[]> {
return observableOf(values.map((value: FacetValue) => value.value));
},
getPage(_paramName: string): Observable<number> {
return observableOf(0);
},
resetPage(_filterName: string): void {
// empty
}
const testSearchLink = 'test-search';
const testSearchFilter = 'test-search-filter';
const VocabularyTreeViewComponent = {
select: new EventEmitter<VocabularyEntryDetail>(),
};
const remoteDataBuildServiceStub = {
aggregate(_input: Observable<RemoteData<FacetValue>>[]): Observable<RemoteData<PaginatedList<FacetValue>[]>> {
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: FILTER_CONFIG, useValue: Object.assign(new SearchFilterConfig(), { name: testSearchFilter }) },
{ provide: REFRESH_FILTER, useValue: new BehaviorSubject<boolean>(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
});
function init() {
fixture = TestBed.createComponent(SearchHierarchyFilterComponent);
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();
});
});
describe('if the vocabulary exists', () => {
beforeEach(() => {
spyOn(vocabularyService, 'searchTopEntries').and.returnValue(observableOf(new RemoteData(
undefined, 0, 0, RequestEntryState.Success, undefined, buildPaginatedList(new PageInfo(), []), 200
)));
init();
});
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 () => {
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.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 navigate to the correct filter with the query operator', () => {
expect((comp as any).searchService.getFacetValuesFor).toHaveBeenCalledWith(comp.filterConfig, 0, {}, null, true);
it('should open the vocabulary tree modal', () => {
expect(ngbModal.open).toHaveBeenCalled();
});
const searchQuery = 'MARVEL';
comp.onSubmit(searchQuery);
describe('when selecting a value from the vocabulary tree', () => {
expect(router.navigate).toHaveBeenCalledWith(['', 'search'], Object({
queryParams: Object({ [mockFilterConfig.paramName]: [...values.map((value: FacetValue) => `${value.value},equals`), `${searchQuery},query`] }),
queryParamsHandling: 'merge'
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',
}));
});
});
});
});
});

View File

@@ -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<boolean>
) {
super(searchService, filterService, rdbs, router, searchConfigService, inPlaceSearch, filterConfig, refreshFilters);
}
vocabularyExists$: Observable<boolean>;
/**
* 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;
}
}
}

View File

@@ -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

View File

@@ -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,
]

View File

@@ -1,6 +0,0 @@
<a class="d-flex flex-row" (click)="click.emit($event)">
<label>
<input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/>
<span class="filter-value pl-1 text-capitalize">{{label}}</span>
</label>
</a>

View File

@@ -1,11 +0,0 @@
a {
color: var(--bs-body-color);
&:hover, &focus {
text-decoration: none;
}
span.badge {
vertical-align: text-top;
}
}

View File

@@ -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<any> = new EventEmitter<any>();
}

Some files were not shown because too many files have changed in this diff Show More