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:
parent
ed69336481
commit
d872e78339
28 changed files with 807 additions and 45 deletions
129
src/models/alias.js
Normal file
129
src/models/alias.js
Normal 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;
|
|
@ -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(/ /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
|
||||
*
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue