Skip to content
Snippets Groups Projects
Commit 514c789d authored by Quoc Huy Nguyen Dinh's avatar Quoc Huy Nguyen Dinh
Browse files

Closes #126

- completing embedded player TODO
- adding support for reddit
parent d5a12925
No related branches found
No related tags found
2 merge requests!224Develop,!222Resolve "Add Reddit rich embed"
......@@ -13,7 +13,7 @@
"objectSrc": "'none'",
"pluginTypes": "application/pdf",
"scriptSrc": "'unsafe-inline' 'unsafe-eval' data: https: 'self' www.google-analytics.com connect.facebook.net",
"styleSrc": "'self' 'unsafe-inline' fonts.googleapis.com platform.twitter.com",
"styleSrc": "'self' 'unsafe-inline' fonts.googleapis.com platform.twitter.com embed.redditmedia.com",
"reportUri": "/api/v1/csp_violation"
},
"reportOnly": false,
......
......@@ -11,6 +11,7 @@ import * as twitch from 'app/components/elements/EmbeddedPlayers/twitch';
import * as twitter from 'app/components/elements/EmbeddedPlayers/twitter';
import * as vimeo from 'app/components/elements/EmbeddedPlayers/vimeo';
import * as youtube from 'app/components/elements/EmbeddedPlayers/youtube';
import * as reddit from 'app/components/elements/EmbeddedPlayers/reddit';
const supportedProviders = {
archiveorg,
......@@ -25,6 +26,7 @@ const supportedProviders = {
twitter,
vimeo,
youtube,
reddit,
};
export default supportedProviders;
......@@ -64,12 +66,7 @@ function getIframeDimensions(large) {
* @param url
* @returns { boolean | { providerId: string, sandboxAttributes: string[], useSandbox: boolean, validUrl: string }}
*/
export function validateIframeUrl(
url,
large = true,
width = null,
height = null
) {
export function validateIframeUrl(url, large = true, width = null, height = null) {
if (!url) {
return {
validUrl: false,
......@@ -86,14 +83,7 @@ export function validateIframeUrl(
const validUrl = callProviderMethod(provider, 'validateIframeUrl', url);
let iframeDimensions;
iframeDimensions = callProviderMethod(
provider,
'getIframeDimensions',
large,
url,
width,
height
);
iframeDimensions = callProviderMethod(provider, 'getIframeDimensions', large, url, width, height);
if (iframeDimensions === null) {
iframeDimensions = getIframeDimensions(large);
}
......@@ -127,11 +117,7 @@ export function normalizeEmbedUrl(url) {
const providerName = providersKeys[pi];
const provider = supportedProviders[providerName];
const validEmbedUrl = callProviderMethod(
provider,
'normalizeEmbedUrlFn',
url
);
const validEmbedUrl = callProviderMethod(provider, 'normalizeEmbedUrlFn', url);
if (validEmbedUrl === true) {
console.log(`Found a valid ${provider.id} embedded URL`);
......@@ -155,13 +141,7 @@ export function embedNode(child, links, images) {
const providerName = providersKeys[pi];
const provider = supportedProviders[providerName];
const newChild = callProviderMethod(
provider,
'embedNode',
child,
links,
images
);
const newChild = callProviderMethod(provider, 'embedNode', child, links, images);
if (newChild) {
child = newChild;
}
......@@ -205,9 +185,7 @@ function getProviderIds() {
export function generateMd(section, idx, large) {
let markdown = null;
const supportedProvidersIds = getProviderIds();
const regexString = `^([A-Za-z0-9\\?\\=\\_\\-\\/\\.]+) (${supportedProvidersIds.join(
'|'
)})\\s?(.*?) ~~~`;
const regexString = `^([A-Za-z0-9\\?\\=\\_\\-\\/\\.]+) (${supportedProvidersIds.join('|')})\\s?(.*?) ~~~`;
const regex = new RegExp(regexString);
const match = section.match(regex);
......@@ -224,12 +202,7 @@ export function generateMd(section, idx, large) {
const provider = getProviderById(type);
if (provider) {
let iframeDimensions = callProviderMethod(
provider,
'getIframeDimensions',
large,
id
);
let iframeDimensions = callProviderMethod(provider, 'getIframeDimensions', large, id);
if (!iframeDimensions) {
iframeDimensions = getIframeDimensions(large);
}
......@@ -248,9 +221,7 @@ export function generateMd(section, idx, large) {
}
if (match[3]) {
section = section.substring(
`${id} ${type} ${metadataString} ~~~`.length
);
section = section.substring(`${id} ${type} ${metadataString} ~~~`.length);
} else {
section = section.substring(`${id} ${type} ~~~`.length);
}
......@@ -270,8 +241,14 @@ export function generateMd(section, idx, large) {
* @returns {*}
*/
export function preprocessHtml(html) {
// @TODO
// html = preprocess3SpeakHtml(html);
// html = preprocessTwitterHtml(html);
const providersKeys = Object.keys(supportedProviders);
for (let pi = 0; pi < providersKeys.length; pi += 1) {
const providerName = providersKeys[pi];
const provider = supportedProviders[providerName];
const preprocessHtmlFn = _.get(provider, 'preprocessHtml');
if (preprocessHtmlFn) {
html = preprocessHtmlFn(html);
}
}
return html;
}
import React from 'react';
/**
* Regular expressions for detecting and validating provider URLs
* @type {{htmlReplacement: RegExp, main: RegExp, sanitize: RegExp}}
*/
/*
<blockquote class="reddit-card" data-card-created="1614855336"><a href="https://www.reddit.com/r/CryptoCurrency/comments/lxcmup/to_all_the_small_hodlers_keeping_your_coins_at_an/">To all the small hodlers, keeping your coins at an exchange might be the best thing for you</a> from <a href="http://www.reddit.com/r/CryptoCurrency">r/CryptoCurrency</a></blockquote>
<script async src="//embed.redditmedia.com/widgets/platform.js" charset="UTF-8"></script>
*/
const regex = {
main: /(?:https?:\/\/www\.reddit\.com\/r\/((.*?)\/comments\/.*?\/.*(?:(?:\/\w+)+|\/?))$)/i,
sanitize: /(?:https?:\/\/www\.reddit\.com\/r\/((.*?)\/comments\/.*?\/.*(?:(?:\/\w+)+|\/?))$)/i,
htmlReplacement: /<blockquote class="reddit-card" data-card-created="([0-9]*?)"[^>]*?><a href="(https:\/\/www\.reddit\.com\/r\/(.*?))">(.*)?<\/a> from <a href=".*?">r\/(.*?)<\/a><\/blockquote>\n<script.*<\/script>/i,
};
export default regex;
/**
* Configuration for HTML iframe's `sandbox` attribute
* @type {useSandbox: boolean, sandboxAttributes: string[]}
*/
export const sandboxConfig = {
useSandbox: false,
sandboxAttributes: [],
};
/**
* Extract the content ID and other metadata from the URL
* @param data
* @returns {null|{id: *, canonical: string, url: *}}
*/
export function extractMetadataFromEmbedCode(data) {
if (!data) return null;
const match = data.match(regex.htmlReplacement);
if (match) {
const date = match[1];
const url = match[2];
const fullId = match[3];
const description = match[4];
const group = match[5];
const id = fullId;
return {
id,
fullId,
url,
canonical: url,
thumbnail: null,
date,
group,
description,
};
}
return null;
}
/**
* Extract the content ID and other metadata from the URL
* @param data
* @returns {null|{id: *, canonical: string, url: *}}
*/
// https://www.reddit.com/r/CryptoCurrency/comments/lxcmup/to_all_the_small_hodlers_keeping_your_coins_at_an/
export function extractMetadata(data) {
if (!data) return null;
const match = data.match(regex.main);
if (match) {
const url = match[0];
const fullId = match[1];
const group = match[2];
const id = fullId;
return {
id,
fullId,
url,
canonical: null,
thumbnail: null,
group,
};
}
return null;
}
/**
* Check if the iframe code in the post editor is to an allowed URL
* @param url
* @returns {boolean|*}
*/
export function validateIframeUrl(url) {
const match = url.match(regex.sanitize);
if (match) {
return url;
}
return false;
}
/**
* Rewrites the embedded URL to a normalized format
* @param url
* @returns {string|boolean}
*/
export function normalizeEmbedUrl(url) {
const match = url.match(regex.main);
if (match && match.length >= 2) {
const tweetId = match[2].split('?').shift();
return `https://www.reddit.com/${match[1]}/status/${tweetId}`;
}
return false;
}
function generateRedditCode(metadata) {
let redditCode = '';
if (metadata) {
let [date, group, url, description] = Buffer.from(metadata, 'base64')
.toString()
.split('|');
// Sanitizing input
date = date.replace(/[^0-9]/gi, '');
group = group.replace(/(<([^>]+)>)/gi, '');
url = url.replace(/(<([^>]+)>)/gi, '');
description = description.replace(/(<([^>]+)>)/gi, '');
if (description === '') {
description = url;
}
redditCode =
`<blockquote class="reddit-card" data-created="${date}">` +
`<a href="${url}">${description}</a>` +
`from <a href="http://www.reddit.com/r/${group}">r/${group}</a></blockquote>`;
}
return {
__html: redditCode,
};
}
/**
* Generates the Markdown/HTML code to override the detected URL with an iFrame
* @param idx
* @param redditId
* @param w
* @param h
* @returns {*}
*/
export function genIframeMd(idx, redditId, w, h, metadata) {
if (typeof window !== 'undefined') {
return (
<div
key={`reddit-${redditId}-${idx}`}
className="tweetWrapper"
dangerouslySetInnerHTML={generateRedditCode(metadata)}
/>
);
}
return null;
}
/**
* Replaces the URL with a custom Markdown for embedded players
* @param child
* @param links
* @returns {*}
*/
export function embedNode(child) {
try {
const { data } = child;
const reddit = extractMetadata(data);
if (reddit) {
const metadata = `|${reddit.group}|${reddit.url}|`;
child.data = data.replace(
regex.main,
`~~~ embed:${reddit.id} reddit metadata:${Buffer.from(metadata).toString('base64')} ~~~`
);
}
} catch (error) {
console.log(error);
}
return child;
}
/**
* Pre-process HTML codes from the Markdown before it gets transformed
* @param child
* @returns {string}
*/
export function preprocessHtml(child) {
try {
if (typeof child === 'string') {
const reddit = extractMetadataFromEmbedCode(child);
if (reddit) {
const metadata = `${reddit.date}|${reddit.group}|${reddit.url}|${reddit.description}`;
child = child.replace(
regex.htmlReplacement,
`~~~ embed:${reddit.id} reddit metadata:${Buffer.from(metadata).toString('base64')} ~~~`
);
}
}
} catch (error) {
console.log(error);
}
return child;
}
This diff is collapsed.
......@@ -20,40 +20,17 @@ export default function ServerHTML({
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{meta &&
meta.map(m => {
if (m.title) {
page_title = m.title;
return null;
}
if (m.canonical)
return (
<link
key="canonical"
rel="canonical"
href={m.canonical}
/>
);
if (m.name && m.content)
return (
<meta
key={m.name}
name={m.name}
content={m.content}
/>
);
if (m.canonical) return <link key="canonical" rel="canonical" href={m.canonical} />;
if (m.name && m.content) return <meta key={m.name} name={m.name} content={m.content} />;
if (m.property && m.content)
return (
<meta
key={m.property}
property={m.property}
content={m.content}
/>
);
return <meta key={m.property} property={m.property} content={m.content} />;
return null;
})}
<link rel="manifest" href="/static/manifest.json" />
......@@ -106,58 +83,18 @@ export default function ServerHTML({
href="/images/favicons/apple-touch-icon-152x152.png"
type="image/png"
/>
<link
rel="icon"
type="image/png"
href="/images/favicons/favicon-196x196.png"
sizes="196x196"
/>
<link
rel="icon"
type="image/png"
href="/images/favicons/favicon-96x96.png"
sizes="96x96"
/>
<link
rel="icon"
type="image/png"
href="/images/favicons/favicon-32x32.png"
sizes="32x32"
/>
<link
rel="icon"
type="image/png"
href="/images/favicons/favicon-16x16.png"
sizes="16x16"
/>
<link
rel="icon"
type="image/png"
href="/images/favicons/favicon-128.png"
sizes="128x128"
/>
<link rel="icon" type="image/png" href="/images/favicons/favicon-196x196.png" sizes="196x196" />
<link rel="icon" type="image/png" href="/images/favicons/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/png" href="/images/favicons/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="/images/favicons/favicon-16x16.png" sizes="16x16" />
<link rel="icon" type="image/png" href="/images/favicons/favicon-128.png" sizes="128x128" />
<meta name="application-name" content="Hive" />
<meta name="msapplication-TileColor" content="#FFFFFF" />
<meta
name="msapplication-TileImage"
content="/images/favicons/mstile-144x144.png"
/>
<meta
name="msapplication-square70x70logo"
content="/images/favicons/mstile-70x70.png"
/>
<meta
name="msapplication-square150x150logo"
content="/images/favicons/mstile-150x150.png"
/>
<meta
name="msapplication-wide310x150logo"
content="/images/favicons/mstile-310x150.png"
/>
<meta
name="msapplication-square310x310logo"
content="/images/favicons/mstile-310x310.png"
/>
<meta name="msapplication-TileImage" content="/images/favicons/mstile-144x144.png" />
<meta name="msapplication-square70x70logo" content="/images/favicons/mstile-70x70.png" />
<meta name="msapplication-square150x150logo" content="/images/favicons/mstile-150x150.png" />
<meta name="msapplication-wide310x150logo" content="/images/favicons/mstile-310x150.png" />
<meta name="msapplication-square310x310logo" content="/images/favicons/mstile-310x310.png" />
<link
href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600"
rel="stylesheet"
......@@ -168,14 +105,7 @@ export default function ServerHTML({
rel="stylesheet"
type="text/css"
/>
{assets.style.map((href, idx) => (
<link
href={href}
key={idx}
rel="stylesheet"
type="text/css"
/>
))}
{assets.style.map((href, idx) => <link href={href} key={idx} rel="stylesheet" type="text/css" />)}
{gptEnabled ? (
<script
dangerouslySetInnerHTML={{
......@@ -193,10 +123,7 @@ export default function ServerHTML({
/>
) : null}
{gptEnabled ? (
<script
src="//m.servedby-buysellads.com/monetization.js"
type="text/javascript"
/>
<script src="//m.servedby-buysellads.com/monetization.js" type="text/javascript" />
) : null}
{shouldSeeCookieConsent ? (
<script
......@@ -229,18 +156,12 @@ export default function ServerHTML({
`,
}}
/>
<script async src="https://embed.redditmedia.com/widgets/platform.js" charSet="UTF-8" />
<title>{page_title}</title>
</head>
<body>
{
<div
id="content"
dangerouslySetInnerHTML={{ __html: body }}
/>
}
{assets.script.map((href, idx) => (
<script key={idx} src={href} />
))}
{<div id="content" dangerouslySetInnerHTML={{ __html: body }} />}
{assets.script.map((href, idx) => <script key={idx} src={href} />)}
{/* gptEnabled ? (
<script
dangerouslySetInnerHTML={{
......
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