Skip to content
Snippets Groups Projects
Commit bab51477 authored by James Calfee's avatar James Calfee
Browse files

Merge branch '164-youtube-iframe-performance' into develop

parents 5a0b6667 25d19ab3
No related branches found
No related tags found
No related merge requests found
......@@ -22,6 +22,7 @@
@import "./elements/TagList";
@import "./elements/ChangePassword";
@import "./elements/Reputation";
@import "./elements/YoutubePreview";
// modules
@import "./modules/Header";
......
......@@ -3,9 +3,11 @@ import {connect} from 'react-redux'
import {Component} from 'react'
import Remarkable from 'remarkable'
// import CardView from 'app/components/cards/CardView'
import YoutubePreview from 'app/components/elements/YoutubePreview'
import sanitizeConfig, {noImageText} from 'app/utils/SanitizeConfig'
import {renderToString} from 'react-dom/server';
import sanitize from 'sanitize-html'
import HtmlReady, {sectionHtml} from 'shared/HtmlReady'
import HtmlReady from 'shared/HtmlReady'
const remarkable = new Remarkable({
html: true, // remarkable renders first then sanitize runs...
......@@ -75,7 +77,7 @@ class MarkdownViewer extends Component {
let renderedText = html ? text : remarkable.render(text)
// Embed videos, link mentions and hashtags, etc...
if(renderedText) renderedText = HtmlReady(renderedText, {large}).html
if(renderedText) renderedText = HtmlReady(renderedText).html
// Complete removal of javascript and other dangerous tags..
// The must remain as close as possible to dangerouslySetInnerHTML
......@@ -89,8 +91,24 @@ class MarkdownViewer extends Component {
const noImageActive = cleanText.indexOf(noImageText) !== -1
// Split and key HTML doc by its root children. This allows react to compare separately preventing excessive re-rendering.
const sections = sectionHtml(cleanText).map( (s, idx) => <div key={idx++} dangerouslySetInnerHTML={{__html: s}} />);
// In addition to inserting the youtube compoennt, this allows react to compare separately preventing excessive re-rendering.
let idx = 0
const sections = []
// HtmlReady inserts ~~~ youtube:${id} ~~~
for(let section of cleanText.split('~~~ youtube:')) {
if(/^[A-Za-z0-9\_\-]+ ~~~/.test(section)) {
const youTubeId = section.split(' ')[0]
section = section.substring(youTubeId.length + ' ~~~'.length)
const w = large ? 640 : 320,
h = large ? 480 : 180
sections.push(
<YoutubePreview key={idx++} width={w} height={h} youTubeId={youTubeId}
frameBorder="0" allowFullScreen="true" />
)
}
if(section === '') continue
sections.push(<div key={idx++} dangerouslySetInnerHTML={{__html: section}} />)
}
const cn = 'Markdown' + (this.props.className ? ` ${this.props.className}` : '') + (html ? ' html' : '')
return (<div className={"MarkdownViewer " + cn}>
......
......@@ -5,30 +5,40 @@ import transaction from 'app/redux/Transaction'
import shouldComponentUpdate from 'app/utils/shouldComponentUpdate'
import {Map} from 'immutable'
const {func, string, object} = React.PropTypes
const {string, object} = React.PropTypes
class Template extends React.Component {
static propTypes = {
}
static defaultProps = {
}
constructor() {
super()
this.state = {}
}
componentWillMount() {
}
componentDidMount() {
}
componentWillReceiveProps(nextProps) {
}
shouldComponentUpdate = shouldComponentUpdate(this, 'Template') // This is based on react PureRenderMixin, it makes the component very efficient by not re-rendering unless something in the props or state changed.. PureRenderMixin comes highly recommended. shouldComponentUpdate adds a debug boolean to show you why your component rendered (what changed, in the browser console type: steemDebug_shouldComponentUpdate=true).
// This is based on react PureRenderMixin, it makes the component very efficient by not re-rendering unless something in the props or state changed.. PureRenderMixin comes highly recommended. shouldComponentUpdate adds a debug boolean to show you why your component rendered (what changed, in the browser console type: steemDebug_shouldComponentUpdate=true).
shouldComponentUpdate = shouldComponentUpdate(this, 'Template')
componentWillUpdate(nextProps, nextState) {
}
componentDidUpdate(prevProps, prevState) {
}
componentWillUnmount() {
}
render() {
const {} = this.props
return (
......@@ -37,7 +47,9 @@ class Template extends React.Component {
)
}
}
import {connect} from 'react-redux'
export default connect(
(state, ownProps) => {
return {
......
/* eslint react/prop-types: 0 */
import React from 'react'
import shouldComponentUpdate from 'app/utils/shouldComponentUpdate'
const {string, number} = React.PropTypes
/** Lots of iframes in a post can be very slow. This component only inserts the iframe when it is actually needed. */
export default class YoutubePreview extends React.Component {
static propTypes = {
youTubeId: string.isRequired,
width: number,
height: number,
dataParams: string,
}
static defaultProps = {
width: 640,
height: 480,
dataParams: 'enablejsapi=0&rel=0&origin=https://steemit.com'
}
constructor() {
super()
this.state = {}
}
shouldComponentUpdate = shouldComponentUpdate(this, 'YoutubePreview')
onPlay = () => {
this.setState({play: true})
}
render() {
const {youTubeId, width, height, dataParams} = this.props
const {play} = this.state
if(!play) {
// mqdefault.jpg (medium quality version, 320px × 180px)
// hqdefault.jpg (high quality version, 480px × 360px
// sddefault.jpg (standard definition version, 640px × 480px)
const thumbnail = width <= 320 ? 'mqdefault.jpg' : width <= 480 ? 'hqdefault.jpg' : 'sddefault.jpg'
const previewLink = `http://img.youtube.com/vi/${youTubeId}/${thumbnail}`
return (
<div className="youtube" onClick={this.onPlay}>
<div className="play"></div>
<img src={previewLink} style={{width, maxWidth: width, height, maxHeight: height}} />
</div>
)
}
const autoPlaySrc = `//www.youtube.com/embed/${youTubeId}?autoplay=1&autohide=1&${dataParams}`
return <iframe width={width} height={height} src={autoPlaySrc} frameBorder="0" allowFullScreen="true"></iframe>
}
}
.youtube {
background-position: center;
background-repeat: no-repeat;
position: relative;
display: inline-block;
overflow: hidden;
transition: all 200ms ease-out;
cursor: pointer;
}
.youtube .play {
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAERklEQVR4nOWbTWhcVRTHb1IJVoxGtNCNdal2JYJReC6GWuO83PM/59yUS3FRFARdFlwYP1CfiojQWt36sRCUurRIdVFXIn41lAoVdRGrG1M01YpKrWjiYmaSl8ybZJL3cd+YA//NLObd3++eO8x79z5jSq5Gw+8kov0AP8vMR5l1BtBZQM4B8ks75wCdZdYZZj5qLZ4hov2Nht9Z9vhKKSIaB/gI4M4w62KeAO6Mte4lYOq20FxrlqqOibhHmeWbvNC9ZfDX1mLae391aN6limO/gwgvAPJbWeAZuSDingdwXTBw7/0IsyaA/Fkh+KqOkD+YNfHej1QKD+y7iVlOhgLvFqFfNJvNGyuBJ+KDAF8MDd0tgS8y64OlgSdJMsysL4cG7SOHkyQZLhTee7+d2R2rAVy/S+Jd7/32ouBHAP4gNNRGQyTHc/84NhqNywZp5rvjjnnvt21aABFeCQ+RLwAf2hQ8s7sv9OCLk6AHNgQvIrvbfzKCD76g/O6cu7lf/iER/aQGgy448pExZmhdegAPhR9sObFWH1gT3lp7DaA/5bkIgJhZPgsNmz02novj+KqeApj1ubwXWe4kdyeznAgNvTpE/HQmvKqOMeuFogTUVQSRno+iaLRLAJF7uIgL9O4ubgL8aWgB7S44mNX+35YpICUiAvS9sBLkq1WzT+NFffl6AuoiApi6NT37h6sWkBIRZGkQ8YtLgyji6e1mBYTqCEBPG2Naz+0BWQgtoGoRgCzEsd9hAN1X5BfnFZASUfrSAFQNsyZ1FJASUVpHiLinDJG8U2cBZYogkrcNs5waBAGdstbeU9zdqpw0gPwwSAI6VUxHyFlDpOcHUUBBIuYNs14aZAE5RVwyzPr3/0EAEY0TyfGNjBWQvwZ +CTSbehfAH29mrID8bET0+0EUkAd8WYDOmqJ3ecsG30yr9wqRfm6Y+a1BEFDEjHfHvWmY9ck6CygHvBVr8Xhtb4ZE5HZA3y8DvBNA1TjnrmXWf+sioMwZX5V/VHXMGGMMoKdDCxCRvRWBdzKzdHEO+EisilbPyopHYqp6S9UCAsz4iojI7hUDAtyXVQgIDd6KnOoaWNkbI6FaPSuZGyMArsi7MZoloB4zviI/Nhr3X95jltwTRQmoIfgisy5ai+me67OI7fE4nrqjrqfK1t0eby0FPRB6oGVlchL3rgnfrq19RKbVBdhV9IOSwJmfmJi4vi/4ThERitwyCxVAFqydshuCX5awhQ9KtmuIWd8IDZED/nXT77rvVVv6sHRKwjYi91poqP7Dr+Y6JJ1VSZIMA3wkPNy6bX+o8Bcm0sXMdwM8Fxo0A3xORPaWBp6uPXsmbxCRD0NDL0dOANhVCXy6iAjMcjbcrMt3RITKwdMVRdFo+y5yvkL4eWZ+zHt/ZVD4dEVRNGotpst+dZZZH8k86lqn2pIvT/eqrNfn2xuyqYPZ8mv7s8pfn/8Pybm4TIjanscAAAAASUVORK5CYII=") no-repeat center center;
background-size: 64px 64px;
position: absolute;
height: 100%;
width: 100%;
opacity: .8;
filter: alpha(opacity=80);
transition: all 0.2s ease-out;
}
.youtube .play:hover {
opacity: 1;
filter: alpha(opacity=100);
}
......@@ -11,16 +11,16 @@ const XMLSerializer = new xmldom.XMLSerializer()
/** Split the HTML on top-level elements. This allows react to compare separately, preventing excessive re-rendering.
* Used in MarkdownViewer.jsx
*/
export function sectionHtml (html) {
const doc = DOMParser.parseFromString(html, 'text/html')
const sections = Array(...doc.childNodes).map(child => XMLSerializer.serializeToString(child))
return sections
}
// export function sectionHtml (html) {
// const doc = DOMParser.parseFromString(html, 'text/html')
// const sections = Array(...doc.childNodes).map(child => XMLSerializer.serializeToString(child))
// return sections
// }
/** Embed videos, link mentions and hashtags, etc...
*/
export default function (html, {large = false, mutate = true}) {
const state = {large, mutate}
export default function (html, {mutate = true} = {}) {
const state = {mutate}
state.hashtags = new Set()
state.usertags = new Set()
state.htmltags = new Set()
......@@ -50,7 +50,7 @@ function traverse(node, state, depth = 0) {
img(state, child)
else if(/a/i.test(child.tagName))
link(state, child)
else if(!embedYouTubeNode(child, state.large, state.links))
else if(!embedYouTubeNode(child, state.links))
linkifyNode(child, state)
traverse(child, state, ++depth)
})
......@@ -143,7 +143,7 @@ function linkify(content, mutate, hashtags, usertags, images, links) {
return content
}
function embedYouTubeNode(child, large, links) {try{
function embedYouTubeNode(child, links) {try{
if(!child.data) return false
const data = child.data
if(/code/i.test(child.parentNode.tagName)) return false
......@@ -152,10 +152,7 @@ function embedYouTubeNode(child, large, links) {try{
const match = url.match(linksRe.youTubeId)
if(match && match.length >= 2) {
const id = match[1]
const src = `//www.youtube.com/embed/${id}?enablejsapi=0&rel=0&origin=https://steemit.com`
const w = large ? 640 : 384,
h = large ? 360 : 240
const v = DOMParser.parseFromString(`<iframe width="${w}" height="${h}" src="${src}" frameBorder="0" allowFullScreen="true"></iframe>`)
const v = DOMParser.parseFromString(`~~~ youtube:${id} ~~~`)
child.parentNode.replaceChild(v, child)
replaced = true
if(links) links.add(url)
......
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