1
0
Fork 0
mirror of https://github.com/codex-team/codex.docs.git synced 2025-07-19 05:09:41 +02:00

remove pages (#27)

* remove pages

* requested changes and unit tests

* update

* fix unit test

* requested changes

* add confirmation

* remove deeply

* remove log

* bugfix

* update placeholder
This commit is contained in:
Murod Khaydarov 2019-01-25 06:19:37 +03:00 committed by GitHub
parent d872e78339
commit ccd627151f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 265 additions and 44 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -186,6 +186,10 @@ class Pages {
throw new Error('Page with given id does not exist'); throw new Error('Page with given id does not exist');
} }
const alias = await Alias.get(page.uri);
await alias.destroy();
return page.destroy(); return page.destroy();
} }
} }

View file

@ -90,6 +90,20 @@ class PagesOrder {
pageOrder.putAbove(currentPageId, putAbovePageId); pageOrder.putAbove(currentPageId, putAbovePageId);
await pageOrder.save(); await pageOrder.save();
} }
/**
* @param parentId
* @returns {Promise<void>}
*/
static async remove(parentId) {
const order = await Model.get(parentId);
if (!order._id) {
throw new Error('Page with given id does not contain order');
}
return order.destroy();
}
} }
module.exports = PagesOrder; module.exports = PagesOrder;

View file

@ -27,6 +27,7 @@ export default class Writing {
this.nodes = { this.nodes = {
editorWrapper: null, editorWrapper: null,
saveButton: null, saveButton: null,
removeButton: null,
parentIdSelector: null, parentIdSelector: null,
putAboveIdSelector: null, putAboveIdSelector: null,
uriInput: null uriInput: null
@ -42,11 +43,7 @@ export default class Writing {
/** /**
* Create Editor * Create Editor
*/ */
this.nodes.editorWrapper = document.createElement('div'); this.nodes.editorWrapper = document.getElementById('codex-editor');
this.nodes.editorWrapper.id = 'codex-editor';
moduleEl.appendChild(this.nodes.editorWrapper);
if (settings.page) { if (settings.page) {
this.page = settings.page; this.page = settings.page;
} }
@ -58,10 +55,24 @@ export default class Writing {
/** /**
* Activate form elements * Activate form elements
*/ */
this.nodes.saveButton = moduleEl.querySelector('[name="js-submit"]'); this.nodes.saveButton = moduleEl.querySelector('[name="js-submit-save"]');
this.nodes.saveButton.addEventListener('click', () => { this.nodes.saveButton.addEventListener('click', () => {
this.saveButtonClicked(); this.saveButtonClicked();
}); });
this.nodes.removeButton = moduleEl.querySelector('[name="js-submit-remove"]');
if (this.nodes.removeButton) {
this.nodes.removeButton.addEventListener('click', () => {
const isUserAgree = confirm("Are you sure?");
if (!isUserAgree) {
return
}
this.removeButtonClicked();
});
}
this.nodes.parentIdSelector = moduleEl.querySelector('[name="parent"]'); this.nodes.parentIdSelector = moduleEl.querySelector('[name="parent"]');
this.nodes.putAboveIdSelector = moduleEl.querySelector('[name="above"]'); this.nodes.putAboveIdSelector = moduleEl.querySelector('[name="above"]');
this.nodes.uriInput = moduleEl.querySelector('[name="uri-input"]'); this.nodes.uriInput = moduleEl.querySelector('[name="uri-input"]');
@ -149,4 +160,31 @@ export default class Writing {
console.log('Saving error: ', savingError); console.log('Saving error: ', savingError);
} }
} }
/**
* @returns {Promise<void>}
*/
async removeButtonClicked() {
try {
const endpoint = this.page ? '/api/page/' + this.page._id : '';
let response = await fetch(endpoint, {
method: 'DELETE'
});
response = await response.json();
if (response.success) {
if (response.result && response.result._id) {
document.location = '/page/' + response.result._id;
} else {
document.location = '/';
}
} else {
alert(response.error);
console.log('Server fetch failed:', response.error);
}
} catch (e) {
console.log('Server fetch failed due to the:', e);
}
}
} }

View file

@ -35,6 +35,7 @@
&__button { &__button {
@apply --button; @apply --button;
@apply --button-primary;
margin: auto 30px auto auto; margin: auto 30px auto auto;
} }
} }

View file

