Skip to content

Commit b6c821a

Browse files
authored
Support paste (#17)
* Support Pasting markdown * Fix single line paste * Reset buffer using assignment * Move eslint rules to eslintrc * Revert eslint config * Added Test case for utils * Specify disable rule * Added test case for handlePastedText * remove redundant check
1 parent 3b67f7c commit b6c821a

File tree

4 files changed

+202
-36
lines changed

4 files changed

+202
-36
lines changed

src/__test__/plugin-test.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,51 @@ describe('draft-js-markdown-shortcuts-plugin', () => {
305305
});
306306
});
307307
});
308+
describe('handlePastedText', () => {
309+
let pastedText;
310+
let html;
311+
beforeEach(() => {
312+
pastedText = `_hello world_
313+
Hello`;
314+
subject = () => plugin.handlePastedText(pastedText, html, store);
315+
});
316+
[
317+
'addText',
318+
'addEmptyBlock',
319+
'handleBlockType',
320+
'handleImage',
321+
'handleLink',
322+
'handleInlineStyle'
323+
].forEach((modifier) => {
324+
describe(modifier, () => {
325+
beforeEach(() => {
326+
createMarkdownShortcutsPlugin.__Rewire__(modifier, modifierSpy); // eslint-disable-line no-underscore-dangle
327+
});
328+
it('returns handled', () => {
329+
expect(subject()).to.equal('handled');
330+
expect(modifierSpy).to.have.been.called();
331+
});
332+
});
333+
});
334+
describe('nothing in clipboard', () => {
335+
beforeEach(() => {
336+
pastedText = '';
337+
});
338+
it('returns not-handled', () => {
339+
expect(subject()).to.equal('not-handled');
340+
});
341+
});
342+
describe('pasted just text', () => {
343+
beforeEach(() => {
344+
pastedText = 'hello';
345+
createMarkdownShortcutsPlugin.__Rewire__('addText', modifierSpy); // eslint-disable-line no-underscore-dangle
346+
});
347+
it('returns handled', () => {
348+
expect(subject()).to.equal('handled');
349+
expect(modifierSpy).to.have.been.calledWith(currentEditorState, 'hello');
350+
});
351+
});
352+
});
308353
});
309354
});
310355
});

src/__test__/utils-test.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { expect } from 'chai';
2+
import Draft, { EditorState } from 'draft-js';
3+
import { addText, addEmptyBlock } from '../utils';
4+
5+
describe('utils test', () => {
6+
it('is loaded', () => {
7+
expect(addText).to.be.a('function');
8+
expect(addEmptyBlock).to.be.a('function');
9+
});
10+
11+
const newRawContentState = {
12+
entityMap: {},
13+
blocks: [{
14+
key: 'item1',
15+
text: 'altered!!',
16+
type: 'unstyled',
17+
depth: 0,
18+
inlineStyleRanges: [],
19+
entityRanges: [],
20+
data: {}
21+
}]
22+
};
23+
it('should add empty block', () => {
24+
let newEditorState = EditorState.createWithContent(Draft.convertFromRaw(newRawContentState));
25+
const initialBlockSize = newEditorState.getCurrentContent().getBlockMap().size;
26+
const randomBlockSize = Math.floor((Math.random() * 50) + 1); // random number bettween 1 to 50
27+
for (let i = 0; i < randomBlockSize; i++) { // eslint-disable-line no-plusplus
28+
newEditorState = addEmptyBlock(newEditorState);
29+
}
30+
const finalBlockSize = newEditorState.getCurrentContent().getBlockMap().size;
31+
expect(finalBlockSize - initialBlockSize).to.equal(randomBlockSize);
32+
33+
const lastBlock = newEditorState.getCurrentContent().getLastBlock();
34+
expect(lastBlock.getType()).to.equal('unstyled');
35+
expect(lastBlock.getText()).to.have.lengthOf(0);
36+
});
37+
38+
it('should addText', () => {
39+
let newEditorState = EditorState.createWithContent(Draft.convertFromRaw(newRawContentState));
40+
const randomText = Date.now().toString(32);
41+
newEditorState = addEmptyBlock(newEditorState);
42+
newEditorState = addText(newEditorState, randomText);
43+
const currentContent = newEditorState.getCurrentContent();
44+
expect(currentContent.hasText()).to.equal(true);
45+
const lastBlock = currentContent.getLastBlock();
46+
expect(lastBlock.getText()).to.equal(randomText);
47+
});
48+
});

