FEATURE: Converting code tags to Markdown while pasting
This commit is contained in:
parent
de20e36629
commit
ad41523895
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 "";
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue