1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Desktop: WYSIWYG: Preserve HTML code in Markdown when editing from wysiwyg editor

This commit is contained in:
Laurent Cozic 2020-04-10 17:12:41 +00:00
parent 12a7b3c73c
commit bd99b25848
21 changed files with 139 additions and 71 deletions

View File

@ -2111,7 +2111,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -2132,12 +2133,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -2152,17 +2155,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -2279,7 +2285,8 @@
"inherits": {
"version": "2.0.4",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -2291,6 +2298,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -2305,6 +2313,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -2312,12 +2321,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.9.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -2336,6 +2347,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -2425,7 +2437,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -2437,6 +2450,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -2522,7 +2536,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -2558,6 +2573,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -2577,6 +2593,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -2620,12 +2637,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
@ -3730,9 +3749,9 @@
"dev": true
},
"joplin-turndown": {
"version": "4.0.24",
"resolved": "https://registry.npmjs.org/joplin-turndown/-/joplin-turndown-4.0.24.tgz",
"integrity": "sha512-mKd2rAFzJKnhTVjEpHomG+T01//uz5rXVSAOYRh3/JKXpY7QUhVp8jCmFfO+kaadrLABTz04mvTmyyoOadxdTA==",
"version": "4.0.27",
"resolved": "https://registry.npmjs.org/joplin-turndown/-/joplin-turndown-4.0.27.tgz",
"integrity": "sha512-qwJEzpbsyXnfjYgCVXWbuf3BdbDAaP7edm5ubeVAaQ3RnaiSEB2xZLg3lubmuFOIxXnkMDMkGJC4G+qAbCW95w==",
"requires": {
"css": "^2.2.4",
"html-entities": "^1.2.1",
@ -6590,7 +6609,7 @@
"requires": {
"chalk": "^2.1.0",
"emphasize": "^1.5.0",
"node-emoji": "git+https://github.com/laurent22/node-emoji.git#9fa01eac463e94dde1316ef8c53089eeef4973b5",
"node-emoji": "git+https://github.com/laurent22/node-emoji.git",
"slice-ansi": "^1.0.0",
"string-width": "^2.1.1",
"terminal-kit": "^1.13.11",

View File

@ -55,7 +55,7 @@
"htmlparser2": "^4.1.0",
"image-data-uri": "^2.0.0",
"image-type": "^3.0.0",
"joplin-turndown": "^4.0.24",
"joplin-turndown": "^4.0.27",
"joplin-turndown-plugin-gfm": "^1.0.12",
"json-stringify-safe": "^5.0.1",
"jssha": "^2.3.0",

View File

@ -0,0 +1,6 @@
<p>Line 1</p>
<div class="jop-noMdConv">Line 2</div>
<div class="jop-noMdConv"><span class="jop-noMdConv">Line</span> 4</div>
<div><span>Line</span> 5</div>
<p>Line 6</p>
<div class="jop-noMdConv">Markdown <strong>inside</strong></div>

View File

@ -0,0 +1,9 @@
Line 1
<div>Line 2</div><div><span>Line</span> 4</div>
Line 5
Line 6
<div>Markdown **inside**</div>

View File

@ -0,0 +1,4 @@
<p>Line 1</p>
<div class="jop-noMdConv">Line 2</div>
<div class="jop-noMdConv"><span class="jop-noMdConv">Line</span> 4</div>
Line 5

View File

@ -0,0 +1,4 @@
Line 1
<div>Line 2</div>
<div><span>Line</span> 4</div>
Line 5

View File

@ -1,2 +1,2 @@
<img src=""/>
<img src=""/>
<img src="" class="jop-noMdConv"/>
<img src="" class="jop-noMdConv"/>

View File

@ -1,4 +1,4 @@
<style>
<style class="jop-noMdConv">
.md-checkbox input[type="checkbox"]:not(:checked),
.md-checkbox input[type="checkbox"]:not(:checked)+label {
display:none;

View File

@ -1 +1 @@
<p><a href="#">Testing <strong>inline</strong> text</a></p>
<p><a href="#" class="jop-noMdConv">Testing <strong>inline</strong> text</a></p>

View File

@ -1 +1 @@
<p>Should be <span style="color: red;">red</span>.</p>
<p>Should be <span style="color: red;" class="jop-noMdConv">red</span>.</p>

View File

@ -1,4 +1,4 @@
<div class="different">
<div class="different jop-noMdConv">
<h5 id="h5">H5</h5>
<h6 id="h6">H6</h6>
</div>

View File

@ -1,4 +1,4 @@
<style>
<style class="jop-noMdConv">
.different h5 { color: lightgreen; }
h5 { color: orange; }
h6 { color: orange; }

View File

@ -1 +1 @@
<p><mark>bla</mark></p>
<p><mark class="jop-noMdConv">bla</mark></p>

View File

@ -1 +1 @@
<div>M&amp;Ms</div>
<div class="jop-noMdConv">M&amp;Ms</div>

View File

@ -375,6 +375,7 @@ function NoteText2(props:NoteTextProps) {
const titleInputRef = useRef<any>();
const formNoteRef = useRef<FormNote>();
formNoteRef.current = { ...formNote };
const isMountedRef = useRef(true);
useWindowCommand(props.windowCommand, props.dispatch, formNote, titleInputRef, editorRef);
@ -471,6 +472,7 @@ function NoteText2(props:NoteTextProps) {
// formNote in the dependency array or that effect will run every time the note changes. We only
// want to run it once on unmount. So because of that we need to use that formNoteRef.
return () => {
isMountedRef.current = false;
saveNoteIfWillChange(formNoteRef.current, editorRef, props.dispatch);
};
}, []);
@ -556,6 +558,15 @@ function NoteText2(props:NoteTextProps) {
}, [props.noteId, props.isProvisional, formNote, waitingToSaveNote]);
const onFieldChange = useCallback((field:string, value:any, changeId: number = 0) => {
if (!isMountedRef.current) {
// When the component is unmounted, various actions can happen which can
// trigger onChange events, for example the textarea might be cleared.
// We need to ignore these events, otherwise the note is going to be saved
// with an invalid body.
reg.logger().debug('Skipping change event because the component is unmounted');
return;
}
handleProvisionalFlag();
const change = field === 'body' ? {

View File

@ -164,7 +164,7 @@ const TinyMCE = (props:TinyMCEProps, ref:any) => {
if (dispatchDidUpdateIID_) clearTimeout(dispatchDidUpdateIID_);
dispatchDidUpdateIID_ = setTimeout(() => {
dispatchDidUpdateIID_ = null;
editor.getDoc().dispatchEvent(new Event('joplin-noteDidUpdate'));
if (editor && editor.getDoc()) editor.getDoc().dispatchEvent(new Event('joplin-noteDidUpdate'));
}, 10);
};

View File

@ -1309,7 +1309,8 @@
"version": "5.1.2",
"resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -1429,7 +1430,8 @@
"version": "3.0.3",
"resolved": false,
"integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
"dev": true
"dev": true,
"optional": true
}
}
},
@ -1447,13 +1449,15 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
"dev": true
"dev": true,
"optional": true
},
"is-glob": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
"dev": true,
"optional": true,
"requires": {
"is-extglob": "^1.0.0"
}
@ -1941,7 +1945,8 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.1.tgz",
"integrity": "sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA==",
"dev": true
"dev": true,
"optional": true
},
"boxen": {
"version": "4.2.0",
@ -4933,13 +4938,15 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
"dev": true
"dev": true,
"optional": true
},
"is-glob": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
"dev": true,
"optional": true,
"requires": {
"is-extglob": "^1.0.0"
}
@ -5107,7 +5114,8 @@
"version": "2.1.1",
"resolved": false,
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -5156,7 +5164,8 @@
"version": "1.1.0",
"resolved": false,
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
@ -5169,7 +5178,8 @@
"version": "1.1.0",
"resolved": false,
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -5300,7 +5310,8 @@
"version": "2.0.4",
"resolved": false,
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -5314,6 +5325,7 @@
"resolved": false,
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -5330,6 +5342,7 @@
"resolved": false,
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -5338,13 +5351,15 @@
"version": "0.0.8",
"resolved": false,
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.9.0",
"resolved": false,
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -5365,6 +5380,7 @@
"resolved": false,
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -5463,7 +5479,8 @@
"version": "1.0.1",
"resolved": false,
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -5477,6 +5494,7 @@
"resolved": false,
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -5572,7 +5590,8 @@
"version": "5.1.2",
"resolved": false,
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -5614,6 +5633,7 @@
"resolved": false,
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -5635,6 +5655,7 @@
"resolved": false,
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -5683,13 +5704,15 @@
"version": "1.0.2",
"resolved": false,
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.1.1",
"resolved": false,
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
"dev": true,
"optional": true
}
}
},
@ -6711,9 +6734,9 @@
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
},
"joplin-turndown": {
"version": "4.0.24",
"resolved": "https://registry.npmjs.org/joplin-turndown/-/joplin-turndown-4.0.24.tgz",
"integrity": "sha512-mKd2rAFzJKnhTVjEpHomG+T01//uz5rXVSAOYRh3/JKXpY7QUhVp8jCmFfO+kaadrLABTz04mvTmyyoOadxdTA==",
"version": "4.0.27",
"resolved": "https://registry.npmjs.org/joplin-turndown/-/joplin-turndown-4.0.27.tgz",
"integrity": "sha512-qwJEzpbsyXnfjYgCVXWbuf3BdbDAaP7edm5ubeVAaQ3RnaiSEB2xZLg3lubmuFOIxXnkMDMkGJC4G+qAbCW95w==",
"requires": {
"css": "^2.2.4",
"html-entities": "^1.2.1",
@ -8621,7 +8644,8 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
"dev": true
"dev": true,
"optional": true
},
"is-glob": {
"version": "2.0.1",

View File

@ -110,7 +110,7 @@
"html-minifier": "^4.0.0",
"htmlparser2": "^4.1.0",
"image-type": "^3.0.0",
"joplin-turndown": "^4.0.24",
"joplin-turndown": "^4.0.27",
"joplin-turndown-plugin-gfm": "^1.0.12",
"json-stringify-safe": "^5.0.1",
"jssha": "^2.3.1",

View File

@ -32,7 +32,7 @@ function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any
// So the sanitizeHtml function must handle this kind of non-valid HTML.
if (!sanitizedContent) {
sanitizedContent = htmlUtils.sanitizeHtml(token.content);
sanitizedContent = htmlUtils.sanitizeHtml(token.content, { addNoMdConvClass: true });
}
token.content = sanitizedContent;

View File

@ -70,7 +70,13 @@ class HtmlUtils {
return selfClosingElements.includes(tagName.toLowerCase());
}
sanitizeHtml(html) {
sanitizeHtml(html, options = null) {
options = Object.assign({}, {
// If true, adds a "jop-noMdConv" class to all the tags.
// It can be used afterwards to restore HTML tags in Markdown.
addNoMdConvClass: false,
}, options);
const htmlparser2 = require('htmlparser2');
const output = [];
@ -95,6 +101,15 @@ class HtmlUtils {
for (const eventName of JS_EVENT_NAMES) {
delete attrs[eventName];
}
if (options.addNoMdConvClass) {
let classAttr = attrs['class'] || '';
if (!classAttr.includes('jop-noMdConv')) {
classAttr += ' jop-noMdConv';
attrs['class'] = classAttr.trim();
}
}
let attrHtml = this.attributesHtml(attrs);
if (attrHtml) attrHtml = ` ${attrHtml}`;
const closingSign = this.isSelfClosingTag(name) ? '/>' : '>';

View File

@ -1,24 +0,0 @@
const { asyncTest } = require('./test-utils');
const MdToHtml = require('../MdToHtml');
describe('MdToHtml', function() {
beforeEach(async (done) => {
done();
});
it('should convert Markdown to HTML', asyncTest(async () => {
const mdToHtml = new MdToHtml({
ResourceModel: null,
});
const md = 'Testing: **Testing**';
const html = mdToHtml.render(md);
console.info(html);
// ![test.jpg](:/b08c25f72bea437ebef5f6470944c3b9)
}));
});