src/index.js

Lines changed: 77 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,48 @@ import leaveList from './modifiers/leaveList';
1616
import insertText from './modifiers/insertText';
1717
import createLinkDecorator from './decorators/link';
1818
import createImageDecorator from './decorators/image';
19+
import { addText, addEmptyBlock } from './utils';
1920

21+
const INLINE_STYLE_CHARACTERS = [' ', '*', '_'];
22+
23+
function checkCharacterForState(editorState, character) {
24+
let newEditorState = handleBlockType(editorState, character);
25+
if (editorState === newEditorState) {
26+
newEditorState = handleImage(editorState, character);
27+
}
28+
if (editorState === newEditorState) {
29+
newEditorState = handleLink(editorState, character);
30+
}
31+
if (editorState === newEditorState) {
32+
newEditorState = handleInlineStyle(editorState, character);
33+
}
34+
return newEditorState;
35+
}
36+
37+
function checkReturnForState(editorState, ev) {
38+
let newEditorState = editorState;
39+
const contentState = editorState.getCurrentContent();
40+
const selection = editorState.getSelection();
41+
const key = selection.getStartKey();
42+
const currentBlock = contentState.getBlockForKey(key);
43+
const type = currentBlock.getType();
44+
const text = currentBlock.getText();
45+
if (/-list-item$/.test(type) && text === '') {
46+
newEditorState = leaveList(editorState);
47+
}
48+
if (newEditorState === editorState &&
49+
(ev.ctrlKey || ev.shiftKey || ev.metaKey || ev.altKey || /^header-/.test(type))) {
50+
newEditorState = insertEmptyBlock(editorState);
51+
}
52+
if (newEditorState === editorState && type === 'code-block') {
53+
newEditorState = insertText(editorState, '\n');
54+
}
55+
if (newEditorState === editorState) {
56+
newEditorState = handleNewCodeBlock(editorState);
57+
}
58+
59+
return newEditorState;
60+
}
2061

