FEATURE: Converting code tags to Markdown while pasting

This commit is contained in:
Vinoth Kannan 2017-12-18 22:12:05 +05:30
parent de20e36629
commit ad41523895
4 changed files with 100 additions and 20 deletions

View File

@ -37,6 +37,11 @@ const FOUR_SPACES_INDENT = '4-spaces-indent';
const _createCallbacks = [];
const isInside = (text, regex) => {
const matches = text.match(regex);
return matches && (matches.length % 2);
};
class Toolbar {
constructor(site) {
@ -639,24 +644,13 @@ export default Ember.Component.extend({
return null;
},
_pasteMarkdown(text) {
const { pre, lineVal } = this._getSelected(null, {lineVal: true});
if(lineVal && pre.match(/[^\n]$/)) { // inline pasting
text = text.replace(/^#+/, "").trim();
text = pre.match(/\S$/) ? ` ${text}` : text;
}
this.appEvents.trigger('composer:insert-text', text);
},
paste(e) {
if (!$(".d-editor-input").is(":focus")) {
return;
}
const isComposer = $("#reply-control .d-editor-input").is(":focus");
const { clipboard, canPasteHtml } = clipboardData(e, isComposer);
let { clipboard, canPasteHtml } = clipboardData(e, isComposer);
let plainText = clipboard.getData("text/plain");
let html = clipboard.getData("text/html");
@ -673,11 +667,27 @@ export default Ember.Component.extend({
}
}
const { pre, lineVal } = this._getSelected(null, {lineVal: true});
const isInlinePasting = pre.match(/[^\n]$/);
if (canPasteHtml && plainText) {
if (isInlinePasting) {
canPasteHtml = !(lineVal.match(/^```/) || isInside(pre, /`/g) || lineVal.match(/^ /));
} else {
canPasteHtml = !isInside(pre, /(^|\n)```/g);
}
}
if (canPasteHtml && !handled) {
const markdown = toMarkdown(html);
let markdown = toMarkdown(html);
if (!plainText || plainText.length < markdown.length) {
this._pasteMarkdown(markdown);
if(isInlinePasting) {
markdown = markdown.replace(/^#+/, "").trim();
markdown = pre.match(/\S$/) ? ` ${markdown}` : markdown;
}
this.appEvents.trigger('composer:insert-text', markdown);
handled = true;
}
}

View File

@ -183,6 +183,24 @@ class Tag {
};
}
static code() {
return class extends Tag {
constructor() {
super("code", "`", "`");
}
decorate(text) {
if (this.element.parentNames.includes("pre")) {
this.prefix = '\n\n```\n';
this.suffix = '\n```\n\n';
}
text = $('<textarea />').html(text).text();
return super.decorate(text);
}
};
}
}
const tags = [
@ -193,7 +211,7 @@ const tags = [
Tag.cell("td"), Tag.cell("th"),
Tag.replace("br", "\n"), Tag.replace("hr", "\n---\n"), Tag.replace("head", ""),
Tag.keep("ins"), Tag.keep("del"), Tag.keep("small"), Tag.keep("big"),
Tag.li(), Tag.link(), Tag.image(),
Tag.li(), Tag.link(), Tag.image(), Tag.code(),
// TO-DO CREATE: code, tbody, blockquote
// UPDATE: ol, pre, thead, th, td
@ -209,9 +227,11 @@ class Element {
if (parent) {
this.parent = parent;
this.parentNames = (parent.parentNames || []).slice();
this.parentNames = parent.parentNames.slice();
this.parentNames.push(parent.name);
}
this.parentNames = this.parentNames || [];
this.previous = previous;
this.next = next;
}
@ -291,11 +311,39 @@ class Element {
}
}
function putPlaceholders(html) {
const codeRegEx = /<code[^>]*>(.*?)<\/code>/gs;
const origHtml = html;
let match = codeRegEx.exec(origHtml);
let placeholders = [];
while(match) {
const placeholder = `DISCOURSE_PLACEHOLDER_${placeholders.length + 1}`;
let code = match[1];
code = $('<div />').html(code).text().replace(/^\n/, '').replace(/\n$/, '');
placeholders.push([placeholder, code]);
html = html.replace(match[0], `<code>${placeholder}</code>`);
match = codeRegEx.exec(origHtml);
}
const elements = parseHTML(html);
return { elements, placeholders };
}
function replacePlaceholders(markdown, placeholders) {
placeholders.forEach(p => {
markdown = markdown.replace(p[0], p[1]);
});
return markdown;
}
export default function toMarkdown(html) {
try {
let markdown = Element.parse(parseHTML(html)).trim();
const { elements, placeholders } = putPlaceholders(html);
let markdown = Element.parse(elements).trim();
markdown = markdown.replace(/^<b>/, "").replace(/<\/b>$/, "").trim(); // fix for google doc copy paste
return markdown.replace(/\r/g, "").replace(/\n \n/g, "\n\n").replace(/\n{3,}/g, "\n\n");
markdown = markdown.replace(/\r/g, "").replace(/\n \n/g, "\n\n").replace(/\n{3,}/g, "\n\n");
return replacePlaceholders(markdown, placeholders);
} catch(err) {
return "";
}

View File

@ -447,7 +447,7 @@ export function clipboardData(e, canUpload) {
const canUploadImage = canUpload && files.filter(f => f.type.match('^image/'))[0];
const canPasteHtml = Discourse.SiteSettings.enable_rich_text_paste && types.includes("text/html") && !canUploadImage;
return { clipboard: clipboard, types: types, canUpload: canUpload, canPasteHtml: canPasteHtml };
return { clipboard, types, canUpload, canPasteHtml };
}
// This prevents a mini racer crash

View File

@ -129,7 +129,7 @@ QUnit.test("converts img tag", assert => {
assert.equal(toMarkdown(html), `![description](${url})`);
});
QUnit.test("suppring html tags by keeping them", assert => {
QUnit.test("supporting html tags by keeping them", assert => {
let html = "Lorem <del>ipsum dolor</del> sit <big>amet, <ins>consectetur</ins></big>";
let output = html;
assert.equal(toMarkdown(html), output);
@ -148,3 +148,25 @@ QUnit.test("suppring html tags by keeping them", assert => {
output = `Lorem [<del>ipsum \n dolor</del> sit.](http://example.com)`;
assert.equal(toMarkdown(html), output);
});
QUnit.test("converts code tags", assert => {
let html = `Lorem ipsum dolor sit amet,
<pre><code>var helloWorld = () => {
alert(' hello \t\t world ');
return;
}
helloWorld();</code></pre>
consectetur.`;
let output = `Lorem ipsum dolor sit amet,\n\n\`\`\`\nvar helloWorld = () => {\n alert(' hello \t\t world ');\n return;\n}\nhelloWorld();\n\`\`\`\n\nconsectetur.`;
assert.equal(toMarkdown(html), output);
html = `Lorem ipsum dolor sit amet, <code>var helloWorld = () => {
alert(' hello \t\t world ');
return;
}
helloWorld();</code> consectetur.`;
output = `Lorem ipsum dolor sit amet, \`var helloWorld = () => {\n alert(' hello \t\t world ');\n return;\n}\nhelloWorld();\` consectetur.`;
assert.equal(toMarkdown(html), output);
});