@ -28,6 +28,7 @@
&-button { &-button {
@apply --button; @apply --button;
@apply --button-primary;
padding: 5px 10px; padding: 5px 10px;
font-size: 13px; font-size: 13px;
margin-left: 10px; margin-left: 10px;

View file

@ -10,6 +10,7 @@
&__save { &__save {
@apply --button; @apply --button;
@apply --button-primary;
margin-left: auto; margin-left: auto;
} }
@ -23,6 +24,13 @@
} }
} }
.writing-buttons {
&__remove {
@apply --button;
@apply --button-danger;
}
}
.uri-input { .uri-input {
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;

View file

@ -3,6 +3,7 @@
--color-text-second: #7B7E89; --color-text-second: #7B7E89;
--color-line-gray: #E8E8EB; --color-line-gray: #E8E8EB;
--color-link-active: #388AE5; --color-link-active: #388AE5;
--color-button-danger: #ff1629;
--color-gray-border: rgba(var(--color-line-gray), 0.48); --color-gray-border: rgba(var(--color-line-gray), 0.48);
/** /**
@ -17,15 +18,33 @@
display: inline-block; display: inline-block;
padding: 9px 15px; padding: 9px 15px;
border-radius: 3px; border-radius: 3px;
background: var(--color-link-active); color: #6c6375;
color: #fff; background: #fcfcff;
box-shadow: inset 0 0 0 1px rgba(184, 189, 206, 0.2);
font-size: 14px; font-size: 14px;
line-height: 1em; line-height: 1em;
text-decoration: none; text-decoration: none;
cursor: pointer;
svg { svg {
margin: 0 0.3em 0 -0.05em; margin: 0 0.3em 0 -0.05em;
} }
}
--button-danger {
background: var(--color-button-danger);
color: #fff;
box-shadow: none;
&:hover {
background: color-mod(var(--color-button-danger) blackness(+10%));
}
}
--button-primary {
background: var(--color-link-active);
color: #fff;
box-shadow: none;
&:hover { &:hover {
background: color-mod(var(--color-link-active) blackness(+10%)); background: color-mod(var(--color-link-active) blackness(+10%));

View file

@ -13,6 +13,8 @@ const binaryMD5 = require('../utils/crypto');
/** /**
* @class Alias * @class Alias
* @classdesc Alias model
*
* @property {string} _id - alias id * @property {string} _id - alias id
* @property {string} hash - alias binary hash * @property {string} hash - alias binary hash
* @property {string} type - entity type * @property {string} type - entity type
@ -124,6 +126,17 @@ class Alias {
return alias.save(); return alias.save();
} }
/**
* @returns {Promise<Alias>}
*/
async destroy() {
await aliasesDb.remove({_id: this._id});
delete this._id;
return this;
}
} }
module.exports = Alias; module.exports = Alias;

View file

