diff --git a/.eslintrc b/.eslintrc index 3f8f97d..59ba29e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,16 +1,16 @@ { - extends: [ + "extends": [ "standard", ], - rules: { + "rules": { "comma-dangle": [2, "always-multiline"] }, - parserOptions: { - ecmaVersion: 6, - sourceType: "module", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" }, - plugins: [ - "standard", + "plugins": [ + "standard" ] } diff --git a/.gitignore b/.gitignore index 66fd66c..d892955 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules npm-debug.log* +dist diff --git a/README.md b/README.md index 6fa8412..9e05d35 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,28 @@ You can pass options to the plugin as you call it: ```js const options = { - stripEntities: false + stripEntities: false, + stripNewlines: false, } const singleLinePlugin = createSingleLinePlugin(options) ``` -There’s only one option so far: `stripEntities: true/false`. +### stripEntities + +Strip Entities from text. + +Type: `boolean`
+Default: `true`
+Options: `true` | `false` + +### stripNewlines + +Strip newline characters `\n` from text. + +Type: `boolean`
+Default: `true`
+Options: `true` | `false` + ## Developing diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index 7c66fb5..0000000 --- a/lib/index.js +++ /dev/null @@ -1,112 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _immutable = require('immutable'); - -var _draftJs = require('draft-js'); - -var _utils = require('./utils'); - -/** - * Default options - * @type {Object} - */ -var defaultOptions = { - stripEntities: true -}; - -/** - * Single Line Plugin - * @param {Object} options Per-instance options to override the defaults - * @return {Object} Compatible draft-js-editor-plugin object - */ -function singleLinePlugin() { - var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - - options = Object.assign({}, defaultOptions, options); - - return { - /** - * Return a compatible blockRenderMap - * - * NOTE: Needs to be explicitly applied, the plugin system doesn’t do - * anything with this at the moment. - * - * @type {ImmutableMap} - */ - blockRenderMap: (0, _immutable.Map)({ - 'unstyled': { - element: 'div' - } - }), - - /** - * onChange - * - * Condense multiple blocks into a single block and (optionally) strip all - * entities from the content of that block. - * - * @param {EditorState} editorState The current state of the editor - * @return {EditorState} A new editor state - */ - onChange: function onChange(editorState) { - var blocks = editorState.getCurrentContent().getBlocksAsArray(); - - // If we have more than one block, compress them - if (blocks.length > 1) { - editorState = (0, _utils.condenseBlocks)(editorState, blocks, options); - } else { - // We only have one content block - var contentBlock = blocks[0]; - var text = contentBlock.getText(); - var characterList = contentBlock.getCharacterList(); - - if (_utils.NEWLINE_REGEX.test(text) || (0, _utils.characterListhasEntities)(characterList)) { - // Replace the text stripped of its newlines. Note that we replace - // one '\n' with one ' ' so we don't need to modify the characterList - text = (0, _utils.replaceNewlines)(text); - - // Strip entities? - if (options.stripEntities) { - characterList = characterList.map(_utils.stripEntityFromCharacterMetadata); - } - - // Create a new content block based on the old one - contentBlock = new _draftJs.ContentBlock({ - key: (0, _draftJs.genKey)(), - text: text, - type: 'unstyled', - characterList: characterList, - depth: 0 - }); - - // Update the editor state with the compressed version - // const selection = editorState.getSelection() - var newContentState = _draftJs.ContentState.createFromBlockArray([contentBlock]); - - // Create the new state as an undoable action - editorState = _draftJs.EditorState.push(editorState, newContentState, 'insert-characters'); - editorState = _draftJs.EditorState.moveFocusToEnd(editorState); - } - } - - return editorState; - }, - - - /** - * Stop new lines being inserted by always handling the return - * - * @param {KeyboardEvent} e Synthetic keyboard event from draftjs - * @return {Boolean} Did we handle the return or not? (pro-trip: yes, we did) - */ - handleReturn: function handleReturn(e) { - return true; - } - }; -} - -exports.default = singleLinePlugin; \ No newline at end of file diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index 02ae41e..0000000 --- a/lib/utils.js +++ /dev/null @@ -1,99 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.NEWLINE_REGEX = undefined; -exports.replaceNewlines = replaceNewlines; -exports.condenseBlocks = condenseBlocks; -exports.stripEntityFromCharacterMetadata = stripEntityFromCharacterMetadata; -exports.characterListhasEntities = characterListhasEntities; - -var _draftJs = require('draft-js'); - -var _immutable = require('immutable'); - -/** - * Greedy regex for matching newlines - * @type {RegExp} - */ -var NEWLINE_REGEX = exports.NEWLINE_REGEX = /\n/g; - -/** - * Replace newline characters with the passed string - * @param {String} str String to replace - * @param {String} replacement Replacement characters - * @return {String} Modified string - */ -function replaceNewlines(str) { - var replacement = arguments.length <= 1 || arguments[1] === undefined ? ' ' : arguments[1]; - - return str.replace(NEWLINE_REGEX, replacement); -} - -/** - * Condense an array of content blocks into a single block - * @param {EditorState} editorState draft-js EditorState instance - * @param {Array} blocks Array of ContentBlocks - * @param {Object} options - * @return {EditorState} A modified EditorState instance - */ -function condenseBlocks(editorState, blocks, options) { - blocks = blocks || editorState.getCurrentContent().getBlocksAsArray(); - var text = (0, _immutable.List)(); - var characterList = (0, _immutable.List)(); - - // Gather all the text/characterList and concat them - blocks.forEach(function (block) { - // Atomic blocks should be ignored (stripped) - if (block.getType() !== 'atomic') { - text = text.push(replaceNewlines(block.getText())); - characterList = characterList.concat(block.getCharacterList()); - } - }); - - // Strip entities? - if (options.stripEntities) { - characterList = characterList.map(stripEntityFromCharacterMetadata); - } - - // Create a new content block - var contentBlock = new _draftJs.ContentBlock({ - key: (0, _draftJs.genKey)(), - text: text.join(''), - type: 'unstyled', - characterList: characterList, - depth: 0 - }); - - // Update the editor state with the compressed version - var newContentState = _draftJs.ContentState.createFromBlockArray([contentBlock]); - // Create the new state as an undoable action - editorState = _draftJs.EditorState.push(editorState, newContentState, 'remove-range'); - // Move the selection to the end - return _draftJs.EditorState.moveFocusToEnd(editorState); -} - -/** - * Strip any `entity` keys from a CharacterMetadata set - * @param {CharacterMetadata} characterMeta An Immutable.Record representing the metadata for an individual character - * @return {CharacterMetadata} - */ -function stripEntityFromCharacterMetadata(characterMeta) { - return characterMeta.set('entity', null); -} - -/** - * Check if a CharacterList contains entities - * @param {CharacterList} characterList The list of characters to check - * @return {Boolean} Contains entities? - */ -function characterListhasEntities(characterList) { - var hasEntities = false; - characterList.forEach(function (characterMeta) { - if (characterMeta.get('entity') !== null) { - hasEntities = true; - } - }); - return hasEntities; -} \ No newline at end of file diff --git a/package.json b/package.json index 325e412..210fa14 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,15 @@ { "name": "draft-js-single-line-plugin", - "version": "0.0.1", + "version": "0.0.2", "description": "Restrict a draft-js editor to a single line of input.", - "main": "lib/index.js", + "main": "dist/index.js", "scripts": { - "build": "babel src --out-dir lib", + "build": "babel src --out-dir dist", "compile": "npm run build", "precompile": "npm run clean", - "prepublish": "npm run compile", "test": "NODE_ENV=test babel-node test | faucet", "posttest": "npm run lint", - "clean": "rm -rf ./lib/*", + "clean": "rm -rf ./dist/*", "lint": "eslint 'src/*.js' 'src/**/*.js'; exit 0" }, "repository": { diff --git a/src/index.js b/src/index.js index 89d98be..d7c6469 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,7 @@ import { */ const defaultOptions = { stripEntities: true, + stripNewlines: true, } /** @@ -65,11 +66,14 @@ function singleLinePlugin (options = {}) { let contentBlock = blocks[0] let text = contentBlock.getText() let characterList = contentBlock.getCharacterList() + const isNewLine = (options.stripNewlines && NEWLINE_REGEX.test(text)) - if (NEWLINE_REGEX.test(text) || characterListhasEntities(characterList)) { + if (isNewLine || characterListhasEntities(characterList)) { // Replace the text stripped of its newlines. Note that we replace // one '\n' with one ' ' so we don't need to modify the characterList - text = replaceNewlines(text) + if (options.stripNewlines) { + text = replaceNewlines(text) + } // Strip entities? if (options.stripEntities) { @@ -86,7 +90,6 @@ function singleLinePlugin (options = {}) { }) // Update the editor state with the compressed version - // const selection = editorState.getSelection() const newContentState = ContentState.createFromBlockArray([contentBlock]) // Create the new state as an undoable action diff --git a/src/utils.js b/src/utils.js index e08ae45..123ec2a 100644 --- a/src/utils.js +++ b/src/utils.js @@ -38,7 +38,11 @@ export function condenseBlocks (editorState, blocks, options) { blocks.forEach((block) => { // Atomic blocks should be ignored (stripped) if (block.getType() !== 'atomic') { - text = text.push(replaceNewlines(block.getText())) + if (options.stripNewlines) { + text = text.push(replaceNewlines(block.getText())) + } else { + text = text.push(block.getText()) + } characterList = characterList.concat(block.getCharacterList()) } }) diff --git a/test/fixtures/content.js b/test/fixtures/content.js index 1175d9c..7e43b91 100644 --- a/test/fixtures/content.js +++ b/test/fixtures/content.js @@ -1,117 +1,117 @@ -export default { - "entityMap": { - "0": { - "type": "LINK", - "mutability": "MUTABLE", - "data": { - "url": "http://icelab.com.au" - } +export default { + 'entityMap': { + '0': { + 'type': 'LINK', + 'mutability': 'MUTABLE', + 'data': { + 'url': 'http://icelab.com.au', + }, }, - "1": { - "type": "image", - "mutability": "IMMUTABLE", - "data": { - "src": "http://placekitten.com/300/100" - } + '1': { + 'type': 'image', + 'mutability': 'IMMUTABLE', + 'data': { + 'src': 'http://placekitten.com/300/100', + }, }, - "2": { - "type": "LINK", - "mutability": "MUTABLE", - "data": { - "url": "http://makenosound.com" - } + '2': { + 'type': 'LINK', + 'mutability': 'MUTABLE', + 'data': { + 'url': 'http://makenosound.com', + }, + }, + '3': { + 'type': 'image', + 'mutability': 'IMMUTABLE', + 'data': { + 'src': 'http://placekitten.com/200/100', + }, }, - "3": { - "type": "image", - "mutability": "IMMUTABLE", - "data": { - "src": "http://placekitten.com/200/100" - } - } }, - "blocks": [ + 'blocks': [ { - "key": "a34sd", - "text": "Hello World!", - "type": "unstyled", - "depth": 0, - "inlineStyleRanges": [ + 'key': 'a34sd', + 'text': 'Hello World!', + 'type': 'unstyled', + 'depth': 0, + 'inlineStyleRanges': [ { - "offset": 0, - "length": 12, - "style": "BOLD" + 'offset': 0, + 'length': 12, + 'style': 'BOLD', }, { - "offset": 6, - "length": 6, - "style": "ITALIC" - } + 'offset': 6, + 'length': 6, + 'style': 'ITALIC', + }, ], - "entityRanges": [ + 'entityRanges': [ { - "offset": 0, - "length": 5, - "key": 0 - } - ] + 'offset': 0, + 'length': 5, + 'key': 0, + }, + ], }, { - "key": "2em79", - "text": "I am new content.", - "type": "unstyled", - "depth": 0, - "inlineStyleRanges": [], - "entityRanges": [] + 'key': '2em79', + 'text': 'I am new content.', + 'type': 'unstyled', + 'depth': 0, + 'inlineStyleRanges': [], + 'entityRanges': [], }, { - "key": "55vrh", - "text": "🍺", - "type": "atomic", - "depth": 0, - "inlineStyleRanges": [], - "entityRanges": [ + 'key': '55vrh', + 'text': '🍺', + 'type': 'atomic', + 'depth': 0, + 'inlineStyleRanges': [], + 'entityRanges': [ { - "offset": 0, - "length": 1, - "key": 1 - } - ] + 'offset': 0, + 'length': 1, + 'key': 1, + }, + ], }, { - "key": "dodnk", - "text": "I am more content.", - "type": "unordered-list-item", - "depth": 0, - "inlineStyleRanges": [], - "entityRanges": [ + 'key': 'dodnk', + 'text': 'I am more content.', + 'type': 'unordered-list-item', + 'depth': 0, + 'inlineStyleRanges': [], + 'entityRanges': [ { - "offset": 10, - "length": 7, - "key": 2 - } - ] + 'offset': 10, + 'length': 7, + 'key': 2, + }, + ], }, { - "key": "6103o", - "text": "🍺", - "type": "atomic", - "depth": 0, - "inlineStyleRanges": [], - "entityRanges": [ + 'key': '6103o', + 'text': '🍺', + 'type': 'atomic', + 'depth': 0, + 'inlineStyleRanges': [], + 'entityRanges': [ { - "offset": 0, - "length": 1, - "key": 3 - } - ] + 'offset': 0, + 'length': 1, + 'key': 3, + }, + ], }, { - "key": "evsu5", - "text": "", - "type": "unstyled", - "depth": 0, - "inlineStyleRanges": [], - "entityRanges": [] - } - ] -}; + 'key': 'evsu5', + 'text': '', + 'type': 'unstyled', + 'depth': 0, + 'inlineStyleRanges': [], + 'entityRanges': [], + }, + ], +}