diff --git a/lib/models/selection.js b/lib/models/selection.js index 5077e4d99..3117bc173 100644 --- a/lib/models/selection.js +++ b/lib/models/selection.js @@ -3,14 +3,6 @@ import includes from 'lodash/includes' import memoize from '../utils/memoize' import { Record } from 'immutable' -/** - * Start-and-end convenience methods to auto-generate. - */ - -const START_END_METHODS = [ - 'collapseTo%' -] - /** * Start-end-and-edge convenience methods to auto-generate. */ @@ -398,6 +390,38 @@ class Selection extends new Record(DEFAULTS) { }) } + /** + * Move the end point to the start point. + * + * @return {Selection} selection + */ + + collapseToStart() { + return this.merge({ + anchorKey: this.startKey, + anchorOffset: this.startOffset, + focusKey: this.startKey, + focusOffset: this.startOffset, + isBackward: false + }) + } + + /** + * Move the end point to the start point. + * + * @return {Selection} selection + */ + + collapseToEnd() { + return this.merge({ + anchorKey: this.endKey, + anchorOffset: this.endOffset, + focusKey: this.endKey, + focusOffset: this.endOffset, + isBackward: false + }) + } + /** * Move to the start of a `node`. * @@ -435,6 +459,7 @@ class Selection extends new Record(DEFAULTS) { * * @param {Node} start * @param {Node} end (optional) + * @param {Document} document * @return {Selection} selection */ @@ -444,7 +469,7 @@ class Selection extends new Record(DEFAULTS) { anchorOffset: 0, focusKey: end.key, focusOffset: end.length, - isBackward: start == end ? false : null + isBackward: null, }) } @@ -485,11 +510,15 @@ class Selection extends new Record(DEFAULTS) { */ moveToOffsets(anchor, focus = anchor) { - return this.merge({ - anchorOffset: anchor, - focusOffset: focus, - isBackward: null - }) + const props = {} + props.anchorOffset = anchor + props.focusOffset = focus + + if (this.anchorKey == this.focusKey) { + props.isBackward = anchor > focus + } + + return this.merge(props) } /** @@ -556,7 +585,7 @@ class Selection extends new Record(DEFAULTS) { * Add start, end and edge convenience methods. */ -START_END_METHODS.concat(EDGE_METHODS).forEach((pattern) => { +EDGE_METHODS.forEach((pattern) => { const [ p, s ] = pattern.split('%') const anchor = `${p}Anchor${s}` const edge = `${p}Edge${s}` @@ -576,8 +605,6 @@ START_END_METHODS.concat(EDGE_METHODS).forEach((pattern) => { : this[focus](...args) } - if (!includes(EDGE_METHODS, pattern)) return - Selection.prototype[edge] = function (...args) { return this[anchor](...args) || this[focus](...args) } diff --git a/lib/models/transform.js b/lib/models/transform.js index f63000d39..9917113d2 100644 --- a/lib/models/transform.js +++ b/lib/models/transform.js @@ -64,6 +64,7 @@ class Transform { const { state } = properties this.state = state this.operations = [] + this.transforms = [] } /** @@ -263,7 +264,7 @@ class Transform { Object.keys(Transforms).forEach((type) => { Transform.prototype[type] = function (...args) { - this.operations.push({ type, args }) + this.transforms.push({ type, args }) return Transforms[type](this, ...args) } }) diff --git a/lib/transforms/at-current-range.js b/lib/transforms/at-current-range.js index 47aa7118c..9a36ea770 100644 --- a/lib/transforms/at-current-range.js +++ b/lib/transforms/at-current-range.js @@ -620,6 +620,7 @@ export function wrapInline(transform, properties) { }) } + selection = selection.normalize(document) state = state.merge({ selection }) transform.state = state return transform diff --git a/lib/transforms/on-selection.js b/lib/transforms/on-selection.js index e1d2d3d6d..0f5a2ff4f 100644 --- a/lib/transforms/on-selection.js +++ b/lib/transforms/on-selection.js @@ -1,4 +1,5 @@ +import Normalize from '../utils/normalize' import Selection from '../models/selection' /** @@ -9,61 +10,53 @@ import Selection from '../models/selection' */ export function blur(transform) { - let { state } = transform - let { document, selection } = state - selection = selection.blur() - selection = selection.normalize(document) - state = state.merge({ selection }) - transform.state = state - return transform + const { state } = transform + const { selection } = state + const sel = selection.blur() + return transform.updateSelection(sel) } /** * Move the focus point to the anchor point. * + * @param {Transform} transform * @return {Transform} */ export function collapseToAnchor(transform) { - let { state } = transform - let { document, selection } = state - selection = selection.collapseToAnchor() - selection = selection.normalize(document) - state = state.merge({ selection }) - transform.state = state - return transform + const { state } = transform + const { selection } = state + const sel = selection.collapseToAnchor() + return transform.updateSelection(sel) } /** - * Move the anchor point to the focus point. - * + * Move the anchor point to the + * focus point. + * @param {Transform} transform * @return {Transform} */ export function collapseToFocus(transform) { - let { state } = transform - let { document, selection } = state - selection = selection.collapseToFocus() - selection = selection.normalize(document) - state = state.merge({ selection }) - transform.state = state - return transform + const { state } = transform + const { selection } = state + const sel = selection.collapseToFocus() + return transform.updateSelection(sel) } /** * Move to the end of a `node`. * + * @param {Transform} transform + * @param {Node} node * @return {Transform} */ -export function collapseToEndOf(transform, ...args) { - let { state } = transform - let { document, selection } = state - selection = selection.collapseToEndOf(...args) - selection = selection.normalize(document) - state = state.merge({ selection }) - transform.state = state - return transform +export function collapseToEndOf(transform, node) { + const { state } = transform + const { selection } = state + const sel = selection.collapseToEndOf(node) + return transform.updateSelection(sel) } /** @@ -74,20 +67,15 @@ export function collapseToEndOf(transform, ...args) { */ export function collapseToEndOfNextBlock(transform) { - let { state } = transform - let { document, selection } = state - let blocks = document.getBlocksAtRange(selection) - let block = blocks.last() - if (!block) return transform - - let next = document.getNextBlock(block) + const { state } = transform + const { document, selection } = state + const blocks = document.getBlocksAtRange(selection) + const last = blocks.last() + const next = document.getNextBlock(last) if (!next) return transform - selection = selection.collapseToEndOf(next) - selection = selection.normalize(document) - state = state.merge({ selection }) - transform.state = state - return transform + const sel = selection.collapseToEndOf(next) + return transform.updateSelection(sel) } /** @@ -98,20 +86,15 @@ export function collapseToEndOfNextBlock(transform) { */ export function collapseToEndOfNextText(transform) { - let { state } = transform - let { document, selection } = state - let texts = document.getTextsAtRange(selection) - let text = texts.last() - if (!text) return transform - - let next = document.getNextText(text) + const { state } = transform + const { document, selection } = state + const texts = document.getTextsAtRange(selection) + const last = texts.last() + const next = document.getNextText(last) if (!next) return transform - selection = selection.collapseToEndOf(next) - selection = selection.normalize(document) - state = state.merge({ selection }) - transform.state = state - return transform + const sel = selection.collapseToEndOf(next) + return transform.updateSelection(sel) } /** @@ -122,20 +105,15 @@ export function collapseToEndOfNextText(transform) { */ export function collapseToEndOfPreviousBlock(transform) { - let { state } = transform - let { document, selection } = state - let blocks = document.getBlocksAtRange(selection) - let block = blocks.first() - if (!block) return transform - - let previous = document.getPreviousBlock(block) + const { state } = transform + const { document, selection } = state + const blocks = document.getBlocksAtRange(selection) + const first = blocks.first() + const previous = document.getPreviousBlock(first) if (!previous) return transform - selection = selection.collapseToEndOf(previous) - selection = selection.normalize(document) - state = state.merge({ selection }) - transform.state = state - return transform + const sel = selection.collapseToEndOf(previous) + return transform.updateSelection(sel) } /** @@ -146,36 +124,29 @@ export function collapseToEndOfPreviousBlock(transform) { */ export function collapseToEndOfPreviousText(transform) { - let { state } = transform - let { document, selection } = state - let texts = document.getTextsAtRange(selection) - let text = texts.first() - if (!text) return transform - - let previous = document.getPreviousText(text) + const { state } = transform + const { document, selection } = state + const texts = document.getTextsAtRange(selection) + const first = texts.first() + const previous = document.getPreviousText(first) if (!previous) return transform - selection = selection.collapseToEndOf(previous) - selection = selection.normalize(document) - state = state.merge({ selection }) - transform.state = state - return transform + const sel = selection.collapseToEndOf(previous) + return transform.updateSelection(sel) } /** - * Move to the start of a `node`. - * + * Move to the start of a `node + * `. + * @param {Transform} transform * @return {Transform} */ export function collapseToStartOf(transform, node) { - let { state } = transform - let { document, selection } = state - selection = selection.collapseToStartOf(node) - selection = selection.normalize(document) - state = state.merge({ selection }) - transform.state = state - return transform + const { state } = transform + const { selection } = state + const sel = selection.collapseToStartOf(node) + return transform.updateSelection(sel) } /** @@ -186,20 +157,15 @@ export function collapseToStartOf(transform, node) { */ export function collapseToStartOfNextBlock(transform) { - let { state } = transform - let { document, selection } = state - let blocks = document.getBlocksAtRange(selection) - let block = blocks.last() - if (!block) return transform - - let next = document.getNextBlock(block) + const { state } = transform + const { document, selection } = state + const blocks = document.getBlocksAtRange(selection) + const last = blocks.last() + const next = document.getNextBlock(last) if (!next) return transform - selection = selection.collapseToStartOf(next) - selection = selection.normalize(document) - state = state.merge({ selection }) - transform.state = state - return transform + const sel = selection.collapseToStartOf(next) + return transform.updateSelection(sel) } /** @@ -210,20 +176,15 @@ export function collapseToStartOfNextBlock(transform) { */ export function collapseToStartOfNextText(transform) { - let { state } = transform - let { document, selection } = state - let texts = document.getTextsAtRange(selection) - let text = texts.last() - if (!text) return transform - - let next = document.getNextText(text) + const { state } = transform + const { document, selection } = state + const texts = document.getTextsAtRange(selection) + const last = texts.last() + const next = document.getNextText(last) if (!next) return transform - selection = selection.collapseToStartOf(next) - selection = selection.normalize(document) - state = state.merge({ selection }) - transform.state = state - return transform + const sel = selection.collapseToStartOf(next) + return transform.updateSelection(sel) } /** @@ -234,20 +195,15 @@ export function collapseToStartOfNextText(transform) { */ export function collapseToStartOfPreviousBlock(transform) { - let { state } = transform - let { document, selection } = state - let blocks = document.getBlocksAtRange(selection) - let block = blocks.first() - if (!block) return transform - - let previous = document.getPreviousBlock(block) + const { state } = transform + const { document, selection } = state + const blocks = document.getBlocksAtRange(selection) + const first = blocks.first() + const previous = document.getPreviousBlock(first) if (!previous) return transform - selection = selection.collapseToStartOf(previous) - selection = selection.normalize(document) - state = state.merge({ selection }) - transform.state = state - return transform + const sel = selection.collapseToStartOf(previous) + return transform.updateSelection(sel) } /** @@ -258,20 +214,15 @@ export function collapseToStartOfPreviousBlock(transform) { */ export function collapseToStartOfPreviousText(transform) { - let { state } = transform - let { document, selection } = state - let texts = document.getTextsAtRange(selection) - let text = texts.first() - if (!text) return transform - - let previous = document.getPreviousText(text) + const { state } = transform + const { document, selection } = state + const texts = document.getTextsAtRange(selection) + const first = texts.first() + const previous = document.getPreviousText(first) if (!previous) return transform - selection = selection.collapseToStartOf(previous) - selection = selection.normalize(document) - state = state.merge({ selection }) - transform.state = state - return transform + const sel = selection.collapseToStartOf(previous) + return transform.updateSelection(sel) } /** @@ -345,6 +296,7 @@ export function extendToStartOf(transform, ...args) { /** * Focus the selection. * + * @param {Transform} transform * @return {Transform} */ @@ -462,3 +414,25 @@ export function moveToRangeOf(transform, ...args) { transform.state = state return transform } + +/** + * Update the selection with a new `selection`. + * + * @param {Transform} transform + * @param {Mixed} sel + * @return {Transform} + */ + +export function updateSelection(transform, sel) { + sel = Normalize.selection(sel) + + let { state } = transform + let { selection } = state + selection = selection.merge(sel) + state = state.merge({ selection }) + + transform.add(state, { + type: 'update-selection', + selection: sel + }) +} diff --git a/lib/utils/normalize.js b/lib/utils/normalize.js index 5c324bb2a..d2acb07ed 100644 --- a/lib/utils/normalize.js +++ b/lib/utils/normalize.js @@ -16,9 +16,8 @@ import typeOf from 'type-of' function block(value) { if (value instanceof Block) return value - const type = typeOf(value) - switch (type) { + switch (typeOf(value)) { case 'string': case 'object': { return Block.create(nodeProperties(value)) @@ -38,9 +37,8 @@ function block(value) { function inline(value) { if (value instanceof Inline) return value - const type = typeOf(value) - switch (type) { + switch (typeOf(value)) { case 'string': case 'object': { return Inline.create(nodeProperties(value)) @@ -64,8 +62,7 @@ function key(value) { if (value instanceof Inline) return value.key if (value instanceof Text) return value.key - const type = typeOf(value) - if (type == 'string') return value + if (typeOf(value) == 'string') return value throw new Error(`Invalid \`key\` argument! It must be either a block, an inline, a text, or a string. You passed: "${value}".`) } @@ -73,15 +70,14 @@ function key(value) { /** * Normalize a mark argument `value`. * - * @param {Mark || String || Object} mark + * @param {Mark || String || Object} value * @return {Mark} */ function mark(value) { if (value instanceof Mark) return value - const type = typeOf(value) - switch (type) { + switch (typeOf(value)) { case 'string': case 'object': { return Mark.create(markProperties(value)) @@ -101,9 +97,8 @@ function mark(value) { function markProperties(value = {}) { const ret = {} - const type = typeOf(value) - switch (type) { + switch (typeOf(value)) { case 'string': { ret.type = value break @@ -135,9 +130,8 @@ function markProperties(value = {}) { function nodeProperties(value = {}) { const ret = {} - const type = typeOf(value) - switch (type) { + switch (typeOf(value)) { case 'string': { ret.type = value break @@ -161,6 +155,26 @@ function nodeProperties(value = {}) { return ret } +/** + * Normalize a selection argument `value`. + * + * @param {Selection || Object} value + * @return {Selection} + */ + +function selection(value) { + if (value instanceof Selection) return value + + switch (typeOf(value)) { + case 'object': { + return Selection.create(value) + } + default: { + throw new Error(`Invalid \`selection\` argument! It must be a selection or an object. You passed: "${value}".`) + } + } +} + /** * Export. * @@ -174,5 +188,6 @@ export default { mark, markProperties, nodeProperties, + selection, }