Commit ba49866b authored by Quoc Huy Nguyen Dinh's avatar Quoc Huy Nguyen Dinh
Browse files

- Mixcloud embed player

- ability to customize iframe dimensions per embed player provider
parent 0a4d7606
......@@ -143,14 +143,11 @@
div.videoWrapper {
width: 100%;
height: 0;
padding-bottom: 56.2%;
height: auto;
position: relative;
iframe {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
}
......
......@@ -27,6 +27,15 @@ import {
sandboxConfig as sandboxConfigSpotify,
} from 'app/components/elements/EmbeddedPlayers/spotify';
import {
validateIframeUrl as validateMixcloudIframeUrl,
genIframeMd as genMixcloudIframeMd,
normalizeEmbedUrl as normalizeMixcloudEmbedUrl,
embedNode as embedMixcloudNode,
getIframeDimensions as getMixcloudIframeDimensions,
sandboxConfig as sandboxConfigMixcloud,
} from 'app/components/elements/EmbeddedPlayers/mixcloud';
import {
genIframeMd as genYoutubeIframeMd,
validateIframeUrl as validateYoutubeIframeUrl,
......@@ -83,6 +92,7 @@ const supportedProviders = [
normalizeEmbedUrlFn: normalizeDtubeEmbedUrl,
embedNodeFn: embedDtubeNode,
genIframeMdFn: genDtubeIframeMd,
getIframeDimensionsFn: null,
...sandboxConfigDtube,
},
{
......@@ -91,6 +101,7 @@ const supportedProviders = [
normalizeEmbedUrlFn: normalizeTwitchEmbedUrl,
embedNodeFn: embedTwitchNode,
genIframeMdFn: genTwitchIframeMd,
getIframeDimensionsFn: null,
...sandboxConfigTwitch,
},
{
......@@ -99,6 +110,7 @@ const supportedProviders = [
normalizeEmbedUrlFn: null,
embedNodeFn: null,
genIframeMdFn: null,
getIframeDimensionsFn: null,
...sandboxConfigSoundcloud,
},
{
......@@ -107,14 +119,25 @@ const supportedProviders = [
normalizeEmbedUrlFn: normalizeSpotifyEmbedUrl,
embedNodeFn: embedSpotifyNode,
genIframeMdFn: genSpotifyIframeMd,
getIframeDimensionsFn: null,
...sandboxConfigSpotify,
},
{
id: 'mixcloud',
validateIframeUrlFn: validateMixcloudIframeUrl,
normalizeEmbedUrlFn: normalizeMixcloudEmbedUrl,
embedNodeFn: embedMixcloudNode,
genIframeMdFn: genMixcloudIframeMd,
getIframeDimensionsFn: getMixcloudIframeDimensions,
...sandboxConfigMixcloud,
},
{
id: 'youtube',
validateIframeUrlFn: validateYoutubeIframeUrl,
normalizeEmbedUrlFn: normalizeYoutubeEmbedUrl,
embedNodeFn: embedYoutubeNode,
genIframeMdFn: genYoutubeIframeMd,
getIframeDimensionsFn: null,
...sandboxConfigYoutube,
},
{
......@@ -123,6 +146,7 @@ const supportedProviders = [
normalizeEmbedUrlFn: normalizeVimeoEmbedUrl,
embedNodeFn: embedVimeoNode,
genIframeMdFn: genVimeoIframeMd,
getIframeDimensionsFn: null,
...sandboxConfigVimeo,
},
{
......@@ -131,6 +155,7 @@ const supportedProviders = [
normalizeEmbedUrlFn: normalizeThreespeakEmbedUrl,
embedNodeFn: embedThreeSpeakNode,
genIframeMdFn: genThreespeakIframeMd,
getIframeDimensionsFn: null,
...sandboxConfigThreespeak,
},
{
......@@ -139,6 +164,7 @@ const supportedProviders = [
normalizeEmbedUrlFn: normalizeTwitterEmbedUrl,
embedNodeFn: embedTwitterNode,
genIframeMdFn: genTwitterIframeMd,
getIframeDimensionsFn: null,
...sandboxConfigTwitter,
},
{
......@@ -147,29 +173,46 @@ const supportedProviders = [
normalizeEmbedUrlFn: null,
embedNodeFn: null,
genIframeMdFn: null,
getIframeDimensionsFn: null,
...sandboxConfigDapplr,
},
];
export default supportedProviders;
function getIframeDimensions(large) {
return {
width: large ? '640' : '480',
height: large ? '360' : '270',
};
}
/**
* Allow iFrame in the Markdown if the source URL is allowed
* @param url
* @returns { boolean | { providerId: string, sandboxAttributes: string[], useSandbox: boolean, validUrl: string }}
*/
export function validateIframeUrl(url) {
export function validateIframeUrl(url, large = true) {
for (let pi = 0; pi < supportedProviders.length; pi += 1) {
const provider = supportedProviders[pi];
const validUrl = provider.validateIframeUrlFn(url);
let iframeDimensions;
if (provider.getIframeDimensionsFn) {
iframeDimensions = provider.getIframeDimensionsFn(large);
} else {
iframeDimensions = getIframeDimensions(large);
}
if (validUrl !== false) {
console.log(`Found a valid ${provider.id} iframe URL`);
return {
providerId: provider.id,
sandboxAttributes: provider.sandboxAttributes || [],
useSandbox: provider.useSandbox,
width: iframeDimensions.width,
height: iframeDimensions.height,
validUrl,
};
}
......@@ -273,12 +316,22 @@ export function generateMd(section, idx, large) {
metadata = metadataString.substring(9);
}
const w = large ? 640 : 480,
h = large ? 360 : 270;
const provider = getProviderById(type);
if (provider) {
markdown = provider.genIframeMdFn(idx, id, w, h, metadata);
let iframeDimensions;
if (provider.getIframeDimensionsFn) {
iframeDimensions = provider.getIframeDimensionsFn(large);
} else {
iframeDimensions = getIframeDimensions(large);
}
markdown = provider.genIframeMdFn(
idx,
id,
iframeDimensions.width,
iframeDimensions.height,
metadata
);
} else {
console.error('MarkdownViewer unknown embed type', type);
}
......
import React from 'react';
/**
* Regular expressions for detecting and validating provider URLs
* @type {{htmlReplacement: RegExp, main: RegExp, sanitize: RegExp}}
*/
const regex = {
main: /(?:https?:\/\/(?:(?:www\.mixcloud.com(\/(.*?)\/(.*?)\/))))/i,
sanitize: /^https:\/\/www\.mixcloud\.com\/widget\/iframe\/.*?feed=(.*)/i,
};
export default regex;
export function getIframeDimensions() {
return {
width: '100%',
height: '120',
};
}
/**
* Configuration for HTML iframe's `sandbox` attribute
* @type {useSandbox: boolean, sandboxAttributes: string[]}
*/
export const sandboxConfig = {
useSandbox: true,
sandboxAttributes: ['allow-scripts', 'allow-same-origin', 'allow-popups'],
};
/**
* Check if the iframe code in the post editor is to an allowed URL
* <iframe width="100%" height="120" src="https://www.mixcloud.com/widget/iframe/?hide_cover=1&feed=%2FMagneticMagazine%2Fambient-meditations-vol-21-anane%2F" frameborder="0" ></iframe>
* @param url
* @returns {boolean|*}
*/
export function validateIframeUrl(url) {
const match = url.match(regex.sanitize);
if (!match || match.length !== 2) {
return false;
}
return `https://www.mixcloud.com/widget/iframe/?hide_cover=1&feed=${
match[1]
}`;
}
//////
/**
* Rewrites the embedded URL to a normalized format
* @param url
* @returns {string|boolean}
*/
export function normalizeEmbedUrl(url) {
const match = url.match(regex.contentId);
if (match && match.length >= 2) {
return `https://www.mixcloud.com/widget/iframe/?feed=${match[1]}`;
}
return false;
}
/**
* Extract the content ID and other metadata from the URL
* @param data
* @returns {null|{id: *, canonical: string, url: *}}
*/
function extractMetadata(data) {
console.log('data', data);
if (!data) return null;
const m = data.match(regex.main);
if (!m || m.length < 2) return null;
const startTime = m.input.match(/t=(\d+)s?/);
return {
id: m[1],
url: m[0],
startTime: startTime ? startTime[1] : 0,
canonical: `https://open.mixcloud.com/playlist/${m[1]}`,
// thumbnail: requires a callback - http://stackoverflow.com/questions/1361149/get-img-thumbnails-from-mixcloud
};
}
/**
* Replaces the URL with a custom Markdown for embedded players
* @param child
* @param links
* @returns {*}
*/
export function embedNode(child, links /*images*/) {
try {
const { data } = child;
const mixcloud = extractMetadata(data);
console.log('mixcloud', mixcloud);
if (!mixcloud) return child;
child.data = data.replace(
mixcloud.url,
`~~~ embed:${mixcloud.id} mixcloud ~~~`
);
if (links) links.add(mixcloud.canonical);
// if(images) images.add(mixcloud.thumbnail) // not available
} catch (error) {
console.log(error);
}
return child;
}
/**
* Generates the Markdown/HTML code to override the detected URL with an iFrame
* @param idx
* @param id
* @param width
* @param height
* @returns {*}
*/
export function genIframeMd(idx, id) {
const width = '100%';
const height = 120;
const url = `https://www.mixcloud.com/widget/iframe/?hide_cover=1&feed=${
id
}`;
let sandbox = sandboxConfig.useSandbox;
if (sandbox) {
if (
Object.prototype.hasOwnProperty.call(
sandboxConfig,
'sandboxAttributes'
)
) {
sandbox = sandboxConfig.sandboxAttributes.join(' ');
}
}
const iframeProps = {
src: url,
width,
height,
frameBorder: '0',
webkitallowfullscreen: 'webkitallowfullscreen',
mozallowfullscreen: 'mozallowfullscreen',
allowFullScreen: 'allowFullScreen',
};
if (sandbox) {
iframeProps.sandbox = sandbox;
}
return (
<div key={`mixcloud-${id}-${idx}`} className="videoWrapper">
<iframe
title="mixcloud embedded player"
// eslint-disable-next-line react/jsx-props-no-spreading
{...iframeProps}
/>
</div>
);
}
......@@ -59,7 +59,6 @@ export function normalizeEmbedUrl(url) {
* @returns {null|{id: *, canonical: string, url: *}}
*/
function extractMetadata(data) {
console.log('data', data);
if (!data) return null;
const m = data.match(regex.main);
if (!m || m.length < 2) return null;
......@@ -85,7 +84,6 @@ export function embedNode(child, links /*images*/) {
try {
const { data } = child;
const spotify = extractMetadata(data);
console.log('spotify', spotify);
if (!spotify) return child;
child.data = data.replace(
......
......@@ -6,6 +6,7 @@ import youtubeRegex from 'app/components/elements/EmbeddedPlayers/youtube';
import threespeakRegex from 'app/components/elements/EmbeddedPlayers/threespeak';
import twitterRegex from 'app/components/elements/EmbeddedPlayers/twitter';
import spotifyRegex from 'app/components/elements/EmbeddedPlayers/spotify';
import mixcloudRegex from 'app/components/elements/EmbeddedPlayers/mixcloud';
describe('Links', () => {
it('all', () => {
......@@ -343,6 +344,16 @@ describe('Performance', () => {
'https://open.spotify.com/embed/playlist/37i9dQZF1DWSDCcNkUu5tr'
);
});
it('mixcloud', () => {
match(
mixcloudRegex.main,
'https://www.mixcloud.com/MagneticMagazine/ambient-meditations-vol-21-anane/'
);
match(
mixcloudRegex.sanitize,
'https://www.mixcloud.com/widget/iframe/?hide_cover=1&feed=%2FMagneticMagazine%2Fambient-meditations-vol-21-anane%2F'
);
});
});
const match = (...args) => compare(true, ...args);
......
......@@ -63,7 +63,9 @@ export default ({
validUrl,
useSandbox,
sandboxAttributes,
} = validateEmbbeddedPlayerIframeUrl(srcAtty);
width,
height,
} = validateEmbbeddedPlayerIframeUrl(srcAtty, large);
if (validUrl !== false) {
const iframe = {
......@@ -74,8 +76,8 @@ export default ({
webkitallowfullscreen: 'webkitallowfullscreen', // deprecated but required for vimeo : https://vimeo.com/forums/help/topic:278181
mozallowfullscreen: 'mozallowfullscreen', // deprecated but required for vimeo
src: validUrl,
width: large ? '640' : '480',
height: large ? '360' : '270',
width,
height,
},
};
if (useSandbox) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment