Skip to content

Commit 2f71dc3

Browse files
committed
issue DSpace#1404, issue DSpace#1762, issue DSpace#1763 Add support for line breaks, markdown and mathjax in metadata
1 parent e4f483c commit 2f71dc3

19 files changed

Lines changed: 562 additions & 27 deletions

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@
9797
"jwt-decode": "^3.1.2",
9898
"klaro": "^0.7.10",
9999
"lodash": "^4.17.21",
100+
"markdown-it": "^13.0.1",
101+
"markdown-it-mathjax3": "^4.3.1",
100102
"mirador": "^3.3.0",
101103
"mirador-dl-plugin": "^0.13.0",
102104
"mirador-share-plugin": "^0.11.0",
@@ -114,6 +116,7 @@
114116
"postcss-cli": "^8.3.0",
115117
"reflect-metadata": "^0.1.13",
116118
"rxjs": "^6.6.3",
119+
"sanitize-html": "^2.7.2",
117120
"sortablejs": "1.13.0",
118121
"tslib": "^2.0.0",
119122
"url-parse": "^1.5.3",

src/app/item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<td class="w-100">
2727
<div class="value-field">
2828
<div *ngIf="!(editable | async)">
29-
<span class="dont-break-out">{{metadata?.value}}</span>
29+
<span class="dont-break-out preserve-line-breaks">{{metadata?.value}}</span>
3030
</div>
3131
<div *ngIf="(editable | async)" class="field-container">
3232
<textarea class="form-control" type="textarea" attr.aria-labelledby="fieldValue" [(ngModel)]="metadata.value" [dsDebounce]
Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
<ds-metadata-field-wrapper [label]="label | translate">
2-
<span class="dont-break-out" *ngFor="let mdValue of mdValues; let last=last;">
3-
{{mdValue.value}}<span *ngIf="!last" [innerHTML]="separator"></span>
4-
</span>
2+
<ng-container *ngFor="let mdValue of mdValues; let last=last;">
3+
<ng-container *ngTemplateOutlet="(renderMarkdown ? markdown : simple); context: {value: mdValue.value, classes: 'dont-break-out preserve-line-breaks'}">
4+
</ng-container>
5+
<span class="separator" *ngIf="!last" [innerHTML]="separator"></span>
6+
</ng-container>
57
</ds-metadata-field-wrapper>
8+
9+
<ng-template #markdown let-value="value" let-classes="classes">
10+
<span class="{{classes}}" [innerHTML]="value | dsMarkdown | async">
11+
</span>
12+
</ng-template>
13+
14+
<ng-template #simple let-value="value" let-classes="classes">
15+
<span class="{{classes}}">
16+
{{value}}
17+
</span>
18+
</ng-template>

src/app/item-page/field-components/metadata-values/metadata-values.component.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ describe('MetadataValuesComponent', () => {
5858
});
5959

6060
it('should contain separators equal to the amount of metadata values minus one', () => {
61-
const separators = fixture.debugElement.queryAll(By.css('span>span'));
61+
const separators = fixture.debugElement.queryAll(By.css('span.separator'));
6262
expect(separators.length).toBe(mockMetadata.length - 1);
6363
});
6464

src/app/item-page/field-components/metadata-values/metadata-values.component.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { Component, Input } from '@angular/core';
1+
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
22
import { MetadataValue } from '../../../core/shared/metadata.models';
3+
import { environment } from '../../../../environments/environment';
34

45
/**
56
* This component renders the configured 'values' into the ds-metadata-field-wrapper component.
@@ -10,7 +11,7 @@ import { MetadataValue } from '../../../core/shared/metadata.models';
1011
styleUrls: ['./metadata-values.component.scss'],
1112
templateUrl: './metadata-values.component.html'
1213
})
13-
export class MetadataValuesComponent {
14+
export class MetadataValuesComponent implements OnChanges {
1415

1516
/**
1617
* The metadata values to display
@@ -27,4 +28,18 @@ export class MetadataValuesComponent {
2728
*/
2829
@Input() label: string;
2930

31+
/**
32+
* Whether this metadata should be rendered with markdown.
33+
*/
34+
@Input() enableMarkdown = false;
35+
36+
/**
37+
* This variable will be true if this metadata should be rendered with markdown, and if markdown is enabled in the
38+
* environment config.
39+
*/
40+
renderMarkdown = false;
41+
42+
ngOnChanges(changes: SimpleChanges): void {
43+
this.renderMarkdown = !!environment.enableMarkdown && this.enableMarkdown;
44+
}
3045
}

src/app/item-page/simple/field-components/specific-field/abstract/item-page-abstract-field.component.spec.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ItemPageAbstractFieldComponent } from './item-page-abstract-field.compo
55
import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
66
import { MetadataValuesComponent } from '../../../../field-components/metadata-values/metadata-values.component';
77
import { mockItemWithMetadataFieldAndValue } from '../item-page-field.component.spec';
8+
import { SharedModule } from '../../../../../shared/shared.module';
89

