mirror of
https://github.com/codex-team/codex.docs.git
synced 2025-08-09 23:45:25 +02:00
Merge branch 'master' of https://github.com/codex-team/codex.editor.docs into rc-file
This commit is contained in:
commit
927b18387c
28 changed files with 15926 additions and 973 deletions
12071
package-lock.json
generated
Normal file
12071
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
11
package.json
11
package.json
|
@ -7,15 +7,19 @@
|
|||
"start:dev": "cross-env NODE_ENV=development nodemon ./bin/www",
|
||||
"test": "cross-env NODE_ENV=testing mocha --recursive ./test",
|
||||
"lint": "eslint --fix --cache ./src/**/*.js",
|
||||
"build": "webpack ./src/frontend/js/app.js --o='./public/dist/bundle.js' --output-library=Docs -d --watch",
|
||||
"build": "webpack ./src/frontend/js/app.js --o='./public/dist/[name].bundle.js' --output-library=Docs --output-public-path=/dist/ -p --watch",
|
||||
"precommit": "yarn lint && yarn test --exit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "^7.0.0",
|
||||
"body-parser": "latest",
|
||||
"codex.editor": "^2.0.11",
|
||||
"codex.editor.header": "^2.0.5",
|
||||
"cookie-parser": "~1.4.3",
|
||||
"debug": "~2.6.9",
|
||||
"express": "~4.16.0",
|
||||
"http-errors": "~1.6.2",
|
||||
"module-dispatcher": "^1.0.2",
|
||||
"morgan": "~1.9.0",
|
||||
"multer": "^1.3.1",
|
||||
"nedb": "^1.8.0",
|
||||
|
@ -26,10 +30,11 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/preset-env": "^7.1.0",
|
||||
"autoprefixer": "^9.1.3",
|
||||
"babel": "^6.23.0",
|
||||
"babel-loader": "^8.0.2",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"chai": "^4.1.2",
|
||||
"chai-http": "^4.0.0",
|
||||
"cross-env": "^5.2.0",
|
||||
|
@ -38,8 +43,8 @@
|
|||
"eslint": "^5.3.0",
|
||||
"eslint-config-codex": "github:codex-team/eslint-config",
|
||||
"eslint-plugin-chai-friendly": "^0.4.1",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"husky": "^0.14.3",
|
||||
"mini-css-extract-plugin": "^0.4.3",
|
||||
"mocha": "^5.2.0",
|
||||
"mocha-sinon": "^2.1.0",
|
||||
"nyc": "^12.0.2",
|
||||
|
|
1
public/dist/bundle.css
vendored
1
public/dist/bundle.css
vendored
|
@ -1 +0,0 @@
|
|||
/*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */html{-webkit-text-size-adjust:100%;line-height:1.15}body{margin:0}h1{font-size:2em;margin:.67em 0}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}:root{--color-line-gray:#e8e8eb;--color-link-active:#388ae5;--color-text-main:#1d202b;--color-text-second:#7b7e89;--layout-padding-horisontal:40px;--layout-width-aside:250px;--layout-width-main-col:650px}.docs{display:-webkit-box;display:-ms-flexbox;display:flex;padding:0 40px;padding:0 var(--layout-padding-horisontal)}.docs__aside{width:250px;width:var(--layout-width-aside)}.docs__content{-webkit-box-flex:2;-ms-flex-positive:2;flex-grow:2}.docs__content-inner{margin:0 auto;max-width:650px;max-width:var(--layout-width-main-col)}.docs__aside,.docs__content{padding:40px 0}.docs-header{border-bottom:1px solid #e8e8eb;border-bottom:1px solid var(--color-line-gray);display:-webkit-box;display:-ms-flexbox;display:flex;font-size:15.8px;line-height:50px;padding:0 40px;padding:0 var(--layout-padding-horisontal)}.docs-header a{color:inherit;display:inline-block;text-decoration:none}.docs-header a:hover{color:#388ae5;color:var(--color-link-active)}.docs-header__logo{font-weight:700}.docs-header__menu{display:-webkit-box;display:-ms-flexbox;display:flex;margin:0 0 0 auto}.docs-header__menu li{list-style:none;margin-left:20px}.docs-aside{color:#7b7e89;color:var(--color-text-second);font-size:15px}.docs-aside a{text-decoration:none}.docs-aside__section{margin-bottom:30px}.docs-aside__section-title{color:#388ae5;color:var(--color-link-active);margin-bottom:15px}.docs-aside__section-list{list-style:none;padding-left:0}.docs-aside__section-list a{color:inherit;display:inline-block;padding:8px 0}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;color:#1d202b;color:var(--color-text-main);font-family:system-ui,-apple-system,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue,Helvetica,Arial,Verdana}
|
9
public/dist/bundle.js
vendored
9
public/dist/bundle.js
vendored
|
@ -1,9 +0,0 @@
|
|||
var Docs=function(n){var e={};function c(t){if(e[t])return e[t].exports;var r=e[t]={i:t,l:!1,exports:{}};return n[t].call(r.exports,r,r.exports,c),r.l=!0,r.exports}return c.m=n,c.c=e,c.d=function(n,e,t){c.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:t})},c.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},c.t=function(n,e){if(1&e&&(n=c(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var t=Object.create(null);if(c.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var r in n)c.d(t,r,function(e){return n[e]}.bind(null,r));return t},c.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return c.d(e,"a",e),e},c.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},c.p="",c(c.s="./src/frontend/js/app.js")}({"./src/frontend/js/app.js":
|
||||
/*!********************************!*\
|
||||
!*** ./src/frontend/js/app.js ***!
|
||||
\********************************/
|
||||
/*! no static exports found */function(module,exports,__webpack_require__){"use strict";eval('\n\nvar _main = __webpack_require__(/*! ../styles/main.pcss */ "./src/frontend/styles/main.pcss");\n\nvar _main2 = _interopRequireDefault(_main);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }\n\nmodule.exports =\n/**\n * @constructor\n */\nfunction Docs() {\n _classCallCheck(this, Docs);\n\n console.log(\'CodeX Docs initialized\');\n};//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9zcmMvZnJvbnRlbmQvanMvYXBwLmpzLmpzIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vRG9jcy8uL3NyYy9mcm9udGVuZC9qcy9hcHAuanM/YmM1NSJdLCJzb3VyY2VzQ29udGVudCI6WyIvLyBObyBpbnNwZWN0aW9uIGZvciB1bnVzZWQgdmFyIGBjc3NgIGJlY2F1c2UgaXQncyB1c2VkIGZvciBjc3MgYnVuZGxlXG4vLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tdW51c2VkLXZhcnNcbmltcG9ydCBjc3MgZnJvbSAnLi4vc3R5bGVzL21haW4ucGNzcyc7XG5cbm1vZHVsZS5leHBvcnRzID0gY2xhc3MgRG9jcyB7XG4gIC8qKlxuICAgKiBAY29uc3RydWN0b3JcbiAgICovXG4gIGNvbnN0cnVjdG9yKCkge1xuICAgIGNvbnNvbGUubG9nKCdDb2RlWCBEb2NzIGluaXRpYWxpemVkJyk7XG4gIH1cbn07XG4iXSwibWFwcGluZ3MiOiI7O0FBRUE7QUFDQTs7Ozs7OztBQUNBO0FBQ0E7OztBQUdBO0FBQUE7QUFDQTtBQUFBO0FBQ0EiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///./src/frontend/js/app.js\n')},"./src/frontend/styles/main.pcss":
|
||||
/*!***************************************!*\
|
||||
!*** ./src/frontend/styles/main.pcss ***!
|
||||
\***************************************/
|
||||
/*! no static exports found */function(module,exports){eval("// removed by extract-text-webpack-plugin//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9zcmMvZnJvbnRlbmQvc3R5bGVzL21haW4ucGNzcy5qcyIsInNvdXJjZXMiOlsid2VicGFjazovL0RvY3MvLi9zcmMvZnJvbnRlbmQvc3R5bGVzL21haW4ucGNzcz83N2VjIl0sInNvdXJjZXNDb250ZW50IjpbIi8vIHJlbW92ZWQgYnkgZXh0cmFjdC10ZXh0LXdlYnBhY2stcGx1Z2luIl0sIm1hcHBpbmdzIjoiQUFBQSIsInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///./src/frontend/styles/main.pcss\n")}});
|
2144
public/dist/editor.bundle.js
vendored
Normal file
2144
public/dist/editor.bundle.js
vendored
Normal file
File diff suppressed because one or more lines are too long
10
public/dist/main.bundle.js
vendored
Normal file
10
public/dist/main.bundle.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/dist/main.css
vendored
Normal file
1
public/dist/main.css
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */html{-webkit-text-size-adjust:100%;line-height:1.15}body{margin:0}h1{font-size:2em;margin:.67em 0}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}:root{--color-line-gray:#e8e8eb;--color-link-active:#388ae5;--color-text-main:#1d202b;--color-text-second:#7b7e89;--layout-padding-horisontal:40px;--layout-padding-vertical:40px;--layout-width-aside:250px;--layout-width-main-col:650px}.docs{display:-webkit-box;display:-ms-flexbox;display:flex;padding:0 40px;padding:0 var(--layout-padding-horisontal)}.docs__aside{width:250px;width:var(--layout-width-aside)}.docs__content{-webkit-box-flex:2;-ms-flex-positive:2;flex-grow:2;word-wrap:break-word}.docs__content-inner{margin:0 auto;max-width:650px;max-width:var(--layout-width-main-col)}.docs__aside,.docs__content{padding:40px 0;padding:var(--layout-padding-vertical) 0}.docs-header{border-bottom:1px solid #e8e8eb;border-bottom:1px solid var(--color-line-gray);display:-webkit-box;display:-ms-flexbox;display:flex;font-size:15.8px;line-height:50px;padding:0 40px;padding:0 var(--layout-padding-horisontal)}.docs-header a{display:inline-block;text-decoration:none}.docs-header__logo{color:inherit;font-weight:700}.docs-header__menu{display:-webkit-box;display:-ms-flexbox;display:flex;margin:0 0 0 auto}.docs-header__menu li{list-style:none;margin-left:20px}.docs-header__menu a:not(.docs-header__button){color:inherit}.docs-header__menu a:not(.docs-header__button):hover{color:#388ae5;color:var(--color-link-active)}.docs-header__button{background:#388ae5;background:var(--color-link-active);border-radius:3px;color:#fff;display:inline-block;font-size:14px;line-height:1em;padding:9px 15px}.docs-header__button svg{margin:0 .3em 0 -.05em}.docs-header__button:hover{background:#387ecc}.docs-header__button{margin:auto 30px auto auto}.docs-aside{color:#7b7e89;color:var(--color-text-second);font-size:15px}.docs-aside a{text-decoration:none}.docs-aside__section{margin-bottom:30px}.docs-aside__section-title{color:#388ae5;color:var(--color-link-active);margin-bottom:15px}.docs-aside__section-list{list-style:none;padding-left:0}.docs-aside__section-list a{color:inherit;display:inline-block;padding:8px 0}.writing-header{background:#fff;-webkit-box-shadow:0 3px 10px #fff;box-shadow:0 3px 10px #fff;display:-webkit-box;display:-ms-flexbox;display:flex;margin-top:-40px;margin-top:calc(-1 * var(--layout-padding-vertical));padding:15px 0;position:-webkit-sticky;position:sticky;top:0;z-index:2}.writing-header__save{background:#388ae5;background:var(--color-link-active);border-radius:3px;color:#fff;display:inline-block;font-size:14px;line-height:1em;padding:9px 15px}.writing-header__save svg{margin:0 .3em 0 -.05em}.writing-header__save:hover{background:#387ecc}.writing-header__save{margin-left:auto}.writing-header__left{color:#7b7e89;color:var(--color-text-second);margin:auto 0}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;color:#1d202b;color:var(--color-text-main);font-family:system-ui,-apple-system,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue,Helvetica,Arial,Verdana}svg{fill:currentColor}
|
|
@ -15,6 +15,7 @@ app.locals.config = config;
|
|||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'twig');
|
||||
require('./utils/twig');
|
||||
|
||||
app.use(logger('dev'));
|
||||
app.use(express.json());
|
||||
|
|
|
@ -12,7 +12,7 @@ class Pages {
|
|||
* @returns {['title', 'body']}
|
||||
*/
|
||||
static get REQUIRED_FIELDS() {
|
||||
return ['title', 'body'];
|
||||
return [ 'body' ];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -48,23 +48,47 @@ class Pages {
|
|||
* @returns {Promise<Page>}
|
||||
*/
|
||||
static async insert(data) {
|
||||
if (!Pages.validate(data)) {
|
||||
throw new Error('Invalid request format');
|
||||
try {
|
||||
Pages.validate(data);
|
||||
|
||||
const page = new Model(data);
|
||||
|
||||
return page.save();
|
||||
} catch (validationError) {
|
||||
throw new Error(validationError);
|
||||
}
|
||||
|
||||
const page = new Model(data);
|
||||
|
||||
return page.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check PageData object for required fields
|
||||
*
|
||||
* @param {PageData} data
|
||||
* @returns {boolean}
|
||||
* @throws {Error} - validation error
|
||||
*/
|
||||
static validate(data) {
|
||||
return Pages.REQUIRED_FIELDS.every(field => typeof data[field] !== 'undefined');
|
||||
const allRequiredFields = Pages.REQUIRED_FIELDS.every(field => typeof data[field] !== 'undefined');
|
||||
|
||||
if (!allRequiredFields) {
|
||||
throw new Error('Some of required fields is missed');
|
||||
}
|
||||
|
||||
const hasBlocks = data.body && data.body.blocks && Array.isArray(data.body.blocks) && data.body.blocks.length > 0;
|
||||
|
||||
if (!hasBlocks) {
|
||||
throw new Error('Page body is invalid');
|
||||
}
|
||||
|
||||
const hasHeaderAsFirstBlock = data.body.blocks[0].type === 'header';
|
||||
|
||||
if (!hasHeaderAsFirstBlock) {
|
||||
throw new Error('First page Block must be a Header');
|
||||
}
|
||||
|
||||
const headerIsNotEmpty = data.body.blocks[0].data.text.replace('<br>', '').trim() !== '';
|
||||
|
||||
if (!headerIsNotEmpty) {
|
||||
throw new Error('Please, fill page Header');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,11 +2,43 @@
|
|||
// eslint-disable-next-line no-unused-vars
|
||||
import css from '../styles/main.pcss';
|
||||
|
||||
module.exports = class Docs {
|
||||
/**
|
||||
* Module Dispatcher
|
||||
* @see {@link https://github.com/codex-team/moduleDispatcher}
|
||||
* @author CodeX
|
||||
*/
|
||||
import ModuleDispatcher from 'module-dispatcher';
|
||||
|
||||
/**
|
||||
* Import modules
|
||||
*/
|
||||
import Writing from './modules/writing';
|
||||
|
||||
/**
|
||||
* Main app class
|
||||
*/
|
||||
class Docs {
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
constructor() {
|
||||
console.log('CodeX Docs initialized');
|
||||
|
||||
this.writing = new Writing();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
this.docReady();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Document is ready
|
||||
*/
|
||||
docReady() {
|
||||
this.moduleDispatcher = new ModuleDispatcher({
|
||||
Library: window.Docs
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new Docs();
|
||||
|
|
42
src/frontend/js/classes/editor.js
Normal file
42
src/frontend/js/classes/editor.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
import CodeXEditor from 'codex.editor';
|
||||
import Header from 'codex.editor.header';
|
||||
|
||||
/**
|
||||
* Class for working with Editor.js
|
||||
*/
|
||||
export default class Editor {
|
||||
/**
|
||||
* Creates Editor instance
|
||||
*/
|
||||
constructor() {
|
||||
this.editor = new CodeXEditor({
|
||||
tools: {
|
||||
header: {
|
||||
class: Header,
|
||||
config: {
|
||||
placeholder: 'Enter a title'
|
||||
}
|
||||
}
|
||||
},
|
||||
data: {
|
||||
blocks: [
|
||||
{
|
||||
type: 'header',
|
||||
data: {
|
||||
text: '',
|
||||
level: 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Editor data
|
||||
* @return {Promise.<{}>}
|
||||
*/
|
||||
save() {
|
||||
return this.editor.saver.save();
|
||||
}
|
||||
}
|
115
src/frontend/js/modules/writing.js
Normal file
115
src/frontend/js/modules/writing.js
Normal file
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* Module for pages create/edit
|
||||
*/
|
||||
/**
|
||||
* @typedef {object} editorData
|
||||
* @property {{type, data}[]} blocks - array of Blocks
|
||||
* @property {string} version - used Editor version
|
||||
* @property {number} time - saving time
|
||||
*/
|
||||
|
||||
export default class Writing {
|
||||
/**
|
||||
* Creates base properties
|
||||
*/
|
||||
constructor(){
|
||||
this.editor = null;
|
||||
this.nodes = {
|
||||
editorWrapper: null,
|
||||
saveButton: null,
|
||||
parentIdSelector: null,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by ModuleDispatcher to initialize module from DOM
|
||||
* @param {object} settings - module settings
|
||||
* @param {HTMLElement} moduleEl - module element
|
||||
*/
|
||||
init(settings, moduleEl) {
|
||||
/**
|
||||
* Create Editor
|
||||
*/
|
||||
this.nodes.editorWrapper = document.createElement('div');
|
||||
this.nodes.editorWrapper.id = 'codex-editor';
|
||||
|
||||
moduleEl.appendChild(this.nodes.editorWrapper);
|
||||
|
||||
this.loadEditor().then((editor) => {
|
||||
this.editor = editor;
|
||||
});
|
||||
|
||||
/**
|
||||
* Activate form elements
|
||||
*/
|
||||
this.nodes.saveButton = moduleEl.querySelector('[name="js-submit"]');
|
||||
this.nodes.saveButton.addEventListener('click', () => {
|
||||
this.saveButtonClicked();
|
||||
});
|
||||
this.nodes.parentIdSelector = moduleEl.querySelector('[name="parent"]');
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads class for working with Editor
|
||||
* @return {Promise<Editor>}
|
||||
*/
|
||||
async loadEditor(){
|
||||
const {default: Editor} = await import(/* webpackChunkName: "editor" */ './../classes/editor');
|
||||
|
||||
return new Editor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all writing form data
|
||||
* @throws {Error} - validation error
|
||||
* @return {Promise.<{parent: string, body: {editorData}}>}
|
||||
*/
|
||||
async getData(){
|
||||
const editorData = await this.editor.save();
|
||||
const firstBlock = editorData.blocks.length ? editorData.blocks[0] : null;
|
||||
const title = firstBlock && firstBlock.type === 'header' ? firstBlock.data.text : null;
|
||||
|
||||
if (!title) {
|
||||
throw new Error('Entry should start with Header');
|
||||
}
|
||||
|
||||
return {
|
||||
parent: this.nodes.parentIdSelector.value,
|
||||
body: editorData
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for clicks on the Save button
|
||||
*/
|
||||
async saveButtonClicked(){
|
||||
try {
|
||||
const writingData = await this.getData();
|
||||
|
||||
try {
|
||||
let response = await fetch('/page', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
body: JSON.stringify(writingData),
|
||||
});
|
||||
|
||||
response = await response.json();
|
||||
|
||||
if (response.success){
|
||||
document.location = '/page/' + response.result._id;
|
||||
} else {
|
||||
alert(response.error);
|
||||
console.log('Validation failed:', response.error);
|
||||
}
|
||||
|
||||
} catch (sendingError) {
|
||||
console.log('Saving request failed:', sendingError);
|
||||
}
|
||||
} catch (savingError){
|
||||
alert(savingError);
|
||||
console.log('Saving error: ', savingError);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,16 +7,12 @@
|
|||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: inline-block;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-link-active);
|
||||
}
|
||||
}
|
||||
|
||||
&__logo {
|
||||
font-weight: bold;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
&__menu {
|
||||
|
@ -27,5 +23,18 @@
|
|||
list-style: none;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
a:not(.docs-header__button) {
|
||||
color: inherit;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-link-active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__button {
|
||||
@apply --button;
|
||||
margin: auto 30px auto auto;
|
||||
}
|
||||
}
|
||||
|
|
20
src/frontend/styles/components/writing.pcss
Normal file
20
src/frontend/styles/components/writing.pcss
Normal file
|
@ -0,0 +1,20 @@
|
|||
.writing-header {
|
||||
display: flex;
|
||||
padding: 15px 0;
|
||||
margin-top: calc(-1 * var(--layout-padding-vertical));
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: #fff;
|
||||
z-index: 2;
|
||||
box-shadow: 0 3px 10px #fff;
|
||||
|
||||
&__save {
|
||||
@apply --button;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
&__left {
|
||||
margin: auto 0;
|
||||
color: var(--color-text-second);
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
&__content {
|
||||
flex-grow: 2;
|
||||
word-wrap: break-word;
|
||||
|
||||
&-inner {
|
||||
max-width: var(--layout-width-main-col);
|
||||
|
@ -17,6 +18,6 @@
|
|||
|
||||
&__aside,
|
||||
&__content {
|
||||
padding: 40px 0;
|
||||
padding: var(--layout-padding-vertical) 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
@import url('layout.pcss');
|
||||
@import url('components/header.pcss');
|
||||
@import url('components/aside.pcss');
|
||||
@import url('components/writing.pcss');
|
||||
|
||||
body {
|
||||
font-family: system-ui, Helvetica, Arial, Verdana;
|
||||
|
@ -10,3 +11,7 @@ body {
|
|||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,25 @@
|
|||
* Site layout sizes
|
||||
*/
|
||||
--layout-padding-horisontal: 40px;
|
||||
--layout-padding-vertical: 40px;
|
||||
--layout-width-aside: 250px;
|
||||
--layout-width-main-col: 650px;
|
||||
|
||||
--button {
|
||||
display: inline-block;
|
||||
background: var(--color-link-active);
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
padding: 9px 15px;
|
||||
font-size: 14px;
|
||||
line-height: 1em;
|
||||
|
||||
svg {
|
||||
margin: 0 0.3em 0 -0.05em;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: color-mod(var(--color-link-active) blackness(+10%));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
3
src/frontend/svg/plus.svg
Normal file
3
src/frontend/svg/plus.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="11" height="11" viewBox="0 0 11 11" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.44 4.64h3.7a.9.9 0 1 1 0 1.8h-3.7v3.7a.9.9 0 1 1-1.8 0v-3.7H.9a.9.9 0 0 1 0-1.8h3.74V.9a.9.9 0 0 1 1.8 0v3.74z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 219 B |
|
@ -67,10 +67,10 @@ class Page {
|
|||
* @param {PageData} pageData
|
||||
*/
|
||||
set data(pageData) {
|
||||
const {title, body, parent} = pageData;
|
||||
const {body, parent} = pageData;
|
||||
|
||||
this.title = title || this.title;
|
||||
this.body = body || this.body;
|
||||
this.title = this.extractTitleFromBody();
|
||||
this._parent = parent || this._parent;
|
||||
}
|
||||
|
||||
|
@ -88,6 +88,16 @@ class Page {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract first header from editor data
|
||||
* @return {string}
|
||||
*/
|
||||
extractTitleFromBody() {
|
||||
const headerBlock = this.body ? this.body.blocks.find(block => block.type === 'header') : '';
|
||||
|
||||
return headerBlock ? headerBlock.data.text : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Link given page as parent
|
||||
*
|
||||
|
|
|
@ -3,6 +3,17 @@ const router = express.Router();
|
|||
const multer = require('multer')();
|
||||
const Pages = require('../controllers/pages');
|
||||
|
||||
/**
|
||||
* Create new page form
|
||||
*/
|
||||
router.get('/page/new', async (req, res) => {
|
||||
let pagesAvailable = await Pages.getAll();
|
||||
|
||||
res.render('pages/form', {
|
||||
pagesAvailable
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /page/:id
|
||||
*
|
||||
|
|
20
src/utils/twig.js
Normal file
20
src/utils/twig.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Twig extensions
|
||||
*/
|
||||
const twig = require('twig');
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = (function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Function for include svg on page
|
||||
*
|
||||
* @example svg('path/from/root/dir')
|
||||
* @param filename - name of icon
|
||||
* @returns {String} - svg code
|
||||
*/
|
||||
twig.extendFunction('svg', function (filename) {
|
||||
return fs.readFileSync(`${__dirname}/../frontend/svg/${filename}.svg`, 'utf-8');
|
||||
});
|
||||
}());
|
|
@ -3,6 +3,12 @@
|
|||
{{ config.title }}
|
||||
</a>
|
||||
<ul class="docs-header__menu">
|
||||
<li>
|
||||
<a class="docs-header__button" href="/page/new">
|
||||
{{ svg('plus') }}
|
||||
Add Page
|
||||
</a>
|
||||
</li>
|
||||
{% for option in config.menu %}
|
||||
<li>
|
||||
<a href="{{option.uri}}">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>{{ title }}</title>
|
||||
<link rel="stylesheet" href="/dist/bundle.css" />
|
||||
<link rel="stylesheet" href="/dist/main.css" />
|
||||
</head>
|
||||
<body>
|
||||
{% include "components/header.twig" %}
|
||||
|
@ -16,9 +16,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/dist/bundle.js"></script>
|
||||
<script>
|
||||
new Docs();
|
||||
</script>
|
||||
<script src="/dist/main.bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
25
src/views/pages/form.twig
Normal file
25
src/views/pages/form.twig
Normal file
|
@ -0,0 +1,25 @@
|
|||
{% extends 'layout.twig' %}
|
||||
|
||||
{% block body %}
|
||||
<style>
|
||||
.docs-header__button {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
<section data-module="writing">
|
||||
<header class="writing-header">
|
||||
<span class="writing-header__left">
|
||||
New Page at the
|
||||
<select name="parent">
|
||||
<option value="0">Root</option>
|
||||
{% for page in pagesAvailable %}
|
||||
<option value="{{ page._id }}">{{ page.title }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</span>
|
||||
<span class="writing-header__save" name="js-submit">
|
||||
Save
|
||||
</span>
|
||||
</header>
|
||||
</section>
|
||||
{% endblock %}
|
|
@ -22,7 +22,7 @@ describe('Page model', () => {
|
|||
let {data} = page;
|
||||
|
||||
expect(data._id).to.be.undefined;
|
||||
expect(data.title).to.be.undefined;
|
||||
expect(data.title).to.be.empty;
|
||||
expect(data.body).to.be.undefined;
|
||||
expect(data.parent).to.be.undefined;
|
||||
|
||||
|
@ -31,11 +31,23 @@ describe('Page model', () => {
|
|||
data = page.data;
|
||||
|
||||
expect(data._id).to.be.undefined;
|
||||
expect(data.title).to.be.undefined;
|
||||
expect(data.title).to.be.empty;
|
||||
expect(data.body).to.be.undefined;
|
||||
expect(data.parent).to.be.undefined;
|
||||
|
||||
const initialData = {_id: 'page_id', title: 'Test page', body: 'Test page body'};
|
||||
const initialData = {
|
||||
_id: 'page_id',
|
||||
body: {
|
||||
blocks: [
|
||||
{
|
||||
type: 'header',
|
||||
data: {
|
||||
text: 'Page header'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
page = new Page(initialData);
|
||||
|
||||
|
@ -44,19 +56,27 @@ describe('Page model', () => {
|
|||
data = page.data;
|
||||
|
||||
expect(data._id).to.equal(initialData._id);
|
||||
expect(data.title).to.equal(initialData.title);
|
||||
expect(data.body).to.equal(initialData.body);
|
||||
expect(data.title).to.equal(initialData.body.blocks[0].data.text);
|
||||
expect(data.body).to.deep.equal(initialData.body);
|
||||
expect(data.parent).to.be.undefined;
|
||||
|
||||
expect(json._id).to.equal(initialData._id);
|
||||
expect(json.title).to.equal(initialData.title);
|
||||
expect(json.body).to.equal(initialData.body);
|
||||
expect(json.title).to.equal(initialData.body.blocks[0].data.text);
|
||||
expect(json.body).to.deep.equal(initialData.body);
|
||||
expect(json.parent).to.be.undefined;
|
||||
|
||||
const update = {
|
||||
_id: 12345,
|
||||
title: 'Test page',
|
||||
body: 'Test page body'
|
||||
body: {
|
||||
blocks: [
|
||||
{
|
||||
type: 'header',
|
||||
data: {
|
||||
text: 'Updated page header'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
page.data = update;
|
||||
|
@ -64,19 +84,30 @@ describe('Page model', () => {
|
|||
data = page.data;
|
||||
|
||||
expect(data._id).to.equal(initialData._id);
|
||||
expect(data.title).to.equal(update.title);
|
||||
expect(data.title).to.equal(update.body.blocks[0].data.text);
|
||||
expect(data.body).to.equal(update.body);
|
||||
expect(data.parent).to.be.undefined;
|
||||
});
|
||||
|
||||
it('Saving, updating and deleting model in the database', async () => {
|
||||
const initialData = {title: 'Test page', body: 'Test page body'};
|
||||
const initialData = {
|
||||
body: {
|
||||
blocks: [
|
||||
{
|
||||
type: 'header',
|
||||
data: {
|
||||
text: 'Page header'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
const page = new Page(initialData);
|
||||
|
||||
let savedPage = await page.save();
|
||||
|
||||
expect(savedPage._id).not.be.undefined;
|
||||
expect(savedPage.title).to.equal(initialData.title);
|
||||
expect(savedPage.title).to.equal(initialData.body.blocks[0].data.text);
|
||||
expect(savedPage.body).to.equal(initialData.body);
|
||||
expect(page._id).not.be.undefined;
|
||||
|
||||
|
@ -84,9 +115,20 @@ describe('Page model', () => {
|
|||
|
||||
expect(insertedPage._id).to.equal(page._id);
|
||||
expect(insertedPage.title).to.equal(page.title);
|
||||
expect(insertedPage.body).to.equal(page.body);
|
||||
expect(insertedPage.body).to.deep.equal(page.body);
|
||||
|
||||
const updateData = {title: 'Updated test page', body: 'Updated test page body'};
|
||||
const updateData = {
|
||||
body: {
|
||||
blocks: [
|
||||
{
|
||||
type: 'header',
|
||||
data: {
|
||||
text: 'Updated page header'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
page.data = updateData;
|
||||
await page.save();
|
||||
|
@ -96,8 +138,8 @@ describe('Page model', () => {
|
|||
const updatedPage = await pages.findOne({_id: page._id});
|
||||
|
||||
expect(updatedPage._id).to.equal(savedPage._id);
|
||||
expect(updatedPage.title).to.equal(updateData.title);
|
||||
expect(updatedPage.body).to.equal(updateData.body);
|
||||
expect(updatedPage.title).to.equal(updateData.body.blocks[0].data.text);
|
||||
expect(updatedPage.body).to.deep.equal(updateData.body);
|
||||
|
||||
await page.destroy();
|
||||
|
||||
|
@ -109,7 +151,18 @@ describe('Page model', () => {
|
|||
});
|
||||
|
||||
it('Static get method', async () => {
|
||||
const initialData = {title: 'Test page', body: 'Test page body'};
|
||||
const initialData = {
|
||||
body: {
|
||||
blocks: [
|
||||
{
|
||||
type: 'header',
|
||||
data: {
|
||||
text: 'Test Page header'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
const page = new Page(initialData);
|
||||
|
||||
const savedPage = await page.save();
|
||||
|
@ -119,16 +172,38 @@ describe('Page model', () => {
|
|||
const {data} = foundPage;
|
||||
|
||||
expect(data._id).to.equal(savedPage._id);
|
||||
expect(data.title).to.equal(initialData.title);
|
||||
expect(data.body).to.equal(initialData.body);
|
||||
expect(data.title).to.equal(initialData.body.blocks[0].data.text);
|
||||
expect(data.body).to.deep.equal(initialData.body);
|
||||
|
||||
await page.destroy();
|
||||
});
|
||||
|
||||
it('Static getAll method', async () => {
|
||||
const pagesToSave = [
|
||||
new Page({title: 'Page 1', body: 'Page 1 body'}),
|
||||
new Page({title: 'Page 2', body: 'Page 2 body'})
|
||||
new Page({
|
||||
body: {
|
||||
blocks: [
|
||||
{
|
||||
type: 'header',
|
||||
data: {
|
||||
text: 'Page 1 header'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}),
|
||||
new Page({
|
||||
body: {
|
||||
blocks: [
|
||||
{
|
||||
type: 'header',
|
||||
data: {
|
||||
text: 'Page 2 header'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
const savedPages = await Promise.all(pagesToSave.map(page => page.save()));
|
||||
|
@ -137,16 +212,42 @@ describe('Page model', () => {
|
|||
|
||||
expect(foundPages.length).to.equal(2);
|
||||
foundPages.forEach((page, i) => {
|
||||
expect(page.title).to.equal(pagesToSave[i].title);
|
||||
expect(page.body).to.equal(pagesToSave[i].body);
|
||||
expect(page.title).to.equal(pagesToSave[i].body.blocks[0].data.text);
|
||||
expect(page.body).to.deep.equal(pagesToSave[i].body);
|
||||
});
|
||||
});
|
||||
|
||||
it('Parent pages', async () => {
|
||||
const parent = new Page({title: 'Parent page', body: 'Parent page body'});
|
||||
const parent = new Page(
|
||||
{
|
||||
body: {
|
||||
blocks: [
|
||||
{
|
||||
type: 'header',
|
||||
data: {
|
||||
text: 'Parent page header'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
const {_id: parentId} = await parent.save();
|
||||
|
||||
const child = new Page({title: 'Child page', body: 'Child page body'});
|
||||
const child = new Page(
|
||||
{
|
||||
body: {
|
||||
blocks: [
|
||||
{
|
||||
type: 'header',
|
||||
data: {
|
||||
text: 'Child page header'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
child.parent = parent;
|
||||
|
||||
|
@ -155,8 +256,8 @@ describe('Page model', () => {
|
|||
const testedParent = await child.parent;
|
||||
|
||||
expect(testedParent._id).to.equal(parentId);
|
||||
expect(testedParent.title).to.equal(parent.title);
|
||||
expect(testedParent.body).to.equal(parent.body);
|
||||
expect(testedParent.title).to.equal(parent.body.blocks[0].data.text);
|
||||
expect(testedParent.body).to.deep.equal(parent.body);
|
||||
|
||||
const children = await parent.children;
|
||||
|
||||
|
@ -165,12 +266,31 @@ describe('Page model', () => {
|
|||
const testedChild = children.pop();
|
||||
|
||||
expect(testedChild._id).to.equal(childId);
|
||||
expect(testedChild.title).to.equal(child.title);
|
||||
expect(testedChild.body).to.equal(child.body);
|
||||
expect(testedChild.title).to.equal(child.body.blocks[0].data.text);
|
||||
expect(testedChild.body).to.deep.equal(child.body);
|
||||
expect(testedChild._parent).to.equal(child._parent);
|
||||
expect(testedChild._parent).to.equal(parent._id);
|
||||
|
||||
parent.destroy();
|
||||
child.destroy();
|
||||
});
|
||||
|
||||
it('Extracting title from page body', async () => {
|
||||
const pageData = {
|
||||
body: {
|
||||
blocks: [
|
||||
{
|
||||
type: 'header',
|
||||
data: {
|
||||
text: 'Page header'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const page = new Page(pageData);
|
||||
|
||||
expect(page.title).to.equal(pageData.body.blocks[0].data.text);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,12 +26,20 @@ describe('Pages REST: ', () => {
|
|||
});
|
||||
|
||||
it('Creating page', async () => {
|
||||
const title = 'Test page';
|
||||
const body = 'Test page body';
|
||||
const body = {
|
||||
blocks: [
|
||||
{
|
||||
type: 'header',
|
||||
data: {
|
||||
text: 'Page header'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const res = await agent
|
||||
.put('/page')
|
||||
.send({title, body});
|
||||
.send({body});
|
||||
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.be.json;
|
||||
|
@ -40,15 +48,15 @@ describe('Pages REST: ', () => {
|
|||
|
||||
expect(success).to.be.true;
|
||||
expect(result._id).to.be.a('string');
|
||||
expect(result.title).to.equal(title);
|
||||
expect(result.body).to.equal(body);
|
||||
expect(result.title).to.equal(body.blocks[0].data.text);
|
||||
expect(result.body).to.deep.equal(body);
|
||||
|
||||
const createdPage = await model.get(result._id);
|
||||
|
||||
expect(createdPage).not.be.null;
|
||||
expect(createdPage._id).to.equal(result._id);
|
||||
expect(createdPage.title).to.equal(title);
|
||||
expect(createdPage.body).to.equal(body);
|
||||
expect(createdPage.title).to.equal(body.blocks[0].data.text);
|
||||
expect(createdPage.body).to.deep.equal(body);
|
||||
|
||||
createdPage.destroy();
|
||||
});
|
||||
|
@ -64,16 +72,24 @@ describe('Pages REST: ', () => {
|
|||
const {success, error} = res.body;
|
||||
|
||||
expect(success).to.be.false;
|
||||
expect(error).to.equal('Invalid request format');
|
||||
expect(error).to.equal('Error: Some of required fields is missed');
|
||||
});
|
||||
|
||||
it('Finding page', async () => {
|
||||
const title = 'Test page';
|
||||
const body = 'Test page body';
|
||||
const body = {
|
||||
blocks: [
|
||||
{
|
||||
type: 'header',
|
||||
data: {
|
||||
text: 'Page header'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const put = await agent
|
||||
.put('/page')
|
||||
.send({title, body});
|
||||
.send({body});
|
||||
|
||||
expect(put).to.have.status(200);
|
||||
expect(put).to.be.json;
|
||||
|
@ -92,8 +108,8 @@ describe('Pages REST: ', () => {
|
|||
const foundPage = await model.get(_id);
|
||||
|
||||
expect(foundPage._id).to.equal(_id);
|
||||
expect(foundPage.title).to.equal(title);
|
||||
expect(foundPage.body).to.equal(body);
|
||||
expect(foundPage.title).to.equal(body.blocks[0].data.text);
|
||||
expect(foundPage.body).to.deep.equal(body);
|
||||
|
||||
foundPage.destroy();
|
||||
});
|
||||
|
@ -111,24 +127,40 @@ describe('Pages REST: ', () => {
|
|||
});
|
||||
|
||||
it('Updating page', async () => {
|
||||
const title = 'Test page';
|
||||
const body = 'Test page body';
|
||||
const body = {
|
||||
blocks: [
|
||||
{
|
||||
type: 'header',
|
||||
data: {
|
||||
text: 'Page header'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let res = await agent
|
||||
.put('/page')
|
||||
.send({title, body});
|
||||
.send({body});
|
||||
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.be.json;
|
||||
|
||||
const {result: {_id}} = res.body;
|
||||
|
||||
const updatedTitle = 'Updated test page';
|
||||
const updatedBody = 'Updated test page body';
|
||||
const updatedBody = {
|
||||
blocks: [
|
||||
{
|
||||
type: 'header',
|
||||
data: {
|
||||
text: 'Updated page header'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
res = await agent
|
||||
.post(`/page/${_id}`)
|
||||
.send({title: updatedTitle, body: updatedBody});
|
||||
.send({body: updatedBody});
|
||||
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.be.json;
|
||||
|
@ -137,26 +169,35 @@ describe('Pages REST: ', () => {
|
|||
|
||||
expect(success).to.be.true;
|
||||
expect(result._id).to.equal(_id);
|
||||
expect(result.title).not.equal(title);
|
||||
expect(result.title).to.equal(updatedTitle);
|
||||
expect(result.title).not.equal(body.blocks[0].data.text);
|
||||
expect(result.title).to.equal(updatedBody.blocks[0].data.text);
|
||||
expect(result.body).not.equal(body);
|
||||
expect(result.body).to.equal(updatedBody);
|
||||
expect(result.body).to.deep.equal(updatedBody);
|
||||
|
||||
const updatedPage = await model.get(_id);
|
||||
|
||||
expect(updatedPage._id).to.equal(_id);
|
||||
expect(updatedPage.title).not.equal(title);
|
||||
expect(updatedPage.title).to.equal(updatedTitle);
|
||||
expect(updatedPage.title).not.equal(body.blocks[0].data.text);
|
||||
expect(updatedPage.title).to.equal(updatedBody.blocks[0].data.text);
|
||||
expect(updatedPage.body).not.equal(body);
|
||||
expect(updatedPage.body).to.equal(updatedBody);
|
||||
expect(updatedPage.body).to.deep.equal(updatedBody);
|
||||
|
||||
updatedPage.destroy();
|
||||
});
|
||||
|
||||
it('Updating page with not existing id', async () => {
|
||||
const res = await agent
|
||||
. post('/page/not-existing-id')
|
||||
.send({title: 'Updated title', body: 'Updated body'});
|
||||
.post('/page/not-existing-id')
|
||||
.send({body: {
|
||||
blocks: [
|
||||
{
|
||||
type: 'header',
|
||||
data: {
|
||||
text: 'Page header'
|
||||
}
|
||||
}
|
||||
]
|
||||
}});
|
||||
|
||||
expect(res).to.have.status(400);
|
||||
expect(res).to.be.json;
|
||||
|
@ -168,12 +209,20 @@ describe('Pages REST: ', () => {
|
|||
});
|
||||
|
||||
it('Removing page', async () => {
|
||||
const title = 'Test page';
|
||||
const body = 'Test page body';
|
||||
const body = {
|
||||
blocks: [
|
||||
{
|
||||
type: 'header',
|
||||
data: {
|
||||
text: 'Page header'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let res = await agent
|
||||
.put('/page')
|
||||
.send({title, body});
|
||||
.send({body});
|
||||
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.be.json;
|
||||
|
@ -190,8 +239,8 @@ describe('Pages REST: ', () => {
|
|||
|
||||
expect(success).to.be.true;
|
||||
expect(result._id).to.be.undefined;
|
||||
expect(result.title).to.equal(title);
|
||||
expect(result.body).to.equal(body);
|
||||
expect(result.title).to.equal(body.blocks[0].data.text);
|
||||
expect(result.body).to.deep.equal(body);
|
||||
|
||||
const deletedPage = await model.get(_id);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
|
||||
/**
|
||||
* Options for the Babel
|
||||
|
@ -8,25 +8,37 @@ const babelLoader = {
|
|||
options: {
|
||||
cacheDirectory: '.cache/babel-loader',
|
||||
presets: [
|
||||
'env'
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
'useBuiltIns': 'usage'
|
||||
}
|
||||
]
|
||||
],
|
||||
plugins: [
|
||||
/**
|
||||
* Dont need to use «.default» after «export default Class Ui {}»
|
||||
* @see {@link https://github.com/59naga/babel-plugin-add-module-exports}
|
||||
*/
|
||||
// 'add-module-exports',
|
||||
'@babel/plugin-syntax-dynamic-import'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = (env) => {
|
||||
return {
|
||||
output: {
|
||||
libraryExport: 'default' // uses to export .default field of app.js exported class instance
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.p?css$/,
|
||||
use: ExtractTextPlugin.extract([
|
||||
use: [
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: {
|
||||
// you can specify a publicPath here
|
||||
// by default it use publicPath in webpackOptions.output
|
||||
publicPath: '../'
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
|
@ -41,9 +53,10 @@ module.exports = (env) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
]
|
||||
}, {
|
||||
test: /\.js$/,
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
use: [
|
||||
babelLoader
|
||||
]
|
||||
|
@ -51,10 +64,14 @@ module.exports = (env) => {
|
|||
]
|
||||
},
|
||||
plugins: [
|
||||
new ExtractTextPlugin('bundle.css')
|
||||
new MiniCssExtractPlugin({
|
||||
// Options similar to the same options in webpackOptions.output
|
||||
filename: '[name].css'
|
||||
})
|
||||
],
|
||||
optimization: {
|
||||
minimize: true
|
||||
minimize: true,
|
||||
splitChunks: false
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue