diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/MarkdownEditor.vue b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/MarkdownEditor.vue
index a70673add2..f8b779ca96 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/MarkdownEditor.vue
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/MarkdownEditor.vue
@@ -57,23 +57,19 @@
import '../mathquill/mathquill.js';
import 'codemirror/lib/codemirror.css';
import '@toast-ui/editor/dist/toastui-editor.css';
- import * as Showdown from 'showdown';
import Editor from '@toast-ui/editor';
- import { stripHtml } from 'string-strip-html';
import imageUpload, { paramsToImageFieldHTML } from '../plugins/image-upload';
import formulas from '../plugins/formulas';
import minimize from '../plugins/minimize';
- import formulaHtmlToMd from '../plugins/formulas/formula-html-to-md';
import formulaMdToHtml from '../plugins/formulas/formula-md-to-html';
- import imagesHtmlToMd from '../plugins/image-upload/image-html-to-md';
import imagesMdToHtml from '../plugins/image-upload/image-md-to-html';
import { CLASS_MATH_FIELD_ACTIVE } from '../constants';
import { registerMarkdownFormulaField } from '../plugins/formulas/MarkdownFormulaField';
import { registerMarkdownImageField } from '../plugins/image-upload/MarkdownImageField';
- import { clearNodeFormat, getExtensionMenuPosition } from './utils';
+ import { clearNodeFormat, generateCustomConverter, getExtensionMenuPosition } from './utils';
import FormulasMenu from './FormulasMenu/FormulasMenu';
import ImagesMenu from './ImagesMenu/ImagesMenu';
import ClickOutside from 'shared/directives/click-outside';
@@ -167,38 +163,7 @@
mounted() {
this.mathQuill = MathQuill.getInterface(2);
- // This is currently the only way of inheriting and adjusting
- // default TUI's convertor methods
- // see https://github.com/nhn/tui.editor/issues/615
- const tmpEditor = new Editor({
- el: this.$refs.editor,
- });
- const showdown = new Showdown.Converter();
- const Convertor = tmpEditor.convertor.constructor;
- class CustomConvertor extends Convertor {
- toMarkdown(content) {
- content = imagesHtmlToMd(content);
- content = formulaHtmlToMd(content);
- content = showdown.makeMarkdown(content);
- // TUI.editor sprinkles in extra `
` tags that Kolibri renders literally
- // When showdown has already added linebreaks to render these in markdown
- // so we just remove these here.
- content = content.replaceAll('
', '');
-
- // any copy pasted rich text that renders as HTML but does not get converted
- // will linger here, so remove it as Kolibri will render it literally also.
- content = stripHtml(content).result;
- return content;
- }
- toHTML(content) {
- // Kolibri and showdown assume double newlines for a single line break,
- // wheras TUI.editor prefers single newline characters.
- content = content.replaceAll('\n\n', '\n');
- content = super.toHTML(content);
- return content;
- }
- }
- tmpEditor.remove();
+ const CustomConvertor = generateCustomConverter(this.$refs.editor);
const createBoldButton = () => {
{
@@ -268,8 +233,8 @@
// https://github.com/nhn/tui.editor/blob/master/apps/editor/docs/custom-html-renderer.md
customHTMLRenderer: {
text(node) {
- let content = formulaMdToHtml(node.literal);
- content = imagesMdToHtml(content);
+ let content = formulaMdToHtml(node.literal, true);
+ content = imagesMdToHtml(content, true);
return {
type: 'html',
content,
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/utils.js b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/utils.js
index 5894845273..f5ce62e98e 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/utils.js
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/MarkdownEditor/utils.js
@@ -1,3 +1,10 @@
+import * as Showdown from 'showdown';
+import Editor from '@toast-ui/editor';
+import { stripHtml } from 'string-strip-html';
+
+import imagesHtmlToMd from '../plugins/image-upload/image-html-to-md';
+import formulaHtmlToMd from '../plugins/formulas/formula-html-to-md';
+
/**
* Clear DOM node by keeping only its text content.
*
@@ -74,3 +81,37 @@ export const getExtensionMenuPosition = ({ editorEl, targetX, targetY }) => {
right: menuRight,
};
};
+
+export const generateCustomConverter = el => {
+ // This is currently the only way of inheriting and adjusting
+ // default TUI's convertor methods
+ // see https://github.com/nhn/tui.editor/issues/615
+ const tmpEditor = new Editor({ el });
+ const showdown = new Showdown.Converter();
+ const Convertor = tmpEditor.convertor.constructor;
+ class CustomConvertor extends Convertor {
+ toMarkdown(content) {
+ content = showdown.makeMarkdown(content);
+ content = imagesHtmlToMd(content);
+ content = formulaHtmlToMd(content);
+ // TUI.editor sprinkles in extra `
` tags that Kolibri renders literally
+ // When showdown has already added linebreaks to render these in markdown
+ // so we just remove these here.
+ content = content.replaceAll('
', '');
+
+ // any copy pasted rich text that renders as HTML but does not get converted
+ // will linger here, so remove it as Kolibri will render it literally also.
+ content = stripHtml(content).result;
+ return content;
+ }
+ toHTML(content) {
+ // Kolibri and showdown assume double newlines for a single line break,
+ // wheras TUI.editor prefers single newline characters.
+ content = content.replaceAll('\n\n', '\n');
+ content = super.toHTML(content);
+ return content;
+ }
+ }
+ tmpEditor.remove();
+ return CustomConvertor;
+};
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/__tests__/utils.spec.js b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/__tests__/utils.spec.js
index d201e81b33..1a55e0b179 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/__tests__/utils.spec.js
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/__tests__/utils.spec.js
@@ -1,4 +1,9 @@
-import { clearNodeFormat } from '../MarkdownEditor/utils';
+/**
+ * @jest-environment jest-environment-jsdom-sixteen
+ */
+// Jsdom@^16 is required to test toast UI, as it relies on the Range API.
+
+import { clearNodeFormat, generateCustomConverter } from '../MarkdownEditor/utils';
const htmlStringToFragment = htmlString => {
const template = document.createElement('template');
@@ -56,3 +61,17 @@ describe('clearNodeFormat', () => {
);
});
});
+
+describe('markdown conversion', () => {
+ it('converts image tags to markdown without escaping them', () => {
+ const el = document.createElement('div');
+ const CustomConvertor = generateCustomConverter(el);
+ const converter = new CustomConvertor();
+ const html =
+ '';
+
+ expect(converter.toMarkdown(html)).toBe(
+ ''
+ );
+ });
+});
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/formulas/formula-md-to-html.js b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/formulas/formula-md-to-html.js
index f99fcd2f09..61669e5eac 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/formulas/formula-md-to-html.js
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/formulas/formula-md-to-html.js
@@ -12,6 +12,10 @@
*
*/
-export default markdown => {
- return markdown.replace(/\$\$(.*?)\$\$/g, '$1');
+export default (markdown, editing) => {
+ const editAttr = editing ? ' editing="true"' : '';
+ return markdown.replace(
+ /\$\$(.*?)\$\$/g,
+ `$1`
+ );
};
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/image-md-to-html.js b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/image-md-to-html.js
index c452c5a82a..1f61c121d1 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/image-md-to-html.js
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/image-md-to-html.js
@@ -14,6 +14,6 @@ import { IMAGE_REGEX, imageMdToImageFieldHTML } from './index';
// convert markdown images to image editor field custom elements
-export default markdown => {
- return markdown.replace(IMAGE_REGEX, imageMd => imageMdToImageFieldHTML(imageMd));
+export default (markdown, editing) => {
+ return markdown.replace(IMAGE_REGEX, imageMd => imageMdToImageFieldHTML(imageMd, editing));
};
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/index.js b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/index.js
index b4b13a7201..0e33f894c4 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/index.js
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/image-upload/index.js
@@ -30,8 +30,10 @@ export const paramsToImageMd = ({ src, alt, width, height }) => {
}
};
-export const imageMdToImageFieldHTML = imageMd =>
- `${imageMd}`;
+export const imageMdToImageFieldHTML = (imageMd, editing) => {
+ const editAttr = editing ? ' editing="true"' : '';
+ return `${imageMd}`;
+};
export const paramsToImageFieldHTML = params => imageMdToImageFieldHTML(paramsToImageMd(params));
export default imageUploadExtension;
diff --git a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/registerCustomMarkdownField.js b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/registerCustomMarkdownField.js
index c9a3f51940..da581955be 100644
--- a/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/registerCustomMarkdownField.js
+++ b/contentcuration/contentcuration/frontend/shared/views/MarkdownEditor/plugins/registerCustomMarkdownField.js
@@ -102,11 +102,11 @@ export default VueComponent => {
''
);
}
- this.parentNode.removeChild(this);
+ if (this.parentNode) {
+ this.parentNode.removeChild(this);
+ }
});
- this.editing = true;
-
if (!hasLeftwardSpace(this)) {
this.insertAdjacentText('beforebegin', '\xa0');
}