From 4b2274eeb4a2ba248693b87accf1288d6ea16bbc Mon Sep 17 00:00:00 2001
From: Tim <roadscape@users.noreply.github.com>
Date: Thu, 20 Oct 2016 16:53:11 -0400
Subject: [PATCH] initial video embed

---
 app/assets/icons/video.svg               |   1 +
 app/components/elements/Icon.jsx         |   1 +
 app/components/elements/SlateEditor.jsx  |  17 +++-
 app/components/elements/SlateEditor.scss |   1 +
 app/utils/SlateEditor/Iframe.js          | 103 +++++++++++++++++++++++
 app/utils/SlateEditor/Schema.js          |   2 +
 6 files changed, 124 insertions(+), 1 deletion(-)
 create mode 100644 app/assets/icons/video.svg
 create mode 100644 app/utils/SlateEditor/Iframe.js

diff --git a/app/assets/icons/video.svg b/app/assets/icons/video.svg
new file mode 100644
index 000000000..4acd2bbfa
--- /dev/null
+++ b/app/assets/icons/video.svg
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M19,19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M10,8V16L15,12L10,8Z" /></svg>
\ No newline at end of file
diff --git a/app/components/elements/Icon.jsx b/app/components/elements/Icon.jsx
index a1ada9717..07ca9b7b0 100644
--- a/app/components/elements/Icon.jsx
+++ b/app/components/elements/Icon.jsx
@@ -30,6 +30,7 @@ const icons = [
     'reblog',
     'photo',
     'line',
+    'video',
 ];
 const icons_map = {};
 for (const i of icons) icons_map[i] = require(`app/assets/icons/${i}.svg`);
diff --git a/app/components/elements/SlateEditor.jsx b/app/components/elements/SlateEditor.jsx
index 5b6d022f3..5cd6a75b0 100644
--- a/app/components/elements/SlateEditor.jsx
+++ b/app/components/elements/SlateEditor.jsx
@@ -367,6 +367,7 @@ console.log(JSON.stringify(Raw.serialize(state, {terse: false}), null, 2))
             <Portal isOpened onOpen={this.onSidebarOpen}>
                 <div className="SlateEditor__sidebar">
                     {this.renderAddBlockButton({type: 'image', label: <Icon name="photo" />, handler: this.onClickInsertImage})}
+                    {this.renderAddBlockButton({type: 'video', label: <Icon name="video" />, handler: this.onClickInsertVideo})}
                     {this.renderAddBlockButton({type: 'hrule', label: <Icon name="line" />, handler: this.onClickInsertHr})}
                 </div>
             </Portal>
@@ -383,8 +384,22 @@ console.log(JSON.stringify(Raw.serialize(state, {terse: false}), null, 2))
         state = state
             .transform()
             .insertInline({type: 'image', isVoid: true, data: {src}})
-            .insertBlock({type: 'paragraph', isVoid: false, nodes: [Slate.Text.create()]})
+            //.insertBlock({type: 'paragraph', isVoid: false, nodes: [Slate.Text.create()]})
             .focus()
