Skip to content
Snippets Groups Projects
Commit 21d6364a authored by Tim's avatar Tim
Browse files

editor serializer and integration progress #185

parent abb3e1a9
No related branches found
No related tags found
No related merge requests found
/* import React from 'react'
import EditBlockquote from 'slate-edit-blockquote'
import TrailingBlock from 'slate-trailing-block'
const plugins = [
TrailingBlock({ type: 'paragraph' }),
EditBlockquote()
]
*/
const plugins = []
import { Editor, Mark, Raw, Html } from 'slate' import { Editor, Mark, Raw, Html } from 'slate'
import Portal from 'react-portal' import Portal from 'react-portal'
import React from 'react'
import position from 'selection-position' import position from 'selection-position'
const serializer = new Html({rules: [ import demoState from 'app/utils/SlateEditor/DemoState'
{ import {HtmlRules, schema, getMarkdownType} from 'app/utils/SlateEditor/Schema'
deserialize: (el, next) => null,
serialize: (object, children) => { const serializer = new Html({rules: HtmlRules})
if(object.kind == 'string') return; export const serializeHtml = (state) => serializer.serialize(state)
if(object.kind == 'block') { export const deserializeHtml = (html) => serializer.deserialize(html)
switch(object.type) { export const getDemoState = () => Raw.deserialize(demoState, { terse: true })
case 'paragraph': return <p>{children}</p>
case 'block-quote': return <blockquote>{children}</blockquote> const plugins = []
case 'bulleted-list': return <ul>{children}</ul>
case 'numbered-list': return <ol>{children}</ol>
case 'heading-one': return <h1>{children}</h1>
case 'heading-two': return <h2>{children}</h2>
case 'heading-three': return <h3>{children}</h3>
case 'heading-four': return <h4>{children}</h4>
case 'bulleted-list-item': return <li>{children}</li>
case 'numbered-list-item': return <li>{children}</li>
}
}
if(object.kind == 'mark') {
switch(object.type) {
case 'bold': return <strong>{children}</strong>
case 'italic': return <i>{children}</i>
case 'underline': return <u>{children}</u>
case 'strike': return <s>{children}</s>
case 'code': return <code>{children}</code>
}
}
console.log("No serializer: ", object.kind, JSON.stringify(object, null, 2), children)
}
},
]})
const schema = {
defaultNode: 'paragraph',
//blockTypes: {
// ...Blocks,
//},
toolbarMarks: [
{ type: 'bold', icon: 'bold' },
{ type: 'italic', icon: 'italic' },
{ type: 'underline', icon: 'underline' },
{ type: 'code', icon: 'code' },
],
toolbarTypes: [
{ type: 'heading-one', icon: 'header' },
{ type: 'heading-two', icon: 'header' },
{ type: 'block-quote', icon: 'quote-left' },
{ type: 'numbered-list', icon: 'list-ol' },
{ type: 'bulleted-list', icon: 'list-ul' },
],
sidebarTypes: [],
nodes: {
'block': ({ children }) => <p style={{background: 'red'}}>{children}</p>,
'paragraph': ({ children }) => <p style={{color: 'blue'}}>{children}</p>,
'block-quote': ({ children }) => <blockquote>{children}</blockquote>,
'bulleted-list': ({ children }) => <ul>{children}</ul>,
'numbered-list': ({ children, attributes }) => <ol {...attributes}>{children}</ol>,
'heading-one': ({ children }) => <h1>{children}</h1>,
'heading-two': ({ children }) => <h2>{children}</h2>,
'heading-three': ({ children }) => <h3>{children}</h3>,
'heading-four': ({ children }) => <h4>{children}</h4>,
'bulleted-list-item': ({ children }) => <li>{children}</li>,
'numbered-list-item': ({ children }) => <li>{children}</li>,
},
marks: {
bold: props => <strong>{props.children}</strong>,
code: props => <code>{props.children}</code>,
italic: props => <em>{props.children}</em>,
underline: props => <u>{props.children}</u>,
strike: props => <s>{props.children}</s>,
},
getMarkdownType: (chars) => {
switch (chars) {
case '*':
case '-': return 'bulleted-list-item';
case '>': return 'block-quote';
case '#': return 'heading-one';
case '##': return 'heading-two';
case '###': return 'heading-three';
case '####': return 'heading-four';
case '1.': return 'numbered-list-item';
default: return null;
}
},
}
class SlateEditor extends React.Component { export default class SlateEditor extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
...@@ -126,6 +37,7 @@ class SlateEditor extends React.Component { ...@@ -126,6 +37,7 @@ class SlateEditor extends React.Component {
onChange = (state) => { onChange = (state) => {
this.setState({ state }) this.setState({ state })
this.props.onChange(state)
} }
// When a mark button is clicked, toggle the current mark. // When a mark button is clicked, toggle the current mark.
...@@ -146,7 +58,6 @@ class SlateEditor extends React.Component { ...@@ -146,7 +58,6 @@ class SlateEditor extends React.Component {
this.setState({ menu: portal.firstChild }) this.setState({ menu: portal.firstChild })
} }
// Markdown-style quick formatting // Markdown-style quick formatting
onKeyDown = (e, data, state) => { onKeyDown = (e, data, state) => {
switch (data.key) { switch (data.key) {
...@@ -162,7 +73,7 @@ class SlateEditor extends React.Component { ...@@ -162,7 +73,7 @@ class SlateEditor extends React.Component {
let { selection } = state let { selection } = state
const { startText, startBlock, startOffset } = state const { startText, startBlock, startOffset } = state
const chars = startBlock.text.slice(0, startOffset).replace(/\s*/g, '') const chars = startBlock.text.slice(0, startOffset).replace(/\s*/g, '')
const type = schema.getMarkdownType(chars) const type = getMarkdownType(chars)
if (!type) return if (!type) return
if (type == 'bulleted-list-item' && startBlock.type == 'bulleted-list-item') return if (type == 'bulleted-list-item' && startBlock.type == 'bulleted-list-item') return
...@@ -230,7 +141,6 @@ class SlateEditor extends React.Component { ...@@ -230,7 +141,6 @@ class SlateEditor extends React.Component {
render = () => { render = () => {
const { state } = this.state const { state } = this.state
console.log(serializer.serialize(state));
return ( return (
<div> <div>
{this.renderMenu()} {this.renderMenu()}
...@@ -248,7 +158,7 @@ class SlateEditor extends React.Component { ...@@ -248,7 +158,7 @@ class SlateEditor extends React.Component {
{this.renderMarkButton('bold', <strong>B</strong>)} {this.renderMarkButton('bold', <strong>B</strong>)}
{this.renderMarkButton('italic', <i>I</i>)} {this.renderMarkButton('italic', <i>I</i>)}
{this.renderMarkButton('underline', <u>U</u>)} {this.renderMarkButton('underline', <u>U</u>)}
{this.renderMarkButton('strike', <s>S</s>)} {this.renderMarkButton('strike', <del>S</del>)}
{this.renderMarkButton('code', <code>{'{}'}</code>)} {this.renderMarkButton('code', <code>{'{}'}</code>)}
</div> </div>
</Portal> </Portal>
...@@ -268,7 +178,7 @@ class SlateEditor extends React.Component { ...@@ -268,7 +178,7 @@ class SlateEditor extends React.Component {
renderEditor = () => { renderEditor = () => {
return ( return (
<div className="SlateEditor"> <div className="SlateEditor Markdown">
<Editor <Editor
schema={schema} schema={schema}
plugins={plugins} plugins={plugins}
...@@ -295,5 +205,3 @@ class SlateEditor extends React.Component { ...@@ -295,5 +205,3 @@ class SlateEditor extends React.Component {
menu.style.left = `${rect.left + window.scrollX - menu.offsetWidth / 2 + rect.width / 2}px` menu.style.left = `${rect.left + window.scrollX - menu.offsetWidth / 2 + rect.width / 2}px`
} }
} }
export default SlateEditor
...@@ -17,18 +17,18 @@ ...@@ -17,18 +17,18 @@
} }
.SlateEditor__menu > * + * { .SlateEditor__menu > * + * {
margin-left: 10px; margin-left: 4px;
} }
.SlateEditor__menu-button { .SlateEditor__menu-button {
font-family: 'Georgia', serif; font-family: 'Georgia', serif;
color: #777; color: #FFF;
cursor: pointer; cursor: pointer;
> span { > span {
display: inline-block; display: inline-block;
width: 2rem; width: 1.5rem;
text-align: center; text-align: center;
background: #333; background: #555;
border-radius: 2px; border-radius: 2px;
code { code {
border: none; border: none;
...@@ -36,16 +36,18 @@ ...@@ -36,16 +36,18 @@
color: inherit; color: inherit;
} }
} }
}
.SlateEditor__menu-button[data-active="false"] {
> span:hover { > span:hover {
background: #555; color: #333;
color: white; background: #CCC;
} }
} }
.SlateEditor__menu-button[data-active="true"] { .SlateEditor__menu-button[data-active="true"] {
color: white;
> span { > span {
background: #555; color: #333;
background: #EEE;
} }
} }
...@@ -59,7 +61,8 @@ ...@@ -59,7 +61,8 @@
*/ */
.SlateEditor__hover-menu { .SlateEditor__hover-menu {
padding: 8px 7px 6px; font-size: 90%;
padding: 5px;
position: absolute; position: absolute;
z-index: 1; z-index: 1;
top: -10000px; top: -10000px;
...@@ -70,11 +73,3 @@ ...@@ -70,11 +73,3 @@
border-radius: 4px; border-radius: 4px;
transition: opacity .75s; transition: opacity .75s;
} }
.SlateEditor__hover-menu .button {
color: #aaa;
}
.SlateEditor__hover-menu .button[data-active="true"] {
color: #fff;
}
export default {
"nodes": [
{
"kind": "block",
"type": "paragraph",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "This is editable "
},
{
"text": "rich",
"marks": [
{
"type": "bold"
}
]
},
{
"text": " text, "
},
{
"text": "much",
"marks": [
{
"type": "italic"
}
]
},
{
"text": " better than a "
},
{
"text": "<textarea>",
"marks": [
{
"type": "code"
}
]
},
{
"text": "!"
}
]
}
]
},
{
"kind": "block",
"type": "paragraph",
"nodes": [
{
"kind": "text",
"ranges": [
{
"text": "Since it's rich text, you can do things like turn a selection of text "
},
{
"text": "bold",
"marks": [
{
"type": "bold"
}
]
},{
"text": ", or add a semantically rendered block quote in the middle of the page, like this:"
}
]
}
]
},
{
"kind": "block",
"type": "block-quote",
"nodes": [
{
"kind": "text",
"text": "A wise quote."
}
]
},
{
"kind": "block",
"type": "paragraph",
"nodes": [
{
"kind": "text",
"text": "Try it out for yourself!"
}
]
}
]
}
import React from 'react'
/*
--deprecate
s, strike?
q
h5, h6
--unsupported
hr
div ['pull-right', 'pull-left', 'text-justify', 'text-rtl']
iframe
br
center
table, thead, tbody, tr, th, td
--inline
a
img
*/
const BLOCK_TAGS = {
blockquote: 'block-quote',
p: 'paragraph',
pre: 'code',
h1: 'heading-one',
h2: 'heading-two',
h3: 'heading-three',
h4: 'heading-four',
ul: 'bulleted-list',
ol: 'numbered-list',
li: 'bulleted-list-item',
}
const MARK_TAGS = {
em: 'italic',
i: 'italic',
strong: 'bold',
b: 'bold',
u: 'underline',
del: 'strike',
strike: 'strike',
code: 'code',
/*
sup: 'sup',
sub: 'sub',
*/
}
export const HtmlRules = [
// Block rules
{
deserialize: (el, next) => {
let type = BLOCK_TAGS[el.tagName]
if (!type) return
if(type == 'bulleted-list-item' && el.parent.name == 'ol') type = 'numbered-list-item'
return {
kind: 'block',
type: type,
nodes: next(el.children)
}
},
serialize: (object, children) => {
if(object.kind !== 'block') return
switch(object.type) {
case 'code': return <pre><code>{children}</code></pre>
case 'paragraph': return <p>{children}</p>
case 'block-quote': return <blockquote>{children}</blockquote>
case 'bulleted-list': return <ul>{children}</ul>
case 'numbered-list': return <ol>{children}</ol>
case 'heading-one': return <h1>{children}</h1>
case 'heading-two': return <h2>{children}</h2>
case 'heading-three': return <h3>{children}</h3>
case 'heading-four': return <h4>{children}</h4>
case 'bulleted-list-item': return <li>{children}</li>
case 'numbered-list-item': return <li>{children}</li>
}
}
},
// Mark rules
{
deserialize: (el, next) => {
const type = MARK_TAGS[el.tagName]
if (!type) return
return {
kind: 'mark',
type: type,
nodes: next(el.children)
}
},
serialize: (object, children) => {
if(object.kind !== 'mark') return;
switch(object.type) {
case 'bold': return <strong>{children}</strong>
case 'italic': return <i>{children}</i>
case 'underline': return <u>{children}</u>
case 'strike': return <del>{children}</del>
case 'code': return <code>{children}</code>
}
}
},
// Custom
{
deserialize: (el, next) => {
if (el.tagName == 'iframe') {
return {
kind: 'block',
type: 'paragraph',
nodes: next(el.children)
}
}
if (el.tagName == 'img') {
return {
kind: 'block',
type: 'image',
data: {src: el.attribs.src},
nodes: next(el.children)
}
}
if (el.tagName == 'a') {
console.log("deserialized <a>, the href is", el.attribs.href)
return {
kind: 'block',
type: 'link',
data: {href: el.attribs.href},
nodes: next(el.children)
}
}
if(el.type == 'text') return
if(BLOCK_TAGS[el.tagName] || MARK_TAGS[el.tagName]) return
console.log("No deserializer for: ", el.tagName, el)
},
serialize: (object, children) => {
if(object.kind == 'string') return;
if(object.kind == 'block' && object.type == 'link') {
console.log("Serialized <a>, the href is", object.data.get('href'), JSON.stringify(object.data, null, 2))
return <a href={object.data.get('href')}>{children}</a>
}
if(object.kind == 'block' && object.type == 'image') {
const data = object.data
const src = data.get('src')
return <img src={src} />
}
console.log("No serializer for: ", object.kind, JSON.stringify(object, null, 2), children)
}
}
]
export const schema = {
defaultNode: 'paragraph',
/*
blockTypes: {
...Blocks,
},
toolbarMarks: [
{ type: 'bold', icon: 'bold' },
{ type: 'italic', icon: 'italic' },
{ type: 'underline', icon: 'underline' },
{ type: 'code', icon: 'code' },
],
toolbarTypes: [
{ type: 'heading-one', icon: 'header' },
{ type: 'heading-two', icon: 'header' },
{ type: 'block-quote', icon: 'quote-left' },
{ type: 'numbered-list', icon: 'list-ol' },
{ type: 'bulleted-list', icon: 'list-ul' },
],
sidebarTypes: [],
*/
nodes: {
'block': ({ children }) => <p style={{background: 'red'}}>{children}</p>,
'paragraph': ({ children }) => <p>{children}</p>,
'block-quote': ({ children }) => <blockquote>{children}</blockquote>,
'bulleted-list': ({ children }) => <ul>{children}</ul>,
'numbered-list': ({ children, attributes }) => <ol {...attributes}>{children}</ol>,
'heading-one': ({ children }) => <h1>{children}</h1>,
'heading-two': ({ children }) => <h2>{children}</h2>,
'heading-three': ({ children }) => <h3>{children}</h3>,
'heading-four': ({ children }) => <h4>{children}</h4>,
'bulleted-list-item': ({ children }) => <li>{children}</li>,
'numbered-list-item': ({ children }) => <li>{children}</li>,
'image': (props) => {
const { data } = props.node
const src = data.get('src')
return <img {...props.attributes} src={'https://img1.steemit.com/0x0/' + src} />
},
'link': (props) => {
const { data } = props.node
const href = data.get('href')
console.log("rendering link...href=",href)
return <a {...props.attributes} href={href}>{props.children}</a>
},
},
marks: {
bold: props => <strong>{props.children}</strong>,
code: props => <code>{props.children}</code>,
italic: props => <em>{props.children}</em>,
underline: props => <u>{props.children}</u>,
strike: props => <del>{props.children}</del>,
},
}
export const getMarkdownType = (chars) => {
switch (chars) {
case '*':
case '-': return 'bulleted-list-item';
case '>': return 'block-quote';
case '#': return 'heading-one';
case '##': return 'heading-two';
case '###': return 'heading-three';
case '####': return 'heading-four';
case '1.': return 'numbered-list-item';
default: return null;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment