react-oembed-container
Advanced tools
Comparing version 0.1.0 to 0.2.0
@@ -1,2 +0,2 @@ | ||
"use strict"; | ||
'use strict'; | ||
@@ -66,5 +66,18 @@ Object.defineProperty(exports, "__esModule", { | ||
*/ | ||
var getScriptTags = exports.getScriptTags = function getScriptTags(string) { | ||
var getScripts = exports.getScripts = function getScripts(string) { | ||
var scripts = string.match(/<script[\s\S]*?<\/script>/gi); | ||
return scripts ? uniqueURIs(scripts.map(extractScriptURL)) : []; | ||
}; | ||
/** | ||
* Create & inject a new <script> tag into the page. | ||
* | ||
* @param {String} src A script URL. | ||
* @returns {HTMLElement} The injected script tag. | ||
*/ | ||
var injectScriptTag = exports.injectScriptTag = function injectScriptTag(src) { | ||
var scriptTag = document.createElement('script'); | ||
scriptTag.src = src; | ||
document.head.appendChild(scriptTag); | ||
return scriptTag; | ||
}; |
@@ -19,2 +19,4 @@ 'use strict'; | ||
var _embeds = require('./embeds'); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -28,41 +30,2 @@ | ||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } | ||
// Dictionary of overrides to customize the handling of certain scripts; | ||
// most scripts are designed to execute onload, but the better-documented | ||
// oEmbed scripts (such as Facebook's) can be manually re-initialized in | ||
// a predictable manner. | ||
var embeds = { | ||
'connect.facebook.net': { | ||
isLoaded: function isLoaded() { | ||
// Ensure FB root element | ||
if (!document.querySelector('body > #fb-root')) { | ||
// There may be multiple #fb-root elements in a post's content: remove them | ||
// all in favor of a new body-level div. | ||
[].concat(_toConsumableArray(document.querySelectorAll('#fb-root'))).forEach(function (n) { | ||
return n.remove(); | ||
}); | ||
// Prepare and create the fb-root element. We only need one. | ||
var fbDiv = document.createElement('div'); | ||
fbDiv.id = 'fb-root'; | ||
document.body.prepend(fbDiv); | ||
} | ||
// Now the root element the script requires exists, check for the script iself. | ||
return window.FB !== undefined; | ||
}, | ||
reload: function reload(container) { | ||
return window.FB.XFBML.parse(container); | ||
} | ||
} | ||
}; | ||
var getEmbedConfiguration = function getEmbedConfiguration(src) { | ||
return Object.keys(embeds).reduce(function (matchingEmbed, key) { | ||
return matchingEmbed || src.indexOf(key) > -1 && embeds[key] || null; | ||
}, null); | ||
}; | ||
var EmbedContainer = function (_Component) { | ||
@@ -84,3 +47,3 @@ _inherits(EmbedContainer, _Component); | ||
this.scripts = (0, _helpers.getScriptTags)(markup).map(function (src) { | ||
this.scripts = (0, _helpers.getScripts)(markup).map(function (src) { | ||
return _this2.injectScript(src); | ||
@@ -107,10 +70,7 @@ }).filter(Boolean); | ||
var embed = getEmbedConfiguration(src); | ||
var embed = (0, _embeds.getEmbedConfiguration)(src); | ||
if (embed && embed.isLoaded()) { | ||
embed.reload(container); | ||
} else { | ||
var scriptTag = document.createElement('script'); | ||
scriptTag.src = src; | ||
document.head.appendChild(scriptTag); | ||
return scriptTag; | ||
return (0, _helpers.injectScriptTag)(src); | ||
} | ||
@@ -126,2 +86,3 @@ return null; | ||
children = _props.children, | ||
className = _props.className, | ||
markup = _props.markup; | ||
@@ -132,5 +93,8 @@ | ||
'div', | ||
{ ref: function ref(node) { | ||
{ | ||
className: className, | ||
ref: function ref(node) { | ||
_this3.container = node; | ||
} }, | ||
} | ||
}, | ||
children | ||
@@ -144,7 +108,12 @@ ); | ||
EmbedContainer.defaultProps = { | ||
className: null | ||
}; | ||
EmbedContainer.propTypes = { | ||
markup: _propTypes2.default.string.isRequired, | ||
children: _propTypes2.default.oneOfType([_propTypes2.default.node, _propTypes2.default.arrayOf(_propTypes2.default.node)]).isRequired | ||
children: _propTypes2.default.oneOfType([_propTypes2.default.node, _propTypes2.default.arrayOf(_propTypes2.default.node)]).isRequired, | ||
className: _propTypes2.default.string, | ||
markup: _propTypes2.default.string.isRequired | ||
}; | ||
exports.default = EmbedContainer; |
{ | ||
"name": "react-oembed-container", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "React container for locating and injecting oembed scripts in string content.", | ||
@@ -12,3 +12,4 @@ "main": "dist/index.js", | ||
"build": "babel src -d dist", | ||
"release": "npm run lint && npm run build && npm publish" | ||
"prerelease": "npm run lint && npm run test", | ||
"release": "npm run build && npm publish" | ||
}, | ||
@@ -32,2 +33,5 @@ "repository": { | ||
"homepage": "https://github.com/humanmade/react-oembed-container#readme", | ||
"jest": { | ||
"setupTestFrameworkScriptFile": "<rootDir>/test/setup.js" | ||
}, | ||
"peerDependencies": { | ||
@@ -51,2 +55,4 @@ "react": "^16.2.0", | ||
"babel-preset-env": "^1.6.1", | ||
"enzyme": "^3.3.0", | ||
"enzyme-adapter-react-16": "^1.1.1", | ||
"eslint": "^4.17.0", | ||
@@ -53,0 +59,0 @@ "eslint-config-airbnb": "^16.1.0", |
@@ -5,2 +5,4 @@ # oEmbedContainer | ||
[![Build Status](https://travis-ci.org/humanmade/react-oembed-container.svg?branch=master)](https://travis-ci.org/humanmade/react-oembed-container) | ||
## Background | ||
@@ -47,2 +49,14 @@ | ||
## Installation | ||
``` | ||
npm install --save react-oembed-container | ||
``` | ||
This library has peerdependencies on `react-dom` and `react` v16. If you do not already have these in your project, run | ||
``` | ||
npm install --save react react-dom | ||
``` | ||
## Usage | ||
@@ -53,7 +67,7 @@ | ||
```js | ||
import SocialEmbedContainer from 'react-oembed-container'; | ||
import EmbedContainer from 'react-oembed-container'; | ||
``` | ||
(or another name such as `OEmbedContainer` if you prefer; this README choses to sidestep the irreconcilable conflict between oEmbed's use of a lowercase "o" and React components' traditionally uppercase identifiers.) | ||
Then use this container to wrap whatever JSX you would normally use to render the content: | ||
```js | ||
@@ -63,3 +77,3 @@ render() { | ||
return ( | ||
<SocialEmbedContainer markup={post.content.rendered}> | ||
<EmbedContainer markup={post.content.rendered}> | ||
@@ -72,3 +86,3 @@ {/* for example, */} | ||
</SocialEmbedContainer> | ||
</EmbedContainer> | ||
); | ||
@@ -78,2 +92,22 @@ } | ||
If you set a `className` on the `EmbedContainer` component, that class will be passed through to the rendered `<div>` container: | ||
```js | ||
render() { | ||
const { post } = this.props; | ||
return ( | ||
<EmbedContainer | ||
className="article-content" | ||
markup={post.content.rendered} | ||
> | ||
<p>Article text here</p> | ||
</EmbedContainer> | ||
); | ||
} | ||
``` | ||
yields | ||
```html | ||
<div class="article-content"><p>Article text here</p></div> | ||
``` | ||
## Local Development | ||
@@ -80,0 +114,0 @@ |
@@ -54,5 +54,18 @@ export const ANY_SCRIPT = /<script[\s\S]*?>[\s\S]*?<\/script>/gi; | ||
*/ | ||
export const getScriptTags = (string) => { | ||
export const getScripts = (string) => { | ||
const scripts = string.match(/<script[\s\S]*?<\/script>/gi); | ||
return scripts ? uniqueURIs(scripts.map(extractScriptURL)) : []; | ||
}; | ||
/** | ||
* Create & inject a new <script> tag into the page. | ||
* | ||
* @param {String} src A script URL. | ||
* @returns {HTMLElement} The injected script tag. | ||
*/ | ||
export const injectScriptTag = (src) => { | ||
const scriptTag = document.createElement('script'); | ||
scriptTag.src = src; | ||
document.head.appendChild(scriptTag); | ||
return scriptTag; | ||
}; |
import React, { Component } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { getScriptTags } from './helpers'; | ||
import { getScripts, injectScriptTag } from './helpers'; | ||
import { getEmbedConfiguration } from './embeds'; | ||
// Dictionary of overrides to customize the handling of certain scripts; | ||
// most scripts are designed to execute onload, but the better-documented | ||
// oEmbed scripts (such as Facebook's) can be manually re-initialized in | ||
// a predictable manner. | ||
const embeds = { | ||
'connect.facebook.net': { | ||
isLoaded() { | ||
// Ensure FB root element | ||
if (!document.querySelector('body > #fb-root')) { | ||
// There may be multiple #fb-root elements in a post's content: remove them | ||
// all in favor of a new body-level div. | ||
[...document.querySelectorAll('#fb-root')].forEach(n => n.remove()); | ||
// Prepare and create the fb-root element. We only need one. | ||
const fbDiv = document.createElement('div'); | ||
fbDiv.id = 'fb-root'; | ||
document.body.prepend(fbDiv); | ||
} | ||
// Now the root element the script requires exists, check for the script iself. | ||
return window.FB !== undefined; | ||
}, | ||
reload: container => window.FB.XFBML.parse(container), | ||
}, | ||
}; | ||
const getEmbedConfiguration = src => Object.keys(embeds) | ||
.reduce((matchingEmbed, key) => ( | ||
matchingEmbed || (src.indexOf(key) > -1 && embeds[key]) || null | ||
), null); | ||
class EmbedContainer extends Component { | ||
componentDidMount() { | ||
const { markup } = this.props; | ||
this.scripts = getScriptTags(markup) | ||
this.scripts = getScripts(markup) | ||
.map(src => this.injectScript(src)) | ||
@@ -61,6 +31,3 @@ .filter(Boolean); | ||
} else { | ||
const scriptTag = document.createElement('script'); | ||
scriptTag.src = src; | ||
document.head.appendChild(scriptTag); | ||
return scriptTag; | ||
return injectScriptTag(src); | ||
} | ||
@@ -71,6 +38,13 @@ return null; | ||
render() { | ||
const { children, markup } = this.props; | ||
const { | ||
children, | ||
className, | ||
markup, | ||
} = this.props; | ||
markup.split(' ').join(' '); | ||
return ( | ||
<div ref={(node) => { this.container = node; }}> | ||
<div | ||
className={className} | ||
ref={(node) => { this.container = node; }} | ||
> | ||
{children} | ||
@@ -82,4 +56,7 @@ </div> | ||
EmbedContainer.defaultProps = { | ||
className: null, | ||
}; | ||
EmbedContainer.propTypes = { | ||
markup: PropTypes.string.isRequired, | ||
children: PropTypes.oneOfType([ | ||
@@ -89,4 +66,6 @@ PropTypes.node, | ||
]).isRequired, | ||
className: PropTypes.string, | ||
markup: PropTypes.string.isRequired, | ||
}; | ||
export default EmbedContainer; |
@@ -7,3 +7,3 @@ import * as fixtures from './fixtures'; | ||
INJECTED_SCRIPT, | ||
getScriptTags, | ||
getScripts, | ||
} from '../src/helpers'; | ||
@@ -84,5 +84,5 @@ | ||
describe('script tag locator', () => { | ||
describe('getScripts', () => { | ||
it('should identify script tags in content', () => { | ||
const result = getScriptTags(fixtures.twitter); | ||
const result = getScripts(fixtures.twitter); | ||
expect(Array.isArray(result)).toBe(true); | ||
@@ -94,3 +94,3 @@ expect(result.length).toBe(1); | ||
it('should de-dupe repeated script tags', () => { | ||
const result = getScriptTags(fixtures.facebook); | ||
const result = getScripts(fixtures.facebook); | ||
expect(Array.isArray(result)).toBe(true); | ||
@@ -102,3 +102,3 @@ expect(result.length).toBe(1); | ||
it('should be able to match multiple different script tags', () => { | ||
const result = getScriptTags(fixtures.all); | ||
const result = getScripts(fixtures.all); | ||
expect(Array.isArray(result)).toBe(true); | ||
@@ -105,0 +105,0 @@ expect(result).toEqual([ |
56076
17
795
112
22