mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-09-02 11:42:53 +02:00
Allow void elements to receive marks (#5135)
Some void elements are effectively stand-ins for text, such as with the mentions example, where the mention element renders the character's name. Users might want to format Void elements like this with bold, or set their font and size, so `editor.markableVoid` tells Slate whether or not to apply Marks to the text children of void elements. - Adds `markableVoid()` as a schema-specific overrideable test. - Changes `addMark` and `removeMark` so marks can apply to voids. Also changes behavior of collapsed selection so that if a markable Void is selected, the mark will be applied / removed. - Shows how `markableVoid()` can work in the mentions example
This commit is contained in:
@@ -28,6 +28,7 @@ export const createEditor = (): Editor => {
|
||||
marks: null,
|
||||
isInline: () => false,
|
||||
isVoid: () => false,
|
||||
markableVoid: () => false,
|
||||
onChange: () => {},
|
||||
|
||||
apply: (op: Operation) => {
|
||||
@@ -99,14 +100,35 @@ export const createEditor = (): Editor => {
|
||||
},
|
||||
|
||||
addMark: (key: string, value: any) => {
|
||||
const { selection } = editor
|
||||
const { selection, markableVoid } = editor
|
||||
|
||||
if (selection) {
|
||||
if (Range.isExpanded(selection)) {
|
||||
const match = (node: Node, path: Path) => {
|
||||
if (!Text.isText(node)) {
|
||||
return false // marks can only be applied to text
|
||||
}
|
||||
const [parentNode, parentPath] = Editor.parent(editor, path)
|
||||
return !editor.isVoid(parentNode) || editor.markableVoid(parentNode)
|
||||
}
|
||||
const expandedSelection = Range.isExpanded(selection)
|
||||
let markAcceptingVoidSelected = false
|
||||
if (!expandedSelection) {
|
||||
const [selectedNode, selectedPath] = Editor.node(editor, selection)
|
||||
if (selectedNode && match(selectedNode, selectedPath)) {
|
||||
const [parentNode] = Editor.parent(editor, selectedPath)
|
||||
markAcceptingVoidSelected =
|
||||
parentNode && editor.markableVoid(parentNode)
|
||||
}
|
||||
}
|
||||
if (expandedSelection || markAcceptingVoidSelected) {
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ [key]: value },
|
||||
{ match: Text.isText, split: true }
|
||||
{
|
||||
match,
|
||||
split: true,
|
||||
voids: true,
|
||||
}
|
||||
)
|
||||
} else {
|
||||
const marks = {
|
||||
@@ -281,10 +303,28 @@ export const createEditor = (): Editor => {
|
||||
const { selection } = editor
|
||||
|
||||
if (selection) {
|
||||
if (Range.isExpanded(selection)) {
|
||||
const match = (node: Node, path: Path) => {
|
||||
if (!Text.isText(node)) {
|
||||
return false // marks can only be applied to text
|
||||
}
|
||||
const [parentNode, parentPath] = Editor.parent(editor, path)
|
||||
return !editor.isVoid(parentNode) || editor.markableVoid(parentNode)
|
||||
}
|
||||
const expandedSelection = Range.isExpanded(selection)
|
||||
let markAcceptingVoidSelected = false
|
||||
if (!expandedSelection) {
|
||||
const [selectedNode, selectedPath] = Editor.node(editor, selection)
|
||||
if (selectedNode && match(selectedNode, selectedPath)) {
|
||||
const [parentNode] = Editor.parent(editor, selectedPath)
|
||||
markAcceptingVoidSelected =
|
||||
parentNode && editor.markableVoid(parentNode)
|
||||
}
|
||||
}
|
||||
if (expandedSelection || markAcceptingVoidSelected) {
|
||||
Transforms.unsetNodes(editor, key, {
|
||||
match: Text.isText,
|
||||
match,
|
||||
split: true,
|
||||
voids: true,
|
||||
})
|
||||
} else {
|
||||
const marks = { ...(Editor.marks(editor) || {}) }
|
||||
|
@@ -61,6 +61,7 @@ export interface BaseEditor {
|
||||
// Schema-specific node behaviors.
|
||||
isInline: (element: Element) => boolean
|
||||
isVoid: (element: Element) => boolean
|
||||
markableVoid: (element: Element) => boolean
|
||||
normalizeNode: (entry: NodeEntry) => void
|
||||
onChange: () => void
|
||||
|
||||
|
@@ -0,0 +1,35 @@
|
||||
/** @jsx jsx */
|
||||
import { Editor, Transforms } from 'slate'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const run = editor => {
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ someKey: true },
|
||||
{ match: n => Editor.isInline(editor, n) }
|
||||
)
|
||||
}
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
<text>word</text>
|
||||
<inline void alreadyHasAKey>
|
||||
<text />
|
||||
<cursor />
|
||||
</inline>
|
||||
<text />
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
export const output = (
|
||||
<editor>
|
||||
<block>
|
||||
<text>word</text>
|
||||
<inline void alreadyHasAKey someKey>
|
||||
<text />
|
||||
<cursor />
|
||||
</inline>
|
||||
<text />
|
||||
</block>
|
||||
</editor>
|
||||
)
|
@@ -0,0 +1,48 @@
|
||||
/** @jsx jsx */
|
||||
// Apply a mark across a range containing text with other marks and a void
|
||||
import { Editor, Transforms } from 'slate'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const run = editor => {
|
||||
Editor.addMark(editor, 'bold', true)
|
||||
}
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
<text>
|
||||
<anchor />
|
||||
word{' '}
|
||||
</text>
|
||||
<text italic>italic words </text>
|
||||
<inline void>
|
||||
<text />
|
||||
</inline>
|
||||
<text underline>
|
||||
{' '}
|
||||
underlined words
|
||||
<focus />
|
||||
</text>
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
export const output = (
|
||||
<editor>
|
||||
<block>
|
||||
<text bold>
|
||||
<anchor />
|
||||
word{' '}
|
||||
</text>
|
||||
<text italic bold>
|
||||
italic words{' '}
|
||||
</text>
|
||||
<inline void>
|
||||
<text />
|
||||
</inline>
|
||||
<text underline bold>
|
||||
{' '}
|
||||
underlined words
|
||||
<focus />
|
||||
</text>
|
||||
</block>
|
||||
</editor>
|
||||
)
|
@@ -0,0 +1,33 @@
|
||||
/** @jsx jsx */
|
||||
// Apply a mark across a range containing text with other marks and one void that supports marks
|
||||
import { Editor, Transforms } from 'slate'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.markableVoid = node => node.markable
|
||||
Editor.addMark(editor, 'bold', true)
|
||||
}
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
<text>word</text>
|
||||
<inline void markable>
|
||||
<text />
|
||||
<cursor />
|
||||
</inline>
|
||||
<text />
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
export const output = (
|
||||
<editor>
|
||||
<block>
|
||||
<text>word</text>
|
||||
<inline void markable>
|
||||
<text bold />
|
||||
<cursor />
|
||||
</inline>
|
||||
<text />
|
||||
</block>
|
||||
</editor>
|
||||
)
|
@@ -0,0 +1,51 @@
|
||||
/** @jsx jsx */
|
||||
// Apply a mark across a range containing text with other marks and some voids that support marks
|
||||
import { Editor, Transforms } from 'slate'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.markableVoid = node => node.markable
|
||||
Editor.addMark(editor, 'bold', true)
|
||||
}
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
<text>
|
||||
<anchor />
|
||||
</text>
|
||||
<inline void markable>
|
||||
<text />
|
||||
</inline>
|
||||
<text italic>italic words </text>
|
||||
<inline void markable>
|
||||
<text />
|
||||
</inline>
|
||||
<text>
|
||||
<focus />
|
||||
</text>
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
export const output = (
|
||||
<editor>
|
||||
<block>
|
||||
<text bold>
|
||||
<anchor />
|
||||
</text>
|
||||
<inline void markable>
|
||||
<text bold />
|
||||
</inline>
|
||||
<text italic bold>
|
||||
italic words{' '}
|
||||
</text>
|
||||
<inline void markable>
|
||||
<text bold />
|
||||
</inline>
|
||||
<text bold>
|
||||
<focus />
|
||||
</text>
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
// TODO this has to be skipped because the second void and the final empty text fail to be marked bold
|
||||
export const skip = true
|
@@ -0,0 +1,55 @@
|
||||
/** @jsx jsx */
|
||||
// Apply a mark across a range containing text with other marks and one void that supports marks
|
||||
import { Editor, Transforms } from 'slate'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.markableVoid = node => node.markable
|
||||
Editor.addMark(editor, 'bold', true)
|
||||
}
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
<text>
|
||||
<anchor />
|
||||
word{' '}
|
||||
</text>
|
||||
<inline void>
|
||||
<text />
|
||||
</inline>
|
||||
<text italic>italic words </text>
|
||||
<inline void markable>
|
||||
<text />
|
||||
</inline>
|
||||
<text underline>
|
||||
{' '}
|
||||
underlined words
|
||||
<focus />
|
||||
</text>
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
export const output = (
|
||||
<editor>
|
||||
<block>
|
||||
<text bold>
|
||||
<anchor />
|
||||
word{' '}
|
||||
</text>
|
||||
<inline void>
|
||||
<text />
|
||||
</inline>
|
||||
<text italic bold>
|
||||
italic words{' '}
|
||||
</text>
|
||||
<inline void markable>
|
||||
<text bold />
|
||||
</inline>
|
||||
<text underline bold>
|
||||
{' '}
|
||||
underlined words
|
||||
<focus />
|
||||
</text>
|
||||
</block>
|
||||
</editor>
|
||||
)
|
Reference in New Issue
Block a user