Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

draftjs-filters

Package Overview
Dependencies
Maintainers
1
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

draftjs-filters - npm Package Compare versions

Comparing version 0.2.2 to 0.3.0

39

CHANGELOG.md

@@ -1,5 +0,42 @@

# Changelog
# Change Log
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
<a name="0.3.0"></a>
# [0.3.0](https://github.com/thibaudcolas/draftjs-filters/compare/v0.2.2...v0.3.0) (2018-01-15)
### Bug Fixes
* **filters:** fix bug preventing atomic block text reset ([6807077](https://github.com/thibaudcolas/draftjs-filters/commit/6807077))
* **filters:** fix filterEditorState discarding updated state ([cec872e](https://github.com/thibaudcolas/draftjs-filters/commit/cec872e))
* **filters:** stop preserveAtomicBlocks resetting blocks with text ([04d0554](https://github.com/thibaudcolas/draftjs-filters/commit/04d0554))
### Features
* **api:** add blockEntities opt for preserveAtomicBlocks ([a2fc941](https://github.com/thibaudcolas/draftjs-filters/commit/a2fc941))
* **api:** add enableLineBreak option to filterEditorState ([6c334b3](https://github.com/thibaudcolas/draftjs-filters/commit/6c334b3))
* **api:** change filterEditorState to separate options & input ([32a4586](https://github.com/thibaudcolas/draftjs-filters/commit/32a4586))
* **api:** change filterEditorState to take object as param, with keys ([cbac155](https://github.com/thibaudcolas/draftjs-filters/commit/cbac155))
* **api:** entity data filter keeps all data if no whitelist is defined ([83199dd](https://github.com/thibaudcolas/draftjs-filters/commit/83199dd))
* **api:** expose all filters to package consumers ([606f8a0](https://github.com/thibaudcolas/draftjs-filters/commit/606f8a0))
* **api:** expose new whitespaceCharacters method to the API ([9b3057d](https://github.com/thibaudcolas/draftjs-filters/commit/9b3057d))
* **api:** filter by attr should keep entity if there is no whitelist ([514e093](https://github.com/thibaudcolas/draftjs-filters/commit/514e093))
* **api:** refactor all filters to work on ContentState ([6c7fbaf](https://github.com/thibaudcolas/draftjs-filters/commit/6c7fbaf))
* **api:** refactor entity filters to iterator + callback pattern ([1e6d3f2](https://github.com/thibaudcolas/draftjs-filters/commit/1e6d3f2))
* **api:** remove enableHorizontalRule - use entities instead ([4603a36](https://github.com/thibaudcolas/draftjs-filters/commit/4603a36))
* **api:** rename entityTypes option to entities ([38ae203](https://github.com/thibaudcolas/draftjs-filters/commit/38ae203))
* **api:** rename filterEntityAttributes to filterEntityData ([9403ca2](https://github.com/thibaudcolas/draftjs-filters/commit/9403ca2))
* **api:** replace enableLineBreak with whitespacedCharacters ([a0c7745](https://github.com/thibaudcolas/draftjs-filters/commit/a0c7745))
* **filters:** add filterEntityAttributes to filterEditorState ([ab0a30e](https://github.com/thibaudcolas/draftjs-filters/commit/ab0a30e))
* **filters:** add new filterEntityAttributes filter w/ whitelist ([745ba09](https://github.com/thibaudcolas/draftjs-filters/commit/745ba09))
* **filters:** add new whitespaceCharacters method ([6fde9ee](https://github.com/thibaudcolas/draftjs-filters/commit/6fde9ee))
* **filters:** add removeInvalidDepthBlocks method to API ([5139451](https://github.com/thibaudcolas/draftjs-filters/commit/5139451))
* **filters:** implement entity filtering by attribute ([ae76945](https://github.com/thibaudcolas/draftjs-filters/commit/ae76945))
* **filters:** preserve atomic blocks based on image emoji ([fba9880](https://github.com/thibaudcolas/draftjs-filters/commit/fba9880))
* **filters:** remove atomic blocks instead of making them unstyled ([31b7664](https://github.com/thibaudcolas/draftjs-filters/commit/31b7664))
* **filters:** remove tabs as part of filterEditorState ([9fd0005](https://github.com/thibaudcolas/draftjs-filters/commit/9fd0005))
* **filters:** start updating filter methods to work on ContentState ([e716dd9](https://github.com/thibaudcolas/draftjs-filters/commit/e716dd9))
* **filters:** update whitespaceCharacters to operate on ContentState ([62fef11](https://github.com/thibaudcolas/draftjs-filters/commit/62fef11))
<a name="0.1.0"></a>

@@ -6,0 +43,0 @@

441

dist/draftjs-filters.cjs.js

@@ -7,14 +7,11 @@ 'use strict';

var ATOMIC = "atomic";
var UNSTYLED = "unstyled";
var ATOMIC = "atomic";
var UNORDERED_LIST_ITEM = "unordered-list-item";
var ORDERED_LIST_ITEM = "ordered-list-item";
var IMAGE = "IMAGE";
var HORIZONTAL_RULE = "HORIZONTAL_RULE";
/**
* Helper functions to filter/whitelist specific formatting.
* Meant to be used when pasting unconstrained content.
*/
/**
* Makes atomic blocks where they would be required for a block-level entity
* Creates atomic blocks where they would be required for a block-level entity
* to work correctly, when such an entity exists.

@@ -24,10 +21,14 @@ * Note: at the moment, this is only useful for IMAGE entities that Draft.js

*/
var preserveAtomicBlocks = function preserveAtomicBlocks(editorState, entityTypes) {
var content = editorState.getCurrentContent();
var preserveAtomicBlocks = function preserveAtomicBlocks(content) {
var blockMap = content.getBlockMap();
var perservedBlocks = blockMap.filter(function (block) {
var text = block.getText();
var entityKey = block.getEntityAt(0);
// Use the ES6 way of counting string length to account for unicode symbols.
// See https://mathiasbynens.be/notes/javascript-unicode.
var isOneSymbol = Array.from(text).length === 1;
var shouldPreserve = entityKey && isOneSymbol && ["📷", " "].includes(text);
return entityKey && entityTypes.includes(content.getEntity(entityKey).getType());
return shouldPreserve;
}).map(function (block) {

@@ -38,90 +39,122 @@ return block.set("type", ATOMIC);

if (perservedBlocks.size !== 0) {
return draftJs.EditorState.set(editorState, {
currentContent: content.merge({
blockMap: blockMap.merge(perservedBlocks)
})
return content.merge({
blockMap: blockMap.merge(perservedBlocks)
});
}
return editorState;
return content;
};
/**
* Resets the depth of all the content to at most maxListNesting.
* Resets atomic blocks to have a single-space char and no styles.
*/
var resetBlockDepth = function resetBlockDepth(editorState, maxListNesting) {
var content = editorState.getCurrentContent();
var resetAtomicBlocks = function resetAtomicBlocks(content) {
var blockMap = content.getBlockMap();
var blocks = blockMap;
var changedBlocks = blockMap.filter(function (block) {
return block.getDepth() > maxListNesting;
var normalisedBlocks = blocks.filter(function (block) {
return block.getType() === ATOMIC && (block.getText() !== " " || block.getInlineStyleAt(0).size !== 0);
}).map(function (block) {
return block.set("depth", maxListNesting);
// Retain only the first character, and remove all of its styles.
var chars = block.getCharacterList().slice(0, 1).map(function (char) {
var newChar = char;
char.getStyle().forEach(function (type) {
newChar = draftJs.CharacterMetadata.removeStyle(newChar, type);
});
return newChar;
});
return block.merge({
text: " ",
characterList: chars
});
});
if (changedBlocks.size !== 0) {
return draftJs.EditorState.set(editorState, {
currentContent: content.merge({
blockMap: blockMap.merge(changedBlocks)
})
});
if (normalisedBlocks.size !== 0) {
blocks = blocks.merge(normalisedBlocks);
}
return editorState;
return content.merge({
blockMap: blocks
});
};
/**
* Resets all blocks that use unavailable types to unstyled.
* Removes atomic blocks for which the entity isn't whitelisted.
*/
var resetBlockType = function resetBlockType(editorState, enabledTypes) {
var content = editorState.getCurrentContent();
var removeInvalidAtomicBlocks = function removeInvalidAtomicBlocks(whitelist, content) {
var blockMap = content.getBlockMap();
var changedBlocks = blockMap.filter(function (block) {
return !enabledTypes.includes(block.getType());
}).map(function (block) {
return block.set("type", UNSTYLED);
});
var isValidAtomicBlock = function isValidAtomicBlock(block) {
if (block.getType() !== ATOMIC) {
return true;
}
if (changedBlocks.size !== 0) {
return draftJs.EditorState.set(editorState, {
currentContent: content.merge({
blockMap: blockMap.merge(changedBlocks)
})
var entityKey = block.getEntityAt(0);
var isValid = void 0;
if (entityKey) {
var type = content.getEntity(entityKey).getType();
isValid = whitelist.some(function (t) {
return t.type === type;
});
} else {
isValid = false;
}
return isValid;
};
var filteredBlocks = blockMap.filter(isValidAtomicBlock);
if (filteredBlocks.size !== blockMap.size) {
return content.merge({
blockMap: filteredBlocks
});
}
return editorState;
return content;
};
/**
* Removes all styles that use unavailable types.
* Removes blocks that have a non-zero depth, and aren't list items.
* Happens with Apple Pages inserting `unstyled` items between list items.
*/
var filterInlineStyle = function filterInlineStyle(editorState, enabledTypes) {
var content = editorState.getCurrentContent();
var removeInvalidDepthBlocks = function removeInvalidDepthBlocks(content) {
var blockMap = content.getBlockMap();
var blocks = blockMap.map(function (block) {
var altered = false;
var isValidDepthBlock = function isValidDepthBlock(block) {
var isListBlock = [UNORDERED_LIST_ITEM, ORDERED_LIST_ITEM].includes(block.getType());
var chars = block.getCharacterList().map(function (char) {
var newChar = char;
return isListBlock || block.getDepth() === 0;
};
char.getStyle().filter(function (type) {
return !enabledTypes.includes(type);
}).forEach(function (type) {
altered = true;
newChar = draftJs.CharacterMetadata.removeStyle(newChar, type);
});
var filteredBlocks = blockMap.filter(isValidDepthBlock);
return newChar;
if (filteredBlocks.size !== blockMap.size) {
return content.merge({
blockMap: filteredBlocks
});
}
return altered ? block.set("characterList", chars) : block;
return content;
};
/**
* Resets the depth of all the content to at most max.
*/
var limitBlockDepth = function limitBlockDepth(max, content) {
var blockMap = content.getBlockMap();
var changedBlocks = blockMap.filter(function (block) {
return block.getDepth() > max;
}).map(function (block) {
return block.set("depth", max);
});
return draftJs.EditorState.set(editorState, {
currentContent: content.merge({
blockMap: blockMap.merge(blocks)
})
return changedBlocks.size === 0 ? content : content.merge({
blockMap: blockMap.merge(changedBlocks)
});

@@ -131,18 +164,34 @@ };

/**
* Resets atomic blocks to unstyled based on which entity types are enabled,
* and also normalises block text to a single "space" character.
* Removes all block types not present in the whitelist.
*/
var resetAtomicBlocks = function resetAtomicBlocks(editorState, enabledTypes) {
var content = editorState.getCurrentContent();
var filterBlockTypes = function filterBlockTypes(whitelist, content) {
var blockMap = content.getBlockMap();
var blocks = blockMap;
var normalisedBlocks = blocks.filter(function (block) {
return block.getType() === ATOMIC && (block.getText() !== " " || block.getInlineStyleAt(0).size !== 0);
var changedBlocks = blockMap.filter(function (block) {
return !whitelist.includes(block.getType());
}).map(function (block) {
// Retain only the first character, and remove all of its styles.
var chars = block.getCharacterList().slice(0, 1).map(function (char) {
return block.set("type", UNSTYLED);
});
return changedBlocks.size === 0 ? content : content.merge({
blockMap: blockMap.merge(changedBlocks)
});
};
/**
* Removes all styles not present in the whitelist.
*/
var filterInlineStyles = function filterInlineStyles(whitelist, content) {
var blockMap = content.getBlockMap();
var blocks = blockMap.map(function (block) {
var altered = false;
var chars = block.getCharacterList().map(function (char) {
var newChar = char;
char.getStyle().forEach(function (type) {
char.getStyle().filter(function (type) {
return !whitelist.includes(type);
}).forEach(function (type) {
altered = true;
newChar = draftJs.CharacterMetadata.removeStyle(newChar, type);

@@ -153,37 +202,8 @@ });

});
return block.merge({
text: " ",
characterList: chars
});
});
if (normalisedBlocks.size !== 0) {
blocks = blockMap.merge(normalisedBlocks);
}
var resetBlocks = blocks.filter(function (block) {
return block.getType() === ATOMIC;
}).filter(function (block) {
var entityKey = block.getEntityAt(0);
var shouldReset = false;
if (entityKey) {
var entityType = content.getEntity(entityKey).getType();
shouldReset = !enabledTypes.includes(entityType);
}
return shouldReset;
}).map(function (block) {
return block.set("type", UNSTYLED);
return altered ? block.set("characterList", chars) : block;
});
if (resetBlocks.size !== 0) {
blocks = blockMap.merge(resetBlocks);
}
return draftJs.EditorState.set(editorState, {
currentContent: content.merge({
blockMap: blockMap.merge(blocks)
})
return content.merge({
blockMap: blockMap.merge(blocks)
});

@@ -193,10 +213,11 @@ };

/**
* Reset all entity types (images, links, documents, embeds) that are unavailable.
* Filters entity ranges (where entities are applied on text) based on the result of
* the callback function. Returning true keeps the entity range, false removes it.
* Draft.js automatically removes entities if they are not applied on any text.
*/
var filterEntityType = function filterEntityType(editorState, enabledTypes) {
var content = editorState.getCurrentContent();
var filterEntityRanges = function filterEntityRanges(filterFn, content) {
var blockMap = content.getBlockMap();
/**
* Removes entities from the character list if the character entity isn't enabled.
* Removes entities from the character list if the entity isn't enabled.
* Also removes image entities placed outside of atomic blocks, which can happen

@@ -209,3 +230,2 @@ * on paste.

var blocks = blockMap.map(function (block) {
var blockType = block.getType();
var altered = false;

@@ -217,15 +237,5 @@

if (entityKey) {
var entityType = content.getEntity(entityKey).getType();
var shouldFilter = !enabledTypes.includes(entityType);
/**
* Special case for images. They should only be in atomic blocks.
* This only removes the image entity, not the camera emoji (📷)
* that Draft.js inserts.
* If we want to remove this in the future, consider that:
* - It needs to be removed in the block text, where it's 2 chars / 1 code point.
* - The corresponding CharacterMetadata needs to be removed too, and it's 2 instances
*/
var shouldFilterImage = entityType === IMAGE && blockType !== ATOMIC;
var shouldRemove = !filterFn(content, entityKey, block);
if (shouldFilter || shouldFilterImage) {
if (shouldRemove) {
altered = true;

@@ -242,6 +252,4 @@ return draftJs.CharacterMetadata.applyEntity(char, null);

return draftJs.EditorState.set(editorState, {
currentContent: content.merge({
blockMap: blockMap.merge(blocks)
})
return content.merge({
blockMap: blockMap.merge(blocks)
});

@@ -251,33 +259,168 @@ };

/**
* Applies whitelist and blacklist operations to the editor content,
* so the resulting editor state is shaped according to Draftail
* expectations and configuration.
* As of now, this doesn't filter line breaks if they aren't disabled
* as Draft.js does not preserve this type of whitespace on paste anyway.
* Keeps all entity types (images, links, documents, embeds) that are enabled.
*/
var filterEditorState = function filterEditorState(editorState, maxListNesting, enableHorizontalRule, blockTypes, inlineStyles, entityTypes) {
var nextEditorState = editorState;
var enabledBlockTypes = blockTypes.concat([
// Always enabled in a Draftail editor.
UNSTYLED,
// Filtered depending on enabled entity types.
ATOMIC]);
var enabledEntityTypes = entityTypes;
var shouldKeepEntityType = function shouldKeepEntityType(whitelist, type) {
return whitelist.some(function (e) {
return e.type === type;
});
};
if (enableHorizontalRule) {
enabledEntityTypes.push(HORIZONTAL_RULE);
/**
* Removes invalid images – they should only be in atomic blocks.
* This only removes the image entity, not the camera emoji (📷) that Draft.js inserts.
*/
var shouldRemoveImageEntity = function shouldRemoveImageEntity(entityType, blockType) {
return entityType === IMAGE && blockType !== ATOMIC;
};
/**
* Filters entities based on the data they contain.
*/
var shouldKeepEntityByAttribute = function shouldKeepEntityByAttribute(entityTypes, entityType, data) {
var config = entityTypes.find(function (t) {
return t.type === entityType;
});
var whitelist = config ? config.whitelist : null;
// If no whitelist is defined, the filter keeps the entity.
if (!whitelist) {
return true;
}
// At the moment the list is hard-coded. In the future, the idea
// would be to have separate config for block entities and inline entities.
nextEditorState = preserveAtomicBlocks(nextEditorState, [HORIZONTAL_RULE, IMAGE]);
nextEditorState = resetBlockDepth(nextEditorState, maxListNesting);
nextEditorState = resetBlockType(nextEditorState, enabledBlockTypes);
nextEditorState = filterInlineStyle(nextEditorState, inlineStyles);
nextEditorState = resetAtomicBlocks(nextEditorState, enabledEntityTypes);
nextEditorState = filterEntityType(nextEditorState, enabledEntityTypes);
var isValid = Object.keys(whitelist).every(function (attr) {
var regex = new RegExp(whitelist[attr]);
return regex.test(data[attr]);
});
return nextEditorState;
return isValid;
};
/**
* Filters data on an entity to only retain what is whitelisted.
*/
var filterEntityData = function filterEntityData(entityTypes, content) {
var newContent = content;
var entities = {};
newContent.getBlockMap().forEach(function (block) {
block.findEntityRanges(function (char) {
var entityKey = char.getEntity();
if (entityKey) {
var entity = newContent.getEntity(entityKey);
entities[entityKey] = entity;
}
});
});
Object.keys(entities).forEach(function (key) {
var entity = entities[key];
var data = entity.getData();
var config = entityTypes.find(function (t) {
return t.type === entity.getType();
});
var whitelist = config ? config.attributes : null;
// If no whitelist is defined, keep all of the data.
if (!whitelist) {
return data;
}
var newData = whitelist.reduce(function (attrs, attr) {
// We do not want to include undefined values if there is no data.
if (data.hasOwnProperty(attr)) {
attrs[attr] = data[attr];
}
return attrs;
}, {});
newContent = newContent.replaceEntityData(key, newData);
});
return newContent;
};
/**
* Replaces the given characters by their equivalent length of spaces, in all blocks.
*/
var replaceTextBySpaces = function replaceTextBySpaces(characters, content) {
var blockMap = content.getBlockMap();
var blocks = blockMap.map(function (block) {
var text = block.getText();
// Only replaces the character(s) with as many spaces as their length,
// so that style and entity ranges are left undisturbed.
// If we want to completely remove the character, we also need to filter
// the corresponding CharacterMetadata entities.
var newText = characters.reduce(function (txt, char) {
return txt.replace(new RegExp(char, "g"), " ".repeat(char.length));
}, text);
return text !== newText ? block.set("text", newText) : block;
});
return content.merge({
blockMap: blockMap.merge(blocks)
});
};
/**
* Applies whitelist and blacklist operations to the editor content,
* to enforce it's shaped according to the options.
*/
var filterEditorState = function filterEditorState(_ref, editorState) {
var blocks = _ref.blocks,
styles = _ref.styles,
entities = _ref.entities,
maxNesting = _ref.maxNesting,
whitespacedCharacters = _ref.whitespacedCharacters;
var shouldKeepEntityRange = function shouldKeepEntityRange(content, entityKey, block) {
var entity = content.getEntity(entityKey);
var entityData = entity.getData();
var entityType = entity.getType();
var blockType = block.getType();
return shouldKeepEntityType(entities, entityType) && shouldKeepEntityByAttribute(entities, entityType, entityData) && !shouldRemoveImageEntity(entityType, blockType);
};
// Order matters. Some filters may need the information filtered out by others.
var filters = [
// 1. clean up blocks.
removeInvalidDepthBlocks, limitBlockDepth.bind(null, maxNesting),
// 2. reset styles and blocks.
filterInlineStyles.bind(null, styles),
// Add block types that are always enabled in Draft.js.
filterBlockTypes.bind(null, blocks.concat([UNSTYLED, ATOMIC])),
// 4. Process atomic blocks.
preserveAtomicBlocks, resetAtomicBlocks,
// 5. Remove entity ranges (and linked entities)
filterEntityRanges.bind(null, shouldKeepEntityRange),
// 6. Remove/filter entity-related matters.
removeInvalidAtomicBlocks.bind(null, entities), filterEntityData.bind(null, entities), replaceTextBySpaces.bind(null, whitespacedCharacters)];
var content = editorState.getCurrentContent();
var nextContent = filters.reduce(function (c, filter) {
return filter(c);
}, content);
return nextContent === content ? editorState : draftJs.EditorState.set(editorState, {
currentContent: nextContent
});
};
exports.preserveAtomicBlocks = preserveAtomicBlocks;
exports.resetAtomicBlocks = resetAtomicBlocks;
exports.removeInvalidAtomicBlocks = removeInvalidAtomicBlocks;
exports.removeInvalidDepthBlocks = removeInvalidDepthBlocks;
exports.limitBlockDepth = limitBlockDepth;
exports.filterBlockTypes = filterBlockTypes;
exports.filterInlineStyles = filterInlineStyles;
exports.filterEntityRanges = filterEntityRanges;
exports.shouldKeepEntityType = shouldKeepEntityType;
exports.shouldRemoveImageEntity = shouldRemoveImageEntity;
exports.shouldKeepEntityByAttribute = shouldKeepEntityByAttribute;
exports.filterEntityData = filterEntityData;
exports.replaceTextBySpaces = replaceTextBySpaces;
exports.filterEditorState = filterEditorState;
import { CharacterMetadata, EditorState } from 'draft-js';
var ATOMIC = "atomic";
var UNSTYLED = "unstyled";
var ATOMIC = "atomic";
var UNORDERED_LIST_ITEM = "unordered-list-item";
var ORDERED_LIST_ITEM = "ordered-list-item";
var IMAGE = "IMAGE";
var HORIZONTAL_RULE = "HORIZONTAL_RULE";
/**
* Helper functions to filter/whitelist specific formatting.
* Meant to be used when pasting unconstrained content.
*/
/**
* Makes atomic blocks where they would be required for a block-level entity
* Creates atomic blocks where they would be required for a block-level entity
* to work correctly, when such an entity exists.

@@ -19,10 +16,14 @@ * Note: at the moment, this is only useful for IMAGE entities that Draft.js

*/
var preserveAtomicBlocks = function preserveAtomicBlocks(editorState, entityTypes) {
var content = editorState.getCurrentContent();
var preserveAtomicBlocks = function preserveAtomicBlocks(content) {
var blockMap = content.getBlockMap();
var perservedBlocks = blockMap.filter(function (block) {
var text = block.getText();
var entityKey = block.getEntityAt(0);
// Use the ES6 way of counting string length to account for unicode symbols.
// See https://mathiasbynens.be/notes/javascript-unicode.
var isOneSymbol = Array.from(text).length === 1;
var shouldPreserve = entityKey && isOneSymbol && ["📷", " "].includes(text);
return entityKey && entityTypes.includes(content.getEntity(entityKey).getType());
return shouldPreserve;
}).map(function (block) {

@@ -33,90 +34,122 @@ return block.set("type", ATOMIC);

if (perservedBlocks.size !== 0) {
return EditorState.set(editorState, {
currentContent: content.merge({
blockMap: blockMap.merge(perservedBlocks)
})
return content.merge({
blockMap: blockMap.merge(perservedBlocks)
});
}
return editorState;
return content;
};
/**
* Resets the depth of all the content to at most maxListNesting.
* Resets atomic blocks to have a single-space char and no styles.
*/
var resetBlockDepth = function resetBlockDepth(editorState, maxListNesting) {
var content = editorState.getCurrentContent();
var resetAtomicBlocks = function resetAtomicBlocks(content) {
var blockMap = content.getBlockMap();
var blocks = blockMap;
var changedBlocks = blockMap.filter(function (block) {
return block.getDepth() > maxListNesting;
var normalisedBlocks = blocks.filter(function (block) {
return block.getType() === ATOMIC && (block.getText() !== " " || block.getInlineStyleAt(0).size !== 0);
}).map(function (block) {
return block.set("depth", maxListNesting);
// Retain only the first character, and remove all of its styles.
var chars = block.getCharacterList().slice(0, 1).map(function (char) {
var newChar = char;
char.getStyle().forEach(function (type) {
newChar = CharacterMetadata.removeStyle(newChar, type);
});
return newChar;
});
return block.merge({
text: " ",
characterList: chars
});
});
if (changedBlocks.size !== 0) {
return EditorState.set(editorState, {
currentContent: content.merge({
blockMap: blockMap.merge(changedBlocks)
})
});
if (normalisedBlocks.size !== 0) {
blocks = blocks.merge(normalisedBlocks);
}
return editorState;
return content.merge({
blockMap: blocks
});
};
/**
* Resets all blocks that use unavailable types to unstyled.
* Removes atomic blocks for which the entity isn't whitelisted.
*/
var resetBlockType = function resetBlockType(editorState, enabledTypes) {
var content = editorState.getCurrentContent();
var removeInvalidAtomicBlocks = function removeInvalidAtomicBlocks(whitelist, content) {
var blockMap = content.getBlockMap();
var changedBlocks = blockMap.filter(function (block) {
return !enabledTypes.includes(block.getType());
}).map(function (block) {
return block.set("type", UNSTYLED);
});
var isValidAtomicBlock = function isValidAtomicBlock(block) {
if (block.getType() !== ATOMIC) {
return true;
}
if (changedBlocks.size !== 0) {
return EditorState.set(editorState, {
currentContent: content.merge({
blockMap: blockMap.merge(changedBlocks)
})
var entityKey = block.getEntityAt(0);
var isValid = void 0;
if (entityKey) {
var type = content.getEntity(entityKey).getType();
isValid = whitelist.some(function (t) {
return t.type === type;
});
} else {
isValid = false;
}
return isValid;
};
var filteredBlocks = blockMap.filter(isValidAtomicBlock);
if (filteredBlocks.size !== blockMap.size) {
return content.merge({
blockMap: filteredBlocks
});
}
return editorState;
return content;
};
/**
* Removes all styles that use unavailable types.
* Removes blocks that have a non-zero depth, and aren't list items.
* Happens with Apple Pages inserting `unstyled` items between list items.
*/
var filterInlineStyle = function filterInlineStyle(editorState, enabledTypes) {
var content = editorState.getCurrentContent();
var removeInvalidDepthBlocks = function removeInvalidDepthBlocks(content) {
var blockMap = content.getBlockMap();
var blocks = blockMap.map(function (block) {
var altered = false;
var isValidDepthBlock = function isValidDepthBlock(block) {
var isListBlock = [UNORDERED_LIST_ITEM, ORDERED_LIST_ITEM].includes(block.getType());
var chars = block.getCharacterList().map(function (char) {
var newChar = char;
return isListBlock || block.getDepth() === 0;
};
char.getStyle().filter(function (type) {
return !enabledTypes.includes(type);
}).forEach(function (type) {
altered = true;
newChar = CharacterMetadata.removeStyle(newChar, type);
});
var filteredBlocks = blockMap.filter(isValidDepthBlock);
return newChar;
if (filteredBlocks.size !== blockMap.size) {
return content.merge({
blockMap: filteredBlocks
});
}
return altered ? block.set("characterList", chars) : block;
return content;
};
/**
* Resets the depth of all the content to at most max.
*/
var limitBlockDepth = function limitBlockDepth(max, content) {
var blockMap = content.getBlockMap();
var changedBlocks = blockMap.filter(function (block) {
return block.getDepth() > max;
}).map(function (block) {
return block.set("depth", max);
});
return EditorState.set(editorState, {
currentContent: content.merge({
blockMap: blockMap.merge(blocks)
})
return changedBlocks.size === 0 ? content : content.merge({
blockMap: blockMap.merge(changedBlocks)
});

@@ -126,18 +159,34 @@ };

/**
* Resets atomic blocks to unstyled based on which entity types are enabled,
* and also normalises block text to a single "space" character.
* Removes all block types not present in the whitelist.
*/
var resetAtomicBlocks = function resetAtomicBlocks(editorState, enabledTypes) {
var content = editorState.getCurrentContent();
var filterBlockTypes = function filterBlockTypes(whitelist, content) {
var blockMap = content.getBlockMap();
var blocks = blockMap;
var normalisedBlocks = blocks.filter(function (block) {
return block.getType() === ATOMIC && (block.getText() !== " " || block.getInlineStyleAt(0).size !== 0);
var changedBlocks = blockMap.filter(function (block) {
return !whitelist.includes(block.getType());
}).map(function (block) {
// Retain only the first character, and remove all of its styles.
var chars = block.getCharacterList().slice(0, 1).map(function (char) {
return block.set("type", UNSTYLED);
});
return changedBlocks.size === 0 ? content : content.merge({
blockMap: blockMap.merge(changedBlocks)
});
};
/**
* Removes all styles not present in the whitelist.
*/
var filterInlineStyles = function filterInlineStyles(whitelist, content) {
var blockMap = content.getBlockMap();
var blocks = blockMap.map(function (block) {
var altered = false;
var chars = block.getCharacterList().map(function (char) {
var newChar = char;
char.getStyle().forEach(function (type) {
char.getStyle().filter(function (type) {
return !whitelist.includes(type);
}).forEach(function (type) {
altered = true;
newChar = CharacterMetadata.removeStyle(newChar, type);

@@ -148,37 +197,8 @@ });

});
return block.merge({
text: " ",
characterList: chars
});
});
if (normalisedBlocks.size !== 0) {
blocks = blockMap.merge(normalisedBlocks);
}
var resetBlocks = blocks.filter(function (block) {
return block.getType() === ATOMIC;
}).filter(function (block) {
var entityKey = block.getEntityAt(0);
var shouldReset = false;
if (entityKey) {
var entityType = content.getEntity(entityKey).getType();
shouldReset = !enabledTypes.includes(entityType);
}
return shouldReset;
}).map(function (block) {
return block.set("type", UNSTYLED);
return altered ? block.set("characterList", chars) : block;
});
if (resetBlocks.size !== 0) {
blocks = blockMap.merge(resetBlocks);
}
return EditorState.set(editorState, {
currentContent: content.merge({
blockMap: blockMap.merge(blocks)
})
return content.merge({
blockMap: blockMap.merge(blocks)
});

@@ -188,10 +208,11 @@ };

/**
* Reset all entity types (images, links, documents, embeds) that are unavailable.
* Filters entity ranges (where entities are applied on text) based on the result of
* the callback function. Returning true keeps the entity range, false removes it.
* Draft.js automatically removes entities if they are not applied on any text.
*/
var filterEntityType = function filterEntityType(editorState, enabledTypes) {
var content = editorState.getCurrentContent();
var filterEntityRanges = function filterEntityRanges(filterFn, content) {
var blockMap = content.getBlockMap();
/**
* Removes entities from the character list if the character entity isn't enabled.
* Removes entities from the character list if the entity isn't enabled.
* Also removes image entities placed outside of atomic blocks, which can happen

@@ -204,3 +225,2 @@ * on paste.

var blocks = blockMap.map(function (block) {
var blockType = block.getType();
var altered = false;

@@ -212,15 +232,5 @@

if (entityKey) {
var entityType = content.getEntity(entityKey).getType();
var shouldFilter = !enabledTypes.includes(entityType);
/**
* Special case for images. They should only be in atomic blocks.
* This only removes the image entity, not the camera emoji (📷)
* that Draft.js inserts.
* If we want to remove this in the future, consider that:
* - It needs to be removed in the block text, where it's 2 chars / 1 code point.
* - The corresponding CharacterMetadata needs to be removed too, and it's 2 instances
*/
var shouldFilterImage = entityType === IMAGE && blockType !== ATOMIC;
var shouldRemove = !filterFn(content, entityKey, block);
if (shouldFilter || shouldFilterImage) {
if (shouldRemove) {
altered = true;

@@ -237,6 +247,4 @@ return CharacterMetadata.applyEntity(char, null);

return EditorState.set(editorState, {
currentContent: content.merge({
blockMap: blockMap.merge(blocks)
})
return content.merge({
blockMap: blockMap.merge(blocks)
});

@@ -246,33 +254,155 @@ };

/**
* Applies whitelist and blacklist operations to the editor content,
* so the resulting editor state is shaped according to Draftail
* expectations and configuration.
* As of now, this doesn't filter line breaks if they aren't disabled
* as Draft.js does not preserve this type of whitespace on paste anyway.
* Keeps all entity types (images, links, documents, embeds) that are enabled.
*/
var filterEditorState = function filterEditorState(editorState, maxListNesting, enableHorizontalRule, blockTypes, inlineStyles, entityTypes) {
var nextEditorState = editorState;
var enabledBlockTypes = blockTypes.concat([
// Always enabled in a Draftail editor.
UNSTYLED,
// Filtered depending on enabled entity types.
ATOMIC]);
var enabledEntityTypes = entityTypes;
var shouldKeepEntityType = function shouldKeepEntityType(whitelist, type) {
return whitelist.some(function (e) {
return e.type === type;
});
};
if (enableHorizontalRule) {
enabledEntityTypes.push(HORIZONTAL_RULE);
/**
* Removes invalid images – they should only be in atomic blocks.
* This only removes the image entity, not the camera emoji (📷) that Draft.js inserts.
*/
var shouldRemoveImageEntity = function shouldRemoveImageEntity(entityType, blockType) {
return entityType === IMAGE && blockType !== ATOMIC;
};
/**
* Filters entities based on the data they contain.
*/
var shouldKeepEntityByAttribute = function shouldKeepEntityByAttribute(entityTypes, entityType, data) {
var config = entityTypes.find(function (t) {
return t.type === entityType;
});
var whitelist = config ? config.whitelist : null;
// If no whitelist is defined, the filter keeps the entity.
if (!whitelist) {
return true;
}
// At the moment the list is hard-coded. In the future, the idea
// would be to have separate config for block entities and inline entities.
nextEditorState = preserveAtomicBlocks(nextEditorState, [HORIZONTAL_RULE, IMAGE]);
nextEditorState = resetBlockDepth(nextEditorState, maxListNesting);
nextEditorState = resetBlockType(nextEditorState, enabledBlockTypes);
nextEditorState = filterInlineStyle(nextEditorState, inlineStyles);
nextEditorState = resetAtomicBlocks(nextEditorState, enabledEntityTypes);
nextEditorState = filterEntityType(nextEditorState, enabledEntityTypes);
var isValid = Object.keys(whitelist).every(function (attr) {
var regex = new RegExp(whitelist[attr]);
return regex.test(data[attr]);
});
return nextEditorState;
return isValid;
};
export { filterEditorState };
/**
* Filters data on an entity to only retain what is whitelisted.
*/
var filterEntityData = function filterEntityData(entityTypes, content) {
var newContent = content;
var entities = {};
newContent.getBlockMap().forEach(function (block) {
block.findEntityRanges(function (char) {
var entityKey = char.getEntity();
if (entityKey) {
var entity = newContent.getEntity(entityKey);
entities[entityKey] = entity;
}
});
});
Object.keys(entities).forEach(function (key) {
var entity = entities[key];
var data = entity.getData();
var config = entityTypes.find(function (t) {
return t.type === entity.getType();
});
var whitelist = config ? config.attributes : null;
// If no whitelist is defined, keep all of the data.
if (!whitelist) {
return data;
}
var newData = whitelist.reduce(function (attrs, attr) {
// We do not want to include undefined values if there is no data.
if (data.hasOwnProperty(attr)) {
attrs[attr] = data[attr];
}
return attrs;
}, {});
newContent = newContent.replaceEntityData(key, newData);
});
return newContent;
};
/**
* Replaces the given characters by their equivalent length of spaces, in all blocks.
*/
var replaceTextBySpaces = function replaceTextBySpaces(characters, content) {
var blockMap = content.getBlockMap();
var blocks = blockMap.map(function (block) {
var text = block.getText();
// Only replaces the character(s) with as many spaces as their length,
// so that style and entity ranges are left undisturbed.
// If we want to completely remove the character, we also need to filter
// the corresponding CharacterMetadata entities.
var newText = characters.reduce(function (txt, char) {
return txt.replace(new RegExp(char, "g"), " ".repeat(char.length));
}, text);
return text !== newText ? block.set("text", newText) : block;
});
return content.merge({
blockMap: blockMap.merge(blocks)
});
};
/**
* Applies whitelist and blacklist operations to the editor content,
* to enforce it's shaped according to the options.
*/
var filterEditorState = function filterEditorState(_ref, editorState) {
var blocks = _ref.blocks,
styles = _ref.styles,
entities = _ref.entities,
maxNesting = _ref.maxNesting,
whitespacedCharacters = _ref.whitespacedCharacters;
var shouldKeepEntityRange = function shouldKeepEntityRange(content, entityKey, block) {
var entity = content.getEntity(entityKey);
var entityData = entity.getData();
var entityType = entity.getType();
var blockType = block.getType();
return shouldKeepEntityType(entities, entityType) && shouldKeepEntityByAttribute(entities, entityType, entityData) && !shouldRemoveImageEntity(entityType, blockType);
};
// Order matters. Some filters may need the information filtered out by others.
var filters = [
// 1. clean up blocks.
removeInvalidDepthBlocks, limitBlockDepth.bind(null, maxNesting),
// 2. reset styles and blocks.
filterInlineStyles.bind(null, styles),
// Add block types that are always enabled in Draft.js.
filterBlockTypes.bind(null, blocks.concat([UNSTYLED, ATOMIC])),
// 4. Process atomic blocks.
preserveAtomicBlocks, resetAtomicBlocks,
// 5. Remove entity ranges (and linked entities)
filterEntityRanges.bind(null, shouldKeepEntityRange),
// 6. Remove/filter entity-related matters.
removeInvalidAtomicBlocks.bind(null, entities), filterEntityData.bind(null, entities), replaceTextBySpaces.bind(null, whitespacedCharacters)];
var content = editorState.getCurrentContent();
var nextContent = filters.reduce(function (c, filter) {
return filter(c);
}, content);
return nextContent === content ? editorState : EditorState.set(editorState, {
currentContent: nextContent
});
};
export { preserveAtomicBlocks, resetAtomicBlocks, removeInvalidAtomicBlocks, removeInvalidDepthBlocks, limitBlockDepth, filterBlockTypes, filterInlineStyles, filterEntityRanges, shouldKeepEntityType, shouldRemoveImageEntity, shouldKeepEntityByAttribute, filterEntityData, replaceTextBySpaces, filterEditorState };
{
"name": "draftjs-filters",
"version": "0.2.2",
"description": "Filter Draft.js content when copy-pasting rich text into the editor",
"version": "0.3.0",
"description": "Filter Draft.js content to preserve only the formatting you allow",
"author": "Thibaud Colas",

@@ -53,49 +53,15 @@ "license": "MIT",

},
"release": {
"branch": "master",
"verifyConditions": [
"@semantic-release/npm",
"@semantic-release/github"
],
"getLastRelease": "@semantic-release/npm",
"analyzeCommits": {
"preset": "angular",
"releaseRules": [
{"type": "docs", "scope": "README", "release": "patch"},
{"type": "refactor", "release": "patch"}
]
},
"verifyRelease": [],
"generateNotes": {
"preset": "angular"
},
"publish": [
"@semantic-release/npm",
{
"path": "@semantic-release/github",
"assets": [
"package.json",
{
"path": "dist/draftjs-filters.cjs.js",
"label": "CommonJS"
},
{
"path": "dist/draftjs-filters.esm.js",
"label": "ES modules"
}
]
}
]
},
"publishConfig": {
"tag": "next"
},
"devDependencies": {
"@commitlint/cli": "^5.2.8",
"@commitlint/config-conventional": "^5.2.3",
"core-js": "^2.5.3",
"danger": "^3.0.3",
"danger-plugin-jest": "^1.1.0",
"draft-js": "^0.10.4",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"enzyme-to-json": "^3.3.0",
"flow-bin": "^0.61.0",
"immutable": "^3.7.6",
"immutable": "~3.7.6",
"normalize.css": "^7.0.0",
"prettier": "^1.9.2",

@@ -106,11 +72,12 @@ "prismjs": "^1.9.0",

"react-scripts": "1.0.17",
"react-test-renderer": "^16.2.0",
"rollup": "^0.53.3",
"rollup-plugin-babel": "^3.0.3",
"semantic-release": "^11.0.2",
"source-map-explorer": "^1.5.0"
"snapshot-diff": "^0.2.2",
"source-map-explorer": "^1.5.0",
"standard-version": "^4.3.0"
},
"dependencies": {},
"peerDependencies": {
"draft-js": "^0.10.4",
"immutable": "~3.7.4"
"draft-js": "^0.10.4"
},

@@ -123,7 +90,6 @@ "scripts": {

"danger": "danger ci --verbose",
"semantic-release": "semantic-release",
"test": "CI=true react-scripts test --env=jsdom",
"test:coverage": "CI=true react-scripts test --env=jsdom --coverage",
"release": "standard-version --no-verify",
"test": "npm run test:coverage -s",
"test:coverage": "react-scripts test --env=jsdom --coverage",
"test:watch": "react-scripts test --env=jsdom",
"test:watch:coverage": "react-scripts test --env=jsdom --coverage",
"linter:css": "prettier --list-different",

@@ -130,0 +96,0 @@ "linter:md": "prettier --list-different",

@@ -1,7 +0,9 @@

# [Draft.js filters](https://thibaudcolas.github.io/draftjs-filters/) [![npm](https://img.shields.io/npm/v/draftjs-filters.svg?style=flat-square)](https://www.npmjs.com/package/draftjs-filters) [![Build Status](https://travis-ci.org/thibaudcolas/draftjs-filters.svg?branch=master)](https://travis-ci.org/thibaudcolas/draftjs-filters) [![Coverage Status](https://coveralls.io/repos/github/thibaudcolas/draftjs-filters/badge.svg)](https://coveralls.io/github/thibaudcolas/draftjs-filters)
# [Draft.js filters](https://thibaudcolas.github.io/draftjs-filters/) [![npm](https://img.shields.io/npm/v/draftjs-filters.svg?style=flat-square)](https://www.npmjs.com/package/draftjs-filters) [![Build Status](https://travis-ci.org/thibaudcolas/draftjs-filters.svg?branch=master)](https://travis-ci.org/thibaudcolas/draftjs-filters) [![Coverage Status](https://coveralls.io/repos/github/thibaudcolas/draftjs-filters/badge.svg)](https://coveralls.io/github/thibaudcolas/draftjs-filters) [<img src="https://cdn.rawgit.com/springload/awesome-wagtail/ac912cc661a7099813f90545adffa6bb3e75216c/logo.svg" width="104" align="right" alt="Wagtail">](https://wagtail.io/)
> Filter [Draft.js](https://facebook.github.io/draft-js/) content when copy-pasting rich text into the editor. Initially made for [Draftail](https://github.com/springload/draftail).
> Filter [Draft.js](https://facebook.github.io/draft-js/) content to preserve only the formatting you allow. Built for [Draftail](https://github.com/springload/draftail) and [Wagtail](https://github.com/wagtail/wagtail).
Check out the [online demo](https://thibaudcolas.github.io/draftjs-filters).
[![Screenshot of Microsoft Word with tens of toolbars activated](https://thibaudcolas.github.io/draftjs-filters/word-toolbars-overload.jpg)](https://thibaudcolas.github.io/draftjs-filters)
The main use case is to filter out disallowed formattings when copy-pasting rich text into an editor, for example from Word or Google Docs. Check out the [online demo](https://thibaudcolas.github.io/draftjs-filters)!
## Using the filters

@@ -15,3 +17,3 @@

WIP – Then, import the filters' entry point and use it in your `<Editor>`'s `onChange` function:
Then, in your editor import `filterEditorState` and call it in the Draft.js `onChange` handler. This function takes two parameters: the filtering configuration, and the `editorState`.

@@ -22,11 +24,2 @@ ```js

function onChange(nextEditorState) {
const {
stateSaveInterval,
maxListNesting,
enableHorizontalRule,
stripPastedStyles,
blockTypes,
inlineStyles,
entityTypes,
} = this.props
const { editorState } = this.state

@@ -37,3 +30,2 @@ const content = editorState.getCurrentContent()

nextContent !== content &&
!stripPastedStyles &&
nextEditorState.getLastChangeType() === "insert-fragment"

@@ -44,8 +36,22 @@

filteredEditorState = filterEditorState(
nextEditorState,
maxListNesting,
enableHorizontalRule,
blockTypes,
inlineStyles,
entityTypes,
{
blocks: ["header-two", "header-three", "unordered-list-item"],
styles: ["BOLD"],
entities: [
{
type: "IMAGE",
attributes: ["src"],
whitelist: {
src: "^http",
},
},
{
type: "LINK",
attributes: ["url"],
},
],
maxNesting: 1,
whitespacedCharacters: ["\n", "\t", "📷"],
},
filteredEditorState,
)

@@ -58,6 +64,156 @@ }

Here are the available options:
```jsx
// Whitelist of allowed block types. unstyled and atomic are always included.
blocks: Array<DraftBlockType>,
// Whitelist of allowed inline styles.
styles: Array<string>,
// Whitelist of allowed entities.
entities: Array<{
// Entity type, eg. "LINK"
type: string,
// Allowed attributes. Other attributes will be removed.
attributes: Array<string>,
// Refine which entities are kept by whitelisting acceptable values with regular expression patterns.
whitelist: Object,
}>,
// Maximum amount of depth for lists (0 = no nesting).
maxNesting: number,
// Characters to replace with whitespace.
whitespacedCharacters: Array<string>,
```
### Advanced usage
`filterEditorState` isn't very flexible. If you want more control over the filtering, simply compose your own filter function with the other single-purpose utilities. The Draft.js filters are published as ES6 modules using [Rollup](https://rollupjs.org/) – module bundlers like Rollup and Webpack will tree shake (remove) the unused functions so you only bundle the code you use.
```jsx
/**
* Creates atomic blocks where they would be required for a block-level entity
* to work correctly, when such an entity exists.
* Note: at the moment, this is only useful for IMAGE entities that Draft.js
* injects on arbitrary blocks on paste.
*/
preserveAtomicBlocks((content: ContentState))
/**
* Resets atomic blocks to have a single-space char and no styles.
*/
resetAtomicBlocks((content: ContentState))
/**
* Removes atomic blocks for which the entity isn't whitelisted.
*/
removeInvalidAtomicBlocks((whitelist: Array<Object>), (content: ContentState))
/**
* Removes blocks that have a non-zero depth, and aren't list items.
* Happens with Apple Pages inserting `unstyled` items between list items.
*/
removeInvalidDepthBlocks((content: ContentState))
/**
* Resets the depth of all the content to at most max.
*/
limitBlockDepth((max: number), (content: ContentState))
/**
* Removes all block types not present in the whitelist.
*/
filterBlockTypes((whitelist: Array<DraftBlockType>), (content: ContentState))
/**
* Removes all styles not present in the whitelist.
*/
filterInlineStyles((whitelist: Array<string>), (content: ContentState))
/**
* Filters entity ranges (where entities are applied on text) based on the result of
* the callback function. Returning true keeps the entity range, false removes it.
* Draft.js automatically removes entities if they are not applied on any text.
*/
filterEntityRanges(
(filterFn: (
content: ContentState,
entityKey: string,
block: ContentBlock,
) => boolean),
(content: ContentState),
)
/**
* Keeps all entity types (images, links, documents, embeds) that are enabled.
*/
shouldKeepEntityType((whitelist: Array<Object>), (type: string))
/**
* Removes invalid images – they should only be in atomic blocks.
* This only removes the image entity, not the camera emoji (📷) that Draft.js inserts.
* If we want to remove this in the future, consider that:
* - It needs to be removed in the block text, where it's 2 chars / 1 code point.
* - The corresponding CharacterMetadata needs to be removed too, and it's 2 instances
*/
shouldRemoveImageEntity((entityType: string), (blockType: DraftBlockType))
/**
* Filters entities based on the data they contain.
*/
shouldKeepEntityByAttribute(
(entityTypes: Array<Object>),
(entityType: string),
(data: Object),
)
/**
* Filters data on an entity to only retain what is whitelisted.
*/
filterEntityData((entityTypes: Array<Object>), (content: ContentState))
/**
* Replaces the given characters by their equivalent length of spaces, in all blocks.
*/
replaceTextBySpaces((characters: Array<string>), (content: ContentState))
```
### Browser support and polyfills
The Draft.js filters follow the browser support targets of Draft.js. Be sure to have a look at the [Draft.js required polyfills](https://facebook.github.io/draft-js/docs/advanced-topics-issues-and-pitfalls).
The Draft.js filters follow the browser support targets of Draft.js. Be sure to have a look at the [required Draft.js polyfills](https://facebook.github.io/draft-js/docs/advanced-topics-issues-and-pitfalls).
#### Word processor support
Have a look at our test data in [`pasting/`](pasting).
| Editor - Browser | Chrome Windows | Chrome macOS | Firefox Windows | Firefox macOS | Edge Windows | IE11 Windows | Safari macOS | Safari iOS | Chrome Android |
| ----------------- | -------------- | ------------ | --------------- | ------------- | ------------ | ------------ | ------------ | ---------- | -------------- |
| **Word 2016** | | | | | | | | N/A | N/A |
| **Word 2010** | | N/A | | N/A | | | N/A | N/A | N/A |
| **Apple Pages** | N/A | | N/A | | N/A | N/A | | | N/A |
| **Google Docs** | | | | | | | | | |
| **Word Online** | | | | | | Unsupported | | ? | ? |
| **Dropbox Paper** | | | | | | Unsupported | | ? | ? |
#### IE11
There are [known Draft.js issues](https://github.com/facebook/draft-js/issues/986) with pasting in IE11. For now, we advise users to turn on `stripPastedStyles` in IE11 only so that Draft.js removes all formatting but preserves whitespace:
```jsx
const IS_IE11 = !window.ActiveXObject && "ActiveXObject" in window
const editor = <Editor stripPastedStyles={IS_IE11} />
```
## Contributing

@@ -110,4 +266,8 @@

Use `npm run release`, which uses [standard-version](https://github.com/conventional-changelog/standard-version) to generate the CHANGELOG and decide on the version bump based on the commits since the last release.
## Credits
View the full list of [contributors](https://github.com/springload/draftail/graphs/contributors). [MIT](LICENSE) licensed. Website content available as [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
Microsoft Word toolbars screenshot from _PCWorld – Microsoft Word Turns 25_ article.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc