281 unfurl show links #303

Merged
bones7242 merged 12 commits from 281-unfurl-show-links into master 2017-12-15 17:01:23 +01:00
14 changed files with 70 additions and 55 deletions
Showing only changes of commit df53760aea - Show all commits

View file

@ -22,4 +22,14 @@ module.exports = {
files: { files: {
uploadDirectory: null, // enter file path to where uploads/publishes should be stored uploadDirectory: null, // enter file path to where uploads/publishes should be stored
}, },
site: {
name : 'Spee.ch',
host : 'https://spee.ch',
description: 'Decentralized video and content hosting.',
},
publishing: {
defaultTitle : 'Spee.h',
defaultThumbnail : 'https://spee.ch/assets/img/video_thumb_default.png',
defaultDescription: 'Open-source, decentralized image and video sharing.',
},
}; };

View file

@ -21,7 +21,7 @@ module.exports = {
} else { } else {
message = error.response; message = error.response;
} }
// check for spee.ch thrown errors // check for thrown errors
} else if (error.message) { } else if (error.message) {
status = 400; status = 400;
message = error.message; message = error.message;

View file

@ -1,13 +1,13 @@
const Handlebars = require('handlebars'); const Handlebars = require('handlebars');
const config = require('../config/speechConfig.js'); const { site, analytics } = require('../config/speechConfig.js');
module.exports = { module.exports = {
placeCommonHeaderTags () { placeCommonHeaderTags () {
const headerBoilerplate = `<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Spee.ch</title><link rel="stylesheet" href="/assets/css/reset.css" type="text/css"><link rel="stylesheet" href="/assets/css/general.css" type="text/css"><link rel="stylesheet" href="/assets/css/mediaQueries.css" type="text/css">`; const headerBoilerplate = `<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>${site.title}</title><link rel="stylesheet" href="/assets/css/reset.css" type="text/css"><link rel="stylesheet" href="/assets/css/general.css" type="text/css"><link rel="stylesheet" href="/assets/css/mediaQueries.css" type="text/css">`;
return new Handlebars.SafeString(headerBoilerplate); return new Handlebars.SafeString(headerBoilerplate);
}, },
googleAnalytics () { googleAnalytics () {
const googleApiKey = config.analytics.googleId; const googleApiKey = analytics.googleId;
const gaCode = `<script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ const gaCode = `<script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
@ -19,7 +19,7 @@ module.exports = {
addOpenGraph ({ ogTitle, contentType, ogDescription, thumbnail, showUrl, source, ogThumbnailContentType }) { addOpenGraph ({ ogTitle, contentType, ogDescription, thumbnail, showUrl, source, ogThumbnailContentType }) {
const ogTitleTag = `<meta property="og:title" content="${ogTitle}" />`; const ogTitleTag = `<meta property="og:title" content="${ogTitle}" />`;
const ogUrlTag = `<meta property="og:url" content="${showUrl}" />`; const ogUrlTag = `<meta property="og:url" content="${showUrl}" />`;
const ogSiteNameTag = `<meta property="og:site_name" content="Spee.ch" />`; const ogSiteNameTag = `<meta property="og:site_name" content="${site.title}" />`;
const ogDescriptionTag = `<meta property="og:description" content="${ogDescription}" />`; const ogDescriptionTag = `<meta property="og:description" content="${ogDescription}" />`;
const ogImageWidthTag = '<meta property="og:image:width" content="600" />'; const ogImageWidthTag = '<meta property="og:image:width" content="600" />';
const ogImageHeightTag = '<meta property="og:image:height" content="315" />'; const ogImageHeightTag = '<meta property="og:image:height" content="315" />';

View file

@ -1,7 +1,7 @@
const logger = require('winston'); const logger = require('winston');
const fs = require('fs'); const fs = require('fs');
const db = require('../models'); const db = require('../models');
const config = require('../config/speechConfig.js'); const { site, wallet } = require('../config/speechConfig.js');
module.exports = { module.exports = {
validateApiPublishRequest (body, files) { validateApiPublishRequest (body, files) {
@ -72,7 +72,7 @@ module.exports = {
}, },
validateLicense (license) { validateLicense (license) {
if ((license.indexOf('Public Domain') === -1) && (license.indexOf('Creative Commons') === -1)) { if ((license.indexOf('Public Domain') === -1) && (license.indexOf('Creative Commons') === -1)) {
throw new Error('Only posts with a "Public Domain" or "Creative Commons" license are eligible for publishing through spee.ch'); throw new Error('Only posts with a "Public Domain" or "Creative Commons" license are eligible for publishing.');
} }
}, },
cleanseChannelName (channelName) { cleanseChannelName (channelName) {
@ -105,12 +105,12 @@ module.exports = {
metadata : { metadata : {
description, description,
title, title,
author : 'spee.ch', author : site.title,
language: 'en', language: 'en',
license, license,
nsfw, nsfw,
}, },
claim_address: config.wallet.lbryClaimAddress, claim_address: wallet.lbryClaimAddress,
}; };
// add thumbnail to channel if video // add thumbnail to channel if video
if (thumbnail !== null) { if (thumbnail !== null) {
@ -137,12 +137,12 @@ module.exports = {
db.File.findAll({ where: { name } }) db.File.findAll({ where: { name } })
.then(result => { .then(result => {
if (result.length >= 1) { if (result.length >= 1) {
const claimAddress = config.wallet.lbryClaimAddress; const claimAddress = wallet.lbryClaimAddress;
// filter out any results that were not published from spee.ch's wallet address // filter out any results that were not published from the site's wallet address
const filteredResult = result.filter((claim) => { const filteredResult = result.filter((claim) => {
return (claim.address === claimAddress); return (claim.address === claimAddress);
}); });
// return based on whether any non-spee.ch claims were left // return based on whether any claims were left
if (filteredResult.length >= 1) { if (filteredResult.length >= 1) {
resolve(false); resolve(false);
} else { } else {

View file

@ -2,7 +2,7 @@ const logger = require('winston');
module.exports = { module.exports = {
serveFile ({ filePath, fileType }, claimId, name, res) { serveFile ({ filePath, fileType }, claimId, name, res) {
logger.verbose(`serving ${name}#${claimId}`); logger.verbose(`serving file: ${filePath}`);
// set response options // set response options
const headerContentType = fileType || 'image/jpeg'; const headerContentType = fileType || 'image/jpeg';
const options = { const options = {
@ -15,9 +15,11 @@ module.exports = {
res.status(200).sendFile(filePath, options); res.status(200).sendFile(filePath, options);
}, },
showFile (claimInfo, shortId, res) { showFile (claimInfo, shortId, res) {
logger.verbose(`showing claim: ${claimInfo.name}#${claimInfo.claimId}`);
res.status(200).render('show', { layout: 'show', claimInfo, shortId }); res.status(200).render('show', { layout: 'show', claimInfo, shortId });
}, },
showFileLite (claimInfo, shortId, res) { showFileLite (claimInfo, shortId, res) {
logger.verbose(`showlite claim: ${claimInfo.name}#${claimInfo.claimId}`);
res.status(200).render('showLite', { layout: 'showlite', claimInfo, shortId }); res.status(200).render('showLite', { layout: 'showlite', claimInfo, shortId });
}, },
}; };

View file

@ -1,8 +1,8 @@
const logger = require('winston'); const logger = require('winston');
const { returnShortId } = require('../helpers/sequelizeHelpers.js'); const { returnShortId } = require('../helpers/sequelizeHelpers.js');
const DEFAULT_THUMBNAIL = 'https://spee.ch/assets/img/video_thumb_default.png'; const { publishing, site } = require('../config/speechConfig.js');
const DEFAULT_TITLE = 'Spee<ch'; const { defaultTitle, defaultThumbnail, defaultDescription } = publishing;
const DEFAULT_DESCRIPTION = 'Decentralized video and content hosting.'; const { host } = site;
function determineFileExtensionFromContentType (contentType) { function determineFileExtensionFromContentType (contentType) {
switch (contentType) { switch (contentType) {
@ -67,19 +67,20 @@ function determineOgThumbnailContentType (thumbnail) {
} }
function addOpengraphDataToClaim (claim) { function addOpengraphDataToClaim (claim) {
claim['embedUrl'] = `https://spee.ch/embed/${claim.claimId}/${claim.name}`; claim['host'] = host;
claim['showUrl'] = `https://spee.ch/${claim.claimId}/${claim.name}`; claim['embedUrl'] = `${host}/${claim.claimId}/${claim.name}`;
claim['source'] = `https://spee.ch/${claim.claimId}/${claim.name}.${claim.fileExt}`; claim['showUrl'] = `${host}/${claim.claimId}/${claim.name}`;
claim['directFileUrl'] = `https://spee.ch/${claim.claimId}/${claim.name}.${claim.fileExt}`; claim['source'] = `${host}/${claim.claimId}/${claim.name}.${claim.fileExt}`;
claim['ogTitle'] = determineOgTitle(claim.title, DEFAULT_TITLE); claim['directFileUrl'] = `${host}/${claim.claimId}/${claim.name}.${claim.fileExt}`;
claim['ogDescription'] = determineOgDescription(claim.description, DEFAULT_DESCRIPTION); claim['ogTitle'] = determineOgTitle(claim.title, defaultTitle);
claim['ogDescription'] = determineOgDescription(claim.description, defaultDescription);
claim['ogThumbnailContentType'] = determineOgThumbnailContentType(claim.thumbnail); claim['ogThumbnailContentType'] = determineOgThumbnailContentType(claim.thumbnail);
return claim; return claim;
}; };
function prepareClaimData (claim) { function prepareClaimData (claim) {
// logger.debug('preparing claim data based on resolved data:', claim); // logger.debug('preparing claim data based on resolved data:', claim);
claim['thumbnail'] = determineThumbnail(claim.thumbnail, DEFAULT_THUMBNAIL); claim['thumbnail'] = determineThumbnail(claim.thumbnail, defaultThumbnail);
claim['fileExt'] = determineFileExtensionFromContentType(claim.contentType); claim['fileExt'] = determineFileExtensionFromContentType(claim.contentType);
claim = addOpengraphDataToClaim(claim); claim = addOpengraphDataToClaim(claim);
return claim; return claim;
@ -280,7 +281,7 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
default: default:
channelClaimsArray.forEach(claim => { channelClaimsArray.forEach(claim => {
claim['fileExt'] = determineFileExtensionFromContentType(claim.contentType); claim['fileExt'] = determineFileExtensionFromContentType(claim.contentType);
claim['thumbnail'] = determineThumbnail(claim.thumbnail, DEFAULT_THUMBNAIL); claim['thumbnail'] = determineThumbnail(claim.thumbnail, defaultThumbnail);
return claim; return claim;
}); });
return resolve(channelClaimsArray); return resolve(channelClaimsArray);

View file

@ -78,7 +78,7 @@ const Asset = function () {
this.isFileAvailable() this.isFileAvailable()
.then(isAvailable => { .then(isAvailable => {
if (!isAvailable) { if (!isAvailable) {
console.log('file is not yet available on spee.ch'); console.log('file is not yet available');
that.showSearchMessage(); that.showSearchMessage();
return that.getAssetOnSpeech(); return that.getAssetOnSpeech();
} }

View file

@ -52,7 +52,7 @@ function publishNewChannel (event) {
validationFunctions.showError(channelNameErrorDisplayElement, error.message); validationFunctions.showError(channelNameErrorDisplayElement, error.message);
} else { } else {
console.log('signup failure:', error); console.log('signup failure:', error);
showChannelCreationError('Unfortunately, Spee.ch encountered an error while creating your channel. Please let us know in slack!'); showChannelCreationError('Unfortunately, we encountered an error while creating your channel. Please let us know in slack!');
} }
}) })
} }

View file

@ -1,7 +1,7 @@
const logger = require('winston'); const logger = require('winston');
const multipart = require('connect-multiparty'); const multipart = require('connect-multiparty');
const config = require('../config/speechConfig.js'); const { files, site } = require('../config/speechConfig.js');
const multipartMiddleware = multipart({uploadDir: config.files.uploadDirectory}); const multipartMiddleware = multipart({uploadDir: files.uploadDirectory});
const db = require('../models'); const db = require('../models');
const { publish } = require('../controllers/publishController.js'); const { publish } = require('../controllers/publishController.js');
const { getClaimList, resolveUri, getClaim } = require('../helpers/lbryApi.js'); const { getClaimList, resolveUri, getClaim } = require('../helpers/lbryApi.js');
@ -83,14 +83,14 @@ module.exports = (app) => {
}); });
}); });
// route to check whether spee.ch has published to a claim // route to check whether this site published to a claim
app.get('/api/claim-is-available/:name', ({ params }, res) => { app.get('/api/claim-is-available/:name', ({ params }, res) => {
checkClaimNameAvailability(params.name) checkClaimNameAvailability(params.name)
.then(result => { .then(result => {
if (result === true) { if (result === true) {
res.status(200).json(true); res.status(200).json(true);
} else { } else {
// logger.debug(`Rejecting '${params.name}' because that name has already been claimed on spee.ch`); // logger.debug(`Rejecting '${params.name}' because that name has already been claimed by this site`);
res.status(200).json(false); res.status(200).json(false);
} }
}) })
@ -98,14 +98,14 @@ module.exports = (app) => {
res.status(500).json(error); res.status(500).json(error);
}); });
}); });
// route to check whether spee.ch has published to a channel // route to check whether site has published to a channel
app.get('/api/channel-is-available/:name', ({ params }, res) => { app.get('/api/channel-is-available/:name', ({ params }, res) => {
checkChannelAvailability(params.name) checkChannelAvailability(params.name)
.then(result => { .then(result => {
if (result === true) { if (result === true) {
res.status(200).json(true); res.status(200).json(true);
} else { } else {
// logger.debug(`Rejecting '${params.name}' because that channel has already been claimed on spee.ch`); // logger.debug(`Rejecting '${params.name}' because that channel has already been claimed`);
res.status(200).json(false); res.status(200).json(false);
} }
}) })
@ -161,13 +161,13 @@ module.exports = (app) => {
} }
channelPassword = body.channelPassword || null; channelPassword = body.channelPassword || null;
skipAuth = false; skipAuth = false;
// case 1: publish from spee.ch, client logged in // case 1: publish from client, client logged in
if (user) { if (user) {
skipAuth = true; skipAuth = true;
if (anonymous) { if (anonymous) {
channelName = null; channelName = null;
} }
// case 2: publish from api or spee.ch, client not logged in // case 2: publish from api or client, client not logged in
} else { } else {
if (anonymous) { if (anonymous) {
skipAuth = true; skipAuth = true;
@ -187,7 +187,7 @@ module.exports = (app) => {
}) })
.then(result => { .then(result => {
if (!result) { if (!result) {
throw new Error('That name is already in use by spee.ch.'); throw new Error('That name is already claimed by another user.');
} }
// create publish parameters object // create publish parameters object
return createPublishParams(filePath, name, title, description, license, nsfw, thumbnail, channelName); return createPublishParams(filePath, name, title, description, license, nsfw, thumbnail, channelName);
@ -202,7 +202,7 @@ module.exports = (app) => {
success: true, success: true,
message: { message: {
name, name,
url : `spee.ch/${result.claim_id}/${name}`, url : `${site.host}/${result.claim_id}/${name}`,
lbryTx: result, lbryTx: result,
}, },
}); });

View file

@ -1,5 +1,6 @@
const errorHandlers = require('../helpers/errorHandlers.js'); const errorHandlers = require('../helpers/errorHandlers.js');
const { getTrendingClaims, getRecentClaims } = require('../controllers/statsController.js'); const { getTrendingClaims, getRecentClaims } = require('../controllers/statsController.js');
const { site } = require('../config/speechConfig.js');
module.exports = (app) => { module.exports = (app) => {
// route to log out // route to log out
@ -15,7 +16,7 @@ module.exports = (app) => {
res.status(200).render('login'); res.status(200).render('login');
} }
}); });
// route to show 'about' page for spee.ch // route to show 'about' page
app.get('/about', (req, res) => { app.get('/about', (req, res) => {
// get and render the content // get and render the content
res.status(200).render('about'); res.status(200).render('about');
@ -52,8 +53,9 @@ module.exports = (app) => {
app.get('/embed/:claimId/:name', ({ params }, res) => { app.get('/embed/:claimId/:name', ({ params }, res) => {
const claimId = params.claimId; const claimId = params.claimId;
const name = params.name; const name = params.name;
const host = site.host;
// get and render the content // get and render the content
res.status(200).render('embed', { layout: 'embed', claimId, name }); res.status(200).render('embed', { layout: 'embed', host, claimId, name });
}); });
// route to display all free public claims at a given name // route to display all free public claims at a given name
app.get('/:name/all', (req, res) => { app.get('/:name/all', (req, res) => {

View file

@ -129,7 +129,7 @@ function showOrServeAsset (responseType, claimId, claimName, res) {
} }
function flipClaimNameAndIdForBackwardsCompatibility (identifier, name) { function flipClaimNameAndIdForBackwardsCompatibility (identifier, name) {
// this is a patch for backwards compatability with 'spee.ch/name/claim_id' url format // this is a patch for backwards compatability with '/name/claim_id' url format
if (isValidShortIdOrClaimId(name) && !isValidShortIdOrClaimId(identifier)) { if (isValidShortIdOrClaimId(name) && !isValidShortIdOrClaimId(identifier)) {
const tempName = name; const tempName = name;
name = identifier; name = identifier;

View file

@ -6,12 +6,12 @@
</head> </head>
<body> <body>
<img src="https://dev1.spee.ch/zackmath"/> <img src="https://dev1.spee.ch/zackmath"/>
<!--<img src="https://staging.spee.ch/8/zackmath"/>--> <!--<img src="https://dev1.spee.ch/8/zackmath"/>-->
<!--<img src="https://staging.spee.ch/zackmath.ext"/>--> <!--<img src="https://dev1.spee.ch/zackmath.ext"/>-->
<!--<img src="https://staging.spee.ch/8/zackmath.ext"/>--> <!--<img src="https://dev1.spee.ch/8/zackmath.ext"/>-->
<video width="50%" controls poster="https://dev1.spee.ch/assets/img/video_thumb_default.png" src="https://dev1.spee.ch/LBRY-Hype"></video> <video width="50%" controls poster="https://dev1.spee.ch/assets/img/video_thumb_default.png" src="https://dev1.spee.ch/LBRY-Hype"></video>
<!--<video width="50%" controls poster="https://spee.ch/assets/img/video_thumb_default.png" src="https://staging.spee.ch/a/LBRY-Hype"></video>--> <!--<video width="50%" controls poster="https://dev1.spee.ch/assets/img/video_thumb_default.png" src="https://staging.spee.ch/a/LBRY-Hype"></video>-->
<!--<video width="50%" controls poster="https://spee.ch/assets/img/video_thumb_default.png" src="https://staging.spee.ch/LBRY-Hype.test"></video>--> <!--<video width="50%" controls poster="https://dev1.spee.ch/assets/img/video_thumb_default.png" src="https://staging.spee.ch/LBRY-Hype.test"></video>-->
<!--<video width="50%" controls poster="https://spee.ch/assets/img/video_thumb_default.png" src="https://staging.spee.ch/a/LBRY-Hype.test"></video>--> <!--<video width="50%" controls poster="https://dev1.spee.ch/assets/img/video_thumb_default.png" src="https://staging.spee.ch/a/LBRY-Hype.test"></video>-->
</body> </body>
</html> </html>

View file

@ -1 +1 @@
<video width="100%" controls src="https://spee.ch/{{claimId}}/{{name}}.mp4" type="video/mp4"></video> <video width="100%" controls src="{{host}}/{{claimId}}/{{name}}.mp4" type="video/mp4"></video>

View file

@ -22,7 +22,7 @@
<div class="row row--short row--wide"> <div class="row row--short row--wide">
<div class="column column--7"> <div class="column column--7">
<div class="input-error" id="input-error-copy-short-link" hidden="true"></div> <div class="input-error" id="input-error-copy-short-link" hidden="true"></div>
<input type="text" id="short-link" class="input-disabled input-text--full-width" readonly spellcheck="false" value="https://spee.ch/{{shortId}}/{{claimInfo.name}}.{{claimInfo.fileExt}}" onclick="select()"/> <input type="text" id="short-link" class="input-disabled input-text--full-width" readonly spellcheck="false" value="{{claimInfo.host}}/{{shortId}}/{{claimInfo.name}}.{{claimInfo.fileExt}}" onclick="select()"/>
</div><div class="column column--1"></div><div class="column column--2"> </div><div class="column column--1"></div><div class="column column--2">
<button class="button--primary" data-elementtocopy="short-link" onclick="copyToClipboard(event)">copy</button> <button class="button--primary" data-elementtocopy="short-link" onclick="copyToClipboard(event)">copy</button>
</div> </div>
@ -37,9 +37,9 @@
<div class="column column--7"> <div class="column column--7">
<div class="input-error" id="input-error-copy-embed-text" hidden="true"></div> <div class="input-error" id="input-error-copy-embed-text" hidden="true"></div>
{{#ifConditional claimInfo.contentType '===' 'video/mp4'}} {{#ifConditional claimInfo.contentType '===' 'video/mp4'}}
<input type="text" id="embed-text" class="input-disabled input-text--full-width" readonly onclick="select()" spellcheck="false" value='&lt;video width="100%" controls poster="{{claimInfo.thumbnail}}" src="https://spee.ch/{{claimInfo.claimId}}/{{claimInfo.name}}.{{claimInfo.fileExt}}"/>&lt;/video>'/> <input type="text" id="embed-text" class="input-disabled input-text--full-width" readonly onclick="select()" spellcheck="false" value='&lt;video width="100%" controls poster="{{claimInfo.thumbnail}}" src="{{claimInfo.host}}/{{claimInfo.claimId}}/{{claimInfo.name}}.{{claimInfo.fileExt}}"/>&lt;/video>'/>
{{else}} {{else}}
<input type="text" id="embed-text" class="input-disabled input-text--full-width" readonly onclick="select()" spellcheck="false" value='&lt;img src="https://spee.ch/{{claimInfo.claimId}}/{{claimInfo.name}}.{{claimInfo.fileExt}}"/>'/> <input type="text" id="embed-text" class="input-disabled input-text--full-width" readonly onclick="select()" spellcheck="false" value='&lt;img src="{{claimInfo.host}}/{{claimInfo.claimId}}/{{claimInfo.name}}.{{claimInfo.fileExt}}"/>'/>
{{/ifConditional}} {{/ifConditional}}
</div><div class="column column--1"></div><div class="column column--2"> </div><div class="column column--1"></div><div class="column column--2">
<button class="button--primary" data-elementtocopy="embed-text" onclick="copyToClipboard(event)">copy</button> <button class="button--primary" data-elementtocopy="embed-text" onclick="copyToClipboard(event)">copy</button>
@ -55,10 +55,10 @@
<span class="text">Share:</span> <span class="text">Share:</span>
</div><div class="column column--7 column--med-10"> </div><div class="column column--7 column--med-10">
<div class="row row--short row--wide flex-container--row flex-container--space-between-bottom flex-container--wrap"> <div class="row row--short row--wide flex-container--row flex-container--space-between-bottom flex-container--wrap">
<a class="link--primary" target="_blank" href="https://twitter.com/intent/tweet?text=https://spee.ch/{{shortId}}/{{claimInfo.name}}">twitter</a> <a class="link--primary" target="_blank" href="https://twitter.com/intent/tweet?text={{claimInfo.host}}/{{shortId}}/{{claimInfo.name}}">twitter</a>
<a class="link--primary" target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=https://spee.ch/{{shortId}}/{{claimInfo.name}}">facebook</a> <a class="link--primary" target="_blank" href="https://www.facebook.com/sharer/sharer.php?u={{claimInfo.host}}/{{shortId}}/{{claimInfo.name}}">facebook</a>
<a class="link--primary" target="_blank" href="http://tumblr.com/widgets/share/tool?canonicalUrl=https://spee.ch/{{shortId}}/{{claimInfo.name}}">tumblr</a> <a class="link--primary" target="_blank" href="http://tumblr.com/widgets/share/tool?canonicalUrl={{claimInfo.host}}/{{shortId}}/{{claimInfo.name}}">tumblr</a>
<a class="link--primary" target="_blank" href="https://www.reddit.com/submit?url=https://spee.ch/{{shortId}}/{{claimInfo.name}}&title={{claimInfo.name}}">reddit</a> <a class="link--primary" target="_blank" href="https://www.reddit.com/submit?url={{claimInfo.host}}/{{shortId}}/{{claimInfo.name}}&title={{claimInfo.name}}">reddit</a>
</div> </div>
</div> </div>
</div> </div>