diff --git a/src/backend/controllers/pages.ts b/src/backend/controllers/pages.ts index 6a4bd07..fa92c2b 100644 --- a/src/backend/controllers/pages.ts +++ b/src/backend/controllers/pages.ts @@ -1,5 +1,8 @@ import Page, { PageData } from '../models/page'; import Alias from '../models/alias'; +import PagesOrder from './pagesOrder'; +import PageOrder from '../models/pageOrder'; +import HttpException from "../exceptions/httpException"; type PageDataFields = keyof PageData; @@ -62,6 +65,121 @@ class Pages { return nullFilteredPages; } + /** + * Group all pages by their parents + * If the pageId is passed, it excludes passed page from result pages + * + * @param {string} pageId - pageId to exclude from result pages + * @returns {Page[]} + */ + public static async groupByParent(pageId = ''): Promise { + const result: Page[] = []; + const orderGroupedByParent: Record = {}; + const rootPageOrder = await PagesOrder.getRootPageOrder(); + const childPageOrder = await PagesOrder.getChildPageOrder(); + const orphanPageOrder: PageOrder[] = []; + + /** + * If there is no root and child page order, then it returns an empty array + */ + if (!rootPageOrder || (!rootPageOrder && childPageOrder.length <= 0)) { + return []; + } + + const pages = (await this.getAll()).reduce((map, _page) => { + map.set(_page._id, _page); + + return map; + }, new Map); + const idsOfRootPages = rootPageOrder.order; + + /** + * It groups root pages and 1 level pages by its parent + */ + idsOfRootPages.reduce((prev, curr, idx) => { + const childPages:PageOrder[] = []; + + childPageOrder.forEach((pageOrder, _idx) => { + if (pageOrder.page === curr) { + childPages.push(pageOrder); + childPageOrder.splice(_idx, 1); + } + }); + + const hasChildPage = childPages.length > 0; + + prev[curr] = []; + prev[curr].push(curr); + + /** + * It attaches 1 level page id to its parent page id + */ + if (hasChildPage) { + prev[curr].push(...childPages[0].order); + } + + /** + * If non-attached childPages which is not 1 level page still remains, + * It is stored as an orphan page so that it can be processed in the next statements + */ + if (idx === idsOfRootPages.length - 1 && childPageOrder.length > 0) { + orphanPageOrder.push(...childPageOrder); + } + + return prev; + }, orderGroupedByParent); + + let count = 0; + + /** + * It groups remained ungrouped pages by its parent + */ + while (orphanPageOrder.length > 0) { + if (count >= 1000) { + throw new HttpException(500, `Page cannot be processed`); + } + + orphanPageOrder.forEach((orphanOrder, idx) => { + // It loops each of grouped orders formatted as [root page id(1): corresponding child pages id(2)] + Object.entries(orderGroupedByParent).forEach(([parentPageId, value]) => { + // If (2) contains orphanOrder's parent id(page) + if (orphanOrder.page && orphanOrder.order && value.includes(orphanOrder.page)) { + // Append orphanOrder's id(order) into its parent id + orderGroupedByParent[parentPageId].splice(value.indexOf(orphanOrder.page) + 1, 0, ...orphanOrder.order); + // Finally, remove orphanOrder from orphanPageOrder + orphanPageOrder.splice(idx, 1); + } + }); + }); + + count += 1; + } + + /** + * It converts grouped pages(object) to array + */ + Object.values(orderGroupedByParent).flatMap(arr => [ ...arr ]) + .forEach(arr => { + result.push(pages.get(arr)); + }); + + /** + * If the pageId passed, it excludes itself from result pages + * Otherwise just returns result itself + */ + if (pageId) { + return this.removeChildren(result, pageId).reduce((prev, curr) => { + if (curr instanceof Page) { + prev.push(curr); + } + + return prev; + }, Array()); + } else { + return result; + } + } + /** * Set all children elements to null * @@ -69,7 +187,7 @@ class Pages { * @param {string} parent - id of parent page * @returns {Array} */ - public static removeChildren(pagesAvailable: Array, parent: string | undefined): Array { + public static removeChildren(pagesAvailable: Array, parent: string | undefined): Array { pagesAvailable.forEach(async (item, index) => { if (item === null || item._parent !== parent) { return; diff --git a/src/backend/controllers/pagesOrder.ts b/src/backend/controllers/pagesOrder.ts index f084af3..846c9ac 100644 --- a/src/backend/controllers/pagesOrder.ts +++ b/src/backend/controllers/pagesOrder.ts @@ -33,6 +33,24 @@ class PagesOrder { return PageOrder.getAll(); } + /** + * Returns only root page's order + * + * @returns {Promise} + */ + public static async getRootPageOrder(): Promise { + return PageOrder.getRootPageOrder(); + } + + /** + * Returns only child page's order + * + * @returns {Promise} + */ + public static async getChildPageOrder(): Promise { + return PageOrder.getChildPageOrder(); + } + /** * Pushes the child page to the parent's order list * diff --git a/src/backend/models/pageOrder.ts b/src/backend/models/pageOrder.ts index 761fc6a..84c18b5 100644 --- a/src/backend/models/pageOrder.ts +++ b/src/backend/models/pageOrder.ts @@ -75,6 +75,28 @@ class PageOrder { return Promise.all(docs.map(doc => new PageOrder(doc))); } + /** + * Returns only root page's order + * + * @returns {Promise} + */ + public static async getRootPageOrder(): Promise { + const docs = await db.findOne({ 'page': '0' }); + + return new PageOrder(docs); + } + + /** + * Returns only child page's order + * + * @returns {Promise} + */ + public static async getChildPageOrder(): Promise { + const docs = await this.getAll({ 'page': { $ne: '0' } }); + + return Promise.all(docs.map(doc => new PageOrder(doc))); + } + /** * constructor data setter * diff --git a/src/backend/routes/pages.ts b/src/backend/routes/pages.ts index 82c8107..fe70f37 100644 --- a/src/backend/routes/pages.ts +++ b/src/backend/routes/pages.ts @@ -11,10 +11,10 @@ const router = express.Router(); */ router.get('/page/new', verifyToken, allowEdit, async (req: Request, res: Response, next: NextFunction) => { try { - const pagesAvailable = await Pages.getAll(); + const pagesAvailableGrouped = await Pages.groupByParent(); res.render('pages/form', { - pagesAvailable, + pagesAvailableGrouped, page: null, }); } catch (error) { @@ -32,6 +32,7 @@ router.get('/page/edit/:id', verifyToken, allowEdit, async (req: Request, res: R try { const page = await Pages.get(pageId); const pagesAvailable = await Pages.getAllExceptChildren(pageId); + const pagesAvailableGrouped = await Pages.groupByParent(pageId); if (!page._parent) { throw new Error('Parent not found'); @@ -42,7 +43,7 @@ router.get('/page/edit/:id', verifyToken, allowEdit, async (req: Request, res: R res.render('pages/form', { page, parentsChildrenOrdered, - pagesAvailable, + pagesAvailableGrouped, }); } catch (error) { res.status(404); diff --git a/src/backend/views/pages/form.twig b/src/backend/views/pages/form.twig index 0ce5a9e..d2bedd7 100644 --- a/src/backend/views/pages/form.twig +++ b/src/backend/views/pages/form.twig @@ -22,7 +22,7 @@ {% endif %}