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

Page creation basics (#7)

* Page cration basics

* remove unused code

* add client-side Header validation

* remove static method

* rm await duplication
This commit is contained in:
Peter Savchenko 2018-10-04 22:08:21 +03:00 committed by GitHub
parent 5c0560a2ed
commit 073772c047
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 476 additions and 93 deletions

View file

@ -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');
}
}
/**

View file

@ -1,7 +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();
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();
}
}

View file

@ -1,29 +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.editorWrapper = null;
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(config, moduleEl) {
this.editorWrapper = document.createElement('div');
this.editorWrapper.id = 'codex-editor';
init(settings, moduleEl) {
/**
* Create Editor
*/
this.nodes.editorWrapper = document.createElement('div');
this.nodes.editorWrapper.id = 'codex-editor';
moduleEl.appendChild(this.editorWrapper);
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);
}
}
}

View 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);
}
}

View file

@ -18,6 +18,6 @@
&__aside,
&__content {
padding: 40px 0;
padding: var(--layout-padding-vertical) 0;
}
}

View file

@ -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;

View file

@ -8,6 +8,7 @@
* Site layout sizes
*/
--layout-padding-horisontal: 40px;
--layout-padding-vertical: 40px;
--layout-width-aside: 250px;
--layout-width-main-col: 650px;

View file

@ -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
*

View file

@ -7,7 +7,11 @@ const Pages = require('../controllers/pages');
* Create new page form
*/
router.get('/page/new', async (req, res) => {
res.render('pages/form');
let pagesAvailable = await Pages.getAll();
res.render('pages/form', {
pagesAvailable
});
});
/**

View file

@ -1,6 +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 %}