UNPKG

draft-js-autolist-plugin

Version:

Automatically create ordered/unordered lists in draft-js as you type

197 lines (170 loc) 7 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _draftJs = require('draft-js'); var _trackCharacters = require('./trackCharacters'); var _trackCharacters2 = _interopRequireDefault(_trackCharacters); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * Regexes for matching list unordered/ordered types. * Split into two since we're sometimes matching with the space at the end * * Unordered lists should match either '* ' or '- ' * Ordered lists should match any `${digit}. `, so '1. ', '3. ', 222. ' should * all work. * * @type {RegExp} */ var UL_FULL_REGEX = /^(\*|\-)\s$/; var UL_INTERCEPT_REGEX = /^(\*|\-)$/; var OL_FULL_REGEX = /^\d+\.\s$/; var OL_INTERCEPT_REGEX = /^\d+\.$/; /** * The autolist namespaced commands * @type {Object} */ var commands = { OL: 'autolist-ordered', UL: 'autolist-unordered' }; var blockTypes = { UL: 'unordered-list-item', OL: 'ordered-list-item', UNSTYLED: 'unstyled' }; /** * Auto-list Plugin * * Infers that the user is trying to write a list of things and turns the * current block into either an ordered or unordered list. * * We do this by tracking the characters typed and, when a matching sequence * is entered, then attempting to turn it into the correct list type. * * This approach is not fool-proof: if you make a mistake while typing a list * and have to delete it we’ll fail to infer that correctly. This is somewhat * intentional as we don't want to have to query the content of the editor * for every single keyDown. * * @return {Object} Object defining the draft-js API methods */ function autoListPlugin() { var keyCharsHistory = []; return { /** * Listen to all keyboard events, intercept and return a custom command * if the sequence of typed characters matches the criteria for creating * a unordered or ordered list. * * @param {KeyboardEvent} e Synthetic keyboard event from draftjs * @return {String} A command, either our custom ones or one of the defaults */ keyBindingFn: function keyBindingFn(e) { keyCharsHistory = (0, _trackCharacters2.default)(keyCharsHistory, e); // Only run our more complex checks if the last character // typed is a space if (e.keyCode === 32) { // Test the last two characters to see if they match the full unordered // list regex var lastTwoChars = keyCharsHistory.slice(-2).join(''); if (UL_FULL_REGEX.test(lastTwoChars)) { return commands.UL; // Test the all the characters to see if they match the full ordered // list regex } else if (OL_FULL_REGEX.test(keyCharsHistory.join(''))) { return commands.OL; } } }, /** * Listen to the when a return is typed, and remove an empty list item. * Means that lists act as you’d expect while typing. * * @param {KeyboardEvent} e Synthetic keyboard event from draftjs * @param {Object} editorState Current editor state * @param {Function} options.setEditorState Setter function passed by * the draft-js-plugin-editor * @return {String} Did we handle the return or not? */ handleReturn: function handleReturn(e, editorState, _ref) { var setEditorState = _ref.setEditorState; var content = editorState.getCurrentContent(); // Retrieve current block var selection = editorState.getSelection(); var blockKey = selection.getStartKey(); var block = content.getBlockForKey(blockKey); var blockType = block.getType(); // If it’s a list-item and it’s empty, toggle its blockType (which should // make it 'unstyled') if (/list-item/.test(blockType) && block.getText() === '') { editorState = _draftJs.RichUtils.toggleBlockType(editorState, blockType); content = editorState.getCurrentContent(); editorState = _draftJs.EditorState.forceSelection(editorState, content.getSelectionAfter()); setEditorState(editorState); return 'handled'; } return 'not-handled'; }, /** * Handle our custom `commands` * @param {String} command The current command to respond to * @param {Object} editorState Current editor state * @param {Number} eventTimeStamp When the event happened. * @param {Function} options.setEditorState Setter function passed by * the draft-js-plugin-editor * @return {String} Did we handle the return or not? */ handleKeyCommand: function handleKeyCommand(command, editorState, eventTimeStamp, _ref2) { var setEditorState = _ref2.setEditorState; if (command === commands.UL || command === commands.OL) { // Set up the base types/checks var listType = blockTypes.UL; var listTest = function listTest(text) { return UL_INTERCEPT_REGEX.test(text); }; if (command === commands.OL) { listType = blockTypes.OL; listTest = function listTest(text) { return OL_INTERCEPT_REGEX.test(text); }; } var content = editorState.getCurrentContent(); // Retrieve the focused block var selection = editorState.getSelection(); var blockKey = selection.getStartKey(); var block = content.getBlockForKey(blockKey); // Check if it matches our criteria for creating a list: unstyled, at // the top-level and empty. var blockText = block.getText(); if (block.getType() === blockTypes.UNSTYLED && block.getDepth() === 0 && listTest(blockText)) { // Convert the existing block to an unordered list editorState = _draftJs.RichUtils.toggleBlockType(editorState, listType); content = editorState.getCurrentContent(); block = content.getBlockForKey(blockKey); // Select the entire block var blockSelection = new _draftJs.SelectionState({ anchorKey: blockKey, anchorOffset: 0, focusKey: blockKey, focusOffset: block.getLength() }); // Replace with the text with either nothing (if we created a list) // or the existing content content = _draftJs.Modifier.replaceText(content, blockSelection, ''); } else { // We’ve intercepted the normal keyboard command propagation here, so // we needs to manually insert a space if we're not injecting a list content = _draftJs.Modifier.insertText(content, selection, ' '); } // Propagate the new state up editorState = _draftJs.EditorState.push(editorState, content); editorState = _draftJs.EditorState.forceSelection(editorState, content.getSelectionAfter()); setEditorState(editorState); return 'handled'; } return 'not-handled'; } }; } exports.default = autoListPlugin;