1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-18 13:11:17 +02:00

Merge branch 'master' of github.com:ianstormtaylor/slate

This commit is contained in:
Ian Storm Taylor
2018-04-27 15:28:18 -07:00
27 changed files with 263 additions and 85 deletions

View File

@@ -69,22 +69,24 @@ Marks work the same way, except they invoke the `renderMark` function. Like so:
```js
function renderMark(props) {
const { children, mark } = props
const { children, mark, attributes } = props
switch (mark.type) {
case 'bold':
return <strong>{children}</strong>
return <strong {...{ attributes }}>{children}</strong>
case 'italic':
return <em>{children}</em>
return <em {...{ attributes }}>{children}</em>
case 'code':
return <code>{children}</code>
return <code {...{ attributes }}>{children}</code>
case 'underline':
return <u>{children}</u>
return <u {...{ attributes }}>{children}</u>
case 'strikethrough':
return <strike>{children}</strike>
return <strike {...{ attributes }}>{children}</strike>
}
}
```
Be sure to mix `props.attributes` in your `renderMark`. `attributes` provides `data-*` dom attributes for spell-check in non-IE browsers.
That way, if you happen to have a global stylesheet that defines `strong`, `em`, etc. styles then your editor's content will already be formatted!
> 🤖 Be aware though that marks aren't guaranteed to be "contiguous". Which means even though a **word** is bolded, it's not guaranteed to render as a single `<strong>` element. If some of its characters are also italic, it might be split up into multiple elements—one `<strong>wo</strong>` and one `<em><strong>rd</strong></em>`.

View File

