1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-29 18:09:49 +02:00

Example how to render slate to IFrame

This commit is contained in:
Vladimir
2016-08-03 03:31:26 +03:00
parent 186f1a7c6d
commit 95eb2c0bdf
4 changed files with 413 additions and 69 deletions

View File

@@ -1,7 +1,14 @@
# IFrame rendering example # IFrame rendering example
This example shows how to render Slate into IFrame, preserving single react component tree. This example shows how to render Slate into IFrame, preserving single react component tree.
You may need this if you want to have separate styles for editor content & application. You may need this if you want to have separate styles for editor content & application.
In example this exmaple you can see,
that editor is using bootstrap styles, while they are not included to parent page.
## React onSelect problem
Current react version has a problem with onSelect event handling, if input is rendered from parent component tree to iframe.
This problem is solved by custom SelectEventPlugin - [react-frame-aware-selection-plugin](https://www.npmjs.com/package/react-frame-aware-selection-plugin)
Check out the [Examples readme](..) to see how to run it! Check out the [Examples readme](..) to see how to run it!

View File

@@ -1,77 +1,129 @@
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
function resolveDocument (reactIFrameElementNode) { import injector from 'react-frame-aware-selection-plugin'
const iFrame = ReactDOM.findDOMNode(reactIFrameElementNode); injector();
return iFrame.contentDocument
import { Editor, Mark, Raw } from '../..'
import initialState from './state.json'
import Frame from 'react-frame-component'
const MARKS = {
bold: {
fontWeight: 'bold'
},
italic: {
fontStyle: 'italic'
}
} }
//appending context to body > div, to suppress react warning const NODES = {
function getRootDiv (doc) { 'table': props => <table className={"table"}><tbody {...props.attributes}>{props.children}</tbody></table>,
let rootDiv = doc.querySelector('div#root') 'table-row': props => <tr {...props.attributes}>{props.children}</tr>,
if (!rootDiv) { 'table-cell': props => <td {...props.attributes}>{props.children}</td>
rootDiv = doc.createElement('div')
rootDiv.setAttribute('id', 'root')
rootDiv.id = 'root'
rootDiv.setAttribute('style', 'width: 100%; height: 100%')
doc.body.appendChild(rootDiv)
}
return rootDiv
}
class IFrame extends React.Component {
static propTypes = {
head: React.PropTypes.node,
children: React.PropTypes.node,
}
//rendering plain frame.
render () {
return <iframe style={{border: 'solid 1px black', width: '100%'}}></iframe>
}
componentDidMount = () => {
this.renderContents()
}
componentDidUpdate = () => {
this.renderContents()
}
componentWillUnmount = () => this.getDocument().then((doc) => {
ReactDOM.unmountComponentAtNode(doc.body)
if (this.props.head) {
ReactDOM.unmountComponentAtNode(doc.head)
}
})
renderContents = () => this.getDocument().then((doc) => {
if (this.props.head) {
ReactDOM.unstable_renderSubtreeIntoContainer(this, this.props.head, doc.head)
}
const rootDiv = getRootDiv(doc)
ReactDOM.unstable_renderSubtreeIntoContainer(this, this.props.children, rootDiv)
})
getDocument = () => new Promise((resolve) => {
const resolveTick = () => { //using arrow function to preserve `this` context
let doc = resolveDocument(this)
if (doc && doc.readyState === 'complete') {
resolve(doc)
} else {
window.requestAnimationFrame(resolveTick)
}
}
resolveTick()
})
} }
class IFrameRendering extends React.Component { class IFrameRendering extends React.Component {
state = {
state: Raw.deserialize(initialState, { terse: true })
};
onChange = (state) => {
this.setState({ state })
}
/**
* On backspace, do nothing if at the start of a table cell.
*
* @param {Event} e
* @param {State} state
* @return {State or Null} state
*/
onBackspace = (e, state) => {
if (state.startOffset != 0) return
e.preventDefault()
return state
}
/**
* On change.
*
* @param {State} state
*/
onChange = (state) => {
this.setState({ state })
}
/**
* On delete, do nothing if at the end of a table cell.
*
* @param {Event} e
* @param {State} state
* @return {State or Null} state
*/
onDelete = (e, state) => {
if (state.endOffset != state.startText.length) return
e.preventDefault()
return state
}
/**
* On return, do nothing if inside a table cell.
*
* @param {Event} e
* @param {State} state
* @return {State or Null} state
*/
onEnter = (e, state) => {
e.preventDefault()
return state
}
/**
* On key down, check for our specific key shortcuts.
*
* @param {Event} e
* @param {Object} data
* @param {State} state
* @return {State or Null} state
*/
onKeyDown = (e, data, state) => {
if (state.startBlock.type != 'table-cell') return
switch (data.key) {
case 'backspace': return this.onBackspace(e, state)
case 'delete': return this.onDelete(e, state)
case 'enter': return this.onEnter(e, state)
}
}
/**
* Return a node renderer for a Slate `node`.
*
* @param {Node} node
* @return {Component or Void}
*/
renderNode = (node) => {
return NODES[node.type]
}
/**
* Return a mark renderer for a Slate `mark`.
*
* @param {Mark} mark
* @return {Object or Void}
*/
renderMark = (mark) => {
return MARKS[mark.type]
}
render () { render () {
const bootstrapCDN = const bootstrapCDN =
<link <link
@@ -82,9 +134,15 @@ class IFrameRendering extends React.Component {
</link> </link>
return ( return (
<IFrame head={bootstrapCDN}> <Frame head={bootstrapCDN} style={{width: `100%`, height: '300px'}}>
<div>I'm in iframe</div> <Editor
</IFrame> state={this.state.state}
onChange={this.onChange}
renderNode={this.renderNode}
renderMark={this.renderMark}
onKeyDown={this.onKeyDown}
/>
</Frame>
) )
} }

View File

@@ -0,0 +1,277 @@
{
"nodes": [
{
"kind": "block",
"type": "paragraph",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "This example shows how you can render editor in frame component."
}
]
}
]
},
{
"kind": "block",
"type": "paragraph",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "Check out this table, it has bootstrap styles, and top level document is not polluted by "
},
{
"text": "bootstrap.min.css",
"marks": [
{
"type": "italic"
},
{
"type": "bold"
}
]
},
{
"text": "."
}
]
}
]
},
{
"kind": "block",
"type": "paragraph",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": ""
}
]
}
]
},
{
"kind": "block",
"type": "table",
"nodes": [
{
"kind": "block",
"type": "table-row",
"nodes": [
{
"kind": "block",
"type": "table-cell",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": ""
}
]
}
]
},
{
"kind": "block",
"type": "table-cell",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "Human",
"marks": [
{
"type": "bold"
}
]
}
]
}
]
},
{
"kind": "block",
"type": "table-cell",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "Dog",
"marks": [
{
"type": "bold"
}
]
}
]
}
]
},
{
"kind": "block",
"type": "table-cell",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "Cat",
"marks": [
{
"type": "bold"
}
]
}
]
}
]
}
]
},
{
"kind": "block",
"type": "table-row",
"nodes": [
{
"kind": "block",
"type": "table-cell",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "# of Feet",
"marks": [
{
"type": "bold"
}
]
}
]
}
]
},
{
"kind": "block",
"type": "table-cell",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "1"
}
]
}
]
},
{
"kind": "block",
"type": "table-cell",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "4"
}
]
}
]
},
{
"kind": "block",
"type": "table-cell",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "4"
}
]
}
]
}
]
},
{
"kind": "block",
"type": "table-row",
"nodes": [
{
"kind": "block",
"type": "table-cell",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "# of Lives",
"marks": [
{
"type": "bold"
}
]
}
]
}
]
},
{
"kind": "block",
"type": "table-cell",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "1"
}
]
}
]
},
{
"kind": "block",
"type": "table-cell",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "1"
}
]
}
]
},
{
"kind": "block",
"type": "table-cell",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "9"
}
]
}
]
}
]
}
]
}
]
}

View File

@@ -48,6 +48,8 @@
"react": "^15.2.0", "react": "^15.2.0",
"react-addons-perf": "^15.2.1", "react-addons-perf": "^15.2.1",
"react-dom": "^15.1.0", "react-dom": "^15.1.0",
"react-frame-aware-selection-plugin": "0.0.1",
"react-frame-component": "^0.6.2",
"react-router": "^2.5.1", "react-router": "^2.5.1",
"read-metadata": "^1.0.0", "read-metadata": "^1.0.0",
"selection-position": "^1.0.0", "selection-position": "^1.0.0",