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 _createCallbacks = [];
|
||||||
|
|
||||||
|
const isInside = (text, regex) => {
|
||||||
|
const matches = text.match(regex);
|
||||||
|
return matches && (matches.length % 2);
|
||||||
|
};
|
||||||
|
|
||||||
class Toolbar {
|
class Toolbar {
|
||||||
|
|
||||||
constructor(site) {
|
constructor(site) {
|
||||||
|
@ -639,24 +644,13 @@ export default Ember.Component.extend({
|
||||||
return null;
|
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) {
|
paste(e) {
|
||||||
if (!$(".d-editor-input").is(":focus")) {
|
if (!$(".d-editor-input").is(":focus")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isComposer = $("#reply-control .d-editor-input").is(":focus");
|
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 plainText = clipboard.getData("text/plain");
|
||||||
let html = clipboard.getData("text/html");
|
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) {
|
if (canPasteHtml && !handled) {
|
||||||
const markdown = toMarkdown(html);
|
let markdown = toMarkdown(html);
|
||||||
|
|
||||||
if (!plainText || plainText.length < markdown.length) {
|
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;
|
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 = [
|
const tags = [
|
||||||
|
@ -193,7 +211,7 @@ const tags = [
|
||||||
Tag.cell("td"), Tag.cell("th"),
|
Tag.cell("td"), Tag.cell("th"),
|
||||||
Tag.replace("br", "\n"), Tag.replace("hr", "\n---\n"), Tag.replace("head", ""),
|
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.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
|
// TO-DO CREATE: code, tbody, blockquote
|
||||||
// UPDATE: ol, pre, thead, th, td
|
// UPDATE: ol, pre, thead, th, td
|
||||||
|
@ -209,9 +227,11 @@ class Element {
|
||||||
|
|
||||||
if (parent) {
|
if (parent) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.parentNames = (parent.parentNames || []).slice();
|
this.parentNames = parent.parentNames.slice();
|
||||||
this.parentNames.push(parent.name);
|
this.parentNames.push(parent.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.parentNames = this.parentNames || [];
|
||||||
this.previous = previous;
|
this.previous = previous;
|
||||||
this.next = next;
|
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) {
|
export default function toMarkdown(html) {
|
||||||
try {
|
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
|
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) {
|
} catch(err) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -447,7 +447,7 @@ export function clipboardData(e, canUpload) {
|
||||||
const canUploadImage = canUpload && files.filter(f => f.type.match('^image/'))[0];
|
const canUploadImage = canUpload && files.filter(f => f.type.match('^image/'))[0];
|
||||||
const canPasteHtml = Discourse.SiteSettings.enable_rich_text_paste && types.includes("text/html") && !canUploadImage;
|
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
|
// This prevents a mini racer crash
|
||||||
|
|
|
@ -129,7 +129,7 @@ QUnit.test("converts img tag", assert => {
|
||||||
assert.equal(toMarkdown(html), `![description](${url})`);
|
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 html = "Lorem <del>ipsum dolor</del> sit <big>amet, <ins>consectetur</ins></big>";
|
||||||
let output = html;
|
let output = html;
|
||||||
assert.equal(toMarkdown(html), output);
|
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)`;
|
output = `Lorem [<del>ipsum \n dolor</del> sit.](http://example.com)`;
|
||||||
assert.equal(toMarkdown(html), output);
|
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