From 950617cd6c8d3689d515f2d5743c43ed8dc05adf Mon Sep 17 00:00:00 2001 From: Ian Storm Taylor Date: Fri, 22 Jul 2016 12:12:23 -0700 Subject: [PATCH] move groupByMarks util into Text model as methods --- lib/components/text.js | 4 +- lib/models/text.js | 75 +++++++++++++++++++++++++++++++++++-- lib/serializers/html.js | 3 +- lib/serializers/raw.js | 14 +++---- lib/utils/group-by-marks.js | 64 ------------------------------- lib/utils/offset-key.js | 2 +- 6 files changed, 81 insertions(+), 81 deletions(-) delete mode 100644 lib/utils/group-by-marks.js diff --git a/lib/components/text.js b/lib/components/text.js index d0600ac04..15337c143 100644 --- a/lib/components/text.js +++ b/lib/components/text.js @@ -1,7 +1,6 @@ import Leaf from './leaf' import React from 'react' -import groupByMarks from '../utils/group-by-marks' import { List } from 'immutable' /** @@ -59,8 +58,7 @@ class Text extends React.Component { renderLeaves() { const { node } = this.props - const { characters, decorations } = node - const ranges = groupByMarks(decorations || characters) + const ranges = node.getDecoratedRanges() return ranges.map((range, i, original) => { const previous = original.slice(0, i) diff --git a/lib/models/text.js b/lib/models/text.js index 6d80d51ad..e1a269a0e 100644 --- a/lib/models/text.js +++ b/lib/models/text.js @@ -1,10 +1,19 @@ import Character from './character' import Mark from './mark' -import groupByMarks from '../utils/group-by-marks' import memoize from '../utils/memoize' import uid from '../utils/uid' -import { List, Record } from 'immutable' +import { List, Record, Set } from 'immutable' + +/** + * Range. + */ + +const Range = new Record({ + kind: 'range', + marks: new Set(), + text: '' +}) /** * Default properties. @@ -103,6 +112,16 @@ class Text extends new Record(DEFAULTS) { }) } + /** + * Get the decorated characters grouped by marks. + * + * @return {List} + */ + + getDecoratedRanges() { + return this.getRangesForCharacters(this.decorations || this.characters) + } + /** * Get the characters grouped by marks. * @@ -110,7 +129,53 @@ class Text extends new Record(DEFAULTS) { */ getRanges() { - return groupByMarks(this.decorations || this.characters) + return this.getRangesForCharacters(this.characters) + } + + /** + * Derive the ranges for a list of `characters`. + * + * @param {List} characters + * @return {List} + */ + + getRangesForCharacters(characters) { + if (characters.size == 0) { + let ranges = new List() + ranges = ranges.push(new Range()) + return ranges + } + + return characters + .toList() + .reduce((ranges, char, i) => { + const { marks, text } = char + + // The first one can always just be created. + if (i == 0) { + return ranges.push(new Range({ text, marks })) + } + + // Otherwise, compare to the previous and see if a new range should be + // created, or whether the text should be added to the previous range. + const previous = characters.get(i - 1) + const prevMarks = previous.marks + const added = marks.filterNot(mark => prevMarks.includes(mark)) + const removed = prevMarks.filterNot(mark => marks.includes(mark)) + const isSame = !added.size && !removed.size + + // If the marks are the same, add the text to the previous range. + if (isSame) { + const index = ranges.size - 1 + let prevRange = ranges.get(index) + let prevText = prevRange.get('text') + prevRange = prevRange.set('text', prevText += text) + return ranges.set(index, prevRange) + } + + // Otherwise, create a new range. + return ranges.push(new Range({ text, marks })) + }, new List()) } /** @@ -169,7 +234,9 @@ class Text extends new Record(DEFAULTS) { */ memoize(Text.prototype, [ - 'getRanges' + 'getDecoratedRanges', + 'getRanges', + 'getRangesForCharacters' ]) /** diff --git a/lib/serializers/html.js b/lib/serializers/html.js index 81f9dc4c7..631ceb499 100644 --- a/lib/serializers/html.js +++ b/lib/serializers/html.js @@ -9,7 +9,6 @@ import ReactDOM from 'react-dom/server' import State from '../models/state' import Text from '../models/text' import cheerio from 'cheerio' -import groupByMarks from '../utils/group-by-marks' import { Record } from 'immutable' /** @@ -235,7 +234,7 @@ class Html { serializeNode = (node) => { if (node.kind == 'text') { - const ranges = groupByMarks(node.characters) + const ranges = node.getRanges() return ranges.map(this.serializeRange) } diff --git a/lib/serializers/raw.js b/lib/serializers/raw.js index 254f5a5f6..f2a50ce06 100644 --- a/lib/serializers/raw.js +++ b/lib/serializers/raw.js @@ -6,7 +6,6 @@ import Inline from '../models/inline' import Mark from '../models/mark' import State from '../models/state' import Text from '../models/text' -import groupByMarks from '../utils/group-by-marks' /** * Serialize a `state`. @@ -36,7 +35,7 @@ function serializeNode(node) { case 'text': { const obj = {} obj.kind = node.kind - obj.ranges = serializeCharacters(node.characters) + obj.ranges = serializeRanges(node) return obj } case 'block': @@ -56,14 +55,15 @@ function serializeNode(node) { } /** - * Serialize a list of `characters`. + * Serialize the ranges of a text `node`. * - * @param {List} characters + * @param {Text} text * @return {Array} */ -function serializeCharacters(characters) { - return groupByMarks(characters) +function serializeRanges(text) { + return text + .getRanges() .toArray() .map((range) => { const obj = {} @@ -182,7 +182,7 @@ function deserializeMark(object) { export default { serialize, - serializeCharacters, + serializeRanges, serializeMark, serializeNode, deserialize, diff --git a/lib/utils/group-by-marks.js b/lib/utils/group-by-marks.js deleted file mode 100644 index 0f7e5dd0c..000000000 --- a/lib/utils/group-by-marks.js +++ /dev/null @@ -1,64 +0,0 @@ - -import { List, Map, Record, Set } from 'immutable' - -/** - * Range. - */ - -const Range = new Record({ - kind: 'range', - marks: new Set(), - text: '' -}) - -/** - * Group a list of `characters` into ranges by the marks they have. - * - * @param {List} characters - * @return {List} ranges - */ - -function groupByMarks(characters) { - if (characters.size == 0) { - let ranges = new List() - ranges = ranges.push(new Range()) - return ranges - } - - return characters - .toList() - .reduce((ranges, char, i) => { - const { marks, text } = char - - // The first one can always just be created. - if (i == 0) { - return ranges.push(new Range({ text, marks })) - } - - // Otherwise, compare to the previous and see if a new range should be - // created, or whether the text should be added to the previous range. - const previous = characters.get(i - 1) - const prevMarks = previous.marks - const added = marks.filterNot(mark => prevMarks.includes(mark)) - const removed = prevMarks.filterNot(mark => marks.includes(mark)) - const isSame = !added.size && !removed.size - - // If the marks are the same, add the text to the previous range. - if (isSame) { - const index = ranges.size - 1 - let prevRange = ranges.get(index) - let prevText = prevRange.get('text') - prevRange = prevRange.set('text', prevText += text) - return ranges.set(index, prevRange) - } - - // Otherwise, create a new range. - return ranges.push(new Range({ text, marks })) - }, new List()) -} - -/** - * Export. - */ - -export default groupByMarks diff --git a/lib/utils/offset-key.js b/lib/utils/offset-key.js index 4a7880aa2..e54a7f740 100644 --- a/lib/utils/offset-key.js +++ b/lib/utils/offset-key.js @@ -23,7 +23,7 @@ const SELECTOR = `[${ATTRIBUTE}]` function findBounds(key, index, state) { const text = state.document.assertDescendant(key) - const ranges = text.getRanges() + const ranges = text.getDecoratedRanges() const range = ranges.get(index) const start = ranges .slice(0, index)