From d64c7117dde419e53561e7580dbf09e51fbd7f13 Mon Sep 17 00:00:00 2001 From: Tim <roadscape@users.noreply.github.com> Date: Mon, 10 Oct 2016 14:10:49 -0400 Subject: [PATCH] image drag-drop #185 --- app/components/elements/SlateEditor.jsx | 32 ++++-- app/components/elements/SlateEditor.scss | 40 ++++--- app/utils/SlateEditor/Image.js | 45 ++++++++ app/utils/SlateEditor/Schema.js | 134 +++++++++++++---------- package.json | 1 + 5 files changed, 170 insertions(+), 82 deletions(-) create mode 100644 app/utils/SlateEditor/Image.js diff --git a/app/components/elements/SlateEditor.jsx b/app/components/elements/SlateEditor.jsx index f2e97f6af..a96256a09 100644 --- a/app/components/elements/SlateEditor.jsx +++ b/app/components/elements/SlateEditor.jsx @@ -11,7 +11,25 @@ export const serializeHtml = (state) => serializer.serialize(state) export const deserializeHtml = (html) => serializer.deserialize(html) export const getDemoState = () => Raw.deserialize(demoState, { terse: true }) -const plugins = [] +let plugins = [] + +if(process.env.BROWSER) { + //import InsertImages from 'slate-drop-or-paste-images' + const InsertImages = require('slate-drop-or-paste-images').default + + plugins.push( + InsertImages({ + extensions: ['jpeg'], + applyTransform: (transform, file) => { + return transform.insertBlock({ + type: 'image', + isVoid: true, + data: { file } + }) + } + }) + ) +} export default class SlateEditor extends React.Component { @@ -138,7 +156,6 @@ export default class SlateEditor extends React.Component { .apply() } - render = () => { const { state } = this.state return ( @@ -155,22 +172,19 @@ export default class SlateEditor extends React.Component { return ( <Portal isOpened onOpen={this.onOpen}> <div className="SlateEditor__menu SlateEditor__hover-menu"> - {this.renderMarkButton('bold', <strong>B</strong>)} - {this.renderMarkButton('italic', <i>I</i>)} - {this.renderMarkButton('underline', <u>U</u>)} - {this.renderMarkButton('strike', <del>S</del>)} - {this.renderMarkButton('code', <code>{'{}'}</code>)} + {schema.toolbarMarks.map(this.renderMarkButton)} </div> </Portal> ) } - renderMarkButton = (type, label) => { + renderMarkButton = (props) => { + const {type, label} = props const isActive = this.hasMark(type) const onMouseDown = e => this.onClickMark(e, type) return ( - <span className="SlateEditor__menu-button" onMouseDown={onMouseDown} data-active={isActive}> + <span key={type} className="SlateEditor__menu-button" onMouseDown={onMouseDown} data-active={isActive}> <span>{label}</span> </span> ) diff --git a/app/components/elements/SlateEditor.scss b/app/components/elements/SlateEditor.scss index 3d3cc2dbc..4edf52744 100644 --- a/app/components/elements/SlateEditor.scss +++ b/app/components/elements/SlateEditor.scss @@ -6,6 +6,7 @@ .SlateEditor.Markdown { a {border-bottom: 1px dotted #00f;} + img.active {box-shadow: 0 0 0 2px blue;} } .SlateEditor > * > * + * { @@ -17,7 +18,7 @@ } .SlateEditor__menu > * + * { - margin-left: 4px; + margin: 0; } .SlateEditor__menu-button { @@ -26,28 +27,26 @@ cursor: pointer; > span { display: inline-block; - width: 1.5rem; + width: 2rem; text-align: center; - background: #555; + background: rgba(0,0,0,0.1); border-radius: 2px; code { border: none; background: transparent; color: inherit; + padding: 0; + font-size: 90%; + vertical-align: top; } } } -.SlateEditor__menu-button[data-active="false"] { - > span:hover { - color: #333; - background: #CCC; - } -} +.SlateEditor__menu-button[data-active="false"]:hover, .SlateEditor__menu-button[data-active="true"] { > span { - color: #333; - background: #EEE; + color: #32cd32; + background: rgba(0,0,0,0.5); } } @@ -61,8 +60,8 @@ */ .SlateEditor__hover-menu { - font-size: 90%; - padding: 5px; + font-size: 110%; + padding: 1px; position: absolute; z-index: 1; top: -10000px; @@ -72,4 +71,19 @@ background-color: #222; border-radius: 4px; transition: opacity .75s; + background-image: linear-gradient(180deg,#464646,#151515); +} + +.SlateEditor__hover-menu:after { + top: 100%; + left: 50%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-top-color: #151515; + border-width: 5px; + margin-left: -5px; } diff --git a/app/utils/SlateEditor/Image.js b/app/utils/SlateEditor/Image.js new file mode 100644 index 000000000..2223c9145 --- /dev/null +++ b/app/utils/SlateEditor/Image.js @@ -0,0 +1,45 @@ +import React from 'react' + +export default class Image extends React.Component { + state = {}; + + componentDidMount() { + console.log("** image mounted..", this.state) + const { node } = this.props + const { data } = node + const file = data.get('file') + if(file) this.load(file) + } + + load(file) { + console.log("** image being loaded.. ----->", file) + const reader = new FileReader() + reader.addEventListener('load', () => this.setState({ src: reader.result })) + reader.readAsDataURL(file) + } + + render() { + const { node, state, attributes } = this.props + let { src } = this.state + + const isFocused = state.selection.hasEdgeIn(node) + const className = isFocused ? 'active' : null + + if(src) { + console.log("** uploaded image being rendered..", src, state) + } else { + const src2 = node.data.get('src') + console.log("** image source was not in state. data.src = ", src2) + src = src2; + } + + if(!src) { + // src = 'https://img1.steemit.com/0x0/http://ariasprado.name/wp-content/uploads/2012/09/missing-tile-256x256.png' + // src = $STM_Config.img_proxy_prefix + '0x0/' + src + } + + return src + ? <img {...attributes} src={src} className={className} /> + : <span>Loading... ({src})</span> + } +} diff --git a/app/utils/SlateEditor/Schema.js b/app/utils/SlateEditor/Schema.js index 162a9ae56..38b95d242 100644 --- a/app/utils/SlateEditor/Schema.js +++ b/app/utils/SlateEditor/Schema.js @@ -1,4 +1,5 @@ import React from 'react' +import Image from 'app/utils/SlateEditor/Image' /* @@ -21,6 +22,7 @@ img */ +// Map html --> block type const BLOCK_TAGS = { blockquote: 'block-quote', p: 'paragraph', @@ -34,6 +36,7 @@ const BLOCK_TAGS = { li: 'bulleted-list-item', } +// Map HTML --> mark type const MARK_TAGS = { em: 'italic', i: 'italic', @@ -43,11 +46,8 @@ const MARK_TAGS = { del: 'strike', strike: 'strike', code: 'code', -/* sup: 'sup', sub: 'sub', - -*/ } @@ -56,57 +56,58 @@ export const HtmlRules = [ // Block rules { deserialize: (el, next) => { - let type = BLOCK_TAGS[el.tagName] - if (!type) return + 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) - } + return { + kind: 'block', + type: type, + nodes: next(el.children) + } }, serialize: (object, children) => { if(object.kind !== 'block') return - switch(object.type) { + 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> - } + 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) - } + 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> - } + 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> + case 'sup': return <sup>{children}</sup> + case 'sub': return <sub>{children}</sub> + } } }, - // Custom { deserialize: (el, next) => { @@ -121,51 +122,65 @@ export const HtmlRules = [ return { kind: 'block', type: 'image', + isVoid: true, data: {src: el.attribs.src}, nodes: next(el.children) } } if (el.tagName == 'a') { - console.log("deserialized <a>, the href is", el.attribs.href) + const {href} = el.attribs + if(!href) console.log("** ERR: deserialized <a> with no href") return { kind: 'block', type: 'link', - data: {href: el.attribs.href}, + data: {href: 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) + 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> + const href = object.data.get('href') + if(!href) console.log("** ERR: serializing <a> with no href", JSON.stringify(object.data, null, 2)) + return <a href={href}>{children}</a> } if(object.kind == 'block' && object.type == 'image') { const data = object.data const src = data.get('src') + if(!src) { + console.log("** ERR: serializing image with no src...") + console.log("Serializing image.... data:", JSON.stringify(data)) + console.log("Serializing image.... object:", JSON.stringify(object)) + console.log("Serializing image.... state:", JSON.stringify(object.state)) + console.log("Serializing image.... node:", JSON.stringify(object.node)) + } return <img src={src} /> } - console.log("No serializer for: ", object.kind, JSON.stringify(object, null, 2), children) + console.log("No serializer for: ", object.kind, JSON.stringify(object, null, 2), children) } } ] export const schema = { defaultNode: 'paragraph', + toolbarMarks: [ + { type: 'bold', label: <strong>B</strong> }, + { type: 'italic', label: <i>I</i> }, + { type: 'underline', label: <u>U</u> }, + { type: 'strike', label: <del>S</del> }, + { type: 'code', label: <code>{'{}'}</code> }, + { type: 'sup', label: <span>x<sup>2</sup></span> }, + { type: 'sub', label: <span>x<sub>2</sub></span> }, + ], + /* 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' }, @@ -175,6 +190,7 @@ export const schema = { ], sidebarTypes: [], */ + nodes: { 'block': ({ children }) => <p style={{background: 'red'}}>{children}</p>, 'paragraph': ({ children }) => <p>{children}</p>, @@ -187,24 +203,22 @@ export const schema = { '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> - }, + 'image': Image, + 'link': (props) => { + const { data } = props.node + const href = data.get('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>, + sub: props => <sub>{props.children}</sub>, + sup: props => <sup>{props.children}</sup>, }, } diff --git a/package.json b/package.json index 4217efa72..68eeca55d 100644 --- a/package.json +++ b/package.json @@ -114,6 +114,7 @@ "sequelize": "^3.21.0", "sequelize-cli": "^2.3.1", "slate": "^0.14.13", + "slate-drop-or-paste-images": "^0.2.0", "speakingurl": "^9.0.0", "style-loader": "^0.13.0", "svg-inline-loader": "^0.4.0", -- GitLab