Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

All: Resolves #3839: Change Markdown rendering to align with CommonMark spec #3975

Merged
merged 7 commits into from
Oct 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 40 additions & 9 deletions CliClient/tests/MdToHtml.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import MdToHtml from 'lib/joplin-renderer/MdToHtml';
const os = require('os');
const { filename } = require('lib/path-utils');
const { asyncTest, setupDatabaseAndSynchronizer, switchClient } = require('test-utils.js');
const shim = require('lib/shim').default;
const MdToHtml = require('lib/joplin-renderer/MdToHtml').default;
const { themeStyle } = require('lib/theme');

function newTestMdToHtml(options:any = null) {
Expand Down Expand Up @@ -120,23 +120,54 @@ describe('MdToHtml', function() {
it('should return the rendered body only', asyncTest(async () => {
const mdToHtml = newTestMdToHtml();

// In this case, the HTML contains only the rendered markdown,
// with no wrapper and no style.
// The style is instead in the cssStrings property.
const result = await mdToHtml.render('just **testing**', null, { bodyOnly: true });
expect(result.cssStrings.length).toBeGreaterThan(0);
expect(result.html.trim()).toBe('just <strong>testing</strong>');
// In this case, the HTML contains only the rendered markdown, with
// no wrapper and no style. The style is instead in the cssStrings
// property.
{
const result = await mdToHtml.render('just **testing**', null, { bodyOnly: true });
expect(result.cssStrings.length).toBeGreaterThan(0);
expect(result.html.trim()).toBe('just <strong>testing</strong>');
}

// But it should not remove the wrapping <p> tags if there's more
// than one line
{
const result = await mdToHtml.render('one\n\ntwo', null, { bodyOnly: true });
expect(result.html.trim()).toBe('<p>one</p>\n<p>two</p>');
}
}));

it('should split HTML and CSS', asyncTest(async () => {
const mdToHtml = newTestMdToHtml();

// It is similar to the bodyOnly option, excepts that
// the rendered Markdown is wrapped in a DIV
// It is similar to the bodyOnly option, excepts that the rendered
// Markdown is wrapped in a DIV
const result = await mdToHtml.render('just **testing**', null, { splitted: true });
expect(result.cssStrings.length).toBeGreaterThan(0);
expect(result.html.trim()).toBe('<div id="rendered-md"><p>just <strong>testing</strong></p>\n</div>');
}));

it('should render links correctly', asyncTest(async () => {
const mdToHtml = newTestMdToHtml();

const testCases = [
// None of these should result in a link
['https://example.com', 'https://example.com'],
['file://C:\\AUTOEXEC.BAT', 'file://C:\\AUTOEXEC.BAT'],
['example.com', 'example.com'],
['oo.ps', 'oo.ps'],
['test@example.com', 'test@example.com'],

// Those should be converted to links
['<https://example.com>', '<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>'],
['[ok](https://example.com)', '<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>ok</a>'],
];

for (const testCase of testCases) {
const [input, expected] = testCase;
const actual = await mdToHtml.render(input, null, { bodyOnly: true, plainResourceRendering: true });
expect(actual.html).toBe(expected);
}
}));

});
9 changes: 7 additions & 2 deletions ReactNativeClient/lib/joplin-renderer/MdToHtml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,9 +318,14 @@ export default class MdToHtml {
return output;
}

// The string we are looking for is: <p></p>\n
private removeMarkdownItWrappingParagraph_(html:string) {
// <p></p>\n
if (html.length < 8) return html;

// If there are multiple <p> tags, we keep them because it's multiple lines
// and removing the first and last tag will result in invalid HTML.
if ((html.match(/<\/p>/g) || []).length > 1) return html;

if (html.substr(0, 3) !== '<p>') return html;
if (html.slice(-5) !== '</p>\n') return html;
return html.substring(3, html.length - 5);
Expand Down Expand Up @@ -376,7 +381,7 @@ export default class MdToHtml {
const markdownIt = new MarkdownIt({
breaks: !this.pluginEnabled('softbreaks'),
typographer: this.pluginEnabled('typographer'),
linkify: true,
// linkify: true,
html: true,
highlight: (str:string, lang:string) => {
let outputCodeHtml = '';
Expand Down
17 changes: 15 additions & 2 deletions ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/link_open.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,24 @@ function plugin(markdownIt:any, ruleOptions:RuleOptions) {

if (hrefAttr.indexOf('#') === 0 && href.indexOf('#') === 0) js = ''; // If it's an internal anchor, don't add any JS since the webview is going to handle navigating to the right place

const attrHtml = [];
attrHtml.push('data-from-md');
if (resourceIdAttr) attrHtml.push(resourceIdAttr);
if (title) attrHtml.push(`title='${htmlentities(title)}'`);
if (mime) attrHtml.push(`type='${htmlentities(mime)}'`);

if (ruleOptions.plainResourceRendering || ruleOptions.linkRenderingType === 2) {
return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${htmlentities(href)}' type='${htmlentities(mime)}'>`;
icon = '';
attrHtml.push(`href='${htmlentities(href)}'`);

// return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${htmlentities(href)}' type='${htmlentities(mime)}'>`;
} else {
return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${hrefAttr}' ${js} type='${htmlentities(mime)}'>${icon}`;
attrHtml.push(`href='${hrefAttr}'`);
if (js) attrHtml.push(js);
// return `<a data-from-md ${resourceIdAttr} title='${htmlentities(title)}' href='${hrefAttr}' ${js} type='${htmlentities(mime)}'>${icon}`;
}

return `<a ${attrHtml.join(' ')}>${icon}`;
};
}

Expand Down
4 changes: 2 additions & 2 deletions ReactNativeClient/lib/models/Setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,11 +565,11 @@ class Setting extends BaseModel {
},

// Deprecated - use markdown.plugin.*
'markdown.softbreaks': { value: false, type: SettingItemType.Bool, public: false, appTypes: ['mobile', 'desktop'] },
'markdown.softbreaks': { value: true, type: SettingItemType.Bool, public: false, appTypes: ['mobile', 'desktop'] },
'markdown.typographer': { value: false, type: SettingItemType.Bool, public: false, appTypes: ['mobile', 'desktop'] },
// Deprecated

'markdown.plugin.softbreaks': { value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable soft breaks')}${wysiwygYes}` },
'markdown.plugin.softbreaks': { value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable soft breaks')}${wysiwygYes}` },
'markdown.plugin.typographer': { value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable typographer support')}${wysiwygYes}` },
'markdown.plugin.katex': { value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable math expressions')}${wysiwygYes}` },
'markdown.plugin.fountain': { value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable Fountain syntax support')}${wysiwygYes}` },
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
"**/node_modules",
"ElectronClient/dist/**/*",
"CliClient/tests/support/**/*",
"CliClient/tests-build/support/**/*",
"CliClient/tests-build/**/*",
],
}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@
"**/node_modules",
"ElectronClient/dist/**/*",
"CliClient/tests/support/**/*",
"CliClient/tests-build/support/**/*",
"CliClient/tests-build/**/*",
],
}