diff --git a/server/docker-compose.yml b/server/docker-compose.yml index 633510a..150ad1e 100644 --- a/server/docker-compose.yml +++ b/server/docker-compose.yml @@ -24,11 +24,9 @@ services: WORDPRESS_DB_PASSWORD: wordpress restart: always node: - image: node:8 + image: wordpress-example volumes: - - ..:/opt/example - web_cache:/var/cache/nginx - command: [ node, /opt/example/server/index.js ] environment: WORDPRESS_HOST: wordpress NGINX_HOST: nginx diff --git a/src/pages/archive-page.jsx b/src/pages/archive-page.jsx index 9d14fe2..eeb4938 100644 --- a/src/pages/archive-page.jsx +++ b/src/pages/archive-page.jsx @@ -14,10 +14,7 @@ class ArchivePage extends AsyncComponent { let { wp, route } = this.props; let { date } = route.params; let month = Moment(`${date.year}-${_.padStart(date.month, 2, '0')}`); - let props = { - route, - month, - }; + let props = { route, month }; meanwhile.show(); let after = month.toISOString(); let before = month.clone().endOf('month').toISOString(); @@ -33,7 +30,7 @@ class ArchivePageSync extends PureComponent { render() { let { route, posts, month } = this.props; let monthLabel = month.format('MMMM YYYY'); - let trail = [ { label: 'Archive' }, { label: monthLabel } ]; + let trail = [ { label: 'Archives' }, { label: monthLabel } ]; return (
diff --git a/src/pages/category-page.jsx b/src/pages/category-page.jsx index b24f0a8..2e61730 100644 --- a/src/pages/category-page.jsx +++ b/src/pages/category-page.jsx @@ -12,15 +12,22 @@ class CategoryPage extends AsyncComponent { async renderAsync(meanwhile) { let { wp, route } = this.props; - let { categorySlugs } = route.params; - let props = { - route, - }; + let { categorySlug } = route.params; + let props = { route }; meanwhile.show(); - props.categories = await wp.fetchMultiple('/wp/v2/categories/', categorySlugs); + props.category = await wp.fetchOne('/wp/v2/categories/', categorySlug); + props.parentCategories = []; + let parentID = props.category.parent; + while (parentID) { + let parentCategory = await wp.fetchOne('/wp/v2/categories/', parentID); + if (!parentCategory) { + break; + } + props.parentCategories.push(parentCategory); + parentID = parentCategory.parent; + } meanwhile.show(); - let category = _.last(props.categories); - props.posts = await wp.fetchList(`/wp/v2/posts/?categories=${category.id}`); + props.posts = await wp.fetchList(`/wp/v2/posts/?categories=${props.category.id}`); return ; } } @@ -29,18 +36,15 @@ class CategoryPageSync extends PureComponent { static displayName = 'CategoryPageSync'; render() { - let { route, posts, categories } = this.props; + let { route, posts, category, parentCategories } = this.props; let trail = [ { label: 'Categories' } ]; - let category = _.last(categories); - for (let c of categories) { - let label = _.get(c, 'name', ''); - if (c !== category) { - let url = route.getObjectURL(c); - trail.push({ label, url }); - } else { - trail.push({ label }); - } + let categoryLabel = _.get(category, 'name', ''); + for (let parentCategory of parentCategories) { + let label = _.get(parentCategory, 'name', ''); + let url = route.getObjectURL(parentCategory); + trail.push({ label, url }); } + trail.push({ label: categoryLabel }); return (
@@ -59,6 +63,7 @@ if (process.env.NODE_ENV !== 'production') { }; CategoryPageSync.propTypes = { category: PropTypes.object, + parentCategories: PropTypes.arrayOf(PropTypes.object), posts: PropTypes.arrayOf(PropTypes.object), route: PropTypes.instanceOf(Route), }; diff --git a/src/pages/page-page.jsx b/src/pages/page-page.jsx index 2a5ea2e..903a2c8 100644 --- a/src/pages/page-page.jsx +++ b/src/pages/page-page.jsx @@ -14,15 +14,21 @@ class PagePage extends AsyncComponent { async renderAsync(meanwhile) { let { wp, route } = this.props; - let { pageSlugs } = route.params; - let props = { - route, - }; + let { pageSlug } = route.params; + let props = { route }; meanwhile.show(); - props.pages = await wp.fetchMultiple('/wp/v2/pages/', pageSlugs); - meanwhile.show(); - let page = _.last(props.pages); - props.childPages = await wp.fetchList(`/wp/v2/pages/?parent=${page.id}`, { minimum: '100%' }); + props.page = await wp.fetchOne('/wp/v2/pages/', pageSlug); + props.parentPages = []; + let parentID = props.page.parent; + while (parentID) { + let parentPage = await wp.fetchOne('/wp/v2/pages/', parentID); + if (!parentPage) { + break; + } + props.parentPages.push(parentPage); + parentID = parentPage.parent; + } + props.childPages = await wp.fetchList(`/wp/v2/pages/?parent=${props.page.id}`); return ; } } @@ -31,15 +37,12 @@ class PagePageSync extends PureComponent { static displayName = 'PagePageSync'; render() { - let { route, pages, childPages, transform } = this.props; - let page = _.last(pages); + let { route, page, parentPages, childPages, transform } = this.props; let trail = []; - for (let p of pages) { - if (p !== page) { - let title = _.get(page, 'title.rendered', ''); - let url = route.getObjectURL(p); - trail.push({ label: , url }) - } + for (let parentPage of parentPages) { + let title = _.get(parentPage, 'title.rendered', ''); + let url = route.getObjectURL(parentPage); + trail.push({ label: , url }) } return (
diff --git a/src/pages/post-page.jsx b/src/pages/post-page.jsx index b1bf4ea..de1f00d 100644 --- a/src/pages/post-page.jsx +++ b/src/pages/post-page.jsx @@ -15,14 +15,11 @@ class PostPage extends AsyncComponent { async renderAsync(meanwhile) { let { wp, route } = this.props; let { postSlug } = route.params; - let props = { - route, - }; + let props = { route }; meanwhile.show(); props.post = await wp.fetchOne('/wp/v2/posts/', postSlug); meanwhile.show(); - let allCategories = await wp.fetchList('/wp/v2/categories/', { minimum: '100%' }); - props.categories = findMatchingCategories(allCategories, props.post.categories, route.history); + props.categories = await this.findCategoryChain(props.post.categories); meanwhile.show(); props.author = await wp.fetchOne('/wp/v2/users/', props.post.author); if (!wp.ssr) { @@ -31,6 +28,64 @@ class PostPage extends AsyncComponent { } return ; } + + async findCategoryChain(ids) { + let { wp, route } = this.props; + let allCategories = await wp.fetchList('/wp/v2/categories/', { minimum: '100%' }); + + // add categories, including their parents as well + let applicable = []; + let include = (id) => { + let category = _.find(allCategories, { id }) + if (category) { + if (!_.includes(applicable, category)) { + applicable.push(category); + } + // add parent category as well + include(category.parent); + } + }; + for (let id of ids) { + include(id); + } + + // see how recently a category was visited + let historyIndex = (c) => { + + return _.findLastIndex(route.history, { params: { categorySlug: c.slug }}); + }; + // see how deep a category is + let depth = (c) => { + if (c.parent) { + let parent = _.find(allCategories, { id: c.parent }); + if (parent) { + return depth(parent) + 1; + } + } + return 0; + }; + + // order applicable categories based on how recently it was visited, + // how deep it is, and alphabetically; the first criteria makes our + // breadcrumb works more sensibly + applicable = _.orderBy(applicable, [ historyIndex, depth, 'name' ], [ 'desc', 'desc', 'asc' ]); + let anchorCategory = _.first(applicable); + + let trail = []; + if (anchorCategory) { + // add category and its ancestors + for (let c = anchorCategory; c; c = _.find(applicable, { id: c.parent })) { + trail.unshift(c); + } + // add applicable child categories + for (let c = anchorCategory; c; c = _.find(applicable, { parent: c.id })) { + if (c !== anchorCategory) { + trail.push(c); + } + } + } + return trail; + } } class PostPageSync extends PureComponent { @@ -54,61 +109,6 @@ class PostPageSync extends PureComponent { } } -function includeCategory(list, id, allCategories) { - let category = _.find(allCategories, { id }) - if (category) { - if (!_.includes(list, category)) { - list.push(category); - } - // add parent category as well - includeCategory(list, category.parent, allCategories); - } -} - -function findMatchingCategories(allCategories, ids, history) { - let applicable = []; - for (let id of ids) { - includeCategory(applicable, id, allCategories); - } - - let historyIndex = (c) => { - return _.findLastIndex(history, (route) => { - if (route.params.categorySlugs) { - return _.includes(route.params.categorySlugs, c.slug); - } - }); - }; - let depth = (c) => { - if (c.parent) { - let parent = _.find(allCategories, { id: c.parent }); - if (parent) { - return depth(parent) + 1; - } - } - return 0; - }; - - // order applicable categories based on how recently it was visited, - // how deep it is, and alphabetically - applicable = _.orderBy(applicable, [ historyIndex, depth, 'name' ], [ 'desc', 'desc', 'asc' ]); - let anchorCategory = _.first(applicable); - - let trail = []; - if (anchorCategory) { - // add category and its ancestors - for (let c = anchorCategory; c; c = _.find(applicable, { id: c.parent })) { - trail.unshift(c); - } - // add applicable child categories - for (let c = anchorCategory; c; c = _.find(applicable, { parent: c.id })) { - if (c !== anchorCategory) { - trail.push(c); - } - } - } - return trail; -} - if (process.env.NODE_ENV !== 'production') { const PropTypes = require('prop-types'); diff --git a/src/pages/search-page.jsx b/src/pages/search-page.jsx index 127cace..affcde0 100644 --- a/src/pages/search-page.jsx +++ b/src/pages/search-page.jsx @@ -12,14 +12,12 @@ class SearchPage extends AsyncComponent { async renderAsync(meanwhile) { let { wp, route } = this.props; let { search } = route.params; - let props = { - route, - }; + let props = { route }; meanwhile.show(); props.categories = await wp.fetchList('/wp/v2/categories/'); if (search) { - let url = `/wp/v2/posts/?search=${encodeURIComponent(search)}'`; - props.posts = await wp.fetchList(url); + let s = encodeURIComponent(search); + props.posts = await wp.fetchList(`/wp/v2/posts/?search=${s}`); } else { props.posts = null; } diff --git a/src/pages/tag-page.jsx b/src/pages/tag-page.jsx index 3491b5c..e921efd 100644 --- a/src/pages/tag-page.jsx +++ b/src/pages/tag-page.jsx @@ -13,9 +13,7 @@ class TagPage extends AsyncComponent { async renderAsync(meanwhile) { let { wp, route } = this.props; let { tagSlug } = route.params; - let props = { - route, - }; + let props = { route }; meanwhile.show(); props.tag = await wp.fetchOne('/wp/v2/tags/', tagSlug); meanwhile.show(); diff --git a/src/pages/welcome-page.jsx b/src/pages/welcome-page.jsx index 0f67613..329ba6a 100644 --- a/src/pages/welcome-page.jsx +++ b/src/pages/welcome-page.jsx @@ -10,11 +10,8 @@ class WelcomePage extends AsyncComponent { async renderAsync(meanwhile) { let { wp, route } = this.props; - let props = { - route, - }; + let props = { route }; meanwhile.show(); - props.categories = await wp.fetchList('/wp/v2/categories/'); props.posts = await wp.fetchList('/wp/v2/posts/'); return ; } @@ -24,10 +21,10 @@ class WelcomePageSync extends PureComponent { static displayName = 'WelcomePageSync'; render() { - let { route, categories, posts } = this.props; + let { route, posts } = this.props; return (
- +
); } diff --git a/src/routing.js b/src/routing.js index 495582d..2e03b1b 100644 --- a/src/routing.js +++ b/src/routing.js @@ -61,7 +61,7 @@ class Route { // get the site URL and see what the page's URL would be if it // were on WordPress itself let siteURL = await this.getSiteURL(); - let link = _.trimEnd(siteURL + path, '/') + '/'; + let link = _.trimEnd(siteURL + path, '/'); // see if it's a search let search = query.s; @@ -85,31 +85,29 @@ class Route { if (postID) { let post = await this.dataSource.fetchOne('/wp/v2/posts/', postID); if (post) { - let postSlug = post.slug; - return { pageType: 'post', postSlug, siteURL }; + return { pageType: 'post', postSlug: post.slug, siteURL }; } } // see if it's pointing to a page let allPages = await this.dataSource.fetchList('/wp/v2/pages/', { minimum: '100%' }); - let pageSlugs = findMatchingSlugs(allPages, link); - if (pageSlugs) { - return { pageType: 'page', pageSlugs, siteURL }; + let page = _.find(allPages, { link }); + if (page) { + return { pageType: 'page', pageSlug: page.slug, siteURL }; } // see if it's pointing to a category let allCategories = await this.dataSource.fetchList('/wp/v2/categories/', { minimum: '100%' }); - let categorySlugs = findMatchingSlugs(allCategories, link); - if (categorySlugs) { - return { pageType: 'category', categorySlugs, siteURL }; + let category = _.find(allCategories, { link }); + if (category) { + return { pageType: 'category', categorySlug: category.slug, siteURL }; } // see if it's pointing to a tag let allTags = await this.dataSource.fetchList('/wp/v2/tags/', { minimum: '100%' }); - let tagSlugs = findMatchingSlugs(allTags, link); - if (tagSlugs) { - let tagSlug = tagSlugs[0]; - return { pageType: 'tag', tagSlug, siteURL }; + let tag = _.find(allTags, { link }); + if (tag) { + return { pageType: 'tag', tagSlug: tag.slug, siteURL }; } // see if it's pointing to a post @@ -187,20 +185,6 @@ function findPostID(path) { } } -function findMatchingSlugs(objects, link) { - let matches = []; - for (let object of objects) { - let objectLink = _.trimEnd(object.link, '/') + '/'; - if (_.startsWith(link, objectLink)) { - matches.push(object); - } - } - if (matches.length > 0) { - let slugs = _.map(_.sortBy(matches, 'link.length'), 'slug'); - return slugs; - } -} - let routes = { 'page': { path: '*' }, }; diff --git a/src/widgets/page-view.jsx b/src/widgets/page-view.jsx index 729528f..630b81f 100644 --- a/src/widgets/page-view.jsx +++ b/src/widgets/page-view.jsx @@ -33,7 +33,7 @@ if (process.env.NODE_ENV !== 'production') { const PropTypes = require('prop-types'); PageView.propTypes = { - category: PropTypes.object, + page: PropTypes.object, transform: PropTypes.func, }; } diff --git a/src/widgets/post-list.jsx b/src/widgets/post-list.jsx index a5cddfa..49041b0 100644 --- a/src/widgets/post-list.jsx +++ b/src/widgets/post-list.jsx @@ -60,6 +60,7 @@ if (process.env.NODE_ENV !== 'production') { const PropTypes = require('prop-types'); PostList.propTypes = { + posts: PropTypes.arrayOf(PropTypes.object), route: PropTypes.instanceOf(Route), minimum: PropTypes.number, maximum: PropTypes.number, diff --git a/src/widgets/side-nav.jsx b/src/widgets/side-nav.jsx index 6eab3d8..336aa8e 100644 --- a/src/widgets/side-nav.jsx +++ b/src/widgets/side-nav.jsx @@ -155,12 +155,12 @@ class SideNavSync extends PureComponent { renderCategory(category, i) { let { route } = this.props; - let { categorySlugs } = route.params; + let { categorySlug } = route.params; let name = _.get(category, 'name', ''); let description = _.get(category, 'description', ''); let url = route.getObjectURL(category); let className; - if (category.slug === _.last(categorySlugs)) { + if (category.slug === categorySlug) { className = 'selected'; } return ( diff --git a/src/widgets/top-nav.jsx b/src/widgets/top-nav.jsx index 8ff923e..7fc72c2 100644 --- a/src/widgets/top-nav.jsx +++ b/src/widgets/top-nav.jsx @@ -15,7 +15,7 @@ class TopNav extends AsyncComponent { meanwhile.show(); props.system = await wp.fetchOne('/'); meanwhile.show(); - props.pages = await wp.fetchList('/wp/v2/pages/?parent=0', { minimum: '100%' }); + props.pages = await wp.fetchList('/wp/v2/pages/', { minimum: '100%' }); return ; } } @@ -61,6 +61,7 @@ class TopNavSync extends PureComponent { renderPageLinkBar() { let { pages } = this.props; + pages = _.filter(pages, { parent: 0 }); pages = _.sortBy(pages, 'menu_order'); return (