mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-02-01 05:16:10 +01:00
Introduces state.activeMarks and has add/remove/toggleMark use it instead of state.marks (#990)
* WIP getting raw marks arrays from the current range * Always handle marksRaw as Array, fixes tests/lint * Clean up * Fixes collapsed selection raw marks, simpler _every_ condition, harmonize toolbar buttons in rich-text example * raw -> perCharacter * Add tests for toggleMark collapsed selection * Add .DS_Store to .gitignore * Added test for toggleMark add in partially marked selection, with and without other marks * Added docs for state.marksPerCharacter * replace marksPerCharacter with activeMarks * Update the other examples * Clarify getActiveMarksAtRange * AddMark/RemoveMark to use getActiveMarksByRange * User activeMarks for toggle§MarkAtRange transform
This commit is contained in:
parent
d21728ed86
commit
786050f732
3
.gitignore
vendored
3
.gitignore
vendored
@ -14,3 +14,6 @@ _book
|
||||
# NPM files.
|
||||
node_modules
|
||||
npm-debug.log
|
||||
|
||||
# Mac stuff.
|
||||
.DS_Store
|
||||
|
@ -18,6 +18,7 @@ For convenience, in addition to transforms, many of the [`Selection`](./selectio
|
||||
- [`{edge}Text`](#edgetext)
|
||||
- [`{edge}Block`](#edgeblock)
|
||||
- [`marks`](#marks)
|
||||
- [`activeMarks`](#activeMarks)
|
||||
- [`blocks`](#blocks)
|
||||
- [`fragment`](#fragment)
|
||||
- [`inlines`](#inlines)
|
||||
@ -79,7 +80,12 @@ Get the leaf [`Block`](./block.md) node at `{edge}`. Where `{edge}` is one of: `
|
||||
### `marks`
|
||||
`Set`
|
||||
|
||||
Get a set of the [`Marks`](./mark.md) in the current selection.
|
||||
Get a set of the [`Marks`](./mark.md) in the current selection.
|
||||
|
||||
### `activeMarks`
|
||||
`Set`
|
||||
|
||||
Get a subset of the [`Marks`](./mark.md) that are present in _all_ the characters in the current selection. It can be used to determine the active/inactive state of toolbar buttons corresponding to marks, based on the usual rich text editing conventions.
|
||||
|
||||
### `blocks`
|
||||
`List`
|
||||
|
@ -70,7 +70,7 @@ class RichText extends React.Component {
|
||||
|
||||
hasMark = (type) => {
|
||||
const { state } = this.state
|
||||
return state.marks.some(mark => mark.type == type)
|
||||
return state.activeMarks.some(mark => mark.type == type)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,7 +58,7 @@ class HoveringMenu extends React.Component {
|
||||
|
||||
hasMark = (type) => {
|
||||
const { state } = this.state
|
||||
return state.marks.some(mark => mark.type == type)
|
||||
return state.activeMarks.some(mark => mark.type == type)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,7 +65,7 @@ class Iframes extends React.Component {
|
||||
|
||||
hasMark = (type) => {
|
||||
const { state } = this.state
|
||||
return state.marks.some(mark => mark.type == type)
|
||||
return state.activeMarks.some(mark => mark.type == type)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,7 +70,7 @@ class RichText extends React.Component {
|
||||
|
||||
hasMark = (type) => {
|
||||
const { state } = this.state
|
||||
return state.marks.some(mark => mark.type == type)
|
||||
return state.activeMarks.some(mark => mark.type == type)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -885,6 +885,18 @@ const Node = {
|
||||
return new OrderedSet(array)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a set of the active marks in a `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @return {Set<Mark>}
|
||||
*/
|
||||
|
||||
getActiveMarksAtRange(range) {
|
||||
const array = this.getActiveMarksAtRangeAsArray(range)
|
||||
return new Set(array)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a set of the marks in a `range`.
|
||||
*
|
||||
@ -920,6 +932,36 @@ const Node = {
|
||||
}, [])
|
||||
},
|
||||
|
||||
getActiveMarksAtRangeAsArray(range) {
|
||||
range = range.normalize(this)
|
||||
const { startKey, startOffset } = range
|
||||
|
||||
// If the range is collapsed at the start of the node, check the previous.
|
||||
if (range.isCollapsed && startOffset == 0) {
|
||||
const previous = this.getPreviousText(startKey)
|
||||
if (!previous || !previous.length) return []
|
||||
const char = previous.characters.get(previous.length - 1)
|
||||
return char.marks.toArray()
|
||||
}
|
||||
|
||||
// If the range is collapsed, check the character before the start.
|
||||
if (range.isCollapsed) {
|
||||
const text = this.getDescendant(startKey)
|
||||
const char = text.characters.get(range.startOffset - 1)
|
||||
return char.marks.toArray()
|
||||
}
|
||||
|
||||
// Otherwise, get a set of the marks for each character in the range.
|
||||
const chars = this.getCharactersAtRange(range)
|
||||
const first = chars.first()
|
||||
let memo = first.marks
|
||||
chars.slice(1).forEach((char) => {
|
||||
memo = memo.intersect(char.marks)
|
||||
return memo.size != 0
|
||||
})
|
||||
return memo.toArray()
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all of the marks that match a `type`.
|
||||
*
|
||||
@ -1852,6 +1894,8 @@ memoize(Node, [
|
||||
|
||||
memoize(Node, [
|
||||
'areDescendantsSorted',
|
||||
'getActiveMarksAtRange',
|
||||
'getActiveMarksAtRangeAsArray',
|
||||
'getAncestors',
|
||||
'getBlocksAtRange',
|
||||
'getBlocksAtRangeAsArray',
|
||||
|
@ -401,6 +401,18 @@ class State extends new Record(DEFAULTS) {
|
||||
: this.selection.marks || this.document.getMarksAtRange(this.selection)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the active marks of the current selection.
|
||||
*
|
||||
* @return {Set<Mark>}
|
||||
*/
|
||||
|
||||
get activeMarks() {
|
||||
return this.selection.isUnset
|
||||
? new Set()
|
||||
: this.selection.marks || this.document.getActiveMarksAtRange(this.selection)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block nodes in the current selection.
|
||||
*
|
||||
|
@ -33,7 +33,7 @@ Transforms.addMark = (transform, mark) => {
|
||||
return
|
||||
}
|
||||
|
||||
const marks = document.getMarksAtRange(selection).add(mark)
|
||||
const marks = document.getActiveMarksAtRange(selection).add(mark)
|
||||
const sel = selection.set('marks', marks)
|
||||
transform.select(sel)
|
||||
}
|
||||
@ -346,7 +346,7 @@ Transforms.removeMark = (transform, mark) => {
|
||||
return
|
||||
}
|
||||
|
||||
const marks = document.getMarksAtRange(selection).remove(mark)
|
||||
const marks = document.getActiveMarksAtRange(selection).remove(mark)
|
||||
const sel = selection.set('marks', marks)
|
||||
transform.select(sel)
|
||||
}
|
||||
@ -362,7 +362,7 @@ Transforms.removeMark = (transform, mark) => {
|
||||
Transforms.toggleMark = (transform, mark) => {
|
||||
mark = Normalize.mark(mark)
|
||||
const { state } = transform
|
||||
const exists = state.marks.some(m => m.equals(mark))
|
||||
const exists = state.activeMarks.some(m => m.equals(mark))
|
||||
|
||||
if (exists) {
|
||||
transform.removeMark(mark)
|
||||
|
@ -979,7 +979,7 @@ Transforms.toggleMarkAtRange = (transform, range, mark, options = {}) => {
|
||||
const { normalize = true } = options
|
||||
const { state } = transform
|
||||
const { document } = state
|
||||
const marks = document.getMarksAtRange(range)
|
||||
const marks = document.getActiveMarksAtRange(range)
|
||||
const exists = marks.some(m => m.equals(mark))
|
||||
|
||||
if (exists) {
|
||||
|
@ -0,0 +1,25 @@
|
||||
|
||||
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()
|
||||
.select(range)
|
||||
.toggleMark('bold')
|
||||
.insertText('a')
|
||||
.apply()
|
||||
|
||||
assert.deepEqual(next.selection.toJS(), range.move(1).toJS())
|
||||
|
||||
return next
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: word
|
@ -0,0 +1,11 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: a
|
||||
marks:
|
||||
- type: bold
|
||||
- text: word
|
@ -7,16 +7,16 @@ export default function (state) {
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
anchorOffset: 4,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
focusOffset: 4
|
||||
})
|
||||
|
||||
const next = state
|
||||
.transform()
|
||||
.select(range)
|
||||
.toggleMark('bold')
|
||||
.insertText('a')
|
||||
.insertText('s')
|
||||
.apply()
|
||||
|
||||
assert.deepEqual(next.selection.toJS(), range.move(1).toJS())
|
||||
|
@ -5,7 +5,7 @@ nodes:
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: a
|
||||
- text: word
|
||||
- text: s
|
||||
marks:
|
||||
- type: bold
|
||||
- text: word
|
||||
|
@ -0,0 +1,24 @@
|
||||
|
||||
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: 3
|
||||
})
|
||||
|
||||
const next = state
|
||||
.transform()
|
||||
.select(range)
|
||||
.toggleMark('bold')
|
||||
.apply()
|
||||
|
||||
assert.deepEqual(next.selection.toJS(), range.toJS())
|
||||
|
||||
return next
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: a
|
||||
marks:
|
||||
- type: bold
|
||||
- text: word
|
||||
marks:
|
||||
- type: italic
|
@ -0,0 +1,17 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: a
|
||||
marks:
|
||||
- type: bold
|
||||
- text: wo
|
||||
marks:
|
||||
- type: italic
|
||||
- type: bold
|
||||
- text: rd
|
||||
marks:
|
||||
- type: italic
|
@ -0,0 +1,23 @@
|
||||
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: 5
|
||||
})
|
||||
|
||||
const next = state
|
||||
.transform()
|
||||
.select(range)
|
||||
.toggleMark('bold')
|
||||
.apply()
|
||||
|
||||
assert.deepEqual(next.selection.toJS(), range.toJS())
|
||||
|
||||
return next
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: a
|
||||
- text: word
|
||||
marks:
|
||||
- type: bold
|
@ -0,0 +1,10 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: aword
|
||||
marks:
|
||||
- type: bold
|
@ -0,0 +1,26 @@
|
||||
|
||||
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()
|
||||
.select(range)
|
||||
.toggleMark('bold')
|
||||
.toggleMark('bold')
|
||||
.insertText('a')
|
||||
.apply()
|
||||
|
||||
assert.deepEqual(next.selection.toJS(), range.move(1).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: aword
|
@ -7,9 +7,9 @@ export default function (state) {
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
anchorOffset: 4,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
focusOffset: 4
|
||||
})
|
||||
|
||||
const next = state
|
||||
@ -17,7 +17,7 @@ export default function (state) {
|
||||
.select(range)
|
||||
.toggleMark('bold')
|
||||
.toggleMark('bold')
|
||||
.insertText('a')
|
||||
.insertText('s')
|
||||
.apply()
|
||||
|
||||
assert.deepEqual(next.selection.toJS(), range.move(1).toJS())
|
||||
|
@ -4,4 +4,4 @@ nodes:
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: aword
|
||||
text: words
|
||||
|
Loading…
x
Reference in New Issue
Block a user