From 2df3609b0ab935020c2937ac478e1e1ee4d69fe3 Mon Sep 17 00:00:00 2001 From: Axel Wahlen Date: Fri, 22 Jun 2018 23:10:59 +0200 Subject: [PATCH 1/8] Allow custom inline style matcher --- src/__test__/plugin.test.js | 3 ++- src/index.js | 3 ++- src/modifiers/handleInlineStyle.js | 25 +++++++++++++++++++------ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/__test__/plugin.test.js b/src/__test__/plugin.test.js index 22f7dc9..072ca69 100755 --- a/src/__test__/plugin.test.js +++ b/src/__test__/plugin.test.js @@ -542,7 +542,8 @@ describe("draft-js-markdown-plugin", () => { expect(modifierSpy).toHaveBeenCalledWith( defaultInlineWhitelist, currentEditorState, - " " + " ", + undefined ); }); it("unstickys inline style", () => { diff --git a/src/index.js b/src/index.js index f824b5e..66c3bd8 100755 --- a/src/index.js +++ b/src/index.js @@ -131,7 +131,8 @@ function checkCharacterForState(config, editorState, character) { newEditorState = handleInlineStyle( config.features.inline, editorState, - character + character, + config.customInlineMatchers ); } return newEditorState; diff --git a/src/modifiers/handleInlineStyle.js b/src/modifiers/handleInlineStyle.js index f494425..bead9e6 100644 --- a/src/modifiers/handleInlineStyle.js +++ b/src/modifiers/handleInlineStyle.js @@ -4,12 +4,14 @@ import { inlineMatchers } from "../constants"; import insertText from "./insertText"; import { getCurrentLine as getLine } from "../utils"; -const handleChange = (editorState, line, whitelist) => { +const handleChange = (editorState, line, whitelist, customInlineMatchers) => { let newEditorState = editorState; - Object.keys(inlineMatchers) + const matchers = Object.assign({}, inlineMatchers, customInlineMatchers); + + Object.keys(matchers) .filter(matcher => whitelist.includes(matcher)) .some(k => { - inlineMatchers[k].some(re => { + matchers[k].some(re => { let matchArr; do { matchArr = re.exec(line); @@ -31,19 +33,30 @@ const handleChange = (editorState, line, whitelist) => { const handleInlineStyle = ( whitelist, editorStateWithoutCharacter, - character + character, + customInlineMatchers = {} ) => { const editorState = insertText(editorStateWithoutCharacter, character); let selection = editorState.getSelection(); let line = getLine(editorState); - let newEditorState = handleChange(editorState, line, whitelist); + let newEditorState = handleChange( + editorState, + line, + whitelist, + customInlineMatchers + ); let lastEditorState = editorState; // Recursively resolve markdown, e.g. _*text*_ should turn into both italic and bold while (newEditorState !== lastEditorState) { lastEditorState = newEditorState; line = getLine(newEditorState); - newEditorState = handleChange(newEditorState, line, whitelist); + newEditorState = handleChange( + newEditorState, + line, + whitelist, + customInlineMatchers + ); } if (newEditorState !== editorState) { From 60407dec8b66a105e7047a9d68d85057834d1fa6 Mon Sep 17 00:00:00 2001 From: Axel Wahlen Date: Mon, 25 Jun 2018 14:04:06 +0200 Subject: [PATCH 2/8] Add handling of whitespaces after the inline style change --- .../__test__/changeCurrentInlineStyle-test.js | 26 +++++++++++++++++ src/modifiers/changeCurrentInlineStyle.js | 29 ++++++++++++++++--- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/modifiers/__test__/changeCurrentInlineStyle-test.js b/src/modifiers/__test__/changeCurrentInlineStyle-test.js index 7f45d70..d93a39b 100644 --- a/src/modifiers/__test__/changeCurrentInlineStyle-test.js +++ b/src/modifiers/__test__/changeCurrentInlineStyle-test.js @@ -57,4 +57,30 @@ describe("changeCurrentInlineStyle", () => { ) ); }); + it("handles a style terminator properly", () => { + const text = "foo **bar** baz"; + const editorState = createEditorState(text, []); + const matchArr = ["**bar** ", "bar", " "]; + matchArr.index = 4; + matchArr.input = text; + const newEditorState = changeCurrentInlineStyle( + editorState, + matchArr, + "BOLD" + ); + expect(newEditorState).not.toEqual(editorState); + expect(Draft.convertToRaw(newEditorState.getCurrentContent())).toEqual( + rawContentState( + "foo bar baz", + [ + { + length: 3, + offset: 4, + style: "BOLD", + }, + ], + "BOLD" + ) + ); + }); }); diff --git a/src/modifiers/changeCurrentInlineStyle.js b/src/modifiers/changeCurrentInlineStyle.js index c611c76..46095fe 100644 --- a/src/modifiers/changeCurrentInlineStyle.js +++ b/src/modifiers/changeCurrentInlineStyle.js @@ -17,16 +17,26 @@ const changeCurrentInlineStyle = (editorState, matchArr, style) => { focusOffset, }); - const inlineStyles = []; - const markdownCharacterLength = (matchArr[0].length - matchArr[1].length) / 2; + // check if match contains a terminator group at the end + let matchTerminatorLength = 0; + if (matchArr.length == 3) { + matchTerminatorLength = matchArr[2].length; + } + + const markdownCharacterLength = + (matchArr[0].length - matchArr[1].length - matchTerminatorLength) / 2; + const inlineStyles = []; let newContentState = currentContent; // remove markdown delimiter at end newContentState = Modifier.removeRange( newContentState, wordSelection.merge({ - anchorOffset: wordSelection.getFocusOffset() - markdownCharacterLength, + anchorOffset: + wordSelection.getFocusOffset() - + markdownCharacterLength - + matchTerminatorLength, }) ); @@ -50,11 +60,22 @@ const changeCurrentInlineStyle = (editorState, matchArr, style) => { newContentState, wordSelection.merge({ anchorOffset: index, - focusOffset: focusOffset - markdownCharacterLength * 2, + focusOffset: + focusOffset - markdownCharacterLength * 2 - matchTerminatorLength, }), style ); + // Check if a terminator exists and re-add it after the styled text + if (matchTerminatorLength > 0) { + newContentState = Modifier.insertText( + newContentState, + afterSelection, + matchArr[2] + ); + afterSelection = newContentState.getSelectionAfter(); + } + const newEditorState = EditorState.push( editorState, newContentState, From 08510bed3402c0a86e6b342797d2bd98a6c085ea Mon Sep 17 00:00:00 2001 From: Maximilian Stoiber Date: Fri, 22 Jun 2018 15:55:28 +0200 Subject: [PATCH 3/8] Remove custom paste handling --- src/__test__/plugin.test.js | 109 ------------------------------------ src/index.js | 58 ------------------- 2 files changed, 167 deletions(-) diff --git a/src/__test__/plugin.test.js b/src/__test__/plugin.test.js index 072ca69..a8c6388 100755 --- a/src/__test__/plugin.test.js +++ b/src/__test__/plugin.test.js @@ -661,115 +661,6 @@ describe("draft-js-markdown-plugin", () => { expect(store.setEditorState).toHaveBeenCalledWith(newEditorState); }); }); - describe("handlePastedText", () => { - let pastedText; - let html; - beforeEach(() => { - pastedText = `_hello world_ - Hello`; - html = undefined; - subject = () => - plugin.handlePastedText( - pastedText, - html, - store.getEditorState(), - store - ); - }); - [ - "replaceText", - // TODO(@mxstbr): This broke when switching mocha->jest, fix it! - // 'insertEmptyBlock', - "handleBlockType", - "handleImage", - "handleLink", - "handleInlineStyle", - ].forEach(modifier => { - describe(modifier, () => { - beforeEach(() => { - createMarkdownPlugin.__Rewire__(modifier, modifierSpy); // eslint-disable-line no-underscore-dangle - }); - it("returns handled", () => { - expect(subject()).toBe("handled"); - expect(modifierSpy).toHaveBeenCalled(); - }); - }); - }); - describe("nothing in clipboard", () => { - beforeEach(() => { - pastedText = ""; - }); - it("returns not-handled", () => { - expect(subject()).toBe("not-handled"); - }); - }); - describe("pasted just text", () => { - beforeEach(() => { - pastedText = "hello"; - createMarkdownPlugin.__Rewire__("replaceText", modifierSpy); // eslint-disable-line no-underscore-dangle - }); - it("returns handled", () => { - expect(subject()).toBe("handled"); - expect(modifierSpy).toHaveBeenCalledWith( - currentEditorState, - "hello" - ); - }); - }); - describe("pasted just text with new line code", () => { - beforeEach(() => { - pastedText = "hello\nworld"; - const rawContentState = { - entityMap: {}, - blocks: [ - { - key: "item1", - text: "", - type: "unstyled", - depth: 0, - inlineStyleRanges: [], - entityRanges: [], - data: {}, - }, - ], - }; - const otherRawContentState = { - entityMap: {}, - blocks: [ - { - key: "item2", - text: "H1", - type: "header-one", - depth: 0, - inlineStyleRanges: [], - entityRanges: [], - data: {}, - }, - ], - }; - /* eslint-disable no-underscore-dangle */ - createMarkdownPlugin.__Rewire__("replaceText", () => - createEditorState(rawContentState, currentSelectionState) - ); - createMarkdownPlugin.__Rewire__("checkReturnForState", () => - createEditorState(otherRawContentState, currentSelectionState) - ); - /* eslint-enable no-underscore-dangle */ - }); - it("return handled", () => { - expect(subject()).toBe("handled"); - }); - }); - describe("passed `html` argument", () => { - beforeEach(() => { - pastedText = "# hello"; - html = "

hello

"; - }); - it("returns not-handled", () => { - expect(subject()).toBe("not-handled"); - }); - }); - }); }); }); }); diff --git a/src/index.js b/src/index.js index 66c3bd8..ba85f2e 100755 --- a/src/index.js +++ b/src/index.js @@ -416,64 +416,6 @@ const createMarkdownPlugin = (_config = {}) => { } } }, - handlePastedText(text, html, editorState, { setEditorState }) { - let newEditorState = editorState; - let buffer = []; - - if (html) { - return "not-handled"; - } - - // If we're in a code block don't add markdown to it - if (inCodeBlock(editorState)) { - setEditorState(insertText(editorState, text)); - return "handled"; - } - - for (let i = 0; i < text.length; i++) { - // eslint-disable-line no-plusplus - if (INLINE_STYLE_CHARACTERS.indexOf(text[i]) >= 0) { - newEditorState = replaceText( - newEditorState, - buffer.join("") + text[i] - ); - newEditorState = checkCharacterForState( - config, - newEditorState, - text[i] - ); - buffer = []; - } else if (text[i].charCodeAt(0) === 10) { - newEditorState = replaceText(newEditorState, buffer.join("")); - const tmpEditorState = checkReturnForState( - config, - newEditorState, - {} - ); - if (newEditorState === tmpEditorState) { - newEditorState = insertEmptyBlock(tmpEditorState); - } else { - newEditorState = tmpEditorState; - } - buffer = []; - } else if (i === text.length - 1) { - newEditorState = replaceText( - newEditorState, - buffer.join("") + text[i] - ); - buffer = []; - } else { - buffer.push(text[i]); - } - } - - if (editorState !== newEditorState) { - setEditorState(newEditorState); - return "handled"; - } - - return "not-handled"; - }, }; }; From 7f2a3ba66505322df997695c9ab9f7574a5f91f1 Mon Sep 17 00:00:00 2001 From: Andrew Fan Date: Mon, 25 Jun 2018 15:09:07 +0300 Subject: [PATCH 4/8] remove styles and links inside inline code block --- .../__test__/changeCurrentInlineStyle-test.js | 41 ++++++++--- .../__test__/handleInlineStyle-test.js | 69 ++++++++++++------- src/modifiers/changeCurrentInlineStyle.js | 22 ++++-- src/modifiers/removeInlineStyles.js | 17 +++++ 4 files changed, 110 insertions(+), 39 deletions(-) create mode 100644 src/modifiers/removeInlineStyles.js diff --git a/src/modifiers/__test__/changeCurrentInlineStyle-test.js b/src/modifiers/__test__/changeCurrentInlineStyle-test.js index d93a39b..8424e68 100644 --- a/src/modifiers/__test__/changeCurrentInlineStyle-test.js +++ b/src/modifiers/__test__/changeCurrentInlineStyle-test.js @@ -44,17 +44,36 @@ describe("changeCurrentInlineStyle", () => { ); expect(newEditorState).not.toEqual(editorState); expect(Draft.convertToRaw(newEditorState.getCurrentContent())).toEqual( - rawContentState( - "foo bar baz", - [ - { - length: 3, - offset: 4, - style: "CODE", - }, - ], - "CODE" - ) + rawContentState("foo bar baz", [ + { + length: 3, + offset: 4, + style: "CODE", + }, + ]) + ); + }); + it("removes inline styles when applying code style", () => { + const text = "`some bold text`"; + const editorState = createEditorState(text, [ + { + length: 4, + offset: 6, + style: "BOLD", + }, + ]); + const matchArr = ["`some bold text`", "some bold text"]; + matchArr.index = 0; + matchArr.input = text; + let newEditorState = changeCurrentInlineStyle( + editorState, + matchArr, + "CODE" + ); + expect(Draft.convertToRaw(newEditorState.getCurrentContent())).toEqual( + rawContentState("some bold text", [ + { length: 14, offset: 0, style: "CODE" }, + ]) ); }); it("handles a style terminator properly", () => { diff --git a/src/modifiers/__test__/handleInlineStyle-test.js b/src/modifiers/__test__/handleInlineStyle-test.js index a4f3bcd..a04fce9 100644 --- a/src/modifiers/__test__/handleInlineStyle-test.js +++ b/src/modifiers/__test__/handleInlineStyle-test.js @@ -47,14 +47,14 @@ describe("handleInlineStyle", () => { }); const testCases = { - "converts a mix of code, bold and italic and strikethrough in one go": { - character: "`", + "converts a mix of bold and italic and strikethrough in one go": { + character: "*", before: { entityMap: {}, blocks: [ { key: "item1", - text: "`h~el*lo _inline~_* style", + text: "*h~ello _inline~_ style", type: "unstyled", depth: 0, inlineStyleRanges: [], @@ -72,32 +72,55 @@ describe("handleInlineStyle", () => { type: "unstyled", depth: 0, inlineStyleRanges: [ - { - length: 12, - offset: 0, - style: "CODE", - }, - { - length: 11, - offset: 1, - style: "STRIKETHROUGH", - }, - { - length: 9, - offset: 3, - style: "BOLD", - }, - { - length: 6, - offset: 6, - style: "ITALIC", - }, + { length: 12, offset: 0, style: "BOLD" }, + { length: 11, offset: 1, style: "STRIKETHROUGH" }, + { length: 6, offset: 6, style: "ITALIC" }, ], entityRanges: [], data: {}, }, ], }, + selection: new SelectionState({ + anchorKey: "item1", + anchorOffset: 17, + focusKey: "item1", + focusOffset: 17, + isBackward: false, + hasFocus: true, + }), + }, + + "should not covert inside the code style": { + character: "`", + before: { + entityMap: {}, + blocks: [ + { + key: "item1", + text: "`h~el*lo _inline~_* style", + type: "unstyled", + depth: 0, + inlineStyleRanges: [], + entityRanges: [], + data: {}, + }, + ], + }, + after: { + entityMap: {}, + blocks: [ + { + key: "item1", + text: "h~el*lo _inline~_* style", + type: "unstyled", + depth: 0, + inlineStyleRanges: [{ length: 18, offset: 0, style: "CODE" }], + entityRanges: [], + data: {}, + }, + ], + }, selection: new SelectionState({ anchorKey: "item1", anchorOffset: 19, diff --git a/src/modifiers/changeCurrentInlineStyle.js b/src/modifiers/changeCurrentInlineStyle.js index 46095fe..b7030e4 100644 --- a/src/modifiers/changeCurrentInlineStyle.js +++ b/src/modifiers/changeCurrentInlineStyle.js @@ -1,5 +1,6 @@ import { OrderedSet } from "immutable"; import { EditorState, SelectionState, Modifier } from "draft-js"; +import removeInlineStyles from "./removeInlineStyles"; const changeCurrentInlineStyle = (editorState, matchArr, style) => { const currentContent = editorState.getCurrentContent(); @@ -8,8 +9,12 @@ const changeCurrentInlineStyle = (editorState, matchArr, style) => { const { index } = matchArr; const blockMap = currentContent.getBlockMap(); const block = blockMap.get(key); - const currentInlineStyle = block.getInlineStyleAt(index).merge(); - const newStyle = currentInlineStyle.merge([style]); + const currentInlineStyle = block.getInlineStyleAt(index); + // do not modify the text if it is inside code style + const hasCodeStyle = currentInlineStyle.find(style => style === "CODE"); + if (hasCodeStyle) { + return editorState; + } const focusOffset = index + matchArr[0].length; const wordSelection = SelectionState.createEmpty(key).merge({ @@ -17,6 +22,14 @@ const changeCurrentInlineStyle = (editorState, matchArr, style) => { focusOffset, }); + let newEditorState = editorState; + // remove all styles if applying code style + if (style === "CODE") { + newEditorState = removeInlineStyles(newEditorState, wordSelection); + } + + let newContentState = newEditorState.getCurrentContent(); + // check if match contains a terminator group at the end let matchTerminatorLength = 0; if (matchArr.length == 3) { @@ -26,8 +39,7 @@ const changeCurrentInlineStyle = (editorState, matchArr, style) => { const markdownCharacterLength = (matchArr[0].length - matchArr[1].length - matchTerminatorLength) / 2; - const inlineStyles = []; - let newContentState = currentContent; + newContentState = currentContent; // remove markdown delimiter at end newContentState = Modifier.removeRange( @@ -76,7 +88,7 @@ const changeCurrentInlineStyle = (editorState, matchArr, style) => { afterSelection = newContentState.getSelectionAfter(); } - const newEditorState = EditorState.push( + newEditorState = EditorState.push( editorState, newContentState, "change-inline-style" diff --git a/src/modifiers/removeInlineStyles.js b/src/modifiers/removeInlineStyles.js new file mode 100644 index 0000000..6d70038 --- /dev/null +++ b/src/modifiers/removeInlineStyles.js @@ -0,0 +1,17 @@ +import { EditorState, RichUtils, Modifier } from "draft-js"; + +export default (editorState, selection = editorState.getSelection()) => { + const styles = ["BOLD", "ITALIC", "STRIKETHROUGH", "CODE"]; + + let newEditorState = EditorState.push( + editorState, + styles.reduce( + (newContentState, style) => + Modifier.removeInlineStyle(newContentState, selection, style), + editorState.getCurrentContent() + ), + "change-inline-style" + ); + + return RichUtils.toggleLink(newEditorState, selection, null); +}; From 71880534af2882515e9399474f530bc5bb591d03 Mon Sep 17 00:00:00 2001 From: Maximilian Stoiber Date: Mon, 2 Jul 2018 14:11:01 +0200 Subject: [PATCH 5/8] 2.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7e9aa1e..a10945a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "draft-js-markdown-plugin", - "version": "2.1.1", + "version": "2.1.2", "description": "A DraftJS plugin for supporting Markdown syntax shortcuts, fork of draft-js-markdown-shortcuts-plugin", "main": "lib/index.js", "scripts": { From 1160fe2f9bbc2fe16ed1cc5f9a8c51fef9f31659 Mon Sep 17 00:00:00 2001 From: Maximilian Stoiber Date: Mon, 2 Jul 2018 14:16:47 +0200 Subject: [PATCH 6/8] 3.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a10945a..fe1c6d6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "draft-js-markdown-plugin", - "version": "2.1.2", + "version": "3.0.0", "description": "A DraftJS plugin for supporting Markdown syntax shortcuts, fork of draft-js-markdown-shortcuts-plugin", "main": "lib/index.js", "scripts": { From 06f0549cd308648690293f2f6723912de27c2d91 Mon Sep 17 00:00:00 2001 From: Axel Wahlen Date: Mon, 25 Jun 2018 14:04:06 +0200 Subject: [PATCH 7/8] Add handling of whitespaces after the inline style change --- src/modifiers/changeCurrentInlineStyle.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modifiers/changeCurrentInlineStyle.js b/src/modifiers/changeCurrentInlineStyle.js index b7030e4..61aed47 100644 --- a/src/modifiers/changeCurrentInlineStyle.js +++ b/src/modifiers/changeCurrentInlineStyle.js @@ -23,6 +23,7 @@ const changeCurrentInlineStyle = (editorState, matchArr, style) => { }); let newEditorState = editorState; + // remove all styles if applying code style if (style === "CODE") { newEditorState = removeInlineStyles(newEditorState, wordSelection); From ba9264780a8ee19a60e081c8fe482973871d22d4 Mon Sep 17 00:00:00 2001 From: Axel Wahlen Date: Thu, 5 Jul 2018 17:37:36 +0200 Subject: [PATCH 8/8] Fix the remove inline styles in code, break through complex merge --- src/modifiers/changeCurrentInlineStyle.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modifiers/changeCurrentInlineStyle.js b/src/modifiers/changeCurrentInlineStyle.js index 61aed47..e76215f 100644 --- a/src/modifiers/changeCurrentInlineStyle.js +++ b/src/modifiers/changeCurrentInlineStyle.js @@ -40,8 +40,6 @@ const changeCurrentInlineStyle = (editorState, matchArr, style) => { const markdownCharacterLength = (matchArr[0].length - matchArr[1].length - matchTerminatorLength) / 2; - newContentState = currentContent; - // remove markdown delimiter at end newContentState = Modifier.removeRange( newContentState,