1
0
Fork 0
mirror of https://github.com/codex-team/codex.docs.git synced 2025-08-08 15:05:26 +02:00

Merged with master, fixed conflicts

This commit is contained in:
DorofeevMark 2019-01-24 19:50:25 +03:00
commit 91f78521a0
18 changed files with 1070 additions and 433 deletions

View file

@ -17,6 +17,7 @@
"codex.editor.header": "^2.0.5",
"cookie-parser": "~1.4.3",
"debug": "~4.1.0",
"eslint-plugin-standard": "^4.0.0",
"express": "~4.16.0",
"http-errors": "~1.7.1",
"module-dispatcher": "^1.0.2",
@ -48,6 +49,8 @@
"eslint": "^5.3.0",
"eslint-config-codex": "github:codex-team/eslint-config",
"eslint-plugin-chai-friendly": "^0.4.1",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-node": "^8.0.1",
"highlight.js": "^9.13.1",
"husky": "^1.1.2",
"mini-css-extract-plugin": "^0.4.3",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -43,6 +43,38 @@ class Pages {
return Model.getAll();
}
/**
* @static
* Return all pages without children of passed page
*
* @param {string} parent - id of current page
* @returns {Promise<Page[]>}
*/
static async getAllExceptChildrens(parent) {
let pagesAvailable = this.removeChildren(await Pages.getAll(), parent);
return pagesAvailable.filter((item) => item !== null);
}
/**
* @static
* Set all children elements to null
*
* @param {Page[]} [pagesAvailable] - Array of all pages
* @param {string} parent - id of parent page
* @returns {Array<?Page>}
*/
static removeChildren(pagesAvailable, parent) {
pagesAvailable.forEach(async (item, index) => {
if (item === null || item._parent !== parent) {
return;
}
pagesAvailable[index] = null;
pagesAvailable = Pages.removeChildren(pagesAvailable, item._id);
});
return pagesAvailable;
}
/**
* Create new page model and save it in the database
*
@ -52,6 +84,7 @@ class Pages {
static async insert(data) {
try {
Pages.validate(data);
const page = new Model(data);
const insertedPage = await page.save();

View file

@ -0,0 +1,95 @@
const Model = require('../models/pageOrder');
/**
* @class PagesOrder
* @classdesc PagesOrder controller
*
* Manipulates with Pages: changes the order, deletes, updates and so on...
*/
class PagesOrder {
/**
* Returns Page's order
*
* @param {string} parentId - of which page we want to get children order
* @returns {Promise<PageOrder>}
*/
static async get(parentId) {
const order = await Model.get(parentId);
if (!order._id) {
throw new Error('Page with given id does not contain order');
}
return order;
}
/**
* Pushes the child page to the parent's order list
*
* @param {string} parentId - parent page's id
* @param {string} childId - new page pushed to the order
*/
static async push(parentId, childId) {
const order = await Model.get(parentId);
order.push(childId);
await order.save();
}
/**
* Move one page to another Page's order
*
* @param {string} oldParentId - old parent page's id
* @param {string} newParentId - new parent page's id
* @param {string} targetPageId - page's id which is changing the parent page
*/
static async move(oldParentId, newParentId, targetPageId) {
const oldParentOrder = await Model.get(oldParentId);
oldParentOrder.remove(targetPageId);
await oldParentOrder.save();
const newParentOrder = await Model.get(newParentId);
newParentOrder.push(targetPageId);
await newParentOrder.save();
}
/**
* Returns new array with ordered pages
*
* @param {Page[]} pages - list of all available pages
* @param {string} currentPageId - page's id around which we are ordering
* @param {string} parentPageId - parent page's id that contains page above
* @param {Boolean} ignoreSelf - should we ignore current page in list or not
* @return {Page[]}
*/
static async getOrderedChildren(pages, currentPageId, parentPageId, ignoreSelf = false) {
const children = await PagesOrder.get(parentPageId);
const result = [];
children.order.forEach(pageId => {
pages.forEach(page => {
if (page._id === pageId && (pageId !== currentPageId || !ignoreSelf)) {
result.push(page);
}
});
});
return result;
}
/**
* @param {string} currentPageId - page's id that changes the order
* @param {string} parentPageId - parent page's id that contains both two pages
* @param {string} putAbovePageId - page's id above which we put the target page
*/
static async update(currentPageId, parentPageId, putAbovePageId) {
const pageOrder = await Model.get(parentPageId);
pageOrder.putAbove(currentPageId, putAbovePageId);
await pageOrder.save();
}
}
module.exports = PagesOrder;

