1
0
Fork 0
mirror of https://github.com/codex-team/codex.docs.git synced 2025-07-30 02:29:42 +02:00

Beautiful urls (#18)

* Added uri property to Page model

* Added aliases collection

* Added routing form aliases

* Fixed redirect after page creation

* Added abiltity to support few pages with same title

* Added ability to change uri manually

* Changed hash function

* Changed uri parsing

* Removed pages controller promise

* Modified page's tests

* Added tests for alias model

* Added tests for aliases

* Escaping special characters

* Added missed files

* Fixed bugs related to translation

* Fixed parent page link

* Added server validation for uri

* Changed css properties order

* Made uri property of page be optional

* Prevented alias creation from empty uri

* Moved alias types to model
This commit is contained in:
DorofeevMark 2019-01-25 02:23:00 +03:00 committed by Peter Savchenko
parent ed69336481
commit d872e78339
28 changed files with 807 additions and 45 deletions

129
src/models/alias.js Normal file
View file

@ -0,0 +1,129 @@
const {aliases: aliasesDb} = require('../utils/database/index');
const binaryMD5 = require('../utils/crypto');
/**
* @typedef {Object} AliasData
* @property {string} _id - alias id
* @property {string} hash - alias binary hash
* @property {string} type - entity type
* @property {boolean} deprecated - indicate if alias deprecated
* @property {string} id - entity id
*
*/
/**
* @class Alias
* @property {string} _id - alias id
* @property {string} hash - alias binary hash
* @property {string} type - entity type
* @property {boolean} deprecated - indicate if alias deprecated
* @property {string} id - entity title
*/
class Alias {
/**
* Return Alias types
*
* @returns {Object}
*/
static get types() {
return {
PAGE: 'page'
};
};
/**
* Find and return alias with given alias
* @param {string} aliasName - alias of entity
* @returns {Promise<Alias>}
*/
static async get(aliasName) {
const hash = binaryMD5(aliasName);
let data = await aliasesDb.findOne({hash: hash, deprecated: false});
if (!data) {
data = await aliasesDb.findOne({hash: hash});
}
return new Alias(data);
}
/**
* @constructor
*
* @param {AliasData} data
* @param {string} aliasName - alias of entity
*/
constructor(data = {}, aliasName = '') {
if (data === null) {
data = {};
}
if (data._id) {
this._id = data._id;
}
if (aliasName) {
this.hash = binaryMD5(aliasName);
}
this.data = data;
}
/**
* Save or update alias data in the database
*
* @returns {Promise<Alias>}
*/
async save() {
if (!this._id) {
const insertedRow = await aliasesDb.insert(this.data);
this._id = insertedRow._id;
} else {
await aliasesDb.update({_id: this._id}, this.data);
}
return this;
}
/**
* Set AliasData object fields to internal model fields
*
* @param {AliasData} aliasData
*/
set data(aliasData) {
const {id, type, hash, deprecated} = aliasData;
this.id = id || this.id;
this.type = type || this.type;
this.hash = hash || this.hash;
this.deprecated = deprecated || false;
}
/**
* Return AliasData object
*
* @returns {AliasData}
*/
get data() {
return {
_id: this._id,
id: this.id,
type: this.type,
hash: this.hash,
deprecated: this.deprecated
};
}
/**
* Mark alias as deprecated
* @param {string} aliasName - alias of entity
* @returns {Promise<Alias>}
*/
static async markAsDeprecated(aliasName) {
const alias = await Alias.get(aliasName);
alias.deprecated = true;
return alias.save();
}
}
module.exports = Alias;

View file

@ -1,9 +1,11 @@
const {pages: db} = require('../utils/database/index');
const {pages: pagesDb} = require('../utils/database/index');
const translateString = require('../utils/translation');
/**
* @typedef {Object} PageData
* @property {string} _id - page id
* @property {string} title - page title
* @property {string} uri - page uri
* @property {*} body - page body
* @property {string} parent - id of parent page
*/
@ -14,6 +16,7 @@ const {pages: db} = require('../utils/database/index');
*
* @property {string} _id - page id
* @property {string} title - page title
* @property {string} uri - page uri
* @property {*} body - page body
* @property {string} _parent - id of parent page
*/
@ -24,7 +27,18 @@ class Page {
* @returns {Promise<Page>}
*/
static async get(_id) {
const data = await db.findOne({_id});
const data = await pagesDb.findOne({_id});
return new Page(data);
}
/**
* Find and return model of page with given uri
* @param {string} uri - page uri
* @returns {Promise<Page>}
*/
static async getByUri(uri) {
const data = await pagesDb.findOne({uri});
return new Page(data);
}
@ -36,7 +50,7 @@ class Page {
* @returns {Promise<Page[]>}
*/
static async getAll(query = {}) {
const docs = await db.find(query);
const docs = await pagesDb.find(query);
return Promise.all(docs.map(doc => new Page(doc)));
}
@ -64,10 +78,11 @@ class Page {
* @param {PageData} pageData
*/
set data(pageData) {
const {body, parent} = pageData;
const {body, parent, uri} = pageData;
this.body = body || this.body;
this.title = this.extractTitleFromBody();
this.uri = uri || '';
this._parent = parent || this._parent;
}
@ -80,6 +95,7 @@ class Page {
return {
_id: this._id,
title: this.title,
uri: this.uri,
body: this.body,
parent: this._parent
};
@ -95,6 +111,21 @@ class Page {
return headerBlock ? headerBlock.data.text : '';
}
/**
* Transform title for uri
* @return {string}
*/
transformTitleToUri() {
return translateString(this.title
.replace(/&nbsp;/g, ' ')
.replace(/[^a-zA-Z0-9А-Яа-яЁё ]/g, ' ')
.replace(/ +/g, ' ')
.trim()
.toLowerCase()
.split(' ')
.join('-'));
}
/**
* Link given page as parent
*
@ -110,7 +141,7 @@ class Page {
* @returns {Promise<Page>}
*/
get parent() {
return db.findOne({_id: this._parent})
return pagesDb.findOne({_id: this._parent})
.then(data => new Page(data));
}
@ -120,7 +151,7 @@ class Page {
* @returns {Promise<Page[]>}
*/
get children() {
return db.find({parent: this._id})
return pagesDb.find({parent: this._id})
.then(data => data.map(page => new Page(page)));
}
@ -130,12 +161,14 @@ class Page {
* @returns {Promise<Page>}
*/
async save() {
this.uri = await this.composeUri(this.uri);
if (!this._id) {
const insertedRow = await db.insert(this.data);
const insertedRow = await pagesDb.insert(this.data);
this._id = insertedRow._id;
} else {
await db.update({_id: this._id}, this.data);
await pagesDb.update({_id: this._id}, this.data);
}
return this;
@ -147,13 +180,37 @@ class Page {
* @returns {Promise<Page>}
*/
async destroy() {
await db.remove({_id: this._id});
await pagesDb.remove({_id: this._id});
delete this._id;
return this;
}
/**
* Find and return available uri
*
* @returns {Promise<string>}
*/
async composeUri(uri) {
let pageWithSameUriCount = 0;
if (!this._id) {
uri = this.transformTitleToUri();
}
if (uri) {
let pageWithSameUri = await Page.getByUri(uri);
while (pageWithSameUri._id && pageWithSameUri._id !== this._id) {
pageWithSameUriCount++;
pageWithSameUri = await Page.getByUri(uri + `-${pageWithSameUriCount}`);
}
}
return pageWithSameUriCount ? uri + `-${pageWithSameUriCount}` : uri;
}
/**
* Return readable page data
*