1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-01-17 21:49:20 +01:00

got void components working

This commit is contained in:
Ian Storm Taylor 2016-06-30 10:43:24 -07:00
parent 27e71715a6
commit aba40a2aaf
10 changed files with 195 additions and 18 deletions

View File

@ -17,11 +17,11 @@ _Slate is currently in **beta**, while work is being done on: cross-browser supp
## Principles
1. **First-class plugins.** The most important factor of Slate is that plugins are first-class entities—the core editor logic is even implemented as its own plugin. This means that you're able to fully customize the editing experience. So you can build complex interations like those found in the Medium or Canvas editors.
1. **First-class plugins.** The most important part of Slate is that plugins are first-class entities—the core editor logic is even implemented as its own plugin. That means you can _completely_ customize the editing experience, to build complex editors like Medium's or Canvas's without having to fight against the library's assumptions.
2. **Schema-less core.** Slate's core logic doesn't assume anything about the schema of the data you'll be editing, which means that there are no assumptions baked into the library that'll trip you up when you need to go beyond basic usage.
3. **Nested document model.** The document model used for Slate is a nested, recursive tree, just like the DOM itself. This means that creating complex components like tables or nested block quotes is possible for advanced use cases. But it's also easy to keep it simple by only using a single level of hierachy.
3. **Nested document model.** The document model used for Slate is a nested, recursive tree, just like the DOM itself. This means that creating complex components like tables or nested block quotes are possible for advanced use cases. But it's also easy to keep it simple by only using a single level of hierachy.
4. **Stateless and immutable.** By using React and Immutable.js, the Slate editor is built in a stateless fashion using immutable data structures, which leads to better performance, and also a much easier time writing plugins.

View File