View file

@ -28,6 +28,7 @@ export default class Writing {
editorWrapper: null,
saveButton: null,
parentIdSelector: null,
putAboveIdSelector: null,
uriInput: null
};
}
@ -62,6 +63,7 @@ export default class Writing {
this.saveButtonClicked();
});
this.nodes.parentIdSelector = moduleEl.querySelector('[name="parent"]');
this.nodes.putAboveIdSelector = moduleEl.querySelector('[name="above"]');
this.nodes.uriInput = moduleEl.querySelector('[name="uri-input"]');
};
@ -100,8 +102,15 @@ export default class Writing {
throw new Error('Entry should start with Header');
}
/** get ordering selector value */
let putAbovePageId = null;
if (this.nodes.putAboveIdSelector) {
putAbovePageId = this.nodes.putAboveIdSelector.value;
}
return {
parent: this.nodes.parentIdSelector.value,
putAbovePageId: putAbovePageId,
uri: uri,
body: editorData
};

View file

@ -16,6 +16,10 @@
&__left {
margin: auto 0;
color: var(--color-text-second);
& span {
margin-right: 10px;
}
}
}

View file

@ -8,7 +8,6 @@ const translateString = require('../utils/translation');
* @property {string} uri - page uri
* @property {*} body - page body
* @property {string} parent - id of parent page
*
*/
/**

156
src/models/pageOrder.js Normal file
View file

@ -0,0 +1,156 @@
const {pagesOrder: db} = require('../utils/database/index');
/**
* @typedef {Object} PageOrderData
* @property {string} _id - row unique id
* @property {string} page - page id
* @property {Array<string>} order - list of ordered pages
*/
/**
* @class PageOrder
* @classdesc PageOrder
*
* Creates order for Pages with children
*/
class PageOrder {
/**
* Returns current Page's children order
*
* @param {string} pageId - page's id
*/
static async get(pageId) {
const order = await db.findOne({page: pageId});
let data = {};
if (!order) {
data.page = pageId;
} else {
data = order;
}
return new PageOrder(data);
}
/**
* @constructor
*
* @param {PageOrderData} data
*/
constructor(data = {}) {
if (data === null) {
data = {};
}
if (data._id) {
this._id = data._id;
}
this.data = data;
}
/**
* constructor data setter
* @param {PageOrderData} pageOrderData
*/
set data(pageOrderData) {
this._page = pageOrderData.page || 0;
this._order = pageOrderData.order || [];
}
/**
* Return Page Children order
* @returns {PageOrderData}
*/
get data() {
return {
_id: this._id,
page: '' + this._page,
order: this._order
};
}
/**
* Pushes page id to the orders array
*
* @param {string} pageId - page's id
*/
push(pageId) {
if (typeof pageId === 'string') {
this._order.push(pageId);
} else {
throw new Error('given id is not string');
}
}
/**
* Removes page id from orders array
*
* @param {string} pageId - page's id
*/
remove(pageId) {
const found = this._order.indexOf(pageId);
if (found >= 0) {
this._order.splice(found, 1);
}
}
/**
* @param {string} currentPageId - page's id that changes the order
* @param {string} putAbovePageId - page's id above which we put the target page
*
* @returns void
*/
putAbove(currentPageId, putAbovePageId) {
const found1 = this.order.indexOf(putAbovePageId);
const found2 = this.order.indexOf(currentPageId);
if (found1 === -1 || found2 === -1) {
return;
}
const margin = found1 < found2 ? 1 : 0;
this.order.splice(found1, 0, currentPageId);
this.order.splice(found2 + margin, 1);
}
/**
* Returns ordered list
*
* @return {string[]}
*/
get order() {
return this._order;
}
/**
* Save or update page data in the database
*/
async save() {
if (!this._id) {
const insertedRow = await db.insert(this.data);
this._id = insertedRow._id;
} else {
await db.update({_id: this._id}, this.data);
}
return this;
}
/**
* Remove page data from the database
*/
async destroy() {
await db.remove({_id: this._id});
delete this._id;
return this;
}
}
module.exports = PageOrder;

View file