@ -83,7 +83,7 @@ class Page {
this.body = body || this.body; this.body = body || this.body;
this.title = this.extractTitleFromBody(); this.title = this.extractTitleFromBody();
this.uri = uri || ''; this.uri = uri || '';
this._parent = parent || this._parent; this._parent = parent || this._parent || '0';
} }
/** /**

View file

@ -18,6 +18,7 @@ class PageOrder {
* Returns current Page's children order * Returns current Page's children order
* *
* @param {string} pageId - page's id * @param {string} pageId - page's id
* @returns {PageOrder}
*/ */
static async get(pageId) { static async get(pageId) {
const order = await db.findOne({page: pageId}); const order = await db.findOne({page: pageId});
@ -117,6 +118,42 @@ class PageOrder {
this.order.splice(found2 + margin, 1); this.order.splice(found2 + margin, 1);
} }
/**
* Returns page before passed page with id
*
* @param {string} pageId
*/
getPageBefore(pageId) {
const currentPageInOrder = this.order.indexOf(pageId);
/**
* If page not found or first return nothing
*/
if (currentPageInOrder <= 0) {
return;
}
return this.order[currentPageInOrder - 1];
}
/**
* Returns page before passed page with id
*
* @param pageId
*/
getPageAfter(pageId) {
const currentPageInOrder = this.order.indexOf(pageId);
/**
* If page not found or is last
*/
if (currentPageInOrder === -1 || currentPageInOrder === this.order.length - 1) {
return;
}
return this.order[currentPageInOrder + 1];
}
/** /**
* Returns ordered list * Returns ordered list
* *

View file

@ -3,7 +3,7 @@ const router = express.Router();
const multer = require('multer')(); const multer = require('multer')();
const Pages = require('../../controllers/pages'); const Pages = require('../../controllers/pages');
const PagesOrder = require('../../controllers/pagesOrder'); const PagesOrder = require('../../controllers/pagesOrder');
const Aliases = require("../../controllers/aliases");
/** /**
* GET /page/:id * GET /page/:id
* *
@ -111,11 +111,53 @@ router.post('/page/:id', multer.any(), async (req, res) => {
*/ */
router.delete('/page/:id', async (req, res) => { router.delete('/page/:id', async (req, res) => {
try { try {
const page = await Pages.remove(req.params.id); const pageId = req.params.id;
const page = await Pages.get(pageId);
const parentPageOrder = await PagesOrder.get(page._parent);
const pageBeforeId = parentPageOrder.getPageBefore(page._id);
const pageAfterId = parentPageOrder.getPageAfter(page._id);
let pageToRedirect;
if (pageBeforeId) {
pageToRedirect = await Pages.get(pageBeforeId);
} else if (pageAfterId) {
pageToRedirect = await Pages.get(pageAfterId);
} else {
pageToRedirect = page._parent !== "0" ? await Pages.get(page._parent) : null;
}
/**
* remove current page and go deeper to remove children with orders
*
* @param startFrom
* @returns {Promise<void>}
*/
async function deleteRecursively(startFrom) {
let order = [];
try {
const children = await PagesOrder.get(startFrom);
order = children.order;
} catch (e) {}
order.forEach(async id => {
await deleteRecursively(id);
});
await Pages.remove(startFrom);
try {
await PagesOrder.remove(startFrom);
} catch (e) {}
}
await deleteRecursively(req.params.id);
// remove also from parent's order
parentPageOrder.remove(req.params.id);
await parentPageOrder.save();
res.json({ res.json({
success: true, success: true,
result: page result: pageToRedirect
}); });
} catch (err) { } catch (err) {
res.status(400).json({ res.status(400).json({

View file

@ -44,14 +44,16 @@
</span> </span>
{% endif %} {% endif %}
</span> </span>
<span class="writing-header__save" name="js-submit"> <span class="writing-header__save" name="js-submit-save">Save</span>
Save
</span>
</header> </header>
<div id="codex-editor"></div>
<div class="writing-buttons">
{% if page._id is not empty %}
<span class="writing-buttons__remove" name="js-submit-remove">Remove</span>
{% endif %}
</div>
{% if page is not empty %} {% if page is not empty %}
<main> <p><input type="text" class="uri-input" name="uri-input" placeholder="URI(Optional)" value="{{ page.uri }}"></p>
<input type="text" class="uri-input" name="uri-input" placeholder="Uri(Optional)" value="{{ page.uri }}">
</main>
{% endif %} {% endif %}
</section> </section>
{% endblock %} {% endblock %}

View file

@ -38,7 +38,7 @@ describe('Page model', () => {
expect(data.title).to.be.empty; expect(data.title).to.be.empty;
expect(data.uri).to.be.empty; expect(data.uri).to.be.empty;
expect(data.body).to.be.undefined; expect(data.body).to.be.undefined;
expect(data.parent).to.be.undefined; expect(data.parent).to.be.equal('0');
page = new Page(null); page = new Page(null);
@ -48,7 +48,7 @@ describe('Page model', () => {
expect(data.title).to.be.empty; expect(data.title).to.be.empty;
expect(data.uri).to.be.empty; expect(data.uri).to.be.empty;
expect(data.body).to.be.undefined; expect(data.body).to.be.undefined;
expect(data.parent).to.be.undefined; expect(data.parent).to.be.equal('0');
const initialData = { const initialData = {
_id: 'page_id', _id: 'page_id',
@ -74,13 +74,13 @@ describe('Page model', () => {
expect(data.title).to.equal(initialData.body.blocks[0].data.text); expect(data.title).to.equal(initialData.body.blocks[0].data.text);
expect(data.uri).to.be.empty; expect(data.uri).to.be.empty;
expect(data.body).to.deep.equal(initialData.body); expect(data.body).to.deep.equal(initialData.body);
expect(data.parent).to.be.undefined; expect(data.parent).to.be.equal('0');
expect(json._id).to.equal(initialData._id); expect(json._id).to.equal(initialData._id);
expect(json.title).to.equal(initialData.body.blocks[0].data.text); expect(json.title).to.equal(initialData.body.blocks[0].data.text);
expect(json.title).to.equal(initialData.body.blocks[0].data.text); expect(json.title).to.equal(initialData.body.blocks[0].data.text);
expect(json.body).to.deep.equal(initialData.body); expect(json.body).to.deep.equal(initialData.body);
expect(json.parent).to.be.undefined; expect(json.parent).to.be.equal('0');
const update = { const update = {
_id: 12345, _id: 12345,
@ -104,7 +104,7 @@ describe('Page model', () => {
expect(data.title).to.equal(update.body.blocks[0].data.text); expect(data.title).to.equal(update.body.blocks[0].data.text);
expect(data.uri).to.be.empty; expect(data.uri).to.be.empty;
expect(data.body).to.equal(update.body); expect(data.body).to.equal(update.body);
expect(data.parent).to.be.undefined; expect(data.parent).to.be.equal('0');
}); });
it('Saving, updating and deleting model in the database', async () => { it('Saving, updating and deleting model in the database', async () => {
@ -352,4 +352,31 @@ describe('Page model', () => {
expect(page.title).to.equal(pageData.body.blocks[0].data.text); expect(page.title).to.equal(pageData.body.blocks[0].data.text);
}); });
it('test deletion', async () => {
const pages = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
const orders = {
'0' : ['1', '2', '3'],
'1' : ['4', '5'],
'5' : ['6', '7', '8'],
'3' : ['9']
};
function deleteRecursively(startFrom) {
const order = orders[startFrom];
if (!order) {
const found = pages.indexOf(startFrom);
pages.splice(found, 1);
return;
}
order.forEach(id => {
deleteRecursively(id);
});
const found = pages.indexOf(startFrom);
pages.splice(found, 1);
}
});
}); });

View file

@ -7,7 +7,7 @@ const {pagesOrder} = require('../../src/utils/database');
describe('PageOrder model', () => { describe('PageOrder model', () => {
after(() => { after(() => {
const pathToDB = path.resolve(__dirname, '../../', config.database, './pages.db'); const pathToDB = path.resolve(__dirname, '../../', config.database, './pagesOrder.db');
if (fs.existsSync(pathToDB)) { if (fs.existsSync(pathToDB)) {
fs.unlinkSync(pathToDB); fs.unlinkSync(pathToDB);

View file

@ -32,6 +32,7 @@ describe('Aliases REST: ', () => {
it('Finding page with alias', async () => { it('Finding page with alias', async () => {
const body = { const body = {
time: 1548375408533,
blocks: [ blocks: [
{ {
type: 'header', type: 'header',
@ -51,7 +52,7 @@ describe('Aliases REST: ', () => {
const {result: {uri}} = put.body; const {result: {uri}} = put.body;
const get = await agent.get(`/${uri}`); const get = await agent.get('/' + uri);
expect(get).to.have.status(200); expect(get).to.have.status(200);
}); });

View file

@ -30,10 +30,20 @@ describe('Pages REST: ', () => {
}); });
after(async () => { after(async () => {
const pathToDB = path.resolve(__dirname, '../../', config.database, './pages.db'); const pathToPagesDB = path.resolve(__dirname, '../../', config.database, './pages.db');
const pathToPagesOrderDB = path.resolve(__dirname, '../../', config.database, './pagesOrder.db');
const pathToAliasesDB = path.resolve(__dirname, '../../', config.database, './aliases.db');
if (fs.existsSync(pathToDB)) { if (fs.existsSync(pathToPagesDB)) {
fs.unlinkSync(pathToDB); fs.unlinkSync(pathToPagesDB);
}
if (fs.existsSync(pathToPagesOrderDB)) {
fs.unlinkSync(pathToPagesOrderDB);
}
if (fs.existsSync(pathToAliasesDB)) {
fs.unlinkSync(pathToAliasesDB);
} }
}); });
@ -325,14 +335,18 @@ describe('Pages REST: ', () => {
const {success, result} = res.body; const {success, result} = res.body;
expect(success).to.be.true; expect(success).to.be.true;
if (result) {
expect(result._id).to.be.undefined; expect(result._id).to.be.undefined;
expect(result.title).to.equal(body.blocks[0].data.text); expect(result.title).to.equal(body.blocks[0].data.text);
expect(result.uri).to.equal(transformToUri(body.blocks[0].data.text)); expect(result.uri).to.equal(transformToUri(body.blocks[0].data.text));
expect(result.body).to.deep.equal(body); expect(result.body).to.deep.equal(body);
const deletedPage = await model.get(_id); const deletedPage = await model.get(_id);
expect(deletedPage._id).to.be.undefined; expect(deletedPage._id).to.be.undefined;
} else {
expect(result).to.be.null;
}
}); });
it('Removing page with not existing id', async () => { it('Removing page with not existing id', async () => {