1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-09-03 12:12:39 +02:00

add start of image example

This commit is contained in:
Ian Storm Taylor
2016-06-28 18:26:56 -07:00
parent d5f0a55515
commit 78a902d7a0
9 changed files with 289 additions and 10 deletions

View File

@@ -8,9 +8,9 @@ Slate is like a pluggable implementation of `contenteditable`, built with React
- [Principles](#principles)
- [Examples](#examples)
- [Plugins](#plugins)
- Plugins
- [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._
@@ -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)
- Selections
- [**API Reference**](docs/reference.md)
- Plugins
- Components
- Editor
- Models

172
examples/images/index.js Normal file
View 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

View 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!"
}
]
}
]
}
]
}

View File

@@ -1,11 +1,13 @@
html {
background: #eee;
padding: 20px;
font-family: 'Roboto', sans-serif;
line-height: 1.4;
background: #eee;
}
main {
max-width: 40em;
max-width: 42em;
margin: 0 auto;
}
@@ -13,6 +15,10 @@ p {
margin: 0;
}
img {
max-width: 100%;
}
blockquote {
border-left: 2px solid #ddd;
margin-left: 0;
@@ -68,7 +74,7 @@ td {
.example {
background: #fff;
padding: 10px;
padding: 20px;
}
.editor > * > * + * {
@@ -93,10 +99,10 @@ td {
}
.toolbar-menu {
padding: 1px 0 9px 8px;
margin: 0 -10px;
padding: 1px 0 17px 18px;
margin: 0 -20px;
border-bottom: 2px solid #eee;
margin-bottom: 10px;
margin-bottom: 20px;
}
.hover-menu {
@@ -105,11 +111,11 @@ td {
z-index: 1;
top: -10000px;
left: -10000px;
margin-top: -3px;
margin-top: -6px;
opacity: 0;
background-color: #222;
border-radius: 4px;
transition: opacity 1s;
transition: opacity .75s;
}
.hover-menu .button {

View File

@@ -3,6 +3,7 @@
<meta charset="utf-8" />
<title>Examples | Editor</title>
<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">
</head>
<body>

View File

@@ -9,6 +9,7 @@ import { Router, Route, Link, IndexRedirect, hashHistory } from 'react-router'
import AutoMarkdown from './auto-markdown'
import HoveringMenu from './hovering-menu'
import Images from './images'
import Links from './links'
import PlainText from './plain-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="hovering-menu">Hovering Menu</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>
</div>
)
@@ -84,6 +86,7 @@ const router = (
<IndexRedirect to="rich-text" />
<Route path="auto-markdown" component={AutoMarkdown} />
<Route path="hovering-menu" component={HoveringMenu} />
<Route path="images" component={Images} />
<Route path="links" component={Links} />
<Route path="plain-text" component={PlainText} />
<Route path="rich-text" component={RichText} />

View File

@@ -617,6 +617,31 @@ const Node = {
.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`.
*

View File

@@ -540,6 +540,27 @@ class State extends Record(DEFAULTS) {
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`.
*

View File

@@ -75,6 +75,7 @@ const STATE_TRANSFORMS = [
'insertFragment',
'insertText',
'mark',
'moveToStartOfPreviousBlock',
'setBlock',
'setInline',
'splitBlock',