Skip to content

Commit 88d7008

Browse files
authored
Merge pull request #3870 from rtibbles/assessment_tweaks
Fix markdown conversion for assessments
2 parents 7517281 + 6e25862 commit 88d7008

7 files changed

Lines changed: 80 additions & 49 deletions

File tree

contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/MarkdownEditor.vue

Lines changed: 4 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -57,23 +57,19 @@
5757
import '../mathquill/mathquill.js';
5858
import 'codemirror/lib/codemirror.css';
5959
import '@toast-ui/editor/dist/toastui-editor.css';
60-
import * as Showdown from 'showdown';
6160
6261
import Editor from '@toast-ui/editor';
63-
import { stripHtml } from 'string-strip-html';
6462
6563
import imageUpload, { paramsToImageFieldHTML } from '../plugins/image-upload';
6664
import formulas from '../plugins/formulas';
6765
import minimize from '../plugins/minimize';
68-
import formulaHtmlToMd from '../plugins/formulas/formula-html-to-md';
6966
import formulaMdToHtml from '../plugins/formulas/formula-md-to-html';
70-
import imagesHtmlToMd from '../plugins/image-upload/image-html-to-md';
7167
import imagesMdToHtml from '../plugins/image-upload/image-md-to-html';
7268
7369
import { CLASS_MATH_FIELD_ACTIVE } from '../constants';
7470
import { registerMarkdownFormulaField } from '../plugins/formulas/MarkdownFormulaField';
7571
import { registerMarkdownImageField } from '../plugins/image-upload/MarkdownImageField';
76-
import { clearNodeFormat, getExtensionMenuPosition } from './utils';
72+
import { clearNodeFormat, generateCustomConverter, getExtensionMenuPosition } from './utils';
7773
import FormulasMenu from './FormulasMenu/FormulasMenu';
7874
import ImagesMenu from './ImagesMenu/ImagesMenu';
7975
import ClickOutside from 'shared/directives/click-outside';
@@ -167,38 +163,7 @@
167163
mounted() {
168164
this.mathQuill = MathQuill.getInterface(2);
169165
170-
// This is currently the only way of inheriting and adjusting
171-
// default TUI's convertor methods
172-
// see https://github.com/nhn/tui.editor/issues/615
173-
const tmpEditor = new Editor({
174-
el: this.$refs.editor,
175-
});
176-
const showdown = new Showdown.Converter();
177-
const Convertor = tmpEditor.convertor.constructor;
178-
class CustomConvertor extends Convertor {
179-
toMarkdown(content) {
180-
content = imagesHtmlToMd(content);
181-
content = formulaHtmlToMd(content);
182-
content = showdown.makeMarkdown(content);
183-
// TUI.editor sprinkles in extra `<br>` tags that Kolibri renders literally
184-
// When showdown has already added linebreaks to render these in markdown
185-
// so we just remove these here.
186-
content = content.replaceAll('<br>', '');
187-
188-
// any copy pasted rich text that renders as HTML but does not get converted
189-
// will linger here, so remove it as Kolibri will render it literally also.
190-
content = stripHtml(content).result;
191-
return content;
192-
}
193-
toHTML(content) {
194-
// Kolibri and showdown assume double newlines for a single line break,
195-
// wheras TUI.editor prefers single newline characters.
196-
content = content.replaceAll('\n\n', '\n');
197-
content = super.toHTML(content);
198-
return content;
199-
}
200-
}
201-
tmpEditor.remove();
166+
const CustomConvertor = generateCustomConverter(this.$refs.editor);
202167
203168
const createBoldButton = () => {
204169
{
@@ -268,8 +233,8 @@
268233
// https://github.com/nhn/tui.editor/blob/master/apps/editor/docs/custom-html-renderer.md
269234
customHTMLRenderer: {
270235
text(node) {
271-
let content = formulaMdToHtml(node.literal);
272-
content = imagesMdToHtml(content);
236+
let content = formulaMdToHtml(node.literal, true);
237+
content = imagesMdToHtml(content, true);
273238
return {
274239
type: 'html',
275240
content,

contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/utils.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
import * as Showdown from 'showdown';
2+
import Editor from '@toast-ui/editor';
3+
import { stripHtml } from 'string-strip-html';
4+
5+
import imagesHtmlToMd from '../plugins/image-upload/image-html-to-md';
6+
import formulaHtmlToMd from '../plugins/formulas/formula-html-to-md';
7+
18
/**
29
* Clear DOM node by keeping only its text content.
310
*
@@ -74,3 +81,37 @@ export const getExtensionMenuPosition = ({ editorEl, targetX, targetY }) => {
7481
right: menuRight,
7582
};
7683
};
84+
85+
export const generateCustomConverter = el => {
86+
// This is currently the only way of inheriting and adjusting
87+
// default TUI's convertor methods
88+
// see https://github.com/nhn/tui.editor/issues/615
89+
const tmpEditor = new Editor({ el });
90+
const showdown = new Showdown.Converter();
91+
const Convertor = tmpEditor.convertor.constructor;
92+
class CustomConvertor extends Convertor {
93+
toMarkdown(content) {
94+
content = showdown.makeMarkdown(content);
95+
content = imagesHtmlToMd(content);
96+
content = formulaHtmlToMd(content);
97+
// TUI.editor sprinkles in extra `<br>` tags that Kolibri renders literally
98+
// When showdown has already added linebreaks to render these in markdown
99+
// so we just remove these here.
100+
content = content.replaceAll('<br>', '');
101+
102+
// any copy pasted rich text that renders as HTML but does not get converted
103+
// will linger here, so remove it as Kolibri will render it literally also.
104+
content = stripHtml(content).result;
105+
return content;
106+
}
107+
toHTML(content) {
108+
// Kolibri and showdown assume double newlines for a single line break,
109+
// wheras TUI.editor prefers single newline characters.
110+
content = content.replaceAll('\n\n', '\n');
111+
content = super.toHTML(content);
112+
return content;
113+
}
114+
}
115+
tmpEditor.remove();
116+
return CustomConvertor;
117+
};

contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/__tests__/utils.spec.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { clearNodeFormat } from '../MarkdownEditor/utils';
1+
/**
2+
* @jest-environment jest-environment-jsdom-sixteen
3+
*/
4+
// Jsdom@^16 is required to test toast UI, as it relies on the Range API.
5+
6+
import { clearNodeFormat, generateCustomConverter } from '../MarkdownEditor/utils';
27

38
const htmlStringToFragment = htmlString => {
49
const template = document.createElement('template');
@@ -56,3 +61,17 @@ describe('clearNodeFormat', () => {
5661
);
5762
});
5863
});
64+
65+
describe('markdown conversion', () => {
66+
it('converts image tags to markdown without escaping them', () => {
67+
const el = document.createElement('div');
68+
const CustomConvertor = generateCustomConverter(el);
69+
const converter = new CustomConvertor();
70+
const html =
71+
'<span is="markdown-image-field" vce-ready="" contenteditable="false" class="markdown-field-753aa86a-8159-403b-8b1c-d2b8f9504408 markdown-field-34843d46-79b8-40b4-866c-b83dc8916a47" editing="true" markdown="![](${☣ CONTENTSTORAGE}/bc1c5a86e1e46f20a6b4ee2c1bb6d6ff.png =485.453125x394)">![](${☣ CONTENTSTORAGE}/bc1c5a86e1e46f20a6b4ee2c1bb6d6ff.png =485.453125x394)</span>';
72+
73+
expect(converter.toMarkdown(html)).toBe(
74+
'![](${☣ CONTENTSTORAGE}/bc1c5a86e1e46f20a6b4ee2c1bb6d6ff.png =485.453125x394)'
75+
);
76+
});
77+
});

contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/formulas/formula-md-to-html.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
*
1313
*/
1414

15-
export default markdown => {
16-
return markdown.replace(/\$\$(.*?)\$\$/g, '<span is="markdown-formula-field">$1</span>');
15+
export default (markdown, editing) => {
16+
const editAttr = editing ? ' editing="true"' : '';
17+
return markdown.replace(
18+
/\$\$(.*?)\$\$/g,
19+
`<span is="markdown-formula-field"${editAttr}>$1</span>`
20+
);
1721
};

contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/image-md-to-html.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ import { IMAGE_REGEX, imageMdToImageFieldHTML } from './index';
1414

1515
// convert markdown images to image editor field custom elements
1616

17-
export default markdown => {
18-
return markdown.replace(IMAGE_REGEX, imageMd => imageMdToImageFieldHTML(imageMd));
17+
export default (markdown, editing) => {
18+
return markdown.replace(IMAGE_REGEX, imageMd => imageMdToImageFieldHTML(imageMd, editing));
1919
};

contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ export const paramsToImageMd = ({ src, alt, width, height }) => {
3030
}
3131
};
3232

33-
export const imageMdToImageFieldHTML = imageMd =>
34-
`<span is='markdown-image-field'>${imageMd}</span>`;
33+
export const imageMdToImageFieldHTML = (imageMd, editing) => {
34+
const editAttr = editing ? ' editing="true"' : '';
35+
return `<span is='markdown-image-field'${editAttr}>${imageMd}</span>`;
36+
};
3537
export const paramsToImageFieldHTML = params => imageMdToImageFieldHTML(paramsToImageMd(params));
3638

3739
export default imageUploadExtension;

contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/registerCustomMarkdownField.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,11 @@ export default VueComponent => {
102102
''
103103
);
104104
}
105-
this.parentNode.removeChild(this);
105+
if (this.parentNode) {
106+
this.parentNode.removeChild(this);
107+
}
106108
});
107109

108-
this.editing = true;
109-
110110
if (!hasLeftwardSpace(this)) {
111111
this.insertAdjacentText('beforebegin', '\xa0');
112112
}

0 commit comments

Comments
 (0)