910
let comp: ItemPageAbstractFieldComponent;
1011
let fixture: ComponentFixture<ItemPageAbstractFieldComponent>;
@@ -15,12 +16,15 @@ const mockValue = 'test value';
1516
describe('ItemPageAbstractFieldComponent', () => {
1617
beforeEach(waitForAsync(() => {
1718
TestBed.configureTestingModule({
18-
imports: [TranslateModule.forRoot({
19-
loader: {
20-
provide: TranslateLoader,
21-
useClass: TranslateLoaderMock
22-
}
23-
})],
19+
imports: [
20+
TranslateModule.forRoot({
21+
loader: {
22+
provide: TranslateLoader,
23+
useClass: TranslateLoaderMock
24+
}
25+
}),
26+
SharedModule,
27+
],
2428
declarations: [ItemPageAbstractFieldComponent, MetadataValuesComponent],
2529
schemas: [NO_ERRORS_SCHEMA]
2630
}).overrideComponent(ItemPageAbstractFieldComponent, {

src/app/item-page/simple/field-components/specific-field/abstract/item-page-abstract-field.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ export class ItemPageAbstractFieldComponent extends ItemPageFieldComponent {
3636
*/
3737
label = 'item.page.abstract';
3838

39+
enableMarkdown = true;
3940
}
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
<div class="item-page-field">
2-
<ds-metadata-values [mdValues]="item?.allMetadata(fields)" [separator]="separator" [label]="label"></ds-metadata-values>
2+
<ds-metadata-values
3+
[mdValues]="item?.allMetadata(fields)"
4+
[separator]="separator"
5+
[label]="label"
6+
[enableMarkdown]="enableMarkdown"
7+
></ds-metadata-values>
38
</div>

src/app/item-page/simple/field-components/specific-field/item-page-field.component.spec.ts

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ import { MetadataValuesComponent } from '../../../field-components/metadata-valu
88
import { MetadataMap, MetadataValue } from '../../../../core/shared/metadata.models';
99
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
1010
import { createPaginatedList } from '../../../../shared/testing/utils.test';
11+
import { environment } from '../../../../../environments/environment';
12+
import { MarkdownPipe } from '../../../../shared/utils/markdown.pipe';
13+
import { SharedModule } from '../../../../shared/shared.module';
1114

1215
let comp: ItemPageFieldComponent;
1316
let fixture: ComponentFixture<ItemPageFieldComponent>;
17+
let markdownSpy;
1418

1519
const mockValue = 'test value';
1620
const mockField = 'dc.test';
@@ -20,17 +24,21 @@ const mockFields = [mockField];
2024
describe('ItemPageFieldComponent', () => {
2125
beforeEach(waitForAsync(() => {
2226
TestBed.configureTestingModule({
23-
imports: [TranslateModule.forRoot({
24-
loader: {
25-
provide: TranslateLoader,
26-
useClass: TranslateLoaderMock
27-
}
28-
})],
27+
imports: [
28+
TranslateModule.forRoot({
29+
loader: {
30+
provide: TranslateLoader,
31+
useClass: TranslateLoaderMock
32+
}
33+
}),
34+
SharedModule,
35+
],
2936
declarations: [ItemPageFieldComponent, MetadataValuesComponent],
3037
schemas: [NO_ERRORS_SCHEMA]
3138
}).overrideComponent(ItemPageFieldComponent, {
3239
set: { changeDetection: ChangeDetectionStrategy.Default }
3340
}).compileComponents();
41+
markdownSpy = spyOn(MarkdownPipe.prototype, 'transform');
3442
}));
3543

3644
beforeEach(waitForAsync(() => {
@@ -45,6 +53,68 @@ describe('ItemPageFieldComponent', () => {
4553
it('should display display the correct metadata value', () => {
4654
expect(fixture.nativeElement.innerHTML).toContain(mockValue);
4755
});
56+
57+
describe('when markdown is disabled in the environment config', () => {
58+
59+
beforeEach(() => {
60+
environment.enableMarkdown = false;
61+
});
62+
63+
describe('and markdown is disabled in this component', () => {
64+
65+
beforeEach(() => {
66+
comp.enableMarkdown = false;
67+
fixture.detectChanges();
68+
});
69+
70+
it('should not use the Markdown Pipe', () => {
71+
expect(markdownSpy).not.toHaveBeenCalled();
72+
});
73+
});
74+
75+
describe('and markdown is enabled in this component', () => {
76+
77+
beforeEach(() => {
78+
comp.enableMarkdown = true;
79+
fixture.detectChanges();
80+
});
81+
82+
it('should not use the Markdown Pipe', () => {
83+
expect(markdownSpy).not.toHaveBeenCalled();
84+
});
85+
});
86+
});
87+
88+
describe('when markdown is enabled in the environment config', () => {
89+
90+
beforeEach(() => {
91+
environment.enableMarkdown = true;
92+
});
93+
94+
describe('and markdown is disabled in this component', () => {
95+
96+
beforeEach(() => {
97+
comp.enableMarkdown = false;
98+
fixture.detectChanges();
99+
});
100+
101+
it('should not use the Markdown Pipe', () => {
102+
expect(markdownSpy).not.toHaveBeenCalled();
103+
});
104+
});
105+
106+
describe('and markdown is enabled in this component', () => {
107+
108+
beforeEach(() => {
109+
comp.enableMarkdown = true;
110+
fixture.detectChanges();
111+
});
112+
113+
it('should use the Markdown Pipe', () => {
114+
expect(markdownSpy).toHaveBeenCalled();
115+
});
116+
});
117+
});
48118
});
49119

50120
export function mockItemWithMetadataFieldAndValue(field: string, value: string): Item {

src/app/item-page/simple/field-components/specific-field/item-page-field.component.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ export class ItemPageFieldComponent {
1818
*/
1919
@Input() item: Item;
2020

21+
/**
22+
* Whether this metadata should be rendered with markdown.
23+
*/
24+
enableMarkdown = false;
25+
2126
/**
2227
* Fields (schema.element.qualifier) used to render their values.
2328
*/

0 commit comments

Comments
 (0)