From 254dfbcc596914328588bd34bd930bcd2cc6b111 Mon Sep 17 00:00:00 2001 From: Matthew Beale Date: Fri, 11 Nov 2016 12:56:58 -0800 Subject: [PATCH] Add range#expandByMarker, list#detect with reverse --- src/js/editor/editor.js | 3 +-- src/js/utils/cursor/position.js | 4 ++-- src/js/utils/cursor/range.js | 34 +++++++++++++++++++++++++++ src/js/utils/linked-list.js | 4 ++-- tests/unit/utils/cursor-range-test.js | 33 ++++++++++++++++++++++++++ tests/unit/utils/linked-list-test.js | 24 +++++++++++++++++++ 6 files changed, 96 insertions(+), 6 deletions(-) diff --git a/src/js/editor/editor.js b/src/js/editor/editor.js index 8c053b26c..e96291f46 100644 --- a/src/js/editor/editor.js +++ b/src/js/editor/editor.js @@ -11,7 +11,7 @@ import mobiledocRenderers from '../renderers/mobiledoc'; import { MOBILEDOC_VERSION } from 'mobiledoc-kit/renderers/mobiledoc'; import { mergeWithOptions } from '../utils/merge'; import { normalizeTagName, clearChildNodes } from '../utils/dom-utils'; -import { forEach, filter, contains, values } from '../utils/array-utils'; +import { forEach, filter, contains, values, detect } from '../utils/array-utils'; import { setData } from '../utils/element-utils'; import Cursor from '../utils/cursor'; import Range from '../utils/cursor/range'; @@ -22,7 +22,6 @@ import { DEFAULT_KEY_COMMANDS, buildKeyCommand, findKeyCommands, validateKeyCommand } from './key-commands'; import { CARD_MODES } from '../models/card'; -import { detect } from '../utils/array-utils'; import assert from '../utils/assert'; import MutationHandler from 'mobiledoc-kit/editor/mutation-handler'; import EditHistory from 'mobiledoc-kit/editor/edit-history'; diff --git a/src/js/utils/cursor/position.js b/src/js/utils/cursor/position.js index ade0db909..27eeb4da8 100644 --- a/src/js/utils/cursor/position.js +++ b/src/js/utils/cursor/position.js @@ -110,8 +110,8 @@ Position = class Position { * @return {Range} * @public */ - toRange(tail=this) { - return new Range(this, tail); + toRange(tail=this, direction=null) { + return new Range(this, tail, direction); } get leafSectionIndex() { diff --git a/src/js/utils/cursor/range.js b/src/js/utils/cursor/range.js index a510b7940..34311b0be 100644 --- a/src/js/utils/cursor/range.js +++ b/src/js/utils/cursor/range.js @@ -117,6 +117,40 @@ class Range { } } + /** + * expand a range to all markers matching a given check + * + * @param {Function} detectMarker + * @return {Range} The expanded range + * + * @public + */ + expandByMarker(detectMarker) { + let { + head, + tail, + direction + } = this; + let {section: headSection} = head; + if (headSection !== tail.section) { + throw new Error('#expandByMarker does not work across sections. Perhaps you should confirm the range is collapsed'); + } + + let firstNotMatchingDetect = i => { + return !detectMarker(i); + }; + + let headMarker = head.section.markers.detect(firstNotMatchingDetect, head.marker, true); + headMarker = (headMarker && headMarker.next) || head.marker; + let headPosition = new Position(headSection, headSection.offsetOfMarker(headMarker)); + + let tailMarker = tail.section.markers.detect(firstNotMatchingDetect, tail.marker); + tailMarker = (tailMarker && tailMarker.prev) || tail.marker; + let tailPosition = new Position(tail.section, tail.section.offsetOfMarker(tailMarker) + tailMarker.length); + + return headPosition.toRange(tailPosition, direction); + } + _collapse(direction) { return new Range(direction === DIRECTION.BACKWARD ? this.head : this.tail); } diff --git a/src/js/utils/linked-list.js b/src/js/utils/linked-list.js index 319947ae1..7364f50cd 100644 --- a/src/js/utils/linked-list.js +++ b/src/js/utils/linked-list.js @@ -143,12 +143,12 @@ export default class LinkedList { toArray() { return this.readRange(); } - detect(callback, item=this.head) { + detect(callback, item=this.head, reverse=false) { while (item) { if (callback(item)) { return item; } - item = item.next; + item = reverse ? item.prev : item.next; } } any(callback) { diff --git a/tests/unit/utils/cursor-range-test.js b/tests/unit/utils/cursor-range-test.js index 8395b0161..d7f9c0961 100644 --- a/tests/unit/utils/cursor-range-test.js +++ b/tests/unit/utils/cursor-range-test.js @@ -1,6 +1,7 @@ import Helpers from '../../test-helpers'; import Range from 'mobiledoc-kit/utils/cursor/range'; import { DIRECTION } from 'mobiledoc-kit/utils/key'; +import { detect } from '../utils/array-utils'; const { FORWARD, BACKWARD } = DIRECTION; const {module, test} = Helpers; @@ -187,3 +188,35 @@ test('#extend(0) returns same range', (assert) => { assert.rangeIsEqual(collapsedRange.extend(0), collapsedRange, 'extending collapsed range 0 is no-op'); assert.rangeIsEqual(nonCollapsedRange.extend(0), nonCollapsedRange, 'extending non-collapsed range 0 is no-op'); }); + +test('#expandByMarker processed markers in a callback and continues as long as the callback returns true', (assert) => { + let post = Helpers.postAbstract.build(({post, markupSection, marker, markup}) => { + let bold = markup('b'); + let italic = markup('i'); + return post([ + markupSection('p', [ + marker('aiya', []), + marker('biya', [bold, italic]), + marker('ciya', [bold]), + marker('diya', [bold]), + ]) + ]); + }); + + let section = post.sections.head; + let head = section.toPosition(9); // i in the third hiya + let tail = section.toPosition(15); // y in the last hiya + let range = head.toRange(tail); + let expandedRange = range.expandByMarker(marker => { + return !!(detect(marker.markups, markup => markup.tagName === 'b')); + }); + + assert.positionIsEqual( + expandedRange.head, section.toPosition(4), + 'range head is start of second marker' + ); + assert.positionIsEqual( + expandedRange.tail, section.toPosition(16), + 'range tail did not change' + ); +}); diff --git a/tests/unit/utils/linked-list-test.js b/tests/unit/utils/linked-list-test.js index f2323ed5c..706883820 100644 --- a/tests/unit/utils/linked-list-test.js +++ b/tests/unit/utils/linked-list-test.js @@ -370,6 +370,30 @@ test(`#detect finds`, (assert) => { assert.equal(list.detect(() => false), undefined, 'no item detected'); }); +test(`#detect finds w/ start`, (assert) => { + let list = new LinkedList(); + let itemOne = new LinkedItem(); + let itemTwo = new LinkedItem(); + let itemThree = new LinkedItem(); + list.append(itemOne); + list.append(itemTwo); + list.append(itemThree); + assert.equal(list.detect(item => item === itemOne, itemOne), itemOne, 'itemOne detected'); + assert.equal(list.detect(item => item === itemTwo, itemThree), null, 'no item detected'); +}); + +test(`#detect finds w/ reverse`, (assert) => { + let list = new LinkedList(); + let itemOne = new LinkedItem(); + let itemTwo = new LinkedItem(); + let itemThree = new LinkedItem(); + list.append(itemOne); + list.append(itemTwo); + list.append(itemThree); + assert.equal(list.detect(item => item === itemOne, itemOne, true), itemOne, 'itemTwo detected'); + assert.equal(list.detect(item => item === itemThree, itemThree, true), itemThree, 'itemThree'); +}); + test(`#objectAt looks up by index`, (assert) => { let list = new LinkedList(); let itemOne = new LinkedItem();