+            .collapseToEndOfNextBlock()
+            .apply()
+
+        this.setState({ state })
+    }
+
+    onClickInsertVideo = (e, type) => {
+        e.preventDefault()
+        let { state } = this.state
+
+        state = state
+            .transform()
+            .insertBlock({type: 'embed', isVoid: true, data: {src: 'https://www.youtube.com/watch?v=7YOozVnEdFQ'}})
+            .insertBlock({type: 'paragraph', isVoid: false})
             .apply()
 
         this.setState({ state })
diff --git a/app/components/elements/SlateEditor.scss b/app/components/elements/SlateEditor.scss
index 675f4cd94..09eb18385 100644
--- a/app/components/elements/SlateEditor.scss
+++ b/app/components/elements/SlateEditor.scss
@@ -4,6 +4,7 @@
     position: relative;
     img {border: 1px dotted #00f}
   }
+  div.active,
   img.active {box-shadow: 0 0 2px 1px #48C;}
   hr.active {box-shadow: 0 0 2px 1px #48C;}
 
diff --git a/app/utils/SlateEditor/Iframe.js b/app/utils/SlateEditor/Iframe.js
new file mode 100644
index 000000000..57b1c4831
--- /dev/null
+++ b/app/utils/SlateEditor/Iframe.js
@@ -0,0 +1,103 @@
+import React from 'react'
+import linksRe from 'app/utils/Links'
+
+export default class Iframe extends React.Component {
+
+    normalizeEmbedUrl = (url) => {
+        // Detect youtube URLs
+        const match = url.match(linksRe.youTubeId)
+        if(match && match.length >= 2) {
+            return 'https://www.youtube.com/embed/' + match[1]
+        }
+
+        console.log("unable to auto-detect embed url", url)
+        return null
+    }
+
+    onChange = (e) => {
+        const { node, state, editor } = this.props
+        const value = e.target.value
+
+        const src = this.normalizeEmbedUrl(value) || value
+
+        const next = editor
+            .getState()
+            .transform()
+            .setNodeByKey(node.key, {data: {src}})
+            .apply()
+
+        editor.onChange(next)
+    }
+
+    onClick = (e) => {
+        // stop propagation so that the void node itself isn't focused, since that would unfocus the input.
+        e.stopPropagation()
+    }
+
+    render = () => {
+        const { node, state, attributes } = this.props
+        const isFocused = state.selection.hasEdgeIn(node)
+        const className = isFocused ? 'active' : null
+        const style = {background: 'black', color: 'white'}
+
+        return (
+            <div {...attributes} className={className} style={style}>
+                {this.renderFrame()}
+                Embed URL: {this.renderInput()}
+            </div>
+        )
+    }
+
+    renderFrame = () => {
+        let src = this.props.node.data.get('src')
+        src = this.normalizeEmbedUrl(src) || src
+
+        const aspectStyle = {
+            position: 'relative',
+            paddingBottom: '56.2%',
+            height: '0'
+        }
+        const lockStyle = {
+            position: 'absolute',
+            top: '0px',
+            left: '0px',
+            width: '100%',
+            height: '100%',
+            background: 'rgba(0,0,0,0.1)'
+        }
+        const style = {
+            position: 'absolute',
+            top: '0px',
+            left: '0px',
+            width: '100%',
+            height: '100%'
+        }
+
+        return (
+            <div style={aspectStyle}>
+                <iframe
+                  type="text/html"
+                  width="640"
+                  height="360"
+                  src={src}
+                  frameBorder="0"
+                  style={style}>
+                </iframe>
+                <div style={lockStyle}></div>
+            </div>
+        )
+    }
+
+    renderInput = () => {
+        const src = this.props.node.data.get('src')
+        return (
+            <input
+              value={src}
+              onChange={this.onChange}
+              onClick={this.onClick}
+              style={{ marginTop: '5px', background: 'black' }}
+              size="70"
+            />
+        )
+    }
+}
diff --git a/app/utils/SlateEditor/Schema.js b/app/utils/SlateEditor/Schema.js
index 048cb1d31..2b103cfd9 100644
--- a/app/utils/SlateEditor/Schema.js
+++ b/app/utils/SlateEditor/Schema.js
@@ -1,6 +1,7 @@
 import React from 'react'
 import Link from 'app/utils/SlateEditor/Link'
 import Image from 'app/utils/SlateEditor/Image'
+import Iframe from 'app/utils/SlateEditor/Iframe'
 import HRule from 'app/utils/SlateEditor/HRule'
 
 
@@ -211,6 +212,7 @@ export const schema = {
         'hr':    HRule,
         'image': Image,
         'link':  Link,
+        'embed': Iframe,
     },
 
     marks: {
-- 
GitLab