@@ -6,11 +6,11 @@
Okay, so you've got Slate installed and rendered on the page, and when you type in it, you can see the changes reflected. But you want to do more than just type a plaintext string.
What makes Slate great is how easy it is to customize. Just like other React components you're used to, Slate allows you to pass in handlers that are triggered on certain events. You've already seen on the `onChange` handler can be used to store the changed editor value, but let's try add something more...
What makes Slate great is how easy it is to customize. Just like other React components you're used to, Slate allows you to pass in handlers that are triggered on certain events. You've already seen how the `onChange` handler can be used to store the changed editor value, but let's try adding more...
We'll show you how to use the `onKeyDown` handler to change the editor's content when the user presses a button.
Let's use the `onKeyDown` handler to change the editor's content when we press a key.
So we start with our app from earlier:
Here's our app from earlier:
```js
class App extends React.Component {
@@ -28,7 +28,7 @@ class App extends React.Component {
}
```
And now we'll add an `onKeyDown` handler:
Now we add an `onKeyDown` handler:
```js
class App extends React.Component {
@@ -57,9 +57,9 @@ class App extends React.Component {
}
```
Okay cool, so now when you press a key in the editor, you'll see the key's code printed to the console. Not very useful, but at least we know it's working.
Cool, now when a key is pressed in the editor, its corresponding keycode is logged in the console.
Now we want to make it actually change the content. For the purposes of our example, let's say we want to make it so that whenever a user types `&` we actually add `and` to the content.
Now we want to make it actually change the content. For the purposes of our example, let's implement turning all ampersand, `&`, keystrokes into the word `and` upon being typed.
Our `onKeyDown` handler might look like this:
@@ -74,13 +74,13 @@ class App extends React.Component {
}
onKeyDown = (event, change) => {
// Return with no changes if it's not the "&" key.
if (event.key != '&') return
// Return with no changes if the keypress is not '&'
if (event.key !== '&') return
// Prevent the ampersand character from being inserted.
event.preventDefault()
// Change the value by inserting "and" at the cursor's position.
// Change the value by inserting 'and' at the cursor's position.
change.insertText('and')
return true
}
@@ -97,9 +97,9 @@ class App extends React.Component {
}
```
With that added, try typing `&`, and you should see it automatically become `and` instead!
With that added, try typing `&`, and you should see it suddenly become `and` instead!
That gives you a sense for what you can do with Slate's event handlers. Each one will be called with the `event` object, and a `change` object that lets you perform changes to the editor's value. Simple!
This offers a sense of what can be done with Slate's event handlers. Each one will be called with the `event` object, and a `change` object that lets you perform changes to the editor's value. Simple!
<br/>
<p align="center"><strong>Next:</strong><br/><a href="./defining-custom-block-nodes.md">Defining Custom Block Nodes</a></p>

View File

@@ -268,13 +268,14 @@ class App extends React.Component {
// Add a `renderMark` method to render marks.
renderMark = props => {
switch (props.mark.type) {
const { mark, attributes } = props
switch (mark.type) {
case 'bold':
return <strong>{props.children}</strong>
return <strong {...{ attributes }}>{props.children}</strong>
case 'italic':
return <em>{props.children}</em>
return <em {...{ attributes }}>{props.children}</em>
case 'underline':
return <u>{props.children}</u>
return <u {...{ attributes }}>{props.children}</u>
}
}
}

View File

@@ -43,7 +43,7 @@ class App extends React.Component {
renderMark = props => {
switch (props.mark.type) {
case 'bold':
return <strong>{props.children}</strong>
return <strong {...{props.attributes}}>{props.children}</strong>
}
}
}

View File

@@ -136,16 +136,32 @@ class CodeHighlighting extends React.Component {
*/
renderMark = props => {
const { children, mark } = props
const { children, mark, attributes } = props
switch (mark.type) {
case 'comment':
return <span style={{ opacity: '0.33' }}>{children}</span>
return (
<span {...attributes} style={{ opacity: '0.33' }}>
{children}
</span>
)
case 'keyword':
return <span style={{ fontWeight: 'bold' }}>{children}</span>
return (
<span {...attributes} style={{ fontWeight: 'bold' }}>
{children}
</span>
)
case 'tag':
return <span style={{ fontWeight: 'bold' }}>{children}</span>
return (
<span {...attributes} style={{ fontWeight: 'bold' }}>
{children}
</span>
)
case 'punctuation':
return <span style={{ opacity: '0.75' }}>{children}</span>
return (
<span {...attributes} style={{ opacity: '0.75' }}>
{children}
</span>
)
}
}

View File

@@ -187,16 +187,16 @@ class HoveringMenu extends React.Component {
*/
renderMark = props => {
const { children, mark } = props
const { children, mark, attributes } = props
switch (mark.type) {
case 'bold':
return <strong>{children}</strong>
return <strong {...attributes}>{children}</strong>
case 'code':
return <code>{children}</code>
return <code {...attributes}>{children}</code>
case 'italic':
return <em>{children}</em>
return <em {...attributes}>{children}</em>
case 'underlined':
return <u>{children}</u>
return <u {...attributes}>{children}</u>
}
}
}

View File

@@ -110,16 +110,16 @@ class HugeDocument extends React.Component {
*/
renderMark = props => {
const { children, mark } = props
const { children, mark, attributes } = props
switch (mark.type) {
case 'bold':
return <strong>{children}</strong>
return <strong {...attributes}>{children}</strong>
case 'code':
return <code>{children}</code>
return <code {...attributes}>{children}</code>
case 'italic':
return <em>{children}</em>
return <em {...attributes}>{children}</em>
case 'underlined':
return <u>{children}</u>
return <u {...attributes}>{children}</u>
}
}
}

View File

@@ -69,19 +69,20 @@ class MarkdownPreview extends React.Component {
*/
renderMark = props => {
const { children, mark } = props
const { children, mark, attributes } = props
switch (mark.type) {
case 'bold':
return <strong>{children}</strong>
return <strong {...attributes}>{children}</strong>
case 'code':
return <code>{children}</code>
return <code {...attributes}>{children}</code>
case 'italic':
return <em>{children}</em>
return <em {...attributes}>{children}</em>
case 'underlined':
return <u>{children}</u>
return <u {...attributes}>{children}</u>
case 'title': {
return (
<span
{...attributes}
style={{
fontWeight: 'bold',
fontSize: '20px',
@@ -94,11 +95,16 @@ class MarkdownPreview extends React.Component {
)
}
case 'punctuation': {
return <span style={{ opacity: 0.2 }}>{children}</span>
return (
<span {...attributes} style={{ opacity: 0.2 }}>
{children}
</span>
)
}
case 'list': {
return (
<span
{...attributes}
style={{
paddingLeft: '10px',
lineHeight: '10px',
@@ -112,6 +118,7 @@ class MarkdownPreview extends React.Component {
case 'hr': {
return (
<span
{...attributes}
style={{
borderBottom: '2px solid #000',
display: 'block',

View File

@@ -257,16 +257,16 @@ class PasteHtml extends React.Component {
*/
renderMark = props => {
const { children, mark } = props
const { children, mark, attributes } = props
switch (mark.type) {
case 'bold':
return <strong>{children}</strong>
return <strong {...attributes}>{children}</strong>
case 'code':
return <code>{children}</code>
return <code {...attributes}>{children}</code>
case 'italic':
return <em>{children}</em>
return <em {...attributes}>{children}</em>
case 'underlined':
return <u>{children}</u>
return <u {...attributes}>{children}</u>
}
}
}

View File

@@ -302,16 +302,16 @@ class RichTextExample extends React.Component {
*/
renderMark = props => {
const { children, mark } = props
const { children, mark, attributes } = props
switch (mark.type) {
case 'bold':
return <strong>{children}</strong>
return <strong {...attributes}>{children}</strong>
case 'code':
return <code>{children}</code>
return <code {...attributes}>{children}</code>
case 'italic':
return <em>{children}</em>
return <em {...attributes}>{children}</em>
case 'underlined':
return <u>{children}</u>
return <u {...attributes}>{children}</u>
}
}
}

View File

@@ -140,10 +140,14 @@ class SearchHighlighting extends React.Component {
*/
renderMark = props => {
const { children, mark } = props
const { children, mark, attributes } = props
switch (mark.type) {
case 'highlight':
return <span style={{ backgroundColor: '#ffeeba' }}>{children}</span>
return (
<span {...attributes} style={{ backgroundColor: '#ffeeba' }}>
{children}
</span>
)
}
}
}

View File

@@ -197,16 +197,16 @@ class SyncingEditor extends React.Component {
*/
renderMark = props => {
const { children, mark } = props
const { children, mark, attributes } = props
switch (mark.type) {
case 'bold':
return <strong>{children}</strong>
return <strong {...attributes}>{children}</strong>
case 'code':
return <code>{children}</code>
return <code {...attributes}>{children}</code>
case 'italic':
return <em>{children}</em>
return <em {...attributes}>{children}</em>
case 'underlined':
return <u>{children}</u>
return <u {...attributes}>{children}</u>
}
}
}

View File

@@ -160,10 +160,10 @@ class Tables extends React.Component {
*/
renderMark = props => {
const { children, mark } = props
const { children, mark, attributes } = props
switch (mark.type) {
case 'bold':
return <strong>{children}</strong>
return <strong {...attributes}>{children}</strong>
}
}
}

View File

@@ -13,7 +13,7 @@
"lib/"
],
"dependencies": {
"debug": "^2.3.2",
"debug": "^3.1.0",
"get-window": "^1.1.1",
"is-window": "^1.0.2",
"keycode": "^2.1.2",

View File

@@ -99,9 +99,21 @@ class Leaf extends React.Component {
const { marks, node, offset, text, editor } = this.props
const { stack } = editor
const leaf = this.renderText()
const attributes = {
'data-slate-leaf': true,
}
return marks.reduce((children, mark) => {
const props = { editor, mark, marks, node, offset, text, children }
const props = {
editor,
mark,
marks,
node,
offset,
text,
children,
attributes,
}
const element = stack.find('renderMark', props)
return element || children
}, leaf)

View File

@@ -305,8 +305,8 @@ function AfterPlugin() {
// Get the selection point.
const native = window.getSelection()
const { anchorNode, anchorOffset } = native
const point = findPoint(anchorNode, anchorOffset, value)
const { anchorNode } = native
const point = findPoint(anchorNode, 0, value)
if (!point) return
// Get the text node and leaf in question.
@@ -323,7 +323,7 @@ function AfterPlugin() {
leaves.find(r => {
start = end
end += r.text.length
if (end >= point.offset) return true
if (end > point.offset) return true
}) || lastLeaf
// Get the text information.

View File

@@ -17,7 +17,7 @@ function decorateNode(block) {
}
function Bold(props) {
return React.createElement('strong', null, props.children)
return React.createElement('strong', { ...props.attributes }, props.children)
}
function renderMark(props) {
@@ -45,7 +45,7 @@ export const output = `
<div style="position:relative">
<span>
<span>o</span>
<span><strong>n</strong></span>
<span><strong data-slate-leaf="true">n</strong></span>
<span>e</span>
</span>
</div>

