mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-31 10:51:44 +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:
@@ -19,6 +19,7 @@ const MentionExample = () => {
|
||||
const [index, setIndex] = useState(0)
|
||||
const [search, setSearch] = useState('')
|
||||
const renderElement = useCallback(props => <Element {...props} />, [])
|
||||
const renderLeaf = useCallback(props => <Leaf {...props} />, [])
|
||||
const editor = useMemo(
|
||||
() => withMentions(withReact(withHistory(createEditor()))),
|
||||
[]
|
||||
@@ -101,6 +102,7 @@ const MentionExample = () => {
|
||||
>
|
||||
<Editable
|
||||
renderElement={renderElement}
|
||||
renderLeaf={renderLeaf}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder="Enter some text..."
|
||||
/>
|
||||
@@ -140,7 +142,7 @@ const MentionExample = () => {
|
||||
}
|
||||
|
||||
const withMentions = editor => {
|
||||
const { isInline, isVoid } = editor
|
||||
const { isInline, isVoid, markableVoid } = editor
|
||||
|
||||
editor.isInline = element => {
|
||||
return element.type === 'mention' ? true : isInline(element)
|
||||
@@ -150,6 +152,10 @@ const withMentions = editor => {
|
||||
return element.type === 'mention' ? true : isVoid(element)
|
||||
}
|
||||
|
||||
editor.markableVoid = element => {
|
||||
return element.type === 'mention' || markableVoid(element)
|
||||
}
|
||||
|
||||
return editor
|
||||
}
|
||||
|
||||
@@ -163,6 +169,28 @@ const insertMention = (editor, character) => {
|
||||
Transforms.move(editor)
|
||||
}
|
||||
|
||||
// Borrow Leaf renderer from the Rich Text example.
|
||||
// In a real project you would get this via `withRichText(editor)` or similar.
|
||||
const Leaf = ({ attributes, children, leaf }) => {
|
||||
if (leaf.bold) {
|
||||
children = <strong>{children}</strong>
|
||||
}
|
||||
|
||||
if (leaf.code) {
|
||||
children = <code>{children}</code>
|
||||
}
|
||||
|
||||
if (leaf.italic) {
|
||||
children = <em>{children}</em>
|
||||
}
|
||||
|
||||
if (leaf.underline) {
|
||||
children = <u>{children}</u>
|
||||
}
|
||||
|
||||
return <span {...attributes}>{children}</span>
|
||||
}
|
||||
|
||||
const Element = props => {
|
||||
const { attributes, children, element } = props
|
||||
switch (element.type) {
|
||||
@@ -176,21 +204,29 @@ const Element = props => {
|
||||
const Mention = ({ attributes, children, element }) => {
|
||||
const selected = useSelected()
|
||||
const focused = useFocused()
|
||||
const style: React.CSSProperties = {
|
||||
padding: '3px 3px 2px',
|
||||
margin: '0 1px',
|
||||
verticalAlign: 'baseline',
|
||||
display: 'inline-block',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: '#eee',
|
||||
fontSize: '0.9em',
|
||||
boxShadow: selected && focused ? '0 0 0 2px #B4D5FF' : 'none',
|
||||
}
|
||||
// See if our empty text child has any styling marks applied and apply those
|
||||
if (element.children[0].bold) {
|
||||
style.fontWeight = 'bold'
|
||||
}
|
||||
if (element.children[0].italic) {
|
||||
style.fontStyle = 'italic'
|
||||
}
|
||||
return (
|
||||
<span
|
||||
{...attributes}
|
||||
contentEditable={false}
|
||||
data-cy={`mention-${element.character.replace(' ', '-')}`}
|
||||
style={{
|
||||
padding: '3px 3px 2px',
|
||||
margin: '0 1px',
|
||||
verticalAlign: 'baseline',
|
||||
display: 'inline-block',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: '#eee',
|
||||
fontSize: '0.9em',
|
||||
boxShadow: selected && focused ? '0 0 0 2px #B4D5FF' : 'none',
|
||||
}}
|
||||
style={style}
|
||||
>
|
||||
{children}@{element.character}
|
||||
</span>
|
||||
@@ -201,9 +237,30 @@ const initialValue: Descendant[] = [
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
text: 'This example shows how you might implement a simple ',
|
||||
},
|
||||
{
|
||||
text: '@-mentions',
|
||||
bold: true,
|
||||
},
|
||||
{
|
||||
text:
|
||||
'This example shows how you might implement a simple @-mentions feature that lets users autocomplete mentioning a user by their username. Which, in this case means Star Wars characters. The mentions are rendered as void inline elements inside the document.',
|
||||
' feature that lets users autocomplete mentioning a user by their username. Which, in this case means Star Wars characters. The ',
|
||||
},
|
||||
{
|
||||
text: 'mentions',
|
||||
bold: true,
|
||||
},
|
||||
{
|
||||
text: ' are rendered as ',
|
||||
},
|
||||
{
|
||||
text: 'void inline elements',
|
||||
code: true,
|
||||
},
|
||||
{
|
||||
text: ' inside the document.',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -214,7 +271,7 @@ const initialValue: Descendant[] = [
|
||||
{
|
||||
type: 'mention',
|
||||
character: 'R2-D2',
|
||||
children: [{ text: '' }],
|
||||
children: [{ text: '', bold: true }],
|
||||
},
|
||||
{ text: ' or ' },
|
||||
{
|
||||
|
Reference in New Issue
Block a user