From 17f703cecbef8c4e26b412149078b4dad5e6013e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Tue, 9 Aug 2016 18:11:21 +0200 Subject: [PATCH] WIP: Add transform wrapText (#227) * Add basic implementation of wrapText * Default suffix for wrapText to prefix * Add more tests for afterText * Add tests "whole-block" and "empty-block" for wrapText * Add tests for across-blocks and across-inlines * Preserve selection on wrapText * Remove comment about cursor position * Document transformation "wratTextAtRange" --- docs/reference/models/transform.md | 11 ++++++ lib/models/state.js | 26 +++++++++++++ lib/models/transform.js | 4 +- lib/models/transforms.js | 22 +++++++++++ .../fixtures/wrap-text/across-blocks/index.js | 37 +++++++++++++++++++ .../wrap-text/across-blocks/input.yaml | 12 ++++++ .../wrap-text/across-blocks/output.yaml | 12 ++++++ .../wrap-text/across-inlines/index.js | 37 +++++++++++++++++++ .../wrap-text/across-inlines/input.yaml | 15 ++++++++ .../wrap-text/across-inlines/output.yaml | 15 ++++++++ .../fixtures/wrap-text/empty-block/index.js | 36 ++++++++++++++++++ .../fixtures/wrap-text/empty-block/input.yaml | 7 ++++ .../wrap-text/empty-block/output.yaml | 7 ++++ .../fixtures/wrap-text/end-of-block/index.js | 36 ++++++++++++++++++ .../wrap-text/end-of-block/input.yaml | 7 ++++ .../wrap-text/end-of-block/output.yaml | 7 ++++ .../wrap-text/middle-of-block/index.js | 36 ++++++++++++++++++ .../wrap-text/middle-of-block/input.yaml | 7 ++++ .../wrap-text/middle-of-block/output.yaml | 7 ++++ .../wrap-text/start-of-block/index.js | 36 ++++++++++++++++++ .../wrap-text/start-of-block/input.yaml | 7 ++++ .../wrap-text/start-of-block/output.yaml | 7 ++++ .../fixtures/wrap-text/whole-block/index.js | 36 ++++++++++++++++++ .../fixtures/wrap-text/whole-block/input.yaml | 7 ++++ .../wrap-text/whole-block/output.yaml | 7 ++++ .../wrap-text/without-suffix/index.js | 36 ++++++++++++++++++ .../wrap-text/without-suffix/input.yaml | 7 ++++ .../wrap-text/without-suffix/output.yaml | 7 ++++ 28 files changed, 490 insertions(+), 1 deletion(-) create mode 100644 test/transforms/fixtures/wrap-text/across-blocks/index.js create mode 100644 test/transforms/fixtures/wrap-text/across-blocks/input.yaml create mode 100644 test/transforms/fixtures/wrap-text/across-blocks/output.yaml create mode 100644 test/transforms/fixtures/wrap-text/across-inlines/index.js create mode 100644 test/transforms/fixtures/wrap-text/across-inlines/input.yaml create mode 100644 test/transforms/fixtures/wrap-text/across-inlines/output.yaml create mode 100644 test/transforms/fixtures/wrap-text/empty-block/index.js create mode 100644 test/transforms/fixtures/wrap-text/empty-block/input.yaml create mode 100644 test/transforms/fixtures/wrap-text/empty-block/output.yaml create mode 100644 test/transforms/fixtures/wrap-text/end-of-block/index.js create mode 100644 test/transforms/fixtures/wrap-text/end-of-block/input.yaml create mode 100644 test/transforms/fixtures/wrap-text/end-of-block/output.yaml create mode 100644 test/transforms/fixtures/wrap-text/middle-of-block/index.js create mode 100644 test/transforms/fixtures/wrap-text/middle-of-block/input.yaml create mode 100644 test/transforms/fixtures/wrap-text/middle-of-block/output.yaml create mode 100644 test/transforms/fixtures/wrap-text/start-of-block/index.js create mode 100644 test/transforms/fixtures/wrap-text/start-of-block/input.yaml create mode 100644 test/transforms/fixtures/wrap-text/start-of-block/output.yaml create mode 100644 test/transforms/fixtures/wrap-text/whole-block/index.js create mode 100644 test/transforms/fixtures/wrap-text/whole-block/input.yaml create mode 100644 test/transforms/fixtures/wrap-text/whole-block/output.yaml create mode 100644 test/transforms/fixtures/wrap-text/without-suffix/index.js create mode 100644 test/transforms/fixtures/wrap-text/without-suffix/input.yaml create mode 100644 test/transforms/fixtures/wrap-text/without-suffix/output.yaml diff --git a/docs/reference/models/transform.md b/docs/reference/models/transform.md index f3927c5af..48f2a2379 100644 --- a/docs/reference/models/transform.md +++ b/docs/reference/models/transform.md @@ -32,6 +32,7 @@ Transform methods can either operate on the [`Document`](./document.md), the [`S - [`unwrapInline`](#unwrapinline) - [`wrapBlock`](#wrapblock) - [`wrapInline`](#wrapinline) + - [`wrapText`](#wraptext) - [Selection Transforms](#selection-transforms) - [`blur`](#blur) - [`collapseTo{Edge}Of`](#collapsetoedgeof) @@ -67,6 +68,7 @@ Transform methods can either operate on the [`Document`](./document.md), the [`S - [`unwrapInlineAtRange`](#unwrapinlineatrange) - [`wrapBlockAtRange`](#wrapblockatrange) - [`wrapInlineAtRange`](#wrapinlineatrange) + - [`wrapTextAtRange`](#wraptextatrange) - [History Transforms](#history-transforms) - [`redo`](#redo) - [`undo`](#undo) @@ -185,6 +187,11 @@ Wrap the [`Block`](./block.md) nodes in the current selection with a new [`Block Wrap the [`Inline`](./inline.md) nodes in the current selection with a new [`Inline`](./inline.md) node of `type`, with optional `data`. +### `wrapText` +`wrapText(before: String, after: String) => Transform` + +Surround the text in the current selection. + ## Selection Transforms @@ -370,6 +377,10 @@ Wrap the [`Block`](./block.md) nodes in a `range` with a new [`Block`](./block.m Wrap the [`Inline`](./inline.md) nodes in a `range` with a new [`Inline`](./inline.md) node with `properties`. For convenience, you can pass a `type` string or `properties` object. +### `wrapTextAtRange` +`wrapTextAtRange(range: Selection, prefix: String, suffix: String) => Transform` + +Surround the text in a `range`. ## History Transforms diff --git a/lib/models/state.js b/lib/models/state.js index 23fa45a1e..911ca42ea 100644 --- a/lib/models/state.js +++ b/lib/models/state.js @@ -1137,6 +1137,32 @@ class State extends new Record(DEFAULTS) { return state } + /** + * Wrap the current selection with prefix/suffix. + * + * @param {String} prefix + * @param {String} suffix + * @return {State} state + */ + + wrapText(prefix, suffix = prefix) { + let { document, selection } = this + let { startKey, endKey, startOffset, endOffset } = selection + let acrossBlocks = (startKey !== endKey) + + document = document.wrapTextAtRange(selection, prefix, suffix) + + selection = selection + .merge({ + anchorKey: startKey, + anchorOffset: startOffset + prefix.length, + focusKey: endKey, + focusOffset: acrossBlocks ? endOffset : endOffset + prefix.length + }) + + return this.merge({ document, selection }) + } + /** * Unwrap the current selection from an inline parent with `properties`. * diff --git a/lib/models/transform.js b/lib/models/transform.js index e185b84c0..eee92c2e0 100644 --- a/lib/models/transform.js +++ b/lib/models/transform.js @@ -45,6 +45,7 @@ const DOCUMENT_RANGE_TRANSFORMS = [ 'unwrapInlineAtRange', 'wrapBlockAtRange', 'wrapInlineAtRange', + 'wrapTextAtRange' ] /** @@ -101,7 +102,8 @@ const STATE_DOCUMENT_TRANSFORMS = [ 'unwrapBlock', 'unwrapInline', 'wrapBlock', - 'wrapInline' + 'wrapInline', + 'wrapText' ] /** diff --git a/lib/models/transforms.js b/lib/models/transforms.js index d0614f05f..c26f5609a 100644 --- a/lib/models/transforms.js +++ b/lib/models/transforms.js @@ -1017,6 +1017,28 @@ const Transforms = { }) return node.normalize() +}, + + /** + * Wrap the text in a `range` in a prefix/suffix. + * + * @param {Selection} range + * @param {String} prefix + * @param {String} suffix + * @return {Node} node + */ + + wrapTextAtRange(range, prefix, suffix = prefix) { + let withPrefix = this.insertTextAtRange(range.collapseToAnchor(), prefix) + let acrossBlocks = (range.startKey !== range.endKey) + let withSuffix = withPrefix.insertTextAtRange( + range + .collapseToFocus() + .moveForward(acrossBlocks ? 0 : prefix.length), + suffix + ) + + return withSuffix } } diff --git a/test/transforms/fixtures/wrap-text/across-blocks/index.js b/test/transforms/fixtures/wrap-text/across-blocks/index.js new file mode 100644 index 000000000..b0a5f9ca3 --- /dev/null +++ b/test/transforms/fixtures/wrap-text/across-blocks/index.js @@ -0,0 +1,37 @@ + +import assert from 'assert' + +export default function (state) { + const { document, selection } = state + const texts = document.getTexts() + const first = texts.first() + const last = texts.last() + const range = selection.merge({ + anchorKey: first.key, + anchorOffset: 2, + focusKey: last.key, + focusOffset: 2 + }) + + const next = state + .transform() + .moveTo(range) + .wrapText('[[', ']]') + .apply() + + + const updated = next.document.getTexts() + + assert.deepEqual( + next.selection.toJS(), + range.merge({ + anchorKey: updated.get(0).key, + anchorOffset: 4, + focusKey: updated.get(1).key, + focusOffset: 2, + isBackward: false + }).toJS() + ) + + return next +} diff --git a/test/transforms/fixtures/wrap-text/across-blocks/input.yaml b/test/transforms/fixtures/wrap-text/across-blocks/input.yaml new file mode 100644 index 000000000..881deb1d9 --- /dev/null +++ b/test/transforms/fixtures/wrap-text/across-blocks/input.yaml @@ -0,0 +1,12 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word + - kind: block + type: paragraph + nodes: + - kind: text + text: another diff --git a/test/transforms/fixtures/wrap-text/across-blocks/output.yaml b/test/transforms/fixtures/wrap-text/across-blocks/output.yaml new file mode 100644 index 000000000..1d855199c --- /dev/null +++ b/test/transforms/fixtures/wrap-text/across-blocks/output.yaml @@ -0,0 +1,12 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: wo[[rd + - kind: block + type: paragraph + nodes: + - kind: text + text: an]]other diff --git a/test/transforms/fixtures/wrap-text/across-inlines/index.js b/test/transforms/fixtures/wrap-text/across-inlines/index.js new file mode 100644 index 000000000..b0a5f9ca3 --- /dev/null +++ b/test/transforms/fixtures/wrap-text/across-inlines/index.js @@ -0,0 +1,37 @@ + +import assert from 'assert' + +export default function (state) { + const { document, selection } = state + const texts = document.getTexts() + const first = texts.first() + const last = texts.last() + const range = selection.merge({ + anchorKey: first.key, + anchorOffset: 2, + focusKey: last.key, + focusOffset: 2 + }) + + const next = state + .transform() + .moveTo(range) + .wrapText('[[', ']]') + .apply() + + + const updated = next.document.getTexts() + + assert.deepEqual( + next.selection.toJS(), + range.merge({ + anchorKey: updated.get(0).key, + anchorOffset: 4, + focusKey: updated.get(1).key, + focusOffset: 2, + isBackward: false + }).toJS() + ) + + return next +} diff --git a/test/transforms/fixtures/wrap-text/across-inlines/input.yaml b/test/transforms/fixtures/wrap-text/across-inlines/input.yaml new file mode 100644 index 000000000..c17ccbcf2 --- /dev/null +++ b/test/transforms/fixtures/wrap-text/across-inlines/input.yaml @@ -0,0 +1,15 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: inline + type: link + nodes: + - kind: text + text: word + - kind: inline + type: link + nodes: + - kind: text + text: another diff --git a/test/transforms/fixtures/wrap-text/across-inlines/output.yaml b/test/transforms/fixtures/wrap-text/across-inlines/output.yaml new file mode 100644 index 000000000..780ee42bd --- /dev/null +++ b/test/transforms/fixtures/wrap-text/across-inlines/output.yaml @@ -0,0 +1,15 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: inline + type: link + nodes: + - kind: text + text: wo[[rd + - kind: inline + type: link + nodes: + - kind: text + text: an]]other diff --git a/test/transforms/fixtures/wrap-text/empty-block/index.js b/test/transforms/fixtures/wrap-text/empty-block/index.js new file mode 100644 index 000000000..9651ac27e --- /dev/null +++ b/test/transforms/fixtures/wrap-text/empty-block/index.js @@ -0,0 +1,36 @@ + +import assert from 'assert' + +export default function (state) { + const { document, selection } = state + const texts = document.getTexts() + const first = texts.first() + const range = selection.merge({ + anchorKey: first.key, + anchorOffset: 0, + focusKey: first.key, + focusOffset: 0 + }) + + const next = state + .transform() + .moveTo(range) + .wrapText('[[', ']]') + .apply() + + + const updated = next.document.getTexts().get(0) + + assert.deepEqual( + next.selection.toJS(), + range.merge({ + anchorKey: updated.key, + anchorOffset: 2, + focusKey: updated.key, + focusOffset: 2, + isBackward: false + }).toJS() + ) + + return next +} diff --git a/test/transforms/fixtures/wrap-text/empty-block/input.yaml b/test/transforms/fixtures/wrap-text/empty-block/input.yaml new file mode 100644 index 000000000..f6de4d08a --- /dev/null +++ b/test/transforms/fixtures/wrap-text/empty-block/input.yaml @@ -0,0 +1,7 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: "" diff --git a/test/transforms/fixtures/wrap-text/empty-block/output.yaml b/test/transforms/fixtures/wrap-text/empty-block/output.yaml new file mode 100644 index 000000000..aae1e88b1 --- /dev/null +++ b/test/transforms/fixtures/wrap-text/empty-block/output.yaml @@ -0,0 +1,7 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: "[[]]" diff --git a/test/transforms/fixtures/wrap-text/end-of-block/index.js b/test/transforms/fixtures/wrap-text/end-of-block/index.js new file mode 100644 index 000000000..c2d7cd51b --- /dev/null +++ b/test/transforms/fixtures/wrap-text/end-of-block/index.js @@ -0,0 +1,36 @@ + +import assert from 'assert' + +export default function (state) { + const { document, selection } = state + const texts = document.getTexts() + const first = texts.first() + const range = selection.merge({ + anchorKey: first.key, + anchorOffset: 2, + focusKey: first.key, + focusOffset: 4 + }) + + const next = state + .transform() + .moveTo(range) + .wrapText('[[', ']]') + .apply() + + + const updated = next.document.getTexts().get(0) + + assert.deepEqual( + next.selection.toJS(), + range.merge({ + anchorKey: updated.key, + anchorOffset: 4, + focusKey: updated.key, + focusOffset: 6, + isBackward: false + }).toJS() + ) + + return next +} diff --git a/test/transforms/fixtures/wrap-text/end-of-block/input.yaml b/test/transforms/fixtures/wrap-text/end-of-block/input.yaml new file mode 100644 index 000000000..27f668fe2 --- /dev/null +++ b/test/transforms/fixtures/wrap-text/end-of-block/input.yaml @@ -0,0 +1,7 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word diff --git a/test/transforms/fixtures/wrap-text/end-of-block/output.yaml b/test/transforms/fixtures/wrap-text/end-of-block/output.yaml new file mode 100644 index 000000000..d6f4e2b53 --- /dev/null +++ b/test/transforms/fixtures/wrap-text/end-of-block/output.yaml @@ -0,0 +1,7 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: wo[[rd]] diff --git a/test/transforms/fixtures/wrap-text/middle-of-block/index.js b/test/transforms/fixtures/wrap-text/middle-of-block/index.js new file mode 100644 index 000000000..92966511c --- /dev/null +++ b/test/transforms/fixtures/wrap-text/middle-of-block/index.js @@ -0,0 +1,36 @@ + +import assert from 'assert' + +export default function (state) { + const { document, selection } = state + const texts = document.getTexts() + const first = texts.first() + const range = selection.merge({ + anchorKey: first.key, + anchorOffset: 1, + focusKey: first.key, + focusOffset: 3 + }) + + const next = state + .transform() + .moveTo(range) + .wrapText('[[', ']]') + .apply() + + + const updated = next.document.getTexts().get(0) + + assert.deepEqual( + next.selection.toJS(), + range.merge({ + anchorKey: updated.key, + anchorOffset: 3, + focusKey: updated.key, + focusOffset: 5, + isBackward: false + }).toJS() + ) + + return next +} diff --git a/test/transforms/fixtures/wrap-text/middle-of-block/input.yaml b/test/transforms/fixtures/wrap-text/middle-of-block/input.yaml new file mode 100644 index 000000000..27f668fe2 --- /dev/null +++ b/test/transforms/fixtures/wrap-text/middle-of-block/input.yaml @@ -0,0 +1,7 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word diff --git a/test/transforms/fixtures/wrap-text/middle-of-block/output.yaml b/test/transforms/fixtures/wrap-text/middle-of-block/output.yaml new file mode 100644 index 000000000..d6bdaa3c1 --- /dev/null +++ b/test/transforms/fixtures/wrap-text/middle-of-block/output.yaml @@ -0,0 +1,7 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: w[[or]]d diff --git a/test/transforms/fixtures/wrap-text/start-of-block/index.js b/test/transforms/fixtures/wrap-text/start-of-block/index.js new file mode 100644 index 000000000..4ff35360e --- /dev/null +++ b/test/transforms/fixtures/wrap-text/start-of-block/index.js @@ -0,0 +1,36 @@ + +import assert from 'assert' + +export default function (state) { + const { document, selection } = state + const texts = document.getTexts() + const first = texts.first() + const range = selection.merge({ + anchorKey: first.key, + anchorOffset: 0, + focusKey: first.key, + focusOffset: 2 + }) + + const next = state + .transform() + .moveTo(range) + .wrapText('[[', ']]') + .apply() + + + const updated = next.document.getTexts().get(0) + + assert.deepEqual( + next.selection.toJS(), + range.merge({ + anchorKey: updated.key, + anchorOffset: 2, + focusKey: updated.key, + focusOffset: 4, + isBackward: false + }).toJS() + ) + + return next +} diff --git a/test/transforms/fixtures/wrap-text/start-of-block/input.yaml b/test/transforms/fixtures/wrap-text/start-of-block/input.yaml new file mode 100644 index 000000000..27f668fe2 --- /dev/null +++ b/test/transforms/fixtures/wrap-text/start-of-block/input.yaml @@ -0,0 +1,7 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word diff --git a/test/transforms/fixtures/wrap-text/start-of-block/output.yaml b/test/transforms/fixtures/wrap-text/start-of-block/output.yaml new file mode 100644 index 000000000..2e61de8bc --- /dev/null +++ b/test/transforms/fixtures/wrap-text/start-of-block/output.yaml @@ -0,0 +1,7 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: "[[wo]]rd" diff --git a/test/transforms/fixtures/wrap-text/whole-block/index.js b/test/transforms/fixtures/wrap-text/whole-block/index.js new file mode 100644 index 000000000..485caa39a --- /dev/null +++ b/test/transforms/fixtures/wrap-text/whole-block/index.js @@ -0,0 +1,36 @@ + +import assert from 'assert' + +export default function (state) { + const { document, selection } = state + const texts = document.getTexts() + const first = texts.first() + const range = selection.merge({ + anchorKey: first.key, + anchorOffset: 0, + focusKey: first.key, + focusOffset: 4 + }) + + const next = state + .transform() + .moveTo(range) + .wrapText('[[', ']]') + .apply() + + + const updated = next.document.getTexts().get(0) + + assert.deepEqual( + next.selection.toJS(), + range.merge({ + anchorKey: updated.key, + anchorOffset: 2, + focusKey: updated.key, + focusOffset: 6, + isBackward: false + }).toJS() + ) + + return next +} diff --git a/test/transforms/fixtures/wrap-text/whole-block/input.yaml b/test/transforms/fixtures/wrap-text/whole-block/input.yaml new file mode 100644 index 000000000..27f668fe2 --- /dev/null +++ b/test/transforms/fixtures/wrap-text/whole-block/input.yaml @@ -0,0 +1,7 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word diff --git a/test/transforms/fixtures/wrap-text/whole-block/output.yaml b/test/transforms/fixtures/wrap-text/whole-block/output.yaml new file mode 100644 index 000000000..6ca0333a2 --- /dev/null +++ b/test/transforms/fixtures/wrap-text/whole-block/output.yaml @@ -0,0 +1,7 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: "[[word]]" diff --git a/test/transforms/fixtures/wrap-text/without-suffix/index.js b/test/transforms/fixtures/wrap-text/without-suffix/index.js new file mode 100644 index 000000000..a4ebe9f23 --- /dev/null +++ b/test/transforms/fixtures/wrap-text/without-suffix/index.js @@ -0,0 +1,36 @@ + +import assert from 'assert' + +export default function (state) { + const { document, selection } = state + const texts = document.getTexts() + const first = texts.first() + const range = selection.merge({ + anchorKey: first.key, + anchorOffset: 1, + focusKey: first.key, + focusOffset: 3 + }) + + const next = state + .transform() + .moveTo(range) + .wrapText('**') + .apply() + + + const updated = next.document.getTexts().get(0) + + assert.deepEqual( + next.selection.toJS(), + range.merge({ + anchorKey: updated.key, + anchorOffset: 3, + focusKey: updated.key, + focusOffset: 5, + isBackward: false + }).toJS() + ) + + return next +} diff --git a/test/transforms/fixtures/wrap-text/without-suffix/input.yaml b/test/transforms/fixtures/wrap-text/without-suffix/input.yaml new file mode 100644 index 000000000..27f668fe2 --- /dev/null +++ b/test/transforms/fixtures/wrap-text/without-suffix/input.yaml @@ -0,0 +1,7 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: word diff --git a/test/transforms/fixtures/wrap-text/without-suffix/output.yaml b/test/transforms/fixtures/wrap-text/without-suffix/output.yaml new file mode 100644 index 000000000..6140323dc --- /dev/null +++ b/test/transforms/fixtures/wrap-text/without-suffix/output.yaml @@ -0,0 +1,7 @@ + +nodes: + - kind: block + type: paragraph + nodes: + - kind: text + text: w**or**d