diff --git a/src/app/entity-groups/journal-entities/item-list-elements/sidebar-search-list-elements/journal-issue/journal-issue-sidebar-search-list-element.component.ts b/src/app/entity-groups/journal-entities/item-list-elements/sidebar-search-list-elements/journal-issue/journal-issue-sidebar-search-list-element.component.ts new file mode 100644 index 0000000000..026a9be15c --- /dev/null +++ b/src/app/entity-groups/journal-entities/item-list-elements/sidebar-search-list-elements/journal-issue/journal-issue-sidebar-search-list-element.component.ts @@ -0,0 +1,38 @@ +import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { Context } from '../../../../../core/shared/context.model'; +import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; +import { Component } from '@angular/core'; +import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component'; +import { Item } from '../../../../../core/shared/item.model'; +import { isNotEmpty } from '../../../../../shared/empty.util'; + +@listableObjectComponent('JournalIssueSearchResult', ViewMode.ListElement, Context.SideBarSearchModal) +@Component({ + selector: 'ds-journal-issue-sidebar-search-list-element', + templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html' +}) +/** + * Component displaying a list element for a {@link ItemSearchResult} of type "JournalIssue" within the context of + * a sidebar search modal + */ +export class JournalIssueSidebarSearchListElementComponent extends SidebarSearchListElementComponent { + /** + * Get the description of the Journal Issue by returning its volume number(s) and/or issue number(s) + */ + getDescription(): string { + const volumeNumbers = this.allMetadataValues(['publicationvolume.volumeNumber']); + const issueNumbers = this.allMetadataValues(['publicationissue.issueNumber']); + let description = ''; + if (isNotEmpty(volumeNumbers)) { + description += volumeNumbers.join(', '); + } + if (isNotEmpty(description) && isNotEmpty(issueNumbers)) { + description += ' - '; + } + if (isNotEmpty(issueNumbers)) { + description += issueNumbers.join(', '); + } + return this.undefinedIfEmpty(description); + } +} diff --git a/src/app/entity-groups/journal-entities/item-list-elements/sidebar-search-list-elements/journal-volume/journal-volume-sidebar-search-list-element.component.ts b/src/app/entity-groups/journal-entities/item-list-elements/sidebar-search-list-elements/journal-volume/journal-volume-sidebar-search-list-element.component.ts new file mode 100644 index 0000000000..ce99d14406 --- /dev/null +++ b/src/app/entity-groups/journal-entities/item-list-elements/sidebar-search-list-elements/journal-volume/journal-volume-sidebar-search-list-element.component.ts @@ -0,0 +1,38 @@ +import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { Context } from '../../../../../core/shared/context.model'; +import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; +import { Component } from '@angular/core'; +import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component'; +import { Item } from '../../../../../core/shared/item.model'; +import { isNotEmpty } from '../../../../../shared/empty.util'; + +@listableObjectComponent('JournalVolumeSearchResult', ViewMode.ListElement, Context.SideBarSearchModal) +@Component({ + selector: 'ds-journal-volume-sidebar-search-list-element', + templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html' +}) +/** + * Component displaying a list element for a {@link ItemSearchResult} of type "JournalVolume" within the context of + * a sidebar search modal + */ +export class JournalVolumeSidebarSearchListElementComponent extends SidebarSearchListElementComponent { + /** + * Get the description of the Journal Volume by returning the journal title and volume number(s) (between parentheses) + */ + getDescription(): string { + const titles = this.allMetadataValues(['journal.title']); + const numbers = this.allMetadataValues(['publicationvolume.volumeNumber']); + let description = ''; + if (isNotEmpty(titles)) { + description += titles.join(', '); + } + if (isNotEmpty(numbers)) { + if (isNotEmpty(description)) { + description += ' '; + } + description += numbers.map((n) => `(${n})`).join(' '); + } + return this.undefinedIfEmpty(description); + } +} diff --git a/src/app/entity-groups/journal-entities/item-list-elements/sidebar-search-list-elements/journal/journal-sidebar-search-list-element.component.ts b/src/app/entity-groups/journal-entities/item-list-elements/sidebar-search-list-elements/journal/journal-sidebar-search-list-element.component.ts new file mode 100644 index 0000000000..f222298ee3 --- /dev/null +++ b/src/app/entity-groups/journal-entities/item-list-elements/sidebar-search-list-elements/journal/journal-sidebar-search-list-element.component.ts @@ -0,0 +1,31 @@ +import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { Context } from '../../../../../core/shared/context.model'; +import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; +import { Component } from '@angular/core'; +import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component'; +import { Item } from '../../../../../core/shared/item.model'; +import { isNotEmpty } from '../../../../../shared/empty.util'; + +@listableObjectComponent('JournalSearchResult', ViewMode.ListElement, Context.SideBarSearchModal) +@Component({ + selector: 'ds-journal-sidebar-search-list-element', + templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html' +}) +/** + * Component displaying a list element for a {@link ItemSearchResult} of type "Journal" within the context of + * a sidebar search modal + */ +export class JournalSidebarSearchListElementComponent extends SidebarSearchListElementComponent { + /** + * Get the description of the Journal by returning its ISSN(s) + */ + getDescription(): string { + const issns = this.allMetadataValues(['creativeworkseries.issn']); + let description = ''; + if (isNotEmpty(issns)) { + description += issns.join(', '); + } + return this.undefinedIfEmpty(description); + } +} diff --git a/src/app/entity-groups/journal-entities/journal-entities.module.ts b/src/app/entity-groups/journal-entities/journal-entities.module.ts index d00eae1e54..11ce6c4c2a 100644 --- a/src/app/entity-groups/journal-entities/journal-entities.module.ts +++ b/src/app/entity-groups/journal-entities/journal-entities.module.ts @@ -18,6 +18,9 @@ import { JournalIssueSearchResultListElementComponent } from './item-list-elemen import { JournalVolumeSearchResultListElementComponent } from './item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component'; import { JournalIssueSearchResultGridElementComponent } from './item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component'; import { JournalVolumeSearchResultGridElementComponent } from './item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component'; +import { JournalVolumeSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal-volume/journal-volume-sidebar-search-list-element.component'; +import { JournalIssueSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal-issue/journal-issue-sidebar-search-list-element.component'; +import { JournalSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal/journal-sidebar-search-list-element.component'; const ENTRY_COMPONENTS = [ JournalComponent, @@ -34,7 +37,10 @@ const ENTRY_COMPONENTS = [ JournalVolumeSearchResultListElementComponent, JournalIssueSearchResultGridElementComponent, JournalVolumeSearchResultGridElementComponent, - JournalSearchResultGridElementComponent + JournalSearchResultGridElementComponent, + JournalVolumeSidebarSearchListElementComponent, + JournalIssueSidebarSearchListElementComponent, + JournalSidebarSearchListElementComponent, ]; @NgModule({ diff --git a/src/app/entity-groups/research-entities/item-list-elements/sidebar-search-list-elements/org-unit/org-unit-sidebar-search-list-element.component.ts b/src/app/entity-groups/research-entities/item-list-elements/sidebar-search-list-elements/org-unit/org-unit-sidebar-search-list-element.component.ts new file mode 100644 index 0000000000..54fed3125f --- /dev/null +++ b/src/app/entity-groups/research-entities/item-list-elements/sidebar-search-list-elements/org-unit/org-unit-sidebar-search-list-element.component.ts @@ -0,0 +1,33 @@ +import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { Context } from '../../../../../core/shared/context.model'; +import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; +import { Component } from '@angular/core'; +import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component'; +import { Item } from '../../../../../core/shared/item.model'; +import { isNotEmpty } from '../../../../../shared/empty.util'; + +@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.SideBarSearchModal) +@Component({ + selector: 'ds-org-unit-sidebar-search-list-element', + templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html' +}) +/** + * Component displaying a list element for a {@link ItemSearchResult} of type "OrgUnit" within the context of + * a sidebar search modal + */ +export class OrgUnitSidebarSearchListElementComponent extends SidebarSearchListElementComponent { + /** + * Get the title of the Org Unit by returning its legal name + */ + getTitle(): string { + return this.firstMetadataValue('organization.legalName'); + } + + /** + * Get the description of the Org Unit by returning its dc.description + */ + getDescription(): string { + return this.firstMetadataValue('dc.description'); + } +} diff --git a/src/app/entity-groups/research-entities/item-list-elements/sidebar-search-list-elements/person/person-sidebar-search-list-element.component.ts b/src/app/entity-groups/research-entities/item-list-elements/sidebar-search-list-elements/person/person-sidebar-search-list-element.component.ts new file mode 100644 index 0000000000..9cbf66d040 --- /dev/null +++ b/src/app/entity-groups/research-entities/item-list-elements/sidebar-search-list-elements/person/person-sidebar-search-list-element.component.ts @@ -0,0 +1,59 @@ +import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { Context } from '../../../../../core/shared/context.model'; +import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; +import { Component } from '@angular/core'; +import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component'; +import { Item } from '../../../../../core/shared/item.model'; +import { isNotEmpty } from '../../../../../shared/empty.util'; +import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; +import { LinkService } from '../../../../../core/cache/builders/link.service'; +import { TranslateService } from '@ngx-translate/core'; + +@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModal) +@Component({ + selector: 'ds-person-sidebar-search-list-element', + templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html' +}) +/** + * Component displaying a list element for a {@link ItemSearchResult} of type "Person" within the context of + * a sidebar search modal + */ +export class PersonSidebarSearchListElementComponent extends SidebarSearchListElementComponent { + constructor(protected truncatableService: TruncatableService, + protected linkService: LinkService, + protected translateService: TranslateService) { + super(truncatableService, linkService); + } + + /** + * Get the title of the Person by returning a combination of its family name and given name (or "No name found") + */ + getTitle(): string { + const familyName = this.firstMetadataValue('person.familyName'); + const givenName = this.firstMetadataValue('person.givenName'); + let title = ''; + if (isNotEmpty(familyName)) { + title = familyName; + } + if (isNotEmpty(title)) { + title += ', '; + } + if (isNotEmpty(givenName)) { + title += givenName; + } + return this.defaultIfEmpty(title, this.translateService.instant('person.listelement.no-title')); + } + + /** + * Get the description of the Person by returning its job title(s) + */ + getDescription(): string { + const titles = this.allMetadataValues(['person.jobTitle']); + let description = ''; + if (isNotEmpty(titles)) { + description += titles.join(', '); + } + return this.undefinedIfEmpty(description); + } +} diff --git a/src/app/entity-groups/research-entities/item-list-elements/sidebar-search-list-elements/project/project-sidebar-search-list-element.component.ts b/src/app/entity-groups/research-entities/item-list-elements/sidebar-search-list-elements/project/project-sidebar-search-list-element.component.ts new file mode 100644 index 0000000000..00124cf497 --- /dev/null +++ b/src/app/entity-groups/research-entities/item-list-elements/sidebar-search-list-elements/project/project-sidebar-search-list-element.component.ts @@ -0,0 +1,26 @@ +import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { Context } from '../../../../../core/shared/context.model'; +import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; +import { Component } from '@angular/core'; +import { SidebarSearchListElementComponent } from '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component'; +import { Item } from '../../../../../core/shared/item.model'; +import { isNotEmpty } from '../../../../../shared/empty.util'; + +@listableObjectComponent('ProjectSearchResult', ViewMode.ListElement, Context.SideBarSearchModal) +@Component({ + selector: 'ds-project-sidebar-search-list-element', + templateUrl: '../../../../../shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html' +}) +/** + * Component displaying a list element for a {@link ItemSearchResult} of type "Project" within the context of + * a sidebar search modal + */ +export class ProjectSidebarSearchListElementComponent extends SidebarSearchListElementComponent { + /** + * Projects currently don't support a description + */ + getDescription(): string { + return undefined; + } +} diff --git a/src/app/entity-groups/research-entities/research-entities.module.ts b/src/app/entity-groups/research-entities/research-entities.module.ts index cef3b4539b..1f50ab830a 100644 --- a/src/app/entity-groups/research-entities/research-entities.module.ts +++ b/src/app/entity-groups/research-entities/research-entities.module.ts @@ -26,6 +26,9 @@ import { NameVariantModalComponent } from './submission/name-variant-modal/name- import { OrgUnitInputSuggestionsComponent } from './submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component'; import { OrgUnitSearchResultListSubmissionElementComponent } from './submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component'; import { ExternalSourceEntryListSubmissionElementComponent } from './submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component'; +import { OrgUnitSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/org-unit/org-unit-sidebar-search-list-element.component'; +import { PersonSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/person/person-sidebar-search-list-element.component'; +import { ProjectSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/project/project-sidebar-search-list-element.component'; const ENTRY_COMPONENTS = [ OrgUnitComponent, @@ -50,7 +53,10 @@ const ENTRY_COMPONENTS = [ NameVariantModalComponent, OrgUnitSearchResultListSubmissionElementComponent, OrgUnitInputSuggestionsComponent, - ExternalSourceEntryListSubmissionElementComponent + ExternalSourceEntryListSubmissionElementComponent, + OrgUnitSidebarSearchListElementComponent, + PersonSidebarSearchListElementComponent, + ProjectSidebarSearchListElementComponent, ]; @NgModule({ diff --git a/src/app/shared/object-list/sidebar-search-list-element/item-types/publication/publication-sidebar-search-list-element.component.html b/src/app/shared/object-list/sidebar-search-list-element/item-types/publication/publication-sidebar-search-list-element.component.html deleted file mode 100644 index be25f1af49..0000000000 --- a/src/app/shared/object-list/sidebar-search-list-element/item-types/publication/publication-sidebar-search-list-element.component.html +++ /dev/null @@ -1 +0,0 @@ -Test display for sidebar-search list elements diff --git a/src/app/shared/object-list/sidebar-search-list-element/item-types/publication/publication-sidebar-search-list-element.component.ts b/src/app/shared/object-list/sidebar-search-list-element/item-types/publication/publication-sidebar-search-list-element.component.ts index 49724a9309..fb79a4924e 100644 --- a/src/app/shared/object-list/sidebar-search-list-element/item-types/publication/publication-sidebar-search-list-element.component.ts +++ b/src/app/shared/object-list/sidebar-search-list-element/item-types/publication/publication-sidebar-search-list-element.component.ts @@ -12,6 +12,10 @@ import { SidebarSearchListElementComponent } from '../../sidebar-search-list-ele selector: 'ds-publication-sidebar-search-list-element', templateUrl: '../../sidebar-search-list-element.component.html' }) +/** + * Component displaying a list element for a {@link ItemSearchResult} of type "Publication" within the context of + * a sidebar search modal + */ export class PublicationSidebarSearchListElementComponent extends SidebarSearchListElementComponent { } diff --git a/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html b/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html index b0fe1e58d2..adcf23dcf7 100644 --- a/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html +++ b/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html @@ -1,3 +1,3 @@ -
{{ parentTitle$ | async }}
-
{{ title }}
-
{{ description }}
+
{{ parentTitle$ | async }}
+
{{ title }}
+
{{ description }}
diff --git a/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.ts b/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.ts index 0489f9f3b7..71547854f9 100644 --- a/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.ts +++ b/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.ts @@ -2,7 +2,7 @@ import { SearchResult } from '../../search/search-result.model'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { SearchResultListElementComponent } from '../search-result-list-element/search-result-list-element.component'; import { Component } from '@angular/core'; -import { hasValue } from '../../empty.util'; +import { hasValue, isNotEmpty } from '../../empty.util'; import { Observable } from 'rxjs/internal/Observable'; import { TruncatableService } from '../../truncatable/truncatable.service'; import { LinkService } from '../../../core/cache/builders/link.service'; @@ -10,14 +10,31 @@ import { find, map } from 'rxjs/operators'; import { ChildHALResource } from '../../../core/shared/child-hal-resource.model'; import { followLink } from '../../utils/follow-link-config.model'; import { RemoteData } from '../../../core/data/remote-data'; +import { of as observableOf } from 'rxjs'; @Component({ selector: 'ds-sidebar-search-list-element', templateUrl: './sidebar-search-list-element.component.html' }) +/** + * Component displaying a list element for a {@link SearchResult} in the sidebar search modal + * It displays the name of the parent, title and description of the object. All of which are customizable in the child + * component by overriding the relevant methods of this component + */ export class SidebarSearchListElementComponent, K extends DSpaceObject> extends SearchResultListElementComponent { + /** + * Observable for the title of the parent object (displayed above the object's title) + */ parentTitle$: Observable; + + /** + * The title for the object to display + */ title: string; + + /** + * A description to display below the title + */ description: string; public constructor(protected truncatableService: TruncatableService, @@ -25,6 +42,9 @@ export class SidebarSearchListElementComponent, K exte super(truncatableService); } + /** + * Initialise the component variables + */ ngOnInit(): void { super.ngOnInit(); if (hasValue(this.dso)) { @@ -34,23 +54,92 @@ export class SidebarSearchListElementComponent, K exte } } + /** + * Get the title of the object's parent + * Retrieve the parent by using the object's parent link and retrieving its 'dc.title' metadata + */ + getParentTitle(): Observable { + return this.getParent().pipe( + map((parentRD: RemoteData) => { + return parentRD ? parentRD.payload.firstMetadataValue('dc.title') : undefined; + }) + ); + } + + /** + * Get the parent of the object + */ + getParent(): Observable> { + if (typeof (this.dso as any).getParentLinkKey === 'function') { + const propertyName = (this.dso as any).getParentLinkKey(); + return this.linkService.resolveLink(this.dso, followLink(propertyName))[propertyName].pipe( + find((parentRD: RemoteData) => parentRD.hasSucceeded || parentRD.statusCode === 204) + ); + } + return observableOf(undefined); + } + + /** + * Get the title of the object + * Default: "dc.title" + */ getTitle(): string { return this.firstMetadataValue('dc.title'); } + /** + * Get the description of the object + * Default: "(dc.publisher, dc.date.issued) authors" + */ getDescription(): string { - // TODO: Expand description - return this.firstMetadataValue('dc.publisher'); + const publisher = this.firstMetadataValue('dc.publisher'); + const date = this.firstMetadataValue('dc.date.issued'); + const authors = this.allMetadataValues(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']); + let description = ''; + if (isNotEmpty(publisher) || isNotEmpty(date)) { + description += '('; + } + if (isNotEmpty(publisher)) { + description += publisher; + } + if (isNotEmpty(date)) { + if (isNotEmpty(publisher)) { + description += ', '; + } + description += date; + } + if (isNotEmpty(description)) { + description += ') '; + } + if (isNotEmpty(authors)) { + authors.forEach((author, i) => { + description += author; + if (i < (authors.length - 1)) { + description += '; '; + } + }); + } + return this.undefinedIfEmpty(description); } - getParentTitle(): Observable { - // TODO: Remove cast to "any" and replace with proper type-check - const propertyName = (this.dso as any).getParentLinkKey(); - return this.linkService.resolveLink(this.dso, followLink(propertyName))[propertyName].pipe( - find((parentRD: RemoteData) => parentRD.hasSucceeded || parentRD.statusCode === 204), - map((parentRD: RemoteData) => { - return parentRD.payload.firstMetadataValue('dc.title'); - }) - ); + /** + * Return undefined if the provided string is empty + * @param value Value to check + */ + undefinedIfEmpty(value: string) { + return this.defaultIfEmpty(value, undefined); + } + + /** + * Return a default value if the provided string is empty + * @param value Value to check + * @param def Default in case value is empty + */ + defaultIfEmpty(value: string, def: string) { + if (isNotEmpty(value)) { + return value; + } else { + return def; + } } } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 84d874388c..a6aaad98ea 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2194,6 +2194,8 @@ "person.listelement.badge": "Person", + "person.listelement.no-title": "No name found", + "person.page.birthdate": "Birth Date", "person.page.email": "Email Address",