View File

@@ -4,7 +4,7 @@ import React from 'react'
import h from '../../helpers/h'
function Bold(props) {
return React.createElement('strong', null, props.children)
return React.createElement('strong', { ...props.attributes }, props.children)
}
function renderMark(props) {
@@ -33,7 +33,7 @@ export const output = `
<div style="position:relative">
<span>
<span>one</span>
<span><strong>two</strong></span>
<span><strong data-slate-leaf="true">two</strong></span>
<span>three</span>
</span>
</div>

View File

@@ -13,7 +13,7 @@
"lib/"
],
"dependencies": {
"debug": "^2.3.2",
"debug": "^3.1.0",
"direction": "^0.1.5",
"esrever": "^0.2.0",
"is-empty": "^1.0.0",

View File

@@ -40,6 +40,11 @@ PROXY_TRANSFORMS.forEach(method => {
const { selection } = value
const methodAtRange = `${method}AtRange`
change[methodAtRange](selection, ...args)
if (method.match(/Backward$/)) {
change.collapseToStart()
} else if (method.match(/Forward$/)) {
change.collapseToEnd()
}
}
})
@@ -217,8 +222,12 @@ Changes.insertText = (change, text, marks) => {
Changes.splitBlock = (change, depth = 1) => {
const { value } = change
const { selection } = value
const { selection, document } = value
const marks = selection.marks || document.getInsertMarksAtRange(selection)
change.splitBlockAtRange(selection, depth).collapseToEnd()
if (marks && marks.size !== 0) {
change.select({ marks })
}
}
/**

View File

@@ -671,7 +671,11 @@ Changes.insertFragmentAtRange = (change, range, fragment, options = {}) => {
// If the range is expanded, delete it first.
if (range.isExpanded) {
change.deleteAtRange(range, { normalize: false })
range = range.collapseToStart()
if (change.value.document.getDescendant(range.startKey)) {
range = range.collapseToStart()
} else {
range = range.collapseTo(range.endKey, 0)
}
}
// If the fragment is empty, there's nothing to do after deleting.
@@ -988,12 +992,7 @@ Changes.setInlineAtRange = (...args) => {
Changes.splitBlockAtRange = (change, range, height = 1, options = {}) => {
const normalize = change.getFlag('normalize', options)
if (range.isExpanded) {
change.deleteAtRange(range, { normalize })
range = range.collapseToStart()
}
const { startKey, startOffset } = range
const { startKey, startOffset, endOffset, endKey } = range
const { value } = change
const { document } = value
let node = document.assertDescendant(startKey)
@@ -1006,7 +1005,19 @@ Changes.splitBlockAtRange = (change, range, height = 1, options = {}) => {
h++
}
change.splitDescendantsByKey(node.key, startKey, startOffset, { normalize })
change.splitDescendantsByKey(node.key, startKey, startOffset, {
normalize: normalize && range.isCollapsed,
})
if (range.isExpanded) {
if (range.isBackward) range = range.flip()
const nextBlock = change.value.document.getNextBlock(node.key)
range = range.moveAnchorToStartOf(nextBlock)
if (startKey === endKey) {
range = range.moveFocusTo(range.anchorKey, endOffset - startOffset)
}
change.deleteAtRange(range, { normalize })
}
}
/**

View File

@@ -47,7 +47,9 @@ Changes.normalizeNodeByKey = (change, key) => {
if (!ancestors) return
ancestors.forEach(ancestor => {
normalizeNode(change, ancestor, schema)
if (change.value.document.getDescendant(ancestor.key)) {
normalizeNode(change, ancestor, schema)
}
})
}

View File

@@ -23,9 +23,8 @@ export const output = (
<value>
<document>
<paragraph>
<anchor />
<link>
<focus />wo
<cursor />wo
</link>
</paragraph>
</document>

View File

@@ -0,0 +1,44 @@
/** @jsx h */
import h from '../../../helpers/h'
const fragment = (
<document>
<paragraph>fragment zero</paragraph>
<paragraph>fragment one</paragraph>
<paragraph>fragment two</paragraph>
</document>
)
export default function(change) {
change.insertFragment(fragment)
}
export const input = (
<value>
<document>
<paragraph>zero</paragraph>
<paragraph>
<anchor />one
</paragraph>
<quote>
<focus />two
</quote>
</document>
</value>
)
// The cursor position of insertFragment has some problems;
// If you submit PR to fixed cursor position restore in insertFragment;
// Please change this test as well
export const output = (
<value>
<document>
<paragraph>zero</paragraph>
<quote>fragment zero</quote>
<paragraph>fragment one</paragraph>
<paragraph>
<cursor />fragment twotwo
</paragraph>
</document>
</value>
)

View File

@@ -0,0 +1,33 @@
/** @jsx h */
import h from '../../../helpers/h'
export default function(change) {
change.splitBlock()
}
export const input = (
<value>
<document>
<paragraph>zero</paragraph>
<paragraph>
<anchor />word
</paragraph>
<quote>
<focus />cat is cute
</quote>
</document>
</value>
)
export const output = (
<value>
<document>
<paragraph>zero</paragraph>
<paragraph />
<quote>
<cursor />cat is cute
</quote>
</document>
</value>
)

View File

@@ -0,0 +1,38 @@
/** @jsx h */
import h from '../../../helpers/h'
export default function(change) {
change
.addMark('italic')
.splitBlock()
.insertText('cat is cute')
}
export const input = (
<value>
<document>
<paragraph>
<b>word</b>
<cursor />
</paragraph>
</document>
</value>
)
export const output = (
<value>
<document>
<paragraph>
<b>word</b>
<cursor />
</paragraph>
<paragraph>
<i>
<b>cat is cute</b>
</i>
<cursor />
</paragraph>
</document>
</value>
)

View File

@@ -2326,7 +2326,7 @@ debug@2.2.0:
dependencies:
ms "0.7.1"
debug@2.6.9, debug@^2.3.2, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9:
debug@2.6.9, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
dependencies: