mirror of
https://github.com/trambarhq/relaks-wordpress-example.git
synced 2025-09-03 05:02:34 +02:00
Migrated all components to hooks.
Moved propTypes to separate file.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
import Wordpress from 'wordpress';
|
import Wordpress from 'wordpress';
|
||||||
import { Route } from 'routing';
|
import { Route } from 'routing';
|
||||||
import 'style.scss';
|
import 'style.scss';
|
||||||
@@ -10,154 +10,120 @@ import SideNav from 'widgets/side-nav';
|
|||||||
import TopNav from 'widgets/top-nav';
|
import TopNav from 'widgets/top-nav';
|
||||||
import ErrorBoundary from 'widgets/error-boundary';
|
import ErrorBoundary from 'widgets/error-boundary';
|
||||||
|
|
||||||
class FrontEnd extends PureComponent {
|
function FrontEnd(props) {
|
||||||
static displayName = 'FrontEnd';
|
const { routeManager, dataSource, ssr } = props;
|
||||||
|
const [ routeChange, setRouteChange ] = useState();
|
||||||
|
const [ wpChange, setWPChange ] = useState();
|
||||||
|
const route = useMemo(() => {
|
||||||
|
return new Route(routeManager, dataSource);
|
||||||
|
}, [ routeManager, dataSource, routeChange ]);
|
||||||
|
const wp = useMemo(() => {
|
||||||
|
return new Wordpress(dataSource, ssr);
|
||||||
|
}, [ dataSource, ssr, wpChange ]);
|
||||||
|
const [ sideNavCollapsed, collapseSideNav ] = useState(true);
|
||||||
|
const [ topNavCollapsed, collapseTopNav ] = useState(false);
|
||||||
|
|
||||||
constructor(props) {
|
useEffect(() => {
|
||||||
super(props);
|
routeManager.addEventListener('change', setRouteChange);
|
||||||
let { routeManager, dataSource } = this.props;
|
dataSource.addEventListener('change', setWPChange);
|
||||||
this.state = {
|
|
||||||
route: new Route(routeManager, dataSource),
|
return () => {
|
||||||
wp: new Wordpress(dataSource, props.ssr),
|
routeManager.addEventListener('change', setRouteChange);
|
||||||
sideNavCollapsed: true,
|
dataSource.addEventListener('change', setWPChange);
|
||||||
topNavCollapsed: false,
|
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
|
useEffect(() => {
|
||||||
/**
|
let previousPos = getScrollPos();
|
||||||
* Render the application
|
const handleScroll = (evt) => {
|
||||||
*
|
const currentPos = getScrollPos();
|
||||||
* @return {VNode}
|
const delta = currentPos - previousPos;
|
||||||
*/
|
if (delta > 0) {
|
||||||
render() {
|
if (!topNavCollapsed) {
|
||||||
let { route, wp } = this.state;
|
// check to see if we have scroll down efficiently, so that
|
||||||
let { topNavCollapsed } = this.state;
|
// hidden the top nav won't reveal white space
|
||||||
let { sideNavCollapsed } = this.state;
|
const pageContainer = document.getElementsByClassName('page-container')[0];
|
||||||
let PageComponent = route.params.module.default;
|
const page = (pageContainer) ? pageContainer.firstChild : null;
|
||||||
let classNames = [];
|
if (page) {
|
||||||
if (topNavCollapsed) {
|
const pageRect = page.getBoundingClientRect();
|
||||||
classNames.push('top-collapsed');
|
if (pageRect.top <= 40) {
|
||||||
}
|
collapseTopNav(true);
|
||||||
if (sideNavCollapsed) {
|
}
|
||||||
classNames.push('side-collapsed');
|
} else {
|
||||||
}
|
collapseTopNav(true);
|
||||||
let key = route.url;
|
|
||||||
return (
|
|
||||||
<div className={classNames.join(' ')}>
|
|
||||||
<ErrorBoundary>
|
|
||||||
<SideNav route={route} wp={wp} />
|
|
||||||
<TopNav route={route} wp={wp} />
|
|
||||||
<div className="page-container">
|
|
||||||
<PageComponent route={route} wp={wp} key={key} />
|
|
||||||
</div>
|
|
||||||
</ErrorBoundary>
|
|
||||||
<div id="overlay" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Added change handlers when component mounts
|
|
||||||
*/
|
|
||||||
componentDidMount() {
|
|
||||||
let { routeManager, dataSource } = this.props;
|
|
||||||
routeManager.addEventListener('change', this.handleRouteChange);
|
|
||||||
dataSource.addEventListener('change', this.handleDataSourceChange);
|
|
||||||
document.addEventListener('scroll', this.handleScroll);
|
|
||||||
|
|
||||||
if (typeof(window) === 'object') {
|
|
||||||
let Hammer = require('hammerjs');
|
|
||||||
let hammer = new Hammer(document.body, { cssProps: { userSelect: 'auto' } });
|
|
||||||
hammer.on('swipeleft', this.handleSwipeLeft);
|
|
||||||
hammer.on('swiperight', this.handleSwipeRight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
|
||||||
let { dataSource, ssr } = this.props;
|
|
||||||
let { route } = this.state;
|
|
||||||
if (prevProps.ssr !== ssr) {
|
|
||||||
this.setState({ wp: new Wordpress(dataSource, ssr) });
|
|
||||||
}
|
|
||||||
if (prevState.route !== route) {
|
|
||||||
if (!(prevState.route.history.length < route.history.length)) {
|
|
||||||
// not going backward
|
|
||||||
if (document.body.parentElement.scrollTop > 0) {
|
|
||||||
document.body.parentElement.scrollTop = 0;
|
|
||||||
} else if (document.body.scrollTop > 0) {
|
|
||||||
document.body.scrollTop = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the data source changes
|
|
||||||
*
|
|
||||||
* @param {RelaksWordpressDataSourceEvent} evt
|
|
||||||
*/
|
|
||||||
handleDataSourceChange = (evt) => {
|
|
||||||
this.setState({ wp: new Wordpress(evt.target) });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the route changes
|
|
||||||
*
|
|
||||||
* @param {RelaksRouteManagerEvent} evt
|
|
||||||
*/
|
|
||||||
handleRouteChange = (evt) => {
|
|
||||||
let { dataSource } = this.props;
|
|
||||||
this.setState({ route: new Route(evt.target, dataSource) });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the user scrolls the page contents
|
|
||||||
*
|
|
||||||
* @param {Event} evt
|
|
||||||
*/
|
|
||||||
handleScroll = (evt) => {
|
|
||||||
let { topNavCollapsed } = this.state;
|
|
||||||
let container = document.body;
|
|
||||||
let previousPos = this.previousScrollPosition || 0;
|
|
||||||
let currentPos = container.scrollTop;
|
|
||||||
if (currentPos === 0 && container.parentNode.scrollTop > 0) {
|
|
||||||
currentPos = container.parentNode.scrollTop;
|
|
||||||
}
|
|
||||||
let delta = currentPos - previousPos;
|
|
||||||
if (delta > 0) {
|
|
||||||
if (!topNavCollapsed) {
|
|
||||||
// check to see if we have scroll down efficiently, so that
|
|
||||||
// hidden the top nav won't reveal white space
|
|
||||||
let pageContainer = document.getElementsByClassName('page-container')[0];
|
|
||||||
let page = (pageContainer) ? pageContainer.firstChild : null;
|
|
||||||
if (page) {
|
|
||||||
let pageRect = page.getBoundingClientRect();
|
|
||||||
if (pageRect.top <= 40) {
|
|
||||||
this.setState({ topNavCollapsed: true });
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
this.setState({ topNavCollapsed: true });
|
} else if (delta < -10) {
|
||||||
|
if (topNavCollapsed) {
|
||||||
|
collapseTopNav(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
previousPos = currentPos;
|
||||||
if (topNavCollapsed) {
|
};
|
||||||
this.setState({ topNavCollapsed: false });
|
document.addEventListener('scroll', handleScroll);
|
||||||
}
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('scroll', handleScroll);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof(window) === 'object') {
|
||||||
|
const handleSwipeLeft = (evt) => {
|
||||||
|
if (!sideNavCollapsed) {
|
||||||
|
collapseSideNav(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleSwipeRight = (evt) => {
|
||||||
|
if (sideNavCollapsed) {
|
||||||
|
collapseSideNav(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Hammer = require('hammerjs');
|
||||||
|
const hammer = new Hammer(document.body, { cssProps: { userSelect: 'auto' } });
|
||||||
|
hammer.on('swipeleft', handleSwipeLeft);
|
||||||
|
hammer.on('swiperight', handleSwipeRight);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const PageComponent = route.params.module.default;
|
||||||
|
const classNames = [];
|
||||||
|
if (topNavCollapsed) {
|
||||||
|
classNames.push('top-collapsed');
|
||||||
|
}
|
||||||
|
if (sideNavCollapsed) {
|
||||||
|
classNames.push('side-collapsed');
|
||||||
|
}
|
||||||
|
const key = route.url;
|
||||||
|
return (
|
||||||
|
<div className={classNames.join(' ')}>
|
||||||
|
<ErrorBoundary>
|
||||||
|
<SideNav route={route} wp={wp} />
|
||||||
|
<TopNav route={route} wp={wp} />
|
||||||
|
<div className="page-container">
|
||||||
|
<PageComponent route={route} wp={wp} key={key} />
|
||||||
|
</div>
|
||||||
|
</ErrorBoundary>
|
||||||
|
<div id="overlay" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
function getScrollPos() {
|
||||||
|
let pos = document.body.scrollTop;
|
||||||
|
if (pos === 0 && document.body.parentNode.scrollTop > 0) {
|
||||||
|
pos = document.body.parentNode.scrollTop;
|
||||||
}
|
}
|
||||||
this.previousScrollPosition = currentPos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSwipeLeft = (evt) => {
|
function resetScrollPos() {
|
||||||
let { sideNavCollapsed } = this.state;
|
if (document.body.parentElement.scrollTop > 0) {
|
||||||
if (!sideNavCollapsed) {
|
document.body.parentElement.scrollTop = 0;
|
||||||
this.setState({ sideNavCollapsed: true });
|
} else if (document.body.scrollTop > 0) {
|
||||||
}
|
document.body.scrollTop = 0;
|
||||||
}
|
|
||||||
|
|
||||||
handleSwipeRight = (evt) => {
|
|
||||||
let { sideNavCollapsed } = this.state;
|
|
||||||
if (sideNavCollapsed) {
|
|
||||||
this.setState({ sideNavCollapsed: false });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,3 +132,7 @@ export {
|
|||||||
FrontEnd as default,
|
FrontEnd as default,
|
||||||
FrontEnd
|
FrontEnd
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
require('./props');
|
||||||
|
}
|
||||||
|
38
src/main.js
38
src/main.js
@@ -6,50 +6,50 @@ import { Route, routes } from 'routing';
|
|||||||
import WordpressDataSource from 'relaks-wordpress-data-source';
|
import WordpressDataSource from 'relaks-wordpress-data-source';
|
||||||
import RouteManager from 'relaks-route-manager';
|
import RouteManager from 'relaks-route-manager';
|
||||||
import { harvest } from 'relaks-harvest';
|
import { harvest } from 'relaks-harvest';
|
||||||
import Relaks, { plant } from 'relaks';
|
import { plant } from 'relaks/hooks';
|
||||||
|
|
||||||
if (process.env.TARGET === 'browser') {
|
if (process.env.TARGET === 'browser') {
|
||||||
async function initialize(evt) {
|
async function initialize(evt) {
|
||||||
// create data source
|
// create data source
|
||||||
let host = process.env.DATA_HOST || `${location.protocol}//${location.host}`;
|
const host = process.env.DATA_HOST || `${location.protocol}//${location.host}`;
|
||||||
let basePath = process.env.BASE_PATH;
|
const basePath = process.env.BASE_PATH;
|
||||||
let dataSource = new WordpressDataSource({
|
const dataSource = new WordpressDataSource({
|
||||||
baseURL: host + basePath + 'json',
|
baseURL: host + basePath + 'json',
|
||||||
});
|
});
|
||||||
dataSource.activate();
|
dataSource.activate();
|
||||||
|
|
||||||
// create route manager
|
// create route manager
|
||||||
let routeManager = new RouteManager({
|
const routeManager = new RouteManager({
|
||||||
routes,
|
routes,
|
||||||
basePath,
|
basePath,
|
||||||
useHashFallback: (location.protocol !== 'http:' && location.protocol !== 'https:'),
|
useHashFallback: (location.protocol !== 'http:' && location.protocol !== 'https:'),
|
||||||
});
|
});
|
||||||
routeManager.addEventListener('beforechange', (evt) => {
|
routeManager.addEventListener('beforechange', (evt) => {
|
||||||
let route = new Route(routeManager, dataSource);
|
const route = new Route(routeManager, dataSource);
|
||||||
evt.postponeDefault(route.setParameters(evt, true));
|
evt.postponeDefault(route.setParameters(evt, true));
|
||||||
});
|
});
|
||||||
routeManager.activate();
|
routeManager.activate();
|
||||||
await routeManager.start();
|
await routeManager.start();
|
||||||
|
|
||||||
let container = document.getElementById('react-container');
|
const container = document.getElementById('react-container');
|
||||||
if (!process.env.DATA_HOST) {
|
if (!process.env.DATA_HOST) {
|
||||||
// there is SSR support when we're fetching data from the same host
|
// there is SSR support when we're fetching data from the same host
|
||||||
// as the HTML page
|
// as the HTML page
|
||||||
let ssrElement = createElement(FrontEnd, { dataSource, routeManager, ssr: 'hydrate' });
|
const ssrElement = createElement(FrontEnd, { dataSource, routeManager, ssr: 'hydrate' });
|
||||||
let seeds = await harvest(ssrElement, { seeds: true });
|
const seeds = await harvest(ssrElement, { seeds: true });
|
||||||
plant(seeds);
|
plant(seeds);
|
||||||
hydrate(ssrElement, container);
|
hydrate(ssrElement, container);
|
||||||
}
|
}
|
||||||
let csrElement = createElement(FrontEnd, { dataSource, routeManager });
|
const csrElement = createElement(FrontEnd, { dataSource, routeManager });
|
||||||
render(csrElement, container);
|
render(csrElement, container);
|
||||||
|
|
||||||
// check for changes periodically
|
// check for changes periodically
|
||||||
let mtimeURL = host + basePath + '.mtime';
|
const mtimeURL = host + basePath + '.mtime';
|
||||||
let mtimeLast;
|
let mtimeLast;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
try {
|
try {
|
||||||
let res = await fetch(mtimeURL);
|
const res = await fetch(mtimeURL);
|
||||||
let mtime = await res.text();
|
const mtime = await res.text();
|
||||||
if (mtime !== mtimeLast) {
|
if (mtime !== mtimeLast) {
|
||||||
if (mtimeLast) {
|
if (mtimeLast) {
|
||||||
dataSource.invalidate();
|
dataSource.invalidate();
|
||||||
@@ -65,28 +65,28 @@ if (process.env.TARGET === 'browser') {
|
|||||||
window.addEventListener('load', initialize);
|
window.addEventListener('load', initialize);
|
||||||
} else if (process.env.TARGET === 'node') {
|
} else if (process.env.TARGET === 'node') {
|
||||||
async function serverSideRender(options) {
|
async function serverSideRender(options) {
|
||||||
let basePath = process.env.BASE_PATH;
|
const basePath = process.env.BASE_PATH;
|
||||||
let dataSource = new WordpressDataSource({
|
const dataSource = new WordpressDataSource({
|
||||||
baseURL: options.host + basePath + 'json',
|
baseURL: options.host + basePath + 'json',
|
||||||
fetchFunc: options.fetch,
|
fetchFunc: options.fetch,
|
||||||
});
|
});
|
||||||
dataSource.activate();
|
dataSource.activate();
|
||||||
|
|
||||||
let routeManager = new RouteManager({
|
const routeManager = new RouteManager({
|
||||||
routes,
|
routes,
|
||||||
basePath,
|
basePath,
|
||||||
});
|
});
|
||||||
routeManager.addEventListener('beforechange', (evt) => {
|
routeManager.addEventListener('beforechange', (evt) => {
|
||||||
let route = new Route(routeManager, dataSource);
|
const route = new Route(routeManager, dataSource);
|
||||||
evt.postponeDefault(route.setParameters(evt, false));
|
evt.postponeDefault(route.setParameters(evt, false));
|
||||||
});
|
});
|
||||||
routeManager.activate();
|
routeManager.activate();
|
||||||
await routeManager.start(options.path);
|
await routeManager.start(options.path);
|
||||||
|
|
||||||
let ssrElement = createElement(FrontEnd, { dataSource, routeManager, ssr: options.target });
|
const ssrElement = createElement(FrontEnd, { dataSource, routeManager, ssr: options.target });
|
||||||
return harvest(ssrElement);
|
return harvest(ssrElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.render = serverSideRender;
|
exports.render = serverSideRender;
|
||||||
exports.basePath = process.env.BASE_PATH;
|
exports.basePath = process.env.BASE_PATH;
|
||||||
}
|
}
|
@@ -1,35 +1,24 @@
|
|||||||
import Moment from 'moment';
|
import Moment from 'moment';
|
||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
import { AsyncComponent } from 'relaks';
|
import Relaks, { useProgress } from 'relaks/hooks';
|
||||||
import { Route } from 'routing';
|
|
||||||
import WordPress from 'wordpress';
|
|
||||||
|
|
||||||
import Breadcrumb from 'widgets/breadcrumb';
|
import Breadcrumb from 'widgets/breadcrumb';
|
||||||
import PostList from 'widgets/post-list';
|
import PostList from 'widgets/post-list';
|
||||||
|
|
||||||
class ArchivePage extends AsyncComponent {
|
async function ArchivePage(props) {
|
||||||
static displayName = 'ArchivePage';
|
const { wp, route } = props;
|
||||||
|
const { date } = route.params;
|
||||||
|
const [ show ] = useProgress();
|
||||||
|
|
||||||
async renderAsync(meanwhile) {
|
render();
|
||||||
let { wp, route } = this.props;
|
const posts = await wp.fetchPostsInMonth(date);
|
||||||
let { date } = route.params;
|
render();
|
||||||
let props = { route };
|
|
||||||
meanwhile.show(<ArchivePageSync {...props} />);
|
|
||||||
props.posts = await wp.fetchPostsInMonth(date);
|
|
||||||
return <ArchivePageSync {...props} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ArchivePageSync extends PureComponent {
|
function render() {
|
||||||
static displayName = 'ArchivePageSync';
|
const month = Moment(new Date(date.year, date.month - 1, 1));
|
||||||
|
const monthLabel = month.format('MMMM YYYY');
|
||||||
render() {
|
const trail = [ { label: 'Archives' }, { label: monthLabel } ];
|
||||||
let { route, posts } = this.props;
|
show(
|
||||||
let { date } = route.params;
|
|
||||||
let month = Moment(new Date(date.year, date.month - 1, 1));
|
|
||||||
let monthLabel = month.format('MMMM YYYY');
|
|
||||||
let trail = [ { label: 'Archives' }, { label: monthLabel } ];
|
|
||||||
return (
|
|
||||||
<div className="page">
|
<div className="page">
|
||||||
<Breadcrumb trail={trail} />
|
<Breadcrumb trail={trail} />
|
||||||
<PostList route={route} posts={posts} minimum={100} />
|
<PostList route={route} posts={posts} minimum={100} />
|
||||||
@@ -38,22 +27,9 @@ class ArchivePageSync extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
const component = Relaks(ArchivePage);
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
ArchivePage.propTypes = {
|
|
||||||
wp: PropTypes.instanceOf(WordPress),
|
|
||||||
route: PropTypes.instanceOf(Route),
|
|
||||||
};
|
|
||||||
ArchivePageSync.propTypes = {
|
|
||||||
posts: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
month: PropTypes.instanceOf(Moment),
|
|
||||||
route: PropTypes.instanceOf(Route),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ArchivePage as default,
|
component as default,
|
||||||
ArchivePage,
|
component as ArchivePage,
|
||||||
ArchivePageSync,
|
|
||||||
};
|
};
|
||||||
|
@@ -1,44 +1,34 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
import { AsyncComponent } from 'relaks';
|
import Relaks, { useProgress } from 'relaks/hooks';
|
||||||
import { Route } from 'routing';
|
|
||||||
import WordPress from 'wordpress';
|
|
||||||
|
|
||||||
import Breadcrumb from 'widgets/breadcrumb';
|
import Breadcrumb from 'widgets/breadcrumb';
|
||||||
import PostList from 'widgets/post-list';
|
import PostList from 'widgets/post-list';
|
||||||
|
|
||||||
class CategoryPage extends AsyncComponent {
|
async function CategoryPage(props) {
|
||||||
static displayName = 'CategoryPage';
|
const { wp, route } = props;
|
||||||
|
const { categorySlug } = route.params;
|
||||||
|
const [ show ] = useProgress();
|
||||||
|
|
||||||
async renderAsync(meanwhile) {
|
render();
|
||||||
let { wp, route } = this.props;
|
const category = await wp.fetchCategory(categorySlug);
|
||||||
let { categorySlug } = route.params;
|
const parentCategories = await wp.fetchParentCategories(category);
|
||||||
let props = { route };
|
render();
|
||||||
meanwhile.show(<CategoryPageSync {...props} />);
|
const posts = await wp.fetchPostsInCategory(category);
|
||||||
props.category = await wp.fetchCategory(categorySlug);
|
render();
|
||||||
props.parentCategories = await wp.fetchParentCategories(props.category);
|
|
||||||
meanwhile.show(<CategoryPageSync {...props} />);
|
|
||||||
props.posts = await wp.fetchPostsInCategory(props.category);
|
|
||||||
return <CategoryPageSync {...props} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CategoryPageSync extends PureComponent {
|
function render() {
|
||||||
static displayName = 'CategoryPageSync';
|
const trail = [ { label: 'Categories' } ];
|
||||||
|
const categoryLabel = _.get(category, 'name', '');
|
||||||
render() {
|
|
||||||
let { route, posts, category, parentCategories } = this.props;
|
|
||||||
let trail = [ { label: 'Categories' } ];
|
|
||||||
let categoryLabel = _.get(category, 'name', '');
|
|
||||||
if (parentCategories) {
|
if (parentCategories) {
|
||||||
for (let parentCategory of parentCategories) {
|
for (let parentCategory of parentCategories) {
|
||||||
let label = _.get(parentCategory, 'name', '');
|
const label = _.get(parentCategory, 'name', '');
|
||||||
let url = route.prefetchObjectURL(parentCategory);
|
const url = route.prefetchObjectURL(parentCategory);
|
||||||
trail.push({ label, url });
|
trail.push({ label, url });
|
||||||
}
|
}
|
||||||
trail.push({ label: categoryLabel });
|
trail.push({ label: categoryLabel });
|
||||||
}
|
}
|
||||||
return (
|
show(
|
||||||
<div className="page">
|
<div className="page">
|
||||||
<Breadcrumb trail={trail} />
|
<Breadcrumb trail={trail} />
|
||||||
<PostList route={route} posts={posts} minimum={40} />
|
<PostList route={route} posts={posts} minimum={40} />
|
||||||
@@ -47,23 +37,9 @@ class CategoryPageSync extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
const component = Relaks(CategoryPage);
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
CategoryPage.propTypes = {
|
|
||||||
wp: PropTypes.instanceOf(WordPress),
|
|
||||||
route: PropTypes.instanceOf(Route),
|
|
||||||
};
|
|
||||||
CategoryPageSync.propTypes = {
|
|
||||||
category: PropTypes.object,
|
|
||||||
parentCategories: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
posts: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
route: PropTypes.instanceOf(Route),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
CategoryPage as default,
|
component as default,
|
||||||
CategoryPage,
|
component as CategoryPage,
|
||||||
CategoryPageSync,
|
|
||||||
};
|
};
|
||||||
|
@@ -1,44 +1,34 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
import { AsyncComponent } from 'relaks';
|
import Relaks, { useProgress } from 'relaks/hooks';
|
||||||
import { Route } from 'routing';
|
|
||||||
import WordPress from 'wordpress';
|
|
||||||
|
|
||||||
import HTML from 'widgets/html';
|
import HTML from 'widgets/html';
|
||||||
import Breadcrumb from 'widgets/breadcrumb';
|
import Breadcrumb from 'widgets/breadcrumb';
|
||||||
import PageView from 'widgets/page-view';
|
import PageView from 'widgets/page-view';
|
||||||
import PageList from 'widgets/page-list';
|
import PageList from 'widgets/page-list';
|
||||||
|
|
||||||
class PagePage extends AsyncComponent {
|
async function PagePage(props) {
|
||||||
static displayName = 'PagePage';
|
const { wp, route } = props;
|
||||||
|
const { pageSlug } = route.params;
|
||||||
|
const [ show ] = useProgress();
|
||||||
|
|
||||||
async renderAsync(meanwhile) {
|
render();
|
||||||
let { wp, route } = this.props;
|
const page = await wp.fetchPage(pageSlug);
|
||||||
let { pageSlug } = route.params;
|
const parentPages = await wp.fetchParentPages(page);
|
||||||
let props = { route };
|
render();
|
||||||
meanwhile.show(<PagePageSync {...props} />);
|
const childPages = await wp.fetchChildPages(page);
|
||||||
props.page = await wp.fetchPage(pageSlug);
|
render();
|
||||||
props.parentPages = await wp.fetchParentPages(props.page);
|
|
||||||
meanwhile.show(<PagePageSync {...props} />);
|
|
||||||
props.childPages = await wp.fetchChildPages(props.page);
|
|
||||||
return <PagePageSync {...props} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PagePageSync extends PureComponent {
|
function render() {
|
||||||
static displayName = 'PagePageSync';
|
const trail = [];
|
||||||
|
|
||||||
render() {
|
|
||||||
let { route, page, parentPages, childPages } = this.props;
|
|
||||||
let trail = [];
|
|
||||||
if (parentPages) {
|
if (parentPages) {
|
||||||
for (let parentPage of parentPages) {
|
for (let parentPage of parentPages) {
|
||||||
let title = _.get(parentPage, 'title.rendered', '');
|
const title = _.get(parentPage, 'title.rendered', '');
|
||||||
let url = route.prefetchObjectURL(parentPage);
|
const url = route.prefetchObjectURL(parentPage);
|
||||||
trail.push({ label: <HTML text={title} />, url })
|
trail.push({ label: <HTML text={title} />, url })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
show(
|
||||||
<div className="page">
|
<div className="page">
|
||||||
<Breadcrumb trail={trail} />
|
<Breadcrumb trail={trail} />
|
||||||
<PageView page={page} transform={route.transformNode} />
|
<PageView page={page} transform={route.transformNode} />
|
||||||
@@ -48,23 +38,9 @@ class PagePageSync extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
const component = Relaks(PagePage);
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
PagePage.propTypes = {
|
|
||||||
wp: PropTypes.instanceOf(WordPress).isRequired,
|
|
||||||
route: PropTypes.instanceOf(Route).isRequired,
|
|
||||||
};
|
|
||||||
PagePageSync.propTypes = {
|
|
||||||
page: PropTypes.object,
|
|
||||||
parentPages: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
childPages: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
route: PropTypes.instanceOf(Route).isRequired,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
PagePage as default,
|
component as default,
|
||||||
PagePage,
|
component as PagePage,
|
||||||
PagePageSync,
|
|
||||||
};
|
};
|
||||||
|
@@ -1,46 +1,59 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import Moment from 'moment';
|
import Moment from 'moment';
|
||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
import { AsyncComponent } from 'relaks';
|
import Relaks, { useProgress } from 'relaks/hooks';
|
||||||
import { Route } from 'routing';
|
|
||||||
import WordPress from 'wordpress';
|
|
||||||
|
|
||||||
import Breadcrumb from 'widgets/breadcrumb';
|
import Breadcrumb from 'widgets/breadcrumb';
|
||||||
import PostView from 'widgets/post-view';
|
import PostView from 'widgets/post-view';
|
||||||
import TagList from 'widgets/tag-list';
|
import TagList from 'widgets/tag-list';
|
||||||
import CommentSection from 'widgets/comment-section';
|
import CommentSection from 'widgets/comment-section';
|
||||||
|
|
||||||
class PostPage extends AsyncComponent {
|
async function PostPage(props) {
|
||||||
static displayName = 'PostPage';
|
const { wp, route } = props;
|
||||||
|
const { postSlug } = route.params;
|
||||||
|
const [ show ] = useProgress();
|
||||||
|
|
||||||
async renderAsync(meanwhile) {
|
render();
|
||||||
let { wp, route } = this.props;
|
const post = await wp.fetchPost(postSlug);
|
||||||
let { postSlug } = route.params;
|
render();
|
||||||
let props = { route };
|
const categories = await findCategoryChain(post);
|
||||||
meanwhile.show(<PostPageSync {...props} />);
|
render();
|
||||||
props.post = await wp.fetchPost(postSlug);
|
const author = await wp.fetchAuthor(post);
|
||||||
meanwhile.show(<PostPageSync {...props} />);
|
render();
|
||||||
props.categories = await this.findCategoryChain(props.post);
|
const tags = await wp.fetchTagsOfPost(post);
|
||||||
meanwhile.show(<PostPageSync {...props} />);
|
render()
|
||||||
props.author = await wp.fetchAuthor(props.post);
|
let comments;
|
||||||
meanwhile.show(<PostPageSync {...props} />);
|
if (!wp.ssr) {
|
||||||
props.tags = await wp.fetchTagsOfPost(props.post);
|
comments = await wp.fetchComments(post);
|
||||||
if (!wp.ssr) {
|
render();
|
||||||
meanwhile.show(<PostPageSync {...props} />);
|
|
||||||
props.comments = await wp.fetchComments(props.post);
|
|
||||||
}
|
|
||||||
return <PostPageSync {...props} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async findCategoryChain(post) {
|
function render() {
|
||||||
|
const trail = [ { label: 'Categories' } ];
|
||||||
|
if (categories) {
|
||||||
|
for (let category of categories) {
|
||||||
|
const label = _.get(category, 'name', '');
|
||||||
|
const url = route.prefetchObjectURL(category);
|
||||||
|
trail.push({ label, url });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
show(
|
||||||
|
<div className="page">
|
||||||
|
<Breadcrumb trail={trail} />
|
||||||
|
<PostView post={post} author={author} transform={route.transformNode} />
|
||||||
|
<TagList route={route} tags={tags} />
|
||||||
|
<CommentSection comments={comments} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findCategoryChain(post) {
|
||||||
if (!post) return [];
|
if (!post) return [];
|
||||||
let ids = post.categories;
|
const allCategories = await wp.fetchCategories();
|
||||||
let { wp, route } = this.props;
|
|
||||||
let allCategories = await wp.fetchCategories();
|
|
||||||
|
|
||||||
// add categories, including their parents as well
|
// add categories, including their parents as well
|
||||||
let applicable = [];
|
const applicable = [];
|
||||||
let include = (id) => {
|
const include = (id) => {
|
||||||
let category = _.find(allCategories, { id })
|
let category = _.find(allCategories, { id })
|
||||||
if (category) {
|
if (category) {
|
||||||
if (!_.includes(applicable, category)) {
|
if (!_.includes(applicable, category)) {
|
||||||
@@ -50,20 +63,20 @@ class PostPage extends AsyncComponent {
|
|||||||
include(category.parent);
|
include(category.parent);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for (let id of ids) {
|
for (let id of post.categories) {
|
||||||
include(id);
|
include(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// see how recently a category was visited
|
// see how recently a category was visited
|
||||||
let historyIndex = (category) => {
|
const historyIndex = (category) => {
|
||||||
let predicate = { params: { categorySlug: category.slug }};
|
const predicate = { params: { categorySlug: category.slug }};
|
||||||
return _.findLastIndex(route.history, predicate);
|
return _.findLastIndex(route.history, predicate);
|
||||||
};
|
};
|
||||||
// see how deep a category is
|
// see how deep a category is
|
||||||
let depth = (category) => {
|
const depth = (category) => {
|
||||||
if (category.parent) {
|
if (category.parent) {
|
||||||
let predicate = { id: category.parent };
|
const predicate = { id: category.parent };
|
||||||
let parent = _.find(allCategories, predicate);
|
const parent = _.find(allCategories, predicate);
|
||||||
if (parent) {
|
if (parent) {
|
||||||
return depth(parent) + 1;
|
return depth(parent) + 1;
|
||||||
}
|
}
|
||||||
@@ -74,10 +87,10 @@ class PostPage extends AsyncComponent {
|
|||||||
// order applicable categories based on how recently it was visited,
|
// order applicable categories based on how recently it was visited,
|
||||||
// how deep it is, and alphabetically; the first criteria makes our
|
// how deep it is, and alphabetically; the first criteria makes our
|
||||||
// breadcrumb works more sensibly
|
// breadcrumb works more sensibly
|
||||||
applicable = _.orderBy(applicable, [ historyIndex, depth, 'name' ], [ 'desc', 'desc', 'asc' ]);
|
const ordered = _.orderBy(applicable, [ historyIndex, depth, 'name' ], [ 'desc', 'desc', 'asc' ]);
|
||||||
let anchorCategory = _.first(applicable);
|
const anchorCategory = _.first(ordered);
|
||||||
|
|
||||||
let trail = [];
|
const trail = [];
|
||||||
if (anchorCategory) {
|
if (anchorCategory) {
|
||||||
// add category and its ancestors
|
// add category and its ancestors
|
||||||
for (let c = anchorCategory; c; c = _.find(applicable, { id: c.parent })) {
|
for (let c = anchorCategory; c; c = _.find(applicable, { id: c.parent })) {
|
||||||
@@ -94,47 +107,9 @@ class PostPage extends AsyncComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PostPageSync extends PureComponent {
|
const component = Relaks(PostPage);
|
||||||
static displayName = 'PostPageSync';
|
|
||||||
|
|
||||||
render() {
|
|
||||||
let { route, categories, post, author, tags, comments } = this.props;
|
|
||||||
let trail = [ { label: 'Categories' } ];
|
|
||||||
for (let category of categories) {
|
|
||||||
let label = _.get(category, 'name', '');
|
|
||||||
let url = route.prefetchObjectURL(category);
|
|
||||||
trail.push({ label, url });
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="page">
|
|
||||||
<Breadcrumb trail={trail} />
|
|
||||||
<PostView post={post} author={author} transform={route.transformNode} />
|
|
||||||
<TagList route={route} tags={tags} />
|
|
||||||
<CommentSection comments={comments} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
PostPage.propTypes = {
|
|
||||||
wp: PropTypes.instanceOf(WordPress),
|
|
||||||
route: PropTypes.instanceOf(Route),
|
|
||||||
};
|
|
||||||
PostPageSync.propTypes = {
|
|
||||||
categories: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
tags: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
post: PropTypes.object,
|
|
||||||
author: PropTypes.object,
|
|
||||||
comments: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
route: PropTypes.instanceOf(Route),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
PostPage as default,
|
component as default,
|
||||||
PostPage,
|
component as PostPage,
|
||||||
PostPageSync,
|
|
||||||
};
|
};
|
||||||
|
@@ -1,42 +1,31 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
import { AsyncComponent } from 'relaks';
|
import Relaks, { useProgress } from 'relaks/hooks';
|
||||||
import { Route } from 'routing';
|
|
||||||
import WordPress from 'wordpress';
|
|
||||||
|
|
||||||
import Breadcrumb from 'widgets/breadcrumb';
|
import Breadcrumb from 'widgets/breadcrumb';
|
||||||
import PostList from 'widgets/post-list';
|
import PostList from 'widgets/post-list';
|
||||||
|
|
||||||
class SearchPage extends AsyncComponent {
|
async function SearchPage(props) {
|
||||||
static displayName = 'SearchPage';
|
const { wp, route } = props;
|
||||||
|
const { search } = route.params;
|
||||||
|
const [ show ] = useProgress();
|
||||||
|
|
||||||
async renderAsync(meanwhile) {
|
render();
|
||||||
let { wp, route } = this.props;
|
const posts = await wp.fetchMatchingPosts(search);
|
||||||
let { search } = route.params;
|
render();
|
||||||
let props = { route };
|
|
||||||
meanwhile.show(<SearchPageSync {...props} />);
|
|
||||||
props.posts = await wp.fetchMatchingPosts(search);
|
|
||||||
return <SearchPageSync {...props} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchPageSync extends PureComponent {
|
function render() {
|
||||||
static displayName = 'SearchPageSync';
|
const trail = [ { label: 'Search' } ];
|
||||||
|
|
||||||
render() {
|
|
||||||
let { route, posts } = this.props;
|
|
||||||
let { search } = route.params;
|
|
||||||
let trail = [ { label: 'Search' } ];
|
|
||||||
if (posts) {
|
if (posts) {
|
||||||
let count = posts.total;
|
const count = posts.total;
|
||||||
if (typeof(count) === 'number') {
|
if (typeof(count) === 'number') {
|
||||||
let s = (count === 1) ? '' : 's';
|
const s = (count === 1) ? '' : 's';
|
||||||
let msg = `${count} matching article${s}`;
|
const msg = `${count} matching article${s}`;
|
||||||
trail.push({ label: msg });
|
trail.push({ label: msg });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
trail.push({ label: '...' });
|
trail.push({ label: '...' });
|
||||||
}
|
}
|
||||||
return (
|
show(
|
||||||
<div className="page">
|
<div className="page">
|
||||||
<Breadcrumb trail={trail} />
|
<Breadcrumb trail={trail} />
|
||||||
<PostList route={route} posts={posts} minimum={40} maximum={1000} />
|
<PostList route={route} posts={posts} minimum={40} maximum={1000} />
|
||||||
@@ -45,22 +34,9 @@ class SearchPageSync extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
const component = Relaks(SearchPage);
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
SearchPage.propTypes = {
|
|
||||||
wp: PropTypes.instanceOf(WordPress),
|
|
||||||
route: PropTypes.instanceOf(Route),
|
|
||||||
};
|
|
||||||
SearchPageSync.propTypes = {
|
|
||||||
categories: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
posts: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
route: PropTypes.instanceOf(Route),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
SearchPage as default,
|
component as default,
|
||||||
SearchPage,
|
component as SearchPage,
|
||||||
SearchPageSync,
|
|
||||||
};
|
};
|
||||||
|
@@ -1,35 +1,25 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
import { AsyncComponent } from 'relaks';
|
import Relaks, { useProgress } from 'relaks/hooks';
|
||||||
import { Route } from 'routing';
|
|
||||||
import WordPress from 'wordpress';
|
|
||||||
|
|
||||||
import Breadcrumb from 'widgets/breadcrumb';
|
import Breadcrumb from 'widgets/breadcrumb';
|
||||||
import PostList from 'widgets/post-list';
|
import PostList from 'widgets/post-list';
|
||||||
|
|
||||||
class TagPage extends AsyncComponent {
|
async function TagPage(props) {
|
||||||
static displayName = 'TagPage';
|
const { wp, route } = props;
|
||||||
|
const { tagSlug } = route.params;
|
||||||
|
const [ show ] = useProgress();
|
||||||
|
|
||||||
async renderAsync(meanwhile) {
|
render();
|
||||||
let { wp, route } = this.props;
|
const tag = await wp.fetchTag(tagSlug);
|
||||||
let { tagSlug } = route.params;
|
render();
|
||||||
let props = { route };
|
const posts = await wp.fetchPostsWithTag(tag);
|
||||||
meanwhile.show(<TagPageSync {...props} />);
|
render();
|
||||||
props.tag = await wp.fetchTag(tagSlug);
|
|
||||||
meanwhile.show(<TagPageSync {...props} />);
|
|
||||||
props.posts = await wp.fetchPostsWithTag(props.tag);
|
|
||||||
return <TagPageSync {...props} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TagPageSync extends PureComponent {
|
function render() {
|
||||||
static displayName = 'TagPageSync';
|
const tagLabel = _.get(tag, 'name', '');
|
||||||
|
const trail = [ { label: 'Tags' }, { label: tagLabel } ];
|
||||||
render() {
|
show(
|
||||||
let { route, posts, tag } = this.props;
|
|
||||||
let tagLabel = _.get(tag, 'name', '');
|
|
||||||
let trail = [ { label: 'Tags' }, { label: tagLabel } ];
|
|
||||||
return (
|
|
||||||
<div className="page">
|
<div className="page">
|
||||||
<Breadcrumb trail={trail} />
|
<Breadcrumb trail={trail} />
|
||||||
<PostList route={route} posts={posts} minimum={40} />
|
<PostList route={route} posts={posts} minimum={40} />
|
||||||
@@ -38,22 +28,9 @@ class TagPageSync extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
const component = Relaks(TagPage);
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
TagPage.propTypes = {
|
|
||||||
wp: PropTypes.instanceOf(WordPress),
|
|
||||||
route: PropTypes.instanceOf(Route),
|
|
||||||
};
|
|
||||||
TagPageSync.propTypes = {
|
|
||||||
tag: PropTypes.object,
|
|
||||||
posts: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
route: PropTypes.instanceOf(Route),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
TagPage as default,
|
component as default,
|
||||||
TagPage,
|
component as TagPage,
|
||||||
TagPageSync,
|
|
||||||
};
|
};
|
||||||
|
@@ -1,30 +1,20 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
import { AsyncComponent } from 'relaks';
|
import Relaks, { useProgress } from 'relaks/hooks';
|
||||||
import { Route } from 'routing';
|
|
||||||
import WordPress from 'wordpress';
|
|
||||||
|
|
||||||
import PostList from 'widgets/post-list';
|
import PostList from 'widgets/post-list';
|
||||||
|
|
||||||
class WelcomePage extends AsyncComponent {
|
async function WelcomePage(props) {
|
||||||
static displayName = 'WelcomePage';
|
const { wp, route } = props;
|
||||||
|
const [ show ] = useProgress();
|
||||||
|
|
||||||
async renderAsync(meanwhile) {
|
render();
|
||||||
let { wp, route } = this.props;
|
const posts = await wp.fetchPosts();
|
||||||
let props = { route };
|
render();
|
||||||
meanwhile.show(<WelcomePageSync {...props} />)
|
const medias = await wp.fetchFeaturedMedias(posts, 10);
|
||||||
props.posts = await wp.fetchPosts();
|
render();
|
||||||
meanwhile.show(<WelcomePageSync {...props} />)
|
|
||||||
props.medias = await wp.fetchFeaturedMedias(props.posts, 10);
|
|
||||||
return <WelcomePageSync {...props} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class WelcomePageSync extends PureComponent {
|
function render() {
|
||||||
static displayName = 'WelcomePageSync';
|
show(
|
||||||
|
|
||||||
render() {
|
|
||||||
let { route, posts, medias } = this.props;
|
|
||||||
return (
|
|
||||||
<div className="page">
|
<div className="page">
|
||||||
<PostList route={route} posts={posts} medias={medias} minimum={40} />
|
<PostList route={route} posts={posts} medias={medias} minimum={40} />
|
||||||
</div>
|
</div>
|
||||||
@@ -32,23 +22,9 @@ class WelcomePageSync extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
const component = Relaks(WelcomePage);
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
WelcomePage.propTypes = {
|
|
||||||
wp: PropTypes.instanceOf(WordPress),
|
|
||||||
route: PropTypes.instanceOf(Route),
|
|
||||||
};
|
|
||||||
WelcomePageSync.propTypes = {
|
|
||||||
categories: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
posts: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
medias: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
route: PropTypes.instanceOf(Route),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
WelcomePage as default,
|
component as default,
|
||||||
WelcomePage,
|
component as WelcomePage,
|
||||||
WelcomePageSync,
|
|
||||||
};
|
};
|
||||||
|
124
src/props.js
Normal file
124
src/props.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import * as PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { Route } from 'routing';
|
||||||
|
import WordPress from 'wordpress';
|
||||||
|
|
||||||
|
import ArchivePage from 'pages/archive-page';
|
||||||
|
import CategoryPage from 'pages/category-page';
|
||||||
|
import PagePage from 'pages/page-page';
|
||||||
|
import PostPage from 'pages/post-page';
|
||||||
|
import SearchPage from 'pages/search-page';
|
||||||
|
import TagPage from 'pages/tag-page';
|
||||||
|
import WelcomePage from 'pages/welcome-page';
|
||||||
|
|
||||||
|
ArchivePage.propTypes = {
|
||||||
|
wp: PropTypes.instanceOf(WordPress).isRequired,
|
||||||
|
route: PropTypes.instanceOf(Route).isRequired,
|
||||||
|
};
|
||||||
|
CategoryPage.propTypes = {
|
||||||
|
wp: PropTypes.instanceOf(WordPress).isRequired,
|
||||||
|
route: PropTypes.instanceOf(Route).isRequired,
|
||||||
|
};
|
||||||
|
PagePage.propTypes = {
|
||||||
|
wp: PropTypes.instanceOf(WordPress).isRequired,
|
||||||
|
route: PropTypes.instanceOf(Route).isRequired,
|
||||||
|
};
|
||||||
|
PostPage.propTypes = {
|
||||||
|
wp: PropTypes.instanceOf(WordPress).isRequired,
|
||||||
|
route: PropTypes.instanceOf(Route).isRequired,
|
||||||
|
};
|
||||||
|
SearchPage.propTypes = {
|
||||||
|
wp: PropTypes.instanceOf(WordPress).isRequired,
|
||||||
|
route: PropTypes.instanceOf(Route).isRequired,
|
||||||
|
};
|
||||||
|
TagPage.propTypes = {
|
||||||
|
wp: PropTypes.instanceOf(WordPress).isRequired,
|
||||||
|
route: PropTypes.instanceOf(Route).isRequired,
|
||||||
|
};
|
||||||
|
WelcomePage.propTypes = {
|
||||||
|
wp: PropTypes.instanceOf(WordPress).isRequired,
|
||||||
|
route: PropTypes.instanceOf(Route).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
import Breadcrumb from 'widgets/breadcrumb';
|
||||||
|
import CommentListView from 'widgets/comment-list-view';
|
||||||
|
import CommentList from 'widgets/comment-list';
|
||||||
|
import CommentSection from 'widgets/comment-section';
|
||||||
|
import HTML from 'widgets/html';
|
||||||
|
import ImageDialog from 'widgets/image-dialog';
|
||||||
|
import MediaView from 'widgets/media-view';
|
||||||
|
import PageListView from 'widgets/page-list-view';
|
||||||
|
import PageList from 'widgets/page-list';
|
||||||
|
import PageView from 'widgets/page-view';
|
||||||
|
import PostListView from 'widgets/post-list-view';
|
||||||
|
import PostList from 'widgets/post-list';
|
||||||
|
import PostView from 'widgets/post-view';
|
||||||
|
import SideNav from 'widgets/side-nav';
|
||||||
|
import TagList from 'widgets/tag-list';
|
||||||
|
import TopNav from 'widgets/top-nav';
|
||||||
|
|
||||||
|
Breadcrumb.propTypes = {
|
||||||
|
trail: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
};
|
||||||
|
CommentListView.propTypes = {
|
||||||
|
allComments: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
comment: PropTypes.object,
|
||||||
|
};
|
||||||
|
CommentList.propTypes = {
|
||||||
|
allComments: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
parentCommentID: PropTypes.number,
|
||||||
|
};
|
||||||
|
CommentSection.propTypes = {
|
||||||
|
comments: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
};
|
||||||
|
HTML.propTypes = {
|
||||||
|
text: PropTypes.string,
|
||||||
|
transform: PropTypes.func,
|
||||||
|
};
|
||||||
|
ImageDialog.propTypes = {
|
||||||
|
imageURL: PropTypes.string,
|
||||||
|
onClose: PropTypes.func,
|
||||||
|
};
|
||||||
|
MediaView.propTypes = {
|
||||||
|
media: PropTypes.object,
|
||||||
|
size: PropTypes.string,
|
||||||
|
};
|
||||||
|
PageListView.propTypes = {
|
||||||
|
page: PropTypes.object,
|
||||||
|
route: PropTypes.instanceOf(Route).isRequired,
|
||||||
|
};
|
||||||
|
PageList.propTypes = {
|
||||||
|
pages: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
route: PropTypes.instanceOf(Route).isRequired,
|
||||||
|
};
|
||||||
|
PageView.propTypes = {
|
||||||
|
page: PropTypes.object,
|
||||||
|
transform: PropTypes.func,
|
||||||
|
};
|
||||||
|
PostList.propTypes = {
|
||||||
|
posts: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
medias: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
route: PropTypes.instanceOf(Route),
|
||||||
|
minimum: PropTypes.number,
|
||||||
|
maximum: PropTypes.number,
|
||||||
|
};
|
||||||
|
PostListView.propTypes = {
|
||||||
|
post: PropTypes.object,
|
||||||
|
route: PropTypes.instanceOf(Route).isRequired,
|
||||||
|
};
|
||||||
|
PostView.propTypes = {
|
||||||
|
post: PropTypes.object,
|
||||||
|
author: PropTypes.object,
|
||||||
|
transform: PropTypes.func,
|
||||||
|
};
|
||||||
|
SideNav.propTypes = {
|
||||||
|
wp: PropTypes.instanceOf(WordPress).isRequired,
|
||||||
|
route: PropTypes.instanceOf(Route).isRequired,
|
||||||
|
};
|
||||||
|
TagList.propTypes = {
|
||||||
|
tags: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
};
|
||||||
|
TopNav.propTypes = {
|
||||||
|
wp: PropTypes.instanceOf(WordPress).isRequired,
|
||||||
|
route: PropTypes.instanceOf(Route).isRequired,
|
||||||
|
};
|
@@ -1,27 +1,15 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
class Breadcrumb extends PureComponent {
|
function Breadcrumb(props) {
|
||||||
static displayName = 'Breadcrumb';
|
const { trail } = props;
|
||||||
|
const children = []
|
||||||
render() {
|
let key = 0;
|
||||||
let { trail } = this.props;
|
for (let item of trail) {
|
||||||
let children = []
|
children.push(<a key={key++} href={item.url}>{item.label}</a>);
|
||||||
let key = 0;
|
children.push(' > ');
|
||||||
for (let item of trail) {
|
|
||||||
children.push(<a key={key++} href={item.url}>{item.label}</a>);
|
|
||||||
children.push(' > ');
|
|
||||||
}
|
|
||||||
children.pop();
|
|
||||||
return <h4 className="breadcrumb">{children}</h4>;
|
|
||||||
}
|
}
|
||||||
}
|
children.pop();
|
||||||
|
return <h4 className="breadcrumb">{children}</h4>;
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
Breadcrumb.propTypes = {
|
|
||||||
trail: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@@ -1,31 +1,27 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import HTML from 'widgets/html';
|
import HTML from 'widgets/html';
|
||||||
import CommentList from 'widgets/comment-list';
|
import CommentList from 'widgets/comment-list';
|
||||||
|
|
||||||
class CommentListView extends PureComponent {
|
function CommentListView(props) {
|
||||||
static displayName = 'CommentListView';
|
const { comment, allComments } = props;
|
||||||
|
const content = _.get(comment, 'content.rendered', '');
|
||||||
|
const avatarURL = _.get(comment, 'author_avatar_urls.24');
|
||||||
|
const name = _.get(comment, 'author_name');
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
let { comment } = this.props;
|
<div className="comment-list-view">
|
||||||
let content = _.get(comment, 'content.rendered', '');
|
<div className="commenter">
|
||||||
let avatarURL = _.get(comment, 'author_avatar_urls.24');
|
<img className="avatar" src={avatarURL} />
|
||||||
let name = _.get(comment, 'author_name');
|
<span className="name">{name}:</span>
|
||||||
return (
|
|
||||||
<div className="comment-list-view">
|
|
||||||
<div className="commenter">
|
|
||||||
<img className="avatar" src={avatarURL} />
|
|
||||||
<span className="name">{name}:</span>
|
|
||||||
</div>
|
|
||||||
<HTML text={content} />
|
|
||||||
{this.renderReplies()}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
<HTML text={content} />
|
||||||
}
|
{renderReplies()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
renderReplies() {
|
function renderReplies() {
|
||||||
let { comment, allComments } = this.props;
|
|
||||||
if (!_.some(allComments, { parent: comment.id })) {
|
if (!_.some(allComments, { parent: comment.id })) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -33,19 +29,10 @@ class CommentListView extends PureComponent {
|
|||||||
<div className="replies">
|
<div className="replies">
|
||||||
<CommentList allComments={allComments} parentCommentID={comment.id} />
|
<CommentList allComments={allComments} parentCommentID={comment.id} />
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
CommentListView.propTypes = {
|
|
||||||
allComments: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
comment: PropTypes.object,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
CommentListView as default,
|
CommentListView as default,
|
||||||
CommentListView,
|
CommentListView,
|
||||||
|
@@ -1,35 +1,23 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import CommentListView from 'widgets/comment-list-view';
|
import CommentListView from 'widgets/comment-list-view';
|
||||||
|
|
||||||
class CommentList extends PureComponent {
|
function CommentList(props) {
|
||||||
static displayName = 'CommentList'
|
const { allComments, parentCommentID } = props;
|
||||||
|
const comments = _.filter(allComments, { parent: parentCommentID });
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
let { allComments, parentCommentID } = this.props;
|
<div className="comments">
|
||||||
let comments = _.filter(allComments, { parent: parentCommentID });
|
{comments.map(renderComment)}
|
||||||
return (
|
</div>
|
||||||
<div className="comments">
|
);
|
||||||
{
|
|
||||||
_.map(comments, (comment) => {
|
function renderComment(comment, i) {
|
||||||
return <CommentListView comment={comment} allComments={allComments} key={comment.id} />;
|
return <CommentListView comment={comment} allComments={allComments} key={comment.id} />;
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
CommentList.propTypes = {
|
|
||||||
allComments: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
parentCommentID: PropTypes.number,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
CommentList as default,
|
CommentList as default,
|
||||||
CommentList,
|
CommentList,
|
||||||
|
@@ -1,42 +1,19 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import CommentList from 'widgets/comment-list';
|
import CommentList from 'widgets/comment-list';
|
||||||
|
|
||||||
class CommentSection extends PureComponent {
|
function CommentSection(props) {
|
||||||
static displayName = 'CommentSection';
|
const { comments } = props;
|
||||||
|
if (_.isEmpty(comments)) {
|
||||||
render() {
|
return null;
|
||||||
let { comments } = this.props;
|
|
||||||
if (_.isEmpty(comments)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="comment-section">
|
|
||||||
<h3>Comments</h3>
|
|
||||||
<CommentList allComments={comments} parentCommentID={0} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return (
|
||||||
componentDidMount() {
|
<div className="comment-section">
|
||||||
this.componentDidUpdate();
|
<h3>Comments</h3>
|
||||||
}
|
<CommentList allComments={comments} parentCommentID={0} />
|
||||||
|
</div>
|
||||||
componentDidUpdate(prevProps, prevState) {
|
);
|
||||||
let { allComments } = this.props;
|
|
||||||
if (allComments) {
|
|
||||||
allComments.more();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
CommentSection.propTypes = {
|
|
||||||
comments: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@@ -1,22 +1,12 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
import ReactHtmlParser from 'react-html-parser';
|
import ReactHtmlParser from 'react-html-parser';
|
||||||
|
|
||||||
class HTML extends PureComponent {
|
function HTML(props) {
|
||||||
render() {
|
const { text, transform } = props;
|
||||||
let { text, transform } = this.props;
|
const options = { transform };
|
||||||
let options = { transform };
|
// fix unescaped <
|
||||||
// fix unescaped <
|
const fixed = text.replace(/<([^>]*)</g, '<$1<');
|
||||||
text = text.replace(/<([^>]*)</g, '<$1<');
|
return ReactHtmlParser(fixed, options);
|
||||||
return ReactHtmlParser(text, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
HTML.propTypes = {
|
|
||||||
text: PropTypes.string,
|
|
||||||
transform: PropTypes.func,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@@ -1,27 +1,34 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
class ImageDialog extends PureComponent {
|
function ImageDialog(props) {
|
||||||
|
const { imageURL, onClose } = props;
|
||||||
render() {
|
const target = { func: ImageDialog, props };
|
||||||
let { imageURL } = this.props;
|
if (!imageURL) {
|
||||||
if (!imageURL) {
|
return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let container = document.getElementById('overlay');
|
|
||||||
let dialog = this.renderDialog();
|
|
||||||
return ReactDOM.createPortal(dialog, container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderDialog() {
|
const handleCloseClick = (evt) => {
|
||||||
let { imageURL } = this.props;
|
if (onClose) {
|
||||||
|
onClose({
|
||||||
|
type: 'close',
|
||||||
|
target,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const container = document.getElementById('overlay');
|
||||||
|
const dialog = renderDialog();
|
||||||
|
return ReactDOM.createPortal(dialog, container);
|
||||||
|
|
||||||
|
function renderDialog() {
|
||||||
return (
|
return (
|
||||||
<div className="image-dialog">
|
<div className="image-dialog">
|
||||||
<div className="background" onClick={this.handleCloseClick}/>
|
<div className="background" onClick={handleCloseClick}/>
|
||||||
<div className="foreground">
|
<div className="foreground">
|
||||||
<div className="box">
|
<div className="box">
|
||||||
<div className="close-button" onClick={this.handleCloseClick}>
|
<div className="close-button" onClick={handleCloseClick}>
|
||||||
<i className="fa fa-times" />
|
<i className="fa fa-times" />
|
||||||
</div>
|
</div>
|
||||||
<img className="image" src={imageURL} />
|
<img className="image" src={imageURL} />
|
||||||
@@ -30,25 +37,6 @@ class ImageDialog extends PureComponent {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCloseClick = (evt) => {
|
|
||||||
let { onClose } = this.props;
|
|
||||||
if (onClose) {
|
|
||||||
onClose({
|
|
||||||
type: 'close',
|
|
||||||
target: this,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
ImageDialog.propTypes = {
|
|
||||||
imageURL: PropTypes.string,
|
|
||||||
onClose: PropTypes.func,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@@ -1,38 +1,20 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import Moment from 'moment';
|
import Moment from 'moment';
|
||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
class MediaView extends PureComponent {
|
function MediaView(props) {
|
||||||
static displayName = 'MediaView';
|
const { media, size } = props;
|
||||||
|
let info = _.get(media, [ 'media_details', 'sizes', size ]);
|
||||||
render() {
|
if (!info) {
|
||||||
let { media, size } = this.props;
|
info = media;
|
||||||
let info = _.get(media, [ 'media_details', 'sizes', size ]);
|
|
||||||
if (!info) {
|
|
||||||
info = media;
|
|
||||||
}
|
|
||||||
let props = {
|
|
||||||
src: info.source_url,
|
|
||||||
width: info.width,
|
|
||||||
height: info.height,
|
|
||||||
};
|
|
||||||
return <img {...props} />;
|
|
||||||
}
|
}
|
||||||
|
return <img src={info.source_url} width={info.width} height={info.height} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaView.defaultProps = {
|
MediaView.defaultProps = {
|
||||||
size: 'thumbnail',
|
size: 'thumbnail',
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
MediaView.propTypes = {
|
|
||||||
media: PropTypes.object,
|
|
||||||
size: PropTypes.string,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
MediaView as default,
|
MediaView as default,
|
||||||
MediaView,
|
MediaView,
|
||||||
|
@@ -1,31 +1,17 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
import { Route } from 'routing';
|
|
||||||
|
|
||||||
import HTML from 'widgets/html';
|
import HTML from 'widgets/html';
|
||||||
|
|
||||||
class PageListView extends PureComponent {
|
function PageListView(props) {
|
||||||
static displayName = 'PageListView';
|
const { route, page } = props;
|
||||||
|
const title = _.get(page, 'title.rendered', '');
|
||||||
render() {
|
const url = route.prefetchObjectURL(page);
|
||||||
let { route, page } = this.props;
|
return (
|
||||||
let title = _.get(page, 'title.rendered', '');
|
<div className="page-list-view">
|
||||||
let url = route.prefetchObjectURL(page);
|
<a href={url}><HTML text={title} /></a>
|
||||||
return (
|
</div>
|
||||||
<div className="page-list-view">
|
);
|
||||||
<a href={url}><HTML text={title} /></a>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
PageListView.propTypes = {
|
|
||||||
page: PropTypes.object,
|
|
||||||
route: PropTypes.instanceOf(Route).isRequired,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@@ -1,51 +1,32 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { PureComponent } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Route } from 'routing';
|
import { Route } from 'routing';
|
||||||
|
|
||||||
import PageListView from 'widgets/page-list-view';
|
import PageListView from 'widgets/page-list-view';
|
||||||
|
|
||||||
class PageList extends PureComponent {
|
function PageList(props) {
|
||||||
static displayName = 'PageList'
|
let { route, pages } = props;
|
||||||
|
if (!pages) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
useEffect(() => {
|
||||||
let { route, pages } = this.props;
|
pages.more();
|
||||||
if (!pages) {
|
}, [ pages ]);
|
||||||
return null;
|
|
||||||
}
|
return (
|
||||||
|
<ul className="pages">
|
||||||
|
{pages.map(renderPage)}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
|
||||||
|
function renderPage(page, i) {
|
||||||
return (
|
return (
|
||||||
<ul className="pages">
|
<li key={page.id}>
|
||||||
{
|
<PageListView route={route} page={page} />
|
||||||
pages.map((page) => {
|
</li>
|
||||||
return (
|
);
|
||||||
<li key={page.id}>
|
|
||||||
<PageListView route={route} page={page} />
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.componentDidUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
|
||||||
let { pages } = this.props;
|
|
||||||
if (pages) {
|
|
||||||
pages.more();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
PageList.propTypes = {
|
|
||||||
pages: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
route: PropTypes.instanceOf(Route).isRequired,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@@ -1,41 +1,27 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import Moment from 'moment';
|
import Moment from 'moment';
|
||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import HTML from 'widgets/html';
|
import HTML from 'widgets/html';
|
||||||
|
|
||||||
class PageView extends PureComponent {
|
function PageView(props) {
|
||||||
static displayName = 'PageView';
|
const { page, transform } = props;
|
||||||
|
const title = _.get(page, 'title.rendered', '');
|
||||||
|
const content = _.get(page, 'content.rendered', '');
|
||||||
|
const modified = _.get(page, 'modified_gmt');
|
||||||
|
const date = (modified) ? Moment(modified).format('LL') : '';
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
let { page, transform } = this.props;
|
<div className="page">
|
||||||
let title = _.get(page, 'title.rendered', '');
|
<div className="meta">
|
||||||
let content = _.get(page, 'content.rendered', '');
|
<div className="date">{date}</div>
|
||||||
let date = _.get(page, 'modified_gmt');
|
|
||||||
if (date) {
|
|
||||||
date = Moment(date).format('LL');
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="page">
|
|
||||||
<div className="meta">
|
|
||||||
<div className="date">{date}</div>
|
|
||||||
</div>
|
|
||||||
<h1><HTML text={title} /></h1>
|
|
||||||
<div className="content">
|
|
||||||
<HTML text={content} transform={transform}/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
<h1><HTML text={title} /></h1>
|
||||||
}
|
<div className="content">
|
||||||
}
|
<HTML text={content} transform={transform}/>
|
||||||
|
</div>
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
</div>
|
||||||
const PropTypes = require('prop-types');
|
);
|
||||||
|
|
||||||
PageView.propTypes = {
|
|
||||||
page: PropTypes.object,
|
|
||||||
transform: PropTypes.func,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@@ -1,46 +1,26 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import Moment from 'moment';
|
import Moment from 'moment';
|
||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
import { Route } from 'routing';
|
|
||||||
|
|
||||||
import HTML from 'widgets/html';
|
import HTML from 'widgets/html';
|
||||||
import MediaView from 'widgets/media-view';
|
import MediaView from 'widgets/media-view';
|
||||||
|
|
||||||
class PostListView extends PureComponent {
|
function PostListView(props) {
|
||||||
static displayName = 'PostListView';
|
const { route, post, media } = props;
|
||||||
|
const title = _.get(post, 'title.rendered', '');
|
||||||
|
const excerptRendered = _.get(post, 'excerpt.rendered', '');
|
||||||
|
const excerpt = cleanExcerpt(excerptRendered);
|
||||||
|
const url = route.prefetchObjectURL(post);
|
||||||
|
const published = _.get(post, 'date_gmt');
|
||||||
|
const date = (published) ? Moment(published).format('L') : '';
|
||||||
|
|
||||||
render() {
|
if (media) {
|
||||||
let { route, post, media } = this.props;
|
return (
|
||||||
let title = _.get(post, 'title.rendered', '');
|
<div className="post-list-view with-media">
|
||||||
let excerpt = _.get(post, 'excerpt.rendered', '');
|
<div className="media">
|
||||||
excerpt = cleanExcerpt(excerpt);
|
<MediaView media={media} />
|
||||||
let url = route.prefetchObjectURL(post);
|
|
||||||
let date = _.get(post, 'date_gmt');
|
|
||||||
if (date) {
|
|
||||||
date = Moment(date).format('L');
|
|
||||||
}
|
|
||||||
if (media) {
|
|
||||||
return (
|
|
||||||
<div className="post-list-view with-media">
|
|
||||||
<div className="media">
|
|
||||||
<MediaView media={media} />
|
|
||||||
</div>
|
|
||||||
<div className="text">
|
|
||||||
<div className="headline">
|
|
||||||
<h3 className="title">
|
|
||||||
<a href={url}><HTML text={title} /></a>
|
|
||||||
</h3>
|
|
||||||
<div className="date">{date}</div>
|
|
||||||
</div>
|
|
||||||
<div className="excerpt">
|
|
||||||
<HTML text={excerpt} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
<div className="text">
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<div className="post-list-view">
|
|
||||||
<div className="headline">
|
<div className="headline">
|
||||||
<h3 className="title">
|
<h3 className="title">
|
||||||
<a href={url}><HTML text={title} /></a>
|
<a href={url}><HTML text={title} /></a>
|
||||||
@@ -51,28 +31,33 @@ class PostListView extends PureComponent {
|
|||||||
<HTML text={excerpt} />
|
<HTML text={excerpt} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div className="post-list-view">
|
||||||
|
<div className="headline">
|
||||||
|
<h3 className="title">
|
||||||
|
<a href={url}><HTML text={title} /></a>
|
||||||
|
</h3>
|
||||||
|
<div className="date">{date}</div>
|
||||||
|
</div>
|
||||||
|
<div className="excerpt">
|
||||||
|
<HTML text={excerpt} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanExcerpt(excerpt) {
|
||||||
|
const index = excerpt.indexOf('<p class="link-more">');
|
||||||
|
if (index !== -1) {
|
||||||
|
excerpt = excerpt.substr(0, index);
|
||||||
}
|
}
|
||||||
|
return excerpt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanExcerpt(excerpt) {
|
|
||||||
let index = excerpt.indexOf('<p class="link-more">');
|
|
||||||
if (index !== -1) {
|
|
||||||
excerpt = excerpt.substr(0, index);
|
|
||||||
}
|
|
||||||
return excerpt;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
PostListView.propTypes = {
|
|
||||||
post: PropTypes.object,
|
|
||||||
route: PropTypes.instanceOf(Route).isRequired,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
PostListView as default,
|
PostListView as default,
|
||||||
PostListView,
|
PostListView,
|
||||||
|
@@ -1,62 +1,51 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import Moment from 'moment';
|
import Moment from 'moment';
|
||||||
import React, { PureComponent } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Route } from 'routing';
|
|
||||||
|
|
||||||
import PostListView from 'widgets/post-list-view';
|
import PostListView from 'widgets/post-list-view';
|
||||||
|
|
||||||
class PostList extends PureComponent {
|
function PostList(props) {
|
||||||
static displayName = 'PostList'
|
const { route, posts, medias, minimum, maximum } = props;
|
||||||
|
|
||||||
render() {
|
useEffect(() => {
|
||||||
let { route, posts, medias } = this.props;
|
const handleScroll = (evt) => {
|
||||||
if (!posts) {
|
loadMore(0.5);
|
||||||
return null;
|
};
|
||||||
}
|
document.addEventListener('scroll', handleScroll);
|
||||||
return (
|
|
||||||
<div className="posts">
|
|
||||||
{
|
|
||||||
posts.map((post) => {
|
|
||||||
let media = _.find(medias, { id: post.featured_media });
|
|
||||||
return <PostListView route={route} post={post} media={media} key={post.id} />
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
return () => {
|
||||||
document.addEventListener('scroll', this.handleScroll);
|
document.removeEventListener('scroll', handleScroll);
|
||||||
this.componentDidUpdate();
|
};
|
||||||
}
|
});
|
||||||
|
useEffect(() => {
|
||||||
componentDidUpdate(prevProps, prevState) {
|
if (posts && posts.more && posts.length < minimum) {
|
||||||
let { posts, minimum, maximum } = this.props;
|
|
||||||
if (posts && posts.length < minimum) {
|
|
||||||
posts.more();
|
posts.more();
|
||||||
} else {
|
} else {
|
||||||
// load more records if we're still near the bottom
|
loadMore(0.75);
|
||||||
let { scrollTop, scrollHeight } = document.body.parentNode;
|
|
||||||
if (scrollTop > scrollHeight * 0.75) {
|
|
||||||
if (posts && posts.length < maximum) {
|
|
||||||
posts.more();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}, [ posts ]);
|
||||||
|
|
||||||
|
if (!posts) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="posts">
|
||||||
|
{posts.map(renderPost)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
function renderPost(post, i) {
|
||||||
|
let media = _.find(medias, { id: post.featured_media });
|
||||||
|
return <PostListView route={route} post={post} media={media} key={post.id} />
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
function loadMore(fraction) {
|
||||||
document.removeEventListener('scroll', this.handleScroll);
|
const { scrollTop, scrollHeight } = document.body.parentNode;
|
||||||
}
|
if (scrollTop > scrollHeight * fraction) {
|
||||||
|
if (posts && posts.more && posts.length < maximum) {
|
||||||
handleScroll = (evt) => {
|
|
||||||
let { posts, maximum } = this.props;
|
|
||||||
let { scrollTop, scrollHeight } = document.body.parentNode;
|
|
||||||
if (scrollTop > scrollHeight * 0.5) {
|
|
||||||
if (posts && posts.length < maximum) {
|
|
||||||
posts.more();
|
posts.more();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,18 +54,6 @@ PostList.defaultProps = {
|
|||||||
maximum: 500,
|
maximum: 500,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
PostList.propTypes = {
|
|
||||||
posts: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
medias: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
route: PropTypes.instanceOf(Route),
|
|
||||||
minimum: PropTypes.number,
|
|
||||||
maximum: PropTypes.number,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
PostList as default,
|
PostList as default,
|
||||||
PostList,
|
PostList,
|
||||||
|
@@ -1,52 +1,17 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import Moment from 'moment';
|
import Moment from 'moment';
|
||||||
import React, { PureComponent } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
import HTML from 'widgets/html';
|
import HTML from 'widgets/html';
|
||||||
import ImageDialog from 'widgets/image-dialog';
|
import ImageDialog from 'widgets/image-dialog';
|
||||||
|
|
||||||
class PostView extends PureComponent {
|
function PostView(props) {
|
||||||
static displayName = 'PostView';
|
const { post, author, transform } = props;
|
||||||
|
const [ imageURL, setImageURL ] = useState(null);
|
||||||
|
|
||||||
constructor(props) {
|
const handleClick = (evt) => {
|
||||||
super(props);
|
const target = evt.target;
|
||||||
this.state = {
|
const container = evt.currentTarget;
|
||||||
imageURL: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
let { post, author, transform } = this.props;
|
|
||||||
let title = _.get(post, 'title.rendered', '');
|
|
||||||
let content = _.get(post, 'content.rendered', '');
|
|
||||||
let date = _.get(post, 'date_gmt');
|
|
||||||
let name = _.get(author, 'name', '\u00a0');
|
|
||||||
if (date) {
|
|
||||||
date = Moment(date).format('LL');
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="post">
|
|
||||||
<div className="meta">
|
|
||||||
<div className="date">{date}</div>
|
|
||||||
<div className="author">{name}</div>
|
|
||||||
</div>
|
|
||||||
<h1><HTML text={title} /></h1>
|
|
||||||
<div className="content" onClick={this.handleClick}>
|
|
||||||
<HTML text={content} transform={transform} />
|
|
||||||
</div>
|
|
||||||
{this.renderImageDialog()}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderImageDialog() {
|
|
||||||
let { imageURL } = this.state;
|
|
||||||
return <ImageDialog imageURL={imageURL} onClose={this.handleDialogClose} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick = (evt) => {
|
|
||||||
let target = evt.target;
|
|
||||||
let container = evt.currentTarget;
|
|
||||||
if (target.tagName === 'IMG') {
|
if (target.tagName === 'IMG') {
|
||||||
let link;
|
let link;
|
||||||
for (let p = target; p && p !== container; p = p.parentNode) {
|
for (let p = target; p && p !== container; p = p.parentNode) {
|
||||||
@@ -56,26 +21,33 @@ class PostView extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (link) {
|
if (link) {
|
||||||
let imageURL = link.href;
|
setImageURL(link.href);
|
||||||
this.setState({ imageURL });
|
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
handleDialogClose = (evt) => {
|
|
||||||
this.setState({ imageURL: null });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
PostView.propTypes = {
|
|
||||||
post: PropTypes.object,
|
|
||||||
author: PropTypes.object,
|
|
||||||
transform: PropTypes.func,
|
|
||||||
};
|
};
|
||||||
|
const handleDialogClose = (evt) => {
|
||||||
|
setImageURL(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const title = _.get(post, 'title.rendered', '');
|
||||||
|
const content = _.get(post, 'content.rendered', '');
|
||||||
|
const name = _.get(author, 'name', '\u00a0');
|
||||||
|
const published = _.get(post, 'date_gmt');
|
||||||
|
const date = (published) ? Moment(published).format('LL') : '';
|
||||||
|
return (
|
||||||
|
<div className="post">
|
||||||
|
<div className="meta">
|
||||||
|
<div className="date">{date}</div>
|
||||||
|
<div className="author">{name}</div>
|
||||||
|
</div>
|
||||||
|
<h1><HTML text={title} /></h1>
|
||||||
|
<div className="content" onClick={handleClick}>
|
||||||
|
<HTML text={content} transform={transform} />
|
||||||
|
</div>
|
||||||
|
<ImageDialog imageURL={imageURL} onClose={handleDialogClose} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@@ -1,160 +1,134 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import Moment from 'moment';
|
import Moment from 'moment';
|
||||||
import React, { PureComponent } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { AsyncComponent } from 'relaks';
|
import Relaks, { useProgress } from 'relaks/hooks';
|
||||||
import { Route } from 'routing';
|
|
||||||
import WordPress from 'wordpress';
|
|
||||||
|
|
||||||
class SideNav extends AsyncComponent {
|
async function SideNav(props) {
|
||||||
static displayName = 'SideNav';
|
const { wp, route } = props;
|
||||||
|
const { date } = route.params;
|
||||||
|
const [ selectedYear, setSelectedYear ] = useState(() => {
|
||||||
|
return _.get(date, 'year', Moment().year());
|
||||||
|
});
|
||||||
|
const [ show ] = useProgress(50, 50);
|
||||||
|
|
||||||
constructor(props) {
|
const handleYearClick = (evt) => {
|
||||||
super(props);
|
const year = parseInt(evt.currentTarget.getAttribute('data-year'));
|
||||||
let { route } = this.props;
|
if (selectedYear !== year) {
|
||||||
let { date } = route.params;
|
setSelectedYear(year);
|
||||||
let selectedYear = _.get(date, 'year', 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} />);
|
|
||||||
|
|
||||||
// get all categories
|
|
||||||
props.categories = await wp.fetchCategories();
|
|
||||||
meanwhile.show(<SideNavSync {...props} />);
|
|
||||||
|
|
||||||
// get top tags
|
|
||||||
props.tags = await wp.fetchTopTags();
|
|
||||||
meanwhile.show(<SideNavSync {...props} />);
|
|
||||||
|
|
||||||
// get the date range of posts and use that to build the list of
|
|
||||||
// years and months
|
|
||||||
let range = await wp.getPostDateRange();
|
|
||||||
props.archives = [];
|
|
||||||
if (range) {
|
|
||||||
// loop through the years
|
|
||||||
let lastYear = range.latest.year();
|
|
||||||
let firstYear = range.earliest.year();
|
|
||||||
for (let y = lastYear; y >= firstYear; y--) {
|
|
||||||
let yearEntry = {
|
|
||||||
year: y,
|
|
||||||
label: Moment(`${y}-01-01`).format('YYYY'),
|
|
||||||
months: []
|
|
||||||
};
|
|
||||||
props.archives.push(yearEntry);
|
|
||||||
|
|
||||||
// loop through the months
|
|
||||||
let lastMonth = (y === lastYear) ? range.latest.month() : 11;
|
|
||||||
let firstMonth = (y === firstYear) ? range.earliest.month() : 0;
|
|
||||||
for (let m = lastMonth; m >= firstMonth; m--) {
|
|
||||||
let start = Moment(new Date(y, m, 1));
|
|
||||||
let end = start.clone().endOf('month');
|
|
||||||
let monthEntry = {
|
|
||||||
year: y,
|
|
||||||
month: m + 1,
|
|
||||||
label: start.format('MMMM'),
|
|
||||||
};
|
|
||||||
yearEntry.months.push(monthEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
meanwhile.show(<SideNavSync {...props} />);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!wp.ssr) {
|
|
||||||
props.postLists = [];
|
|
||||||
try {
|
|
||||||
// load the posts of each month of the selected year
|
|
||||||
for (let yearEntry of props.archives) {
|
|
||||||
if (yearEntry.year === selectedYear) {
|
|
||||||
for (let monthEntry of yearEntry.months) {
|
|
||||||
let posts = await wp.fetchPostsInMonth(monthEntry);
|
|
||||||
props.postLists = _.concat(props.postLists, { monthEntry, posts });
|
|
||||||
meanwhile.show(<SideNavSync {...props} />);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// load the posts of each category
|
|
||||||
for (let category of props.categories) {
|
|
||||||
if (category.count > 0) {
|
|
||||||
let posts = await wp.fetchPostsInCategory(category);
|
|
||||||
props.postLists = _.concat(props.postLists, { category, posts });
|
|
||||||
meanwhile.show(<SideNavSync {...props} />);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// load the posts of each tag
|
|
||||||
for (let tag of props.tags) {
|
|
||||||
if (tag.count > 0) {
|
|
||||||
let posts = await wp.fetchPostsWithTag(tag);
|
|
||||||
props.postLists = _.concat(props.postLists, { tag, posts });
|
|
||||||
meanwhile.show(<SideNavSync {...props} />);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return <SideNavSync {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleYearSelect = (evt) => {
|
|
||||||
let { selectedYear } = this.state;
|
|
||||||
if (selectedYear !== evt.year) {
|
|
||||||
selectedYear = evt.year;
|
|
||||||
} else {
|
} else {
|
||||||
selectedYear = NaN;
|
setSelectedYear(NaN);
|
||||||
}
|
}
|
||||||
this.setState({ selectedYear });
|
};
|
||||||
|
const handleMoreTagClick = (evt) => {
|
||||||
|
tags.more();
|
||||||
|
};
|
||||||
|
|
||||||
|
render();
|
||||||
|
// get all categories
|
||||||
|
const categories = await wp.fetchCategories();
|
||||||
|
render ();
|
||||||
|
// get top tags
|
||||||
|
const tags = await wp.fetchTopTags();
|
||||||
|
render ();
|
||||||
|
|
||||||
|
// get the date range of posts and use that to build the list of
|
||||||
|
// years and months
|
||||||
|
const range = await wp.getPostDateRange();
|
||||||
|
const archives = [];
|
||||||
|
if (range) {
|
||||||
|
// loop through the years
|
||||||
|
let lastYear = range.latest.year();
|
||||||
|
let firstYear = range.earliest.year();
|
||||||
|
for (let y = lastYear; y >= firstYear; y--) {
|
||||||
|
let yearEntry = {
|
||||||
|
year: y,
|
||||||
|
label: Moment(`${y}-01-01`).format('YYYY'),
|
||||||
|
months: []
|
||||||
|
};
|
||||||
|
archives.push(yearEntry);
|
||||||
|
|
||||||
|
// loop through the months
|
||||||
|
let lastMonth = (y === lastYear) ? range.latest.month() : 11;
|
||||||
|
let firstMonth = (y === firstYear) ? range.earliest.month() : 0;
|
||||||
|
for (let m = lastMonth; m >= firstMonth; m--) {
|
||||||
|
let start = Moment(new Date(y, m, 1));
|
||||||
|
let end = start.clone().endOf('month');
|
||||||
|
let monthEntry = {
|
||||||
|
year: y,
|
||||||
|
month: m + 1,
|
||||||
|
label: start.format('MMMM'),
|
||||||
|
};
|
||||||
|
yearEntry.months.push(monthEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class SideNavSync extends PureComponent {
|
const postLists = [];
|
||||||
static displayName = 'SideNavSync';
|
if (!wp.ssr) {
|
||||||
|
try {
|
||||||
|
// load the posts of each month of the selected year
|
||||||
|
for (let yearEntry of archives) {
|
||||||
|
if (yearEntry.year === selectedYear) {
|
||||||
|
for (let monthEntry of yearEntry.months) {
|
||||||
|
const posts = await wp.fetchPostsInMonth(monthEntry);
|
||||||
|
postLists.push({ monthEntry, posts });
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
// load the posts of each category
|
||||||
return (
|
for (let category of categories) {
|
||||||
|
if (category.count > 0) {
|
||||||
|
const posts = await wp.fetchPostsInCategory(category);
|
||||||
|
postLists.push({ category, posts });
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// load the posts of each tag
|
||||||
|
for (let tag of tags) {
|
||||||
|
if (tag.count > 0) {
|
||||||
|
const posts = await wp.fetchPostsWithTag(tag);
|
||||||
|
postLists.push({ tag, posts });
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
show(
|
||||||
<div className="side-nav">
|
<div className="side-nav">
|
||||||
{this.renderCategories()}
|
{renderCategories()}
|
||||||
{this.renderTags()}
|
{renderTags()}
|
||||||
{this.renderArchives()}
|
{renderArchives()}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCategories() {
|
function renderCategories() {
|
||||||
let { categories } = this.props;
|
|
||||||
// only top-level categories
|
// only top-level categories
|
||||||
categories = _.filter(categories, { parent: 0 });
|
const subcategories = _.filter(categories, { parent: 0 });
|
||||||
// don't show categories with no post
|
// don't show categories with no post
|
||||||
categories = _.filter(categories, 'count');
|
const filtered = _.filter(subcategories, 'count');
|
||||||
categories = _.orderBy(categories, [ 'name' ], [ 'asc' ]);
|
const ordered = _.orderBy(filtered, [ 'name' ], [ 'asc' ]);
|
||||||
if (_.isEmpty(categories)) {
|
if (_.isEmpty(ordered)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3>Categories</h3>
|
<h3>Categories</h3>
|
||||||
<ul className="categories">
|
<ul className="categories">
|
||||||
{
|
{ordered.map(renderCategory)}
|
||||||
categories.map((category, i) => {
|
|
||||||
return this.renderCategory(category, i);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCategory(category, i) {
|
function renderCategory(category, i) {
|
||||||
let { route, postLists } = this.props;
|
|
||||||
let { categorySlug } = route.params;
|
let { categorySlug } = route.params;
|
||||||
let name = _.get(category, 'name', '');
|
let name = _.get(category, 'name', '');
|
||||||
let description = _.unescape(_.get(category, 'description', '').replace(/'/g, "'"));
|
let description = _.unescape(_.get(category, 'description', '').replace(/'/g, "'"));
|
||||||
@@ -171,37 +145,31 @@ class SideNavSync extends PureComponent {
|
|||||||
return (
|
return (
|
||||||
<li key={i}>
|
<li key={i}>
|
||||||
<a className={className} href={url} title={description}>{name}</a>
|
<a className={className} href={url} title={description}>{name}</a>
|
||||||
{this.renderSubcategories(category)}
|
{renderSubcategories(category)}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTags() {
|
function renderTags() {
|
||||||
let { tags } = this.props;
|
|
||||||
// don't show tags with no post
|
// don't show tags with no post
|
||||||
tags = _.filter(tags, 'count');
|
const activeTags = _.filter(tags, 'count');
|
||||||
// list tags with more posts first
|
// list tags with more posts first
|
||||||
tags = _.orderBy(tags, [ 'count', 'name' ], [ 'desc', 'asc' ]);
|
const ordered = _.orderBy(activeTags, [ 'count', 'name' ], [ 'desc', 'asc' ]);
|
||||||
if (_.isEmpty(tags)) {
|
if (_.isEmpty(ordered)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3>Tags</h3>
|
<h3>Tags</h3>
|
||||||
<div className="tags">
|
<div className="tags">
|
||||||
{
|
{ordered.map(renderTag)}
|
||||||
tags.map((tag, i) => {
|
{renderMoreTagButton()}
|
||||||
return this.renderTag(tag, i);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
{this.renderMoreTagButton()}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTag(tag, i) {
|
function renderTag(tag, i) {
|
||||||
let { route, postLists } = this.props;
|
|
||||||
let { tagSlug } = route.params;
|
let { tagSlug } = route.params;
|
||||||
let name = _.get(tag, 'name', '');
|
let name = _.get(tag, 'name', '');
|
||||||
let description = _.unescape(_.get(tag, 'description', '').replace(/'/g, "'"));
|
let description = _.unescape(_.get(tag, 'description', '').replace(/'/g, "'"));
|
||||||
@@ -223,38 +191,31 @@ class SideNavSync extends PureComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMoreTagButton() {
|
function renderMoreTagButton() {
|
||||||
let { tags } = this.props;
|
|
||||||
if (!_.some(tags, 'count')) {
|
if (!_.some(tags, 'count')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!(tags.length < tags.total) || tags.length >= 100) {
|
if (!(tags.length < tags.total) || tags.length >= 100) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return <a className="more" onClick={this.handleMoreTagClick}>... more</a>;
|
return <a className="more" onClick={handleMoreTagClick}>... more</a>;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSubcategories(category) {
|
function renderSubcategories(category) {
|
||||||
let { categories } = this.props;
|
const subcategories = _.filter(categories, { parent: category.id });
|
||||||
let subcategories = _.filter(categories, { parent: category.id });
|
const filtered = _.filter(subcategories, 'count');
|
||||||
subcategories = _.filter(subcategories, 'count');
|
const ordered = _.orderBy(filtered, [ 'count', 'name' ], [ 'desc', 'asc' ]);
|
||||||
subcategories = _.orderBy(subcategories, [ 'count', 'name' ], [ 'desc', 'asc' ]);
|
if (_.isEmpty(ordered)) {
|
||||||
if (_.isEmpty(subcategories)) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<ul className="subcategories">
|
<ul className="subcategories">
|
||||||
{
|
{ordered.map(renderCategory)}
|
||||||
subcategories.map((subcategory, i) => {
|
|
||||||
return this.renderCategory(subcategory, i);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderArchives() {
|
function renderArchives() {
|
||||||
let { archives } = this.props;
|
|
||||||
if (_.isEmpty(archives)) {
|
if (_.isEmpty(archives)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -262,109 +223,68 @@ class SideNavSync extends PureComponent {
|
|||||||
<div>
|
<div>
|
||||||
<h3>Archives</h3>
|
<h3>Archives</h3>
|
||||||
<ul className="archives">
|
<ul className="archives">
|
||||||
{
|
{archives.map(renderYear)}
|
||||||
archives.map((yearEntry, i) => {
|
|
||||||
return this.renderYear(yearEntry, i);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderYear(yearEntry, i) {
|
function renderYear(yearEntry, i) {
|
||||||
let { selectedYear } = this.props;
|
const listClassNames = [ 'months'] ;
|
||||||
let listClass = 'months';
|
|
||||||
if (yearEntry.year !== selectedYear) {
|
if (yearEntry.year !== selectedYear) {
|
||||||
listClass += ' collapsed';
|
listClassNames.push('collapsed');
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<li key={i}>
|
<li key={i}>
|
||||||
<a className="year" data-year={yearEntry.year} onClick={this.handleYearClick}>
|
<a className="year" data-year={yearEntry.year} onClick={handleYearClick}>
|
||||||
{yearEntry.label}
|
{yearEntry.label}
|
||||||
</a>
|
</a>
|
||||||
<ul className={listClass}>
|
<ul className={listClassNames.join(' ')}>
|
||||||
{
|
{yearEntry.months.map(renderMonth)}
|
||||||
yearEntry.months.map((entry, i) => {
|
|
||||||
return this.renderMonth(entry, i);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMonth(monthEntry, i) {
|
function renderMonth(monthEntry, i) {
|
||||||
let { route, postLists, selectedYear } = this.props;
|
const { date } = route.params;
|
||||||
let { date } = route.params;
|
const classNames = [];
|
||||||
let className, url;
|
let url;
|
||||||
if (monthEntry.year !== selectedYear) {
|
if (monthEntry.year !== selectedYear) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (date && monthEntry.year === date.year && monthEntry.month === date.month) {
|
if (date && monthEntry.year === date.year && monthEntry.month === date.month) {
|
||||||
className = 'selected';
|
classNames.push('selected');
|
||||||
}
|
}
|
||||||
let postList = _.find(postLists, { monthEntry });
|
const postList = _.find(postLists, { monthEntry });
|
||||||
if (!postList || !_.isEmpty(postList.posts)) {
|
if (!postList || !_.isEmpty(postList.posts)) {
|
||||||
url = route.prefetchArchiveURL(monthEntry);
|
url = route.prefetchArchiveURL(monthEntry);
|
||||||
} else {
|
} else {
|
||||||
className = 'disabled';
|
classNames.push('disabled');
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<li key={i}>
|
<li key={i}>
|
||||||
<a className={className} href={url}>{monthEntry.label}</a>
|
<a className={classNames.join(' ')} href={url}>
|
||||||
|
{monthEntry.label}
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleYearClick = (evt) => {
|
function hasRecentPost(postList, daysOld) {
|
||||||
let { onYearSelect } = this.props;
|
if (!postList || _.isEmpty(postList.posts)) {
|
||||||
let year = parseInt(evt.currentTarget.getAttribute('data-year'));
|
return false;
|
||||||
if (onYearSelect) {
|
|
||||||
onYearSelect({
|
|
||||||
type: 'yearselect',
|
|
||||||
target: this,
|
|
||||||
year,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
const post = _.first(postList.posts);
|
||||||
|
const limit = Moment().subtract(daysOld, 'day');
|
||||||
handleMoreTagClick = (evt) => {
|
const publicationDate = Moment(post.date_gmt);
|
||||||
let { tags } = this.props;
|
return limit < publicationDate;
|
||||||
tags.more();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasRecentPost(postList, daysOld) {
|
const component = Relaks(SideNav);
|
||||||
if (!postList || _.isEmpty(postList.posts)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let post = _.first(postList.posts);
|
|
||||||
let limit = Moment().subtract(daysOld, 'day');
|
|
||||||
let publicationDate = Moment(post.date_gmt);
|
|
||||||
return limit < publicationDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
tags: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
archives: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
postLists: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
selectedYear: PropTypes.number,
|
|
||||||
route: PropTypes.instanceOf(Route),
|
|
||||||
onYearSelect: PropTypes.func,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
SideNav as default,
|
component as default,
|
||||||
SideNav,
|
component as SideNav,
|
||||||
SideNavSync,
|
|
||||||
};
|
};
|
||||||
|
@@ -1,30 +1,20 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
class TagList extends PureComponent {
|
function TagList(props) {
|
||||||
static displayName = 'TagList';
|
const { route, tags } = props;
|
||||||
|
if (_.isEmpty(tags)) {
|
||||||
render() {
|
return null;
|
||||||
let { tags } = this.props;
|
|
||||||
if (_.isEmpty(tags)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="tag-list">
|
|
||||||
<b>Tags: </b>
|
|
||||||
{
|
|
||||||
tags.map((tag, i) => {
|
|
||||||
return this.renderTag(tag, i);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return (
|
||||||
|
<div className="tag-list">
|
||||||
|
<b>Tags: </b> {tags.map(renderTag)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
renderTag(tag, i) {
|
function renderTag(tag, i) {
|
||||||
let { route } = this.props;
|
const name = _.get(tag, 'name', '');
|
||||||
let name = _.get(tag, 'name', '');
|
const url = route.prefetchObjectURL(tag);
|
||||||
let url = route.prefetchObjectURL(tag);
|
|
||||||
return (
|
return (
|
||||||
<span key={i}>
|
<span key={i}>
|
||||||
<a href={url}>{name}</a>
|
<a href={url}>{name}</a>
|
||||||
@@ -34,14 +24,6 @@ class TagList extends PureComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
const PropTypes = require('prop-types');
|
|
||||||
|
|
||||||
TagList.propTypes = {
|
|
||||||
tags: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
TagList as default,
|
TagList as default,
|
||||||
TagList,
|
TagList,
|
||||||
|
@@ -1,52 +1,46 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { PureComponent } from 'react';
|
import React from 'react';
|
||||||
import { AsyncComponent } from 'relaks';
|
import Relaks, { useProgress, useSaveBuffer } from 'relaks/hooks';
|
||||||
import { Route } from 'routing';
|
|
||||||
import WordPress from 'wordpress';
|
|
||||||
|
|
||||||
class TopNav extends AsyncComponent {
|
async function TopNav(props) {
|
||||||
static displayName = 'TopNav';
|
const { wp, route } = props;
|
||||||
|
const [ show ] = useProgress();
|
||||||
|
const [ search, setSearch ] = useSaveBuffer(route.params.search, {
|
||||||
|
delay: 500,
|
||||||
|
save: (newSearch) => {
|
||||||
|
const url = route.getSearchURL(newSearch);
|
||||||
|
const options = {
|
||||||
|
replace: (route.params.pageType === 'search')
|
||||||
|
};
|
||||||
|
route.change(url);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
async renderAsync(meanwhile) {
|
const handleSearchChange = (evt) => {
|
||||||
let { wp, route } = this.props;
|
setSearch(evt.target.value);
|
||||||
let props = {
|
};
|
||||||
route,
|
|
||||||
};
|
|
||||||
meanwhile.show(<TopNavSync {...props} />);
|
|
||||||
props.site = await wp.fetchSite();
|
|
||||||
meanwhile.show(<TopNavSync {...props} />);
|
|
||||||
props.pages = await wp.fetchPages();
|
|
||||||
return <TopNavSync {...props} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TopNavSync extends PureComponent {
|
render();
|
||||||
static displayName = 'TopNavSync';
|
const site = await wp.fetchSite();
|
||||||
|
render();
|
||||||
|
const pages = await wp.fetchPages();
|
||||||
|
render();
|
||||||
|
|
||||||
constructor(props) {
|
function render() {
|
||||||
super(props);
|
show(
|
||||||
let { route } = props;
|
<div className="top-nav">
|
||||||
let { search } = route.params;
|
{renderTitleBar()}
|
||||||
this.searchTimeout = 0;
|
{renderPageLinkBar()}
|
||||||
this.state = { search };
|
{renderSearchBar()}
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
let { onMouseOver, onMouseOut } = this.props;
|
|
||||||
return (
|
|
||||||
<div className="top-nav" onMouseOver={onMouseOver} onMouseOut={onMouseOut}>
|
|
||||||
{this.renderTitleBar()}
|
|
||||||
{this.renderPageLinkBar()}
|
|
||||||
{this.renderSearchBar()}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTitleBar() {
|
function renderTitleBar() {
|
||||||
let { route, site } = this.props;
|
const name = _.get(site, 'name', '');
|
||||||
let name = _.get(site, 'name', '');
|
const descriptionHTML = _.get(site, 'description', '');
|
||||||
let description = _.unescape(_.get(site, 'description', '').replace(/'/g, "'"));
|
const description = _.unescape(descriptionHTML.replace(/'/g, "'"));
|
||||||
let url = route.getRootURL();
|
const url = route.getRootURL();
|
||||||
return (
|
return (
|
||||||
<div className="title-bar">
|
<div className="title-bar">
|
||||||
<div className="title" title={description}>
|
<div className="title" title={description}>
|
||||||
@@ -59,25 +53,19 @@ class TopNavSync extends PureComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPageLinkBar() {
|
function renderPageLinkBar() {
|
||||||
let { pages } = this.props;
|
let filtered = _.filter(pages, { parent: 0 });
|
||||||
pages = _.filter(pages, { parent: 0 });
|
let ordered = _.sortBy(filtered, 'menu_order');
|
||||||
pages = _.sortBy(pages, 'menu_order');
|
|
||||||
return (
|
return (
|
||||||
<div className="page-bar">
|
<div className="page-bar">
|
||||||
{
|
{ordered.map(renderPageLinkButton)}
|
||||||
pages.map((page, i) => {
|
|
||||||
return this.renderPageLinkButton(page, i);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPageLinkButton(page, i) {
|
function renderPageLinkButton(page, i) {
|
||||||
let { route } = this.props;
|
const title = _.get(page, 'title.rendered');
|
||||||
let title = _.get(page, 'title.rendered');
|
const url = route.prefetchObjectURL(page);
|
||||||
let url = route.prefetchObjectURL(page);
|
|
||||||
return (
|
return (
|
||||||
<div className="button" key={i}>
|
<div className="button" key={i}>
|
||||||
<a href={url}>{title}</a>
|
<a href={url}>{title}</a>
|
||||||
@@ -85,71 +73,21 @@ class TopNavSync extends PureComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSearchBar() {
|
function renderSearchBar() {
|
||||||
let { route } = this.props;
|
|
||||||
let { search } = this.state;
|
|
||||||
return (
|
return (
|
||||||
<div className="search-bar">
|
<div className="search-bar">
|
||||||
<span className="input-container">
|
<span className="input-container">
|
||||||
<input type="text" value={search || ''} onChange={this.handleSearchChange} />
|
<input type="text" value={search || ''} onChange={handleSearchChange} />
|
||||||
<i className="fa fa-search" />
|
<i className="fa fa-search" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
performSearch = (evt) => {
|
|
||||||
let { search } = this.state;
|
|
||||||
let { route } = this.props;
|
|
||||||
let url = route.getSearchURL(search);
|
|
||||||
let options = {
|
|
||||||
replace: (route.params.pageType === 'search')
|
|
||||||
};
|
|
||||||
route.change(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
|
||||||
let { route } = this.props;
|
|
||||||
if (prevProps.route !== route) {
|
|
||||||
let { search } = route.params;
|
|
||||||
this.setState({ search });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
clearTimeout(this.searchTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSearchChange = (evt) => {
|
|
||||||
let search = evt.target.value;
|
|
||||||
this.setState({ search });
|
|
||||||
clearTimeout(this.searchTimeout);
|
|
||||||
this.searchTimeout = setTimeout(this.performSearch, 500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TopNavSync.defaultProps = {
|
const component = Relaks(TopNav);
|
||||||
site: {},
|
|
||||||
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 = {
|
|
||||||
site: PropTypes.object,
|
|
||||||
pages: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
route: PropTypes.instanceOf(Route).isRequired,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
TopNav as default,
|
component as default,
|
||||||
TopNav,
|
component as TopNav,
|
||||||
TopNavSync,
|
|
||||||
};
|
};
|
||||||
|
@@ -14,7 +14,8 @@ module.exports = function(config) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
config.resolve.modules.push(Path.resolve('./node_modules'));
|
||||||
};
|
};
|
||||||
|
|
||||||
function resolve(type, module) {
|
function resolve(type, module) {
|
||||||
|
Reference in New Issue
Block a user