diff --git a/packages/ui-markdown-editor/src/plugins/withText.js b/packages/ui-markdown-editor/src/plugins/withText.js index dbf850e..8dee44b 100644 --- a/packages/ui-markdown-editor/src/plugins/withText.js +++ b/packages/ui-markdown-editor/src/plugins/withText.js @@ -1,6 +1,7 @@ import { Node } from 'slate'; import { isBlockHeading } from '../utilities/toolbarHelpers'; import { matchCases } from "utilities/matchCases"; +import { SPACE_BAR } from "utilities/constants"; export const withText = (editor) => { const { insertText } = editor; @@ -10,8 +11,16 @@ export const withText = (editor) => { if(isBlockHeading(editor)){ return; } - const currentLine = currentNode.text; - matchCases(editor, currentLine); + + const currentLine = currentNode.text + + onkeyup = (ev) => { + switch (ev.key) { + case SPACE_BAR: + matchCases(editor, currentLine); + break; + } + } } return editor; } \ No newline at end of file diff --git a/packages/ui-markdown-editor/src/utilities/constants.js b/packages/ui-markdown-editor/src/utilities/constants.js index e1fa4a3..015dede 100644 --- a/packages/ui-markdown-editor/src/utilities/constants.js +++ b/packages/ui-markdown-editor/src/utilities/constants.js @@ -4,6 +4,8 @@ const MISC_CONSTANTS = { DROPDOWN_NORMAL: 'Normal', }; +export const SPACE_BAR = " "; + export const BUTTON_COLORS = { BACKGROUND_INACTIVE: '#FFFFFF', BACKGROUND_ACTIVE: '#F0F0F0', diff --git a/packages/ui-markdown-editor/src/utilities/matchCases.js b/packages/ui-markdown-editor/src/utilities/matchCases.js index 76bd3b6..cd85b7b 100644 --- a/packages/ui-markdown-editor/src/utilities/matchCases.js +++ b/packages/ui-markdown-editor/src/utilities/matchCases.js @@ -4,7 +4,11 @@ import { insertThematicBreak } from "./toolbarHelpers"; export const matchCases = (editor, currentLine) => { - const matchHeadings = (editor, currentLine) => { + const offsetBeforeSpace = editor.selection.anchor.offset - 2; + const lastChar = currentLine.charAt(offsetBeforeSpace); + const prevTextFromSpace = currentLine.substr(0, offsetBeforeSpace + 1); + + const matchHeadings = () => { const headingMatchCase = currentLine.match(/(^\s*)#{1,6}\s/m); if(!headingMatchCase) return; @@ -20,8 +24,8 @@ export const matchCases = (editor, currentLine) => { return; } - const matchPageBreak = (editor, currentLine)=>{ - const pageBreakMatchCase = currentLine.match(/(^\s*)([*-])(?:[\t ]*\2){2,}/m); + const matchPageBreak = () => { + const pageBreakMatchCase = currentLine.match(/(^\s*)([-])(?:[\t ]*\2){2,}/m); if(!pageBreakMatchCase) return Editor.deleteBackward(editor, { unit: 'word' }); @@ -30,6 +34,87 @@ export const matchCases = (editor, currentLine) => { return; } - matchHeadings(editor, currentLine); - matchPageBreak(editor, currentLine); -} + /** + * + * @param {string} textToInsert The text that we want to format + * @param {Object} textFormats This is the format style of the text (bold, italic, code) we want to apply or remove as the key in an object + * and a boolean as the value. i.e. `{ bold: true }` + * @param {Integer} markdownCharacters The number of markdown characters that the user has to type to trigger WYSIWYG + */ + const insertFormattedInlineText = (textToInsert, textFormats, markdownCharacters) => { + const currentRange = { + anchor: editor.selection.anchor, + focus: { + path: editor.selection.focus.path, + offset: editor.selection.focus.offset - (textToInsert.length + 1 + markdownCharacters) + } + } + + Transforms.insertText(editor, " "); + + Transforms.insertNodes( + editor, { + text: textToInsert, + + ...textFormats + + }, { + at: currentRange + } + ) + } + + const matchCodeInline = () => { + const codeInlineMatchCase = prevTextFromSpace.match(/\s?(`|``)((?!\1).)+?\1$/m); + if (!lastChar === "`") return; + if (!codeInlineMatchCase) return; + + const codeText = codeInlineMatchCase[0].trim().replace(new RegExp(codeInlineMatchCase[1], "g"), ""); + + insertFormattedInlineText(codeText, { + code: true + }, 2); + + return; + } + + const matchBoldAndItalic = () => { + + let boldAndItalicMatchCase; + let boldMatchCase; + let italicMatchCase; + + if (lastChar === "*" || lastChar === "_") { + if ((boldAndItalicMatchCase = prevTextFromSpace.match(/\s?(\*\*\*|___)((?!\1).)+?\1$/m))) { + // ***[bold + italic]***, ___[bold + italic]___ + const reg = boldAndItalicMatchCase[1] === "***" ? /\*\*\*/ : boldAndItalicMatchCase[1]; + const boldAndItalicText = boldAndItalicMatchCase[0].trim().replace(new RegExp(reg, "g"), ""); + insertFormattedInlineText(boldAndItalicText, { + bold: true, + italic: true + }, 6) + } else if ((boldMatchCase = prevTextFromSpace.match(/\s?(\*\*|__)((?!\1).)+?\1$/m))) { + // **bold**, __bold__ + const reg = boldMatchCase[1] === "**" ? /\*\*/ : boldMatchCase[1]; + const boldText = boldMatchCase[0].replace(new RegExp(reg, "g"), ""); + insertFormattedInlineText(boldText, { + bold: true + }, 4) + } else if ((italicMatchCase = prevTextFromSpace.match(/\s?(\*|_)((?!\1).)+?\1$/m))) { + // *italic*, _italic_ + const reg = italicMatchCase[1] === "*" ? /\*/ : italicMatchCase[1]; + const italicText = italicMatchCase[0].replace(new RegExp(reg, "g"), ""); + insertFormattedInlineText(italicText, { + italic: true + }, 2) + } + + } + } + + matchHeadings(); + matchPageBreak(); + matchBoldAndItalic() + matchCodeInline(); + return; +} \ No newline at end of file