draftjs-filters
Advanced tools
Comparing version 0.6.1 to 0.7.0
@@ -5,2 +5,16 @@ # Change Log | ||
<a name="0.7.0"></a> | ||
# [0.7.0](https://github.com/thibaudcolas/draftjs-filters/compare/v0.6.1...v0.7.0) (2018-02-09) | ||
### Features | ||
* **api:** add preserveBlockByText to exposed filters ([077e008](https://github.com/thibaudcolas/draftjs-filters/commit/077e008)) | ||
* **filters:** add first implementation of preserveBlockByText ([ed27ca7](https://github.com/thibaudcolas/draftjs-filters/commit/ed27ca7)) | ||
* **filters:** add preserveBlockByText to filterEditorState ([0c52d72](https://github.com/thibaudcolas/draftjs-filters/commit/0c52d72)) | ||
* **filters:** add special case to preserveBlockByText for entities ([21583de](https://github.com/thibaudcolas/draftjs-filters/commit/21583de)) | ||
* **filters:** change atomic block check to convert camera emoji + space ([9249033](https://github.com/thibaudcolas/draftjs-filters/commit/9249033)) | ||
* **filters:** filterBlockTypes now also resets block depth to 0 ([33c337d](https://github.com/thibaudcolas/draftjs-filters/commit/33c337d)) | ||
* **filters:** remove matched prefix when preserving list items ([059bf9e](https://github.com/thibaudcolas/draftjs-filters/commit/059bf9e)) | ||
<a name="0.6.1"></a> | ||
@@ -7,0 +21,0 @@ |
@@ -26,6 +26,3 @@ 'use strict'; | ||
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); | ||
var shouldPreserve = entityKey && ["📷", " ", "📷 "].includes(text); | ||
@@ -145,2 +142,68 @@ return shouldPreserve; | ||
/** | ||
* Changes block type and depth based on the block's text. – some word processors | ||
* add a specific prefix within the text, eg. "· Bulleted list" in Word 2010. | ||
* Also removes the matched text. | ||
* This is meant first and foremost for list items where the list bullet or numeral | ||
* ends up in the text. Other use cases may not be well covered. | ||
*/ | ||
var preserveBlockByText = function preserveBlockByText(rules, content) { | ||
var blockMap = content.getBlockMap(); | ||
var blocks = blockMap.filter(function (block) { | ||
return block.getType() === "unstyled"; | ||
}).map(function (block) { | ||
var text = block.getText(); | ||
var newBlock = block; | ||
var match = void 0; | ||
var matchingRule = rules.find(function (rule) { | ||
match = new RegExp(rule.test).exec(text); | ||
return match !== null; | ||
}); | ||
if (matchingRule && match && match[0]) { | ||
var _text = block.getText(); | ||
var entity = block.getEntityAt(0); | ||
// Special case – do not convert the block if there is an entity at the start, and the matching text is the full block’s text. | ||
// This can happen in Word for equations, which are injected as images with text "📷 ". | ||
if (entity && match[0] === _text) { | ||
return newBlock; | ||
} | ||
// Unicode gotcha: | ||
// At the moment, Draft.js stores one CharacterMetadata in the character list | ||
// for each "character" in an astral symbol. "📷" has a length of 2, is stored with two CharacterMetadata instances. | ||
// What matters is that we remove the correct number of chars from both | ||
// the text and the List<CharacterMetadata>. So – we want to use the ES5 way of counting | ||
// a string length. | ||
// See https://mathiasbynens.be/notes/javascript-unicode. | ||
var sliceOffset = match[0].length; | ||
// Maintain persistence in the list while removing chars from the start. | ||
// https://github.com/facebook/draft-js/blob/788595984da7c1e00d1071ea82b063ff87140be4/src/model/transaction/removeRangeFromContentState.js#L333 | ||
var chars = block.getCharacterList(); | ||
var startOffset = 0; | ||
while (startOffset < sliceOffset) { | ||
chars = chars.shift(); | ||
startOffset++; | ||
} | ||
newBlock = newBlock.merge({ | ||
type: matchingRule.type, | ||
depth: matchingRule.depth, | ||
text: _text.slice(sliceOffset), | ||
characterList: chars | ||
}); | ||
} | ||
return newBlock; | ||
}); | ||
return blocks.size === 0 ? content : content.merge({ | ||
blockMap: blockMap.merge(blocks) | ||
}); | ||
}; | ||
/** | ||
* Resets the depth of all the content to at most max. | ||
@@ -163,3 +226,4 @@ */ | ||
/** | ||
* Removes all block types not present in the whitelist. | ||
* Converts all block types not present in the whitelist to unstyled. | ||
* Also sets depth to 0 (for potentially nested list items). | ||
*/ | ||
@@ -172,3 +236,6 @@ var filterBlockTypes = function filterBlockTypes(whitelist, content) { | ||
}).map(function (block) { | ||
return block.set("type", UNSTYLED); | ||
return block.merge({ | ||
type: UNSTYLED, | ||
depth: 0 | ||
}); | ||
}); | ||
@@ -430,2 +497,32 @@ | ||
var PREFIX_RULES = [{ | ||
// https://regexper.com/#%5E(%C2%B7%20%7C%E2%80%A2%5Ct%7C%E2%80%A2%7C%F0%9F%93%B7%20%7C%5Ct%7C%20%5Ct) | ||
test: "^(· |•\t|•|📷 |\t| \t)", | ||
type: "unordered-list-item", | ||
depth: 0 | ||
}, | ||
// https://regexper.com/#%5E(%E2%97%A6%7Co%20%7Co%5Ct) | ||
{ test: "^(◦|o |o\t)", type: "unordered-list-item", depth: 1 }, | ||
// https://regexper.com/#%5E(%C2%A7%20%7C%EF%82%A7%5Ct%7C%E2%97%BE) | ||
{ test: "^(§ |\t|◾)", type: "unordered-list-item", depth: 2 }, { | ||
// https://regexper.com/#%5E1%7B0%2C1%7D%5Cd%5C.%5B%20%5Ct%5D | ||
test: "^1{0,1}\\d\\.[ \t]", | ||
type: "ordered-list-item", | ||
depth: 0 | ||
}, { | ||
// Roman numerals from I to XX. | ||
// https://regexper.com/#%5Ex%7B0%2C1%7D(i%7Cii%7Ciii%7Civ%7Cv%7Cvi%7Cvii%7Cviii%7Cix%7Cx)%5C.%5B%20%5Ct%5D | ||
test: "^x{0,1}(i|ii|iii|iv|v|vi|vii|viii|ix|x)\\.[ \t]", | ||
type: "ordered-list-item", | ||
depth: 2 | ||
}, { | ||
// There is a clash between this and the i., v., x. roman numerals. | ||
// Those tests are executed in order though, so the roman numerals take priority. | ||
// We do not want to match too many letters (say aa.), because those could be actual text. | ||
// https://regexper.com/#%5E%5Ba-z%5D%5C.%5B%20%5Ct%5D | ||
test: "^[a-z]\\.[ \t]", | ||
type: "ordered-list-item", | ||
depth: 1 | ||
}]; | ||
/** | ||
@@ -454,3 +551,3 @@ * Applies whitelist and blacklist operations to the editor content, | ||
// 1. clean up blocks. | ||
removeInvalidDepthBlocks, limitBlockDepth.bind(null, maxNesting), | ||
removeInvalidDepthBlocks, preserveBlockByText.bind(null, PREFIX_RULES), limitBlockDepth.bind(null, maxNesting), | ||
// 2. reset styles and blocks. | ||
@@ -486,2 +583,3 @@ filterInlineStyles.bind(null, styles), | ||
exports.limitBlockDepth = limitBlockDepth; | ||
exports.preserveBlockByText = preserveBlockByText; | ||
exports.filterBlockTypes = filterBlockTypes; | ||
@@ -488,0 +586,0 @@ exports.filterInlineStyles = filterInlineStyles; |
@@ -22,6 +22,3 @@ import { CharacterMetadata, EditorState } from 'draft-js'; | ||
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); | ||
var shouldPreserve = entityKey && ["📷", " ", "📷 "].includes(text); | ||
@@ -141,2 +138,68 @@ return shouldPreserve; | ||
/** | ||
* Changes block type and depth based on the block's text. – some word processors | ||
* add a specific prefix within the text, eg. "· Bulleted list" in Word 2010. | ||
* Also removes the matched text. | ||
* This is meant first and foremost for list items where the list bullet or numeral | ||
* ends up in the text. Other use cases may not be well covered. | ||
*/ | ||
var preserveBlockByText = function preserveBlockByText(rules, content) { | ||
var blockMap = content.getBlockMap(); | ||
var blocks = blockMap.filter(function (block) { | ||
return block.getType() === "unstyled"; | ||
}).map(function (block) { | ||
var text = block.getText(); | ||
var newBlock = block; | ||
var match = void 0; | ||
var matchingRule = rules.find(function (rule) { | ||
match = new RegExp(rule.test).exec(text); | ||
return match !== null; | ||
}); | ||
if (matchingRule && match && match[0]) { | ||
var _text = block.getText(); | ||
var entity = block.getEntityAt(0); | ||
// Special case – do not convert the block if there is an entity at the start, and the matching text is the full block’s text. | ||
// This can happen in Word for equations, which are injected as images with text "📷 ". | ||
if (entity && match[0] === _text) { | ||
return newBlock; | ||
} | ||
// Unicode gotcha: | ||
// At the moment, Draft.js stores one CharacterMetadata in the character list | ||
// for each "character" in an astral symbol. "📷" has a length of 2, is stored with two CharacterMetadata instances. | ||
// What matters is that we remove the correct number of chars from both | ||
// the text and the List<CharacterMetadata>. So – we want to use the ES5 way of counting | ||
// a string length. | ||
// See https://mathiasbynens.be/notes/javascript-unicode. | ||
var sliceOffset = match[0].length; | ||
// Maintain persistence in the list while removing chars from the start. | ||
// https://github.com/facebook/draft-js/blob/788595984da7c1e00d1071ea82b063ff87140be4/src/model/transaction/removeRangeFromContentState.js#L333 | ||
var chars = block.getCharacterList(); | ||
var startOffset = 0; | ||
while (startOffset < sliceOffset) { | ||
chars = chars.shift(); | ||
startOffset++; | ||
} | ||
newBlock = newBlock.merge({ | ||
type: matchingRule.type, | ||
depth: matchingRule.depth, | ||
text: _text.slice(sliceOffset), | ||
characterList: chars | ||
}); | ||
} | ||
return newBlock; | ||
}); | ||
return blocks.size === 0 ? content : content.merge({ | ||
blockMap: blockMap.merge(blocks) | ||
}); | ||
}; | ||
/** | ||
* Resets the depth of all the content to at most max. | ||
@@ -159,3 +222,4 @@ */ | ||
/** | ||
* Removes all block types not present in the whitelist. | ||
* Converts all block types not present in the whitelist to unstyled. | ||
* Also sets depth to 0 (for potentially nested list items). | ||
*/ | ||
@@ -168,3 +232,6 @@ var filterBlockTypes = function filterBlockTypes(whitelist, content) { | ||
}).map(function (block) { | ||
return block.set("type", UNSTYLED); | ||
return block.merge({ | ||
type: UNSTYLED, | ||
depth: 0 | ||
}); | ||
}); | ||
@@ -426,2 +493,32 @@ | ||
var PREFIX_RULES = [{ | ||
// https://regexper.com/#%5E(%C2%B7%20%7C%E2%80%A2%5Ct%7C%E2%80%A2%7C%F0%9F%93%B7%20%7C%5Ct%7C%20%5Ct) | ||
test: "^(· |•\t|•|📷 |\t| \t)", | ||
type: "unordered-list-item", | ||
depth: 0 | ||
}, | ||
// https://regexper.com/#%5E(%E2%97%A6%7Co%20%7Co%5Ct) | ||
{ test: "^(◦|o |o\t)", type: "unordered-list-item", depth: 1 }, | ||
// https://regexper.com/#%5E(%C2%A7%20%7C%EF%82%A7%5Ct%7C%E2%97%BE) | ||
{ test: "^(§ |\t|◾)", type: "unordered-list-item", depth: 2 }, { | ||
// https://regexper.com/#%5E1%7B0%2C1%7D%5Cd%5C.%5B%20%5Ct%5D | ||
test: "^1{0,1}\\d\\.[ \t]", | ||
type: "ordered-list-item", | ||
depth: 0 | ||
}, { | ||
// Roman numerals from I to XX. | ||
// https://regexper.com/#%5Ex%7B0%2C1%7D(i%7Cii%7Ciii%7Civ%7Cv%7Cvi%7Cvii%7Cviii%7Cix%7Cx)%5C.%5B%20%5Ct%5D | ||
test: "^x{0,1}(i|ii|iii|iv|v|vi|vii|viii|ix|x)\\.[ \t]", | ||
type: "ordered-list-item", | ||
depth: 2 | ||
}, { | ||
// There is a clash between this and the i., v., x. roman numerals. | ||
// Those tests are executed in order though, so the roman numerals take priority. | ||
// We do not want to match too many letters (say aa.), because those could be actual text. | ||
// https://regexper.com/#%5E%5Ba-z%5D%5C.%5B%20%5Ct%5D | ||
test: "^[a-z]\\.[ \t]", | ||
type: "ordered-list-item", | ||
depth: 1 | ||
}]; | ||
/** | ||
@@ -450,3 +547,3 @@ * Applies whitelist and blacklist operations to the editor content, | ||
// 1. clean up blocks. | ||
removeInvalidDepthBlocks, limitBlockDepth.bind(null, maxNesting), | ||
removeInvalidDepthBlocks, preserveBlockByText.bind(null, PREFIX_RULES), limitBlockDepth.bind(null, maxNesting), | ||
// 2. reset styles and blocks. | ||
@@ -477,2 +574,2 @@ filterInlineStyles.bind(null, styles), | ||
export { preserveAtomicBlocks, resetAtomicBlocks, removeInvalidAtomicBlocks, removeInvalidDepthBlocks, limitBlockDepth, filterBlockTypes, filterInlineStyles, cloneEntities, filterEntityRanges, shouldKeepEntityType, shouldRemoveImageEntity, shouldKeepEntityByAttribute, filterEntityData, replaceTextBySpaces, filterEditorState }; | ||
export { preserveAtomicBlocks, resetAtomicBlocks, removeInvalidAtomicBlocks, removeInvalidDepthBlocks, limitBlockDepth, preserveBlockByText, filterBlockTypes, filterInlineStyles, cloneEntities, filterEntityRanges, shouldKeepEntityType, shouldRemoveImageEntity, shouldKeepEntityByAttribute, filterEntityData, replaceTextBySpaces, filterEditorState }; |
{ | ||
"name": "draftjs-filters", | ||
"version": "0.6.1", | ||
"version": "0.7.0", | ||
"description": "Filter Draft.js content to preserve only the formatting you allow", | ||
@@ -5,0 +5,0 @@ "author": "Thibaud Colas", |
@@ -22,9 +22,12 @@ # [Draft.js filters](https://thibaudcolas.github.io/draftjs-filters/) [![npm](https://img.shields.io/npm/v/draftjs-filters.svg)](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/) | ||
function onChange(editorState) { | ||
function onChange(nextState) { | ||
const { editorState } = this.state | ||
let filteredState = nextState | ||
const shouldFilterPaste = | ||
editorState.getLastChangeType() === "insert-fragment" | ||
let nextState = editorState | ||
nextState.getCurrentContent() !== editorState.getCurrentContent() && | ||
nextState.getLastChangeType() === "insert-fragment" | ||
if (shouldFilterPaste) { | ||
nextState = filterEditorState( | ||
filteredState = filterEditorState( | ||
{ | ||
@@ -49,7 +52,7 @@ blocks: ["header-two", "header-three", "unordered-list-item"], | ||
}, | ||
nextState, | ||
filteredState, | ||
) | ||
} | ||
this.setState({ editorState: nextState }) | ||
this.setState({ editorState: filteredState }) | ||
} | ||
@@ -122,5 +125,22 @@ ``` | ||
/** | ||
* Removes all block types not present in the whitelist. | ||
* Changes block type and depth based on the block's text. – some word processors | ||
* add a specific prefix within the text, eg. "· Bulleted list" in Word 2010. | ||
* Also removes the matched text. | ||
* This is meant first and foremost for list items where the list bullet or numeral | ||
* ends up in the text. Other use cases may not be well covered. | ||
*/ | ||
preserveBlockByText( | ||
(rules: Array<{ | ||
test: string, | ||
type: DraftBlockType, | ||
depth: number, | ||
}>), | ||
(content: ContentState), | ||
) | ||
/** | ||
* Converts all block types not present in the whitelist to unstyled. | ||
* Also sets depth to 0 (for potentially nested list items). | ||
*/ | ||
filterBlockTypes((whitelist: Array<DraftBlockType>), (content: ContentState)) | ||
@@ -211,2 +231,3 @@ | ||
| **Dropbox Paper** | | | | | | Unsupported | | ? | ? | | ||
| **Draft.js** | | | | | | | | | | | ||
@@ -213,0 +234,0 @@ Use the [Draft.js Cut/Copy/Paste testing plan](https://github.com/facebook/draft-js/wiki/Manual-Testing#cutcopypaste). We target specific external sources, and have ready-made test documents available to test them: |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
59785
944
307