diff --git a/package.json b/package.json
index 7e9aa1e..fe1c6d6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "draft-js-markdown-plugin",
- "version": "2.1.1",
+ "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": {
diff --git a/src/__test__/plugin.test.js b/src/__test__/plugin.test.js
index 22f7dc9..a8c6388 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", () => {
@@ -660,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 f824b5e..ba85f2e 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;
@@ -415,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";
- },
};
};
diff --git a/src/modifiers/__test__/changeCurrentInlineStyle-test.js b/src/modifiers/__test__/changeCurrentInlineStyle-test.js
index 7f45d70..8424e68 100644
--- a/src/modifiers/__test__/changeCurrentInlineStyle-test.js
+++ b/src/modifiers/__test__/changeCurrentInlineStyle-test.js
@@ -43,6 +43,51 @@ describe("changeCurrentInlineStyle", () => {
"CODE"
);
expect(newEditorState).not.toEqual(editorState);
+ expect(Draft.convertToRaw(newEditorState.getCurrentContent())).toEqual(
+ 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", () => {
+ 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",
@@ -50,10 +95,10 @@ describe("changeCurrentInlineStyle", () => {
{
length: 3,
offset: 4,
- style: "CODE",
+ style: "BOLD",
},
],
- "CODE"
+ "BOLD"
)
);
});
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 c611c76..e76215f 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,16 +22,32 @@ const changeCurrentInlineStyle = (editorState, matchArr, style) => {
focusOffset,
});
- const inlineStyles = [];
- const markdownCharacterLength = (matchArr[0].length - matchArr[1].length) / 2;
+ let newEditorState = editorState;
- let newContentState = currentContent;
+ // 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) {
+ matchTerminatorLength = matchArr[2].length;
+ }
+
+ const markdownCharacterLength =
+ (matchArr[0].length - matchArr[1].length - matchTerminatorLength) / 2;
// remove markdown delimiter at end
newContentState = Modifier.removeRange(
newContentState,
wordSelection.merge({
- anchorOffset: wordSelection.getFocusOffset() - markdownCharacterLength,
+ anchorOffset:
+ wordSelection.getFocusOffset() -
+ markdownCharacterLength -
+ matchTerminatorLength,
})
);
@@ -50,12 +71,23 @@ const changeCurrentInlineStyle = (editorState, matchArr, style) => {
newContentState,
wordSelection.merge({
anchorOffset: index,
- focusOffset: focusOffset - markdownCharacterLength * 2,
+ focusOffset:
+ focusOffset - markdownCharacterLength * 2 - matchTerminatorLength,
}),
style
);
- const newEditorState = EditorState.push(
+ // 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();
+ }
+
+ newEditorState = EditorState.push(
editorState,
newContentState,
"change-inline-style"
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) {
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);
+};