diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js
index 460920d8..d744a2e7 100644
--- a/browser/components/MarkdownPreview.js
+++ b/browser/components/MarkdownPreview.js
@@ -8,21 +8,7 @@ import flowchart from 'flowchart'
import SequenceDiagram from 'js-sequence-diagrams'
import eventEmitter from 'browser/main/lib/eventEmitter'
import fs from 'fs'
-
-function decodeHTMLEntities (text) {
- var entities = [
- ['apos', '\''],
- ['amp', '&'],
- ['lt', '<'],
- ['gt', '>']
- ]
-
- for (var i = 0, max = entities.length; i < max; ++i) {
- text = text.replace(new RegExp('&' + entities[i][0] + ';', 'g'), entities[i][1])
- }
-
- return text
-}
+import htmlTextHelper from 'browser/lib/htmlTextHelper'
const { remote } = require('electron')
const { app } = remote
@@ -241,6 +227,13 @@ export default class MarkdownPreview extends React.Component {
let { value, theme, indentSize, codeBlockTheme } = this.props
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
+
+ const codeBlocks = value.match(/(```)(.|[\n])*?(```)/g)
+ if (codeBlocks !== null) {
+ codeBlocks.forEach((codeBlock) => {
+ value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock))
+ })
+ }
this.refs.root.contentWindow.document.body.innerHTML = markdown.render(value)
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.taskListItem'), (el) => {
@@ -263,7 +256,7 @@ export default class MarkdownPreview extends React.Component {
let syntax = CodeMirror.findModeByName(el.className)
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
CodeMirror.requireMode(syntax.mode, () => {
- let content = decodeHTMLEntities(el.innerHTML)
+ let content = htmlTextHelper.decodeEntities(el.innerHTML)
el.innerHTML = ''
el.parentNode.className += ` cm-s-${codeBlockTheme} CodeMirror`
CodeMirror.runMode(content, syntax.mime, el, {
@@ -281,7 +274,7 @@ export default class MarkdownPreview extends React.Component {
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.flowchart'), (el) => {
Raphael.setWindow(this.getWindow())
try {
- let diagram = flowchart.parse(decodeHTMLEntities(el.innerHTML))
+ let diagram = flowchart.parse(htmlTextHelper.decodeEntities(el.innerHTML))
el.innerHTML = ''
diagram.drawSVG(el, opts)
_.forEach(el.querySelectorAll('a'), (el) => {
@@ -297,7 +290,7 @@ export default class MarkdownPreview extends React.Component {
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.sequence'), (el) => {
Raphael.setWindow(this.getWindow())
try {
- let diagram = SequenceDiagram.parse(decodeHTMLEntities(el.innerHTML))
+ let diagram = SequenceDiagram.parse(htmlTextHelper.decodeEntities(el.innerHTML))
el.innerHTML = ''
diagram.drawSVG(el, {theme: 'simple'})
_.forEach(el.querySelectorAll('a'), (el) => {
diff --git a/browser/lib/htmlTextHelper.js b/browser/lib/htmlTextHelper.js
new file mode 100644
index 00000000..49952fbd
--- /dev/null
+++ b/browser/lib/htmlTextHelper.js
@@ -0,0 +1,43 @@
+/**
+ * @fileoverview Text trimmer for html.
+ */
+
+/**
+ * @param {string} text
+ * @return {string}
+ */
+
+export function decodeEntities (text) {
+ var entities = [
+ ['apos', '\''],
+ ['amp', '&'],
+ ['lt', '<'],
+ ['gt', '>'],
+ ['#63', '\\?']
+ ]
+
+ for (var i = 0, max = entities.length; i < max; ++i) {
+ text = text.replace(new RegExp(`&${entities[i][0]};`, 'g'), entities[i][1])
+ }
+
+ return text
+}
+
+export function encodeEntities (text) {
+ const entities = [
+ ['\'', 'apos'],
+ ['<', 'lt'],
+ ['>', 'gt'],
+ ['\\?', '#63']
+ ]
+
+ entities.forEach((entity) => {
+ text = text.replace(new RegExp(entity[0], 'g'), `&${entity[1]};`)
+ })
+ return text
+}
+
+export default {
+ decodeEntities,
+ encodeEntities
+}
diff --git a/tests/lib/html-text-helper-test.js b/tests/lib/html-text-helper-test.js
new file mode 100644
index 00000000..a476c0dd
--- /dev/null
+++ b/tests/lib/html-text-helper-test.js
@@ -0,0 +1,52 @@
+/**
+ * @fileoverview Unit test for browser/lib/htmlTextHelper
+ */
+const test = require('ava')
+const htmlTextHelper = require('browser/lib/htmlTextHelper')
+
+// Unit test
+test('htmlTextHelper#decodeEntities should return encoded text (string)', t => {
+ // [input, expected]
+ const testCases = [
+ ['<a href=', 'Boostnote'],
+ ['<\\\\?php\n var = 'hoge';', '<\\\\?php\n var = \'hoge\';'],
+ ['&', '&']
+ ]
+
+ testCases.forEach(testCase => {
+ const [input, expected] = testCase
+ t.is(htmlTextHelper.decodeEntities(input), expected, `Test for decodeEntities() input: ${input} expected: ${expected}`)
+ })
+})
+
+test('htmlTextHelper#decodeEntities() should return decoded text (string)', t => {
+ // [input, expected]
+ const testCases = [
+ ['Boostnote', '<a href='https://boostnote.io'>Boostnote'],
+ [' {
+ const [input, expected] = testCase
+ t.is(htmlTextHelper.encodeEntities(input), expected, `Test for encodeEntities() input: ${input} expected: ${expected}`)
+ })
+})
+
+// Integration test
+test(t => {
+ const testCases = [
+ 'var test = \'test\'',
+ 'Boostnote',
+ ''
+ ]
+
+ testCases.forEach(testCase => {
+ const encodedText = htmlTextHelper.encodeEntities(testCase)
+ const decodedText = htmlTextHelper.decodeEntities(encodedText)
+ t.is(decodedText, testCase, 'Integration test through encodedText() and decodedText()')
+ })
+})