mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-19 05:31:56 +02:00
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"
This commit is contained in:
committed by
Ian Storm Taylor
parent
10cfb6e48b
commit
17f703cecb
@@ -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
|
||||
|
||||
|
@@ -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`.
|
||||
*
|
||||
|
@@ -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'
|
||||
]
|
||||
|
||||
/**
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
37
test/transforms/fixtures/wrap-text/across-blocks/index.js
Normal file
37
test/transforms/fixtures/wrap-text/across-blocks/index.js
Normal file
@@ -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
|
||||
}
|
12
test/transforms/fixtures/wrap-text/across-blocks/input.yaml
Normal file
12
test/transforms/fixtures/wrap-text/across-blocks/input.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: word
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: another
|
12
test/transforms/fixtures/wrap-text/across-blocks/output.yaml
Normal file
12
test/transforms/fixtures/wrap-text/across-blocks/output.yaml
Normal file
@@ -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
|
37
test/transforms/fixtures/wrap-text/across-inlines/index.js
Normal file
37
test/transforms/fixtures/wrap-text/across-inlines/index.js
Normal file
@@ -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
|
||||
}
|
15
test/transforms/fixtures/wrap-text/across-inlines/input.yaml
Normal file
15
test/transforms/fixtures/wrap-text/across-inlines/input.yaml
Normal file
@@ -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
|
@@ -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
|
36
test/transforms/fixtures/wrap-text/empty-block/index.js
Normal file
36
test/transforms/fixtures/wrap-text/empty-block/index.js
Normal file
@@ -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
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: ""
|
@@ -0,0 +1,7 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: "[[]]"
|
36
test/transforms/fixtures/wrap-text/end-of-block/index.js
Normal file
36
test/transforms/fixtures/wrap-text/end-of-block/index.js
Normal file
@@ -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
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: word
|
@@ -0,0 +1,7 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: wo[[rd]]
|
36
test/transforms/fixtures/wrap-text/middle-of-block/index.js
Normal file
36
test/transforms/fixtures/wrap-text/middle-of-block/index.js
Normal file
@@ -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
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: word
|
@@ -0,0 +1,7 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: w[[or]]d
|
36
test/transforms/fixtures/wrap-text/start-of-block/index.js
Normal file
36
test/transforms/fixtures/wrap-text/start-of-block/index.js
Normal file
@@ -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
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: word
|
@@ -0,0 +1,7 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: "[[wo]]rd"
|
36
test/transforms/fixtures/wrap-text/whole-block/index.js
Normal file
36
test/transforms/fixtures/wrap-text/whole-block/index.js
Normal file
@@ -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
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: word
|
@@ -0,0 +1,7 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: "[[word]]"
|
36
test/transforms/fixtures/wrap-text/without-suffix/index.js
Normal file
36
test/transforms/fixtures/wrap-text/without-suffix/index.js
Normal file
@@ -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
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: word
|
@@ -0,0 +1,7 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: w**or**d
|
Reference in New Issue
Block a user