mirror of
https://github.com/trambarhq/relaks-wordpress-example.git
synced 2025-09-03 05:02:34 +02:00
Loading live data into nav.
This commit is contained in:
12
package-lock.json
generated
12
package-lock.json
generated
@@ -4879,9 +4879,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.10",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
|
||||
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==",
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
|
||||
"dev": true
|
||||
},
|
||||
"lodash._baseclone": {
|
||||
@@ -5247,6 +5247,12 @@
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.23.0",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.23.0.tgz",
|
||||
"integrity": "sha512-3IE39bHVqFbWWaPOMHZF98Q9c3LDKGTmypMiTM2QygGXXElkFWIH7GxfmlwmY2vwa+wmNsoYZmG2iusf1ZjJoA==",
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
|
@@ -29,7 +29,10 @@
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"hammerjs": "^2.0.8",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"lodash": "^4.17.11",
|
||||
"moment": "^2.23.0",
|
||||
"node-sass": "^4.5.3",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.6.3",
|
||||
"regenerator-runtime": "^0.12.0",
|
||||
"relaks": "^1.1.7",
|
||||
|
15
src/main.js
15
src/main.js
@@ -13,6 +13,10 @@ if (typeof(window) === 'object') {
|
||||
async function initialize(evt) {
|
||||
// create data source
|
||||
let host = `${location.protocol}//${location.host}`;
|
||||
if (process.env.NODE_ENV !== 'production' && process.env.WEBPACK_DEV_SERVER) {
|
||||
// use hardcoded URL when we're running in dev-server
|
||||
host = 'http://localhost:8000';
|
||||
}
|
||||
let dataSource = new WordpressDataSource({
|
||||
baseURL: `${host}/wp-json`,
|
||||
});
|
||||
@@ -31,10 +35,13 @@ if (typeof(window) === 'object') {
|
||||
if (!appContainer) {
|
||||
throw new Error('Unable to find app element in DOM');
|
||||
}
|
||||
//let ssrElement = createElement(FrontEnd, { dataSource, routeManager, ssr: 'hydrate' });
|
||||
//let seeds = await harvest(ssrElement, { seeds: true });
|
||||
//Relaks.set('seeds', seeds);
|
||||
//hydrate(ssrElement, appContainer);
|
||||
// expect SSR unless we're running in dev-server
|
||||
if (!(process.env.NODE_ENV !== 'production' && process.env.WEBPACK_DEV_SERVER)) {
|
||||
let ssrElement = createElement(FrontEnd, { dataSource, routeManager, ssr: 'hydrate' });
|
||||
let seeds = await harvest(ssrElement, { seeds: true });
|
||||
Relaks.set('seeds', seeds);
|
||||
hydrate(ssrElement, appContainer);
|
||||
}
|
||||
|
||||
let appElement = createElement(FrontEnd, { dataSource, routeManager });
|
||||
render(appElement, appContainer);
|
||||
|
@@ -18,12 +18,6 @@ BODY {
|
||||
A {
|
||||
&:link, &:visited {
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
color: #f11;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +29,30 @@ A {
|
||||
bottom: 0;
|
||||
background-color: #66023c;
|
||||
overflow: hidden;
|
||||
color: #cccccc;
|
||||
|
||||
A {
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
.archive {
|
||||
LI {
|
||||
margin-top: 0.1em;
|
||||
margin-bottom: 0.1em;
|
||||
}
|
||||
|
||||
.year {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.months {
|
||||
overflow: hidden;
|
||||
|
||||
&.collapsed {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.top-nav {
|
||||
@@ -44,6 +62,10 @@ A {
|
||||
width: calc(100% - 18em);
|
||||
background-color: #990000; // prevent visual glitch in Android
|
||||
|
||||
A {
|
||||
color: #cccccc;
|
||||
}
|
||||
|
||||
.title-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@@ -1,5 +1,105 @@
|
||||
import _ from 'lodash';
|
||||
import Moment from 'moment';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { AsyncComponent } from 'relaks';
|
||||
import { Route } from 'routing';
|
||||
import WordPress from 'wordpress';
|
||||
|
||||
class SideNav extends AsyncComponent {
|
||||
static displayName = 'SideNav';
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
let { route } = this.props;
|
||||
let selectedYear;
|
||||
if (route.params.month) {
|
||||
selectedYear = parseInt(route.params.month.substr(0, 4));
|
||||
} else {
|
||||
selectedYear = Moment().year();
|
||||
}
|
||||
this.state = {
|
||||
selectedYear
|
||||
};
|
||||
}
|
||||
|
||||
async renderAsync(meanwhile) {
|
||||
let { wp, route } = this.props;
|
||||
let { selectedYear } = this.state;
|
||||
let props = {
|
||||
route,
|
||||
selectedYear,
|
||||
onYearSelect: this.handleYearSelect,
|
||||
};
|
||||
meanwhile.delay(50, 50);
|
||||
meanwhile.show(<SideNavSync {...props} />);
|
||||
props.categories = await wp.fetchList('/wp/v2/categories/');
|
||||
meanwhile.show(<SideNavSync {...props} />);
|
||||
|
||||
// get the latest post and the earliest post
|
||||
let latestPosts = await wp.fetchList('/wp/v2/posts/');
|
||||
let latestPost = _.first(latestPosts);
|
||||
let earliestPosts = await wp.fetchList(`/wp/v2/posts/?order=asc&per_page=1`)
|
||||
let earliestPost = _.first(earliestPosts);
|
||||
|
||||
// build the archive tree
|
||||
props.archive = [];
|
||||
if (latestPost && earliestPost) {
|
||||
let lastMonthEnd = Moment(latestPost.date_gmt).endOf('month');
|
||||
let firstMonthStart = Moment(earliestPost.date_gmt).startOf('month');
|
||||
let currentYearEntry;
|
||||
// loop from the last month to the first
|
||||
let e = lastMonthEnd.clone();
|
||||
let s = e.clone().startOf('month');
|
||||
while (s >= firstMonthStart) {
|
||||
let year = s.year();
|
||||
let month = s.month() + 1;
|
||||
if (!currentYearEntry || currentYearEntry.year !== year) {
|
||||
// start a new year
|
||||
currentYearEntry = {
|
||||
year,
|
||||
label: s.format('YYYY'),
|
||||
months: []
|
||||
};
|
||||
props.archive.push(currentYearEntry);
|
||||
}
|
||||
let monthEntry = {
|
||||
month,
|
||||
label: s.format('MMMM'),
|
||||
slug: s.format('YYYY-MM'),
|
||||
post: undefined,
|
||||
start: s.clone(),
|
||||
end: e.clone(),
|
||||
};
|
||||
currentYearEntry.months.push(monthEntry);
|
||||
|
||||
e.subtract(1, 'month');
|
||||
s.subtract(1, 'month');
|
||||
}
|
||||
meanwhile.show(<SideNavSync {...props} />);
|
||||
|
||||
// load the posts of the selected year
|
||||
for (let yearEntry of props.archive) {
|
||||
if (yearEntry.year === selectedYear) {
|
||||
for (let monthEntry of yearEntry.months) {
|
||||
let before = monthEntry.end.toISOString();
|
||||
let after = monthEntry.start.toISOString();
|
||||
let url = `/wp/v2/posts/?before=${before}&after=${after}`;
|
||||
monthEntry.posts = await wp.fetchList(url);
|
||||
|
||||
// force prop change
|
||||
props.archive = _.clone(props.archive);
|
||||
meanwhile.show(<SideNavSync {...props} />);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return <SideNavSync {...props} />;
|
||||
}
|
||||
|
||||
handleYearSelect = (evt) => {
|
||||
this.setState({ selectedYear: evt.year });
|
||||
}
|
||||
}
|
||||
|
||||
class SideNavSync extends PureComponent {
|
||||
static displayName = 'SideNavSync';
|
||||
@@ -7,13 +107,128 @@ class SideNavSync extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<div className="side-nav">
|
||||
Side-Nav
|
||||
{this.renderCategories()}
|
||||
{this.renderArchive()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderCategories() {
|
||||
let { categories } = this.props;
|
||||
if (!categories) {
|
||||
return null;
|
||||
}
|
||||
// don't show categories with no post
|
||||
categories = _.filter(categories, 'count');
|
||||
// list category with more posts first
|
||||
categories = _.orderBy(categories, [ 'count', 'name' ], [ 'desc', 'asc' ]);
|
||||
return (
|
||||
<ul className="categories">
|
||||
{
|
||||
categories.map((category, i) => {
|
||||
return this.renderCategory(category, i);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
renderCategory(category, i) {
|
||||
let name = _.get(category, 'name', '');
|
||||
let description = _.get(category, 'description', '');
|
||||
let slug = _.get(category, 'slug', '');
|
||||
let url = '';
|
||||
return (
|
||||
<li key={i}>
|
||||
<a href={url} title={description}>{name}</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
renderArchive() {
|
||||
let { archive } = this.props;
|
||||
if (!archive) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ul className="archive">
|
||||
{
|
||||
archive.map((entry, i) => {
|
||||
return this.renderYear(entry, i);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
renderYear(yearEntry, i) {
|
||||
let { selectedYear } = this.props;
|
||||
let labelClass = 'year';
|
||||
let listClass = 'months';
|
||||
if (yearEntry.year === selectedYear) {
|
||||
labelClass += ' selected';
|
||||
} else {
|
||||
listClass += ' collapsed';
|
||||
}
|
||||
return (
|
||||
<li key={i}>
|
||||
<span className={labelClass} data-year={yearEntry.year} onClick={this.handleYearClick}>
|
||||
{yearEntry.label}
|
||||
</span>
|
||||
<ul className={listClass}>
|
||||
{
|
||||
yearEntry.months.map((entry, i) => {
|
||||
return this.renderMonth(entry, i);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
renderMonth(monthEntry, i) {
|
||||
let url;
|
||||
if (!_.isEmpty(monthEntry.posts) || _.isUndefined(monthEntry.posts)) {
|
||||
url = '#';
|
||||
}
|
||||
return (
|
||||
<li key={i}>
|
||||
<a href={url}>{monthEntry.label}</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
handleYearClick = (evt) => {
|
||||
let { onYearSelect } = this.props;
|
||||
let year = parseInt(evt.currentTarget.getAttribute('data-year'));
|
||||
if (onYearSelect) {
|
||||
onYearSelect({
|
||||
type: 'yearselect',
|
||||
target: this,
|
||||
year,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const PropTypes = require('prop-types');
|
||||
|
||||
SideNav.propTypes = {
|
||||
wp: PropTypes.instanceOf(WordPress).isRequired,
|
||||
route: PropTypes.instanceOf(Route).isRequired,
|
||||
};
|
||||
SideNavSync.propTypes = {
|
||||
categories: PropTypes.arrayOf(PropTypes.object),
|
||||
archive: PropTypes.arrayOf(PropTypes.object),
|
||||
selectedYear: PropTypes.number,
|
||||
route: PropTypes.instanceOf(Route),
|
||||
onYearSelect: PropTypes.func,
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
SideNavSync as default,
|
||||
SideNav as default,
|
||||
SideNav,
|
||||
SideNavSync,
|
||||
};
|
||||
|
@@ -1,12 +1,32 @@
|
||||
import _ from 'lodash';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { AsyncComponent } from 'relaks';
|
||||
import { Route } from 'routing';
|
||||
import WordPress from 'wordpress';
|
||||
|
||||
class TopBarSync extends PureComponent {
|
||||
class TopNav extends AsyncComponent {
|
||||
static displayName = 'TopNav';
|
||||
|
||||
async renderAsync(meanwhile) {
|
||||
let { wp, route } = this.props;
|
||||
let props = {
|
||||
route,
|
||||
};
|
||||
meanwhile.show(<TopNavSync {...props} />);
|
||||
props.system = await wp.fetchOne('/');
|
||||
meanwhile.show(<TopNavSync {...props} />);
|
||||
props.pages = await wp.fetchList('/wp/v2/pages/');
|
||||
return <TopNavSync {...props} />;
|
||||
}
|
||||
}
|
||||
|
||||
class TopNavSync extends PureComponent {
|
||||
static displayName = 'TopNavSync';
|
||||
|
||||
render() {
|
||||
let { onMouseOver, onMouseOut } = this.props;
|
||||
return (
|
||||
<div className="top-nav">
|
||||
<div className="top-nav" onMouseOver={onMouseOver} onMouseOut={onMouseOut}>
|
||||
{this.renderTitleBar()}
|
||||
{this.renderPageLinkBar()}
|
||||
{this.renderSearchBar()}
|
||||
@@ -15,34 +35,52 @@ class TopBarSync extends PureComponent {
|
||||
}
|
||||
|
||||
renderTitleBar() {
|
||||
let { system } = this.props;
|
||||
let name = _.get(system, 'name', '');
|
||||
let description = _.get(system, 'description', '');
|
||||
return (
|
||||
<div className="title-bar">
|
||||
<div className="title">
|
||||
<div className="title" title={description}>
|
||||
<i className="fa fa-home" />
|
||||
<span className="site-name">Romanes eunt domus</span>
|
||||
<span className="site-name">{name}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderPageLinkBar() {
|
||||
let { pages } = this.props;
|
||||
pages = _.sortBy(pages, 'menu_order');
|
||||
return (
|
||||
<div className="page-bar">
|
||||
<div className="button">
|
||||
Hello
|
||||
</div>
|
||||
<div className="button">
|
||||
World
|
||||
</div>
|
||||
{
|
||||
pages.map((page, i) => {
|
||||
return this.renderPageLinkButton(page, i);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderPageLinkButton(page, i) {
|
||||
let { route } = this.props;
|
||||
let title = _.get(page, 'title.rendered');
|
||||
let slug = _.get(page, 'slug');
|
||||
let url = '';
|
||||
return (
|
||||
<div className="button" key={i}>
|
||||
<a href={url}>{title}</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderSearchBar() {
|
||||
let { route } = this.props;
|
||||
let search = _.get(route.params, 'search', '');
|
||||
return (
|
||||
<div className="search-bar">
|
||||
<span className="input-container">
|
||||
<input type="text" value="Hello" />
|
||||
<input type="text" value={search} />
|
||||
<i className="fa fa-search" />
|
||||
</span>
|
||||
</div>
|
||||
@@ -50,7 +88,28 @@ class TopBarSync extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
TopBarSync as default,
|
||||
TopBarSync,
|
||||
TopNavSync.defaultProps = {
|
||||
system: {},
|
||||
pages: [],
|
||||
search: '',
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const PropTypes = require('prop-types');
|
||||
|
||||
TopNav.propTypes = {
|
||||
wp: PropTypes.instanceOf(WordPress).isRequired,
|
||||
route: PropTypes.instanceOf(Route).isRequired,
|
||||
};
|
||||
TopNavSync.propTypes = {
|
||||
system: PropTypes.object,
|
||||
pages: PropTypes.arrayOf(PropTypes.object),
|
||||
route: PropTypes.instanceOf(Route).isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
TopNav as default,
|
||||
TopNav,
|
||||
TopNavSync,
|
||||
};
|
||||
|
@@ -57,26 +57,9 @@ var clientConfig = {
|
||||
analyzerMode: (event === 'build') ? 'static' : 'disabled',
|
||||
reportFilename: `report.html`,
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
template: Path.resolve(`./src/index.html`),
|
||||
filename: Path.resolve(`./server/www/index.html`),
|
||||
}),
|
||||
new ExtractTextPlugin("styles.css"),
|
||||
],
|
||||
devtool: (event === 'build') ? false : 'inline-source-map',
|
||||
devServer: {
|
||||
inline: true,
|
||||
historyApiFallback: {
|
||||
rewrites: [
|
||||
{
|
||||
from: /.*/,
|
||||
to: function(context) {
|
||||
return context.parsedUrl.pathname.replace(/.*\/(.*)$/, '/$1');
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var serverConfig = {
|
||||
@@ -102,7 +85,39 @@ var serverConfig = {
|
||||
devtool: clientConfig.devtool,
|
||||
};
|
||||
|
||||
var configs = module.exports = clientConfig; //[ clientConfig, serverConfig ];
|
||||
var configs = module.exports = [ clientConfig, serverConfig ];
|
||||
|
||||
var isDevServer = process.argv.find((arg) => {
|
||||
return arg.includes('webpack-dev-server') ;
|
||||
});
|
||||
if (isDevServer) {
|
||||
// remove server config
|
||||
configs.pop();
|
||||
// need HTML page
|
||||
clientConfig.plugins.push(new HtmlWebpackPlugin({
|
||||
template: Path.resolve(`./src/index.html`),
|
||||
filename: Path.resolve(`./server/www/index.html`),
|
||||
}));
|
||||
// set constant
|
||||
var constants = {
|
||||
'process.env.WEBPACK_DEV_SERVER': 'true',
|
||||
};
|
||||
clientConfig.plugins.unshift(new DefinePlugin(constants));
|
||||
// config dev-server to support client-side routing
|
||||
clientConfig.devServer = {
|
||||
inline: true,
|
||||
historyApiFallback: {
|
||||
rewrites: [
|
||||
{
|
||||
from: /.*/,
|
||||
to: function(context) {
|
||||
return context.parsedUrl.pathname.replace(/.*\/(.*)$/, '/$1');
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var constants = {};
|
||||
if (event === 'build') {
|
||||
@@ -110,14 +125,13 @@ if (event === 'build') {
|
||||
|
||||
configs.forEach((config) => {
|
||||
// set NODE_ENV to production
|
||||
var plugins = config.plugins;
|
||||
var constants = {
|
||||
'process.env.NODE_ENV': '"production"',
|
||||
};
|
||||
plugins.unshift(new DefinePlugin(constants));
|
||||
config.plugins.unshift(new DefinePlugin(constants));
|
||||
|
||||
// use Uglify to remove dead-code
|
||||
plugins.unshift(new UglifyJSPlugin({
|
||||
config.plugins.unshift(new UglifyJSPlugin({
|
||||
uglifyOptions: {
|
||||
compress: {
|
||||
drop_console: true,
|
||||
|
Reference in New Issue
Block a user