1
0
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:
Dan Burzo 2017-09-06 03:48:19 +03:00 committed by Ian Storm Taylor
parent d21728ed86
commit 786050f732
26 changed files with 263 additions and 18 deletions

3
.gitignore vendored
View File

@ -14,3 +14,6 @@ _book
# NPM files.
node_modules
npm-debug.log
# Mac stuff.
.DS_Store

View File

@ -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`

View File

@ -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)
}
/**

View File

@ -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)
}
/**

View File

@ -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)
}
/**

View File

@ -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)
}
/**

View File

@ -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',

View File

@ -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.
*

View File

@ -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)

View File

@ -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) {

View File

@ -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
}

View File

@ -0,0 +1,7 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
text: word

View File

@ -0,0 +1,11 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: a
marks:
- type: bold
- text: word

View File

@ -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())

View File

@ -5,7 +5,7 @@ nodes:
nodes:
- kind: text
ranges:
- text: a
- text: word
- text: s
marks:
- type: bold
- text: word

View File

@ -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
}

View File

@ -0,0 +1,13 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: a
marks:
- type: bold
- text: word
marks:
- type: italic

View File

@ -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

View File

@ -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
}

View File

@ -0,0 +1,10 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: a
- text: word
marks:
- type: bold

View File

@ -0,0 +1,10 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
ranges:
- text: aword
marks:
- type: bold

View File

@ -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
}

View File

@ -0,0 +1,7 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
text: word

View File

@ -0,0 +1,7 @@
nodes:
- kind: block
type: paragraph
nodes:
- kind: text
text: aword

View File

@ -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())

View File

@ -4,4 +4,4 @@ nodes:
type: paragraph
nodes:
- kind: text
text: aword
text: words