350 open graph react #360
|
@ -1,9 +1,10 @@
|
||||||
{
|
{
|
||||||
"extends": "standard",
|
"extends": ["standard", "standard-jsx"],
|
||||||
"env": {
|
"env": {
|
||||||
"es6": true,
|
"es6": true,
|
||||||
"jest": true,
|
"jest": true,
|
||||||
"node": true
|
"node": true,
|
||||||
|
"browser": true
|
||||||
},
|
},
|
||||||
"globals": {
|
"globals": {
|
||||||
"GENTLY": true
|
"GENTLY": true
|
||||||
|
|
5
.gitignore
vendored
|
@ -1,4 +1,7 @@
|
||||||
node_modules
|
node_modules
|
||||||
.idea
|
.idea
|
||||||
config/sequelizeCliConfig.js
|
config/sequelizeCliConfig.js
|
||||||
config/speechConfig.js
|
config/speechConfig.js
|
||||||
|
public/bundle
|
||||||
|
server.js
|
||||||
|
webpack.config.js
|
||||||
|
|
10
README.md
|
@ -1,5 +1,5 @@
|
||||||
# spee.ch
|
# Spee.ch
|
||||||
spee.ch is a single-serving site that reads and publishes images and videos to and from the [LBRY](https://lbry.io/) blockchain.
|
Spee.ch is a web app that reads and publishes images and videos to and from the [LBRY](https://lbry.io/) blockchain.
|
||||||
|
|
||||||
## how to run this repository locally
|
## how to run this repository locally
|
||||||
* start mysql
|
* start mysql
|
||||||
|
@ -16,9 +16,9 @@ spee.ch is a single-serving site that reads and publishes images and videos to a
|
||||||
* run `npm install`
|
* run `npm install`
|
||||||
* create your `speechConfig.js` file
|
* create your `speechConfig.js` file
|
||||||
* copy `speechConfig.js.example` and name it `speechConfig.js`
|
* copy `speechConfig.js.example` and name it `speechConfig.js`
|
||||||
* replace the `null` values in the config file with the appropriate values for your environement
|
* replace the `null` values in the config file with the appropriate values for your environment
|
||||||
* to start the server, from your command line run `node speech.js`
|
* build the app by running `npm run build-prod`
|
||||||
* To run hot, use `nodemon` instead of `node`
|
* to start the server, run `npm run start`
|
||||||
* visit [localhost:3000](http://localhost:3000)
|
* visit [localhost:3000](http://localhost:3000)
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
|
@ -7,31 +7,31 @@ module.exports = {
|
||||||
const userName = channelName.substring(1);
|
const userName = channelName.substring(1);
|
||||||
logger.debug(`authenticateChannelCredentials > channelName: ${channelName} username: ${userName} pass: ${userPassword}`);
|
logger.debug(`authenticateChannelCredentials > channelName: ${channelName} username: ${userName} pass: ${userPassword}`);
|
||||||
db.User
|
db.User
|
||||||
.findOne({where: { userName }})
|
.findOne({where: { userName }})
|
||||||
.then(user => {
|
.then(user => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
logger.debug('no user found');
|
logger.debug('no user found');
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return user.comparePassword(userPassword, (passwordErr, isMatch) => {
|
||||||
|
if (passwordErr) {
|
||||||
|
logger.error('comparePassword error:', passwordErr);
|
||||||
resolve(false);
|
resolve(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return user.comparePassword(userPassword, (passwordErr, isMatch) => {
|
if (!isMatch) {
|
||||||
if (passwordErr) {
|
logger.debug('incorrect password');
|
||||||
logger.error('comparePassword error:', passwordErr);
|
resolve(false);
|
||||||
resolve(false);
|
return;
|
||||||
return;
|
}
|
||||||
}
|
logger.debug('...password was a match...');
|
||||||
if (!isMatch) {
|
resolve(true);
|
||||||
logger.debug('incorrect password');
|
|
||||||
resolve(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.debug('...password was a match...');
|
|
||||||
resolve(true);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
reject(error);
|
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
authenticateIfNoUserToken (channelName, channelPassword, user) {
|
authenticateIfNoUserToken (channelName, channelPassword, user) {
|
||||||
|
|
|
@ -23,8 +23,10 @@ module.exports = {
|
||||||
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: {
|
site: {
|
||||||
name: 'Spee.ch',
|
title: 'Spee.ch',
|
||||||
host: 'https://spee.ch',
|
name : 'Spee.ch',
|
||||||
|
host : 'https://spee.ch',
|
||||||
|
description: 'Open-source, decentralized image and video sharing.'
|
||||||
},
|
},
|
||||||
claim: {
|
claim: {
|
||||||
defaultTitle : 'Spee.ch',
|
defaultTitle : 'Spee.ch',
|
||||||
|
|
|
@ -10,80 +10,80 @@ module.exports = {
|
||||||
let publishResults, certificateId, channelName;
|
let publishResults, certificateId, channelName;
|
||||||
// publish the file
|
// publish the file
|
||||||
return lbryApi.publishClaim(publishParams)
|
return lbryApi.publishClaim(publishParams)
|
||||||
.then(tx => {
|
.then(tx => {
|
||||||
logger.info(`Successfully published ${publishParams.name} ${fileName}`, tx);
|
logger.info(`Successfully published ${publishParams.name} ${fileName}`, tx);
|
||||||
publishResults = tx;
|
publishResults = tx;
|
||||||
// get the channel information
|
// get the channel information
|
||||||
if (publishParams.channel_name) {
|
if (publishParams.channel_name) {
|
||||||
logger.debug(`this claim was published in channel: ${publishParams.channel_name}`);
|
logger.debug(`this claim was published in channel: ${publishParams.channel_name}`);
|
||||||
return db.Channel.findOne({where: {channelName: publishParams.channel_name}});
|
return db.Channel.findOne({where: {channelName: publishParams.channel_name}});
|
||||||
} else {
|
} else {
|
||||||
logger.debug('this claim was not published in a channel');
|
logger.debug('this claim was not published in a channel');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(channel => {
|
.then(channel => {
|
||||||
// set channel information
|
// set channel information
|
||||||
certificateId = null;
|
certificateId = null;
|
||||||
channelName = null;
|
channelName = null;
|
||||||
if (channel) {
|
if (channel) {
|
||||||
certificateId = channel.channelClaimId;
|
certificateId = channel.channelClaimId;
|
||||||
channelName = channel.channelName;
|
channelName = channel.channelName;
|
||||||
}
|
}
|
||||||
logger.debug(`certificateId: ${certificateId}`);
|
logger.debug(`certificateId: ${certificateId}`);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// create the File record
|
// create the File record
|
||||||
const fileRecord = {
|
const fileRecord = {
|
||||||
name : publishParams.name,
|
name : publishParams.name,
|
||||||
claimId : publishResults.claim_id,
|
claimId : publishResults.claim_id,
|
||||||
title : publishParams.metadata.title,
|
title : publishParams.metadata.title,
|
||||||
description: publishParams.metadata.description,
|
description: publishParams.metadata.description,
|
||||||
address : publishParams.claim_address,
|
address : publishParams.claim_address,
|
||||||
outpoint : `${publishResults.txid}:${publishResults.nout}`,
|
outpoint : `${publishResults.txid}:${publishResults.nout}`,
|
||||||
height : 0,
|
height : 0,
|
||||||
fileName,
|
fileName,
|
||||||
filePath : publishParams.file_path,
|
filePath : publishParams.file_path,
|
||||||
fileType,
|
fileType,
|
||||||
nsfw : publishParams.metadata.nsfw,
|
nsfw : publishParams.metadata.nsfw,
|
||||||
};
|
};
|
||||||
// create the Claim record
|
// create the Claim record
|
||||||
const claimRecord = {
|
const claimRecord = {
|
||||||
name : publishParams.name,
|
name : publishParams.name,
|
||||||
claimId : publishResults.claim_id,
|
claimId : publishResults.claim_id,
|
||||||
title : publishParams.metadata.title,
|
title : publishParams.metadata.title,
|
||||||
description: publishParams.metadata.description,
|
description: publishParams.metadata.description,
|
||||||
address : publishParams.claim_address,
|
address : publishParams.claim_address,
|
||||||
thumbnail : publishParams.metadata.thumbnail,
|
thumbnail : publishParams.metadata.thumbnail,
|
||||||
outpoint : `${publishResults.txid}:${publishResults.nout}`,
|
outpoint : `${publishResults.txid}:${publishResults.nout}`,
|
||||||
height : 0,
|
height : 0,
|
||||||
contentType: fileType,
|
contentType: fileType,
|
||||||
nsfw : publishParams.metadata.nsfw,
|
nsfw : publishParams.metadata.nsfw,
|
||||||
amount : publishParams.bid,
|
amount : publishParams.bid,
|
||||||
certificateId,
|
certificateId,
|
||||||
channelName,
|
channelName,
|
||||||
};
|
};
|
||||||
// upsert criteria
|
// upsert criteria
|
||||||
const upsertCriteria = {
|
const upsertCriteria = {
|
||||||
name : publishParams.name,
|
name : publishParams.name,
|
||||||
claimId: publishResults.claim_id,
|
claimId: publishResults.claim_id,
|
||||||
};
|
};
|
||||||
// upsert the records
|
// upsert the records
|
||||||
return Promise.all([db.upsert(db.File, fileRecord, upsertCriteria, 'File'), db.upsert(db.Claim, claimRecord, upsertCriteria, 'Claim')]);
|
return Promise.all([db.upsert(db.File, fileRecord, upsertCriteria, 'File'), db.upsert(db.Claim, claimRecord, upsertCriteria, 'Claim')]);
|
||||||
})
|
})
|
||||||
.then(([file, claim]) => {
|
.then(([file, claim]) => {
|
||||||
logger.debug('File and Claim records successfully created');
|
logger.debug('File and Claim records successfully created');
|
||||||
return Promise.all([file.setClaim(claim), claim.setFile(file)]);
|
return Promise.all([file.setClaim(claim), claim.setFile(file)]);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.debug('File and Claim records successfully associated');
|
logger.debug('File and Claim records successfully associated');
|
||||||
resolve(publishResults); // resolve the promise with the result from lbryApi.publishClaim;
|
resolve(publishResults); // resolve the promise with the result from lbryApi.publishClaim;
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
logger.error('PUBLISH ERROR', error);
|
logger.error('PUBLISH ERROR', error);
|
||||||
publishHelpers.deleteTemporaryFile(publishParams.file_path); // delete the local file
|
publishHelpers.deleteTemporaryFile(publishParams.file_path); // delete the local file
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
checkClaimNameAvailability (name) {
|
checkClaimNameAvailability (name) {
|
||||||
|
|
|
@ -6,11 +6,11 @@ module.exports = function () {
|
||||||
|
|
||||||
for (let configCategoryKey in config) {
|
for (let configCategoryKey in config) {
|
||||||
if (config.hasOwnProperty(configCategoryKey)) {
|
if (config.hasOwnProperty(configCategoryKey)) {
|
||||||
// get the final variables for each config category
|
// get the final variables for each config category
|
||||||
const configVariables = config[configCategoryKey];
|
const configVariables = config[configCategoryKey];
|
||||||
for (let configVarKey in configVariables) {
|
for (let configVarKey in configVariables) {
|
||||||
if (configVariables.hasOwnProperty(configVarKey)) {
|
if (configVariables.hasOwnProperty(configVarKey)) {
|
||||||
// print each variable
|
// print each variable
|
||||||
logger.debug(`CONFIG CHECK: ${configCategoryKey}.${configVarKey} === ${configVariables[configVarKey]}`);
|
logger.debug(`CONFIG CHECK: ${configCategoryKey}.${configVarKey} === ${configVariables[configVarKey]}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
45
helpers/handlePageRender.jsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { renderToString } from 'react-dom/server';
|
||||||
|
import { createStore } from 'redux';
|
||||||
|
import Reducer from '../react/reducers';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { StaticRouter } from 'react-router-dom';
|
||||||
|
import GAListener from '../react/components/GAListener';
|
||||||
|
import App from '../react/app';
|
||||||
|
import renderFullPage from './renderFullPage.js';
|
||||||
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
|
module.exports = (req, res) => {
|
||||||
|
let context = {};
|
||||||
|
|
||||||
|
// create a new Redux store instance
|
||||||
|
const store = createStore(Reducer);
|
||||||
|
|
||||||
|
// render component to a string
|
||||||
|
const html = renderToString(
|
||||||
|
<Provider store={store}>
|
||||||
|
<StaticRouter location={req.url} context={context}>
|
||||||
|
<GAListener>
|
||||||
|
<App />
|
||||||
|
</GAListener>
|
||||||
|
</StaticRouter>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
// get head tags from helmet
|
||||||
|
const helmet = Helmet.renderStatic();
|
||||||
|
|
||||||
|
// check for a redirect
|
||||||
|
if (context.url) {
|
||||||
|
// Somewhere a `<Redirect>` was rendered
|
||||||
|
return res.redirect(301, context.url);
|
||||||
|
} else {
|
||||||
|
// we're good, send the response
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the initial state from our Redux store
|
||||||
|
const preloadedState = store.getState();
|
||||||
|
|
||||||
|
// send the rendered page back to the client
|
||||||
|
res.send(renderFullPage(helmet, html, preloadedState));
|
||||||
|
};
|
71
helpers/handleShowRender.jsx
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { renderToString } from 'react-dom/server';
|
||||||
|
import { createStore, applyMiddleware } from 'redux';
|
||||||
|
import Reducer from '../react/reducers';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { StaticRouter } from 'react-router-dom';
|
||||||
|
import GAListener from '../react/components/GAListener';
|
||||||
|
import App from '../react/app';
|
||||||
|
import renderFullPage from './renderFullPage';
|
||||||
|
import createSagaMiddleware from 'redux-saga';
|
||||||
|
import { call } from 'redux-saga/effects';
|
||||||
|
import { handleShowPageUri } from '../react/sagas/show_uri';
|
||||||
|
import { onHandleShowPageUri } from '../react/actions/show';
|
||||||
|
|
||||||
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
|
const returnSagaWithParams = (saga, params) => {
|
||||||
|
return function * () {
|
||||||
|
yield call(saga, params);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = (req, res) => {
|
||||||
|
let context = {};
|
||||||
|
|
||||||
|
// create and apply middleware
|
||||||
|
const sagaMiddleware = createSagaMiddleware();
|
||||||
|
const middleware = applyMiddleware(sagaMiddleware);
|
||||||
|
|
||||||
|
// create a new Redux store instance
|
||||||
|
const store = createStore(Reducer, middleware);
|
||||||
|
|
||||||
|
// create saga
|
||||||
|
const action = onHandleShowPageUri(req.params);
|
||||||
|
const saga = returnSagaWithParams(handleShowPageUri, action);
|
||||||
|
|
||||||
|
// run the saga middleware
|
||||||
|
sagaMiddleware
|
||||||
|
.run(saga)
|
||||||
|
.done
|
||||||
|
.then(() => {
|
||||||
|
console.log('preload sagas are done');
|
||||||
|
// render component to a string
|
||||||
|
const html = renderToString(
|
||||||
|
<Provider store={store}>
|
||||||
|
<StaticRouter location={req.url} context={context}>
|
||||||
|
<GAListener>
|
||||||
|
<App />
|
||||||
|
</GAListener>
|
||||||
|
</StaticRouter>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
// get head tags from helmet
|
||||||
|
const helmet = Helmet.renderStatic();
|
||||||
|
|
||||||
|
// check for a redirect
|
||||||
|
if (context.url) {
|
||||||
|
console.log('REDIRECTING:', context.url);
|
||||||
|
return res.redirect(301, context.url);
|
||||||
|
} else {
|
||||||
|
console.log(`we're good, send the response`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the initial state from our Redux store
|
||||||
|
const preloadedState = store.getState();
|
||||||
|
|
||||||
|
// send the rendered page back to the client
|
||||||
|
res.send(renderFullPage(helmet, html, preloadedState));
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,113 +0,0 @@
|
||||||
const Handlebars = require('handlebars');
|
|
||||||
const { site, claim: claimDefaults } = require('../config/speechConfig.js');
|
|
||||||
|
|
||||||
function determineOgTitle (storedTitle, defaultTitle) {
|
|
||||||
return ifEmptyReturnOther(storedTitle, defaultTitle);
|
|
||||||
};
|
|
||||||
|
|
||||||
function determineOgDescription (storedDescription, defaultDescription) {
|
|
||||||
const length = 200;
|
|
||||||
let description = ifEmptyReturnOther(storedDescription, defaultDescription);
|
|
||||||
if (description.length >= length) {
|
|
||||||
description = `${description.substring(0, length)}...`;
|
|
||||||
};
|
|
||||||
return description;
|
|
||||||
};
|
|
||||||
|
|
||||||
function ifEmptyReturnOther (value, replacement) {
|
|
||||||
if (value === '') {
|
|
||||||
return replacement;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function determineContentTypeFromFileExtension (fileExtension) {
|
|
||||||
switch (fileExtension) {
|
|
||||||
case 'jpeg':
|
|
||||||
case 'jpg':
|
|
||||||
return 'image/jpeg';
|
|
||||||
case 'png':
|
|
||||||
return 'image/png';
|
|
||||||
case 'gif':
|
|
||||||
return 'image/gif';
|
|
||||||
case 'mp4':
|
|
||||||
return 'video/mp4';
|
|
||||||
default:
|
|
||||||
return 'image/jpeg';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function determineOgThumbnailContentType (thumbnail) {
|
|
||||||
if (thumbnail) {
|
|
||||||
if (thumbnail.lastIndexOf('.') !== -1) {
|
|
||||||
return determineContentTypeFromFileExtension(thumbnail.substring(thumbnail.lastIndexOf('.')));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function createOpenGraphDataFromClaim (claim, defaultTitle, defaultDescription) {
|
|
||||||
let openGraphData = {};
|
|
||||||
openGraphData['embedUrl'] = `${site.host}/${claim.claimId}/${claim.name}`;
|
|
||||||
openGraphData['showUrl'] = `${site.host}/${claim.claimId}/${claim.name}`;
|
|
||||||
openGraphData['source'] = `${site.host}/${claim.claimId}/${claim.name}.${claim.fileExt}`;
|
|
||||||
openGraphData['directFileUrl'] = `${site.host}/${claim.claimId}/${claim.name}.${claim.fileExt}`;
|
|
||||||
openGraphData['ogTitle'] = determineOgTitle(claim.title, defaultTitle);
|
|
||||||
openGraphData['ogDescription'] = determineOgDescription(claim.description, defaultDescription);
|
|
||||||
openGraphData['ogThumbnailContentType'] = determineOgThumbnailContentType(claim.thumbnail);
|
|
||||||
return openGraphData;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
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>${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);
|
|
||||||
},
|
|
||||||
addOpenGraph (claim) {
|
|
||||||
const { ogTitle, ogDescription, showUrl, source, ogThumbnailContentType } = createOpenGraphDataFromClaim(claim, claimDefaults.defaultTitle, claimDefaults.defaultDescription);
|
|
||||||
const thumbnail = claim.thumbnail;
|
|
||||||
const contentType = claim.contentType;
|
|
||||||
const ogTitleTag = `<meta property="og:title" content="${ogTitle}" />`;
|
|
||||||
const ogUrlTag = `<meta property="og:url" content="${showUrl}" />`;
|
|
||||||
const ogSiteNameTag = `<meta property="og:site_name" content="${site.title}" />`;
|
|
||||||
const ogDescriptionTag = `<meta property="og:description" content="${ogDescription}" />`;
|
|
||||||
const ogImageWidthTag = '<meta property="og:image:width" content="600" />';
|
|
||||||
const ogImageHeightTag = '<meta property="og:image:height" content="315" />';
|
|
||||||
const basicTags = `${ogTitleTag} ${ogUrlTag} ${ogSiteNameTag} ${ogDescriptionTag} ${ogImageWidthTag} ${ogImageHeightTag}`;
|
|
||||||
let ogImageTag = `<meta property="og:image" content="${source}" />`;
|
|
||||||
let ogImageTypeTag = `<meta property="og:image:type" content="${contentType}" />`;
|
|
||||||
let ogTypeTag = `<meta property="og:type" content="article" />`;
|
|
||||||
if (contentType === 'video/mp4') {
|
|
||||||
const ogVideoTag = `<meta property="og:video" content="${source}" />`;
|
|
||||||
const ogVideoSecureUrlTag = `<meta property="og:video:secure_url" content="${source}" />`;
|
|
||||||
const ogVideoTypeTag = `<meta property="og:video:type" content="${contentType}" />`;
|
|
||||||
ogImageTag = `<meta property="og:image" content="${thumbnail}" />`;
|
|
||||||
ogImageTypeTag = `<meta property="og:image:type" content="${ogThumbnailContentType}" />`;
|
|
||||||
ogTypeTag = `<meta property="og:type" content="video" />`;
|
|
||||||
return new Handlebars.SafeString(`${basicTags} ${ogImageTag} ${ogImageTypeTag} ${ogTypeTag} ${ogVideoTag} ${ogVideoSecureUrlTag} ${ogVideoTypeTag}`);
|
|
||||||
} else {
|
|
||||||
if (contentType === 'image/gif') {
|
|
||||||
ogTypeTag = `<meta property="og:type" content="video.other" />`;
|
|
||||||
};
|
|
||||||
return new Handlebars.SafeString(`${basicTags} ${ogImageTag} ${ogImageTypeTag} ${ogTypeTag}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addTwitterCard (claim) {
|
|
||||||
const { embedUrl, directFileUrl } = createOpenGraphDataFromClaim(claim, claimDefaults.defaultTitle, claimDefaults.defaultDescription);
|
|
||||||
const basicTwitterTags = `<meta name="twitter:site" content="@spee_ch" >`;
|
|
||||||
const contentType = claim.contentType;
|
|
||||||
if (contentType === 'video/mp4') {
|
|
||||||
const twitterName = '<meta name="twitter:card" content="player" >';
|
|
||||||
const twitterPlayer = `<meta name="twitter:player" content="${embedUrl}" >`;
|
|
||||||
const twitterPlayerWidth = '<meta name="twitter:player:width" content="600" >';
|
|
||||||
const twitterTextPlayerWidth = '<meta name="twitter:text:player_width" content="600" >';
|
|
||||||
const twitterPlayerHeight = '<meta name="twitter:player:height" content="337" >';
|
|
||||||
const twitterPlayerStream = `<meta name="twitter:player:stream" content="${directFileUrl}" >`;
|
|
||||||
const twitterPlayerStreamContentType = '<meta name="twitter:player:stream:content_type" content="video/mp4" >';
|
|
||||||
return new Handlebars.SafeString(`${basicTwitterTags} ${twitterName} ${twitterPlayer} ${twitterPlayerWidth} ${twitterTextPlayerWidth} ${twitterPlayerHeight} ${twitterPlayerStream} ${twitterPlayerStreamContentType}`);
|
|
||||||
} else {
|
|
||||||
const twitterCard = '<meta name="twitter:card" content="summary_large_image" >';
|
|
||||||
return new Handlebars.SafeString(`${basicTwitterTags} ${twitterCard}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
32
helpers/renderFullPage.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
module.exports = (helmet, html, preloadedState) => {
|
||||||
|
// take the html and preloadedState and return the full page
|
||||||
|
return `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#">
|
||||||
|
<head>
|
||||||
|
<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">
|
||||||
|
<!--helmet-->
|
||||||
|
${helmet.title.toString()}
|
||||||
|
${helmet.meta.toString()}
|
||||||
|
${helmet.link.toString()}
|
||||||
|
<!--style sheets-->
|
||||||
|
<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">
|
||||||
|
<!--google font-->
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Roboto:300" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body id="main-body">
|
||||||
|
<div class="row row--tall flex-container--column">
|
||||||
|
<div id="react-app" class="row row--tall flex-container--column">${html}</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\\u003c')}
|
||||||
|
</script>
|
||||||
|
<script src="/bundle/bundle.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
};
|
|
@ -3,11 +3,9 @@ const express = require('express');
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const expressHandlebars = require('express-handlebars');
|
const expressHandlebars = require('express-handlebars');
|
||||||
const Handlebars = require('handlebars');
|
const Handlebars = require('handlebars');
|
||||||
const handlebarsHelpers = require('./helpers/handlebarsHelpers.js');
|
|
||||||
const { populateLocalsDotUser, serializeSpeechUser, deserializeSpeechUser } = require('./helpers/authHelpers.js');
|
const { populateLocalsDotUser, serializeSpeechUser, deserializeSpeechUser } = require('./helpers/authHelpers.js');
|
||||||
const config = require('./config/speechConfig.js');
|
const config = require('./config/speechConfig.js');
|
||||||
const logger = require('winston');
|
const logger = require('winston');
|
||||||
const { getDownloadDirectory } = require('./helpers/lbryApi');
|
|
||||||
const helmet = require('helmet');
|
const helmet = require('helmet');
|
||||||
const PORT = 3000; // set port
|
const PORT = 3000; // set port
|
||||||
const app = express(); // create an Express application
|
const app = express(); // create an Express application
|
||||||
|
@ -54,9 +52,8 @@ app.use(passport.session());
|
||||||
|
|
||||||
// configure handlebars & register it with express app
|
// configure handlebars & register it with express app
|
||||||
const hbs = expressHandlebars.create({
|
const hbs = expressHandlebars.create({
|
||||||
defaultLayout: 'main', // sets the default layout
|
defaultLayout: 'embed', // sets the default layout
|
||||||
handlebars : Handlebars, // includes basic handlebars for access to that library
|
handlebars : Handlebars, // includes basic handlebars for access to that library
|
||||||
helpers : handlebarsHelpers, // custom defined helpers
|
|
||||||
});
|
});
|
||||||
app.engine('handlebars', hbs.engine);
|
app.engine('handlebars', hbs.engine);
|
||||||
app.set('view engine', 'handlebars');
|
app.set('view engine', 'handlebars');
|
||||||
|
@ -67,19 +64,12 @@ app.use(populateLocalsDotUser);
|
||||||
// start the server
|
// start the server
|
||||||
db.sequelize
|
db.sequelize
|
||||||
.sync() // sync sequelize
|
.sync() // sync sequelize
|
||||||
.then(() => { // get the download directory from the daemon
|
.then(() => { // require routes
|
||||||
logger.info('Retrieving daemon download directory...');
|
|
||||||
return getDownloadDirectory();
|
|
||||||
})
|
|
||||||
.then(hostedContentPath => {
|
|
||||||
// add the hosted content folder at a static path
|
|
||||||
app.use('/media', express.static(hostedContentPath));
|
|
||||||
// require routes
|
|
||||||
require('./routes/auth-routes.js')(app);
|
require('./routes/auth-routes.js')(app);
|
||||||
require('./routes/api-routes.js')(app);
|
require('./routes/api-routes.js')(app);
|
||||||
require('./routes/page-routes.js')(app);
|
require('./routes/page-routes.js')(app);
|
||||||
require('./routes/serve-routes.js')(app);
|
require('./routes/serve-routes.js')(app);
|
||||||
require('./routes/home-routes.js')(app);
|
require('./routes/fallback-routes.js')(app);
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
return http.Server(app);
|
return http.Server(app);
|
||||||
})
|
})
|
|
@ -176,15 +176,15 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
|
||||||
this.findOne({
|
this.findOne({
|
||||||
where: {name, claimId},
|
where: {name, claimId},
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return resolve(null);
|
return resolve(null);
|
||||||
};
|
};
|
||||||
resolve(claimId);
|
resolve(claimId);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -318,15 +318,15 @@ module.exports = (sequelize, { STRING, BOOLEAN, INTEGER, TEXT, DECIMAL }) => {
|
||||||
this.findOne({
|
this.findOne({
|
||||||
where: {name, claimId},
|
where: {name, claimId},
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return resolve(null);
|
return resolve(null);
|
||||||
};
|
};
|
||||||
resolve(claimId);
|
resolve(claimId);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const fs = require('fs');
|
// const fs = require('fs');
|
||||||
const path = require('path');
|
// const path = require('path');
|
||||||
const Sequelize = require('sequelize');
|
const Sequelize = require('sequelize');
|
||||||
const basename = path.basename(module.filename);
|
// const basename = path.basename(module.filename);
|
||||||
const logger = require('winston');
|
const logger = require('winston');
|
||||||
const config = require('../config/speechConfig.js');
|
const config = require('../config/speechConfig.js');
|
||||||
const { database, username, password } = config.sql;
|
const { database, username, password } = config.sql;
|
||||||
|
@ -30,16 +30,19 @@ sequelize
|
||||||
logger.error('Sequelize was unable to connect to the database:', err);
|
logger.error('Sequelize was unable to connect to the database:', err);
|
||||||
});
|
});
|
||||||
|
|
||||||
// add each model to the db object
|
// manually add each model to the db
|
||||||
fs
|
const Certificate = require('./certificate.js');
|
||||||
.readdirSync(__dirname)
|
const Channel = require('./channel.js');
|
||||||
.filter(file => {
|
const Claim = require('./claim.js');
|
||||||
return (file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js');
|
const File = require('./file.js');
|
||||||
})
|
const Request = require('./request.js');
|
||||||
.forEach(file => {
|
const User = require('./user.js');
|
||||||
const model = sequelize['import'](path.join(__dirname, file));
|
db['Certificate'] = sequelize.import('Certificate', Certificate);
|
||||||
db[model.name] = model;
|
db['Channel'] = sequelize.import('Channel', Channel);
|
||||||
});
|
db['Claim'] = sequelize.import('Claim', Claim);
|
||||||
|
db['File'] = sequelize.import('File', File);
|
||||||
|
db['Request'] = sequelize.import('Request', Request);
|
||||||
|
db['User'] = sequelize.import('User', User);
|
||||||
|
|
||||||
// run model.association for each model in the db object that has an association
|
// run model.association for each model in the db object that has an association
|
||||||
Object.keys(db).forEach(modelName => {
|
Object.keys(db).forEach(modelName => {
|
||||||
|
|
34
package.json
|
@ -6,12 +6,14 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha --recursive",
|
"test": "mocha --recursive",
|
||||||
"test-all": "mocha --recursive",
|
"test-all": "mocha --recursive",
|
||||||
"start": "node speech.js",
|
"start": "node server.js",
|
||||||
|
"start-dev": "nodemon server.js",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"fix": "eslint . --fix",
|
"fix": "eslint . --fix",
|
||||||
"precommit": "eslint .",
|
"precommit": "eslint .",
|
||||||
"babel": "babel",
|
"babel": "babel",
|
||||||
"webpack": "webpack"
|
"build-dev": "webpack --config webpack.dev.js",
|
||||||
|
"build-prod": "webpack --config webpack.prod.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -35,18 +37,20 @@
|
||||||
"config": "^1.26.1",
|
"config": "^1.26.1",
|
||||||
"connect-multiparty": "^2.0.0",
|
"connect-multiparty": "^2.0.0",
|
||||||
"cookie-session": "^2.0.0-beta.3",
|
"cookie-session": "^2.0.0-beta.3",
|
||||||
|
"cross-fetch": "^1.1.1",
|
||||||
"express": "^4.15.2",
|
"express": "^4.15.2",
|
||||||
"express-handlebars": "^3.0.0",
|
"express-handlebars": "^3.0.0",
|
||||||
"form-data": "^2.3.1",
|
"form-data": "^2.3.1",
|
||||||
"helmet": "^3.8.1",
|
"helmet": "^3.8.1",
|
||||||
"mysql2": "^1.3.5",
|
"mysql2": "^1.3.5",
|
||||||
"nodemon": "^1.11.0",
|
"node-fetch": "^2.0.0",
|
||||||
"passport": "^0.4.0",
|
"passport": "^0.4.0",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"prop-types": "^15.6.0",
|
"prop-types": "^15.6.0",
|
||||||
"react": "^16.2.0",
|
"react": "^16.2.0",
|
||||||
"react-dom": "^16.2.0",
|
"react-dom": "^16.2.0",
|
||||||
"react-ga": "^2.4.1",
|
"react-ga": "^2.4.1",
|
||||||
|
"react-helmet": "^5.2.0",
|
||||||
"react-redux": "^5.0.6",
|
"react-redux": "^5.0.6",
|
||||||
"react-router-dom": "^4.2.2",
|
"react-router-dom": "^4.2.2",
|
||||||
"redux": "^3.7.2",
|
"redux": "^3.7.2",
|
||||||
|
@ -57,6 +61,7 @@
|
||||||
"sequelize-cli": "^3.0.0-3",
|
"sequelize-cli": "^3.0.0-3",
|
||||||
"sleep": "^5.1.1",
|
"sleep": "^5.1.1",
|
||||||
"universal-analytics": "^0.4.13",
|
"universal-analytics": "^0.4.13",
|
||||||
|
"webpack-node-externals": "^1.6.0",
|
||||||
"whatwg-fetch": "^2.0.3",
|
"whatwg-fetch": "^2.0.3",
|
||||||
"winston": "^2.3.1",
|
"winston": "^2.3.1",
|
||||||
"winston-slack-webhook": "billbitt/winston-slack-webhook"
|
"winston-slack-webhook": "billbitt/winston-slack-webhook"
|
||||||
|
@ -64,22 +69,29 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-core": "^6.26.0",
|
"babel-core": "^6.26.0",
|
||||||
"babel-loader": "^7.1.2",
|
"babel-loader": "^7.1.2",
|
||||||
|
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||||
"babel-polyfill": "^6.26.0",
|
"babel-polyfill": "^6.26.0",
|
||||||
"babel-preset-es2015": "^6.24.1",
|
"babel-preset-es2015": "^6.24.1",
|
||||||
"babel-preset-react": "^6.24.1",
|
"babel-preset-react": "^6.24.1",
|
||||||
"babel-preset-stage-2": "^6.24.1",
|
"babel-preset-stage-2": "^6.24.1",
|
||||||
|
"babel-register": "^6.26.0",
|
||||||
"chai": "^4.1.2",
|
"chai": "^4.1.2",
|
||||||
"chai-http": "^3.0.0",
|
"chai-http": "^3.0.0",
|
||||||
"eslint": "3.19.0",
|
"css-loader": "^0.28.9",
|
||||||
"eslint-config-standard": "10.2.1",
|
"eslint": "4.18.0",
|
||||||
"eslint-plugin-import": "^2.2.0",
|
"eslint-config-standard": "^10.2.1",
|
||||||
"eslint-plugin-node": "^4.2.2",
|
"eslint-config-standard-jsx": "^5.0.0",
|
||||||
"eslint-plugin-promise": "3.5.0",
|
"eslint-plugin-import": "^2.8.0",
|
||||||
"eslint-plugin-react": "6.10.3",
|
"eslint-plugin-node": "^4.2.3",
|
||||||
"eslint-plugin-standard": "3.0.1",
|
"eslint-plugin-promise": "^3.5.0",
|
||||||
|
"eslint-plugin-react": "^7.6.1",
|
||||||
|
"eslint-plugin-standard": "^3.0.1",
|
||||||
"husky": "^0.13.4",
|
"husky": "^0.13.4",
|
||||||
"mocha": "^4.0.1",
|
"mocha": "^4.0.1",
|
||||||
|
"nodemon": "^1.15.1",
|
||||||
"redux-devtools": "^3.4.1",
|
"redux-devtools": "^3.4.1",
|
||||||
"webpack": "^3.10.0"
|
"regenerator-transform": "^0.12.3",
|
||||||
|
"webpack": "^3.10.0",
|
||||||
|
"webpack-merge": "^4.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,19 +8,19 @@ function returnUserAndChannelInfo (userInstance) {
|
||||||
userInfo['id'] = userInstance.id;
|
userInfo['id'] = userInstance.id;
|
||||||
userInfo['userName'] = userInstance.userName;
|
userInfo['userName'] = userInstance.userName;
|
||||||
userInstance
|
userInstance
|
||||||
.getChannel()
|
.getChannel()
|
||||||
.then(({channelName, channelClaimId}) => {
|
.then(({channelName, channelClaimId}) => {
|
||||||
userInfo['channelName'] = channelName;
|
userInfo['channelName'] = channelName;
|
||||||
userInfo['channelClaimId'] = channelClaimId;
|
userInfo['channelClaimId'] = channelClaimId;
|
||||||
return db.Certificate.getShortChannelIdFromLongChannelId(channelClaimId, channelName);
|
return db.Certificate.getShortChannelIdFromLongChannelId(channelClaimId, channelName);
|
||||||
})
|
})
|
||||||
.then(shortChannelId => {
|
.then(shortChannelId => {
|
||||||
userInfo['shortChannelId'] = shortChannelId;
|
userInfo['shortChannelId'] = shortChannelId;
|
||||||
resolve(userInfo);
|
resolve(userInfo);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,34 +32,34 @@ module.exports = new PassportLocalStrategy(
|
||||||
(username, password, done) => {
|
(username, password, done) => {
|
||||||
logger.debug('logging user in');
|
logger.debug('logging user in');
|
||||||
return db
|
return db
|
||||||
.User
|
.User
|
||||||
.findOne({where: {userName: username}})
|
.findOne({where: {userName: username}})
|
||||||
.then(user => {
|
.then(user => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
// logger.debug('no user found');
|
// logger.debug('no user found');
|
||||||
|
return done(null, false, {message: 'Incorrect username or password'});
|
||||||
|
}
|
||||||
|
user.comparePassword(password, (passwordErr, isMatch) => {
|
||||||
|
if (passwordErr) {
|
||||||
|
logger.error('passwordErr:', passwordErr);
|
||||||
|
return done(null, false, {message: passwordErr});
|
||||||
|
}
|
||||||
|
if (!isMatch) {
|
||||||
|
// logger.debug('incorrect password');
|
||||||
return done(null, false, {message: 'Incorrect username or password'});
|
return done(null, false, {message: 'Incorrect username or password'});
|
||||||
}
|
}
|
||||||
user.comparePassword(password, (passwordErr, isMatch) => {
|
logger.debug('Password was a match, returning User');
|
||||||
if (passwordErr) {
|
return returnUserAndChannelInfo(user)
|
||||||
logger.error('passwordErr:', passwordErr);
|
.then((userInfo) => {
|
||||||
return done(null, false, {message: passwordErr});
|
return done(null, userInfo);
|
||||||
}
|
})
|
||||||
if (!isMatch) {
|
.catch(error => {
|
||||||
// logger.debug('incorrect password');
|
return done(error);
|
||||||
return done(null, false, {message: 'Incorrect username or password'});
|
});
|
||||||
}
|
|
||||||
logger.debug('Password was a match, returning User');
|
|
||||||
return returnUserAndChannelInfo(user)
|
|
||||||
.then((userInfo) => {
|
|
||||||
return done(null, userInfo);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
return done(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
return done(error);
|
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
return done(error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
25176
public/bundle/bundle.js
|
@ -3,7 +3,7 @@ import * as actions from 'constants/show_action_types';
|
||||||
import { CHANNEL, ASSET_LITE, ASSET_DETAILS } from 'constants/show_request_types';
|
import { CHANNEL, ASSET_LITE, ASSET_DETAILS } from 'constants/show_request_types';
|
||||||
|
|
||||||
// basic request parsing
|
// basic request parsing
|
||||||
export function handleShowPageUri (params) {
|
export function onHandleShowPageUri (params) {
|
||||||
return {
|
return {
|
||||||
type: actions.HANDLE_SHOW_URI,
|
type: actions.HANDLE_SHOW_URI,
|
||||||
data: params,
|
data: params,
|
||||||
|
@ -12,7 +12,7 @@ export function handleShowPageUri (params) {
|
||||||
|
|
||||||
export function onRequestError (error) {
|
export function onRequestError (error) {
|
||||||
return {
|
return {
|
||||||
type: actions.REQUEST_UPDATE_ERROR,
|
type: actions.REQUEST_ERROR,
|
||||||
data: error,
|
data: error,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -46,6 +46,16 @@ export function onNewAssetRequest (name, id, channelName, channelId, extension)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function onRequestUpdate (requestType, requestId) {
|
||||||
|
return {
|
||||||
|
type: actions.REQUEST_UPDATE,
|
||||||
|
data: {
|
||||||
|
requestType,
|
||||||
|
requestId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export function addRequestToRequestList (id, error, key) {
|
export function addRequestToRequestList (id, error, key) {
|
||||||
return {
|
return {
|
||||||
type: actions.REQUEST_LIST_ADD,
|
type: actions.REQUEST_LIST_ADD,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import Request from 'utils/request';
|
import Request from 'utils/request';
|
||||||
|
const { site: { host } } = require('../../config/speechConfig.js');
|
||||||
|
|
||||||
export function getLongClaimId (name, modifier) {
|
export function getLongClaimId (name, modifier) {
|
||||||
// console.log('getting long claim id for asset:', name, modifier);
|
// console.log('getting long claim id for asset:', name, modifier);
|
||||||
|
@ -15,25 +16,23 @@ export function getLongClaimId (name, modifier) {
|
||||||
body['claimName'] = name;
|
body['claimName'] = name;
|
||||||
const params = {
|
const params = {
|
||||||
method : 'POST',
|
method : 'POST',
|
||||||
headers: new Headers({
|
headers: { 'Content-Type': 'application/json' },
|
||||||
'Content-Type': 'application/json',
|
body : JSON.stringify(body),
|
||||||
}),
|
};
|
||||||
body: JSON.stringify(body),
|
|
||||||
}
|
|
||||||
// create url
|
// create url
|
||||||
const url = `/api/claim/long-id`;
|
const url = `${host}/api/claim/long-id`;
|
||||||
// return the request promise
|
// return the request promise
|
||||||
return Request(url, params);
|
return Request(url, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getShortId (name, claimId) {
|
export function getShortId (name, claimId) {
|
||||||
// console.log('getting short id for asset:', name, claimId);
|
// console.log('getting short id for asset:', name, claimId);
|
||||||
const url = `/api/claim/short-id/${claimId}/${name}`;
|
const url = `${host}/api/claim/short-id/${claimId}/${name}`;
|
||||||
return Request(url);
|
return Request(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getClaimData (name, claimId) {
|
export function getClaimData (name, claimId) {
|
||||||
// console.log('getting claim data for asset:', name, claimId);
|
// console.log('getting claim data for asset:', name, claimId);
|
||||||
const url = `/api/claim/data/${name}/${claimId}`;
|
const url = `${host}/api/claim/data/${name}/${claimId}`;
|
||||||
return Request(url);
|
return Request(url);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import Request from 'utils/request';
|
import Request from 'utils/request';
|
||||||
import request from '../utils/request';
|
const { site: { host } } = require('../../config/speechConfig.js');
|
||||||
|
|
||||||
export function getChannelData (name, id) {
|
export function getChannelData (name, id) {
|
||||||
console.log('getting channel data for channel:', name, id);
|
console.log('getting channel data for channel:', name, id);
|
||||||
if (!id) id = 'none';
|
if (!id) id = 'none';
|
||||||
const url = `/api/channel/data/${name}/${id}`;
|
const url = `${host}/api/channel/data/${name}/${id}`;
|
||||||
return request(url);
|
return Request(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getChannelClaims (name, longId, page) {
|
export function getChannelClaims (name, longId, page) {
|
||||||
console.log('getting channel claims for channel:', name, longId);
|
console.log('getting channel claims for channel:', name, longId);
|
||||||
if (!page) page = 1;
|
if (!page) page = 1;
|
||||||
const url = `/api/channel/claims/${name}/${longId}/${page}`;
|
const url = `${host}/api/channel/claims/${name}/${longId}/${page}`;
|
||||||
return Request(url);
|
return Request(url);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import Request from 'utils/request';
|
import Request from 'utils/request';
|
||||||
|
const { site: { host } } = require('../../config/speechConfig.js');
|
||||||
|
|
||||||
export function checkFileAvailability (name, claimId) {
|
export function checkFileAvailability (name, claimId) {
|
||||||
const url = `/api/file/availability/${name}/${claimId}`;
|
const url = `${host}/api/file/availability/${name}/${claimId}`;
|
||||||
return Request(url);
|
return Request(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function triggerClaimGet (name, claimId) {
|
export function triggerClaimGet (name, claimId) {
|
||||||
const url = `/api/claim/get/${name}/${claimId}`;
|
const url = `${host}/api/claim/get/${name}/${claimId}`;
|
||||||
return Request(url);
|
return Request(url);
|
||||||
}
|
}
|
||||||
|
|
22
react/app.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Route, Switch } from 'react-router-dom';
|
||||||
|
import HomePage from 'components/HomePage';
|
||||||
|
import AboutPage from 'components/AboutPage';
|
||||||
|
import LoginPage from 'containers/LoginPage';
|
||||||
|
import ShowPage from 'containers/ShowPage';
|
||||||
|
import FourOhFourPage from 'components/FourOhFourPage';
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
return (
|
||||||
|
<Switch>
|
||||||
|
<Route exact path='/' component={HomePage} />
|
||||||
|
<Route exact path='/about' component={AboutPage} />
|
||||||
|
<Route exact path='/login' component={LoginPage} />
|
||||||
|
<Route exact path='/:identifier/:claim' component={ShowPage} />
|
||||||
|
<Route exact path='/:claim' component={ShowPage} />
|
||||||
|
<Route component={FourOhFourPage} />
|
||||||
|
</Switch>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
45
react/client.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
import React from 'react';
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
import { hydrate } from 'react-dom';
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
import { Provider } from 'react-redux';
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
import { createStore, applyMiddleware, compose } from 'redux';
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
import Reducer from 'reducers';
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
import createSagaMiddleware from 'redux-saga';
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
import rootSaga from 'sagas';
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
import GAListener from 'components/GAListener';
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
import App from './app';
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
// get the state from a global variable injected into the server-generated HTML
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
const preloadedState = window.__PRELOADED_STATE__ || null;
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
// Allow the passed state to be garbage-collected
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
delete window.__PRELOADED_STATE__;
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
// create and apply middleware
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
const sagaMiddleware = createSagaMiddleware();
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
const middleware = applyMiddleware(sagaMiddleware);
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
const reduxMiddleware = window.__REDUX_DEVTOOLS_EXTENSION__ ? compose(middleware, window.__REDUX_DEVTOOLS_EXTENSION__()) : middleware;
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
// create teh store
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
let store;
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
if (preloadedState) {
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
store = createStore(Reducer, preloadedState, reduxMiddleware);
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
} else {
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
store = createStore(Reducer, reduxMiddleware);
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
}
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
// run the saga middlweare
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
sagaMiddleware.run(rootSaga);
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
// render the app
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
hydrate(
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
<Provider store={store}>
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
<BrowserRouter>
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
<GAListener>
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
<App />
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
</GAListener>
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
</BrowserRouter>
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
</Provider>,
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
document.getElementById('react-app')
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|||||||
|
);
|
||||||
Probably don't need these Probably don't need these `console.log`s here.
|
|
@ -1,27 +1,29 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import NavBar from 'containers/NavBar';
|
import NavBar from 'containers/NavBar';
|
||||||
|
import SEO from 'components/SEO';
|
||||||
|
|
||||||
class AboutPage extends React.Component {
|
class AboutPage extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<NavBar/>
|
<SEO pageTitle={'About'} pageUri={'about'} />
|
||||||
<div className="row row--padded">
|
<NavBar />
|
||||||
<div className="column column--5 column--med-10 align-content-top">
|
<div className='row row--padded'>
|
||||||
<div className="column column--8 column--med-10">
|
<div className='column column--5 column--med-10 align-content-top'>
|
||||||
<p className="pull-quote">Spee.ch is an open-source project. Please contribute to the existing site, or fork it and make your own.</p>
|
<div className='column column--8 column--med-10'>
|
||||||
<p><a className="link--primary" target="_blank" href="https://twitter.com/spee_ch">TWITTER</a></p>
|
<p className='pull-quote'>Spee.ch is an open-source project. Please contribute to the existing site, or fork it and make your own.</p>
|
||||||
<p><a className="link--primary" target="_blank" href="https://github.com/lbryio/spee.ch">GITHUB</a></p>
|
<p><a className='link--primary' target='_blank' href='https://twitter.com/spee_ch'>TWITTER</a></p>
|
||||||
<p><a className="link--primary" target="_blank" href="https://discord.gg/YjYbwhS">DISCORD CHANNEL</a></p>
|
<p><a className='link--primary' target='_blank' href='https://github.com/lbryio/spee.ch'>GITHUB</a></p>
|
||||||
<p><a className="link--primary" target="_blank" href="https://github.com/lbryio/spee.ch/blob/master/README.md">DOCUMENTATION</a></p>
|
<p><a className='link--primary' target='_blank' href='https://discord.gg/YjYbwhS'>DISCORD CHANNEL</a></p>
|
||||||
|
<p><a className='link--primary' target='_blank' href='https://github.com/lbryio/spee.ch/blob/master/README.md'>DOCUMENTATION</a></p>
|
||||||
</div>
|
</div>
|
||||||
</div><div className="column column--5 column--med-10 align-content-top">
|
</div><div className='column column--5 column--med-10 align-content-top'>
|
||||||
<div className="column column--8 column--med-10">
|
<div className='column column--8 column--med-10'>
|
||||||
<p>Spee.ch is a media-hosting site that reads from and publishes content to the <a className="link--primary" href="https://lbry.io">LBRY</a> blockchain.</p>
|
<p>Spee.ch is a media-hosting site that reads from and publishes content to the <a className='link--primary' href='https://lbry.io'>LBRY</a> blockchain.</p>
|
||||||
<p>Spee.ch is a hosting service, but with the added benefit that it stores your content on a decentralized network of computers -- the LBRY network. This means that your images are stored in multiple locations without a single point of failure.</p>
|
<p>Spee.ch is a hosting service, but with the added benefit that it stores your content on a decentralized network of computers -- the LBRY network. This means that your images are stored in multiple locations without a single point of failure.</p>
|
||||||
<h3>Contribute</h3>
|
<h3>Contribute</h3>
|
||||||
<p>If you have an idea for your own spee.ch-like site on top of LBRY, fork our <a className="link--primary" href="https://github.com/lbryio/spee.ch">github repo</a> and go to town!</p>
|
<p>If you have an idea for your own spee.ch-like site on top of LBRY, fork our <a className='link--primary' href='https://github.com/lbryio/spee.ch'>github repo</a> and go to town!</p>
|
||||||
<p>If you want to improve spee.ch, join our <a className="link--primary" href="https://discord.gg/YjYbwhS">discord channel</a> or solve one of our <a className="link--primary" href="https://github.com/lbryio/spee.ch/issues">github issues</a>.</p>
|
<p>If you want to improve spee.ch, join our <a className='link--primary' href='https://discord.gg/YjYbwhS'>discord channel</a> or solve one of our <a className='link--primary' href='https://github.com/lbryio/spee.ch/issues'>github issues</a>.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,7 +8,6 @@ class AssetDisplay extends React.Component {
|
||||||
this.props.onFileRequest(name, claimId);
|
this.props.onFileRequest(name, claimId);
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
console.log('rendering assetdisplay', this.props);
|
|
||||||
const { status, error, asset: { claimData: { name, claimId, contentType, fileExt, thumbnail } } } = this.props;
|
const { status, error, asset: { claimData: { name, claimId, contentType, fileExt, thumbnail } } } = this.props;
|
||||||
return (
|
return (
|
||||||
<div id="asset-display-component">
|
<div id="asset-display-component">
|
||||||
|
|
|
@ -3,9 +3,9 @@ import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
const AssetPreview = ({ name, claimId, fileExt, contentType }) => {
|
const AssetPreview = ({ name, claimId, fileExt, contentType }) => {
|
||||||
const directSourceLink = `${claimId}/${name}.${fileExt}`;
|
const directSourceLink = `${claimId}/${name}.${fileExt}`;
|
||||||
const showUrlLink = `${claimId}/${name}`;
|
const showUrlLink = `/${claimId}/${name}`;
|
||||||
return (
|
return (
|
||||||
<div className="asset-holder">
|
<div className='asset-holder'>
|
||||||
<Link to={showUrlLink} >
|
<Link to={showUrlLink} >
|
||||||
{(() => {
|
{(() => {
|
||||||
switch (contentType) {
|
switch (contentType) {
|
||||||
|
@ -13,16 +13,16 @@ const AssetPreview = ({ name, claimId, fileExt, contentType }) => {
|
||||||
case 'image/jpg':
|
case 'image/jpg':
|
||||||
case 'image/png':
|
case 'image/png':
|
||||||
return (
|
return (
|
||||||
<img className={'asset-preview'} src={directSourceLink} alt={name}/>
|
<img className={'asset-preview'} src={directSourceLink} alt={name} />
|
||||||
);
|
);
|
||||||
case 'image/gif':
|
case 'image/gif':
|
||||||
return (
|
return (
|
||||||
<img className={'asset-preview'} src={directSourceLink} alt={name}/>
|
<img className={'asset-preview'} src={directSourceLink} alt={name} />
|
||||||
);
|
);
|
||||||
case 'video/mp4':
|
case 'video/mp4':
|
||||||
return (
|
return (
|
||||||
<video className={'asset-preview'}>
|
<video className={'asset-preview'}>
|
||||||
<source src={directSourceLink} type={contentType}/>
|
<source src={directSourceLink} type={contentType} />
|
||||||
</video>
|
</video>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -7,9 +7,9 @@ class ErrorPage extends React.Component {
|
||||||
const { error } = this.props;
|
const { error } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<NavBar/>
|
<NavBar />
|
||||||
<div className="row row--padded">
|
<div className='row row--padded'>
|
||||||
<p>{error}</p>
|
<p>{error}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -18,6 +18,6 @@ class ErrorPage extends React.Component {
|
||||||
|
|
||||||
ErrorPage.propTypes = {
|
ErrorPage.propTypes = {
|
||||||
error: PropTypes.string.isRequired,
|
error: PropTypes.string.isRequired,
|
||||||
}
|
};
|
||||||
|
|
||||||
export default ErrorPage;
|
export default ErrorPage;
|
||||||
|
|
|
@ -1,14 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import NavBar from 'containers/NavBar';
|
import NavBar from 'containers/NavBar';
|
||||||
|
import Helmet from 'react-helmet';
|
||||||
|
const { site: { title, host } } = require('../../../config/speechConfig.js');
|
||||||
|
|
||||||
class FourOhForPage extends React.Component {
|
class FourOhForPage extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<NavBar/>
|
<Helmet>
|
||||||
<div className="row row--padded">
|
<title>{title} - 404</title>
|
||||||
<h2>404</h2>
|
<link rel='canonical' href={`${host}/404`} />
|
||||||
<p>That page does not exist</p>
|
</Helmet>
|
||||||
|
<NavBar />
|
||||||
|
<div className='row row--padded'>
|
||||||
|
<h2>404</h2>
|
||||||
|
<p>That page does not exist</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import SEO from 'components/SEO';
|
||||||
import NavBar from 'containers/NavBar';
|
import NavBar from 'containers/NavBar';
|
||||||
import PublishTool from 'containers/PublishTool';
|
import PublishTool from 'containers/PublishTool';
|
||||||
|
|
||||||
class PublishPage extends React.Component {
|
class HomePage extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<div className={'row row--tall flex-container--column'}>
|
<div className={'row row--tall flex-container--column'}>
|
||||||
<NavBar/>
|
<SEO />
|
||||||
|
<NavBar />
|
||||||
<div className={'row row--tall row--padded flex-container--column'}>
|
<div className={'row row--tall row--padded flex-container--column'}>
|
||||||
<PublishTool/>
|
<PublishTool />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PublishPage;
|
export default HomePage;
|
32
react/components/SEO/index.jsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Helmet from 'react-helmet';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { createPageTitle } from 'utils/pageTitle';
|
||||||
|
import { createMetaTags } from 'utils/metaTags';
|
||||||
|
import { createCanonicalLink } from 'utils/canonicalLink';
|
||||||
|
|
||||||
|
class SEO extends React.Component {
|
||||||
|
render () {
|
||||||
|
let { pageTitle, asset, channel, pageUri } = this.props;
|
||||||
|
pageTitle = createPageTitle(pageTitle);
|
||||||
|
const metaTags = createMetaTags(asset, channel);
|
||||||
|
const canonicalLink = createCanonicalLink(asset, channel, pageUri);
|
||||||
|
return (
|
||||||
|
<Helmet
|
||||||
|
title={pageTitle}
|
||||||
|
meta={metaTags}
|
||||||
|
link={[{rel: 'canonical', href: canonicalLink}]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SEO.propTypes = {
|
||||||
|
pageTitle: PropTypes.string,
|
||||||
|
pageUri : PropTypes.string,
|
||||||
|
channel : PropTypes.object,
|
||||||
|
asset : PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SEO;
|
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import SEO from 'components/SEO';
|
||||||
import NavBar from 'containers/NavBar';
|
import NavBar from 'containers/NavBar';
|
||||||
import ErrorPage from 'components/ErrorPage';
|
import ErrorPage from 'components/ErrorPage';
|
||||||
import AssetTitle from 'components/AssetTitle';
|
import AssetTitle from 'components/AssetTitle';
|
||||||
|
@ -9,29 +10,31 @@ class ShowAssetDetails extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
const { asset } = this.props;
|
const { asset } = this.props;
|
||||||
if (asset) {
|
if (asset) {
|
||||||
|
const { name } = asset.claimData;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<NavBar/>
|
<SEO pageTitle={`${name} - details`} asset={asset} />
|
||||||
<div className="row row--tall row--padded">
|
<NavBar />
|
||||||
<div className="column column--10">
|
<div className='row row--tall row--padded'>
|
||||||
|
<div className='column column--10'>
|
||||||
<AssetTitle />
|
<AssetTitle />
|
||||||
</div>
|
</div>
|
||||||
<div className="column column--5 column--sml-10 align-content-top">
|
<div className='column column--5 column--sml-10 align-content-top'>
|
||||||
<div className="row row--padded">
|
<div className='row row--padded'>
|
||||||
<AssetDisplay />
|
<AssetDisplay />
|
||||||
</div>
|
</div>
|
||||||
</div><div className="column column--5 column--sml-10 align-content-top">
|
</div><div className='column column--5 column--sml-10 align-content-top'>
|
||||||
<div className="row row--padded">
|
<div className='row row--padded'>
|
||||||
<AssetInfo />
|
<AssetInfo />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<ErrorPage error={'loading asset data...'}/>
|
<ErrorPage error={'loading asset data...'} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,19 +1,26 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import SEO from 'components/SEO';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import AssetDisplay from 'components/AssetDisplay';
|
import AssetDisplay from 'components/AssetDisplay';
|
||||||
|
|
||||||
class ShowLite extends React.Component {
|
class ShowLite extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
const { asset } = this.props;
|
const { asset } = this.props;
|
||||||
return (
|
if (asset) {
|
||||||
<div className="row row--tall flex-container--column flex-container--center-center">
|
const { name, claimId } = asset.claimData;
|
||||||
{ (asset) &&
|
return (
|
||||||
<div>
|
<div className='row row--tall flex-container--column flex-container--center-center'>
|
||||||
<AssetDisplay />
|
<SEO pageTitle={name} asset={asset} />
|
||||||
<Link id="asset-boilerpate" className="link--primary fine-print" to={`/${asset.claimId}/${asset.name}`}>hosted via Spee.ch</Link>
|
<div>
|
||||||
|
<AssetDisplay />
|
||||||
|
<Link id='asset-boilerpate' className='link--primary fine-print' to={`/${claimId}/${name}`}>hosted
|
||||||
|
via Spee.ch</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
);
|
||||||
</div>
|
}
|
||||||
|
return (
|
||||||
|
<p>loading asset data...</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import SEO from 'components/SEO';
|
||||||
import ErrorPage from 'components/ErrorPage';
|
import ErrorPage from 'components/ErrorPage';
|
||||||
import NavBar from 'containers/NavBar';
|
import NavBar from 'containers/NavBar';
|
||||||
import ChannelClaimsDisplay from 'containers/ChannelClaimsDisplay';
|
import ChannelClaimsDisplay from 'containers/ChannelClaimsDisplay';
|
||||||
|
@ -10,14 +11,15 @@ class ShowChannel extends React.Component {
|
||||||
const { name, longId, shortId } = channel;
|
const { name, longId, shortId } = channel;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<NavBar/>
|
<SEO pageTitle={name} channel={channel} />
|
||||||
<div className="row row--tall row--padded">
|
<NavBar />
|
||||||
<div className="column column--10">
|
<div className='row row--tall row--padded'>
|
||||||
<h2>channel name: {name || 'loading...'}</h2>
|
<div className='column column--10'>
|
||||||
<p className={'fine-print'}>full channel id: {longId || 'loading...'}</p>
|
<h2>channel name: {name}</h2>
|
||||||
<p className={'fine-print'}>short channel id: {shortId || 'loading...'}</p>
|
<p className={'fine-print'}>full channel id: {longId}</p>
|
||||||
|
<p className={'fine-print'}>short channel id: {shortId}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="column column--10">
|
<div className='column column--10'>
|
||||||
<ChannelClaimsDisplay />
|
<ChannelClaimsDisplay />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,7 +27,7 @@ class ShowChannel extends React.Component {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<ErrorPage error={'loading channel data...'}/>
|
<ErrorPage error={'loading channel data...'} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// request actions
|
// request actions
|
||||||
export const HANDLE_SHOW_URI = 'HANDLE_SHOW_URI';
|
export const HANDLE_SHOW_URI = 'HANDLE_SHOW_URI';
|
||||||
export const REQUEST_UPDATE_ERROR = 'REQUEST_UPDATE_ERROR';
|
export const REQUEST_ERROR = 'REQUEST_ERROR';
|
||||||
|
export const REQUEST_UPDATE = 'REQUEST_UPDATE';
|
||||||
export const ASSET_REQUEST_NEW = 'ASSET_REQUEST_NEW';
|
export const ASSET_REQUEST_NEW = 'ASSET_REQUEST_NEW';
|
||||||
export const CHANNEL_REQUEST_NEW = 'CHANNEL_REQUEST_NEW';
|
export const CHANNEL_REQUEST_NEW = 'CHANNEL_REQUEST_NEW';
|
||||||
export const REQUEST_LIST_ADD = 'REQUEST_LIST_ADD';
|
export const REQUEST_LIST_ADD = 'REQUEST_LIST_ADD';
|
||||||
|
|
|
@ -22,6 +22,6 @@ const mapDispatchToProps = dispatch => {
|
||||||
dispatch(updateSelectedChannel(value));
|
dispatch(updateSelectedChannel(value));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(View);
|
export default connect(mapStateToProps, mapDispatchToProps)(View);
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
|
import SEO from 'components/SEO';
|
||||||
import NavBar from 'containers/NavBar';
|
import NavBar from 'containers/NavBar';
|
||||||
import ChannelLoginForm from 'containers/ChannelLoginForm';
|
import ChannelLoginForm from 'containers/ChannelLoginForm';
|
||||||
import ChannelCreateForm from 'containers/ChannelCreateForm';
|
import ChannelCreateForm from 'containers/ChannelCreateForm';
|
||||||
|
|
||||||
class PublishPage extends React.Component {
|
class LoginPage extends React.Component {
|
||||||
componentWillReceiveProps (newProps) {
|
componentWillReceiveProps (newProps) {
|
||||||
// re-route the user to the homepage if the user is logged in
|
// re-route the user to the homepage if the user is logged in
|
||||||
if (newProps.loggedInChannelName !== this.props.loggedInChannelName) {
|
if (newProps.loggedInChannelName !== this.props.loggedInChannelName) {
|
||||||
|
@ -15,24 +16,25 @@ class PublishPage extends React.Component {
|
||||||
render () {
|
render () {
|
||||||
You can move all of these utility functions into the You can move all of these utility functions into the `SEO` component. Just pass in the minimum values needed and create the actual title/link/tags inside the component.
Then you don't need to add the same three lines to every page component. Then in the component you can check if
Then you don't need to add the same three lines to every page component.
`<SEO title="Login" link="login" />`
or
`<SEO title="Channel" link="channel" channel={channel} />`
Then in the component you can check if `channel` or `asset` was passed in and build the meta tags accordingly.
```
if (!props.asset || !props.channel) {
metaTags = createBasicMetaTags();
}
```
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<NavBar/>
|
<SEO pageTitle={'Login'} pageUri={'login'} />
|
||||||
<div className="row row--padded">
|
<NavBar />
|
||||||
<div className="column column--5 column--med-10 align-content-top">
|
<div className='row row--padded'>
|
||||||
<div className="column column--8 column--med-10">
|
<div className='column column--5 column--med-10 align-content-top'>
|
||||||
<p>Channels allow you to publish and group content under an identity. You can create a channel for yourself, or share one with like-minded friends. You can create 1 channel, or 100, so whether you're <a className="link--primary" target="_blank" href="/@catalonia2017:43dcf47163caa21d8404d9fe9b30f78ef3e146a8">documenting important events</a>, or making a public repository for <a className="link--primary" target="_blank" href="/@catGifs">cat gifs</a> (password: '1234'), try creating a channel for it!</p>
|
<div className='column column--8 column--med-10'>
|
||||||
|
<p>Channels allow you to publish and group content under an identity. You can create a channel for yourself, or share one with like-minded friends. You can create 1 channel, or 100, so whether you're <a className='link--primary' target='_blank' href='/@catalonia2017:43dcf47163caa21d8404d9fe9b30f78ef3e146a8'>documenting important events</a>, or making a public repository for <a className='link--primary' target='_blank' href='/@catGifs'>cat gifs</a> (password: '1234'), try creating a channel for it!</p>
|
||||||
|
</div>
|
||||||
|
</div><div className='column column--5 column--med-10 align-content-top'>
|
||||||
|
<div className='column column--8 column--med-10'>
|
||||||
|
<h3 className='h3--no-bottom'>Log in to an existing channel:</h3>
|
||||||
|
<ChannelLoginForm />
|
||||||
|
<h3 className='h3--no-bottom'>Create a brand new channel:</h3>
|
||||||
|
<ChannelCreateForm />
|
||||||
</div>
|
</div>
|
||||||
</div><div className="column column--5 column--med-10 align-content-top">
|
|
||||||
<div className="column column--8 column--med-10">
|
|
||||||
<h3 className="h3--no-bottom">Log in to an existing channel:</h3>
|
|
||||||
<ChannelLoginForm />
|
|
||||||
<h3 className="h3--no-bottom">Create a brand new channel:</h3>
|
|
||||||
<ChannelCreateForm />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withRouter(PublishPage);
|
export default withRouter(LoginPage);
|
||||||
|
|
|
@ -14,6 +14,6 @@ const mapDispatchToProps = dispatch => {
|
||||||
dispatch(updateMetadata(name, value));
|
dispatch(updateMetadata(name, value));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(View);
|
export default connect(mapStateToProps, mapDispatchToProps)(View);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { handleShowPageUri } from 'actions/show';
|
import { onHandleShowPageUri } from 'actions/show';
|
||||||
import View from './view';
|
import View from './view';
|
||||||
|
|
||||||
const mapStateToProps = ({ show }) => {
|
const mapStateToProps = ({ show }) => {
|
||||||
|
@ -10,7 +10,7 @@ const mapStateToProps = ({ show }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
handleShowPageUri,
|
onHandleShowPageUri,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(View);
|
export default connect(mapStateToProps, mapDispatchToProps)(View);
|
||||||
|
|
|
@ -8,18 +8,18 @@ import { CHANNEL, ASSET_LITE, ASSET_DETAILS } from 'constants/show_request_types
|
||||||
|
|
||||||
class ShowPage extends React.Component {
|
class ShowPage extends React.Component {
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.props.handleShowPageUri(this.props.match.params);
|
this.props.onHandleShowPageUri(this.props.match.params);
|
||||||
}
|
}
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.match.params !== this.props.match.params) {
|
if (nextProps.match.params !== this.props.match.params) {
|
||||||
this.props.handleShowPageUri(nextProps.match.params);
|
this.props.onHandleShowPageUri(nextProps.match.params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
const { error, requestType } = this.props;
|
const { error, requestType } = this.props;
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<ErrorPage error={error}/>
|
<ErrorPage error={error} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
switch (requestType) {
|
switch (requestType) {
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { render } from 'react-dom';
|
|
||||||
import { createStore, applyMiddleware, compose } from 'redux';
|
|
||||||
import Reducer from 'reducers';
|
|
||||||
import createSagaMiddleware from 'redux-saga';
|
|
||||||
import rootSaga from 'sagas';
|
|
||||||
import Root from './root';
|
|
||||||
|
|
||||||
const sagaMiddleware = createSagaMiddleware();
|
|
||||||
const middleware = applyMiddleware(sagaMiddleware);
|
|
||||||
|
|
||||||
const enhancer = window.__REDUX_DEVTOOLS_EXTENSION__ ? compose(middleware, window.__REDUX_DEVTOOLS_EXTENSION__()) : middleware;
|
|
||||||
|
|
||||||
let store = createStore(
|
|
||||||
Reducer,
|
|
||||||
enhancer,
|
|
||||||
);
|
|
||||||
|
|
||||||
sagaMiddleware.run(rootSaga);
|
|
||||||
|
|
||||||
render(
|
|
||||||
<Root store={store} />,
|
|
||||||
document.getElementById('react-app')
|
|
||||||
);
|
|
|
@ -19,14 +19,13 @@ const initialState = {
|
||||||
export default function (state = initialState, action) {
|
export default function (state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
// handle request
|
// handle request
|
||||||
case actions.REQUEST_UPDATE_ERROR:
|
case actions.REQUEST_ERROR:
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
request: Object.assign({}, state.request, {
|
request: Object.assign({}, state.request, {
|
||||||
error: action.data,
|
error: action.data,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
case actions.CHANNEL_REQUEST_NEW:
|
case actions.REQUEST_UPDATE:
|
||||||
case actions.ASSET_REQUEST_NEW:
|
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
request: Object.assign({}, state.request, {
|
request: Object.assign({}, state.request, {
|
||||||
type: action.data.requestType,
|
type: action.data.requestType,
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
|
||||||
|
|
||||||
import GAListener from 'components/GAListener';
|
|
||||||
import PublishPage from 'components/PublishPage';
|
|
||||||
import AboutPage from 'components/AboutPage';
|
|
||||||
import LoginPage from 'containers/LoginPage';
|
|
||||||
import ShowPage from 'containers/ShowPage';
|
|
||||||
import FourOhFourPage from 'components/FourOhFourPage';
|
|
||||||
|
|
||||||
const Root = ({ store }) => (
|
|
||||||
<Provider store={store}>
|
|
||||||
<BrowserRouter>
|
|
||||||
<GAListener>
|
|
||||||
<Switch>
|
|
||||||
<Route exact path="/" component={PublishPage} />
|
|
||||||
<Route exact path="/about" component={AboutPage} />
|
|
||||||
<Route exact path="/login" component={LoginPage} />
|
|
||||||
<Route exact path="/:identifier/:claim" component={ShowPage} />
|
|
||||||
<Route exact path="/:claim" component={ShowPage} />
|
|
||||||
<Route component={FourOhFourPage} />
|
|
||||||
</Switch>
|
|
||||||
</GAListener>
|
|
||||||
</BrowserRouter>
|
|
||||||
</Provider>
|
|
||||||
);
|
|
||||||
|
|
||||||
Root.propTypes = {
|
|
||||||
store: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Root;
|
|
|
@ -4,7 +4,7 @@ import { updateFileAvailability, updateDisplayAssetError } from 'actions/show';
|
||||||
import { UNAVAILABLE, AVAILABLE } from 'constants/asset_display_states';
|
import { UNAVAILABLE, AVAILABLE } from 'constants/asset_display_states';
|
||||||
import { checkFileAvailability, triggerClaimGet } from 'api/fileApi';
|
import { checkFileAvailability, triggerClaimGet } from 'api/fileApi';
|
||||||
|
|
||||||
function* retrieveFile (action) {
|
function * retrieveFile (action) {
|
||||||
const name = action.data.name;
|
const name = action.data.name;
|
||||||
const claimId = action.data.claimId;
|
const claimId = action.data.claimId;
|
||||||
// see if the file is available
|
// see if the file is available
|
||||||
|
@ -28,6 +28,6 @@ function* retrieveFile (action) {
|
||||||
yield put(updateFileAvailability(AVAILABLE));
|
yield put(updateFileAvailability(AVAILABLE));
|
||||||
};
|
};
|
||||||
|
|
||||||
export function* watchFileIsRequested () {
|
export function * watchFileIsRequested () {
|
||||||
yield takeLatest(actions.FILE_REQUESTED, retrieveFile);
|
yield takeLatest(actions.FILE_REQUESTED, retrieveFile);
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { watchNewAssetRequest } from './show_asset';
|
||||||
import { watchNewChannelRequest, watchUpdateChannelClaims } from './show_channel';
|
import { watchNewChannelRequest, watchUpdateChannelClaims } from './show_channel';
|
||||||
import { watchFileIsRequested } from './file';
|
import { watchFileIsRequested } from './file';
|
||||||
|
|
||||||
export default function* rootSaga () {
|
export default function * rootSaga () {
|
||||||
yield all([
|
yield all([
|
||||||
watchHandleShowPageUri(),
|
watchHandleShowPageUri(),
|
||||||
watchNewAssetRequest(),
|
watchNewAssetRequest(),
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import { call, put, select, takeLatest } from 'redux-saga/effects';
|
import { call, put, select, takeLatest } from 'redux-saga/effects';
|
||||||
import * as actions from 'constants/show_action_types';
|
import * as actions from 'constants/show_action_types';
|
||||||
import { addRequestToRequestList, onRequestError, addAssetToAssetList } from 'actions/show';
|
import { addRequestToRequestList, onRequestError, onRequestUpdate, addAssetToAssetList } from 'actions/show';
|
||||||
import { getLongClaimId, getShortId, getClaimData } from 'api/assetApi';
|
import { getLongClaimId, getShortId, getClaimData } from 'api/assetApi';
|
||||||
import { selectShowState } from 'selectors/show';
|
import { selectShowState } from 'selectors/show';
|
||||||
|
|
||||||
function* newAssetRequest (action) {
|
export function * newAssetRequest (action) {
|
||||||
const { requestId, name, modifier } = action.data;
|
const { requestType, requestId, name, modifier } = action.data;
|
||||||
const state = yield select(selectShowState);
|
// put an action to update the request in redux
|
||||||
|
yield put(onRequestUpdate(requestType, requestId));
|
||||||
// is this an existing request?
|
// is this an existing request?
|
||||||
// If this uri is in the request list, it's already been fetched
|
// If this uri is in the request list, it's already been fetched
|
||||||
|
const state = yield select(selectShowState);
|
||||||
if (state.requestList[requestId]) {
|
if (state.requestList[requestId]) {
|
||||||
console.log('that request already exists in the request list!');
|
console.log('that request already exists in the request list!');
|
||||||
return null;
|
return null;
|
||||||
|
@ -52,6 +54,6 @@ function* newAssetRequest (action) {
|
||||||
yield put(onRequestError(null));
|
yield put(onRequestError(null));
|
||||||
};
|
};
|
||||||
|
|
||||||
export function* watchNewAssetRequest () {
|
export function * watchNewAssetRequest () {
|
||||||
yield takeLatest(actions.ASSET_REQUEST_NEW, newAssetRequest);
|
yield takeLatest(actions.ASSET_REQUEST_NEW, newAssetRequest);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import {call, put, select, takeLatest} from 'redux-saga/effects';
|
import {call, put, select, takeLatest} from 'redux-saga/effects';
|
||||||
import * as actions from 'constants/show_action_types';
|
import * as actions from 'constants/show_action_types';
|
||||||
import { addNewChannelToChannelList, addRequestToRequestList, onRequestError, updateChannelClaims } from 'actions/show';
|
import { addNewChannelToChannelList, addRequestToRequestList, onRequestError, onRequestUpdate, updateChannelClaims } from 'actions/show';
|
||||||
import { getChannelClaims, getChannelData } from 'api/channelApi';
|
import { getChannelClaims, getChannelData } from 'api/channelApi';
|
||||||
import { selectShowState } from 'selectors/show';
|
import { selectShowState } from 'selectors/show';
|
||||||
|
|
||||||
function* getNewChannelAndUpdateChannelList (action) {
|
export function * newChannelRequest (action) {
|
||||||
const { requestId, channelName, channelId } = action.data;
|
const { requestType, requestId, channelName, channelId } = action.data;
|
||||||
const state = yield select(selectShowState);
|
// put an action to update the request in redux
|
||||||
|
yield put(onRequestUpdate(requestType, requestId));
|
||||||
// is this an existing request?
|
// is this an existing request?
|
||||||
// If this uri is in the request list, it's already been fetched
|
// If this uri is in the request list, it's already been fetched
|
||||||
|
const state = yield select(selectShowState);
|
||||||
if (state.requestList[requestId]) {
|
if (state.requestList[requestId]) {
|
||||||
console.log('that request already exists in the request list!');
|
console.log('that request already exists in the request list!');
|
||||||
return null;
|
return null;
|
||||||
|
@ -44,11 +46,11 @@ function* getNewChannelAndUpdateChannelList (action) {
|
||||||
yield put(onRequestError(null));
|
yield put(onRequestError(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* watchNewChannelRequest () {
|
export function * watchNewChannelRequest () {
|
||||||
yield takeLatest(actions.CHANNEL_REQUEST_NEW, getNewChannelAndUpdateChannelList);
|
yield takeLatest(actions.CHANNEL_REQUEST_NEW, newChannelRequest);
|
||||||
};
|
};
|
||||||
|
|
||||||
function* getNewClaimsAndUpdateChannel (action) {
|
function * getNewClaimsAndUpdateChannel (action) {
|
||||||
const { channelKey, name, longId, page } = action.data;
|
const { channelKey, name, longId, page } = action.data;
|
||||||
let claimsData;
|
let claimsData;
|
||||||
try {
|
try {
|
||||||
|
@ -59,6 +61,6 @@ function* getNewClaimsAndUpdateChannel (action) {
|
||||||
yield put(updateChannelClaims(channelKey, claimsData));
|
yield put(updateChannelClaims(channelKey, claimsData));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* watchUpdateChannelClaims () {
|
export function * watchUpdateChannelClaims () {
|
||||||
yield takeLatest(actions.CHANNEL_CLAIMS_UPDATE_ASYNC, getNewClaimsAndUpdateChannel);
|
yield takeLatest(actions.CHANNEL_CLAIMS_UPDATE_ASYNC, getNewClaimsAndUpdateChannel);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { call, put, takeLatest } from 'redux-saga/effects';
|
import { call, put, takeLatest } from 'redux-saga/effects';
|
||||||
import * as actions from 'constants/show_action_types';
|
import * as actions from 'constants/show_action_types';
|
||||||
import { onRequestError, onNewChannelRequest, onNewAssetRequest } from 'actions/show';
|
import { onRequestError, onNewChannelRequest, onNewAssetRequest } from 'actions/show';
|
||||||
|
import { newAssetRequest } from 'sagas/show_asset';
|
||||||
|
import { newChannelRequest } from 'sagas/show_channel';
|
||||||
import lbryUri from 'utils/lbryUri';
|
import lbryUri from 'utils/lbryUri';
|
||||||
|
|
||||||
function* parseAndUpdateIdentifierAndClaim (modifier, claim) {
|
function * parseAndUpdateIdentifierAndClaim (modifier, claim) {
|
||||||
console.log('parseAndUpdateIdentifierAndClaim');
|
console.log('parseAndUpdateIdentifierAndClaim');
|
||||||
// this is a request for an asset
|
// this is a request for an asset
|
||||||
// claim will be an asset claim
|
// claim will be an asset claim
|
||||||
|
@ -17,11 +19,11 @@ function* parseAndUpdateIdentifierAndClaim (modifier, claim) {
|
||||||
}
|
}
|
||||||
// trigger an new action to update the store
|
// trigger an new action to update the store
|
||||||
if (isChannel) {
|
if (isChannel) {
|
||||||
return yield put(onNewAssetRequest(claimName, null, channelName, channelClaimId, extension));
|
return yield call(newAssetRequest, onNewAssetRequest(claimName, null, channelName, channelClaimId, extension));
|
||||||
};
|
};
|
||||||
yield put(onNewAssetRequest(claimName, claimId, null, null, extension));
|
yield call(newAssetRequest, onNewAssetRequest(claimName, claimId, null, null, extension));
|
||||||
}
|
}
|
||||||
function* parseAndUpdateClaimOnly (claim) {
|
function * parseAndUpdateClaimOnly (claim) {
|
||||||
console.log('parseAndUpdateIdentifierAndClaim');
|
console.log('parseAndUpdateIdentifierAndClaim');
|
||||||
// this could be a request for an asset or a channel page
|
// this could be a request for an asset or a channel page
|
||||||
// claim could be an asset claim or a channel claim
|
// claim could be an asset claim or a channel claim
|
||||||
|
@ -34,7 +36,7 @@ function* parseAndUpdateClaimOnly (claim) {
|
||||||
// trigger an new action to update the store
|
// trigger an new action to update the store
|
||||||
// return early if this request is for a channel
|
// return early if this request is for a channel
|
||||||
if (isChannel) {
|
if (isChannel) {
|
||||||
return yield put(onNewChannelRequest(channelName, channelClaimId));
|
return yield call(newChannelRequest, onNewChannelRequest(channelName, channelClaimId));
|
||||||
}
|
}
|
||||||
// if not for a channel, parse the claim request
|
// if not for a channel, parse the claim request
|
||||||
let claimName, extension;
|
let claimName, extension;
|
||||||
|
@ -43,10 +45,10 @@ function* parseAndUpdateClaimOnly (claim) {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return yield put(onRequestError(error.message));
|
return yield put(onRequestError(error.message));
|
||||||
}
|
}
|
||||||
yield put(onNewAssetRequest(claimName, null, null, null, extension));
|
yield call(newAssetRequest, onNewAssetRequest(claimName, null, null, null, extension));
|
||||||
}
|
}
|
||||||
|
|
||||||
function* handleShowPageUri (action) {
|
export function * handleShowPageUri (action) {
|
||||||
console.log('handleShowPageUri');
|
console.log('handleShowPageUri');
|
||||||
const { identifier, claim } = action.data;
|
const { identifier, claim } = action.data;
|
||||||
if (identifier) {
|
if (identifier) {
|
||||||
|
@ -55,6 +57,6 @@ function* handleShowPageUri (action) {
|
||||||
yield call(parseAndUpdateClaimOnly, claim);
|
yield call(parseAndUpdateClaimOnly, claim);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function* watchHandleShowPageUri () {
|
export function * watchHandleShowPageUri () {
|
||||||
yield takeLatest(actions.HANDLE_SHOW_URI, handleShowPageUri);
|
yield takeLatest(actions.HANDLE_SHOW_URI, handleShowPageUri);
|
||||||
};
|
};
|
||||||
|
|
37
react/utils/canonicalLink.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
const { site: { host } } = require('../../config/speechConfig.js');
|
||||||
|
|
||||||
|
const createBasicCanonicalLink = (page) => {
|
||||||
|
if (!page) {
|
||||||
|
return `${host}`;
|
||||||
|
};
|
||||||
|
return `${host}/${page}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createAssetCanonicalLink = (asset) => {
|
||||||
|
let channelName, certificateId, name, claimId;
|
||||||
|
if (asset.claimData) {
|
||||||
|
({ channelName, certificateId, name, claimId } = asset.claimData);
|
||||||
|
};
|
||||||
|
if (channelName) {
|
||||||
|
return `${host}/${channelName}:${certificateId}/${name}`;
|
||||||
|
};
|
||||||
|
return `${host}/${claimId}/${name}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createChannelCanonicalLink = (channel) => {
|
||||||
|
const { name, longId } = channel;
|
||||||
|
return `${host}/${name}:${longId}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createCanonicalLink = (asset, channel, page) => {
|
||||||
|
if (asset) {
|
||||||
|
return createAssetCanonicalLink(asset);
|
||||||
|
}
|
||||||
|
if (channel) {
|
||||||
|
return createChannelCanonicalLink(channel);
|
||||||
|
}
|
||||||
|
if (page) {
|
||||||
|
return createBasicCanonicalLink(page);
|
||||||
|
}
|
||||||
|
return createBasicCanonicalLink();
|
||||||
|
};
|
|
@ -35,4 +35,4 @@ module.exports = {
|
||||||
throw new Error(file.type + ' is not a supported file type. Only, .jpeg, .png, .gif, and .mp4 files are currently supported.');
|
throw new Error(file.type + ' is not a supported file type. Only, .jpeg, .png, .gif, and .mp4 files are currently supported.');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
|
@ -8,7 +8,7 @@ module.exports = {
|
||||||
'([^:$#/]*)' + // value (stops at the first separator or end)
|
'([^:$#/]*)' + // value (stops at the first separator or end)
|
||||||
'([:$#]?)([^/]*)' // modifier separator, modifier (stops at the first path separator or end)
|
'([:$#]?)([^/]*)' // modifier separator, modifier (stops at the first path separator or end)
|
||||||
);
|
);
|
||||||
const [proto, value, modifierSeperator, modifier] = componentsRegex
|
const [proto, value, modifierSeperator, modifier] = componentsRegex // eslint-disable-line no-unused-vars
|
||||||
.exec(identifier)
|
.exec(identifier)
|
||||||
.map(match => match || null);
|
.map(match => match || null);
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ module.exports = {
|
||||||
'([^:$#/.]*)' + // name (stops at the first extension)
|
'([^:$#/.]*)' + // name (stops at the first extension)
|
||||||
'([:$#.]?)([^/]*)' // extension separator, extension (stops at the first path separator or end)
|
'([:$#.]?)([^/]*)' // extension separator, extension (stops at the first path separator or end)
|
||||||
);
|
);
|
||||||
const [proto, claimName, extensionSeperator, extension] = componentsRegex
|
const [proto, claimName, extensionSeperator, extension] = componentsRegex // eslint-disable-line no-unused-vars
|
||||||
.exec(name)
|
.exec(name)
|
||||||
.map(match => match || null);
|
.map(match => match || null);
|
||||||
|
|
||||||
|
|
96
react/utils/metaTags.js
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
const { site: { title, host, description }, claim: { defaultThumbnail, defaultDescription } } = require('../../config/speechConfig.js');
|
||||||
|
|
||||||
|
const determineOgThumbnailContentType = (thumbnail) => {
|
||||||
|
if (thumbnail) {
|
||||||
|
const fileExt = thumbnail.substring(thumbnail.lastIndexOf('.'));
|
||||||
|
switch (fileExt) {
|
||||||
|
case 'jpeg':
|
||||||
|
case 'jpg':
|
||||||
|
return 'image/jpeg';
|
||||||
|
case 'png':
|
||||||
|
return 'image/png';
|
||||||
|
case 'gif':
|
||||||
|
return 'image/gif';
|
||||||
|
case 'mp4':
|
||||||
|
return 'video/mp4';
|
||||||
|
default:
|
||||||
|
return 'image/jpeg';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const createBasicMetaTags = () => {
|
||||||
|
return [
|
||||||
|
{property: 'og:title', content: title},
|
||||||
|
{property: 'og:url', content: host},
|
||||||
|
{property: 'og:site_name', content: title},
|
||||||
|
{property: 'og:description', content: description},
|
||||||
|
{property: 'twitter:site', content: '@spee_ch'},
|
||||||
|
{property: 'twitter:card', content: 'summary'},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const createChannelMetaTags = (channel) => {
|
||||||
|
const { name, longId } = channel;
|
||||||
|
return [
|
||||||
|
{property: 'og:title', content: `${name} on ${title}`},
|
||||||
|
{property: 'og:url', content: `${host}/${name}:${longId}`},
|
||||||
|
{property: 'og:site_name', content: title},
|
||||||
|
{property: 'og:description', content: `${name}, a channel on ${title}`},
|
||||||
|
{property: 'twitter:site', content: '@spee_ch'},
|
||||||
|
{property: 'twitter:card', content: 'summary'},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const createAssetMetaTags = (asset) => {
|
||||||
|
const { claimData } = asset;
|
||||||
|
const { contentType } = claimData;
|
||||||
|
const embedUrl = `${host}/${claimData.claimId}/${claimData.name}`;
|
||||||
|
const showUrl = `${host}/${claimData.claimId}/${claimData.name}`;
|
||||||
|
const source = `${host}/${claimData.claimId}/${claimData.name}.${claimData.fileExt}`;
|
||||||
|
const ogTitle = claimData.title || claimData.name;
|
||||||
|
const ogDescription = claimData.description || defaultDescription;
|
||||||
|
const ogThumbnailContentType = determineOgThumbnailContentType(claimData.thumbnail);
|
||||||
|
const ogThumbnail = claimData.thumbnail || defaultThumbnail;
|
||||||
|
const metaTags = [
|
||||||
|
{property: 'og:title', content: ogTitle},
|
||||||
|
{property: 'og:url', content: showUrl},
|
||||||
|
{property: 'og:site_name', content: title},
|
||||||
|
{property: 'og:description', content: ogDescription},
|
||||||
|
{property: 'og:image:width', content: 600},
|
||||||
|
{property: 'og:image:height', content: 315},
|
||||||
|
{property: 'twitter:site', content: '@spee_ch'},
|
||||||
|
];
|
||||||
|
if (contentType === 'video/mp4' || contentType === 'video/webm') {
|
||||||
|
metaTags.push({property: 'og:video', content: source});
|
||||||
|
metaTags.push({property: 'og:video:secure_url', content: source});
|
||||||
|
metaTags.push({property: 'og:video:type', content: contentType});
|
||||||
|
metaTags.push({property: 'og:image', content: ogThumbnail});
|
||||||
|
metaTags.push({property: 'og:image:type', content: ogThumbnailContentType});
|
||||||
|
metaTags.push({property: 'og:type', content: 'video'});
|
||||||
|
metaTags.push({property: 'twitter:card', content: 'player'});
|
||||||
|
metaTags.push({property: 'twitter:player', content: embedUrl});
|
||||||
|
metaTags.push({property: 'twitter:player:width', content: 600});
|
||||||
|
metaTags.push({property: 'twitter:text:player_width', content: 600});
|
||||||
|
metaTags.push({property: 'twitter:player:height', content: 337});
|
||||||
|
metaTags.push({property: 'twitter:player:stream', content: source});
|
||||||
|
metaTags.push({property: 'twitter:player:stream:content_type', content: contentType});
|
||||||
|
} else {
|
||||||
|
metaTags.push({property: 'og:image', content: source});
|
||||||
|
metaTags.push({property: 'og:image:type', content: contentType});
|
||||||
|
metaTags.push({property: 'og:type', content: 'article'});
|
||||||
|
metaTags.push({property: 'twitter:card', content: 'summary_large_image'});
|
||||||
|
}
|
||||||
|
return metaTags;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createMetaTags = (asset, channel) => {
|
||||||
|
if (asset) {
|
||||||
|
return createAssetMetaTags(asset);
|
||||||
|
};
|
||||||
|
if (channel) {
|
||||||
|
return createChannelMetaTags(channel);
|
||||||
|
};
|
||||||
|
return createBasicMetaTags();
|
||||||
|
};
|
8
react/utils/pageTitle.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
const { site: { title: siteTitle } } = require('../../config/speechConfig.js');
|
||||||
|
|
||||||
|
export const createPageTitle = (pageTitle) => {
|
||||||
|
if (!pageTitle) {
|
||||||
|
return `${siteTitle}`;
|
||||||
|
}
|
||||||
|
return `${siteTitle} - ${pageTitle}`;
|
||||||
|
};
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'cross-fetch/polyfill';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the JSON returned by a network request
|
* Parses the JSON returned by a network request
|
||||||
*
|
*
|
||||||
|
|
9
routes/fallback-routes.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
const handlePageRender = require('../helpers/handlePageRender.jsx');
|
||||||
|
|
||||||
|
module.exports = app => {
|
||||||
|
// a catch-all route if someone visits a page that does not exist
|
||||||
|
app.use('*', (req, res) => {
|
||||||
|
// send response
|
||||||
|
handlePageRender(req, res);
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,11 +0,0 @@
|
||||||
module.exports = app => {
|
|
||||||
// route for the home page
|
|
||||||
app.get('/', (req, res) => {
|
|
||||||
res.status(200).render('index');
|
|
||||||
});
|
|
||||||
// a catch-all route if someone visits a page that does not exist
|
|
||||||
app.use('*', ({ originalUrl, ip }, res) => {
|
|
||||||
// send response
|
|
||||||
res.status(404).render('404');
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,24 +1,29 @@
|
||||||
const { site } = require('../config/speechConfig.js');
|
const { site } = require('../config/speechConfig.js');
|
||||||
|
const handlePageRender = require('../helpers/handlePageRender.jsx');
|
||||||
|
|
||||||
module.exports = (app) => {
|
module.exports = (app) => {
|
||||||
|
// route for the home page
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
handlePageRender(req, res);
|
||||||
|
});
|
||||||
// route to display login page
|
// route to display login page
|
||||||
app.get('/login', (req, res) => {
|
app.get('/login', (req, res) => {
|
||||||
res.status(200).render('index');
|
handlePageRender(req, res);
|
||||||
});
|
});
|
||||||
// route to show 'about' page
|
// route to show 'about' page
|
||||||
app.get('/about', (req, res) => {
|
app.get('/about', (req, res) => {
|
||||||
res.status(200).render('index');
|
handlePageRender(req, res);
|
||||||
});
|
});
|
||||||
// route to display a list of the trending images
|
// route to display a list of the trending images
|
||||||
app.get('/trending', (req, res) => {
|
app.get('/trending', (req, res) => {
|
||||||
res.status(301).redirect('/popular');
|
res.status(301).redirect('/popular');
|
||||||
});
|
});
|
||||||
app.get('/popular', ({ ip, originalUrl }, res) => {
|
app.get('/popular', (req, res) => {
|
||||||
res.status(200).render('index');
|
handlePageRender(req, res);
|
||||||
});
|
});
|
||||||
// route to display a list of the trending images
|
// route to display a list of the trending images
|
||||||
app.get('/new', ({ ip, originalUrl }, res) => {
|
app.get('/new', (req, res) => {
|
||||||
res.status(200).render('index');
|
handlePageRender(req, res);
|
||||||
});
|
});
|
||||||
// route to send embedable video player (for twitter)
|
// route to send embedable video player (for twitter)
|
||||||
app.get('/embed/:claimId/:name', ({ params }, res) => {
|
app.get('/embed/:claimId/:name', ({ params }, res) => {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
const { sendGAServeEvent } = require('../helpers/googleAnalytics');
|
const { sendGAServeEvent } = require('../helpers/googleAnalytics');
|
||||||
|
|
||||||
const { determineResponseType, flipClaimNameAndIdForBackwardsCompatibility, logRequestData, getClaimIdAndServeAsset } = require('../helpers/serveHelpers.js');
|
const { determineResponseType, flipClaimNameAndIdForBackwardsCompatibility, logRequestData, getClaimIdAndServeAsset } = require('../helpers/serveHelpers.js');
|
||||||
const lbryUri = require('../helpers/lbryUri.js');
|
const lbryUri = require('../helpers/lbryUri.js');
|
||||||
|
const handleShowRender = require('../helpers/handleShowRender.jsx');
|
||||||
const SERVE = 'SERVE';
|
const SERVE = 'SERVE';
|
||||||
|
|
||||||
module.exports = (app) => {
|
module.exports = (app) => {
|
||||||
// route to serve a specific asset using the channel or claim id
|
// route to serve a specific asset using the channel or claim id
|
||||||
app.get('/:identifier/:claim', ({ headers, ip, originalUrl, params }, res) => {
|
app.get('/:identifier/:claim', (req, res) => {
|
||||||
|
const { headers, ip, originalUrl, params } = req;
|
||||||
// decide if this is a show request
|
// decide if this is a show request
|
||||||
let hasFileExtension;
|
let hasFileExtension;
|
||||||
try {
|
try {
|
||||||
|
@ -17,7 +17,7 @@ module.exports = (app) => {
|
||||||
}
|
}
|
||||||
let responseType = determineResponseType(hasFileExtension, headers);
|
let responseType = determineResponseType(hasFileExtension, headers);
|
||||||
if (responseType !== SERVE) {
|
if (responseType !== SERVE) {
|
||||||
return res.status(200).render('index');
|
return handleShowRender(req, res);
|
||||||
}
|
}
|
||||||
// handle serve request
|
// handle serve request
|
||||||
// send google analytics
|
// send google analytics
|
||||||
|
@ -45,7 +45,8 @@ module.exports = (app) => {
|
||||||
getClaimIdAndServeAsset(channelName, channelClaimId, claimName, claimId, originalUrl, ip, res);
|
getClaimIdAndServeAsset(channelName, channelClaimId, claimName, claimId, originalUrl, ip, res);
|
||||||
});
|
});
|
||||||
// route to serve the winning asset at a claim or a channel page
|
// route to serve the winning asset at a claim or a channel page
|
||||||
app.get('/:claim', ({ headers, ip, originalUrl, params, query }, res) => {
|
app.get('/:claim', (req, res) => {
|
||||||
|
const { headers, ip, originalUrl, params } = req;
|
||||||
// decide if this is a show request
|
// decide if this is a show request
|
||||||
let hasFileExtension;
|
let hasFileExtension;
|
||||||
try {
|
try {
|
||||||
|
@ -55,7 +56,7 @@ module.exports = (app) => {
|
||||||
}
|
}
|
||||||
let responseType = determineResponseType(hasFileExtension, headers);
|
let responseType = determineResponseType(hasFileExtension, headers);
|
||||||
if (responseType !== SERVE) {
|
if (responseType !== SERVE) {
|
||||||
return res.status(200).render('index');
|
return handleShowRender(req, res);
|
||||||
}
|
}
|
||||||
// handle serve request
|
// handle serve request
|
||||||
// send google analytics
|
// send google analytics
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
<div class="row row--tall flex-container--column flex-container--center-center">
|
|
||||||
<h3>404: Not Found</h3>
|
|
||||||
<p>That page does not exist. Return <a class="link--primary" href="/">home</a>.</p>
|
|
||||||
</div>
|
|
|
@ -1,10 +0,0 @@
|
||||||
<div class="row row--tall flex-container--column">
|
|
||||||
<div id="react-app" class="row row--tall flex-container--column">
|
|
||||||
<div class="row row--padded row--tall flex-container--column flex-container--center-center">
|
|
||||||
<p>loading...</p>
|
|
||||||
{{> progressBar}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/bundle/bundle.js"></script>
|
|
|
@ -1,19 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#">
|
|
||||||
<head>
|
|
||||||
{{ placeCommonHeaderTags }}
|
|
||||||
<meta name="twitter:card" content="summary" />
|
|
||||||
<meta name="twitter:site" content="@spee_ch" />
|
|
||||||
<meta property="og:title" content="Spee.ch" />
|
|
||||||
<meta property="og:site_name" content="Spee.ch" />
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta property="og:image" content="https://spee.ch/assets/img/Speech_Logo_Main@OG-02.jpg" />
|
|
||||||
<meta property="og:url" content="http://spee.ch/" />
|
|
||||||
<meta property="og:description" content="Open-source, decentralized image and video sharing." />
|
|
||||||
<!--google font-->
|
|
||||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
<body id="main-body">
|
|
||||||
{{{ body }}}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,14 +1,14 @@
|
||||||
const Path = require('path');
|
const Path = require('path');
|
||||||
|
|
||||||
const REACT_ROOT = Path.resolve(__dirname, 'react/');
|
const REACT_ROOT = Path.resolve(__dirname, 'react/');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry : ['babel-polyfill', 'whatwg-fetch', './react/index.js'],
|
target: 'web',
|
||||||
|
entry : ['babel-polyfill', 'whatwg-fetch', './react/client.js'],
|
||||||
output: {
|
output: {
|
||||||
path : Path.join(__dirname, '/public/bundle/'),
|
path : Path.join(__dirname, 'public/bundle/'),
|
||||||
filename: 'bundle.js',
|
publicPath: 'public/bundle/',
|
||||||
|
filename : 'bundle.js',
|
||||||
},
|
},
|
||||||
watch : true,
|
|
||||||
module: {
|
module: {
|
||||||
loaders: [
|
loaders: [
|
||||||
{
|
{
|
13
webpack.dev.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
const serverBaseConfig = require('./webpack.server.common.js');
|
||||||
|
const clientBaseConfig = require('./webpack.client.common.js');
|
||||||
|
const merge = require('webpack-merge');
|
||||||
|
|
||||||
|
const devBuildConfig = {
|
||||||
|
watch : true,
|
||||||
|
devtool: 'inline-source-map',
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
merge(serverBaseConfig, devBuildConfig),
|
||||||
|
merge(clientBaseConfig, devBuildConfig),
|
||||||
|
];
|
22
webpack.prod.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const merge = require('webpack-merge');
|
||||||
|
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
|
||||||
|
const serverBaseConfig = require('./webpack.server.common.js');
|
||||||
|
const clientBaseConfig = require('./webpack.client.common.js');
|
||||||
|
|
||||||
|
const productionBuildConfig = {
|
||||||
|
devtool: 'source-map',
|
||||||
|
plugins: [
|
||||||
|
new UglifyJSPlugin({
|
||||||
|
sourceMap: true,
|
||||||
|
}),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
merge(serverBaseConfig, productionBuildConfig),
|
||||||
|
merge(clientBaseConfig, productionBuildConfig),
|
||||||
|
];
|
41
webpack.server.common.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
const Path = require('path');
|
||||||
|
const nodeExternals = require('webpack-node-externals');
|
||||||
|
const REACT_ROOT = Path.resolve(__dirname, 'react/');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
target: 'node',
|
||||||
|
node : {
|
||||||
|
__dirname: false,
|
||||||
|
},
|
||||||
|
externals: [nodeExternals()],
|
||||||
|
entry : ['babel-polyfill', 'whatwg-fetch', './index.js'],
|
||||||
|
output : {
|
||||||
|
path : Path.join(__dirname, '/'),
|
||||||
|
publicPath: '/',
|
||||||
|
filename : 'server.js',
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test : /.jsx?$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
loader : 'babel-loader',
|
||||||
|
options: {
|
||||||
|
presets: ['es2015', 'react', 'stage-2'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test : /.css$/,
|
||||||
|
loader: 'css-loader',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
modules: [
|
||||||
|
REACT_ROOT,
|
||||||
|
'node_modules',
|
||||||
|
__dirname,
|
||||||
|
],
|
||||||
|
extensions: ['.js', '.json', '.jsx', '.css'],
|
||||||
|
},
|
||||||
|
};
|
Probably don't need these
console.log
s here.