feat: fully implement basic commonmark support

This commit is contained in:
thecodrr
2020-09-08 15:06:28 +05:00
parent 745b37c3b5
commit fe8bdeacc7

View File

@@ -44,32 +44,54 @@ function matchInline(text, pattern, lineStart, commit) {
return { annotatedText, matchedText, startIndex };
}
class MarkdownShortcuts {
_makeEmphasisMatcher(name, numberOfChars, bold, italic) {
_makeHeaderAction(headerSize) {
return (text, selection, _pattern, lineStart) => {
const [prevLine] = this.quill.getLine(lineStart - 1);
const prevLineStart =
selection.index - text.length - prevLine.cache.length;
const prevLineText = this.quill.getText(
prevLineStart,
prevLine.cache.length
);
if (prevLineText.trim().length <= 0) return false;
setTimeout(() => {
this.quill.formatLine(prevLineStart, 0, "header", headerSize);
this.quill.deleteText(lineStart, text.length);
}, 0);
};
}
_makeInlineAction(inlineStyle) {
return (text, _selection, pattern, lineStart, commit, trigger) => {
const matchInfo = matchInline(text, pattern, lineStart, commit);
if (!matchInfo) return;
const { matchedText, annotatedText, startIndex } = matchInfo;
setTimeout(() => {
this.quill.deleteText(startIndex, annotatedText.length);
if (!commit) this.quill.insertText(startIndex, " ");
this.quill.insertText(
startIndex + (commit ? 0 : 1),
commit ? matchedText : annotatedText.trim(),
inlineStyle
);
if (commit)
this.quill.insertText(this.quill.getSelection(), trigger, {
bold: false,
italic: false,
strike: false,
code: false,
});
}, 0);
};
}
_makeEmphasisMatcher(name, numberOfChars, inlineStyle) {
return {
name,
pattern: new RegExp(
`(?:^|\\s|\\n|[^A-z0-9_*~\`])(\\*{${numberOfChars}}|_{${numberOfChars}})((?!\\1).*?)(\\1)($|\\s|\\n|[^A-z0-9_*~\`])`,
"g"
),
action: (text, _selection, pattern, lineStart, commit) => {
const matchInfo = matchInline(text, pattern, lineStart, commit);
if (!matchInfo) return;
const { matchedText, annotatedText, startIndex } = matchInfo;
setTimeout(() => {
this.quill.deleteText(startIndex, annotatedText.length);
this.quill.insertText(
startIndex,
commit ? matchedText + " " : annotatedText,
{
bold,
italic,
}
);
this.quill.format("bold", false);
this.quill.format("italic", false);
}, 0);
},
action: this._makeInlineAction(inlineStyle),
};
}
@@ -79,9 +101,9 @@ class MarkdownShortcuts {
this.ignoreTags = ["PRE"];
this.matches = [
this._makeEmphasisMatcher("bolditalic", 3, true, true),
this._makeEmphasisMatcher("bold", 2, true, false),
this._makeEmphasisMatcher("italic", 1, false, true),
this._makeEmphasisMatcher("bolditalic", 3, { bold: true, italic: true }),
this._makeEmphasisMatcher("bold", 2, { bold: true }),
this._makeEmphasisMatcher("italic", 1, { italic: true }),
{
name: "header",
pattern: /^(#){1,6}\s/g,
@@ -96,6 +118,16 @@ class MarkdownShortcuts {
}, 0);
},
},
{
name: "subtext-header-1",
pattern: /^=+$/g,
action: this._makeHeaderAction(1),
},
{
name: "subtext-header-2",
pattern: /^-+$/g,
action: this._makeHeaderAction(2),
},
{
name: "blockquote",
pattern: /^(>)\s/g,
@@ -109,33 +141,22 @@ class MarkdownShortcuts {
},
{
name: "code-block",
pattern: /^`{3}(?:\s|\n)/g,
action: (text, selection) => {
pattern: /^`{3}/g,
action: (text, selection, _pattern, lineStart, commit, trigger) => {
if (!commit) return;
// Need to defer this action https://github.com/quilljs/quill/issues/1134
setTimeout(() => {
this.quill.formatLine(selection.index, 1, "code-block", true);
this.quill.deleteText(selection.index - 4, 4);
let deleteCount = trigger === " " ? 4 : 3;
this.quill.deleteText(selection.index - deleteCount, deleteCount);
this.quill.setSelection(selection.index - deleteCount);
}, 0);
},
},
{
name: "strikethrough",
pattern: /(?:^|\s|\n|[^A-z0-9_*~`])(~{2})((?!\1).*?)(\1)($|\s|\n|[^A-z0-9_*~`])/g,
action: (text, _selection, pattern, lineStart, commit) => {
const matchInfo = matchInline(text, pattern, lineStart, commit);
if (!matchInfo) return;
const { matchedText, annotatedText, startIndex } = matchInfo;
setTimeout(() => {
this.quill.deleteText(startIndex, annotatedText.length);
this.quill.insertText(
startIndex,
commit ? matchedText : annotatedText,
{ strike: true }
);
this.quill.format("strike", false);
}, 0);
},
action: this._makeInlineAction({ strike: true }),
},
{
name: "displayformula",
@@ -177,27 +198,13 @@ class MarkdownShortcuts {
{
name: "code",
pattern: /(?:^|\s|\n|[^A-z0-9_*~`])(`)((?!\1).*?)(\1)($|\s|\n|[^A-z0-9_*~`])/g,
action: (text, _selection, pattern, lineStart, commit) => {
const matchInfo = matchInline(text, pattern, lineStart, commit);
if (!matchInfo) return;
const { matchedText, annotatedText, startIndex } = matchInfo;
setTimeout(() => {
this.quill.deleteText(startIndex, annotatedText.length);
if (!commit) this.quill.insertText(startIndex, " ");
this.quill.insertText(
startIndex + (commit ? 0 : 1),
commit ? matchedText : annotatedText.trim(),
{ code: true }
);
this.quill.format("code", false);
if (commit) this.quill.insertText(this.quill.getSelection(), " ");
}, 0);
},
action: this._makeInlineAction({
code: true,
}),
},
{
name: "hr",
pattern: /^(-\s?){3}/g,
pattern: /^(-|\*){3}/g,
action: (text, selection, pattern) => {
setTimeout(() => {
const matchedText = text.match(pattern)[0];
@@ -222,45 +229,30 @@ class MarkdownShortcuts {
},
{
name: "image",
pattern: /(?:!\[(.+?)\])(?:\((.+?)\))/g,
action: (text, selection, pattern) => {
const startIndex = text.search(pattern);
const matchedText = text.match(pattern)[0];
// const hrefText = text.match(/(?:!\[(.*?)\])/g)[0]
const hrefLink = text.match(/(?:\((.*?)\))/g)[0];
const start = selection.index - matchedText.length - 1;
if (startIndex !== -1) {
setTimeout(() => {
this.quill.deleteText(start, matchedText.length);
this.quill.insertEmbed(
start,
"image",
hrefLink.slice(1, hrefLink.length - 1)
);
}, 0);
}
pattern: /!\[([^\]]*)]\(([^)"]+)(?: "([^"]+)")?\)/g,
action: (text, selection, pattern, _lineStart, commit, trigger) => {
if (!commit) return;
const [matchedText, , link] = pattern.exec(text);
const start =
selection.index - matchedText.length - (trigger === " " ? 1 : 0);
setTimeout(() => {
this.quill.deleteText(start, matchedText.length);
this.quill.insertEmbed(start, "image", link);
}, 0);
},
},
{
name: "link",
pattern: /(?:\[(.+?)\])(?:\((.+?)\))/g,
action: (text, selection, pattern) => {
const startIndex = text.search(pattern);
const matchedText = text.match(pattern)[0];
const hrefText = text.match(/(?:\[(.*?)\])/g)[0];
const hrefLink = text.match(/(?:\((.*?)\))/g)[0];
const start = selection.index - matchedText.length - 1;
if (startIndex !== -1) {
setTimeout(() => {
this.quill.deleteText(start, matchedText.length);
this.quill.insertText(
start,
hrefText.slice(1, hrefText.length - 1),
"link",
hrefLink.slice(1, hrefLink.length - 1)
);
}, 0);
}
pattern: /\[([^\]]+)]\(([^)"]+)(?: "([^"]+)")?\)/g,
action: (text, selection, pattern, _lineStart, commit, trigger) => {
if (!commit) return;
const [matchedText, title, link] = pattern.exec(text);
const start =
selection.index - matchedText.length - (trigger === " " ? 1 : 0);
setTimeout(() => {
this.quill.deleteText(start, matchedText.length);
this.quill.insertText(start, title, "link", link);
}, 0);
},
},
];
@@ -270,11 +262,10 @@ class MarkdownShortcuts {
if (source !== "user") return;
for (let i = 0; i < delta.ops.length; i++) {
if (delta.ops[i].hasOwnProperty("insert")) {
if (delta.ops[i].insert === " ") {
this.onSpace(true);
} else if (delta.ops[i].insert === "\n") {
this.onEnter();
} else this.onSpace(false);
let char = delta.ops[i].insert;
if (char === " " || char === "\n") {
this.onSpace(char, true);
} else this.onSpace("", false);
} else if (delta.ops[i].hasOwnProperty("delete") && source === "user") {
this.onDelete();
}
@@ -290,7 +281,7 @@ class MarkdownShortcuts {
);
}
onSpace(commit) {
onSpace(trigger, commit) {
const selection = this.quill.getSelection();
if (!selection) return;
const [line, offset] = this.quill.getLine(selection.index);
@@ -309,10 +300,20 @@ class MarkdownShortcuts {
const matchedText = text.match(match.pattern);
if (matchedText) {
// We need to replace only matched text not the whole line
match.action(text, selection, match.pattern, lineStart, commit);
this.lastMatch = match;
console.log("Quill match made (" + match.name + ")");
return;
if (
match.action(
text,
selection,
match.pattern,
lineStart,
commit,
trigger
) !== false
) {
this.lastMatch = match;
console.log("Quill match made (" + match.name + ")");
return;
}
}
}
}
@@ -341,24 +342,6 @@ class MarkdownShortcuts {
const isEmpty = line?.children?.head?.text?.trim() === "";
return isEmpty;
}
onEnter() {
let selection = this.quill.getSelection();
if (!selection) return;
const [line, offset] = this.quill.getLine(selection.index);
const text = line.domNode.textContent + " ";
const lineStart = selection.index - offset;
selection.length = selection.index++;
if (this.isValid(text, line.domNode.tagName)) {
for (let match of this.matches) {
const matchedText = text.match(match.pattern);
if (matchedText) {
match.action(text, selection, match.pattern, lineStart, true);
return;
}
}
}
}
}
if (window.Quill) {