Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] Refactors Page service to support nested routes #21

Merged
merged 1 commit into from
Sep 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
238 changes: 123 additions & 115 deletions app/services/page.js
Original file line number Diff line number Diff line change
@@ -1,152 +1,160 @@
import Service from '@ember/service';
import { inject as service } from '@ember/service';
import { get, set, computed } from '@ember/object';

export default Service.extend({
router: service(),
fastboot: service(),
headData: service(),

currentSection: computed('router.currentURL', 'pages.[]', 'content.id', function() {
let tocSections = this.pages;

let contentId = get(this, 'content.id');

if(!tocSections || !contentId) { return; }

let section = contentId.split('/')[0]
let currentSection = tocSections.find((tocSection) => tocSection.id === section);

if(!currentSection) {
return;
import { get, computed } from '@ember/object';
import { assert } from '@ember/debug';

/**
* Build up a tree of the pages that matches the URL structure:
*
* {
* index: { page: Page, next: PageTreeNode }
* 'getting-started': {
* first: PageTreeNode,
* last: PageTreeNode,
* parent: PageTreeNode,
* 'core-concepts': { page: Page, prev: PageTreeNode, next: PageTreeNode },
* ...
* }
* }
*
* TypeScript Type:
*
* interface Node {
* page?: Page,
* parent: Node,
* }
*
* interface SectionNode extends Node {
* subNodes: Map<string, Node>,
* first: PageNode,
* last: PageNode,
* }
*
* interface PageNode extends Node {
* prev: PageNode,
* next: PageNode,
* }
*/
function buildPageTreeNode(pages, page, parent, depth = 0) {
let pageTreeNode = {
page,
parent,
subNodes: new Map(),
};

let subNodes = pages.map(page => {
if (page.id === 'index') {
// special case for index, which always has a single sub page
return { page: page.pages[0], parent: pageTreeNode };
} else if (page.pages === undefined) {
return { page, parent: pageTreeNode };
} else {
return buildPageTreeNode(page.pages, page, pageTreeNode, depth + 1);
}
});

// eslint-disable-next-line ember/no-side-effects
set(this, 'metaSection', get(currentSection, 'title'));

return currentSection;
}),

/**
* Find the TOC item that matches the current visible content. This is needed because the title comes
* from the TOC and not the content. Also we use this to compute nextPage and previousPage
* @return {Promise} the current page as a POJO
*/
currentPage: computed('router.currentURL', 'currentSection.pages', 'content.id', function() {
let currentSection = this.currentSection;

if(!currentSection) { return; }

// special case for the index section - there should always be only exactly 1 page in the "index" section
if (currentSection.id === 'index') {
return get(currentSection, 'pages')[0];
subNodes.forEach((node, index) => {
if (index === 0) {
pageTreeNode.first = node.first || node;
}

let pages = get(currentSection, 'pages') || [];

let currentPage = pages.find((page) => page.url === get(this, 'content.id'));

if(!currentPage) {
return;
if (index === subNodes.length - 1) {
pageTreeNode.last = node.last || node;
}

// eslint-disable-next-line ember/no-side-effects
set(this, 'metaPage', get(currentPage, 'title'));
let nextNode = subNodes[index + 1];

return currentPage;
}),
if (nextNode) {
let prevPage = node.last || node;
let nextPage = nextNode.first || nextNode;

isFirstPage: computed('currentSection', 'currentPage', function() {
let currentSection = this.currentSection;

if(!currentSection) { return; }

let pages = get(currentSection, 'pages');
if(pages) {
return pages.indexOf(this.currentPage) === 0;
prevPage.next = nextPage;
nextPage.prev = prevPage;
}
}),

isLastPage: computed('currentSection', 'currentPage', function() {
let currentSection = this.currentSection;
let id = node.page.id || node.page.url.split('/')[depth];

if(!currentSection) { return; }
assert(
`You can only have one page/section with a given title at any level in the guides, received duplicate: ${id}`,
!pageTreeNode.subNodes.has(id)
);

let pages = get(currentSection, 'pages');
if(pages) {
return pages.indexOf(this.currentPage) === (pages.length-1);
}
}),
pageTreeNode.subNodes.set(id, node);
});

previousPage: computed('currentSection.pages', 'currentPage.url', function() {
let currentSection = this.currentSection;
let currentPage = this.currentPage;
return pageTreeNode;
}

if(!currentSection || !currentPage) { return; }

let pages = get(currentSection, 'pages');
export default Service.extend({
router: service(),
fastboot: service(),
headData: service(),

if(pages) {
let currentLocalPage = pages.find((page) => page.url === currentPage.url);
let index = pages.indexOf(currentLocalPage);
pages: null,

if (index > 0) {
return pages[index - 1];
}
}
pageTree: computed('pages', function() {
let { pages } = this;
return buildPageTreeNode(pages.toArray ? pages.toArray() : pages);
}),

nextPage: computed('currentSection.pages', 'currentPage.url', function() {
let currentSection = this.currentSection;
let currentPage = this.currentPage;

if(!currentSection || !currentPage) { return; }

let pages = get(currentSection, 'pages');

if(pages) {
let currentLocalPage = pages.find((page) => page.url === currentPage.url);
let index = pages.indexOf(currentLocalPage);
currentNode: computed('content.id', function() {
let contentId = get(this, 'content.id');

if (index < pages.length-1) {
return pages[index + 1];
}
if (!contentId) {
return;
}
}),

previousSection: computed('currentSection', 'pages.[]', function() {
let currentSection = this.currentSection;
let path = contentId.split('/');

if(!currentSection) { return; }
let current = get(this, 'pageTree');

let pages = this.pages;
for (let segment of path) {
current = current.subNodes.get(segment);
}

if (pages) {
let page = pages.content.find((content) => content.id === currentSection.id);
return current;
}),

let index = pages.content.indexOf(page);
currentSection: computed('currentNode', function() {
return get(this, 'currentNode.parent.page');
}),

if (index > 0) {
return pages.objectAt(index-1);
}
}
/**
* Find the TOC item that matches the current visible content. This is needed because the title comes
* from the TOC and not the content. Also we use this to compute nextPage and previousPage
* @return {Promise} the current page as a POJO
*/
currentPage: computed('currentNode', function() {
return get(this, 'currentNode.page');
}),

nextSection: computed('currentSection', 'pages.[]', function() {
let currentSection = this.currentSection;
isFirstPage: computed('currentNode', function() {
return (
get(this, 'currentNode.page.url') === 'index' ||
get(this, 'currentNode') === get(this, 'currentNode.parent.first')
);
}),

if(!currentSection) { return; }
isLastPage: computed('currentNode', function() {
return (
get(this, 'currentNode.page.url') === 'index' ||
get(this, 'currentNode') === get(this, 'currentNode.parent.last')
);
}),

let pages = this.pages;
previousPage: computed('currentNode', function() {
return get(this, 'currentNode.prev.page');
}),

if (pages) {
let page = pages.content.find((content) => content.id === currentSection.id);
nextPage: computed('currentNode', function() {
return get(this, 'currentNode.next.page');
}),

let index = pages.content.indexOf(page);
previousSection: computed('currentNode', function() {
return get(this, 'currentNode.prev.parent.page');
}),

if (index < get(pages, 'length') - 1) {
return pages.objectAt(index + 1);
}
}
nextSection: computed('currentNode', function() {
return get(this, 'currentNode.next.parent.page');
}),
});
Empty file removed tests/unit/.gitkeep
Empty file.
Loading