diff --git a/src/app/core/data-services-map.ts b/src/app/core/data-services-map.ts index f4ba7a1fe9b..9e7b2d90f9f 100644 --- a/src/app/core/data-services-map.ts +++ b/src/app/core/data-services-map.ts @@ -30,6 +30,7 @@ import { RESOURCE_POLICY } from './resource-policy/models/resource-policy.resour import { ACCESS_STATUS } from './shared/access-status.resource-type'; import { ADMIN_NOTIFY_MESSAGE } from './shared/admin-notify-message.resource-type'; import { AUTHORIZATION } from './shared/authorization.resource-type'; +import { BIBLIOGRAPHY } from './shared/bibliography/bibliography.resource-type'; import { BITSTREAM } from './shared/bitstream.resource-type'; import { BITSTREAM_FORMAT } from './shared/bitstream-format.resource-type'; import { BROWSE_DEFINITION } from './shared/browse-definition.resource-type'; @@ -88,6 +89,7 @@ export const LAZY_DATA_SERVICES: LazyDataServicesMap = new Map([ [SUBSCRIPTION.value, () => import('./data/subscriptions-data.service').then(m => m.SubscriptionsDataService)], [COMMUNITY.value, () => import('./data/community-data.service').then(m => m.CommunityDataService)], [VOCABULARY.value, () => import('./submission/vocabularies/vocabulary.data.service').then(m => m.VocabularyDataService)], + [BIBLIOGRAPHY.value, () => import('./data/bibliography-data.service').then(m => m.ItemBibliographyService)], [BUNDLE.value, () => import('./data/bundle-data.service').then(m => m.BundleDataService)], [CONFIG_PROPERTY.value, () => import('./data/configuration-data.service').then(m => m.ConfigurationDataService)], [POOL_TASK.value, () => import('./tasks/pool-task-data.service').then(m => m.PoolTaskDataService)], diff --git a/src/app/core/data/bibliography-data.service.ts b/src/app/core/data/bibliography-data.service.ts new file mode 100644 index 00000000000..ae9fc3fc82e --- /dev/null +++ b/src/app/core/data/bibliography-data.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@angular/core'; +import { + map, + Observable, +} from 'rxjs'; + +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { BibliographyData } from '../shared/bibliography/bibliography-data.model'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { Item } from '../shared/item.model'; +import { getFirstCompletedRemoteData } from '../shared/operators'; +import { BaseDataService } from './base/base-data.service'; +import { RequestService } from './request.service'; + + + + +@Injectable({ providedIn: 'root' }) +export class ItemBibliographyService extends BaseDataService { + + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + ) { + super('bibliographies', requestService, rdbService, objectCache, halService); + } + + getBibliographies(item: Item): Observable { + return this.findByHref(item._links.bibliography.href).pipe( + getFirstCompletedRemoteData(), + map(res => res.payload), + ); + } +} diff --git a/src/app/core/provide-core.ts b/src/app/core/provide-core.ts index 5307709ad4f..0c272c22e47 100644 --- a/src/app/core/provide-core.ts +++ b/src/app/core/provide-core.ts @@ -50,6 +50,7 @@ import { ResearcherProfile } from './profile/model/researcher-profile.model'; import { ResourcePolicy } from './resource-policy/models/resource-policy.model'; import { AccessStatusObject } from './shared/access-status.model'; import { Authorization } from './shared/authorization.model'; +import { BibliographyData } from './shared/bibliography/bibliography-data.model'; import { Bitstream } from './shared/bitstream.model'; import { BitstreamFormat } from './shared/bitstream-format.model'; import { BrowseDefinition } from './shared/browse-definition.model'; @@ -134,6 +135,7 @@ export const models = [ Root, DSpaceObject, + BibliographyData, Bundle, Bitstream, BitstreamFormat, diff --git a/src/app/core/shared/bibliography/bibliography-data.model.ts b/src/app/core/shared/bibliography/bibliography-data.model.ts new file mode 100644 index 00000000000..a06f31ecec9 --- /dev/null +++ b/src/app/core/shared/bibliography/bibliography-data.model.ts @@ -0,0 +1,42 @@ +import { + autoserialize, + deserialize, +} from 'cerialize'; +import { typedObject } from 'src/app/core/cache/builders/build-decorators'; +import { DSpaceObject } from 'src/app/core/shared/dspace-object.model'; +import { HALLink } from 'src/app/core/shared/hal-link.model'; +import { ResourceType } from 'src/app/core/shared/resource-type'; +import { excludeFromEquals } from 'src/app/core/utilities/equals.decorators'; + +import { Bibliography } from './bibliography.model'; +import { BIBLIOGRAPHY } from './bibliography.resource-type'; + +/** + * Class representing a DSpace Version + */ +@typedObject +export class BibliographyData extends DSpaceObject { + static type = BIBLIOGRAPHY; + + /** + * The type for this IdentifierData + */ + @excludeFromEquals + @autoserialize + type: ResourceType; + + /** + * The + */ + @autoserialize + bibliographies: Bibliography[]; + + /** + * The {@link HALLink}s for this IdentifierData + */ + @deserialize + _links: { + self: HALLink; + }; + +} diff --git a/src/app/core/shared/bibliography/bibliography.model.ts b/src/app/core/shared/bibliography/bibliography.model.ts new file mode 100644 index 00000000000..752623e01fd --- /dev/null +++ b/src/app/core/shared/bibliography/bibliography.model.ts @@ -0,0 +1,9 @@ +import { autoserialize } from 'cerialize'; + +export class Bibliography { + @autoserialize + style: string; + + @autoserialize + value: string; +} diff --git a/src/app/core/shared/bibliography/bibliography.resource-type.ts b/src/app/core/shared/bibliography/bibliography.resource-type.ts new file mode 100644 index 00000000000..c5c2018c01c --- /dev/null +++ b/src/app/core/shared/bibliography/bibliography.resource-type.ts @@ -0,0 +1,9 @@ +import { ResourceType } from 'src/app/core/shared/resource-type'; + +/** + * The resource type for Bibliography + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const BIBLIOGRAPHY = new ResourceType('bibliography'); diff --git a/src/app/core/shared/item.model.ts b/src/app/core/shared/item.model.ts index 06fc0b01e84..5f2e630c6b4 100644 --- a/src/app/core/shared/item.model.ts +++ b/src/app/core/shared/item.model.ts @@ -107,6 +107,7 @@ export class Item extends DSpaceObject implements ChildHALResource, HandleObject mappedCollections: HALLink; relationships: HALLink; bundles: HALLink; + bibliography: HALLink; owningCollection: HALLink; templateItemOf: HALLink; version: HALLink; @@ -155,8 +156,8 @@ export class Item extends DSpaceObject implements ChildHALResource, HandleObject * The access status for this Item * Will be undefined unless the access status {@link HALLink} has been resolved. */ - @link(ACCESS_STATUS, false, 'accessStatus') - accessStatus?: Observable>; + @link(ACCESS_STATUS, false, 'accessStatus') + accessStatus?: Observable>; /** * The identifier data for this Item diff --git a/src/app/core/shared/listable.module.ts b/src/app/core/shared/listable.module.ts new file mode 100644 index 00000000000..29a3714819c --- /dev/null +++ b/src/app/core/shared/listable.module.ts @@ -0,0 +1,270 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; + +import { CollectionAdminSearchResultGridElementComponent } from '../../admin/admin-search-page/admin-search-results/admin-search-result-grid-element/collection-search-result/collection-admin-search-result-grid-element.component'; +import { CommunityAdminSearchResultGridElementComponent } from '../../admin/admin-search-page/admin-search-results/admin-search-result-grid-element/community-search-result/community-admin-search-result-grid-element.component'; +import { ItemAdminSearchResultGridElementComponent } from '../../admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component'; +import { CollectionAdminSearchResultListElementComponent } from '../../admin/admin-search-page/admin-search-results/admin-search-result-list-element/collection-search-result/collection-admin-search-result-list-element.component'; +import { CommunityAdminSearchResultListElementComponent } from '../../admin/admin-search-page/admin-search-results/admin-search-result-list-element/community-search-result/community-admin-search-result-list-element.component'; +import { ItemAdminSearchResultListElementComponent } from '../../admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component'; +import { ItemAdminSearchResultActionsComponent } from '../../admin/admin-search-page/admin-search-results/item-admin-search-result-actions.component'; +import { WorkflowItemAdminWorkflowActionsComponent } from '../../admin/admin-workflow-page/admin-workflow-search-results/actions/workflow-item/workflow-item-admin-workflow-actions.component'; +import { WorkspaceItemAdminWorkflowActionsComponent } from '../../admin/admin-workflow-page/admin-workflow-search-results/actions/workspace-item/workspace-item-admin-workflow-actions.component'; +import { WorkflowItemSearchResultAdminWorkflowGridElementComponent } from '../../admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component'; +import { WorkspaceItemSearchResultAdminWorkflowGridElementComponent } from '../../admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component'; +import { WorkflowItemSearchResultAdminWorkflowListElementComponent } from '../../admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component'; +import { WorkspaceItemSearchResultAdminWorkflowListElementComponent } from '../../admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workspace-item/workspace-item-search-result-admin-workflow-list-element.component'; +import { JournalGridElementComponent } from '../../entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component'; +import { JournalIssueGridElementComponent } from '../../entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component'; +import { JournalVolumeGridElementComponent } from '../../entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component'; +import { JournalSearchResultGridElementComponent } from '../../entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component'; +import { JournalIssueSearchResultGridElementComponent } from '../../entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component'; +import { JournalVolumeSearchResultGridElementComponent } from '../../entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component'; +import { JournalListElementComponent } from '../../entity-groups/journal-entities/item-list-elements/journal/journal-list-element.component'; +import { JournalIssueListElementComponent } from '../../entity-groups/journal-entities/item-list-elements/journal-issue/journal-issue-list-element.component'; +import { JournalVolumeListElementComponent } from '../../entity-groups/journal-entities/item-list-elements/journal-volume/journal-volume-list-element.component'; +import { JournalSearchResultListElementComponent } from '../../entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component'; +import { JournalIssueSearchResultListElementComponent } from '../../entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component'; +import { JournalVolumeSearchResultListElementComponent } from '../../entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component'; +import { JournalSidebarSearchListElementComponent } from '../../entity-groups/journal-entities/item-list-elements/sidebar-search-list-elements/journal/journal-sidebar-search-list-element.component'; +import { JournalIssueSidebarSearchListElementComponent } from '../../entity-groups/journal-entities/item-list-elements/sidebar-search-list-elements/journal-issue/journal-issue-sidebar-search-list-element.component'; +import { JournalVolumeSidebarSearchListElementComponent } from '../../entity-groups/journal-entities/item-list-elements/sidebar-search-list-elements/journal-volume/journal-volume-sidebar-search-list-element.component'; +import { JournalComponent } from '../../entity-groups/journal-entities/item-pages/journal/journal.component'; +import { JournalIssueComponent } from '../../entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component'; +import { JournalVolumeComponent } from '../../entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component'; +import { OrgUnitGridElementComponent } from '../../entity-groups/research-entities/item-grid-elements/org-unit/org-unit-grid-element.component'; +import { PersonGridElementComponent } from '../../entity-groups/research-entities/item-grid-elements/person/person-grid-element.component'; +import { ProjectGridElementComponent } from '../../entity-groups/research-entities/item-grid-elements/project/project-grid-element.component'; +import { OrgUnitSearchResultGridElementComponent } from '../../entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component'; +import { PersonSearchResultGridElementComponent } from '../../entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component'; +import { ProjectSearchResultGridElementComponent } from '../../entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component'; +import { OrgUnitListElementComponent } from '../../entity-groups/research-entities/item-list-elements/org-unit/org-unit-list-element.component'; +import { PersonListElementComponent } from '../../entity-groups/research-entities/item-list-elements/person/person-list-element.component'; +import { ProjectListElementComponent } from '../../entity-groups/research-entities/item-list-elements/project/project-list-element.component'; +import { OrgUnitSearchResultListElementComponent } from '../../entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component'; +import { PersonSearchResultListElementComponent } from '../../entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component'; +import { ProjectSearchResultListElementComponent } from '../../entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component'; +import { OrgUnitSidebarSearchListElementComponent } from '../../entity-groups/research-entities/item-list-elements/sidebar-search-list-elements/org-unit/org-unit-sidebar-search-list-element.component'; +import { PersonSidebarSearchListElementComponent } from '../../entity-groups/research-entities/item-list-elements/sidebar-search-list-elements/person/person-sidebar-search-list-element.component'; +import { ProjectSidebarSearchListElementComponent } from '../../entity-groups/research-entities/item-list-elements/sidebar-search-list-elements/project/project-sidebar-search-list-element.component'; +import { OrgUnitComponent } from '../../entity-groups/research-entities/item-pages/org-unit/org-unit.component'; +import { PersonComponent } from '../../entity-groups/research-entities/item-pages/person/person.component'; +import { ProjectComponent } from '../../entity-groups/research-entities/item-pages/project/project.component'; +import { ExternalSourceEntryListSubmissionElementComponent } from '../../entity-groups/research-entities/submission/item-list-elements/external-source-entry/external-source-entry-list-submission-element.component'; +import { OrgUnitSearchResultListSubmissionElementComponent } from '../../entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-search-result-list-submission-element.component'; +import { OrgUnitInputSuggestionsComponent } from '../../entity-groups/research-entities/submission/item-list-elements/org-unit/org-unit-suggestions/org-unit-input-suggestions.component'; +import { PersonSearchResultListSubmissionElementComponent } from '../../entity-groups/research-entities/submission/item-list-elements/person/person-search-result-list-submission-element.component'; +import { PersonInputSuggestionsComponent } from '../../entity-groups/research-entities/submission/item-list-elements/person/person-suggestions/person-input-suggestions.component'; +import { CollectionsComponent } from '../../item-page/field-components/collections/collections.component'; +import { ThemedMediaViewerComponent } from '../../item-page/media-viewer/themed-media-viewer.component'; +import { MiradorViewerComponent } from '../../item-page/mirador-viewer/mirador-viewer.component'; +import { ThemedFileSectionComponent } from '../../item-page/simple/field-components/file-section/themed-file-section.component'; +import { ItemPageAbstractFieldComponent } from '../../item-page/simple/field-components/specific-field/abstract/item-page-abstract-field.component'; +import { ItemPageBibliographyComponent } from '../../item-page/simple/field-components/specific-field/bibliography/item-page-bibliography.component'; +import { ItemPageDateFieldComponent } from '../../item-page/simple/field-components/specific-field/date/item-page-date-field.component'; +import { GenericItemPageFieldComponent } from '../../item-page/simple/field-components/specific-field/generic/generic-item-page-field.component'; +import { ThemedItemPageTitleFieldComponent } from '../../item-page/simple/field-components/specific-field/title/themed-item-page-field.component'; +import { ItemPageUriFieldComponent } from '../../item-page/simple/field-components/specific-field/uri/item-page-uri-field.component'; +import { PublicationComponent } from '../../item-page/simple/item-types/publication/publication.component'; +import { UntypedItemComponent } from '../../item-page/simple/item-types/untyped-item/untyped-item.component'; +import { ThemedMetadataRepresentationListComponent } from '../../item-page/simple/metadata-representation-list/themed-metadata-representation-list.component'; +import { TabbedRelatedEntitiesSearchComponent } from '../../item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component'; +import { RelatedItemsComponent } from '../../item-page/simple/related-items/related-items-component'; +import { DsoEditMenuComponent } from '../../shared/dso-page/dso-edit-menu/dso-edit-menu.component'; +import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component'; +import { MetadataFieldWrapperComponent } from '../../shared/metadata-field-wrapper/metadata-field-wrapper.component'; +import { ClaimedTaskActionsComponent } from '../../shared/mydspace-actions/claimed-task/claimed-task-actions.component'; +import { ItemActionsComponent } from '../../shared/mydspace-actions/item/item-actions.component'; +import { PoolTaskActionsComponent } from '../../shared/mydspace-actions/pool-task/pool-task-actions.component'; +import { WorkflowitemActionsComponent } from '../../shared/mydspace-actions/workflowitem/workflowitem-actions.component'; +import { WorkspaceitemActionsComponent } from '../../shared/mydspace-actions/workspaceitem/workspaceitem-actions.component'; +import { BadgesComponent } from '../../shared/object-collection/shared/badges/badges.component'; +import { ThemedBadgesComponent } from '../../shared/object-collection/shared/badges/themed-badges.component'; +import { ListableObjectComponentLoaderComponent } from '../../shared/object-collection/shared/listable-object/listable-object-component-loader.component'; +import { ClaimedTaskSearchResultDetailElementComponent } from '../../shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component'; +import { ItemDetailPreviewComponent } from '../../shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component'; +import { ItemSearchResultDetailElementComponent } from '../../shared/object-detail/my-dspace-result-detail-element/item-search-result/item-search-result-detail-element.component'; +import { PoolSearchResultDetailElementComponent } from '../../shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component'; +import { WorkflowItemSearchResultDetailElementComponent } from '../../shared/object-detail/my-dspace-result-detail-element/workflow-item-search-result/workflow-item-search-result-detail-element.component'; +import { WorkspaceItemSearchResultDetailElementComponent } from '../../shared/object-detail/my-dspace-result-detail-element/workspace-item-search-result/workspace-item-search-result-detail-element.component'; +import { CollectionGridElementComponent } from '../../shared/object-grid/collection-grid-element/collection-grid-element.component'; +import { CommunityGridElementComponent } from '../../shared/object-grid/community-grid-element/community-grid-element.component'; +import { ItemGridElementComponent } from '../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component'; +import { CollectionSearchResultGridElementComponent } from '../../shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component'; +import { CommunitySearchResultGridElementComponent } from '../../shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component'; +import { ItemSearchResultGridElementComponent } from '../../shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component'; +import { BitstreamListItemComponent } from '../../shared/object-list/bitstream-list-item/bitstream-list-item.component'; +import { BrowseEntryListElementComponent } from '../../shared/object-list/browse-entry-list-element/browse-entry-list-element.component'; +import { BundleListElementComponent } from '../../shared/object-list/bundle-list-element/bundle-list-element.component'; +import { CollectionListElementComponent } from '../../shared/object-list/collection-list-element/collection-list-element.component'; +import { CommunityListElementComponent } from '../../shared/object-list/community-list-element/community-list-element.component'; +import { ItemListElementComponent } from '../../shared/object-list/item-list-element/item-types/item/item-list-element.component'; +import { ListableNotificationObjectComponent } from '../../shared/object-list/listable-notification-object/listable-notification-object.component'; +import { ClaimedApprovedSearchResultListElementComponent } from '../../shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component'; +import { ClaimedDeclinedSearchResultListElementComponent } from '../../shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declined-search-result-list-element.component'; +import { ClaimedDeclinedTaskSearchResultListElementComponent } from '../../shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-task-search-result/claimed-declined-task-search-result-list-element.component'; +import { ClaimedSearchResultListElementComponent } from '../../shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component'; +import { ThemedItemListPreviewComponent } from '../../shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component'; +import { ItemSearchResultListElementSubmissionComponent } from '../../shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component'; +import { PoolSearchResultListElementComponent } from '../../shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component'; +import { WorkflowItemSearchResultListElementComponent } from '../../shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component'; +import { WorkspaceItemSearchResultListElementComponent } from '../../shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component'; +import { CollectionSearchResultListElementComponent } from '../../shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component'; +import { CommunitySearchResultListElementComponent } from '../../shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component'; +import { ItemSearchResultListElementComponent } from '../../shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component'; +import { CollectionSidebarSearchListElementComponent } from '../../shared/object-list/sidebar-search-list-element/collection/collection-sidebar-search-list-element.component'; +import { CommunitySidebarSearchListElementComponent } from '../../shared/object-list/sidebar-search-list-element/community/community-sidebar-search-list-element.component'; +import { PublicationSidebarSearchListElementComponent } from '../../shared/object-list/sidebar-search-list-element/item-types/publication/publication-sidebar-search-list-element.component'; +import { ThemedResultsBackButtonComponent } from '../../shared/results-back-button/themed-results-back-button.component'; +import { TruncatableComponent } from '../../shared/truncatable/truncatable.component'; +import { TruncatablePartComponent } from '../../shared/truncatable/truncatable-part/truncatable-part.component'; +import { ThemedThumbnailComponent } from '../../thumbnail/themed-thumbnail.component'; +import { ThumbnailComponent } from '../../thumbnail/thumbnail.component'; + +const ENTRY_COMPONENTS = [ + BitstreamListItemComponent, + BrowseEntryListElementComponent, + BundleListElementComponent, + CollectionAdminSearchResultGridElementComponent, + CollectionAdminSearchResultListElementComponent, + CollectionGridElementComponent, + CollectionListElementComponent, + CollectionSearchResultGridElementComponent, + CollectionSearchResultListElementComponent, + CollectionSidebarSearchListElementComponent, + CommunityAdminSearchResultGridElementComponent, + CommunityAdminSearchResultListElementComponent, + CommunityGridElementComponent, + CommunityListElementComponent, + CommunitySearchResultGridElementComponent, + CommunitySearchResultListElementComponent, + CommunitySidebarSearchListElementComponent, + ExternalSourceEntryListSubmissionElementComponent, + ItemAdminSearchResultGridElementComponent, + ItemAdminSearchResultListElementComponent, + ItemGridElementComponent, + ItemListElementComponent, + ItemSearchResultGridElementComponent, + ItemSearchResultListElementComponent, + JournalComponent, + JournalGridElementComponent, + JournalIssueComponent, + JournalIssueGridElementComponent, + JournalIssueListElementComponent, + JournalIssueSearchResultGridElementComponent, + JournalIssueSearchResultListElementComponent, + JournalIssueSidebarSearchListElementComponent, + JournalListElementComponent, + JournalSearchResultGridElementComponent, + JournalSearchResultListElementComponent, + JournalSidebarSearchListElementComponent, + JournalVolumeComponent, + JournalVolumeGridElementComponent, + JournalVolumeListElementComponent, + JournalVolumeSearchResultGridElementComponent, + JournalVolumeSearchResultListElementComponent, + JournalVolumeSidebarSearchListElementComponent, + ListableNotificationObjectComponent, + OrgUnitComponent, + OrgUnitGridElementComponent, + OrgUnitListElementComponent, + OrgUnitSearchResultGridElementComponent, + OrgUnitSearchResultListElementComponent, + OrgUnitSearchResultListSubmissionElementComponent, + OrgUnitSidebarSearchListElementComponent, + PersonComponent, + PersonGridElementComponent, + PersonListElementComponent, + PersonSearchResultGridElementComponent, + PersonSearchResultListElementComponent, + PersonSearchResultListSubmissionElementComponent, + PersonSidebarSearchListElementComponent, + ProjectComponent, + ProjectGridElementComponent, + ProjectListElementComponent, + ProjectSearchResultGridElementComponent, + ProjectSearchResultListElementComponent, + ProjectSidebarSearchListElementComponent, + PublicationSidebarSearchListElementComponent, + WorkflowItemSearchResultAdminWorkflowListElementComponent, + WorkflowItemSearchResultAdminWorkflowGridElementComponent, + WorkspaceItemSearchResultAdminWorkflowListElementComponent, + WorkspaceItemSearchResultAdminWorkflowGridElementComponent, + WorkspaceItemSearchResultListElementComponent, + WorkflowItemSearchResultListElementComponent, + ClaimedSearchResultListElementComponent, + ClaimedApprovedSearchResultListElementComponent, + ClaimedDeclinedSearchResultListElementComponent, + ClaimedDeclinedTaskSearchResultListElementComponent, + PoolSearchResultListElementComponent, + ItemSearchResultDetailElementComponent, + WorkspaceItemSearchResultDetailElementComponent, + WorkflowItemSearchResultDetailElementComponent, + ClaimedTaskSearchResultDetailElementComponent, + PoolSearchResultDetailElementComponent, + ItemSearchResultListElementSubmissionComponent, + PublicationComponent, + UntypedItemComponent, +]; + +@NgModule({ + exports: [...ENTRY_COMPONENTS], + imports: [ + CommonModule, + RouterModule, + TranslateModule, + ThemedLoadingComponent, + TruncatableComponent, + TruncatablePartComponent, + ThumbnailComponent, + ItemPageBibliographyComponent, + BadgesComponent, + ThemedBadgesComponent, + ItemDetailPreviewComponent, + GenericItemPageFieldComponent, + RelatedItemsComponent, + WorkspaceitemActionsComponent, + ListableObjectComponentLoaderComponent, + PoolTaskActionsComponent, + ThemedItemListPreviewComponent, + OrgUnitInputSuggestionsComponent, + ThemedMetadataRepresentationListComponent, + ClaimedTaskActionsComponent, + WorkflowitemActionsComponent, + ItemAdminSearchResultActionsComponent, + MetadataFieldWrapperComponent, + ThemedThumbnailComponent, + ThemedItemPageTitleFieldComponent, + ThemedResultsBackButtonComponent, + DsoEditMenuComponent, + ItemActionsComponent, + PersonInputSuggestionsComponent, + TabbedRelatedEntitiesSearchComponent, + WorkspaceItemAdminWorkflowActionsComponent, + WorkflowItemAdminWorkflowActionsComponent, + FormsModule, + MiradorViewerComponent, + ThemedMediaViewerComponent, + ThemedFileSectionComponent, + ItemPageDateFieldComponent, + ItemPageAbstractFieldComponent, + ItemPageUriFieldComponent, + CollectionsComponent, + ...ENTRY_COMPONENTS, + ], +}) +export class ListableModule { + /** + * NOTE: this method allows to resolve issue with components that using a custom decorator + * which are not loaded during SSR otherwise + */ + static withEntryComponents() { + return { + ngModule: ListableModule, + providers: ENTRY_COMPONENTS.map((component) => ({ provide: component })), + }; + } +} diff --git a/src/app/item-page/simple/field-components/specific-field/bibliography/item-page-bibliography.component.html b/src/app/item-page/simple/field-components/specific-field/bibliography/item-page-bibliography.component.html new file mode 100644 index 00000000000..0303431a399 --- /dev/null +++ b/src/app/item-page/simple/field-components/specific-field/bibliography/item-page-bibliography.component.html @@ -0,0 +1,53 @@ + + + {{ "item.cite.open-modal" | translate }} + + + + + + + + + diff --git a/src/app/item-page/simple/field-components/specific-field/bibliography/item-page-bibliography.component.spec.ts b/src/app/item-page/simple/field-components/specific-field/bibliography/item-page-bibliography.component.spec.ts new file mode 100644 index 00000000000..0eefe7cf21a --- /dev/null +++ b/src/app/item-page/simple/field-components/specific-field/bibliography/item-page-bibliography.component.spec.ts @@ -0,0 +1,116 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { + ComponentFixture, + fakeAsync, + TestBed, + tick, + waitForAsync, +} from '@angular/core/testing'; +import { TranslateLoaderMock } from '@dspace/core/testing/translate-loader.mock'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { + TranslateLoader, + TranslateModule, +} from '@ngx-translate/core'; +import { + delay, + of, + throwError, +} from 'rxjs'; + +import { ItemBibliographyService } from '../../../../../core/data/bibliography-data.service'; +import { Bibliography } from '../../../../../core/shared/bibliography/bibliography.model'; +import { BibliographyData } from '../../../../../core/shared/bibliography/bibliography-data.model'; +import { Item } from '../../../../../core/shared/item.model'; +import { ItemPageBibliographyComponent } from './item-page-bibliography.component'; + +describe('ItemPageBibliographyComponent', () => { + let component: ItemPageBibliographyComponent; + let fixture: ComponentFixture; + let mockService: jasmine.SpyObj; + let mockModalService: jasmine.SpyObj; + let testItem: Item; + + beforeEach(waitForAsync(() => { + mockService = jasmine.createSpyObj('ItemBibliographyService', ['getBibliographies']); + mockModalService = jasmine.createSpyObj('NgbModal', ['open']); + + void TestBed.configureTestingModule({ + imports: [ + ItemPageBibliographyComponent, + TranslateModule.forRoot({ + loader: { provide: TranslateLoader, useClass: TranslateLoaderMock }, + }), + ], + providers: [ + { provide: ItemBibliographyService, useValue: mockService }, + { provide: NgbModal, useValue: mockModalService }, + ], + schemas: [NO_ERRORS_SCHEMA], + }) + .overrideComponent(ItemPageBibliographyComponent, { + set: { providers: [{ provide: NgbModal, useValue: mockModalService }] }, + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemPageBibliographyComponent); + component = fixture.componentInstance; + testItem = new Item(); + component.item = testItem; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should load bibliographies and open modal', fakeAsync(() => { + const mockBibliographies: Bibliography[] = [ + { style: 'bibtex', value: '@article{test}' }, + { style: 'apa', value: 'Author, 2025' }, + ]; + const mockData = { bibliographies: mockBibliographies }; + mockService.getBibliographies.and.returnValue(of(mockData as BibliographyData).pipe(delay(0))); + const modalContent = {}; + + component.openModal(modalContent); + expect(component.loading).toBeTrue(); + expect(component.error).toBeFalse(); + + tick(); + expect(component.bibliographies).toEqual(mockBibliographies); + expect(component.loading).toBeFalse(); + expect(mockModalService.open).toHaveBeenCalledWith(modalContent, { size: 'lg' }); + })); + + it('should handle error when loading bibliographies', fakeAsync(() => { + const mockError = new Error('Failed'); + mockService.getBibliographies.and.returnValue(throwError(() => mockError)); + const modalContent = {}; + + component.openModal(modalContent); + tick(); + + expect(component.loading).toBeFalse(); + expect(component.error).toBeTrue(); + expect(mockModalService.open).not.toHaveBeenCalled(); + })); + + it('should copy text to clipboard', () => { + const text = 'Some text to copy'; + spyOn(navigator.clipboard, 'writeText').and.returnValue(Promise.resolve()); + + component.copyToClipboard(text); + expect(navigator.clipboard.writeText).toHaveBeenCalledWith(text); + }); + + it('should copy HTML text to clipboard as plain text', () => { + const htmlText = '

HTML text

'; + spyOn(navigator.clipboard, 'writeText').and.returnValue(Promise.resolve()); + + component.copyToClipboard(htmlText); + expect(navigator.clipboard.writeText).toHaveBeenCalledWith('HTML text'); + }); +}); diff --git a/src/app/item-page/simple/field-components/specific-field/bibliography/item-page-bibliography.component.ts b/src/app/item-page/simple/field-components/specific-field/bibliography/item-page-bibliography.component.ts new file mode 100644 index 00000000000..e910c480574 --- /dev/null +++ b/src/app/item-page/simple/field-components/specific-field/bibliography/item-page-bibliography.component.ts @@ -0,0 +1,69 @@ +import { + Component, + Input, +} from '@angular/core'; +import { + NgbModal, + NgbModalModule, +} from '@ng-bootstrap/ng-bootstrap'; +import { TranslateModule } from '@ngx-translate/core'; + +import { ItemBibliographyService } from '../../../../../core/data/bibliography-data.service'; +import { Bibliography } from '../../../../../core/shared/bibliography/bibliography.model'; +import { BibliographyData } from '../../../../../core/shared/bibliography/bibliography-data.model'; +import { Item } from '../../../../../core/shared/item.model'; + + +@Component({ + selector: 'ds-item-page-bibliography', + templateUrl: './item-page-bibliography.component.html', + standalone: true, + imports: [ + NgbModalModule, + TranslateModule, + ], +}) +export class ItemPageBibliographyComponent { + @Input() item: Item; + bibliographies: Bibliography[] = []; + loading = false; + error = false; + + constructor( + private bibliographyService: ItemBibliographyService, + private modalService: NgbModal, + ) { + } + + openModal(content: any) { + this.loading = true; + this.error = false; + + this.bibliographyService.getBibliographies(this.item).subscribe({ + next: (bibliographyData: BibliographyData) => { + this.bibliographies = bibliographyData?.bibliographies || []; + this.loading = false; + this.modalService.open(content, { size: 'lg' }); + }, + error: (err: unknown) => { + this.loading = false; + this.error = true; + console.error(err); + }, + }); + } + + copyToClipboard(value: string) { + const tempElement = document.createElement('div'); + tempElement.innerHTML = value; + const plainText = tempElement.textContent || tempElement.innerText || ''; + + navigator.clipboard.writeText(plainText).then( + () => { + }, + (err) => { + console.error('Could not copy text: ', err); + }, + ); + } +} diff --git a/src/app/item-page/simple/item-types/publication/publication.component.html b/src/app/item-page/simple/item-types/publication/publication.component.html index a294cc025bc..678c19645d4 100644 --- a/src/app/item-page/simple/item-types/publication/publication.component.html +++ b/src/app/item-page/simple/item-types/publication/publication.component.html @@ -53,6 +53,7 @@ [fields]="['dc.publisher']" [label]="'publication.page.publisher'"> +
+
diff --git a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts index 1d15ff59e8e..1006a38d730 100644 --- a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts +++ b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts @@ -18,6 +18,7 @@ import { ThemedMediaViewerComponent } from '../../../media-viewer/themed-media-v import { MiradorViewerComponent } from '../../../mirador-viewer/mirador-viewer.component'; import { ThemedFileSectionComponent } from '../../field-components/file-section/themed-file-section.component'; import { ItemPageAbstractFieldComponent } from '../../field-components/specific-field/abstract/item-page-abstract-field.component'; +import { ItemPageBibliographyComponent } from '../../field-components/specific-field/bibliography/item-page-bibliography.component'; import { ItemPageCcLicenseFieldComponent } from '../../field-components/specific-field/cc-license/item-page-cc-license-field.component'; import { ItemPageDateFieldComponent } from '../../field-components/specific-field/date/item-page-date-field.component'; import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component'; @@ -45,6 +46,7 @@ import { ItemComponent } from '../shared/item.component'; GenericItemPageFieldComponent, GeospatialItemPageFieldComponent, ItemPageAbstractFieldComponent, + ItemPageBibliographyComponent, ItemPageCcLicenseFieldComponent, ItemPageDateFieldComponent, ItemPageUriFieldComponent, @@ -60,4 +62,4 @@ import { ItemComponent } from '../shared/item.component'; TranslateModule, ], }) -export class UntypedItemComponent extends ItemComponent {} +export class UntypedItemComponent extends ItemComponent { } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index c0b620d24d7..49a7ee5d7e1 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -7213,4 +7213,26 @@ "item.preview.organization.address.addressLocality": "City", "item.preview.organization.alternateName": "Alternative name", + + "item.cite.open-modal": "Cite", + + "item.cite.modal.title": "Cite", + + "item.cite.modal.close": "Close", + + "item.cite.modal.copy": "Copy", + + // TODO New key - Add a translation + "item.cite.modal.error": "Error", + + "modern-language-association": "MLA", + + "apa": "APA", + + "vancouver": "Vancouver", + + "bibtex": "BibTeX", + + "instituto-brasileiro-de-informacao-em-ciencia-e-tecnologia-abnt": "ABNT", + } diff --git a/src/assets/i18n/es.json5 b/src/assets/i18n/es.json5 index 7f1b2f4d6f6..29ca8cf2004 100644 --- a/src/assets/i18n/es.json5 +++ b/src/assets/i18n/es.json5 @@ -10975,5 +10975,25 @@ // TODO New key - Add a translation "file-download-link.request-copy": "Request a copy of ", + "item.cite.open-modal": "Cita", + + "item.cite.modal.title": "Cita", + + "item.cite.modal.close": "Cerrar", + + "item.cite.modal.copy": "Copiar", + + // TODO New key - Add a translation + "item.cite.modal.error": "Error", + + "modern-language-association": "MLA", + + "apa": "APA", + + "vancouver": "Vancouver", + + "bibtex": "BibTeX", + + "instituto-brasileiro-de-informacao-em-ciencia-e-tecnologia-abnt": "ABNT", } diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index 7d35b5ccfe8..dd21ca16320 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -11144,4 +11144,25 @@ "item.preview.organization.address.addressLocality": "Cidade", "item.preview.organization.alternateName": "Nome alternativo", + + "item.cite.open-modal": "Citar", + + "item.cite.modal.title": "Citar", + + "item.cite.modal.close": "Fechar", + + "item.cite.modal.copy": "Copiar", + + // TODO New key - Add a translation + "item.cite.modal.error": "Erro", + + "modern-language-association": "MLA", + + "apa": "APA", + + "vancouver": "Vancouver", + + "bibtex": "BibTeX", + + "instituto-brasileiro-de-informacao-em-ciencia-e-tecnologia-abnt": "ABNT", } diff --git a/src/themes/custom/app/item-page/simple/item-types/publication/publication.component.ts b/src/themes/custom/app/item-page/simple/item-types/publication/publication.component.ts index 6630003ffaf..3cd2c6b7f3e 100644 --- a/src/themes/custom/app/item-page/simple/item-types/publication/publication.component.ts +++ b/src/themes/custom/app/item-page/simple/item-types/publication/publication.component.ts @@ -7,6 +7,7 @@ import { RouterLink } from '@angular/router'; import { Context } from '@dspace/core/shared/context.model'; import { ViewMode } from '@dspace/core/shared/view-mode.model'; import { TranslateModule } from '@ngx-translate/core'; +import { ItemPageBibliographyComponent } from 'src/app/item-page/simple/field-components/specific-field/bibliography/item-page-bibliography.component'; import { CollectionsComponent } from '../../../../../../../app/item-page/field-components/collections/collections.component'; import { ThemedMediaViewerComponent } from '../../../../../../../app/item-page/media-viewer/themed-media-viewer.component'; @@ -43,6 +44,7 @@ import { ThemedThumbnailComponent } from '../../../../../../../app/thumbnail/the GenericItemPageFieldComponent, GeospatialItemPageFieldComponent, ItemPageAbstractFieldComponent, + ItemPageBibliographyComponent, ItemPageDateFieldComponent, ItemPageUriFieldComponent, MetadataFieldWrapperComponent, diff --git a/src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts b/src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts index 4448da77de9..069e6e1a8ab 100644 --- a/src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts +++ b/src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts @@ -8,6 +8,7 @@ import { Context } from '@dspace/core/shared/context.model'; import { Item } from '@dspace/core/shared/item.model'; import { ViewMode } from '@dspace/core/shared/view-mode.model'; import { TranslateModule } from '@ngx-translate/core'; +import { ItemPageBibliographyComponent } from 'src/app/item-page/simple/field-components/specific-field/bibliography/item-page-bibliography.component'; import { CollectionsComponent } from '../../../../../../../app/item-page/field-components/collections/collections.component'; import { ThemedMediaViewerComponent } from '../../../../../../../app/item-page/media-viewer/themed-media-viewer.component'; @@ -47,6 +48,7 @@ import { ThemedThumbnailComponent } from '../../../../../../../app/thumbnail/the GenericItemPageFieldComponent, GeospatialItemPageFieldComponent, ItemPageAbstractFieldComponent, + ItemPageBibliographyComponent, ItemPageCcLicenseFieldComponent, ItemPageDateFieldComponent, ItemPageUriFieldComponent,