mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-09-08 06:00:40 +02:00
add start of image example
This commit is contained in:
@@ -8,9 +8,9 @@ Slate is like a pluggable implementation of `contenteditable`, built with React
|
|||||||
|
|
||||||
- [Principles](#principles)
|
- [Principles](#principles)
|
||||||
- [Examples](#examples)
|
- [Examples](#examples)
|
||||||
- [Plugins](#plugins)
|
- Plugins
|
||||||
- [Documentation](#documentation)
|
- [Documentation](#documentation)
|
||||||
- [Contributing](#contributing)
|
- Contributing
|
||||||
|
|
||||||
_Slate is currently in **beta**, while work is being done on: cross-browser support, atomic node support, and collaboration support. It's useable now, but you might need to pull request one or two fixes for your use case._
|
_Slate is currently in **beta**, while work is being done on: cross-browser support, atomic node support, and collaboration support. It's useable now, but you might need to pull request one or two fixes for your use case._
|
||||||
|
|
||||||
@@ -52,6 +52,7 @@ If you're using Slate for the first time, check out the [Getting Started](docs/g
|
|||||||
- [The Document Model](docs/getting-started.md#the-document-model)
|
- [The Document Model](docs/getting-started.md#the-document-model)
|
||||||
- Selections
|
- Selections
|
||||||
- [**API Reference**](docs/reference.md)
|
- [**API Reference**](docs/reference.md)
|
||||||
|
- Plugins
|
||||||
- Components
|
- Components
|
||||||
- Editor
|
- Editor
|
||||||
- Models
|
- Models
|
||||||
|
172
examples/images/index.js
Normal file
172
examples/images/index.js
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
|
||||||
|
import Editor, { Mark, Raw } from '../..'
|
||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import state from './state.json'
|
||||||
|
import { Map } from 'immutable'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The images example.
|
||||||
|
*
|
||||||
|
* @type {Component} Images
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Images extends React.Component {
|
||||||
|
|
||||||
|
state = {
|
||||||
|
state: Raw.deserialize(state)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert an image with `src` at the current selection.
|
||||||
|
*
|
||||||
|
* @param {String} src
|
||||||
|
*/
|
||||||
|
|
||||||
|
insertImage(src) {
|
||||||
|
let { state } = this.state
|
||||||
|
|
||||||
|
if (state.isExpanded) {
|
||||||
|
state = state
|
||||||
|
.transform()
|
||||||
|
.delete()
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
const { anchorBlock, selection } = state
|
||||||
|
|
||||||
|
if (anchorBlock.text == '') {
|
||||||
|
state = state
|
||||||
|
.transform()
|
||||||
|
.setBlock('image', { src })
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (selection.isAtEndOf(anchorBlock)) {
|
||||||
|
state = state
|
||||||
|
.transform()
|
||||||
|
.splitBlock()
|
||||||
|
.setBlock('image', { src })
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (selection.isAtStartOf(anchorBlock)) {
|
||||||
|
state = state
|
||||||
|
.transform()
|
||||||
|
.splitBlock()
|
||||||
|
.moveToStartOfPreviousBlock()
|
||||||
|
.setBlock('image', { src })
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
state = state
|
||||||
|
.transform()
|
||||||
|
.splitBlock()
|
||||||
|
.splitBlock()
|
||||||
|
.moveToStartOfPreviousBlock()
|
||||||
|
.setBlock('image', { src })
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ state })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On clicking the image button, prompt for an image and insert it.
|
||||||
|
*
|
||||||
|
* @param {Event} e
|
||||||
|
*/
|
||||||
|
|
||||||
|
onClickImage(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
const src = window.prompt('Enter the URL of the image:')
|
||||||
|
if (!src) return
|
||||||
|
this.insertImage(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the app.
|
||||||
|
*
|
||||||
|
* @return {Element} element
|
||||||
|
*/
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.renderToolbar()}
|
||||||
|
{this.renderEditor()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the toolbar.
|
||||||
|
*
|
||||||
|
* @return {Element} element
|
||||||
|
*/
|
||||||
|
|
||||||
|
renderToolbar() {
|
||||||
|
return (
|
||||||
|
<div className="menu toolbar-menu">
|
||||||
|
<span className="button" onMouseDown={e => this.onClickImage(e)}>
|
||||||
|
<span className="material-icons">image</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the editor.
|
||||||
|
*
|
||||||
|
* @return {Element} element
|
||||||
|
*/
|
||||||
|
|
||||||
|
renderEditor() {
|
||||||
|
return (
|
||||||
|
<div className="editor">
|
||||||
|
<Editor
|
||||||
|
state={this.state.state}
|
||||||
|
renderNode={node => this.renderNode(node)}
|
||||||
|
onChange={(state) => {
|
||||||
|
console.groupCollapsed('Change!')
|
||||||
|
console.log('Document:', state.document.toJS())
|
||||||
|
console.log('Selection:', state.selection.toJS())
|
||||||
|
console.log('Content:', Raw.serialize(state))
|
||||||
|
console.groupEnd()
|
||||||
|
this.setState({ state })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render our custom `node`.
|
||||||
|
*
|
||||||
|
* @param {Node} node
|
||||||
|
* @return {Element} element
|
||||||
|
*/
|
||||||
|
|
||||||
|
renderNode(node) {
|
||||||
|
switch (node.type) {
|
||||||
|
case 'image': {
|
||||||
|
return (props) => {
|
||||||
|
const { data } = props.node
|
||||||
|
const src = data.get('src')
|
||||||
|
return <img src={src} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'paragraph': {
|
||||||
|
return (props) => <p>{props.children}</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default Images
|
49
examples/images/state.json
Normal file
49
examples/images/state.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"kind": "block",
|
||||||
|
"type": "paragraph",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"kind": "text",
|
||||||
|
"ranges": [
|
||||||
|
{
|
||||||
|
"text": "In addition to nodes that contain editable text, you can also create other types of nodes, like images or videos."
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "block",
|
||||||
|
"type": "image",
|
||||||
|
"data": {
|
||||||
|
"src": "https://img.washingtonpost.com/wp-apps/imrs.php?src=https://img.washingtonpost.com/news/speaking-of-science/wp-content/uploads/sites/36/2015/10/as12-49-7278-1024x1024.jpg&w=1484"
|
||||||
|
},
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"kind": "text",
|
||||||
|
"ranges": [
|
||||||
|
{
|
||||||
|
"text": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "block",
|
||||||
|
"type": "paragraph",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"kind": "text",
|
||||||
|
"ranges": [
|
||||||
|
{
|
||||||
|
"text": "This example shows images in action. It features two ways to add images. You can either add an image via the toolbar icon above, or if you want in on a little secret, copy an image URL to your keyboard and paste it anywhere in the editor!"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@@ -1,11 +1,13 @@
|
|||||||
|
|
||||||
html {
|
html {
|
||||||
background: #eee;
|
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
line-height: 1.4;
|
||||||
|
background: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
max-width: 40em;
|
max-width: 42em;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13,6 +15,10 @@ p {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
border-left: 2px solid #ddd;
|
border-left: 2px solid #ddd;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
@@ -68,7 +74,7 @@ td {
|
|||||||
|
|
||||||
.example {
|
.example {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 10px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor > * > * + * {
|
.editor > * > * + * {
|
||||||
@@ -93,10 +99,10 @@ td {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-menu {
|
.toolbar-menu {
|
||||||
padding: 1px 0 9px 8px;
|
padding: 1px 0 17px 18px;
|
||||||
margin: 0 -10px;
|
margin: 0 -20px;
|
||||||
border-bottom: 2px solid #eee;
|
border-bottom: 2px solid #eee;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover-menu {
|
.hover-menu {
|
||||||
@@ -105,11 +111,11 @@ td {
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
top: -10000px;
|
top: -10000px;
|
||||||
left: -10000px;
|
left: -10000px;
|
||||||
margin-top: -3px;
|
margin-top: -6px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
background-color: #222;
|
background-color: #222;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: opacity 1s;
|
transition: opacity .75s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover-menu .button {
|
.hover-menu .button {
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Examples | Editor</title>
|
<title>Examples | Editor</title>
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:400,400i,700,700i&subset=latin-ext" >
|
||||||
<link rel="stylesheet" href="index.css">
|
<link rel="stylesheet" href="index.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@@ -9,6 +9,7 @@ import { Router, Route, Link, IndexRedirect, hashHistory } from 'react-router'
|
|||||||
|
|
||||||
import AutoMarkdown from './auto-markdown'
|
import AutoMarkdown from './auto-markdown'
|
||||||
import HoveringMenu from './hovering-menu'
|
import HoveringMenu from './hovering-menu'
|
||||||
|
import Images from './images'
|
||||||
import Links from './links'
|
import Links from './links'
|
||||||
import PlainText from './plain-text'
|
import PlainText from './plain-text'
|
||||||
import RichText from './rich-text'
|
import RichText from './rich-text'
|
||||||
@@ -51,6 +52,7 @@ class App extends React.Component {
|
|||||||
<Link className="tab" activeClassName="active" to="auto-markdown">Auto-markdown</Link>
|
<Link className="tab" activeClassName="active" to="auto-markdown">Auto-markdown</Link>
|
||||||
<Link className="tab" activeClassName="active" to="hovering-menu">Hovering Menu</Link>
|
<Link className="tab" activeClassName="active" to="hovering-menu">Hovering Menu</Link>
|
||||||
<Link className="tab" activeClassName="active" to="links">Links</Link>
|
<Link className="tab" activeClassName="active" to="links">Links</Link>
|
||||||
|
<Link className="tab" activeClassName="active" to="images">Images</Link>
|
||||||
<Link className="tab" activeClassName="active" to="tables">Tables</Link>
|
<Link className="tab" activeClassName="active" to="tables">Tables</Link>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -84,6 +86,7 @@ const router = (
|
|||||||
<IndexRedirect to="rich-text" />
|
<IndexRedirect to="rich-text" />
|
||||||
<Route path="auto-markdown" component={AutoMarkdown} />
|
<Route path="auto-markdown" component={AutoMarkdown} />
|
||||||
<Route path="hovering-menu" component={HoveringMenu} />
|
<Route path="hovering-menu" component={HoveringMenu} />
|
||||||
|
<Route path="images" component={Images} />
|
||||||
<Route path="links" component={Links} />
|
<Route path="links" component={Links} />
|
||||||
<Route path="plain-text" component={PlainText} />
|
<Route path="plain-text" component={PlainText} />
|
||||||
<Route path="rich-text" component={RichText} />
|
<Route path="rich-text" component={RichText} />
|
||||||
|
@@ -617,6 +617,31 @@ const Node = {
|
|||||||
.last()
|
.last()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the block node before a descendant text node by `key`.
|
||||||
|
*
|
||||||
|
* @param {String or Node} key
|
||||||
|
* @return {Node or Null} node
|
||||||
|
*/
|
||||||
|
|
||||||
|
getPreviousBlock(key) {
|
||||||
|
key = normalizeKey(key)
|
||||||
|
const child = this.getDescendant(key)
|
||||||
|
let first
|
||||||
|
|
||||||
|
if (child.kind == 'block') {
|
||||||
|
first = child.getTextNodes().first()
|
||||||
|
} else {
|
||||||
|
const block = this.getClosestBlock(key)
|
||||||
|
first = block.getTextNodes().first()
|
||||||
|
}
|
||||||
|
|
||||||
|
const previous = this.getPreviousText(first)
|
||||||
|
if (!previous) return null
|
||||||
|
|
||||||
|
return this.getClosestBlock(previous)
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the descendent text node at an `offset`.
|
* Get the descendent text node at an `offset`.
|
||||||
*
|
*
|
||||||
|
@@ -540,6 +540,27 @@ class State extends Record(DEFAULTS) {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the selection to the start of the previous block.
|
||||||
|
*
|
||||||
|
* @return {State} state
|
||||||
|
*/
|
||||||
|
|
||||||
|
moveToStartOfPreviousBlock() {
|
||||||
|
let state = this
|
||||||
|
let { document, selection } = state
|
||||||
|
let blocks = document.getBlocksAtRange(selection)
|
||||||
|
let block = blocks.first()
|
||||||
|
if (!block) return state
|
||||||
|
|
||||||
|
let previous = document.getPreviousBlock(block)
|
||||||
|
if (!previous) return state
|
||||||
|
|
||||||
|
selection = selection.moveToStartOf(previous)
|
||||||
|
state = state.merge({ selection })
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the block nodes in the current selection to `type`.
|
* Set the block nodes in the current selection to `type`.
|
||||||
*
|
*
|
||||||
|
@@ -75,6 +75,7 @@ const STATE_TRANSFORMS = [
|
|||||||
'insertFragment',
|
'insertFragment',
|
||||||
'insertText',
|
'insertText',
|
||||||
'mark',
|
'mark',
|
||||||
|
'moveToStartOfPreviousBlock',
|
||||||
'setBlock',
|
'setBlock',
|
||||||
'setInline',
|
'setInline',
|
||||||
'splitBlock',
|
'splitBlock',
|
||||||
|
Reference in New Issue
Block a user