mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-16 06:23:03 +00:00
Merge pull request #1972 from the-library-code/TLC-380_browse_links_in_item_pages_PR
Browse links and "by regex" links in item display (Angular)
This commit is contained in:
@@ -2,27 +2,66 @@ import { BrowseDefinitionDataService } from './browse-definition-data.service';
|
|||||||
import { followLink } from '../../shared/utils/follow-link-config.model';
|
import { followLink } from '../../shared/utils/follow-link-config.model';
|
||||||
import { EMPTY } from 'rxjs';
|
import { EMPTY } from 'rxjs';
|
||||||
import { FindListOptions } from '../data/find-list-options.model';
|
import { FindListOptions } from '../data/find-list-options.model';
|
||||||
|
import { getMockRemoteDataBuildService } from '../../shared/mocks/remote-data-build.service.mock';
|
||||||
|
import { RequestService } from '../data/request.service';
|
||||||
|
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
||||||
|
import { getMockObjectCacheService } from '../../shared/mocks/object-cache.service.mock';
|
||||||
|
|
||||||
describe(`BrowseDefinitionDataService`, () => {
|
describe(`BrowseDefinitionDataService`, () => {
|
||||||
|
let requestService: RequestService;
|
||||||
let service: BrowseDefinitionDataService;
|
let service: BrowseDefinitionDataService;
|
||||||
const findAllDataSpy = jasmine.createSpyObj('findAllData', {
|
let findAllDataSpy;
|
||||||
findAll: EMPTY,
|
let searchDataSpy;
|
||||||
});
|
const browsesEndpointURL = 'https://rest.api/browses';
|
||||||
|
const halService: any = new HALEndpointServiceStub(browsesEndpointURL);
|
||||||
|
|
||||||
const options = new FindListOptions();
|
const options = new FindListOptions();
|
||||||
const linksToFollow = [
|
const linksToFollow = [
|
||||||
followLink('entries'),
|
followLink('entries'),
|
||||||
followLink('items')
|
followLink('items')
|
||||||
];
|
];
|
||||||
|
|
||||||
|
function initTestService() {
|
||||||
|
return new BrowseDefinitionDataService(
|
||||||
|
requestService,
|
||||||
|
getMockRemoteDataBuildService(),
|
||||||
|
getMockObjectCacheService(),
|
||||||
|
halService,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
service = new BrowseDefinitionDataService(null, null, null, null);
|
service = initTestService();
|
||||||
|
findAllDataSpy = jasmine.createSpyObj('findAllData', {
|
||||||
|
findAll: EMPTY,
|
||||||
|
});
|
||||||
|
searchDataSpy = jasmine.createSpyObj('searchData', {
|
||||||
|
searchBy: EMPTY,
|
||||||
|
getSearchByHref: EMPTY,
|
||||||
|
});
|
||||||
(service as any).findAllData = findAllDataSpy;
|
(service as any).findAllData = findAllDataSpy;
|
||||||
|
(service as any).searchData = searchDataSpy;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('findByFields', () => {
|
||||||
|
it(`should call searchByHref on searchData`, () => {
|
||||||
|
service.findByFields(['test'], true, false, ...linksToFollow);
|
||||||
|
expect(searchDataSpy.getSearchByHref).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('searchBy', () => {
|
||||||
|
it(`should call searchBy on searchData`, () => {
|
||||||
|
service.searchBy('test', options, true, false, ...linksToFollow);
|
||||||
|
expect(searchDataSpy.searchBy).toHaveBeenCalledWith('test', options, true, false, ...linksToFollow);
|
||||||
|
});
|
||||||
|
});
|
||||||
describe(`findAll`, () => {
|
describe(`findAll`, () => {
|
||||||
it(`should call findAll on findAllData`, () => {
|
it(`should call findAll on findAllData`, () => {
|
||||||
service.findAll(options, true, false, ...linksToFollow);
|
service.findAll(options, true, false, ...linksToFollow);
|
||||||
expect(findAllDataSpy.findAll).toHaveBeenCalledWith(options, true, false, ...linksToFollow);
|
expect(findAllDataSpy.findAll).toHaveBeenCalledWith(options, true, false, ...linksToFollow);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -13,6 +13,8 @@ import { FindListOptions } from '../data/find-list-options.model';
|
|||||||
import { IdentifiableDataService } from '../data/base/identifiable-data.service';
|
import { IdentifiableDataService } from '../data/base/identifiable-data.service';
|
||||||
import { FindAllData, FindAllDataImpl } from '../data/base/find-all-data';
|
import { FindAllData, FindAllDataImpl } from '../data/base/find-all-data';
|
||||||
import { dataService } from '../data/base/data-service.decorator';
|
import { dataService } from '../data/base/data-service.decorator';
|
||||||
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
|
import { SearchData, SearchDataImpl } from '../data/base/search-data';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data service responsible for retrieving browse definitions from the REST server
|
* Data service responsible for retrieving browse definitions from the REST server
|
||||||
@@ -21,8 +23,9 @@ import { dataService } from '../data/base/data-service.decorator';
|
|||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
@dataService(BROWSE_DEFINITION)
|
@dataService(BROWSE_DEFINITION)
|
||||||
export class BrowseDefinitionDataService extends IdentifiableDataService<BrowseDefinition> implements FindAllData<BrowseDefinition> {
|
export class BrowseDefinitionDataService extends IdentifiableDataService<BrowseDefinition> implements FindAllData<BrowseDefinition>, SearchData<BrowseDefinition> {
|
||||||
private findAllData: FindAllDataImpl<BrowseDefinition>;
|
private findAllData: FindAllDataImpl<BrowseDefinition>;
|
||||||
|
private searchData: SearchDataImpl<BrowseDefinition>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
@@ -31,7 +34,7 @@ export class BrowseDefinitionDataService extends IdentifiableDataService<BrowseD
|
|||||||
protected halService: HALEndpointService,
|
protected halService: HALEndpointService,
|
||||||
) {
|
) {
|
||||||
super('browses', requestService, rdbService, objectCache, halService);
|
super('browses', requestService, rdbService, objectCache, halService);
|
||||||
|
this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||||
this.findAllData = new FindAllDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
this.findAllData = new FindAllDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,5 +55,71 @@ export class BrowseDefinitionDataService extends IdentifiableDataService<BrowseD
|
|||||||
findAll(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
|
findAll(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
|
||||||
return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a new FindListRequest with given search method
|
||||||
|
*
|
||||||
|
* @param searchMethod The search method for the object
|
||||||
|
* @param options The [[FindListOptions]] object
|
||||||
|
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||||
|
* no valid cached version. Defaults to true
|
||||||
|
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||||
|
* requested after the response becomes stale
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||||
|
* {@link HALLink}s should be automatically resolved
|
||||||
|
* @return {Observable<RemoteData<PaginatedList<T>>}
|
||||||
|
* Return an observable that emits response from the server
|
||||||
|
*/
|
||||||
|
public searchBy(searchMethod: string, options?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
|
||||||
|
return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the HREF for a specific object's search method with given options object
|
||||||
|
*
|
||||||
|
* @param searchMethod The search method for the object
|
||||||
|
* @param options The [[FindListOptions]] object
|
||||||
|
* @return {Observable<string>}
|
||||||
|
* Return an observable that emits created HREF
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||||
|
*/
|
||||||
|
public getSearchByHref(searchMethod: string, options?: FindListOptions, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<string> {
|
||||||
|
return this.searchData.getSearchByHref(searchMethod, options, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the browse URL by providing a list of metadata keys. The first matching browse index definition
|
||||||
|
* for any of the fields is returned. This is used in eg. item page field component, which can be configured
|
||||||
|
* with several fields for a component like 'Author', and needs to know if and how to link the values
|
||||||
|
* to configured browse indices.
|
||||||
|
*
|
||||||
|
* @param fields an array of field strings, eg. ['dc.contributor.author', 'dc.creator']
|
||||||
|
* @param useCachedVersionIfAvailable Override the data service useCachedVersionIfAvailable parameter (default: true)
|
||||||
|
* @param reRequestOnStale Override the data service reRequestOnStale parameter (default: true)
|
||||||
|
* @param linksToFollow Override the data service linksToFollow parameter (default: empty array)
|
||||||
|
*/
|
||||||
|
findByFields(
|
||||||
|
fields: string[],
|
||||||
|
useCachedVersionIfAvailable = true,
|
||||||
|
reRequestOnStale = true,
|
||||||
|
...linksToFollow: FollowLinkConfig<BrowseDefinition>[]
|
||||||
|
): Observable<RemoteData<BrowseDefinition>> {
|
||||||
|
const searchParams = [];
|
||||||
|
searchParams.push(new RequestParam('fields', fields));
|
||||||
|
|
||||||
|
const hrefObs = this.getSearchByHref(
|
||||||
|
'byFields',
|
||||||
|
{ searchParams },
|
||||||
|
...linksToFollow
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.findByHref(
|
||||||
|
hrefObs,
|
||||||
|
useCachedVersionIfAvailable,
|
||||||
|
reRequestOnStale,
|
||||||
|
...linksToFollow,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -19,9 +19,9 @@ import {
|
|||||||
} from '../shared/operators';
|
} from '../shared/operators';
|
||||||
import { URLCombiner } from '../url-combiner/url-combiner';
|
import { URLCombiner } from '../url-combiner/url-combiner';
|
||||||
import { BrowseEntrySearchOptions } from './browse-entry-search-options.model';
|
import { BrowseEntrySearchOptions } from './browse-entry-search-options.model';
|
||||||
import { BrowseDefinitionDataService } from './browse-definition-data.service';
|
|
||||||
import { HrefOnlyDataService } from '../data/href-only-data.service';
|
import { HrefOnlyDataService } from '../data/href-only-data.service';
|
||||||
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
|
import { BrowseDefinitionDataService } from './browse-definition-data.service';
|
||||||
|
|
||||||
|
|
||||||
export const BROWSE_LINKS_TO_FOLLOW: FollowLinkConfig<BrowseEntry | Item>[] = [
|
export const BROWSE_LINKS_TO_FOLLOW: FollowLinkConfig<BrowseEntry | Item>[] = [
|
||||||
@@ -35,7 +35,7 @@ export const BROWSE_LINKS_TO_FOLLOW: FollowLinkConfig<BrowseEntry | Item>[] = [
|
|||||||
export class BrowseService {
|
export class BrowseService {
|
||||||
protected linkPath = 'browses';
|
protected linkPath = 'browses';
|
||||||
|
|
||||||
private static toSearchKeyArray(metadataKey: string): string[] {
|
public static toSearchKeyArray(metadataKey: string): string[] {
|
||||||
const keyParts = metadataKey.split('.');
|
const keyParts = metadataKey.split('.');
|
||||||
const searchFor = [];
|
const searchFor = [];
|
||||||
searchFor.push('*');
|
searchFor.push('*');
|
||||||
|
@@ -1,11 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* An Enum defining the representation type of metadata
|
* An Enum defining the representation type of metadata
|
||||||
*/
|
*/
|
||||||
|
import { BrowseDefinition } from '../browse-definition.model';
|
||||||
|
|
||||||
export enum MetadataRepresentationType {
|
export enum MetadataRepresentationType {
|
||||||
None = 'none',
|
None = 'none',
|
||||||
Item = 'item',
|
Item = 'item',
|
||||||
AuthorityControlled = 'authority_controlled',
|
AuthorityControlled = 'authority_controlled',
|
||||||
PlainText = 'plain_text'
|
PlainText = 'plain_text',
|
||||||
|
BrowseLink = 'browse_link'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,8 +27,14 @@ export interface MetadataRepresentation {
|
|||||||
*/
|
*/
|
||||||
representationType: MetadataRepresentationType;
|
representationType: MetadataRepresentationType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The browse definition (optional)
|
||||||
|
*/
|
||||||
|
browseDefinition?: BrowseDefinition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the value to be displayed
|
* Fetches the value to be displayed
|
||||||
*/
|
*/
|
||||||
getValue(): string;
|
getValue(): string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { MetadataRepresentation, MetadataRepresentationType } from '../metadata-representation.model';
|
import { MetadataRepresentation, MetadataRepresentationType } from '../metadata-representation.model';
|
||||||
import { hasValue } from '../../../../shared/empty.util';
|
import { hasValue } from '../../../../shared/empty.util';
|
||||||
import { MetadataValue } from '../../metadata.models';
|
import { MetadataValue } from '../../metadata.models';
|
||||||
|
import { BrowseDefinition } from '../../browse-definition.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class defines the way the metadatum it extends should be represented
|
* This class defines the way the metadatum it extends should be represented
|
||||||
@@ -12,9 +13,15 @@ export class MetadatumRepresentation extends MetadataValue implements MetadataRe
|
|||||||
*/
|
*/
|
||||||
itemType: string;
|
itemType: string;
|
||||||
|
|
||||||
constructor(itemType: string) {
|
/**
|
||||||
|
* The browse definition ID passed in with the metadatum, if any
|
||||||
|
*/
|
||||||
|
browseDefinition?: BrowseDefinition;
|
||||||
|
|
||||||
|
constructor(itemType: string, browseDefinition?: BrowseDefinition) {
|
||||||
super();
|
super();
|
||||||
this.itemType = itemType;
|
this.itemType = itemType;
|
||||||
|
this.browseDefinition = browseDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,6 +30,8 @@ export class MetadatumRepresentation extends MetadataValue implements MetadataRe
|
|||||||
get representationType(): MetadataRepresentationType {
|
get representationType(): MetadataRepresentationType {
|
||||||
if (hasValue(this.authority)) {
|
if (hasValue(this.authority)) {
|
||||||
return MetadataRepresentationType.AuthorityControlled;
|
return MetadataRepresentationType.AuthorityControlled;
|
||||||
|
} else if (hasValue(this.browseDefinition)) {
|
||||||
|
return MetadataRepresentationType.BrowseLink;
|
||||||
} else {
|
} else {
|
||||||
return MetadataRepresentationType.PlainText;
|
return MetadataRepresentationType.PlainText;
|
||||||
}
|
}
|
||||||
|
@@ -35,6 +35,10 @@ import { VersionDataService } from '../../../../core/data/version-data.service';
|
|||||||
import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service';
|
import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service';
|
||||||
import { SearchService } from '../../../../core/shared/search/search.service';
|
import { SearchService } from '../../../../core/shared/search/search.service';
|
||||||
import { mockRouteService } from '../../../../item-page/simple/item-types/shared/item.component.spec';
|
import { mockRouteService } from '../../../../item-page/simple/item-types/shared/item.component.spec';
|
||||||
|
import {
|
||||||
|
BrowseDefinitionDataServiceStub
|
||||||
|
} from '../../../../shared/testing/browse-definition-data-service.stub';
|
||||||
|
import { BrowseDefinitionDataService } from '../../../../core/browse/browse-definition-data.service';
|
||||||
|
|
||||||
let comp: JournalComponent;
|
let comp: JournalComponent;
|
||||||
let fixture: ComponentFixture<JournalComponent>;
|
let fixture: ComponentFixture<JournalComponent>;
|
||||||
@@ -100,7 +104,8 @@ describe('JournalComponent', () => {
|
|||||||
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
||||||
{ provide: WorkspaceitemDataService, useValue: {} },
|
{ provide: WorkspaceitemDataService, useValue: {} },
|
||||||
{ provide: SearchService, useValue: {} },
|
{ provide: SearchService, useValue: {} },
|
||||||
{ provide: RouteService, useValue: mockRouteService }
|
{ provide: RouteService, useValue: mockRouteService },
|
||||||
|
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@@ -1,16 +1,38 @@
|
|||||||
<ds-metadata-field-wrapper [label]="label | translate">
|
<ds-metadata-field-wrapper [label]="label | translate">
|
||||||
<ng-container *ngFor="let mdValue of mdValues; let last=last;">
|
<ng-container *ngFor="let mdValue of mdValues; let last=last;">
|
||||||
<ng-container *ngTemplateOutlet="(renderMarkdown ? markdown : simple); context: {value: mdValue.value}">
|
<!--
|
||||||
|
Choose a template. Priority: markdown, link, browse link.
|
||||||
|
-->
|
||||||
|
<ng-container *ngTemplateOutlet="(renderMarkdown ? markdown : (hasLink(mdValue) ? link : (hasBrowseDefinition() ? browselink : simple)));
|
||||||
|
context: {value: mdValue.value}">
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<span class="separator" *ngIf="!last" [innerHTML]="separator"></span>
|
<span class="separator" *ngIf="!last" [innerHTML]="separator"></span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ds-metadata-field-wrapper>
|
</ds-metadata-field-wrapper>
|
||||||
|
|
||||||
|
<!-- Render value as markdown -->
|
||||||
<ng-template #markdown let-value="value">
|
<ng-template #markdown let-value="value">
|
||||||
<span class="dont-break-out" [innerHTML]="value | dsMarkdown | async">
|
<span class="dont-break-out" [innerHTML]="value | dsMarkdown | async">
|
||||||
</span>
|
</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
<!-- Render value as a link (href and label) -->
|
||||||
|
<ng-template #link let-value="value">
|
||||||
|
<a class="dont-break-out ds-simple-metadata-link" target="_blank" [href]="value">
|
||||||
|
{{value}}
|
||||||
|
</a>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<!-- Render simple value in a span -->
|
||||||
<ng-template #simple let-value="value">
|
<ng-template #simple let-value="value">
|
||||||
<span class="dont-break-out preserve-line-breaks">{{value}}</span>
|
<span class="dont-break-out preserve-line-breaks">{{value}}</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
<!-- Render value as a link to browse index -->
|
||||||
|
<ng-template #browselink let-value="value">
|
||||||
|
<a class="dont-break-out preserve-line-breaks ds-browse-link"
|
||||||
|
[routerLink]="['/browse', browseDefinition.id]"
|
||||||
|
[queryParams]="getQueryParams(value)">
|
||||||
|
{{value}}
|
||||||
|
</a>
|
||||||
|
</ng-template>
|
||||||
|
@@ -52,6 +52,7 @@ describe('MetadataValuesComponent', () => {
|
|||||||
comp.mdValues = mockMetadata;
|
comp.mdValues = mockMetadata;
|
||||||
comp.separator = mockSeperator;
|
comp.separator = mockSeperator;
|
||||||
comp.label = mockLabel;
|
comp.label = mockLabel;
|
||||||
|
comp.urlRegex = /^.*test.*$/;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -67,4 +68,9 @@ describe('MetadataValuesComponent', () => {
|
|||||||
expect(separators.length).toBe(mockMetadata.length - 1);
|
expect(separators.length).toBe(mockMetadata.length - 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should correctly detect a pattern on string containing "test"', () => {
|
||||||
|
const mdValue = {value: 'This is a test value'} as MetadataValue;
|
||||||
|
expect(comp.hasLink(mdValue)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import { Component, Inject, Input, OnChanges, SimpleChanges } from '@angular/core';
|
import { Component, Inject, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
import { MetadataValue } from '../../../core/shared/metadata.models';
|
import { MetadataValue } from '../../../core/shared/metadata.models';
|
||||||
import { APP_CONFIG, AppConfig } from '../../../../config/app-config.interface';
|
import { APP_CONFIG, AppConfig } from '../../../../config/app-config.interface';
|
||||||
|
import { BrowseDefinition } from '../../../core/shared/browse-definition.model';
|
||||||
|
import { hasValue } from '../../../shared/empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders the configured 'values' into the ds-metadata-field-wrapper component.
|
* This component renders the configured 'values' into the ds-metadata-field-wrapper component.
|
||||||
@@ -40,12 +42,51 @@ export class MetadataValuesComponent implements OnChanges {
|
|||||||
*/
|
*/
|
||||||
@Input() enableMarkdown = false;
|
@Input() enableMarkdown = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether any valid HTTP(S) URL should be rendered as a link
|
||||||
|
*/
|
||||||
|
@Input() urlRegex?;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This variable will be true if both {@link environment.markdown.enabled} and {@link enableMarkdown} are true.
|
* This variable will be true if both {@link environment.markdown.enabled} and {@link enableMarkdown} are true.
|
||||||
*/
|
*/
|
||||||
renderMarkdown;
|
renderMarkdown;
|
||||||
|
|
||||||
|
@Input() browseDefinition?: BrowseDefinition;
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
this.renderMarkdown = !!this.appConfig.markdown.enabled && this.enableMarkdown;
|
this.renderMarkdown = !!this.appConfig.markdown.enabled && this.enableMarkdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this metadata value have a configured link to a browse definition?
|
||||||
|
*/
|
||||||
|
hasBrowseDefinition(): boolean {
|
||||||
|
return hasValue(this.browseDefinition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this metadata value have a valid URL that should be rendered as a link?
|
||||||
|
* @param value A MetadataValue being displayed
|
||||||
|
*/
|
||||||
|
hasLink(value: MetadataValue): boolean {
|
||||||
|
if (hasValue(this.urlRegex)) {
|
||||||
|
const pattern = new RegExp(this.urlRegex);
|
||||||
|
return pattern.test(value.value);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a queryparams object for use in a link, with the key dependent on whether this browse
|
||||||
|
* definition is metadata browse, or item browse
|
||||||
|
* @param value the specific metadata value being linked
|
||||||
|
*/
|
||||||
|
getQueryParams(value) {
|
||||||
|
let queryParams = {startsWith: value};
|
||||||
|
if (this.browseDefinition.metadataBrowse) {
|
||||||
|
return {value: value};
|
||||||
|
}
|
||||||
|
return queryParams;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,8 @@ import { SharedModule } from '../../../../../shared/shared.module';
|
|||||||
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
|
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
|
||||||
import { environment } from '../../../../../../environments/environment';
|
import { environment } from '../../../../../../environments/environment';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { BrowseDefinitionDataService } from '../../../../../core/browse/browse-definition-data.service';
|
||||||
|
import { BrowseDefinitionDataServiceStub } from '../../../../../shared/testing/browse-definition-data-service.stub';
|
||||||
|
|
||||||
let comp: ItemPageAbstractFieldComponent;
|
let comp: ItemPageAbstractFieldComponent;
|
||||||
let fixture: ComponentFixture<ItemPageAbstractFieldComponent>;
|
let fixture: ComponentFixture<ItemPageAbstractFieldComponent>;
|
||||||
@@ -25,6 +27,7 @@ describe('ItemPageAbstractFieldComponent', () => {
|
|||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: APP_CONFIG, useValue: environment },
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
|
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }
|
||||||
],
|
],
|
||||||
declarations: [ItemPageAbstractFieldComponent],
|
declarations: [ItemPageAbstractFieldComponent],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@@ -3,10 +3,12 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
|||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
|
import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
|
||||||
import { MetadataValuesComponent } from '../../../../field-components/metadata-values/metadata-values.component';
|
import { MetadataValuesComponent } from '../../../../field-components/metadata-values/metadata-values.component';
|
||||||
import { mockItemWithMetadataFieldAndValue } from '../item-page-field.component.spec';
|
import { mockItemWithMetadataFieldsAndValue } from '../item-page-field.component.spec';
|
||||||
import { ItemPageAuthorFieldComponent } from './item-page-author-field.component';
|
import { ItemPageAuthorFieldComponent } from './item-page-author-field.component';
|
||||||
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
|
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
|
||||||
import { environment } from '../../../../../../environments/environment';
|
import { environment } from '../../../../../../environments/environment';
|
||||||
|
import { BrowseDefinitionDataService } from '../../../../../core/browse/browse-definition-data.service';
|
||||||
|
import { BrowseDefinitionDataServiceStub } from '../../../../../shared/testing/browse-definition-data-service.stub';
|
||||||
|
|
||||||
let comp: ItemPageAuthorFieldComponent;
|
let comp: ItemPageAuthorFieldComponent;
|
||||||
let fixture: ComponentFixture<ItemPageAuthorFieldComponent>;
|
let fixture: ComponentFixture<ItemPageAuthorFieldComponent>;
|
||||||
@@ -25,6 +27,7 @@ describe('ItemPageAuthorFieldComponent', () => {
|
|||||||
})],
|
})],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: APP_CONFIG, useValue: environment },
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
|
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }
|
||||||
],
|
],
|
||||||
declarations: [ItemPageAuthorFieldComponent, MetadataValuesComponent],
|
declarations: [ItemPageAuthorFieldComponent, MetadataValuesComponent],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@@ -37,7 +40,7 @@ describe('ItemPageAuthorFieldComponent', () => {
|
|||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
fixture = TestBed.createComponent(ItemPageAuthorFieldComponent);
|
fixture = TestBed.createComponent(ItemPageAuthorFieldComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
comp.item = mockItemWithMetadataFieldAndValue(field, mockValue);
|
comp.item = mockItemWithMetadataFieldsAndValue([field], mockValue);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@@ -3,10 +3,12 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
|||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
|
import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
|
||||||
import { MetadataValuesComponent } from '../../../../field-components/metadata-values/metadata-values.component';
|
import { MetadataValuesComponent } from '../../../../field-components/metadata-values/metadata-values.component';
|
||||||
import { mockItemWithMetadataFieldAndValue } from '../item-page-field.component.spec';
|
import { mockItemWithMetadataFieldsAndValue } from '../item-page-field.component.spec';
|
||||||
import { ItemPageDateFieldComponent } from './item-page-date-field.component';
|
import { ItemPageDateFieldComponent } from './item-page-date-field.component';
|
||||||
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
|
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
|
||||||
import { environment } from '../../../../../../environments/environment';
|
import { environment } from '../../../../../../environments/environment';
|
||||||
|
import { BrowseDefinitionDataService } from '../../../../../core/browse/browse-definition-data.service';
|
||||||
|
import { BrowseDefinitionDataServiceStub } from '../../../../../shared/testing/browse-definition-data-service.stub';
|
||||||
|
|
||||||
let comp: ItemPageDateFieldComponent;
|
let comp: ItemPageDateFieldComponent;
|
||||||
let fixture: ComponentFixture<ItemPageDateFieldComponent>;
|
let fixture: ComponentFixture<ItemPageDateFieldComponent>;
|
||||||
@@ -25,6 +27,7 @@ describe('ItemPageDateFieldComponent', () => {
|
|||||||
})],
|
})],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: APP_CONFIG, useValue: environment },
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
|
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }
|
||||||
],
|
],
|
||||||
declarations: [ItemPageDateFieldComponent, MetadataValuesComponent],
|
declarations: [ItemPageDateFieldComponent, MetadataValuesComponent],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@@ -36,7 +39,7 @@ describe('ItemPageDateFieldComponent', () => {
|
|||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
fixture = TestBed.createComponent(ItemPageDateFieldComponent);
|
fixture = TestBed.createComponent(ItemPageDateFieldComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
comp.item = mockItemWithMetadataFieldAndValue(mockField, mockValue);
|
comp.item = mockItemWithMetadataFieldsAndValue([mockField], mockValue);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@@ -3,10 +3,12 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
|||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
|
import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
|
||||||
import { MetadataValuesComponent } from '../../../../field-components/metadata-values/metadata-values.component';
|
import { MetadataValuesComponent } from '../../../../field-components/metadata-values/metadata-values.component';
|
||||||
import { mockItemWithMetadataFieldAndValue } from '../item-page-field.component.spec';
|
import { mockItemWithMetadataFieldsAndValue } from '../item-page-field.component.spec';
|
||||||
import { GenericItemPageFieldComponent } from './generic-item-page-field.component';
|
import { GenericItemPageFieldComponent } from './generic-item-page-field.component';
|
||||||
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
|
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
|
||||||
import { environment } from '../../../../../../environments/environment';
|
import { environment } from '../../../../../../environments/environment';
|
||||||
|
import { BrowseDefinitionDataService } from '../../../../../core/browse/browse-definition-data.service';
|
||||||
|
import { BrowseDefinitionDataServiceStub } from '../../../../../shared/testing/browse-definition-data-service.stub';
|
||||||
|
|
||||||
let comp: GenericItemPageFieldComponent;
|
let comp: GenericItemPageFieldComponent;
|
||||||
let fixture: ComponentFixture<GenericItemPageFieldComponent>;
|
let fixture: ComponentFixture<GenericItemPageFieldComponent>;
|
||||||
@@ -27,6 +29,7 @@ describe('GenericItemPageFieldComponent', () => {
|
|||||||
})],
|
})],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: APP_CONFIG, useValue: environment },
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
|
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }
|
||||||
],
|
],
|
||||||
declarations: [GenericItemPageFieldComponent, MetadataValuesComponent],
|
declarations: [GenericItemPageFieldComponent, MetadataValuesComponent],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@@ -38,7 +41,7 @@ describe('GenericItemPageFieldComponent', () => {
|
|||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
fixture = TestBed.createComponent(GenericItemPageFieldComponent);
|
fixture = TestBed.createComponent(GenericItemPageFieldComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
comp.item = mockItemWithMetadataFieldAndValue(mockField, mockValue);
|
comp.item = mockItemWithMetadataFieldsAndValue([mockField], mockValue);
|
||||||
comp.fields = mockFields;
|
comp.fields = mockFields;
|
||||||
comp.label = mockLabel;
|
comp.label = mockLabel;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
@@ -40,5 +40,10 @@ export class GenericItemPageFieldComponent extends ItemPageFieldComponent {
|
|||||||
*/
|
*/
|
||||||
@Input() enableMarkdown = false;
|
@Input() enableMarkdown = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether any valid HTTP(S) URL should be rendered as a link
|
||||||
|
*/
|
||||||
|
@Input() urlRegex?: string;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -4,5 +4,7 @@
|
|||||||
[separator]="separator"
|
[separator]="separator"
|
||||||
[label]="label"
|
[label]="label"
|
||||||
[enableMarkdown]="enableMarkdown"
|
[enableMarkdown]="enableMarkdown"
|
||||||
|
[urlRegex]="urlRegex"
|
||||||
|
[browseDefinition]="browseDefinition|async"
|
||||||
></ds-metadata-values>
|
></ds-metadata-values>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -12,6 +12,10 @@ import { environment } from '../../../../../environments/environment';
|
|||||||
import { MarkdownPipe } from '../../../../shared/utils/markdown.pipe';
|
import { MarkdownPipe } from '../../../../shared/utils/markdown.pipe';
|
||||||
import { SharedModule } from '../../../../shared/shared.module';
|
import { SharedModule } from '../../../../shared/shared.module';
|
||||||
import { APP_CONFIG } from '../../../../../config/app-config.interface';
|
import { APP_CONFIG } from '../../../../../config/app-config.interface';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { BrowseDefinitionDataService } from '../../../../core/browse/browse-definition-data.service';
|
||||||
|
import { BrowseDefinitionDataServiceStub } from '../../../../shared/testing/browse-definition-data-service.stub';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
|
||||||
let comp: ItemPageFieldComponent;
|
let comp: ItemPageFieldComponent;
|
||||||
let fixture: ComponentFixture<ItemPageFieldComponent>;
|
let fixture: ComponentFixture<ItemPageFieldComponent>;
|
||||||
@@ -20,7 +24,9 @@ let markdownSpy;
|
|||||||
const mockValue = 'test value';
|
const mockValue = 'test value';
|
||||||
const mockField = 'dc.test';
|
const mockField = 'dc.test';
|
||||||
const mockLabel = 'test label';
|
const mockLabel = 'test label';
|
||||||
const mockFields = [mockField];
|
const mockAuthorField = 'dc.contributor.author';
|
||||||
|
const mockDateIssuedField = 'dc.date.issued';
|
||||||
|
const mockFields = [mockField, mockAuthorField, mockDateIssuedField];
|
||||||
|
|
||||||
describe('ItemPageFieldComponent', () => {
|
describe('ItemPageFieldComponent', () => {
|
||||||
|
|
||||||
@@ -34,6 +40,7 @@ describe('ItemPageFieldComponent', () => {
|
|||||||
const buildTestEnvironment = async () => {
|
const buildTestEnvironment = async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
RouterTestingModule.withRoutes([]),
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
loader: {
|
loader: {
|
||||||
provide: TranslateLoader,
|
provide: TranslateLoader,
|
||||||
@@ -44,6 +51,7 @@ describe('ItemPageFieldComponent', () => {
|
|||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: APP_CONFIG, useValue: appConfig },
|
{ provide: APP_CONFIG, useValue: appConfig },
|
||||||
|
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }
|
||||||
],
|
],
|
||||||
declarations: [ItemPageFieldComponent, MetadataValuesComponent],
|
declarations: [ItemPageFieldComponent, MetadataValuesComponent],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@@ -53,7 +61,7 @@ describe('ItemPageFieldComponent', () => {
|
|||||||
markdownSpy = spyOn(MarkdownPipe.prototype, 'transform');
|
markdownSpy = spyOn(MarkdownPipe.prototype, 'transform');
|
||||||
fixture = TestBed.createComponent(ItemPageFieldComponent);
|
fixture = TestBed.createComponent(ItemPageFieldComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
comp.item = mockItemWithMetadataFieldAndValue(mockField, mockValue);
|
comp.item = mockItemWithMetadataFieldsAndValue(mockFields, mockValue);
|
||||||
comp.fields = mockFields;
|
comp.fields = mockFields;
|
||||||
comp.label = mockLabel;
|
comp.label = mockLabel;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -126,17 +134,57 @@ describe('ItemPageFieldComponent', () => {
|
|||||||
expect(markdownSpy).toHaveBeenCalled();
|
expect(markdownSpy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('test rendering of configured browse links', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
waitForAsync(() => {
|
||||||
|
it('should have a browse link', () => {
|
||||||
|
expect(fixture.debugElement.query(By.css('a.ds-browse-link')).nativeElement.innerHTML).toContain(mockValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('test rendering of configured regex-based links', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.urlRegex = '^test';
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
waitForAsync(() => {
|
||||||
|
it('should have a rendered (non-browse) link since the value matches ^test', () => {
|
||||||
|
expect(fixture.debugElement.query(By.css('a.ds-simple-metadata-link')).nativeElement.innerHTML).toContain(mockValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('test skipping of configured links that do NOT match regex', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.urlRegex = '^nope';
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
it('should NOT have a rendered (non-browse) link since the value matches ^test', () => {
|
||||||
|
expect(fixture.debugElement.query(By.css('a.ds-simple-metadata-link'))).toBeNull();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export function mockItemWithMetadataFieldAndValue(field: string, value: string): Item {
|
export function mockItemWithMetadataFieldsAndValue(fields: string[], value: string): Item {
|
||||||
const item = Object.assign(new Item(), {
|
const item = Object.assign(new Item(), {
|
||||||
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
||||||
metadata: new MetadataMap()
|
metadata: new MetadataMap()
|
||||||
});
|
});
|
||||||
item.metadata[field] = [{
|
fields.forEach((field: string) => {
|
||||||
language: 'en_US',
|
item.metadata[field] = [{
|
||||||
value: value
|
language: 'en_US',
|
||||||
}] as MetadataValue[];
|
value: value
|
||||||
|
}] as MetadataValue[];
|
||||||
|
});
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { BrowseDefinition } from '../../../../core/shared/browse-definition.model';
|
||||||
|
import { BrowseDefinitionDataService } from '../../../../core/browse/browse-definition-data.service';
|
||||||
|
import { getRemoteDataPayload } from '../../../../core/shared/operators';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component can be used to represent metadata on a simple item page.
|
* This component can be used to represent metadata on a simple item page.
|
||||||
@@ -12,6 +17,9 @@ import { Item } from '../../../../core/shared/item.model';
|
|||||||
})
|
})
|
||||||
export class ItemPageFieldComponent {
|
export class ItemPageFieldComponent {
|
||||||
|
|
||||||
|
constructor(protected browseDefinitionDataService: BrowseDefinitionDataService) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The item to display metadata for
|
* The item to display metadata for
|
||||||
*/
|
*/
|
||||||
@@ -38,4 +46,19 @@ export class ItemPageFieldComponent {
|
|||||||
*/
|
*/
|
||||||
separator = '<br/>';
|
separator = '<br/>';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether any valid HTTP(S) URL should be rendered as a link
|
||||||
|
*/
|
||||||
|
urlRegex?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return browse definition that matches any field used in this component if it is configured as a browse
|
||||||
|
* link in dspace.cfg (webui.browse.link.<n>)
|
||||||
|
*/
|
||||||
|
get browseDefinition(): Observable<BrowseDefinition> {
|
||||||
|
return this.browseDefinitionDataService.findByFields(this.fields).pipe(
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
map((def) => def)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
|||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
|
import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
|
||||||
import { MetadataValuesComponent } from '../../../../field-components/metadata-values/metadata-values.component';
|
import { MetadataValuesComponent } from '../../../../field-components/metadata-values/metadata-values.component';
|
||||||
import { mockItemWithMetadataFieldAndValue } from '../item-page-field.component.spec';
|
import { mockItemWithMetadataFieldsAndValue } from '../item-page-field.component.spec';
|
||||||
import { ItemPageTitleFieldComponent } from './item-page-title-field.component';
|
import { ItemPageTitleFieldComponent } from './item-page-title-field.component';
|
||||||
|
|
||||||
let comp: ItemPageTitleFieldComponent;
|
let comp: ItemPageTitleFieldComponent;
|
||||||
@@ -31,7 +31,7 @@ describe('ItemPageTitleFieldComponent', () => {
|
|||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
fixture = TestBed.createComponent(ItemPageTitleFieldComponent);
|
fixture = TestBed.createComponent(ItemPageTitleFieldComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
comp.item = mockItemWithMetadataFieldAndValue(mockField, mockValue);
|
comp.item = mockItemWithMetadataFieldsAndValue([mockField], mockValue);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@@ -2,11 +2,13 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
|||||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
|
import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
|
||||||
import { mockItemWithMetadataFieldAndValue } from '../item-page-field.component.spec';
|
import { mockItemWithMetadataFieldsAndValue } from '../item-page-field.component.spec';
|
||||||
import { ItemPageUriFieldComponent } from './item-page-uri-field.component';
|
import { ItemPageUriFieldComponent } from './item-page-uri-field.component';
|
||||||
import { MetadataUriValuesComponent } from '../../../../field-components/metadata-uri-values/metadata-uri-values.component';
|
import { MetadataUriValuesComponent } from '../../../../field-components/metadata-uri-values/metadata-uri-values.component';
|
||||||
import { environment } from '../../../../../../environments/environment';
|
import { environment } from '../../../../../../environments/environment';
|
||||||
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
|
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
|
||||||
|
import { BrowseDefinitionDataService } from '../../../../../core/browse/browse-definition-data.service';
|
||||||
|
import { BrowseDefinitionDataServiceStub } from '../../../../../shared/testing/browse-definition-data-service.stub';
|
||||||
|
|
||||||
let comp: ItemPageUriFieldComponent;
|
let comp: ItemPageUriFieldComponent;
|
||||||
let fixture: ComponentFixture<ItemPageUriFieldComponent>;
|
let fixture: ComponentFixture<ItemPageUriFieldComponent>;
|
||||||
@@ -26,6 +28,7 @@ describe('ItemPageUriFieldComponent', () => {
|
|||||||
})],
|
})],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: APP_CONFIG, useValue: environment },
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
|
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }
|
||||||
],
|
],
|
||||||
declarations: [ItemPageUriFieldComponent, MetadataUriValuesComponent],
|
declarations: [ItemPageUriFieldComponent, MetadataUriValuesComponent],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@@ -37,7 +40,7 @@ describe('ItemPageUriFieldComponent', () => {
|
|||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
fixture = TestBed.createComponent(ItemPageUriFieldComponent);
|
fixture = TestBed.createComponent(ItemPageUriFieldComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
comp.item = mockItemWithMetadataFieldAndValue(mockField, mockValue);
|
comp.item = mockItemWithMetadataFieldsAndValue([mockField], mockValue);
|
||||||
comp.fields = [mockField];
|
comp.fields = [mockField];
|
||||||
comp.label = mockLabel;
|
comp.label = mockLabel;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
@@ -36,6 +36,10 @@ import { VersionDataService } from '../../../../core/data/version-data.service';
|
|||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service';
|
import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service';
|
||||||
import { SearchService } from '../../../../core/shared/search/search.service';
|
import { SearchService } from '../../../../core/shared/search/search.service';
|
||||||
|
import { BrowseDefinitionDataService } from '../../../../core/browse/browse-definition-data.service';
|
||||||
|
import {
|
||||||
|
BrowseDefinitionDataServiceStub
|
||||||
|
} from '../../../../shared/testing/browse-definition-data-service.stub';
|
||||||
|
|
||||||
const noMetadata = new MetadataMap();
|
const noMetadata = new MetadataMap();
|
||||||
|
|
||||||
@@ -87,7 +91,8 @@ describe('PublicationComponent', () => {
|
|||||||
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
||||||
{ provide: WorkspaceitemDataService, useValue: {} },
|
{ provide: WorkspaceitemDataService, useValue: {} },
|
||||||
{ provide: SearchService, useValue: {} },
|
{ provide: SearchService, useValue: {} },
|
||||||
{ provide: RouteService, useValue: mockRouteService }
|
{ provide: RouteService, useValue: mockRouteService },
|
||||||
|
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub },
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@@ -23,7 +23,9 @@ import { UUIDService } from '../../../../core/shared/uuid.service';
|
|||||||
import { isNotEmpty } from '../../../../shared/empty.util';
|
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||||
import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock';
|
import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock';
|
||||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
|
import {
|
||||||
|
createSuccessfulRemoteDataObject$
|
||||||
|
} from '../../../../shared/remote-data.utils';
|
||||||
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
|
||||||
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
||||||
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
|
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
|
||||||
@@ -38,6 +40,10 @@ import { VersionHistoryDataService } from '../../../../core/data/version-history
|
|||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
|
||||||
import { ResearcherProfileDataService } from '../../../../core/profile/researcher-profile-data.service';
|
import { ResearcherProfileDataService } from '../../../../core/profile/researcher-profile-data.service';
|
||||||
|
import { BrowseDefinitionDataService } from '../../../../core/browse/browse-definition-data.service';
|
||||||
|
import {
|
||||||
|
BrowseDefinitionDataServiceStub
|
||||||
|
} from '../../../../shared/testing/browse-definition-data-service.stub';
|
||||||
|
|
||||||
import { buildPaginatedList } from '../../../../core/data/paginated-list.model';
|
import { buildPaginatedList } from '../../../../core/data/paginated-list.model';
|
||||||
import { PageInfo } from '../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
@@ -125,7 +131,8 @@ export function getItemPageFieldsTest(mockItem: Item, component) {
|
|||||||
{ provide: SearchService, useValue: {} },
|
{ provide: SearchService, useValue: {} },
|
||||||
{ provide: RouteService, useValue: mockRouteService },
|
{ provide: RouteService, useValue: mockRouteService },
|
||||||
{ provide: AuthorizationDataService, useValue: authorizationService },
|
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||||
{ provide: ResearcherProfileDataService, useValue: {} }
|
{ provide: ResearcherProfileDataService, useValue: {} },
|
||||||
|
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub },
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@@ -444,7 +451,7 @@ describe('ItemComponent', () => {
|
|||||||
{ provide: SearchService, useValue: {} },
|
{ provide: SearchService, useValue: {} },
|
||||||
{ provide: RouteService, useValue: mockRouteService },
|
{ provide: RouteService, useValue: mockRouteService },
|
||||||
{ provide: AuthorizationDataService, useValue: {} },
|
{ provide: AuthorizationDataService, useValue: {} },
|
||||||
{ provide: ResearcherProfileDataService, useValue: {} }
|
{ provide: ResearcherProfileDataService, useValue: {} },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).overrideComponent(ItemComponent, {
|
}).overrideComponent(ItemComponent, {
|
||||||
|
@@ -37,6 +37,10 @@ import { RouterTestingModule } from '@angular/router/testing';
|
|||||||
import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service';
|
import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service';
|
||||||
import { SearchService } from '../../../../core/shared/search/search.service';
|
import { SearchService } from '../../../../core/shared/search/search.service';
|
||||||
import { ItemVersionsSharedService } from '../../../versions/item-versions-shared.service';
|
import { ItemVersionsSharedService } from '../../../versions/item-versions-shared.service';
|
||||||
|
import { BrowseDefinitionDataService } from '../../../../core/browse/browse-definition-data.service';
|
||||||
|
import {
|
||||||
|
BrowseDefinitionDataServiceStub
|
||||||
|
} from '../../../../shared/testing/browse-definition-data-service.stub';
|
||||||
|
|
||||||
const noMetadata = new MetadataMap();
|
const noMetadata = new MetadataMap();
|
||||||
|
|
||||||
@@ -90,7 +94,8 @@ describe('UntypedItemComponent', () => {
|
|||||||
{ provide: SearchService, useValue: {} },
|
{ provide: SearchService, useValue: {} },
|
||||||
{ provide: ItemDataService, useValue: {} },
|
{ provide: ItemDataService, useValue: {} },
|
||||||
{ provide: ItemVersionsSharedService, useValue: {} },
|
{ provide: ItemVersionsSharedService, useValue: {} },
|
||||||
{ provide: RouteService, useValue: mockRouteService }
|
{ provide: RouteService, useValue: mockRouteService },
|
||||||
|
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).overrideComponent(UntypedItemComponent, {
|
}).overrideComponent(UntypedItemComponent, {
|
||||||
|
@@ -77,7 +77,7 @@ describe('VersionedItemComponent', () => {
|
|||||||
{ provide: WorkspaceitemDataService, useValue: {} },
|
{ provide: WorkspaceitemDataService, useValue: {} },
|
||||||
{ provide: SearchService, useValue: {} },
|
{ provide: SearchService, useValue: {} },
|
||||||
{ provide: ItemDataService, useValue: {} },
|
{ provide: ItemDataService, useValue: {} },
|
||||||
{ provide: RouteService, useValue: mockRouteService }
|
{ provide: RouteService, useValue: mockRouteService },
|
||||||
]
|
]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
versionService = TestBed.inject(VersionDataService);
|
versionService = TestBed.inject(VersionDataService);
|
||||||
|
@@ -35,7 +35,7 @@ export class VersionedItemComponent extends ItemComponent {
|
|||||||
private workspaceItemDataService: WorkspaceitemDataService,
|
private workspaceItemDataService: WorkspaceitemDataService,
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private itemService: ItemDataService,
|
private itemService: ItemDataService,
|
||||||
protected routeService: RouteService,
|
protected routeService: RouteService
|
||||||
) {
|
) {
|
||||||
super(routeService, router);
|
super(routeService, router);
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,8 @@ import { MetadataValue } from '../../../core/shared/metadata.models';
|
|||||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
|
import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
|
||||||
import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
||||||
|
import { BrowseDefinitionDataService } from '../../../core/browse/browse-definition-data.service';
|
||||||
|
import { BrowseDefinitionDataServiceStub } from '../../../shared/testing/browse-definition-data-service.stub';
|
||||||
|
|
||||||
const itemType = 'Person';
|
const itemType = 'Person';
|
||||||
const metadataFields = ['dc.contributor.author', 'dc.creator'];
|
const metadataFields = ['dc.contributor.author', 'dc.creator'];
|
||||||
@@ -104,7 +106,8 @@ describe('MetadataRepresentationListComponent', () => {
|
|||||||
imports: [TranslateModule.forRoot()],
|
imports: [TranslateModule.forRoot()],
|
||||||
declarations: [MetadataRepresentationListComponent, VarDirective],
|
declarations: [MetadataRepresentationListComponent, VarDirective],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: RelationshipDataService, useValue: relationshipService }
|
{ provide: RelationshipDataService, useValue: relationshipService },
|
||||||
|
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).overrideComponent(MetadataRepresentationListComponent, {
|
}).overrideComponent(MetadataRepresentationListComponent, {
|
||||||
|
@@ -8,6 +8,13 @@ import { RelationshipDataService } from '../../../core/data/relationship-data.se
|
|||||||
import { MetadataValue } from '../../../core/shared/metadata.models';
|
import { MetadataValue } from '../../../core/shared/metadata.models';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { AbstractIncrementalListComponent } from '../abstract-incremental-list/abstract-incremental-list.component';
|
import { AbstractIncrementalListComponent } from '../abstract-incremental-list/abstract-incremental-list.component';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { getRemoteDataPayload } from '../../../core/shared/operators';
|
||||||
|
import {
|
||||||
|
MetadatumRepresentation
|
||||||
|
} from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
||||||
|
import { BrowseService } from '../../../core/browse/browse.service';
|
||||||
|
import { BrowseDefinitionDataService } from '../../../core/browse/browse-definition-data.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-metadata-representation-list',
|
selector: 'ds-metadata-representation-list',
|
||||||
@@ -52,7 +59,8 @@ export class MetadataRepresentationListComponent extends AbstractIncrementalList
|
|||||||
*/
|
*/
|
||||||
total: number;
|
total: number;
|
||||||
|
|
||||||
constructor(public relationshipService: RelationshipDataService) {
|
constructor(public relationshipService: RelationshipDataService,
|
||||||
|
private browseDefinitionDataService: BrowseDefinitionDataService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +84,21 @@ export class MetadataRepresentationListComponent extends AbstractIncrementalList
|
|||||||
...metadata
|
...metadata
|
||||||
.slice((this.objects.length * this.incrementBy), (this.objects.length * this.incrementBy) + this.incrementBy)
|
.slice((this.objects.length * this.incrementBy), (this.objects.length * this.incrementBy) + this.incrementBy)
|
||||||
.map((metadatum: any) => Object.assign(new MetadataValue(), metadatum))
|
.map((metadatum: any) => Object.assign(new MetadataValue(), metadatum))
|
||||||
.map((metadatum: MetadataValue) => this.relationshipService.resolveMetadataRepresentation(metadatum, this.parentItem, this.itemType)),
|
.map((metadatum: MetadataValue) => {
|
||||||
|
if (metadatum.isVirtual) {
|
||||||
|
return this.relationshipService.resolveMetadataRepresentation(metadatum, this.parentItem, this.itemType);
|
||||||
|
} else {
|
||||||
|
// Check for a configured browse link and return a standard metadata representation
|
||||||
|
let searchKeyArray: string[] = [];
|
||||||
|
this.metadataFields.forEach((field: string) => {
|
||||||
|
searchKeyArray = searchKeyArray.concat(BrowseService.toSearchKeyArray(field));
|
||||||
|
});
|
||||||
|
return this.browseDefinitionDataService.findByFields(this.metadataFields).pipe(
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
map((def) => Object.assign(new MetadatumRepresentation(this.itemType, def), metadatum))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
<div>
|
||||||
|
<a *ngIf="(metadataRepresentation.representationType=='browse_link')"
|
||||||
|
target="_blank" class="dont-break-out"
|
||||||
|
[routerLink]="['/browse/', metadataRepresentation.browseDefinition.id]"
|
||||||
|
[queryParams]="getQueryParams()">
|
||||||
|
{{metadataRepresentation.getValue()}}
|
||||||
|
</a>
|
||||||
|
<b>(new browse link page)</b>
|
||||||
|
</div>
|
@@ -0,0 +1,62 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { BrowseLinkMetadataListElementComponent } from './browse-link-metadata-list-element.component';
|
||||||
|
import { MetadatumRepresentation } from '../../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
||||||
|
|
||||||
|
const mockMetadataRepresentation = Object.assign(new MetadatumRepresentation('type'), {
|
||||||
|
key: 'dc.contributor.author',
|
||||||
|
value: 'Test Author'
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockMetadataRepresentationWithUrl = Object.assign(new MetadatumRepresentation('type'), {
|
||||||
|
key: 'dc.subject',
|
||||||
|
value: 'http://purl.org/test/subject'
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('BrowseLinkMetadataListElementComponent', () => {
|
||||||
|
let comp: BrowseLinkMetadataListElementComponent;
|
||||||
|
let fixture: ComponentFixture<BrowseLinkMetadataListElementComponent>;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [BrowseLinkMetadataListElementComponent],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(BrowseLinkMetadataListElementComponent, {
|
||||||
|
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
fixture = TestBed.createComponent(BrowseLinkMetadataListElementComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.metadataRepresentation = mockMetadataRepresentation;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
waitForAsync(() => {
|
||||||
|
it('should contain the value as a browse link', () => {
|
||||||
|
expect(fixture.debugElement.nativeElement.textContent).toContain(mockMetadataRepresentation.value);
|
||||||
|
});
|
||||||
|
it('should NOT match isLink', () => {
|
||||||
|
expect(comp.isLink).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
fixture = TestBed.createComponent(BrowseLinkMetadataListElementComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.metadataRepresentation = mockMetadataRepresentationWithUrl;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
waitForAsync(() => {
|
||||||
|
it('should contain the value expected', () => {
|
||||||
|
expect(fixture.debugElement.nativeElement.textContent).toContain(mockMetadataRepresentationWithUrl.value);
|
||||||
|
});
|
||||||
|
it('should match isLink', () => {
|
||||||
|
expect(comp.isLink).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,29 @@
|
|||||||
|
import { MetadataRepresentationType } from '../../../../core/shared/metadata-representation/metadata-representation.model';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { MetadataRepresentationListElementComponent } from '../metadata-representation-list-element.component';
|
||||||
|
import { metadataRepresentationComponent } from '../../../metadata-representation/metadata-representation.decorator';
|
||||||
|
//@metadataRepresentationComponent('Publication', MetadataRepresentationType.PlainText)
|
||||||
|
// For now, authority controlled fields are rendered the same way as plain text fields
|
||||||
|
//@metadataRepresentationComponent('Publication', MetadataRepresentationType.AuthorityControlled)
|
||||||
|
@metadataRepresentationComponent('Publication', MetadataRepresentationType.BrowseLink)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-browse-link-metadata-list-element',
|
||||||
|
templateUrl: './browse-link-metadata-list-element.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* A component for displaying MetadataRepresentation objects in the form of plain text
|
||||||
|
* It will simply use the value retrieved from MetadataRepresentation.getValue() to display as plain text
|
||||||
|
*/
|
||||||
|
export class BrowseLinkMetadataListElementComponent extends MetadataRepresentationListElementComponent {
|
||||||
|
/**
|
||||||
|
* Get the appropriate query parameters for this browse link, depending on whether the browse definition
|
||||||
|
* expects 'startsWith' (eg browse by date) or 'value' (eg browse by title)
|
||||||
|
*/
|
||||||
|
getQueryParams() {
|
||||||
|
let queryParams = {startsWith: this.metadataRepresentation.getValue()};
|
||||||
|
if (this.metadataRepresentation.browseDefinition.metadataBrowse) {
|
||||||
|
return {value: this.metadataRepresentation.getValue()};
|
||||||
|
}
|
||||||
|
return queryParams;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,59 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
||||||
|
import { mockData } from '../../testing/browse-definition-data-service.stub';
|
||||||
|
import { MetadataRepresentationListElementComponent } from './metadata-representation-list-element.component';
|
||||||
|
|
||||||
|
// Mock metadata representation values
|
||||||
|
const mockMetadataRepresentation = Object.assign(new MetadatumRepresentation('type', mockData[1]), {
|
||||||
|
key: 'dc.contributor.author',
|
||||||
|
value: 'Test Author'
|
||||||
|
});
|
||||||
|
const mockMetadataRepresentationUrl = Object.assign(new MetadatumRepresentation('type', mockData[1]), {
|
||||||
|
key: 'dc.subject',
|
||||||
|
value: 'https://www.google.com'
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('MetadataRepresentationListElementComponent', () => {
|
||||||
|
let comp: MetadataRepresentationListElementComponent;
|
||||||
|
let fixture: ComponentFixture<MetadataRepresentationListElementComponent>;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [MetadataRepresentationListElementComponent],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(MetadataRepresentationListElementComponent, {
|
||||||
|
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
fixture = TestBed.createComponent(MetadataRepresentationListElementComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('when the value is not a URL', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.metadataRepresentation = mockMetadataRepresentation;
|
||||||
|
});
|
||||||
|
it('isLink correctly detects a non-URL string as false', () => {
|
||||||
|
waitForAsync(() => {
|
||||||
|
expect(comp.isLink()).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the value is a URL', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.metadataRepresentation = mockMetadataRepresentationUrl;
|
||||||
|
});
|
||||||
|
it('isLink correctly detects a URL string as true', () => {
|
||||||
|
waitForAsync(() => {
|
||||||
|
expect(comp.isLink()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -13,4 +13,14 @@ export class MetadataRepresentationListElementComponent {
|
|||||||
* The metadata representation of this component
|
* The metadata representation of this component
|
||||||
*/
|
*/
|
||||||
metadataRepresentation: MetadataRepresentation;
|
metadataRepresentation: MetadataRepresentation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this component's value matches a basic regex "Is this an HTTP URL" test
|
||||||
|
*/
|
||||||
|
isLink(): boolean {
|
||||||
|
// Match any string that begins with http:// or https://
|
||||||
|
const linkPattern = new RegExp(/^https?\/\/.*/);
|
||||||
|
return linkPattern.test(this.metadataRepresentation.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,17 @@
|
|||||||
<div>
|
<div>
|
||||||
<span class="dont-break-out">{{metadataRepresentation.getValue()}}</span>
|
<!-- Because this template is used by default, we will additionally test for representation type and display accordingly -->
|
||||||
|
<span *ngIf="(metadataRepresentation.representationType=='plain_text') && !isLink()" class="dont-break-out">
|
||||||
|
{{metadataRepresentation.getValue()}}
|
||||||
|
</span>
|
||||||
|
<a *ngIf="(metadataRepresentation.representationType=='plain_text') && isLink()" class="dont-break-out"
|
||||||
|
target="_blank" [href]="metadataRepresentation.getValue()">
|
||||||
|
{{metadataRepresentation.getValue()}}
|
||||||
|
</a>
|
||||||
|
<span *ngIf="(metadataRepresentation.representationType=='authority_controlled')" class="dont-break-out">{{metadataRepresentation.getValue()}}</span>
|
||||||
|
<a *ngIf="(metadataRepresentation.representationType=='browse_link')"
|
||||||
|
class="dont-break-out ds-browse-link"
|
||||||
|
[routerLink]="['/browse/', metadataRepresentation.browseDefinition.id]"
|
||||||
|
[queryParams]="getQueryParams()">
|
||||||
|
{{metadataRepresentation.getValue()}}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -2,8 +2,12 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
|||||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { PlainTextMetadataListElementComponent } from './plain-text-metadata-list-element.component';
|
import { PlainTextMetadataListElementComponent } from './plain-text-metadata-list-element.component';
|
||||||
import { MetadatumRepresentation } from '../../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
import { MetadatumRepresentation } from '../../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { mockData } from '../../../testing/browse-definition-data-service.stub';
|
||||||
|
|
||||||
const mockMetadataRepresentation = Object.assign(new MetadatumRepresentation('type'), {
|
// Render the mock representation with the default mock author browse definition so it is also rendered as a link
|
||||||
|
// without affecting other tests
|
||||||
|
const mockMetadataRepresentation = Object.assign(new MetadatumRepresentation('type', mockData[1]), {
|
||||||
key: 'dc.contributor.author',
|
key: 'dc.contributor.author',
|
||||||
value: 'Test Author'
|
value: 'Test Author'
|
||||||
});
|
});
|
||||||
@@ -33,4 +37,8 @@ describe('PlainTextMetadataListElementComponent', () => {
|
|||||||
expect(fixture.debugElement.nativeElement.textContent).toContain(mockMetadataRepresentation.value);
|
expect(fixture.debugElement.nativeElement.textContent).toContain(mockMetadataRepresentation.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should contain the browse link as plain text', () => {
|
||||||
|
expect(fixture.debugElement.query(By.css('a.ds-browse-link')).nativeElement.innerHTML).toContain(mockMetadataRepresentation.value);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -15,4 +15,15 @@ import { metadataRepresentationComponent } from '../../../metadata-representatio
|
|||||||
* It will simply use the value retrieved from MetadataRepresentation.getValue() to display as plain text
|
* It will simply use the value retrieved from MetadataRepresentation.getValue() to display as plain text
|
||||||
*/
|
*/
|
||||||
export class PlainTextMetadataListElementComponent extends MetadataRepresentationListElementComponent {
|
export class PlainTextMetadataListElementComponent extends MetadataRepresentationListElementComponent {
|
||||||
|
/**
|
||||||
|
* Get the appropriate query parameters for this browse link, depending on whether the browse definition
|
||||||
|
* expects 'startsWith' (eg browse by date) or 'value' (eg browse by title)
|
||||||
|
*/
|
||||||
|
getQueryParams() {
|
||||||
|
let queryParams = {startsWith: this.metadataRepresentation.getValue()};
|
||||||
|
if (this.metadataRepresentation.browseDefinition.metadataBrowse) {
|
||||||
|
return {value: this.metadataRepresentation.getValue()};
|
||||||
|
}
|
||||||
|
return queryParams;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -84,6 +84,8 @@ import { LangSwitchComponent } from './lang-switch/lang-switch.component';
|
|||||||
import {
|
import {
|
||||||
PlainTextMetadataListElementComponent
|
PlainTextMetadataListElementComponent
|
||||||
} from './object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component';
|
} from './object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component';
|
||||||
|
import { BrowseLinkMetadataListElementComponent }
|
||||||
|
from './object-list/metadata-representation-list-element/browse-link/browse-link-metadata-list-element.component';
|
||||||
import {
|
import {
|
||||||
ItemMetadataListElementComponent
|
ItemMetadataListElementComponent
|
||||||
} from './object-list/metadata-representation-list-element/item/item-metadata-list-element.component';
|
} from './object-list/metadata-representation-list-element/item/item-metadata-list-element.component';
|
||||||
@@ -383,6 +385,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
EditItemSelectorComponent,
|
EditItemSelectorComponent,
|
||||||
ThemedEditItemSelectorComponent,
|
ThemedEditItemSelectorComponent,
|
||||||
PlainTextMetadataListElementComponent,
|
PlainTextMetadataListElementComponent,
|
||||||
|
BrowseLinkMetadataListElementComponent,
|
||||||
ItemMetadataListElementComponent,
|
ItemMetadataListElementComponent,
|
||||||
MetadataRepresentationListElementComponent,
|
MetadataRepresentationListElementComponent,
|
||||||
ItemMetadataRepresentationListElementComponent,
|
ItemMetadataRepresentationListElementComponent,
|
||||||
|
@@ -0,0 +1,63 @@
|
|||||||
|
import { EMPTY, Observable, of as observableOf } from 'rxjs';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model';
|
||||||
|
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
||||||
|
import { BrowseService } from '../../core/browse/browse.service';
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../remote-data.utils';
|
||||||
|
import { PageInfo } from '../../core/shared/page-info.model';
|
||||||
|
|
||||||
|
// This data is in post-serialized form (metadata -> metadataKeys)
|
||||||
|
export const mockData: BrowseDefinition[] = [
|
||||||
|
Object.assign(new BrowseDefinition, {
|
||||||
|
'id' : 'dateissued',
|
||||||
|
'metadataBrowse' : false,
|
||||||
|
'dataType' : 'date',
|
||||||
|
'sortOptions' : EMPTY,
|
||||||
|
'order' : 'ASC',
|
||||||
|
'type' : 'browse',
|
||||||
|
'metadataKeys' : [ 'dc.date.issued' ],
|
||||||
|
'_links' : EMPTY
|
||||||
|
}),
|
||||||
|
Object.assign(new BrowseDefinition, {
|
||||||
|
'id' : 'author',
|
||||||
|
'metadataBrowse' : true,
|
||||||
|
'dataType' : 'text',
|
||||||
|
'sortOptions' : EMPTY,
|
||||||
|
'order' : 'ASC',
|
||||||
|
'type' : 'browse',
|
||||||
|
'metadataKeys' : [ 'dc.contributor.*', 'dc.creator' ],
|
||||||
|
'_links' : EMPTY
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
export const BrowseDefinitionDataServiceStub: any = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all BrowseDefinitions
|
||||||
|
*/
|
||||||
|
findAll(): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
|
||||||
|
return observableOf(createSuccessfulRemoteDataObject(buildPaginatedList(new PageInfo(), mockData)));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all BrowseDefinitions with any link configuration
|
||||||
|
*/
|
||||||
|
findAllLinked(): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
|
||||||
|
return observableOf(createSuccessfulRemoteDataObject(buildPaginatedList(new PageInfo(), mockData)));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the browse URL by providing a list of metadata keys
|
||||||
|
*
|
||||||
|
* @param metadataKeys a list of fields eg. ['dc.contributor.author', 'dc.creator']
|
||||||
|
*/
|
||||||
|
findByFields(metadataKeys: string[]): Observable<RemoteData<BrowseDefinition>> {
|
||||||
|
let searchKeyArray: string[] = [];
|
||||||
|
metadataKeys.forEach((metadataKey) => {
|
||||||
|
searchKeyArray = searchKeyArray.concat(BrowseService.toSearchKeyArray(metadataKey));
|
||||||
|
});
|
||||||
|
// Return just the first, as a pretend match
|
||||||
|
return observableOf(createSuccessfulRemoteDataObject(mockData[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
Reference in New Issue
Block a user