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

Editorjs checklist tool (#98)

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
This commit is contained in:
Alexander Menshikov 2020-05-09 05:38:25 +03:00 committed by GitHub
parent c0a4f6f3fd
commit b744ed592a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 2628 additions and 2152 deletions

View file

@ -13,7 +13,7 @@
"build:dev": "webpack ./src/frontend/js/app.js --o='./public/dist/[name].bundle.js' --output-library=Docs --output-public-path=/dist/ -p --mode=development --watch", "build:dev": "webpack ./src/frontend/js/app.js --o='./public/dist/[name].bundle.js' --output-library=Docs --output-public-path=/dist/ -p --mode=development --watch",
"precommit": "yarn lint && yarn test --exit", "precommit": "yarn lint && yarn test --exit",
"generatePassword": "node ./generatePassword.js", "generatePassword": "node ./generatePassword.js",
"editor-upgrade": "yarn add -D @editorjs/{editorjs,header,code,delimiter,list,image,table,inline-code,marker,warning}@latest" "editor-upgrade": "yarn add -D @editorjs/{editorjs,header,code,delimiter,list,image,table,inline-code,marker,warning,checklist}@latest"
}, },
"dependencies": { "dependencies": {
"bcrypt": "^3.0.3", "bcrypt": "^3.0.3",
@ -35,6 +35,7 @@
"node-fetch": "^2.3.0", "node-fetch": "^2.3.0",
"nodemon": "^1.18.3", "nodemon": "^1.18.3",
"twig": "~1.12.0", "twig": "~1.12.0",
"typescript-eslint": "^0.0.1-alpha.0",
"uuid4": "^1.0.0" "uuid4": "^1.0.0"
}, },
"devDependencies": { "devDependencies": {
@ -43,15 +44,16 @@
"@babel/polyfill": "^7.2.5", "@babel/polyfill": "^7.2.5",
"@babel/preset-env": "^7.1.0", "@babel/preset-env": "^7.1.0",
"@codexteam/misprints": "^1.0.0", "@codexteam/misprints": "^1.0.0",
"@editorjs/checklist": "^1.1.0",
"@editorjs/code": "^2.4.1", "@editorjs/code": "^2.4.1",
"@editorjs/delimiter": "^1.1.0", "@editorjs/delimiter": "^1.1.0",
"@editorjs/editorjs": "^2.16.0", "@editorjs/editorjs": "^2.17.0",
"@editorjs/header": "^2.3.2", "@editorjs/header": "^2.4.1",
"@editorjs/image": "^2.3.3", "@editorjs/image": "^2.3.4",
"@editorjs/inline-code": "^1.3.1", "@editorjs/inline-code": "^1.3.1",
"@editorjs/list": "^1.4.0", "@editorjs/list": "^1.4.0",
"@editorjs/marker": "^1.2.2", "@editorjs/marker": "^1.2.2",
"@editorjs/table": "^1.2.0", "@editorjs/table": "^1.2.2",
"@editorjs/warning": "^1.1.1", "@editorjs/warning": "^1.1.1",
"autoprefixer": "^9.1.3", "autoprefixer": "^9.1.3",
"babel": "^6.23.0", "babel": "^6.23.0",
@ -61,8 +63,8 @@
"chai-http": "^4.0.0", "chai-http": "^4.0.0",
"css-loader": "^1.0.0", "css-loader": "^1.0.0",
"cssnano": "^4.1.0", "cssnano": "^4.1.0",
"eslint": "^5.3.0", "eslint": "^6.8.0",
"eslint-config-codex": "github:codex-team/eslint-config", "eslint-config-codex": "^1.3.4",
"eslint-plugin-chai-friendly": "^0.4.1", "eslint-plugin-chai-friendly": "^0.4.1",
"eslint-plugin-import": "^2.14.0", "eslint-plugin-import": "^2.14.0",
"eslint-plugin-node": "^8.0.1", "eslint-plugin-node": "^8.0.1",

File diff suppressed because one or more lines are too long

View file

@ -1,2 +1,2 @@
.hljs{display:block;background:#fff;padding:.5em;color:#333;overflow-x:auto}.hljs-comment,.hljs-meta{color:#969896}.hljs-emphasis,.hljs-quote,.hljs-string,.hljs-strong,.hljs-template-variable,.hljs-variable{color:#df5000}.hljs-keyword,.hljs-selector-tag,.hljs-type{color:#a71d5d}.hljs-attribute,.hljs-bullet,.hljs-literal,.hljs-symbol{color:#0086b3}.hljs-name,.hljs-section{color:#63a35c}.hljs-tag{color:#333}.hljs-attr,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-title{color:#795da3}.hljs-addition{color:#55a532;background-color:#eaffea}.hljs-deletion{color:#bd2c00;background-color:#ffecec}.hljs-link{text-decoration:underline} .hljs{display:block;background:#fff;padding:.5em;color:#333;overflow-x:auto}.hljs-comment,.hljs-meta{color:#969896}.hljs-emphasis,.hljs-quote,.hljs-strong,.hljs-template-variable,.hljs-variable{color:#df5000}.hljs-keyword,.hljs-selector-tag,.hljs-type{color:#d73a49}.hljs-attribute,.hljs-bullet,.hljs-literal,.hljs-symbol{color:#0086b3}.hljs-name,.hljs-section{color:#63a35c}.hljs-tag{color:#333}.hljs-attr,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-title{color:#6f42c1}.hljs-addition{color:#55a532;background-color:#eaffea}.hljs-deletion{color:#bd2c00;background-color:#ffecec}.hljs-link{text-decoration:underline}.hljs-number{color:#005cc5}.hljs-string{color:#032f62}
.diff{display:inline-block;width:100%}.diff span{color:inherit!important}.diff--added{color:#277030;background-color:#e2fce7}.diff--added:before{content:"+";opacity:.4}.diff--removed{color:#ae363c;background-color:#ffe6e6}.diff--removed:before{content:"-";opacity:.4} .diff{display:inline-block;width:100%}.diff span{color:inherit!important}.diff--added{color:#277030;background-color:#e2fce7}.diff--added:before{content:"+";opacity:.4}.diff--removed{color:#ae363c;background-color:#ffe6e6}.diff--removed:before{content:"-";opacity:.4}

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

@ -6,7 +6,6 @@ const Alias = require('../models/alias');
*/ */
class Aliases { class Aliases {
/** /**
* @static
* Find and return entity with given alias * Find and return entity with given alias
* *
* @param {string} aliasName - alias name of entity * @param {string} aliasName - alias name of entity

View file

@ -7,7 +7,6 @@ const Alias = require('../models/alias');
*/ */
class Pages { class Pages {
/** /**
* @static
* Fields required for page model creation * Fields required for page model creation
* *
* @returns {['title', 'body']} * @returns {['title', 'body']}
@ -17,7 +16,6 @@ class Pages {
} }
/** /**
* @static
* Find and return page model with passed id * Find and return page model with passed id
* *
* @param {string} id - page id * @param {string} id - page id
@ -43,20 +41,18 @@ class Pages {
} }
/** /**
* @static
* Return all pages without children of passed page * Return all pages without children of passed page
* *
* @param {string} parent - id of current page * @param {string} parent - id of current page
* @returns {Promise<Page[]>} * @returns {Promise<Page[]>}
*/ */
static async getAllExceptChildren(parent) { static async getAllExceptChildren(parent) {
let pagesAvailable = this.removeChildren(await Pages.getAll(), parent); const pagesAvailable = this.removeChildren(await Pages.getAll(), parent);
return pagesAvailable.filter((item) => item !== null); return pagesAvailable.filter((item) => item !== null);
} }
/** /**
* @static
* Set all children elements to null * Set all children elements to null
* *
* @param {Page[]} [pagesAvailable] - Array of all pages * @param {Page[]} [pagesAvailable] - Array of all pages
@ -71,6 +67,7 @@ class Pages {
pagesAvailable[index] = null; pagesAvailable[index] = null;
pagesAvailable = Pages.removeChildren(pagesAvailable, item._id); pagesAvailable = Pages.removeChildren(pagesAvailable, item._id);
}); });
return pagesAvailable; return pagesAvailable;
} }
@ -91,7 +88,7 @@ class Pages {
if (insertedPage.uri) { if (insertedPage.uri) {
const alias = new Alias({ const alias = new Alias({
id: insertedPage._id, id: insertedPage._id,
type: Alias.types.PAGE type: Alias.types.PAGE,
}, insertedPage.uri); }, insertedPage.uri);
alias.save(); alias.save();
@ -161,7 +158,7 @@ class Pages {
if (updatedPage.uri) { if (updatedPage.uri) {
const alias = new Alias({ const alias = new Alias({
id: updatedPage._id, id: updatedPage._id,
type: Alias.types.PAGE type: Alias.types.PAGE,
}, updatedPage.uri); }, updatedPage.uri);
alias.save(); alias.save();

View file

@ -70,8 +70,8 @@ class PagesOrder {
* @param {Page[]} pages - list of all available pages * @param {Page[]} pages - list of all available pages
* @param {string} currentPageId - page's id around which we are ordering * @param {string} currentPageId - page's id around which we are ordering
* @param {string} parentPageId - parent page's id that contains page above * @param {string} parentPageId - parent page's id that contains page above
* @param {Boolean} ignoreSelf - should we ignore current page in list or not * @param {boolean} ignoreSelf - should we ignore current page in list or not
* @return {Page[]} * @returns {Page[]}
*/ */
static async getOrderedChildren(pages, currentPageId, parentPageId, ignoreSelf = false) { static async getOrderedChildren(pages, currentPageId, parentPageId, ignoreSelf = false) {
const children = await Model.get(parentPageId); const children = await Model.get(parentPageId);

View file

@ -17,6 +17,7 @@ const config = require('../../config');
class Transport { class Transport {
/** /**
* Saves file passed from client * Saves file passed from client
*
* @param {object} multerData - file data from multer * @param {object} multerData - file data from multer
* @param {string} multerData.originalname - original name of the file * @param {string} multerData.originalname - original name of the file
* @param {string} multerData.filename - name of the uploaded file * @param {string} multerData.filename - name of the uploaded file
@ -25,12 +26,18 @@ class Transport {
* @param {string} multerData.mimetype - MIME type of the uploaded file * @param {string} multerData.mimetype - MIME type of the uploaded file
* *
* @param {object} map - object that represents how should fields of File object should be mapped to response * @param {object} map - object that represents how should fields of File object should be mapped to response
* @return {Promise<FileData>} * @returns {Promise<FileData>}
*/ */
static async save(multerData, map) { static async save(multerData, map) {
const { originalname: name, path, filename, size, mimetype } = multerData; const { originalname: name, path, filename, size, mimetype } = multerData;
const file = new Model({ name, filename, path, size, mimetype }); const file = new Model({
name,
filename,
path,
size,
mimetype,
});
await file.save(); await file.save();
@ -45,9 +52,10 @@ class Transport {
/** /**
* Fetches file by passed URL * Fetches file by passed URL
*
* @param {string} url - URL of the file * @param {string} url - URL of the file
* @param {object} map - object that represents how should fields of File object should be mapped to response * @param {object} map - object that represents how should fields of File object should be mapped to response
* @return {Promise<FileData>} * @returns {Promise<FileData>}
*/ */
static async fetch(url, map) { static async fetch(url, map) {
const fetchedFile = await fetch(url); const fetchedFile = await fetch(url);
@ -64,7 +72,7 @@ class Transport {
filename: `${filename}.${ext}`, filename: `${filename}.${ext}`,
path: `${config.uploads}/${filename}.${ext}`, path: `${config.uploads}/${filename}.${ext}`,
size: buffer.length, size: buffer.length,
mimetype: type ? type.mime : fetchedFile.headers.get('content-type') mimetype: type ? type.mime : fetchedFile.headers.get('content-type'),
}); });
await file.save(); await file.save();
@ -94,11 +102,12 @@ class Transport {
if (fields.length > 1) { if (fields.length > 1) {
let object = {}; let object = {};
let result = object; const result = object;
fields.forEach((field, i) => { fields.forEach((field, i) => {
if (i === fields.length - 1) { if (i === fields.length - 1) {
object[field] = data[name]; object[field] = data[name];
return; return;
} }

View file

@ -6,7 +6,6 @@ const Model = require('../models/user');
*/ */
class Users { class Users {
/** /**
* @static
* Find and return user model. * Find and return user model.
* *
* @returns {Promise<User>} * @returns {Promise<User>}

View file

@ -10,6 +10,7 @@ import List from '@editorjs/list';
import Delimiter from '@editorjs/delimiter'; import Delimiter from '@editorjs/delimiter';
import Table from '@editorjs/table'; import Table from '@editorjs/table';
import Warning from '@editorjs/warning'; import Warning from '@editorjs/warning';
import Checklist from '@editorjs/checklist';
/** /**
* Inline Tools for the Editor * Inline Tools for the Editor
@ -80,6 +81,11 @@ export default class Editor {
inlineToolbar: true inlineToolbar: true
}, },
checklist: {
class: Checklist,
inlineToolbar: true,
},
/** /**
* Inline Tools * Inline Tools
*/ */

View file

@ -362,3 +362,59 @@
padding-left: 15px; padding-left: 15px;
} }
} }
/**
* Checklist
* ==================
*/
.block-checklist {
margin: 20px 0;
&__item {
display: flex;
box-sizing: content-box;
align-items: center;
&-checkbox {
display: inline-block;
flex-shrink: 0;
position: relative;
width: 20px;
height: 20px;
margin: 0 10px 0 0;
border-radius: 50%;
border: 1px solid #d0d0d0;
background: #fff;
user-select: none;
&::after {
position: absolute;
top: 5px;
left: 5px;
width: 8px;
height: 5px;
border: 2px solid #fcfff4;
border-top: none;
border-right: none;
background: transparent;
content: '';
opacity: 0;
transform: rotate(-45deg);
}
&--checked {
background: #388ae5;
border-color: #388ae5;
}
}
&-text {
outline: none;
flex-grow: 1;
padding: 5px 0;
}
}
}
.block-checklist__item-checkbox--checked, .block-checklist__item-checkbox::after {
opacity: 1;
}

View file

@ -2,7 +2,7 @@ const { aliases: aliasesDb } = require('../utils/database/index');
const { binaryMD5 } = require('../utils/crypto'); const { binaryMD5 } = require('../utils/crypto');
/** /**
* @typedef {Object} AliasData * @typedef {object} AliasData
* @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
@ -25,22 +25,26 @@ class Alias {
/** /**
* Return Alias types * Return Alias types
* *
* @returns {Object} * @returns {object}
*/ */
static get types() { static get types() {
return { return {
PAGE: 'page' PAGE: 'page',
}; };
}; };
/** /**
* Find and return alias with given alias * Find and return alias with given alias
*
* @param {string} aliasName - alias of entity * @param {string} aliasName - alias of entity
* @returns {Promise<Alias>} * @returns {Promise<Alias>}
*/ */
static async get(aliasName) { static async get(aliasName) {
const hash = binaryMD5(aliasName); const hash = binaryMD5(aliasName);
let data = await aliasesDb.findOne({ hash: hash, deprecated: false }); let data = await aliasesDb.findOne({
hash: hash,
deprecated: false,
});
if (!data) { if (!data) {
data = await aliasesDb.findOne({ hash: hash }); data = await aliasesDb.findOne({ hash: hash });
@ -50,7 +54,7 @@ class Alias {
} }
/** /**
* @constructor * @class
* *
* @param {AliasData} data * @param {AliasData} data
* @param {string} aliasName - alias of entity * @param {string} aliasName - alias of entity
@ -110,12 +114,13 @@ class Alias {
id: this.id, id: this.id,
type: this.type, type: this.type,
hash: this.hash, hash: this.hash,
deprecated: this.deprecated deprecated: this.deprecated,
}; };
} }
/** /**
* Mark alias as deprecated * Mark alias as deprecated
*
* @param {string} aliasName - alias of entity * @param {string} aliasName - alias of entity
* @returns {Promise<Alias>} * @returns {Promise<Alias>}
*/ */

View file

@ -1,7 +1,7 @@
const { files: filesDb } = require('../utils/database/index'); const { files: filesDb } = require('../utils/database/index');
/** /**
* @typedef {Object} FileData * @typedef {object} FileData
* *
* @property {string} _id - file id * @property {string} _id - file id
* @property {string} name - original file name * @property {string} name - original file name
@ -25,6 +25,7 @@ const { files: filesDb } = require('../utils/database/index');
class File { class File {
/** /**
* Find and return model of file with given id * Find and return model of file with given id
*
* @param {string} _id - file id * @param {string} _id - file id
* @returns {Promise<File>} * @returns {Promise<File>}
*/ */
@ -36,6 +37,7 @@ class File {
/** /**
* Find and return model of file with given id * Find and return model of file with given id
*
* @param {string} filename - uploaded filename * @param {string} filename - uploaded filename
* @returns {Promise<File>} * @returns {Promise<File>}
*/ */
@ -48,7 +50,7 @@ class File {
/** /**
* Find all files which match passed query object * Find all files which match passed query object
* *
* @param {Object} query * @param {object} query
* @returns {Promise<File[]>} * @returns {Promise<File[]>}
*/ */
static async getAll(query = {}) { static async getAll(query = {}) {
@ -58,7 +60,7 @@ class File {
} }
/** /**
* @constructor * @class
* *
* @param {FileData} data * @param {FileData} data
*/ */
@ -101,7 +103,7 @@ class File {
filename: this.filename, filename: this.filename,
path: this.path, path: this.path,
mimetype: this.mimetype, mimetype: this.mimetype,
size: this.size size: this.size,
}; };
} }
@ -137,8 +139,9 @@ class File {
/** /**
* Removes unnecessary public folder prefix * Removes unnecessary public folder prefix
*
* @param {string} path * @param {string} path
* @return {string} * @returns {string}
*/ */
processPath(path) { processPath(path) {
return path.replace(/^public/, ''); return path.replace(/^public/, '');

View file

@ -2,7 +2,7 @@ const urlify = require('../utils/urlify');
const { pages: pagesDb } = require('../utils/database/index'); const { pages: pagesDb } = require('../utils/database/index');
/** /**
* @typedef {Object} PageData * @typedef {object} PageData
* @property {string} _id - page id * @property {string} _id - page id
* @property {string} title - page title * @property {string} title - page title
* @property {string} uri - page uri * @property {string} uri - page uri
@ -23,6 +23,7 @@ const { pages: pagesDb } = require('../utils/database/index');
class Page { class Page {
/** /**
* Find and return model of page with given id * Find and return model of page with given id
*
* @param {string} _id - page id * @param {string} _id - page id
* @returns {Promise<Page>} * @returns {Promise<Page>}
*/ */
@ -34,6 +35,7 @@ class Page {
/** /**
* Find and return model of page with given uri * Find and return model of page with given uri
*
* @param {string} uri - page uri * @param {string} uri - page uri
* @returns {Promise<Page>} * @returns {Promise<Page>}
*/ */
@ -46,7 +48,7 @@ class Page {
/** /**
* Find all pages which match passed query object * Find all pages which match passed query object
* *
* @param {Object} query * @param {object} query
* @returns {Promise<Page[]>} * @returns {Promise<Page[]>}
*/ */
static async getAll(query = {}) { static async getAll(query = {}) {
@ -56,7 +58,7 @@ class Page {
} }
/** /**
* @constructor * @class
* *
* @param {PageData} data * @param {PageData} data
*/ */
@ -89,7 +91,7 @@ class Page {
/** /**
* Return PageData object * Return PageData object
* *
* @return {PageData} * @returns {PageData}
*/ */
get data() { get data() {
return { return {
@ -97,13 +99,14 @@ class Page {
title: this.title, title: this.title,
uri: this.uri, uri: this.uri,
body: this.body, body: this.body,
parent: this._parent parent: this._parent,
}; };
} }
/** /**
* Extract first header from editor data * Extract first header from editor data
* @return {string} *
* @returns {string}
*/ */
extractTitleFromBody() { extractTitleFromBody() {
const headerBlock = this.body ? this.body.blocks.find(block => block.type === 'header') : ''; const headerBlock = this.body ? this.body.blocks.find(block => block.type === 'header') : '';
@ -113,7 +116,8 @@ class Page {
/** /**
* Transform title for uri * Transform title for uri
* @return {string} *
* @returns {string}
*/ */
transformTitleToUri() { transformTitleToUri() {
return urlify(this.title); return urlify(this.title);
@ -184,6 +188,7 @@ class Page {
* Find and return available uri * Find and return available uri
* *
* @returns {Promise<string>} * @returns {Promise<string>}
* @param uri
*/ */
async composeUri(uri) { async composeUri(uri) {
let pageWithSameUriCount = 0; let pageWithSameUriCount = 0;

View file

@ -1,7 +1,7 @@
const { pagesOrder: db } = require('../utils/database/index'); const { pagesOrder: db } = require('../utils/database/index');
/** /**
* @typedef {Object} PageOrderData * @typedef {object} PageOrderData
* @property {string} _id - row unique id * @property {string} _id - row unique id
* @property {string} page - page id * @property {string} page - page id
* @property {Array<string>} order - list of ordered pages * @property {Array<string>} order - list of ordered pages
@ -37,7 +37,7 @@ class PageOrder {
/** /**
* Find all pages which match passed query object * Find all pages which match passed query object
* *
* @param {Object} query * @param {object} query
* @returns {Promise<Page[]>} * @returns {Promise<Page[]>}
*/ */
static async getAll(query = {}) { static async getAll(query = {}) {
@ -47,7 +47,7 @@ class PageOrder {
} }
/** /**
* @constructor * @class
* *
* @param {PageOrderData} data * @param {PageOrderData} data
*/ */
@ -65,6 +65,7 @@ class PageOrder {
/** /**
* constructor data setter * constructor data setter
*
* @param {PageOrderData} pageOrderData * @param {PageOrderData} pageOrderData
*/ */
set data(pageOrderData) { set data(pageOrderData) {
@ -74,13 +75,14 @@ class PageOrder {
/** /**
* Return Page Children order * Return Page Children order
*
* @returns {PageOrderData} * @returns {PageOrderData}
*/ */
get data() { get data() {
return { return {
_id: this._id, _id: this._id,
page: '' + this._page, page: '' + this._page,
order: this._order order: this._order,
}; };
} }
@ -176,7 +178,7 @@ class PageOrder {
/** /**
* Returns ordered list * Returns ordered list
* *
* @return {string[]} * @returns {string[]}
*/ */
get order() { get order() {
return this._order; return this._order;

View file

@ -24,9 +24,9 @@ class User {
} }
/** /**
* @constructor * @class
* *
* @param {Object} userData * @param {object} userData
*/ */
constructor(userData) { constructor(userData) {
this.passHash = userData.passHash; this.passHash = userData.passHash;

View file

@ -23,19 +23,20 @@ router.get('*', verifyToken, async (req, res) => {
switch (alias.type) { switch (alias.type) {
case Alias.types.PAGE: { case Alias.types.PAGE: {
let page = await Pages.get(alias.id); const page = await Pages.get(alias.id);
let pageParent = await page.parent; const pageParent = await page.parent;
res.render('pages/page', { res.render('pages/page', {
page, pageParent page,
pageParent,
}); });
} }
} }
} catch (err) { } catch (err) {
res.status(400).json({ res.status(400).json({
success: false, success: false,
error: err.message error: err.message,
}); });
} }
}); });

View file

@ -16,7 +16,7 @@ const parseForm = express.urlencoded({ extended: false });
router.get('/auth', csrfProtection, function (req, res) { router.get('/auth', csrfProtection, function (req, res) {
res.render('auth', { res.render('auth', {
title: 'Login page', title: 'Login page',
csrfToken: req.csrfToken() csrfToken: req.csrfToken(),
}); });
}); });
@ -24,13 +24,13 @@ router.get('/auth', csrfProtection, function (req, res) {
* Process given password * Process given password
*/ */
router.post('/auth', parseForm, csrfProtection, async (req, res) => { router.post('/auth', parseForm, csrfProtection, async (req, res) => {
let userDoc = await Users.get(); const userDoc = await Users.get();
if (!userDoc) { if (!userDoc) {
res.render('auth', { res.render('auth', {
title: 'Login page', title: 'Login page',
header: 'Password not set', header: 'Password not set',
csrfToken: req.csrfToken() csrfToken: req.csrfToken(),
}); });
} }
@ -41,14 +41,14 @@ router.post('/auth', parseForm, csrfProtection, async (req, res) => {
res.render('auth', { res.render('auth', {
title: 'Login page', title: 'Login page',
header: 'Wrong password', header: 'Wrong password',
csrfToken: req.csrfToken() csrfToken: req.csrfToken(),
}); });
} }
const token = jwt.sign({ const token = jwt.sign({
'iss': 'Codex Team', iss: 'Codex Team',
'sub': 'auth', sub: 'auth',
'iat': Date.now() iat: Date.now(),
}, passHash + config.secret); }, passHash + config.secret);
res.cookie('authToken', token, { httpOnly: true }); res.cookie('authToken', token, { httpOnly: true });

View file

@ -10,11 +10,11 @@ const allowEdit = require('./middlewares/locals');
* Create new page form * Create new page form
*/ */
router.get('/page/new', verifyToken, allowEdit, async (req, res, next) => { router.get('/page/new', verifyToken, allowEdit, async (req, res, next) => {
let pagesAvailable = await Pages.getAll(); const pagesAvailable = await Pages.getAll();
res.render('pages/form', { res.render('pages/form', {
pagesAvailable, pagesAvailable,
page: null page: null,
}); });
}); });
@ -32,7 +32,7 @@ router.get('/page/edit/:id', verifyToken, allowEdit, async (req, res, next) => {
res.render('pages/form', { res.render('pages/form', {
page, page,
parentsChildrenOrdered, parentsChildrenOrdered,
pagesAvailable pagesAvailable,
}); });
} catch (error) { } catch (error) {
res.status(404); res.status(404);
@ -47,12 +47,13 @@ router.get('/page/:id', verifyToken, async (req, res, next) => {
const pageId = req.params.id; const pageId = req.params.id;
try { try {
let page = await Pages.get(pageId); const page = await Pages.get(pageId);
let pageParent = await page.parent; const pageParent = await page.parent;
res.render('pages/page', { res.render('pages/page', {
page, pageParent page,
pageParent,
}); });
} catch (error) { } catch (error) {
res.status(404); res.status(404);

View file

@ -1,7 +1,8 @@
/** /**
* Helper for making async middlewares for express router * Helper for making async middlewares for express router
*
* @param fn * @param fn
* @return {function(*=, *=, *=)} * @returns {function(*=, *=, *=)}
*/ */
module.exports = function asyncMiddleware(fn) { module.exports = function asyncMiddleware(fn) {
return (req, res, next) => { return (req, res, next) => {

View file

@ -2,6 +2,7 @@ const crypto = require('crypto');
/** /**
* Create binary md5 * Create binary md5
*
* @param stringToHash - string to hash * @param stringToHash - string to hash
* @returns {string} - binary hash of argument * @returns {string} - binary hash of argument
*/ */
@ -13,7 +14,8 @@ function binaryMD5(stringToHash) {
/** /**
* Returns 16 random bytes in hex format * Returns 16 random bytes in hex format
* @return {Promise<string>} *
* @returns {Promise<string>}
*/ */
function random16() { function random16() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -29,5 +31,5 @@ function random16() {
module.exports = { module.exports = {
binaryMD5, binaryMD5,
random16 random16,
}; };

View file

@ -1,13 +1,16 @@
/** /**
* Merge to objects recursively * Merge to objects recursively
*
* @param {object} target * @param {object} target
* @param {object[]} sources * @param {object[]} sources
* @return {object} * @returns {object}
*/ */
function deepMerge(target, ...sources) { function deepMerge(target, ...sources) {
const isObject = item => item && typeof item === 'object' && !Array.isArray(item); const isObject = item => item && typeof item === 'object' && !Array.isArray(item);
if (!sources.length) return target; if (!sources.length) {
return target;
}
const source = sources.shift(); const source = sources.shift();
if (isObject(target) && isObject(source)) { if (isObject(target) && isObject(source)) {
@ -28,5 +31,5 @@ function deepMerge(target, ...sources) {
} }
module.exports = { module.exports = {
deepMerge deepMerge,
}; };

View file

@ -4,7 +4,7 @@ const config = require('../../config');
const rcPath = path.resolve(__dirname, '../../', config.rcFile || './.codexdocsrc'); const rcPath = path.resolve(__dirname, '../../', config.rcFile || './.codexdocsrc');
/** /**
* @typedef {Object} RCData * @typedef {object} RCData
* @property {string} title - website title * @property {string} title - website title
* @property {object[]} menu - options for website menu * @property {object[]} menu - options for website menu
* @property {string} menu[].title - menu option title * @property {string} menu[].title - menu option title
@ -20,12 +20,12 @@ module.exports = class RCParser {
* Default CodeX Docs configuration * Default CodeX Docs configuration
* *
* @static * @static
* @return {{title: string, menu: Array}} * @returns {{title: string, menu: Array}}
*/ */
static get DEFAULTS() { static get DEFAULTS() {
return { return {
title: 'CodeX Docs', title: 'CodeX Docs',
menu: [] menu: [],
}; };
} }
@ -33,7 +33,7 @@ module.exports = class RCParser {
* Find and parse runtime configuration file * Find and parse runtime configuration file
* *
* @static * @static
* @return {{title: string, menu: []}} * @returns {{title: string, menu: []}}
*/ */
static getConfiguration() { static getConfiguration() {
if (!fs.existsSync(rcPath)) { if (!fs.existsSync(rcPath)) {
@ -48,11 +48,12 @@ module.exports = class RCParser {
userConfig = JSON.parse(file); userConfig = JSON.parse(file);
} catch (e) { } catch (e) {
console.log('CodeX Docs rc file should be in JSON format.'); console.log('CodeX Docs rc file should be in JSON format.');
return RCParser.DEFAULTS; return RCParser.DEFAULTS;
} }
for (let option in userConfig) { for (const option in userConfig) {
if (userConfig.hasOwnProperty(option)) { if (Object.prototype.hasOwnProperty.call(userConfig, option)) {
rConfig[option] = userConfig[option] || RCParser.DEFAULTS[option] || undefined; rConfig[option] = userConfig[option] || RCParser.DEFAULTS[option] || undefined;
} }
} }
@ -70,6 +71,7 @@ module.exports = class RCParser {
if (!option || option instanceof Array || typeof option !== 'object') { if (!option || option instanceof Array || typeof option !== 'object') {
console.log(`Menu option #${i} in rc file must be a string or an object`); console.log(`Menu option #${i} in rc file must be a string or an object`);
return false; return false;
} }
@ -77,11 +79,13 @@ module.exports = class RCParser {
if (!title || typeof title !== 'string') { if (!title || typeof title !== 'string') {
console.log(`Menu option #${i} title must be a string.`); console.log(`Menu option #${i} title must be a string.`);
return false; return false;
} }
if (!uri || typeof uri !== 'string') { if (!uri || typeof uri !== 'string') {
console.log(`Menu option #${i} uri must be a string.`); console.log(`Menu option #${i} uri must be a string.`);
return false; return false;
} }
@ -93,7 +97,7 @@ module.exports = class RCParser {
return { return {
title: option, title: option,
/* Replace all non alpha- and numeric-symbols with '-' */ /* Replace all non alpha- and numeric-symbols with '-' */
uri: '/' + option.toLowerCase().replace(/[ -/:-@[-`{-~]+/, '-') uri: '/' + option.toLowerCase().replace(/[ -/:-@[-`{-~]+/, '-'),
}; };
} }

View file

@ -1,70 +1,70 @@
const translationTable = { const translationTable = {
'а': 'a', а: 'a',
'б': 'b', б: 'b',
'в': 'v', в: 'v',
'г': 'g', г: 'g',
'д': 'd', д: 'd',
'е': 'e', е: 'e',
'ж': 'g', ж: 'g',
'з': 'z', з: 'z',
'и': 'i', и: 'i',
'й': 'y', й: 'y',
'к': 'k', к: 'k',
'л': 'l', л: 'l',
'м': 'm', м: 'm',
'н': 'n', н: 'n',
'о': 'o', о: 'o',
'п': 'p', п: 'p',
'р': 'r', р: 'r',
'с': 's', с: 's',
'т': 't', т: 't',
'у': 'u', у: 'u',
'ф': 'f', ф: 'f',
'ы': 'i', ы: 'i',
'э': 'e', э: 'e',
'А': 'A', А: 'A',
'Б': 'B', Б: 'B',
'В': 'V', В: 'V',
'Г': 'G', Г: 'G',
'Д': 'D', Д: 'D',
'Е': 'E', Е: 'E',
'Ж': 'G', Ж: 'G',
'З': 'Z', З: 'Z',
'И': 'I', И: 'I',
'Й': 'Y', Й: 'Y',
'К': 'K', К: 'K',
'Л': 'L', Л: 'L',
'М': 'M', М: 'M',
'Н': 'N', Н: 'N',
'О': 'O', О: 'O',
'П': 'P', П: 'P',
'Р': 'R', Р: 'R',
'С': 'S', С: 'S',
'Т': 'T', Т: 'T',
'У': 'U', У: 'U',
'Ф': 'F', Ф: 'F',
'Ы': 'I', Ы: 'I',
'Э': 'E', Э: 'E',
'ё': 'yo', ё: 'yo',
'х': 'h', х: 'h',
'ц': 'ts', ц: 'ts',
'ч': 'ch', ч: 'ch',
'ш': 'sh', ш: 'sh',
'щ': 'shch', щ: 'shch',
'ъ': "''", ъ: "''",
'ь': "'", ь: "'",
'ю': 'yu', ю: 'yu',
'я': 'ya', я: 'ya',
'Ё': 'YO', Ё: 'YO',
'Х': 'H', Х: 'H',
'Ц': 'TS', Ц: 'TS',
'Ч': 'CH', Ч: 'CH',
'Ш': 'SH', Ш: 'SH',
'Щ': 'SHCH', Щ: 'SHCH',
'Ъ': "''", Ъ: "''",
'Ь': "'", Ь: "'",
'Ю': 'YU', Ю: 'YU',
'Я': 'YA' Я: 'YA',
}; };
/** /**
* Function to translate string * Function to translate string

View file

@ -13,7 +13,7 @@ module.exports = (function () {
* *
* @example svg('path/from/root/dir') * @example svg('path/from/root/dir')
* @param filename - name of icon * @param filename - name of icon
* @returns {String} - svg code * @returns {string} - svg code
*/ */
twig.extendFunction('svg', function (filename) { twig.extendFunction('svg', function (filename) {
return fs.readFileSync(`${__dirname}/../frontend/svg/${filename}.svg`, 'utf-8'); return fs.readFileSync(`${__dirname}/../frontend/svg/${filename}.svg`, 'utf-8');

View file

@ -0,0 +1,12 @@
<div class="block-checklist">
{% for item in items %}
<div class="block-checklist__item">
{% if item.checked %}
<span class="block-checklist__item-checkbox block-checklist__item-checkbox--checked"></span>
{% else %}
<span class="block-checklist__item-checkbox"></span>
{% endif %}
<div class="block-checklist__item-text">{{ item.text }}</div>
</div>
{% endfor %}
</div>

View file

@ -32,7 +32,7 @@
{% for block in page.body.blocks %} {% for block in page.body.blocks %}
{# Skip first header, because it is already showed as a Title #} {# Skip first header, because it is already showed as a Title #}
{% if not (loop.first and block.type == 'header') %} {% if not (loop.first and block.type == 'header') %}
{% if block.type in ['paragraph', 'header', 'image', 'code', 'list', 'delimiter', 'table', 'warning'] %} {% if block.type in ['paragraph', 'header', 'image', 'code', 'list', 'delimiter', 'table', 'warning', 'checklist'] %}
{% include './blocks/' ~ block.type ~ '.twig' with block.data %} {% include './blocks/' ~ block.type ~ '.twig' with block.data %}
{% endif %} {% endif %}
{% endif %} {% endif %}

4300
yarn.lock

File diff suppressed because it is too large Load diff