2162
const createMarkdownShortcutsPlugin = (config = {}) => {
2263
const store = {};
@@ -36,34 +77,6 @@ const createMarkdownShortcutsPlugin = (config = {}) => {
3677
store.setEditorState = setEditorState;
3778
store.getEditorState = getEditorState;
3879
},
39-
handleReturn(ev, { setEditorState, getEditorState }) {
40-
const editorState = getEditorState();
41-
let newEditorState = editorState;
42-
const contentState = editorState.getCurrentContent();
43-
const selection = editorState.getSelection();
44-
const key = selection.getStartKey();
45-
const currentBlock = contentState.getBlockForKey(key);
46-
const type = currentBlock.getType();
47-
const text = currentBlock.getText();
48-
if (/-list-item$/.test(type) && text === '') {
49-
newEditorState = leaveList(editorState);
50-
}
51-
if (newEditorState === editorState &&
52-
(ev.ctrlKey || ev.shiftKey || ev.metaKey || ev.altKey || /^header-/.test(type))) {
53-
newEditorState = insertEmptyBlock(editorState);
54-
}
55-
if (newEditorState === editorState && type === 'code-block') {
56-
newEditorState = insertText(editorState, '\n');
57-
}
58-
if (newEditorState === editorState) {
59-
newEditorState = handleNewCodeBlock(editorState);
60-
}
61-
if (editorState !== newEditorState) {
62-
setEditorState(newEditorState);
63-
return 'handled';
64-
}
65-
return 'not-handled';
66-
},
6780
blockStyleFn(block) {
6881
switch (block.getType()) {
6982
case CHECKABLE_LIST_ITEM:
@@ -100,21 +113,49 @@ const createMarkdownShortcutsPlugin = (config = {}) => {
100113
}
101114
return 'not-handled';
102115
},
116+
handleReturn(ev, { setEditorState, getEditorState }) {
117+
const editorState = getEditorState();
118+
const newEditorState = checkReturnForState(editorState, ev);
119+
if (editorState !== newEditorState) {
120+
setEditorState(newEditorState);
121+
return 'handled';
122+
}
123+
return 'not-handled';
124+
},
103125
handleBeforeInput(character, { getEditorState, setEditorState }) {
104126
if (character !== ' ') {
105127
return 'not-handled';
106128
}
107129
const editorState = getEditorState();
108-
let newEditorState = handleBlockType(editorState, character);
109-
if (editorState === newEditorState) {
110-
newEditorState = handleImage(editorState, character);
111-
}
112-
if (editorState === newEditorState) {
113-
newEditorState = handleLink(editorState, character);
130+
const newEditorState = checkCharacterForState(editorState, character);
131+
if (editorState !== newEditorState) {
132+
setEditorState(newEditorState);
133+
return 'handled';
114134
}
115-
if (editorState === newEditorState) {
116-
newEditorState = handleInlineStyle(editorState, character);
135+
return 'not-handled';
136+
},
137+
handlePastedText(text, html, { getEditorState, setEditorState }) {
138+
const editorState = getEditorState();
139+
let newEditorState = editorState;
140+
let buffer = [];
141+
for (let i = 0; i < text.length; i++) { // eslint-disable-line no-plusplus
142+
if (INLINE_STYLE_CHARACTERS.indexOf(text[i]) >= 0) {
143+
newEditorState = addText(newEditorState, buffer.join('') + text[i]);
144+
newEditorState = checkCharacterForState(newEditorState, text[i]);
145+
buffer = [];
146+
} else if (text[i].charCodeAt(0) === 10) {
147+
newEditorState = addText(newEditorState, buffer.join(''));
148+
newEditorState = addEmptyBlock(newEditorState);
149+
newEditorState = checkReturnForState(newEditorState, {});
150+
buffer = [];
151+
} else if (i === text.length - 1) {
152+
newEditorState = addText(newEditorState, buffer.join('') + text[i]);
153+
buffer = [];
154+
} else {
155+
buffer.push(text[i]);
156+
}
117157
}
158+
118159
if (editorState !== newEditorState) {
119160
setEditorState(newEditorState);
120161
return 'handled';

src/utils.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { genKey, ContentBlock, Modifier, EditorState } from 'draft-js';
2+
import { List } from 'immutable';
3+
4+
function getEmptyContentBlock() {
5+
return new ContentBlock({
6+
key: genKey(),
7+
text: '',
8+
characterList: List(),
9+
});
10+
}
11+
12+
export function addText(editorState, bufferText) {
13+
const contentState = Modifier.insertText(editorState.getCurrentContent(), editorState.getSelection(), bufferText);
14+
return EditorState.push(editorState, contentState, 'insert-characters');
15+
}
16+
17+
export function addEmptyBlock(editorState) {
18+
let contentState = editorState.getCurrentContent();
19+
const emptyBlock = getEmptyContentBlock();
20+
const blockMap = contentState.getBlockMap();
21+
const selectionState = editorState.getSelection();
22+
contentState = contentState.merge({
23+
blockMap: blockMap.set(emptyBlock.getKey(), emptyBlock),
24+
selectionAfter: selectionState.merge({
25+
anchorKey: emptyBlock.getKey(),
26+
focusKey: emptyBlock.getKey(),
27+
anchorOffset: 0,
28+
focusOffset: 0,
29+
}),
30+
});
31+
return EditorState.push(editorState, contentState, 'insert-characters');
32+
}

0 commit comments

Comments
 (0)