Comparing version 0.4.1 to 0.5.0
@@ -7,9 +7,25 @@ Changelog | ||
## [[v0.4.1]](https://github.com/springload/draftail/releases/tag/v0.4.1) - 2017-01-18 | ||
## [[v0.5.0]](https://github.com/springload/draftail/releases/tag/v0.5.0) | ||
### Added | ||
- Implement list depth normalisation on copy/paste. | ||
- Add title attributes on buttons to display keyboard shortcuts. Fix #37. | ||
- Override default code-block element. Fix #41. | ||
### Changed | ||
- Update project to use draft-js@0.10 API | ||
- Move draftjs-utils `peerDependency` to be a dependency. | ||
- Move immutable `peerDependency` to be a dependency. | ||
- Copy/paste of rich text is now configurable via the `stripPastedStyles` option. | ||
- Copy/paste of rich text is now disabled by default. This will be enabled by default once it is better supported. | ||
## [[v0.4.1]](https://github.com/springload/draftail/releases/tag/v0.4.1) | ||
### Fixed | ||
- Fix image block not unlocking editor on cancel | ||
- Fix image block not unlocking editor on cancel. | ||
## [[v0.4.0]](https://github.com/springload/draftail/releases/tag/v0.4.0) - 2017-01-18 | ||
## [[v0.4.0]](https://github.com/springload/draftail/releases/tag/v0.4.0) | ||
@@ -50,3 +66,3 @@ ### Added | ||
## [[v0.3.3]](https://github.com/springload/draftail/releases/tag/v0.3.3) - 2016-12-13 | ||
## [[v0.3.3]](https://github.com/springload/draftail/releases/tag/v0.3.3) | ||
@@ -57,3 +73,3 @@ ### Added | ||
## [[v0.3.2]](https://github.com/springload/draftail/releases/tag/v0.3.2) - 2016-11-29 | ||
## [[v0.3.2]](https://github.com/springload/draftail/releases/tag/v0.3.2) | ||
@@ -65,3 +81,3 @@ ### Added | ||
## [[v0.3.1]](https://github.com/springload/draftail/releases/tag/v0.3.1) - 2016-11-28 | ||
## [[v0.3.1]](https://github.com/springload/draftail/releases/tag/v0.3.1) | ||
@@ -72,3 +88,3 @@ ### Fixed | ||
## [[v0.3.0]](https://github.com/springload/draftail/releases/tag/v0.3.0) - 2016-11-28 | ||
## [[v0.3.0]](https://github.com/springload/draftail/releases/tag/v0.3.0) | ||
@@ -91,3 +107,3 @@ > This release contains __breaking changes__. | ||
## [[v0.2.0]](https://github.com/springload/draftail/releases/tag/v0.2.0) - 2016-11-14 | ||
## [[v0.2.0]](https://github.com/springload/draftail/releases/tag/v0.2.0) | ||
@@ -99,3 +115,3 @@ ### Changed | ||
## [[v0.1.0]](https://github.com/springload/draftail/releases/tag/v0.1.0) - 2016-11-11 | ||
## [[v0.1.0]](https://github.com/springload/draftail/releases/tag/v0.1.0) | ||
@@ -106,4 +122,6 @@ First usable release! | ||
## [[x.y.z]](https://github.com/springload/draftail/releases/tag/x.y.z) - YYYY-MM-DD (Template: http://keepachangelog.com/) | ||
Template from http://keepachangelog.com/ | ||
## [[vx.y.z]](https://github.com/springload/draftail/releases/tag/x.y.z) | ||
### Added | ||
@@ -110,0 +128,0 @@ |
@@ -13,8 +13,14 @@ 'use strict'; | ||
var hasCommandModifier = _draftJs.KeyBindingUtil.hasCommandModifier; | ||
var hasCommandModifier = _draftJs.KeyBindingUtil.hasCommandModifier, | ||
isOptionKeyCommand = _draftJs.KeyBindingUtil.isOptionKeyCommand; | ||
// TODO Get rid of this as soon as possible. | ||
// Hack relying on the internals of Draft.js. | ||
// See https://github.com/facebook/draft-js/pull/869 | ||
var isMacOS = isOptionKeyCommand({ altKey: 'test' }) === 'test'; | ||
/** | ||
* Behavioral methods for the editor, generated from its configuration. | ||
*/ | ||
exports.default = { | ||
@@ -29,2 +35,13 @@ /** | ||
// Override default element for code block. | ||
// Fix https://github.com/facebook/draft-js/issues/406. | ||
if (blockTypes.some(function (block) { | ||
return block.type === _constants.BLOCK_TYPE.CODE; | ||
})) { | ||
renderMap[_constants.BLOCK_TYPE.CODE] = { | ||
element: 'code', | ||
wrapper: _draftJs.DefaultDraftBlockRenderMap.get(_constants.BLOCK_TYPE.CODE).wrapper | ||
}; | ||
} | ||
blockTypes.filter(function (block) { | ||
@@ -148,2 +165,8 @@ return block.element; | ||
}, | ||
getKeyboardShortcut: function getKeyboardShortcut(type) { | ||
var shortcut = _constants.KEYBOARD_SHORTCUTS[type]; | ||
var system = isMacOS ? 'macOS' : 'other'; | ||
return shortcut ? shortcut[system] : 'No keyboard shortcut'; | ||
}, | ||
getBeforeInputBlockType: function getBeforeInputBlockType(input) { | ||
@@ -150,0 +173,0 @@ var blockTypes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; |
@@ -28,2 +28,12 @@ 'use strict'; | ||
}); | ||
describe('code block element', function () { | ||
it('default is "code"', function () { | ||
expect(_behavior2.default.getBlockRenderMap([{ type: _constants.BLOCK_TYPE.CODE }]).get(_constants.BLOCK_TYPE.CODE).element).toEqual('code'); | ||
}); | ||
it('can be overriden', function () { | ||
expect(_behavior2.default.getBlockRenderMap([{ type: _constants.BLOCK_TYPE.CODE, element: 'span' }]).get(_constants.BLOCK_TYPE.CODE).element).toEqual('span'); | ||
}); | ||
}); | ||
}); | ||
@@ -173,2 +183,12 @@ | ||
describe('#getKeyboardShortcut', function () { | ||
it('exists', function () { | ||
expect(_behavior2.default.getKeyboardShortcut).toBeDefined(); | ||
}); | ||
it('header five shortcut', function () { | ||
expect(_behavior2.default.getKeyboardShortcut(_constants.BLOCK_TYPE.HEADER_FIVE)).toBe('ctrl + alt + 5'); | ||
}); | ||
}); | ||
describe('#getBeforeInputBlockType', function () { | ||
@@ -175,0 +195,0 @@ it('exists', function () { |
@@ -43,2 +43,4 @@ 'use strict'; | ||
var BR_TYPE = exports.BR_TYPE = 'BR'; | ||
// Originally from https://github.com/facebook/draft-js/blob/master/src/component/utils/getDefaultKeyBinding.js. | ||
@@ -62,2 +64,19 @@ var KEY_CODES = exports.KEY_CODES = { | ||
var KEYBOARD_SHORTCUTS = exports.KEYBOARD_SHORTCUTS = {}; | ||
KEYBOARD_SHORTCUTS[BLOCK_TYPE.UNSTYLED] = { other: 'ctrl + alt + 0', macOS: '⌘ + option + 0' }; | ||
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_ONE] = { other: 'ctrl + alt + 1', macOS: '⌘ + option + 1' }; | ||
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_TWO] = { other: 'ctrl + alt + 2', macOS: '⌘ + option + 2' }; | ||
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_THREE] = { other: 'ctrl + alt + 3', macOS: '⌘ + option + 3' }; | ||
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_FOUR] = { other: 'ctrl + alt + 4', macOS: '⌘ + option + 4' }; | ||
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_FIVE] = { other: 'ctrl + alt + 5', macOS: '⌘ + option + 5' }; | ||
KEYBOARD_SHORTCUTS[BLOCK_TYPE.HEADER_SIX] = { other: 'ctrl + alt + 6', macOS: '⌘ + option + 6' }; | ||
KEYBOARD_SHORTCUTS[BLOCK_TYPE.UNORDERED_LIST_ITEM] = { other: 'ctrl + shift + 8', macOS: '⌘ + shift + 8' }; | ||
KEYBOARD_SHORTCUTS[BLOCK_TYPE.ORDERED_LIST_ITEM] = { other: 'ctrl + shift + 7', macOS: '⌘ + shift + 7' }; | ||
KEYBOARD_SHORTCUTS[ENTITY_TYPE.LINK] = { other: 'ctrl + K', macOS: '⌘ + K' }; | ||
KEYBOARD_SHORTCUTS[BR_TYPE] = { other: 'shift + enter', macOS: 'shift + enter' }; | ||
KEYBOARD_SHORTCUTS[INLINE_STYLE.BOLD] = { other: 'ctrl + B', macOS: '⌘ + B' }; | ||
KEYBOARD_SHORTCUTS[INLINE_STYLE.ITALIC] = { other: 'ctrl + I', macOS: '⌘ + I' }; | ||
KEYBOARD_SHORTCUTS[INLINE_STYLE.UNDERLINE] = { other: 'ctrl + U', macOS: '⌘ + U' }; | ||
KEYBOARD_SHORTCUTS[INLINE_STYLE.STRIKETHROUGH] = { other: 'alt + shift + 5', macOS: 'option + shift + 5' }; | ||
var NBSP = exports.NBSP = '\xA0'; | ||
@@ -64,0 +83,0 @@ |
@@ -24,2 +24,8 @@ 'use strict'; | ||
describe('#BR_TYPE', function () { | ||
it('exists', function () { | ||
expect(_constants.BR_TYPE).toBeDefined(); | ||
}); | ||
}); | ||
describe('#KEY_CODES', function () { | ||
@@ -31,2 +37,8 @@ it('exists', function () { | ||
describe('#KEYBOARD_SHORTCUTS', function () { | ||
it('exists', function () { | ||
expect(_constants.KEYBOARD_SHORTCUTS).toBeDefined(); | ||
}); | ||
}); | ||
describe('#NBSP', function () { | ||
@@ -33,0 +45,0 @@ it('exists', function () { |
@@ -11,24 +11,21 @@ 'use strict'; | ||
var stubs = { | ||
emptyContent: {}, | ||
realContent: { | ||
entityMap: {}, | ||
blocks: [{ | ||
key: '1dcqo', | ||
text: 'Hello, World!', | ||
type: 'unstyled', | ||
depth: 0, | ||
inlineStyleRanges: [], | ||
entityRanges: [], | ||
data: {} | ||
}, { | ||
key: 'dmtba', | ||
text: 'This is a title', | ||
type: 'header-two', | ||
depth: 0, | ||
inlineStyleRanges: [], | ||
entityRanges: [], | ||
data: {} | ||
}] | ||
} | ||
var stubContent = { | ||
entityMap: {}, | ||
blocks: [{ | ||
key: '1dcqo', | ||
text: 'Hello, World!', | ||
type: 'unstyled', | ||
depth: 0, | ||
inlineStyleRanges: [], | ||
entityRanges: [], | ||
data: {} | ||
}, { | ||
key: 'dmtba', | ||
text: 'This is a title', | ||
type: 'header-two', | ||
depth: 0, | ||
inlineStyleRanges: [], | ||
entityRanges: [], | ||
data: {} | ||
}] | ||
}; | ||
@@ -43,3 +40,3 @@ | ||
it('creates empty state from empty content', function () { | ||
var state = _conversion2.default.createEditorState(stubs.emptyContent); | ||
var state = _conversion2.default.createEditorState({}); | ||
var result = (0, _draftJs.convertToRaw)(state.getCurrentContent()); | ||
@@ -51,3 +48,3 @@ expect(result.blocks.length).toEqual(1); | ||
it('creates state from real content', function () { | ||
var state = _conversion2.default.createEditorState(stubs.realContent); | ||
var state = _conversion2.default.createEditorState(stubContent); | ||
var result = (0, _draftJs.convertToRaw)(state.getCurrentContent()); | ||
@@ -64,12 +61,19 @@ expect(result.blocks.length).toEqual(2); | ||
it('keeps real content', function () { | ||
var state = _conversion2.default.createEditorState(stubContent); | ||
expect(_conversion2.default.serialiseEditorState(state)).toEqual(stubContent); | ||
}); | ||
it('discards empty content', function () { | ||
var state = _conversion2.default.createEditorState(stubs.emptyContent); | ||
expect(_conversion2.default.serialiseEditorState(state)).toEqual(stubs.emptyContent); | ||
var state = _conversion2.default.createEditorState({}); | ||
expect(_conversion2.default.serialiseEditorState(state)).toEqual({}); | ||
}); | ||
it('keeps real content', function () { | ||
var state = _conversion2.default.createEditorState(stubs.realContent); | ||
expect(_conversion2.default.serialiseEditorState(state)).toEqual(stubs.realContent); | ||
it('discards content with only empty text', function () { | ||
var contentBlocks = (0, _draftJs.convertFromHTML)('<h1> </h1>'); | ||
var contentState = _draftJs.ContentState.createFromBlockArray(contentBlocks); | ||
var editorState = _draftJs.EditorState.createWithContent(contentState); | ||
expect(_conversion2.default.serialiseEditorState(editorState)).toEqual({}); | ||
}); | ||
}); | ||
}); |
@@ -31,2 +31,4 @@ 'use strict'; | ||
getAllBlocks: _draftjsUtils2.default.getAllBlocks.bind(_draftjsUtils2.default), | ||
/** | ||
@@ -75,10 +77,10 @@ * Creates a selection for the entirety of an entity that can be partially selected. | ||
// TODO Document. | ||
// TODO Refactor to be a shortcut rather than use `Modifier`. | ||
createEntity: function createEntity(editorState, entityType, entityData, entityText) { | ||
var entityMutability = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 'IMMUTABLE'; | ||
var entityKey = _draftJs.Entity.create(entityType, entityMutability, entityData); | ||
var contentState = editorState.getCurrentContent(); | ||
var selection = editorState.getSelection(); | ||
var contentStateWithEntity = contentState.createEntity(entityType, entityMutability, entityData); | ||
var entityKey = contentStateWithEntity.getLastCreatedEntityKey(); | ||
@@ -88,5 +90,5 @@ var nextContentState = void 0; | ||
if (selection.isCollapsed()) { | ||
nextContentState = _draftJs.Modifier.insertText(contentState, editorState.getSelection(), entityText, null, entityKey); | ||
nextContentState = _draftJs.Modifier.insertText(contentState, selection, entityText, null, entityKey); | ||
} else { | ||
nextContentState = _draftJs.Modifier.replaceText(contentState, editorState.getSelection(), entityText, null, entityKey); | ||
nextContentState = _draftJs.Modifier.replaceText(contentState, selection, entityText, null, entityKey); | ||
} | ||
@@ -98,4 +100,13 @@ | ||
}, | ||
/** | ||
* Inserts a horizontal rule in the place of the current selection. | ||
* Returns updated EditorState. | ||
* Inspired by DraftUtils.addLineBreakRemovingSelection. | ||
*/ | ||
addHorizontalRuleRemovingSelection: function addHorizontalRuleRemovingSelection(editorState) { | ||
var entityKey = _draftJs.Entity.create(_constants.ENTITY_TYPE.HORIZONTAL_RULE, 'IMMUTABLE', {}); | ||
var contentState = editorState.getCurrentContent(); | ||
var contentStateWithEntity = contentState.createEntity(_constants.ENTITY_TYPE.HORIZONTAL_RULE, 'IMMUTABLE', {}); | ||
var entityKey = contentStateWithEntity.getLastCreatedEntityKey(); | ||
var nextState = _draftJs.AtomicBlockUtils.insertAtomicBlock(editorState, entityKey, ' '); | ||
@@ -105,2 +116,8 @@ | ||
}, | ||
/** | ||
* Changes a block type to be `newType`. Other attributes of the block | ||
* can also be changed at the same time with `overrides`. | ||
*/ | ||
resetBlockWithType: function resetBlockWithType(editorState, newType) { | ||
@@ -126,3 +143,26 @@ var overrides = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; | ||
return _draftJs.EditorState.push(editorState, newContentState, 'change-block-type'); | ||
}, | ||
/** | ||
* Reset the depth of all the content to be at most maxListNesting. | ||
* Meant to be used after a paste of non-constrained list items. | ||
*/ | ||
normaliseBlockDepth: function normaliseBlockDepth(editorState, maxListNesting) { | ||
var contentState = editorState.getCurrentContent(); | ||
var blockMap = contentState.getBlockMap(); | ||
var blocks = blockMap.filter(function (block) { | ||
return block.getDepth() > maxListNesting; | ||
}).map(function (block) { | ||
return block.set('depth', maxListNesting); | ||
}); | ||
blockMap = blockMap.merge(blocks); | ||
contentState = contentState.merge({ blockMap: blockMap }); | ||
// Use an immutable `set` instead of the `push` API to prevent undo. | ||
return _draftJs.EditorState.set(editorState, { | ||
currentContent: contentState | ||
}); | ||
} | ||
}; |
@@ -42,2 +42,8 @@ 'use strict'; | ||
describe('#getAllBlocks', function () { | ||
it('exists', function () { | ||
expect(_DraftUtils2.default.getAllBlocks).toBeDefined(); | ||
}); | ||
}); | ||
describe('#isSelectedBlockType', function () { | ||
@@ -56,3 +62,4 @@ it('exists', function () { | ||
}); | ||
var entityKey = _draftJs.Entity.create('LINK', 'MUTABLE', { url: 'www.testing.com' }); | ||
var contentStateWithEntity = contentState.createEntity('LINK', 'MUTABLE', { url: 'www.testing.com' }); | ||
var entityKey = contentStateWithEntity.getLastCreatedEntityKey(); | ||
editorState = _draftJs.RichUtils.toggleLink(editorState, updatedSelection, entityKey); | ||
@@ -70,3 +77,4 @@ expect(_DraftUtils2.default.isSelectedBlockType(editorState, 'header-one')).toBeTruthy(); | ||
}); | ||
var entityKey = _draftJs.Entity.create('LINK', 'MUTABLE', { url: 'www.testing.com' }); | ||
var contentStateWithEntity = contentState.createEntity('LINK', 'MUTABLE', { url: 'www.testing.com' }); | ||
var entityKey = contentStateWithEntity.getLastCreatedEntityKey(); | ||
editorState = _draftJs.RichUtils.toggleLink(editorState, updatedSelection, entityKey); | ||
@@ -90,3 +98,4 @@ expect(_DraftUtils2.default.isSelectedBlockType(editorState, 'header-two')).toBeFalsy(); | ||
}); | ||
var entityKey = _draftJs.Entity.create('LINK', 'MUTABLE', { url: 'www.testing.com' }); | ||
var contentStateWithEntity = contentState.createEntity('LINK', 'MUTABLE', { url: 'www.testing.com' }); | ||
var entityKey = contentStateWithEntity.getLastCreatedEntityKey(); | ||
editorState = _draftJs.RichUtils.toggleLink(editorState, updatedSelection, entityKey); | ||
@@ -96,2 +105,18 @@ expect(_DraftUtils2.default.getSelectedEntitySelection(editorState)).toBeInstanceOf(_draftJs.SelectionState); | ||
}); | ||
describe('#normaliseBlockDepth', function () { | ||
it('exists', function () { | ||
expect(_DraftUtils2.default.normaliseBlockDepth).toBeDefined(); | ||
}); | ||
it('normalises depth to a given number', function () { | ||
var contentBlocks = (0, _draftJs.convertFromHTML)('<ul>\n <li>Depth 0</li>\n <li><ul>\n <li>Depth 1</li>\n <li><ul><li>Depth 2</li></ul></li>\n </ul></li>\n </ul>'); | ||
var contentState = _draftJs.ContentState.createFromBlockArray(contentBlocks); | ||
var editorState = _DraftUtils2.default.normaliseBlockDepth(_draftJs.EditorState.createWithContent(contentState), 1); | ||
var blocks = _DraftUtils2.default.getAllBlocks(editorState); | ||
expect(blocks.map(function (block) { | ||
return block.getDepth(); | ||
}).toJS()).toEqual([0, 0, 0, 1, 1]); | ||
}); | ||
}); | ||
}); |
@@ -17,3 +17,3 @@ 'use strict'; | ||
var stubProps = { | ||
var mockProps = { | ||
entity: { | ||
@@ -40,7 +40,7 @@ getData: function getData() { | ||
it('basic', function () { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_EmbedBlock2.default, stubProps))).toMatchSnapshot(); | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_EmbedBlock2.default, mockProps))).toMatchSnapshot(); | ||
}); | ||
it('#isActive', function () { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_EmbedBlock2.default, _extends({}, stubProps, { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_EmbedBlock2.default, _extends({}, mockProps, { | ||
isActive: true | ||
@@ -47,0 +47,0 @@ })))).toMatchSnapshot(); |
@@ -17,3 +17,3 @@ 'use strict'; | ||
var stubProps = { | ||
var mockProps = { | ||
entity: { | ||
@@ -42,7 +42,7 @@ getData: function getData() { | ||
it('basic', function () { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_ImageBlock2.default, stubProps))).toMatchSnapshot(); | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_ImageBlock2.default, mockProps))).toMatchSnapshot(); | ||
}); | ||
it('#isActive', function () { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_ImageBlock2.default, _extends({}, stubProps, { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_ImageBlock2.default, _extends({}, mockProps, { | ||
isActive: true | ||
@@ -53,3 +53,3 @@ })))).toMatchSnapshot(); | ||
it('#entityConfig.imageFormats', function () { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_ImageBlock2.default, _extends({}, stubProps, { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_ImageBlock2.default, _extends({}, mockProps, { | ||
entityConfig: { | ||
@@ -63,3 +63,3 @@ imageFormats: [{ label: 'Left', value: 'left' }, { label: 'Right', value: 'right' }] | ||
it('#alignment', function () { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_ImageBlock2.default, _extends({}, stubProps, { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_ImageBlock2.default, _extends({}, mockProps, { | ||
entityConfig: { | ||
@@ -73,7 +73,7 @@ imageFormats: [{ label: 'Left', value: 'left' }, { label: 'Right', value: 'right' }] | ||
it('#onCancel', function () { | ||
(0, _enzyme.shallow)(_react2.default.createElement(_ImageBlock2.default, _extends({}, stubProps, { | ||
(0, _enzyme.shallow)(_react2.default.createElement(_ImageBlock2.default, _extends({}, mockProps, { | ||
isActive: true | ||
}))).find('.button.no').simulate('click'); | ||
expect(stubProps.onCancel.mock.calls.length).toBe(1); | ||
expect(mockProps.onCancel).toHaveBeenCalledTimes(1); | ||
}); | ||
}); |
@@ -13,4 +13,2 @@ 'use strict'; | ||
var _draftJs = require('draft-js'); | ||
var _constants = require('../api/constants'); | ||
@@ -91,7 +89,9 @@ | ||
value: function onSave(nextData) { | ||
var block = this.props.block; | ||
var _props = this.props, | ||
block = _props.block, | ||
contentState = _props.contentState; | ||
// This will update in place | ||
_draftJs.Entity.mergeData(block.getEntityAt(0), nextData); | ||
contentState.mergeEntityData(block.getEntityAt(0), nextData); | ||
this.closeBlock(); | ||
@@ -127,2 +127,3 @@ } | ||
block: _react2.default.PropTypes.object.isRequired, | ||
contentState: _react2.default.PropTypes.object.isRequired, | ||
blockProps: _react2.default.PropTypes.object.isRequired | ||
@@ -129,0 +130,0 @@ }; |
@@ -19,3 +19,3 @@ 'use strict'; | ||
var stubProps = { | ||
var mockProps = { | ||
block: {}, | ||
@@ -27,3 +27,4 @@ blockProps: { | ||
entityConfig: {} | ||
} | ||
}, | ||
contentState: {} | ||
}; | ||
@@ -37,7 +38,7 @@ | ||
it('basic', function () { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_MediaBlock2.default, stubProps))).toMatchSnapshot(); | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_MediaBlock2.default, mockProps))).toMatchSnapshot(); | ||
}); | ||
it('image', function () { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_MediaBlock2.default, _extends({}, stubProps, { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_MediaBlock2.default, _extends({}, mockProps, { | ||
blockProps: { entity: { type: _constants.ENTITY_TYPE.IMAGE }, entityConfig: {} } | ||
@@ -48,3 +49,3 @@ })))).toMatchSnapshot(); | ||
it('embed', function () { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_MediaBlock2.default, _extends({}, stubProps, { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_MediaBlock2.default, _extends({}, mockProps, { | ||
blockProps: { entity: { type: _constants.ENTITY_TYPE.EMBED }, entityConfig: {} } | ||
@@ -55,3 +56,3 @@ })))).toMatchSnapshot(); | ||
it('#isActive', function () { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_MediaBlock2.default, stubProps)).setState({ | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_MediaBlock2.default, mockProps)).setState({ | ||
isActive: true | ||
@@ -63,7 +64,15 @@ })).toMatchSnapshot(); | ||
var unlockEditor = jest.fn(); | ||
(0, _enzyme.shallow)(_react2.default.createElement(_MediaBlock2.default, _extends({}, stubProps, { | ||
blockProps: Object.assign({ unlockEditor: unlockEditor }, stubProps.blockProps) | ||
(0, _enzyme.shallow)(_react2.default.createElement(_MediaBlock2.default, _extends({}, mockProps, { | ||
blockProps: Object.assign({ unlockEditor: unlockEditor }, mockProps.blockProps) | ||
}))).instance().closeBlock(); | ||
expect(unlockEditor.mock.calls.length).toBe(1); | ||
expect(unlockEditor).toHaveBeenCalledTimes(1); | ||
}); | ||
it('#blockProps.lockEditor', function () { | ||
var lockEditor = jest.fn(); | ||
(0, _enzyme.shallow)(_react2.default.createElement(_MediaBlock2.default, _extends({}, mockProps, { | ||
blockProps: Object.assign({ lockEditor: lockEditor }, mockProps.blockProps) | ||
}))).instance().onClick(); | ||
expect(lockEditor).toHaveBeenCalledTimes(1); | ||
}); | ||
}); |
@@ -24,5 +24,6 @@ 'use strict'; | ||
var Button = function Button(_ref) { | ||
var icon = _ref.icon, | ||
var active = _ref.active, | ||
label = _ref.label, | ||
active = _ref.active, | ||
title = _ref.title, | ||
icon = _ref.icon, | ||
onClick = _ref.onClick; | ||
@@ -34,2 +35,3 @@ return _react2.default.createElement( | ||
type: 'button', | ||
title: title, | ||
onMouseDown: onMouseDown.bind(null, onClick) | ||
@@ -43,15 +45,17 @@ }, | ||
Button.propTypes = { | ||
active: _react2.default.PropTypes.bool, | ||
label: _react2.default.PropTypes.string, | ||
onClick: _react2.default.PropTypes.func, | ||
title: _react2.default.PropTypes.string, | ||
icon: _react2.default.PropTypes.string, | ||
active: _react2.default.PropTypes.bool | ||
onClick: _react2.default.PropTypes.func | ||
}; | ||
Button.defaultProps = { | ||
active: false, | ||
label: null, | ||
onClick: function onClick() {}, | ||
title: null, | ||
icon: null, | ||
active: false | ||
onClick: function onClick() {} | ||
}; | ||
exports.default = Button; |
@@ -40,5 +40,5 @@ 'use strict'; | ||
(0, _enzyme.shallow)(_react2.default.createElement(_Button2.default, { onClick: onClick })).simulate('mousedown', event); | ||
expect(onClick.mock.calls.length).toBe(1); | ||
expect(event.preventDefault.mock.calls.length).toBe(1); | ||
expect(onClick).toHaveBeenCalledTimes(1); | ||
expect(event.preventDefault).toHaveBeenCalledTimes(1); | ||
}); | ||
}); |
@@ -71,2 +71,5 @@ 'use strict'; | ||
enableLineBreak: false, | ||
// Disable copy/paste of rich text in the editor. | ||
// TODO Make this false by default once copy/paste is better supported. | ||
stripPastedStyles: true, | ||
// List of the available entity types. | ||
@@ -78,3 +81,3 @@ entityTypes: [], | ||
inlineStyles: [], | ||
// Max level of nesting for unordered and ordered lists. | ||
// Max level of nesting for unordered and ordered lists. 0 = no nesting. | ||
maxListNesting: 1, | ||
@@ -90,2 +93,3 @@ // Frequency at which the save callback is triggered (ms). | ||
enableLineBreak: _react2.default.PropTypes.bool, | ||
stripPastedStyles: _react2.default.PropTypes.bool, | ||
entityTypes: _react2.default.PropTypes.arrayOf(_react2.default.PropTypes.shape({ | ||
@@ -182,9 +186,21 @@ label: _react2.default.PropTypes.string.isRequired, | ||
key: 'onChange', | ||
value: function onChange(editorState) { | ||
value: function onChange(nextEditorState) { | ||
var _this2 = this; | ||
var stateSaveInterval = this.props.stateSaveInterval; | ||
var _props = this.props, | ||
stateSaveInterval = _props.stateSaveInterval, | ||
maxListNesting = _props.maxListNesting; | ||
var editorState = this.state.editorState; | ||
var contentState = editorState.getCurrentContent(); | ||
var nextContentState = nextEditorState.getCurrentContent(); | ||
this.setState({ editorState: editorState }, function () { | ||
if (nextContentState !== contentState && nextEditorState.getLastChangeType() === 'insert-fragment') { | ||
// eslint-disable-next-line no-param-reassign | ||
nextEditorState = _DraftUtils2.default.normaliseBlockDepth(nextEditorState, maxListNesting); | ||
} | ||
this.setState({ | ||
editorState: nextEditorState | ||
}, function () { | ||
global.clearTimeout(_this2.updateTimeout); | ||
@@ -239,2 +255,3 @@ _this2.updateTimeout = global.setTimeout(_this2.saveState, stateSaveInterval); | ||
var contentState = editorState.getCurrentContent(); | ||
var ret = false; | ||
@@ -251,3 +268,3 @@ | ||
if (entityKey) { | ||
var entityData = _draftJs.Entity.get(entityKey).getData(); | ||
var entityData = contentState.getEntity(entityKey).getData(); | ||
@@ -375,3 +392,4 @@ if (entityData.url) { | ||
var entity = _draftJs.Entity.get(entityKey); | ||
var contentState = editorState.getCurrentContent(); | ||
var entity = contentState.getEntity(entityKey); | ||
@@ -419,2 +437,3 @@ // TODO It seems strange to update the selection state when requesting an edit. | ||
var contentState = editorState.getCurrentContent(); | ||
var entity = void 0; | ||
@@ -426,3 +445,3 @@ var isHorizontalRule = void 0; | ||
case _constants.BLOCK_TYPE.ATOMIC: | ||
entity = _draftJs.Entity.get(block.getEntityAt(0)); | ||
entity = contentState.getEntity(block.getEntityAt(0)); | ||
isHorizontalRule = entity.type === _constants.ENTITY_TYPE.HORIZONTAL_RULE; | ||
@@ -465,5 +484,6 @@ | ||
var contentState = editorState.getCurrentContent(); | ||
var entityKey = _DraftUtils2.default.getSelectionEntity(editorState); | ||
this.toggleDialog(entityType, entityKey ? _draftJs.Entity.get(entityKey) : null); | ||
this.toggleDialog(entityType, entityKey ? contentState.getEntity(entityKey) : null); | ||
} | ||
@@ -520,4 +540,7 @@ }, { | ||
value: function renderTooltip() { | ||
var shouldShowTooltip = this.state.shouldShowTooltip; | ||
var _state2 = this.state, | ||
editorState = _state2.editorState, | ||
shouldShowTooltip = _state2.shouldShowTooltip; | ||
var contentState = editorState.getCurrentContent(); | ||
var entityKey = this.tooltip && this.tooltip.getAttribute('data-tooltip'); | ||
@@ -531,3 +554,3 @@ | ||
onEdit: this.onEditEntity.bind(this, entityKey), | ||
entityData: _draftJs.Entity.get(entityKey).getData(), | ||
entityData: contentState.getEntity(entityKey).getData(), | ||
position: { | ||
@@ -545,11 +568,12 @@ top: this.tooltip.getBoundingClientRect().top + document.body.scrollTop + this.tooltip.offsetHeight, | ||
var _props = this.props, | ||
enableHorizontalRule = _props.enableHorizontalRule, | ||
enableLineBreak = _props.enableLineBreak, | ||
blockTypes = _props.blockTypes, | ||
inlineStyles = _props.inlineStyles, | ||
entityTypes = _props.entityTypes; | ||
var _state2 = this.state, | ||
editorState = _state2.editorState, | ||
readOnly = _state2.readOnly; | ||
var _props2 = this.props, | ||
enableHorizontalRule = _props2.enableHorizontalRule, | ||
enableLineBreak = _props2.enableLineBreak, | ||
stripPastedStyles = _props2.stripPastedStyles, | ||
blockTypes = _props2.blockTypes, | ||
inlineStyles = _props2.inlineStyles, | ||
entityTypes = _props2.entityTypes; | ||
var _state3 = this.state, | ||
editorState = _state3.editorState, | ||
readOnly = _state3.readOnly; | ||
@@ -587,2 +611,3 @@ /* eslint-disable jsx-a11y/no-static-element-interactions */ | ||
readOnly: readOnly, | ||
stripPastedStyles: stripPastedStyles, | ||
handleReturn: this.handleReturn, | ||
@@ -589,0 +614,0 @@ keyBindingFn: _behavior2.default.getKeyBindingFn(blockTypes, inlineStyles, entityTypes), |
@@ -19,2 +19,8 @@ 'use strict'; | ||
var _constants = require('../api/constants'); | ||
var _behavior = require('../api/behavior'); | ||
var _behavior2 = _interopRequireDefault(_behavior); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -42,2 +48,3 @@ | ||
label: block.label, | ||
title: _behavior2.default.getKeyboardShortcut(block.type), | ||
icon: block.icon, | ||
@@ -52,2 +59,3 @@ onClick: toggleBlockType.bind(null, block.type) | ||
label: style.label, | ||
title: _behavior2.default.getKeyboardShortcut(style.type), | ||
icon: style.icon, | ||
@@ -63,3 +71,4 @@ onClick: toggleInlineStyle.bind(null, style.type) | ||
onClick: addBR, | ||
label: 'BR' | ||
label: 'BR', | ||
title: _behavior2.default.getKeyboardShortcut(_constants.BR_TYPE) | ||
}) : null, | ||
@@ -71,2 +80,3 @@ entityTypes.map(function (entity) { | ||
label: entity.label, | ||
title: _behavior2.default.getKeyboardShortcut(entity.type), | ||
icon: entity.icon | ||
@@ -73,0 +83,0 @@ }); |
@@ -17,3 +17,3 @@ 'use strict'; | ||
var stubProps = { | ||
var mockProps = { | ||
position: { | ||
@@ -34,7 +34,7 @@ top: 0, | ||
it('basic', function () { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_Tooltip2.default, stubProps))).toMatchSnapshot(); | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_Tooltip2.default, mockProps))).toMatchSnapshot(); | ||
}); | ||
it('#entityData.url', function () { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_Tooltip2.default, _extends({}, stubProps, { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_Tooltip2.default, _extends({}, mockProps, { | ||
entityData: { | ||
@@ -47,3 +47,3 @@ url: 'http://www.example.com/' | ||
it('#entityData.label', function () { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_Tooltip2.default, _extends({}, stubProps, { | ||
expect((0, _enzyme.shallow)(_react2.default.createElement(_Tooltip2.default, _extends({}, mockProps, { | ||
entityData: { | ||
@@ -56,10 +56,10 @@ label: 'Test label' | ||
it('#onEdit', function () { | ||
(0, _enzyme.shallow)(_react2.default.createElement(_Tooltip2.default, stubProps)).find('button').first().simulate('click'); | ||
expect(stubProps.onEdit.mock.calls.length).toBe(1); | ||
(0, _enzyme.shallow)(_react2.default.createElement(_Tooltip2.default, mockProps)).find('button').first().simulate('click'); | ||
expect(mockProps.onEdit).toHaveBeenCalledTimes(1); | ||
}); | ||
it('#onRemove', function () { | ||
(0, _enzyme.shallow)(_react2.default.createElement(_Tooltip2.default, stubProps)).find('button').last().simulate('click'); | ||
expect(stubProps.onRemove.mock.calls.length).toBe(1); | ||
(0, _enzyme.shallow)(_react2.default.createElement(_Tooltip2.default, mockProps)).find('button').last().simulate('click'); | ||
expect(mockProps.onRemove).toHaveBeenCalledTimes(1); | ||
}); | ||
}); |
{ | ||
"name": "draftail", | ||
"version": "0.4.1", | ||
"version": "0.5.0", | ||
"description": "A batteries-excluded rich text editor based on Draft.js", | ||
@@ -38,23 +38,25 @@ "author": "Springload", | ||
}, | ||
"dependencies": {}, | ||
"dependencies": { | ||
"draftjs-utils": "0.6.0", | ||
"immutable": "^3.0.0" | ||
}, | ||
"devDependencies": { | ||
"babel-core": "^6.18.2", | ||
"babel-cli": "^6.22.2", | ||
"babel-jest": "^18.0.0", | ||
"babel-loader": "^6.2.7", | ||
"babel-loader": "^6.2.10", | ||
"babel-preset-latest": "^6.16.0", | ||
"babel-preset-react": "^6.16.0", | ||
"coveralls": "^2.11.15", | ||
"draft-js": "^0.9.1", | ||
"draftjs-utils": "^0.3.2", | ||
"dotenv": "^4.0.0", | ||
"draft-js": "^0.10.0", | ||
"enzyme": "^2.7.0", | ||
"enzyme-to-json": "^1.4.5", | ||
"eslint": "^3.9.1", | ||
"eslint-config-airbnb": "^13.0.0", | ||
"eslint": "^3.13.1", | ||
"eslint-config-airbnb": "^14.0.0", | ||
"eslint-plugin-import": "^2.2.0", | ||
"eslint-plugin-jsx-a11y": "^2.2.3", | ||
"eslint-plugin-react": "^6.6.0", | ||
"eslint-plugin-jsx-a11y": "^4.0.0", | ||
"eslint-plugin-react": "^6.9.0", | ||
"express": "^4.14.0", | ||
"immutable": "^3.8.1", | ||
"jest": "^18.1.0", | ||
"node-sass": "^3.11.2", | ||
"node-sass": "^4.3.0", | ||
"progress-bar-webpack-plugin": "^1.9.0", | ||
@@ -69,7 +71,5 @@ "react": "^15.4.1", | ||
"peerDependencies": { | ||
"draft-js": "0.9.1", | ||
"draftjs-utils": "0.3.2", | ||
"immutable": "^3.x.x", | ||
"react": "^15.x.x", | ||
"react-dom": "^15.x.x" | ||
"draft-js": "^0.10.0", | ||
"react": "^15.0.0", | ||
"react-dom": "^15.0.0" | ||
}, | ||
@@ -76,0 +76,0 @@ "scripts": { |
@@ -8,10 +8,46 @@ [draftail](https://springload.github.io/draftail/) [](https://www.npmjs.com/package/draftail) [](https://travis-ci.org/springload/draftail) [](https://coveralls.io/github/springload/draftail) [](https://david-dm.org/springload/draftail) [](https://david-dm.org/springload/draftail#info=devDependencies) [](https://codeclimate.com/github/springload/draftail) | ||
## Usage | ||
First, grab the package from npm: | ||
```sh | ||
npm install --save draftail | ||
# Draftail's peerDependencies: | ||
npm install --save draft-js@^0.9.x draftjs-utils@^0.3.2 immutable@^3.x.x react@^15.x.x react-dom@^15.x.x | ||
npm install --save draft-js@^0.10.0 react@^15.0.0 react-dom@^15.0.0 | ||
# Note: Draft.js builds upon ES6 language features. If targeting browsers that do not support them, | ||
# see https://facebook.github.io/draft-js/docs/advanced-topics-issues-and-pitfalls.html#polyfills. | ||
``` | ||
[ES6 polyfills for Draft.js](https://facebook.github.io/draft-js/docs/advanced-topics-issues-and-pitfalls.html#polyfills) are also required. | ||
Then, import the editor and use it in your code. Here is a [basic example](https://springload.github.io/draftail/example.html): | ||
```js | ||
import React from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import DraftailEditor, { BLOCK_TYPE, INLINE_STYLE } from 'draftail'; | ||
const initialContentState = JSON.parse(sessionStorage.getItem('basic:contentState')) || {}; | ||
const onSave = (contentState) => { | ||
sessionStorage.setItem('basic:contentState', JSON.stringify(contentState)); | ||
}; | ||
const editor = ( | ||
<DraftailEditor | ||
rawContentState={initialContentState} | ||
onSave={onSave} | ||
blockTypes={[ | ||
{ label: 'H3', type: BLOCK_TYPE.HEADER_THREE }, | ||
{ label: 'UL', type: BLOCK_TYPE.UNORDERED_LIST_ITEM, icon: 'icon-list-ul' }, | ||
]} | ||
inlineStyles={[ | ||
{ label: 'Bold', type: INLINE_STYLE.BOLD, icon: 'icon-bold' }, | ||
{ label: 'Italic', type: INLINE_STYLE.ITALIC, icon: 'icon-italic' }, | ||
]} | ||
/> | ||
); | ||
ReactDOM.render(editor, document.querySelector('[data-mount-basic]')); | ||
``` | ||
## Development | ||
@@ -27,4 +63,6 @@ | ||
npm install | ||
# Optionally, install the git hooks. | ||
# Install the git hooks. | ||
./.githooks/deploy | ||
# Set up a `.env` file with the appropriate secrets. | ||
touch .env | ||
``` | ||
@@ -31,0 +69,0 @@ |
Sorry, the diff of this file is not supported yet
151044
25
3137
94
+ Addeddraftjs-utils@0.6.0
+ Addedimmutable@^3.0.0
+ Addeddraft-js@0.10.5(transitive)
+ Addeddraftjs-utils@0.6.0(transitive)
- Removeddraft-js@0.9.1(transitive)
- Removeddraftjs-utils@0.3.2(transitive)