@ -2,6 +2,7 @@ const express = require('express');
const router = express.Router();
const multer = require('multer')();
const Pages = require('../../controllers/pages');
const PagesOrder = require('../../controllers/pagesOrder');
/**
* GET /page/:id
@ -55,6 +56,9 @@ router.put('/page', multer.any(), async (req, res) => {
const {title, body, parent} = req.body;
const page = await Pages.insert({title, body, parent});
/** push to the orders array */
await PagesOrder.push(parent, page._id);
res.json({
success: true,
result: page
@ -76,9 +80,18 @@ router.post('/page/:id', multer.any(), async (req, res) => {
const {id} = req.params;
try {
const {title, body, parent, uri} = req.body;
const page = await Pages.update(id, {title, body, parent, uri});
const {title, body, parent, putAbovePageId, uri} = req.body;
let page = await Pages.get(id);
if (page._parent !== parent) {
await PagesOrder.move(page._parent, parent, id);
} else {
if (putAbovePageId && putAbovePageId !== '0') {
await PagesOrder.update(page._id, page._parent, putAbovePageId);
}
}
page = await Pages.update(id, {title, body, parent, uri});
res.json({
success: true,
result: page

View file

@ -1,17 +1,47 @@
const Pages = require('../../controllers/pages');
const PagesOrder = require('../../controllers/pagesOrder');
const asyncMiddleware = require('../../utils/asyncMiddleware');
/**
* Process one-level pages list to parent-childrens list
* @param {Page[]} pages - list of all available pages
* Process one-level pages list to parent-children list
* @param {string[]} pages - list of all available pages
* @param {number} level
* @param {number} currentLevel
*
* @return {Page[]}
*/
function createMenuTree(pages) {
return pages.filter(page => page._parent === '0').map(page => {
async function createMenuTree(pages, level = 1, currentLevel = 1) {
return await Promise.all(pages.map(async pageId => {
const parent = await Pages.get(pageId);
/**
* By default we accept that deepestChildren is empty Array
* @type {Array}
*/
let deepestChildren = [];
/**
* Here we try to check parent's children order
* If we got something, pluck to found Page deeper and get its children order
*/
try {
/**
* Go deeper until we didn't get the deepest level
* On each 'currentLevel' create new Menu Tree with ordered Page ids
*/
if (currentLevel !== level) {
const children = await PagesOrder.get(pageId);
deepestChildren = await createMenuTree(children.order, level, currentLevel + 1)
}
} catch (e) {}
/**
* Assign parent's children with found Menu Tree
*/
return Object.assign({
children: pages.filter(child => child._parent === page._id).reverse()
}, page.data);
});
children: deepestChildren
}, parent.data);
}));
}
/**
@ -21,13 +51,16 @@ function createMenuTree(pages) {
* @param next
*/
module.exports = asyncMiddleware(async function (req, res, next) {
/**
* Pages without parent
* @type {string}
*/
const parentIdOfRootPages = '0';
try {
const menu = await Pages.getAll();
res.locals.menu = createMenuTree(menu);
const rootPages = await PagesOrder.get(parentIdOfRootPages);
res.locals.menu = await createMenuTree(rootPages.order, 2);
} catch (error) {
console.log('Can not load menu:', error);
}
next();
});

View file

@ -1,6 +1,7 @@
const express = require('express');
const router = express.Router();
const Pages = require('../controllers/pages');
const PagesOrder = require('../controllers/pagesOrder');
/**
* Create new page form
@ -21,12 +22,14 @@ router.get('/page/edit/:id', async (req, res, next) => {
const pageId = req.params.id;
try {
let page = await Pages.get(pageId);
let pagesAvailable = await Pages.getAll();
const page = await Pages.get(pageId);
const pagesAvailable = await Pages.getAllExceptChildrens(pageId);
const parentsChildrenOrdered = await PagesOrder.getOrderedChildren(pagesAvailable, pageId, page._parent, true);
res.render('pages/form', {
pagesAvailable,
page
page,
parentsChildrenOrdered,
pagesAvailable
});
} catch (error) {
res.status(404);

View file

@ -1,5 +1,6 @@
const pages = require('./pages');
const aliases = require('./aliases');
const pagesOrder = require('./pagesOrder');
/**
* @class Database
@ -144,5 +145,6 @@ class Database {
module.exports = {
class: Database,
pages: new Database(pages),
aliases: new Database(aliases)
aliases: new Database(aliases),
pagesOrder: new Database(pagesOrder)
};

View file

@ -0,0 +1,34 @@
const Datastore = require('nedb');
const config = require('../../../config');
const db = new Datastore({filename: `./${config.database}/pagesOrder.db`, autoload: true});
/**
* Current DataStore preparation
* Add initial row for RootPage
*/
(async function() {
const parentIdOfRootPages = '0';
const cbk = (resolve, reject) => (err, doc) => {
if (err) {
reject(err);
}
resolve(doc);
};
const order = await new Promise((resolve, reject) => {
db.findOne({page: parentIdOfRootPages}, cbk(resolve, reject));
});
if (!order) {
const initialData = {
page: '0',
order: []
};
await db.insert(initialData);
}
}());
module.exports = db;

View file

@ -9,26 +9,40 @@
<section data-module="writing">
<module-settings hidden>
{
"page": {{ page | json_encode }}
"page": {{ page | json_encode }}
}
</module-settings>
<header class="writing-header">
<span class="writing-header__left">
<span>
New Page at the
{% set currentPageId = 0 %}
{% if page is not empty %}
{% set currentPageId = page._id %}
{% endif %}
<select name="parent">
<option value="0">Root</option>
{% for _page in pagesAvailable %}
{% if _page._id != currentPageId %}
<option value="{{ _page._id }}" {{ page is not empty and page._parent == _page._id ? 'selected' : '' }}>
<select name="parent">
<option value="0">Root</option>
{% for _page in pagesAvailable %}
{% if _page._id != currentPageId %}
<option value="{{ _page._id }}" {{ page is not empty and page._parent == _page._id ? 'selected' : ''}}>
{{ _page.title }}
</option>
{% endif %}
{% endfor %}
</select>
{% endif %}
{% endfor %}
</select>
</span>
{% if parentsChildrenOrdered is not empty %}
<span>
Put Above
<select name="above">
<option value="0">—</option>
{% for _page in parentsChildrenOrdered %}
<option value="{{ _page._id }}">{{ _page.title }}</option>
{% endfor %}
</select>
</span>
{% endif %}
</span>
<span class="writing-header__save" name="js-submit">
Save

143
test/models/pageOrder.js Normal file
View file

@ -0,0 +1,143 @@
const {expect} = require('chai');
const fs = require('fs');
const path = require('path');
const config = require('../../config');
const PageOrder = require('../../src/models/pageOrder');
const {pagesOrder} = require('../../src/utils/database');
describe('PageOrder model', () => {
after(() => {
const pathToDB = path.resolve(__dirname, '../../', config.database, './pages.db');
if (fs.existsSync(pathToDB)) {
fs.unlinkSync(pathToDB);
}
});
it('Empty Model', async () => {
let pageOrder = new PageOrder();
expect(pageOrder.data).to.be.a('object');
let {data} = pageOrder;
expect(data._id).to.be.undefined;
expect(data.page).to.be.to.equal('0');
expect(data.order).to.be.an('array').that.is.empty;
page = new PageOrder(null);
data = page.data;
expect(data._id).to.be.undefined;
expect(data.page).to.be.to.equal('0');
expect(data.order).to.be.an('array').that.is.empty;
const testData = {
_id: 'order_id',
page: 'page_id',
order: []
};
page = new PageOrder(testData);
data = page.data;
expect(data._id).to.equal(testData._id);
expect(data.page).to.equal(testData.page);
expect(data.order).to.be.an('array').that.is.empty;
});
it('Testing Model methods', async () => {
const testData = {
page: 'page_id',
order: ['1', '2']
};
const pageOrder = new PageOrder(testData);
let {data} = await pageOrder.save();
expect(data._id).not.be.undefined;
expect(data.page).to.equal(testData.page);
expect(data.order).to.deep.equals(testData.order);
const insertedPageOrder = await pagesOrder.findOne({_id: data._id});
expect(insertedPageOrder._id).to.equal(data._id);
expect(insertedPageOrder.page).to.equal(data.page);
expect(insertedPageOrder.order).to.deep.equal(data.order);
const updateData = {
page: 'page_id_2',
order: ['3']
};
pageOrder.data = updateData;
await pageOrder.save();
expect(pageOrder.data._id).to.equal(insertedPageOrder._id);
const updatedData = await pagesOrder.findOne({_id: insertedPageOrder._id});
expect(updatedData.page).to.equal(updateData.page);
expect(updatedData.order).to.deep.equal(updateData.order);
await pageOrder.destroy();
expect(pageOrder.data._id).to.be.undefined;
const removedPage = await pagesOrder.findOne({_id: updatedData._id});
expect(removedPage).to.be.null;
});
it('Testing push and remove order methods', async () => {
const testData = {
page: 'page_id',
order: ['1', '2']
};
const pageOrder = new PageOrder(testData);
await pageOrder.save();
pageOrder.push('3');
expect(pageOrder.data.order).to.be.an('array').that.is.not.empty;
pageOrder.data.order.forEach((el) => {
expect(el).to.be.an('string')
});
expect(pageOrder.data.order).to.deep.equals(['1', '2', '3']);
pageOrder.remove('2');
expect(pageOrder.data.order).to.deep.equals(['1', '3']);
expect(() => {
pageOrder.push(3);
}).to.throw('given id is not string');
pageOrder.push('4');
pageOrder.push('5');
pageOrder.push('2');
pageOrder.putAbove('2', '3');
expect(pageOrder.data.order).to.deep.equals(['1', '2', '3', '4', '5']);
pageOrder.putAbove('2', '10');
expect(pageOrder.data.order).to.deep.equals(['1', '2', '3', '4', '5']);
await pageOrder.destroy();
});
it('Testing static methods', async () => {
const testData = {
page: 'page_id',
order: ['1', '2']
};
const pageOrder = new PageOrder(testData);
const insertedData = await pageOrder.save();
const insertedPageOrder = await PageOrder.get(insertedData.data.page);
expect(insertedPageOrder).to.instanceOf(PageOrder);
expect(insertedPageOrder.data._id).to.be.equal(insertedData.data._id);
const emptyInstance = await PageOrder.get(null);
expect(emptyInstance.data.page).to.be.equal('0');
expect(emptyInstance.data.order).to.be.an('array').that.is.empty;
await pageOrder.destroy();
});
});

View file

@ -1,5 +1,6 @@
const {app} = require('../../bin/www');
const model = require('../../src/models/page');
const PageOrder = require('../../src/models/pageOrder');
const translateString = require('../../src/utils/translation');
const fs = require('fs');
@ -47,10 +48,10 @@ describe('Pages REST: ', () => {
}
]
};
const parent = 0;
const res = await agent
.put('/api/page')
.send({body});
.send({body, parent});
expect(res).to.have.status(200);
expect(res).to.be.json;
@ -71,7 +72,11 @@ describe('Pages REST: ', () => {
expect(createdPage.uri).to.equal(transformToUri(body.blocks[0].data.text));
expect(createdPage.body).to.deep.equal(body);
createdPage.destroy();
const pageOrder = await PageOrder.get('' + (createdPage.data.parent || 0));
expect(pageOrder.order).to.be.an('array');
await createdPage.destroy();
await pageOrder.destroy();
});
it('Page data validation on create', async () => {
@ -119,13 +124,15 @@ describe('Pages REST: ', () => {
expect(success).to.be.true;
const foundPage = await model.get(_id);
const pageOrder = await PageOrder.get('' + foundPage._parent);
expect(foundPage._id).to.equal(_id);
expect(foundPage.title).to.equal(body.blocks[0].data.text);
expect(foundPage.uri).to.equal(transformToUri(body.blocks[0].data.text));
expect(foundPage.body).to.deep.equal(body);
foundPage.destroy();
await pageOrder.destroy();
await foundPage.destroy();
});
it('Finding page with not existing id', async () => {
@ -192,6 +199,7 @@ describe('Pages REST: ', () => {
expect(result.body).to.deep.equal(updatedBody);
const updatedPage = await model.get(_id);
const pageOrder = await PageOrder.get('' + updatedPage._parent);
expect(updatedPage._id).to.equal(_id);
expect(updatedPage.title).not.equal(body.blocks[0].data.text);
@ -201,7 +209,8 @@ describe('Pages REST: ', () => {
expect(updatedPage.body).not.equal(body);
expect(updatedPage.body).to.deep.equal(updatedBody);
updatedPage.destroy();
await pageOrder.destroy();
await updatedPage.destroy();
});
it('Handle multiple page creation with the same uri', async () => {

881
yarn.lock

File diff suppressed because it is too large Load diff