Skip to content

Commit 6f9d310

Browse files
authored
Merge pull request DSpace#1851 from atmire/w2p-93963-Add_support_for_line_breaks_markdown_and_mathjax_in_metadata
Add support for line breaks markdown and mathjax in metadata
2 parents 1370363 + e3351eb commit 6f9d310

20 files changed

Lines changed: 600 additions & 28 deletions

package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@
104104
"jwt-decode": "^3.1.2",
105105
"klaro": "^0.7.10",
106106
"lodash": "^4.17.21",
107+
"markdown-it": "^13.0.1",
108+
"markdown-it-mathjax3": "^4.3.1",
107109
"mirador": "^3.3.0",
108110
"mirador-dl-plugin": "^0.13.0",
109111
"mirador-share-plugin": "^0.11.0",
@@ -116,20 +118,21 @@
116118
"ngx-moment": "^5.0.0",
117119
"ngx-pagination": "5.0.0",
118120
"ngx-sortablejs": "^11.1.0",
121+
"ngx-ui-switch": "^11.0.1",
119122
"nouislider": "^14.6.3",
120123
"pem": "1.14.4",
121124
"postcss-cli": "^9.1.0",
122125
"prop-types": "^15.7.2",
123126
"react-copy-to-clipboard": "^5.0.1",
124127
"reflect-metadata": "^0.1.13",
125128
"rxjs": "^7.5.5",
129+
"sanitize-html": "^2.7.2",
126130
"sortablejs": "1.13.0",
127131
"tslib": "^2.0.0",
128132
"url-parse": "^1.5.6",
129133
"uuid": "^8.3.2",
130134
"webfontloader": "1.6.28",
131-
"zone.js": "~0.11.5",
132-
"ngx-ui-switch": "^11.0.1"
135+
"zone.js": "~0.11.5"
133136
},
134137
"devDependencies": {
135138
"@angular-builders/custom-webpack": "~13.1.0",
@@ -155,6 +158,7 @@
155158
"@types/js-cookie": "2.2.6",
156159
"@types/lodash": "^4.14.165",
157160
"@types/node": "^14.14.9",
161+
"@types/sanitize-html": "^2.6.2",
158162
"@typescript-eslint/eslint-plugin": "5.11.0",
159163
"@typescript-eslint/parser": "5.11.0",
160164
"axe-core": "^4.3.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: 18 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,19 @@ export class MetadataValuesComponent {
2728
*/
2829
@Input() label: string;
2930

31+
/**
32+
* Whether the {@link MarkdownPipe} should be used to render these metadata values.
33+
* This will only have effect if {@link MarkdownConfig#enabled} is true.
34+
* Mathjax will only be rendered if {@link MarkdownConfig#mathjax} is true.
35+
*/
36+
@Input() enableMarkdown = false;
37+
38+
/**
39+
* This variable will be true if both {@link environment.markdown.enabled} and {@link enableMarkdown} are true.
40+
*/
41+
renderMarkdown;
42+
43+
ngOnChanges(changes: SimpleChanges): void {
44+
this.renderMarkdown = !!environment.markdown.enabled && this.enableMarkdown;
45+
}
3046
}

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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,8 @@ export class ItemPageAbstractFieldComponent extends ItemPageFieldComponent {
3636
*/
3737
label = 'item.page.abstract';
3838

39+
/**
40+
* Use the {@link MarkdownPipe} to render dc.description.abstract values
41+
*/
42+
enableMarkdown = true;
3943
}
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: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,14 @@ 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';
14+
import { APP_CONFIG } from '../../../../../config/app-config.interface';
1115

1216
let comp: ItemPageFieldComponent;
1317
let fixture: ComponentFixture<ItemPageFieldComponent>;
18+
let markdownSpy;
1419

1520
const mockValue = 'test value';
1621
const mockField = 'dc.test';
@@ -20,17 +25,24 @@ const mockFields = [mockField];
2025
describe('ItemPageFieldComponent', () => {
2126
beforeEach(waitForAsync(() => {
2227
TestBed.configureTestingModule({
23-
imports: [TranslateModule.forRoot({
24-
loader: {
25-
provide: TranslateLoader,
26-
useClass: TranslateLoaderMock
27-
}
28-
})],
28+
imports: [
29+
TranslateModule.forRoot({
30+
loader: {
31+
provide: TranslateLoader,
32+
useClass: TranslateLoaderMock
33+
}
34+
}),
35+
SharedModule,
36+
],
37+
providers: [
38+
{ provide: APP_CONFIG, useValue: Object.assign({}, environment) },
39+
],
2940
declarations: [ItemPageFieldComponent, MetadataValuesComponent],
3041
schemas: [NO_ERRORS_SCHEMA]
3142
}).overrideComponent(ItemPageFieldComponent, {
3243
set: { changeDetection: ChangeDetectionStrategy.Default }
3344
}).compileComponents();
45+
markdownSpy = spyOn(MarkdownPipe.prototype, 'transform');
3446
}));
3547

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

50124
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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Component, Input } from '@angular/core';
2-
32
import { Item } from '../../../../core/shared/item.model';
43

54
/**
@@ -18,6 +17,11 @@ export class ItemPageFieldComponent {
1817
*/
1918
@Input() item: Item;
2019

20+
/**
21+
* Whether the {@link MarkdownPipe} should be used to render this metadata.
22+
*/
23+
enableMarkdown = false;
24+
2125
/**
2226
* Fields (schema.element.qualifier) used to render their values.
2327
*/

0 commit comments

Comments
 (0)