@ -152,9 +152,11 @@ class Images extends React.Component {
switch (node.type) {
case 'image': {
return (props) => {
const { data } = props.node
const { node, state } = props
const { data } = node
const isActive = state.blocks.includes(node)
const src = data.get('src')
return <img src={src} />
return <img src={src} data-active={isActive} />
}
}
case 'paragraph': {

View File

@ -18,6 +18,7 @@
"kind": "block",
"type": "image",
"data": {
"isVoid": true,
"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": [

View File

@ -17,6 +17,11 @@ pre {
img {
max-width: 100%;
max-height: 20em;
}
img[data-active="true"] {
box-shadow: 0 0 0 2px blue;
}
blockquote {

View File

@ -16,6 +16,7 @@
<p>
<strong>Some text.</strong>
</p>
<img 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" />
<blockquote>
<p>
<strong>Some <a href="google">text.</a></strong>

View File

@ -3,6 +3,7 @@ import OffsetKey from '../utils/offset-key'
import React from 'react'
import ReactDOM from 'react-dom'
import Text from './text'
import Void from './void'
import keycode from 'keycode'
import { Raw } from '..'
import { isCommand, isWindowsCommand } from '../utils/event'
@ -18,6 +19,7 @@ class Content extends React.Component {
*/
static propTypes = {
editor: React.PropTypes.object.isRequired,
onBeforeInput: React.PropTypes.func,
onChange: React.PropTypes.func,
onKeyDown: React.PropTypes.func,
@ -48,10 +50,13 @@ class Content extends React.Component {
*/
shouldComponentUpdate(props, state) {
if (props.state == this.props.state) return false
if (props.state.document == this.props.state.document) return false
if (props.state.isNative) return false
// if (props.state.isNative) return false
return true
// return (
// props.state != this.props.state ||
// props.state.selection != this.props.state.selection ||
// props.state.document != this.props.state.document
// )
}
/**
@ -291,7 +296,7 @@ class Content extends React.Component {
/**
* Render the editor content.
*
* @return {Component} component
* @return {Element} element
*/
render() {
@ -328,7 +333,7 @@ class Content extends React.Component {
* Render a `node`.
*
* @param {Node} node
* @return {Component} component
* @return {Element} element
*/
renderNode(node) {
@ -345,39 +350,67 @@ class Content extends React.Component {
* Render an element `node`.
*
* @param {Node} node
* @return {Component} component
* @return {Element} element
*/
renderElement(node) {
const { renderNode, state } = this.props
const { editor, renderNode, state } = this.props
const Component = renderNode(node)
const children = node.nodes
.map(child => this.renderNode(child))
.toArray()
return (
const element = (
<Component
key={node.key}
editor={editor}
node={node}
state={state}
>
{children}
</Component>
)
return node.data.get('isVoid')
? this.renderVoid(element, node)
: element
}
/**
* Render a void wrapper around an `element` for `node`.
*
* @param {Node} node
* @param {Element} element
* @return {Element} element
*/
renderVoid(element, node) {
const { editor, state } = this.props
return (
<Void
key={node.key}
editor={editor}
node={node}
state={state}
>
{element}
</Void>
)
}
/**
* Render a text `node`.
*
* @param {Node} node
* @return {Component} component
* @return {Element} element
*/
renderText(node) {
const { renderMark, renderNode, state } = this.props
const { editor, renderMark, renderNode, state } = this.props
return (
<Text
key={node.key}
editor={editor}
node={node}
renderMark={renderMark}
state={state}

View File

@ -102,6 +102,7 @@ class Editor extends React.Component {
render() {
return (
<Content
editor={this}
state={this.props.state}
onChange={state => this.onChange(state)}
renderMark={mark => this.renderMark(mark)}

View File

@ -12,6 +12,7 @@ import { List } from 'immutable'
class Text extends React.Component {
static propTypes = {
editor: React.PropTypes.object.isRequired,
node: React.PropTypes.object.isRequired,
renderMark: React.PropTypes.func.isRequired,
state: React.PropTypes.object.isRequired
@ -54,13 +55,13 @@ class Text extends React.Component {
return (
<Leaf
key={offsetKey}
marks={marks}
state={state}
node={node}
start={start}
end={end}
renderMark={renderMark}
state={state}
text={text}
marks={marks}
renderMark={renderMark}
/>
)
}

121
lib/components/void.js Normal file
View File

@ -0,0 +1,121 @@
import Leaf from './leaf'
import Mark from '../models/mark'
import OffsetKey from '../utils/offset-key'
import React from 'react'
import ReactDOM from 'react-dom'
import keycode from 'keycode'
/**
* Void.
*/
class Void extends React.Component {
static propTypes = {
children: React.PropTypes.any.isRequired,
editor: React.PropTypes.object.isRequired,
node: React.PropTypes.object.isRequired,
state: React.PropTypes.object.isRequired
};
onClick(e) {
e.preventDefault()
let { editor, node, state } = this.props
let text = node.getTextNodes().first()
state = state
.transform()
.moveToStartOf(text)
.apply()
editor.onChange(state)
}
onKeyDown(e) {
let { state, editor } = this.props
const key = keycode(e)
switch (key) {
default:
return
case 'left arrow':
case 'up arrow':
state = state
.transform()
.moveToEndOfPreviousBlock()
.apply()
case 'right arrow':
case 'down arrow':
state = state
.transform()
.moveToStartOfNextBlock()
.apply()
}
e.preventDefault()
editor.onChange(state)
}
render() {
const { children, node } = this.props
const Tag = node.kind == 'block' ? 'div' : 'span'
const style = {
position: 'relative'
}
return (
<Tag style={style} onClick={e => this.onClick(e)}>
{this.renderSpacer()}
<span contenteditable={false}>{children}</span>
</Tag>
)
}
renderSpacer() {
const style = {
position: 'absolute',
top: '0px', // vertically the same, to not scroll the window
left: '-10000px'
}
return (
<span style={style}>{this.renderLeaf()}</span>
)
}
renderLeaf() {
const { node, state } = this.props
const child = node.getTextNodes().first()
const text = ''
const marks = Mark.createSet()
const start = 0
const end = 0
const offsetKey = OffsetKey.stringify({
key: child.key,
start,
end
})
return (
<Leaf
ref={el => this.leaf = el}
key={offsetKey}
state={state}
node={child}
start={start}
end={end}
text={text}
marks={marks}
renderMark={mark => {}}
/>
)
}
}
/**
* Export.
*/
export default Void

View File

@ -30,6 +30,12 @@ function findKey(node) {
const child = node.querySelector(SELECTOR)
if (child) return child.getAttribute(ATTRIBUTE)
// Otherwise, move up the tree looking for cousin offset keys in parents.
while (node = node.parentNode) {
const cousin = node.querySelector(SELECTOR)
if (cousin) return cousin.getAttribute(ATTRIBUTE)
}
// Shouldn't get here... else we have an edge case to handle.
console.error('No offset key found for node:', node)
}
@ -45,9 +51,15 @@ function findKey(node) {
function findPoint(node, offset) {
const key = findKey(node)
const parsed = parse(key)
// Don't let the offset be outside the start and end bounds.
offset = parsed.start + offset
offset = Math.max(offset, parsed.start)
offset = Math.min(offset, parsed.end)
return {
key: parsed.key,
offset: parsed.start + offset
offset
}
}