diff --git a/src/app/item-page/field-components/metadata-values/metadata-values.component.html b/src/app/item-page/field-components/metadata-values/metadata-values.component.html
index 7ed177f5b6d..9038a2b023c 100644
--- a/src/app/item-page/field-components/metadata-values/metadata-values.component.html
+++ b/src/app/item-page/field-components/metadata-values/metadata-values.component.html
@@ -4,15 +4,30 @@
-
-
+
+
@if (!last) {
}
}
+
+
+ {{ value }}
+
+
+
diff --git a/src/app/item-page/field-components/metadata-values/metadata-values.component.spec.ts b/src/app/item-page/field-components/metadata-values/metadata-values.component.spec.ts
index f3581f8cc8c..7c23933f7a3 100644
--- a/src/app/item-page/field-components/metadata-values/metadata-values.component.spec.ts
+++ b/src/app/item-page/field-components/metadata-values/metadata-values.component.spec.ts
@@ -8,6 +8,8 @@ import {
waitForAsync,
} from '@angular/core/testing';
import { By } from '@angular/platform-browser';
+import { ActivatedRoute } from '@angular/router';
+import { RouterTestingModule } from '@angular/router/testing';
import { APP_CONFIG } from '@dspace/config/app-config.interface';
import { buildPaginatedList } from '@dspace/core/data/paginated-list.model';
import { MetadataValue } from '@dspace/core/shared/metadata.models';
@@ -28,18 +30,10 @@ let comp: MetadataValuesComponent;
let fixture: ComponentFixture;
const mockMetadata = [
- {
- language: 'en_US',
- value: '1234',
- },
- {
- language: 'en_US',
- value: 'a publisher',
- },
- {
- language: 'en_US',
- value: 'desc',
- }] as MetadataValue[];
+ { language: 'en_US', value: '1234' },
+ { language: 'en_US', value: 'a publisher' },
+ { language: 'en_US', value: 'desc' },
+] as MetadataValue[];
const mockSeperator = '
';
const mockLabel = 'fake.message';
const vocabularyServiceMock = {
@@ -58,15 +52,28 @@ const controlledMetadata = {
describe('MetadataValuesComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
- imports: [TranslateModule.forRoot({
- loader: {
- provide: TranslateLoader,
- useClass: TranslateLoaderMock,
- },
- }), MetadataValuesComponent],
+ imports: [
+ RouterTestingModule.withRoutes([]),
+ TranslateModule.forRoot({
+ loader: {
+ provide: TranslateLoader,
+ useClass: TranslateLoaderMock,
+ },
+ }),
+ MetadataValuesComponent,
+ ],
providers: [
{ provide: APP_CONFIG, useValue: environment },
{ provide: VocabularyService, useValue: vocabularyServiceMock },
+ {
+ provide: ActivatedRoute,
+ useValue: {
+ snapshot: {},
+ params: of({}),
+ queryParams: of({}),
+ data: of({}),
+ },
+ },
],
schemas: [NO_ERRORS_SCHEMA],
}).overrideComponent(MetadataValuesComponent, {
@@ -103,43 +110,48 @@ describe('MetadataValuesComponent', () => {
it('should return correct target and rel for internal links', () => {
spyOn(comp, 'hasInternalLink').and.returnValue(true);
- const urlValue = '/internal-link';
- const result = comp.getLinkAttributes(urlValue);
+ const result = comp.getLinkAttributes('/internal-link');
expect(result.target).toBe('_self');
expect(result.rel).toBe('');
});
it('should return correct target and rel for external links', () => {
spyOn(comp, 'hasInternalLink').and.returnValue(false);
- const urlValue = 'https://www.dspace.org';
- const result = comp.getLinkAttributes(urlValue);
+ const result = comp.getLinkAttributes('https://www.dspace.org');
expect(result.target).toBe('_blank');
expect(result.rel).toBe('noopener noreferrer');
});
it('should detect controlled vocabulary metadata', () => {
- const result = comp.isControlledVocabulary(controlledMetadata);
- expect(result).toBeTrue();
+ expect(comp.isControlledVocabulary(controlledMetadata)).toBeTrue();
});
it('should return translated vocabulary value when available', (done) => {
- const vocabEntry = {
- display: 'Translated Value',
- };
-
vocabularyServiceMock.getPublicVocabularyEntryByID.and.returnValue(
- of(
- createSuccessfulRemoteDataObject(
- buildPaginatedList(new PageInfo(), [vocabEntry]),
- ),
- ),
+ of(createSuccessfulRemoteDataObject(
+ buildPaginatedList(new PageInfo(), [{ display: 'Translated Value' }]),
+ )),
);
-
comp.getVocabularyValue(controlledMetadata).subscribe((value) => {
expect(value).toBe('Translated Value');
done();
});
});
+ it('should render search link when searchFilter is present', () => {
+ comp.searchFilter = 'subject';
+ expect(comp.hasSearchFilter()).toBeTrue();
+ expect(comp.getSearchQueryParams('test')).toEqual({ 'f.subject': 'test,equals' });
+ });
+
+ it('should render browse link when browseDefinition is provided', () => {
+ comp.browseDefinition = {
+ id: 'subject',
+ metadataKeys: [],
+ order: 0,
+ getRenderType: () => 'metadata',
+ } as any;
+ expect(comp.hasBrowseDefinition()).toBeTrue();
+ });
});
diff --git a/src/app/item-page/field-components/metadata-values/metadata-values.component.ts b/src/app/item-page/field-components/metadata-values/metadata-values.component.ts
index c37f1f975bf..04ed9473a24 100644
--- a/src/app/item-page/field-components/metadata-values/metadata-values.component.ts
+++ b/src/app/item-page/field-components/metadata-values/metadata-values.component.ts
@@ -103,10 +103,24 @@ export class MetadataValuesComponent implements OnChanges {
hasValue = hasValue;
+ /**
+ * Optional metadata field used to build search links for values.
+ * If defined, values will link to the search page using this field as filter.
+ */
+ @Input() searchFilter?: string;
+
ngOnChanges(changes: SimpleChanges): void {
this.renderMarkdown = !!this.appConfig.markdown.enabled && this.enableMarkdown;
}
+ /**
+ * Determines whether a search filter has been configured for this metadata field.
+ * Used to decide if values should be rendered as search links.
+ */
+ hasSearchFilter(): boolean {
+ return !!this.searchFilter;
+ }
+
/**
* Does this metadata value have a configured link to a browse definition?
*/
@@ -126,6 +140,17 @@ export class MetadataValuesComponent implements OnChanges {
return false;
}
+ /**
+ * Builds query parameters for the search page based on the configured search filter.
+ * The metadata value is used as the search term for the specified field.
+ *
+ * @param value The metadata value to search for.
+ * @returns Query parameters object for Angular router navigation.
+ */
+ getSearchQueryParams(value: string): any {
+ return { [`f.${this.searchFilter}`]: `${value},equals` };
+ }
+
/**
* Whether the metadata is a controlled vocabulary
* @param value A MetadataValue being displayed
diff --git a/src/app/item-page/simple/field-components/specific-field/item-page-field.component.html b/src/app/item-page/simple/field-components/specific-field/item-page-field.component.html
index f45d4657a46..576fbf43d4e 100644
--- a/src/app/item-page/simple/field-components/specific-field/item-page-field.component.html
+++ b/src/app/item-page/simple/field-components/specific-field/item-page-field.component.html
@@ -5,7 +5,8 @@
[label]="label"
[enableMarkdown]="enableMarkdown"
[urlRegex]="urlRegex"
- [browseDefinition]="browseDefinition|async"
- [img]="img"
- >
+ [browseDefinition]="browseDefinition | async"
+ [searchFilter]="searchFilter"
+ [img]="img">
+
diff --git a/src/app/item-page/simple/field-components/specific-field/item-page-field.component.ts b/src/app/item-page/simple/field-components/specific-field/item-page-field.component.ts
index 69c9acd03af..bcf188f36e1 100644
--- a/src/app/item-page/simple/field-components/specific-field/item-page-field.component.ts
+++ b/src/app/item-page/simple/field-components/specific-field/item-page-field.component.ts
@@ -55,7 +55,7 @@ export class ItemPageFieldComponent {
/**
* Fields (schema.element.qualifier) used to render their values.
*/
- fields: string[];
+ @Input() fields: string[];
/**
* Label i18n key for the rendered metadata
@@ -78,6 +78,13 @@ export class ItemPageFieldComponent {
*/
img: ImageField;
+ /**
+ * Search filter used when rendering subject metadata as search links.
+ */
+ get searchFilter(): string | undefined {
+ return this.fields?.includes('dc.subject') ? 'subject' : undefined;
+ }
+
/**
* 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.)