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

Page showing, page edit, move API to /api/ (#10)

This commit is contained in:
Peter Savchenko 2018-10-15 22:06:01 +03:00 committed by GitHub
parent ff09f695d0
commit 730eff7995
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 316 additions and 133 deletions

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

@ -7,8 +7,9 @@ import Header from 'codex.editor.header';
export default class Editor {
/**
* Creates Editor instance
* @property {object} initialData - data to start with
*/
constructor() {
constructor({initialData}) {
this.editor = new CodeXEditor({
tools: {
header: {
@ -18,7 +19,7 @@ export default class Editor {
}
}
},
data: {
data: initialData || {
blocks: [
{
type: 'header',

View file

@ -7,6 +7,12 @@
* @property {string} version - used Editor version
* @property {number} time - saving time
*/
/**
* @typedef {object} writingSettings
* @property {{_id, _parent, title, body: editorData}} [page] - page data for editing
*/
/**
* @class Writing
* @classdesc Class for create/edit pages
@ -17,6 +23,7 @@ export default class Writing {
*/
constructor() {
this.editor = null;
this.page = null; // stores Page on editing
this.nodes = {
editorWrapper: null,
saveButton: null,
@ -26,10 +33,10 @@ export default class Writing {
/**
* Called by ModuleDispatcher to initialize module from DOM
* @param {object} settings - module settings
* @param {writingSettings} settings - module settings
* @param {HTMLElement} moduleEl - module element
*/
init(settings, moduleEl) {
init(settings = {}, moduleEl) {
/**
* Create Editor
*/
@ -38,6 +45,10 @@ export default class Writing {
moduleEl.appendChild(this.nodes.editorWrapper);
if (settings.page){
this.page = settings.page;
}
this.loadEditor().then((editor) => {
this.editor = editor;
});
@ -59,7 +70,9 @@ export default class Writing {
async loadEditor() {
const {default: Editor} = await import(/* webpackChunkName: "editor" */ './../classes/editor');
return new Editor();
return new Editor({
initialData: this.page ? this.page.body : null
});
}
/**
@ -88,10 +101,11 @@ export default class Writing {
async saveButtonClicked() {
try {
const writingData = await this.getData();
const endpoint = this.page ? '/api/page/' + this.page._id : '/api/page';
try {
let response = await fetch('/page', {
method: 'PUT',
let response = await fetch(endpoint, {
method: this.page ? 'POST' : 'PUT',
headers: {
'Content-Type': 'application/json; charset=utf-8'
},

View file

@ -0,0 +1,57 @@
.page {
font-size: 16px;
line-height: 1.6;
&__header {
display: flex;
color: var(--color-text-second);
&-nav {
color: inherit;
text-decoration: none;
&:hover {
color: var(--color-link-active);
}
&:not(:last-of-type) {
&::after {
content: '»';
margin: 0 0.7em 0 0.45em;
}
}
}
&-time {
margin-left: auto;
}
&-button {
@apply --button;
padding: 5px 10px;
font-size: 13px;
margin-left: 10px;
}
}
&__title {
font-size: 26px;
font-weight: 700;
letter-spacing: -0.04px;
margin-bottom: -0.2em;
}
}
.block-header {
margin: 1.5em 0 0.5em;
&--2 {
font-size: 22px;
font-weight: 500;
}
&--3 {
font-size: 18px;
font-weight: 500;
}
}

View file

@ -4,6 +4,7 @@
@import url('components/header.pcss');
@import url('components/aside.pcss');
@import url('components/writing.pcss');
@import url('components/page.pcss');
body {
font-family: system-ui, Helvetica, Arial, Verdana;

View file

@ -9,7 +9,7 @@
*/
--layout-padding-horisontal: 40px;
--layout-padding-vertical: 40px;
--layout-width-aside: 250px;
--layout-width-aside: 200px;
--layout-width-main-col: 650px;
--button {
@ -20,6 +20,7 @@
padding: 9px 15px;
font-size: 14px;
line-height: 1em;
text-decoration: none;
svg {
margin: 0 0.3em 0 -0.05em;

View file

@ -1,4 +1,4 @@
const {pages} = require('../utils/database/index');
const {pages: db} = require('../utils/database/index');
/**
* @typedef {Object} PageData
@ -25,7 +25,7 @@ class Page {
* @returns {Promise<Page>}
*/
static async get(_id) {
const data = await pages.findOne({_id});
const data = await db.findOne({_id});
return new Page(data);
}
@ -37,7 +37,7 @@ class Page {
* @returns {Promise<Page[]>}
*/
static async getAll(query = {}) {
const docs = await pages.find(query);
const docs = await db.find(query);
return Promise.all(docs.map(doc => new Page(doc)));
}
@ -52,8 +52,6 @@ class Page {
data = {};
}
this.db = pages;
if (data._id) {
this._id = data._id;
}
@ -113,7 +111,7 @@ class Page {
* @returns {Promise<Page>}
*/
get parent() {
return this.db.findOne({_id: this._parent})
return db.findOne({_id: this._parent})
.then(data => new Page(data));
}
@ -123,7 +121,7 @@ class Page {
* @returns {Promise<Page[]>}
*/
get children() {
return this.db.find({parent: this._id})
return db.find({parent: this._id})
.then(data => data.map(page => new Page(page)));
}
@ -134,11 +132,11 @@ class Page {
*/
async save() {
if (!this._id) {
const insertedRow = await this.db.insert(this.data);
const insertedRow = await db.insert(this.data);
this._id = insertedRow._id;
} else {
await this.db.update({_id: this._id}, this.data);
await db.update({_id: this._id}, this.data);
}
return this;
@ -150,7 +148,7 @@ class Page {
* @returns {Promise<Page>}
*/
async destroy() {
await this.db.remove({_id: this._id});
await db.remove({_id: this._id});
delete this._id;

8
src/routes/api/index.js Normal file
View file

@ -0,0 +1,8 @@
const express = require('express');
const router = express.Router();
const pagesAPI = require('./pages');
router.use('/', pagesAPI);
module.exports = router;

115
src/routes/api/pages.js Normal file
View file

@ -0,0 +1,115 @@
const express = require('express');
const router = express.Router();
const multer = require('multer')();
const Pages = require('../../controllers/pages');
/**
* GET /page/:id
*
* Return PageData of page with given id
*/
router.get('/page/:id', async (req, res) => {
try {
const page = await Pages.get(req.params.id);
res.json({
success: true,
result: page.data
});
} catch (err) {
res.status(400).json({
success: false,
error: err.message
});
}
});
/**
* GET /pages
*
* Return PageData for all pages
*/
router.get('/pages', async (req, res) => {
try {
const pages = await Pages.getAll();
res.json({
success: true,
result: pages
});
} catch (err) {
res.status(400).json({
success: false,
error: err.message
});
}
});
/**
* PUT /page
*
* Create new page in the database
*/
router.put('/page', multer.any(), async (req, res) => {
try {
const {title, body, parent} = req.body;
const page = await Pages.insert({title, body, parent});
res.json({
success: true,
result: page
});
} catch (err) {
res.status(400).json({
success: false,
error: err.message
});
}
});
/**
* POST /page/:id
*
* Update page data in the database
*/
router.post('/page/:id', multer.any(), async (req, res) => {
const {id} = req.params;
try {
const {title, body, parent} = req.body;
const page = await Pages.update(id, {title, body, parent});
res.json({
success: true,
result: page
});
} catch (err) {
res.status(400).json({
success: false,
error: err.message
});
}
});
/**
* DELETE /page/:id
*
* Remove page from the database
*/
router.delete('/page/:id', async (req, res) => {
try {
const page = await Pages.remove(req.params.id);
res.json({
success: true,
result: page
});
} catch (err) {
res.status(400).json({
success: false,
error: err.message
});
}
});
module.exports = router;

View file

@ -3,8 +3,10 @@ const router = express.Router();
const home = require('./home');
const pages = require('./pages');
const api = require('./api');
router.use('/', home);
router.use('/', pages);
router.use('/api', api);
module.exports = router;

View file

@ -1,6 +1,5 @@
const express = require('express');
const router = express.Router();
const multer = require('multer')();
const Pages = require('../controllers/pages');
/**
@ -10,116 +9,46 @@ router.get('/page/new', async (req, res) => {
let pagesAvailable = await Pages.getAll();
res.render('pages/form', {
pagesAvailable
pagesAvailable,
page: null
});
});
/**
* GET /page/:id
*
* Return PageData of page with given id
* Edit page form
*/
router.get('/page/:id', async (req, res) => {
try {
const page = await Pages.get(req.params.id);
router.get('/page/edit/:id', async (req, res, next) => {
const pageId = req.params.id;
res.json({
success: true,
result: page.data
});
} catch (err) {
res.status(400).json({
success: false,
error: err.message
try {
let page = await Pages.get(pageId);
let pagesAvailable = await Pages.getAll();
res.render('pages/form', {
pagesAvailable,
page
});
} catch (error) {
res.status(404);
next(error);
}
});
/**
* GET /pages
*
* Return PageData for all pages
* View page
*/
router.get('/pages', async (req, res) => {
try {
const pages = await Pages.getAll();
res.json({
success: true,
result: pages
});
} catch (err) {
res.status(400).json({
success: false,
error: err.message
});
}
});
/**
* PUT /page
*
* Create new page in the database
*/
router.put('/page', multer.any(), async (req, res) => {
try {
const {title, body, parent} = req.body;
const page = await Pages.insert({title, body, parent});
res.json({
success: true,
result: page
});
} catch (err) {
res.status(400).json({
success: false,
error: err.message
});
}
});
/**
* POST /page/:id
*
* Update page data in the database
*/
router.post('/page/:id', multer.any(), async (req, res) => {
const {id} = req.params;
router.get('/page/:id', async (req, res, next) => {
const pageId = req.params.id;
try {
const {title, body, parent} = req.body;
const page = await Pages.update(id, {title, body, parent});
let page = await Pages.get(pageId);
res.json({
success: true,
result: page
});
} catch (err) {
res.status(400).json({
success: false,
error: err.message
});
}
});
/**
* DELETE /page/:id
*
* Remove page from the database
*/
router.delete('/page/:id', async (req, res) => {
try {
const page = await Pages.remove(req.params.id);
res.json({
success: true,
result: page
});
} catch (err) {
res.status(400).json({
success: false,
error: err.message
res.render('pages/page', {
page
});
} catch (error) {
res.status(404);
next(error);
}
});

View file

@ -0,0 +1,3 @@
<h{{ level }} class="block-header block-header--{{ level }}">
{{ text }}
</h{{ level }}>

View file

@ -0,0 +1,3 @@
<p class="block-paragraph">
{{ text }}
</p>

View file

@ -7,13 +7,26 @@
}
</style>
<section data-module="writing">
<module-settings hidden>
{
"page": {{ page | json_encode }}
}
</module-settings>
<header class="writing-header">
<span class="writing-header__left">
New Page at the
{% set currentPageId = 0 %}
{% if page is not empty %}
{% set currentPageId = page._id %}
{% endif %}
<select name="parent">
<option value="0">Root</option>
{% for page in pagesAvailable %}
<option value="{{ page._id }}">{{ page.title }}</option>
{% for _page in pagesAvailable %}
{% if _page._id != currentPageId %}
<option value="{{ _page._id }}" {{ page is not empty and page._parent == _page._id ? 'selected' : ''}}>
{{ _page.title }}
</option>
{% endif %}
{% endfor %}
</select>
</span>

38
src/views/pages/page.twig Normal file
View file

@ -0,0 +1,38 @@
{% extends 'layout.twig' %}
{% block body %}
<article class="page">
<header class="page__header">
<a href="/" class="page__header-nav">
Documentation
</a>
{% if page._parent %}
<a href="/page/{{ page._parent }}" class="page__header-nav">
Parent {{ page._parent }}
</a>
{% endif %}
<time class="page__header-time">
Last edit {{ (page.body.time / 1000) | date("M d Y") }}
<a href="/page/edit/{{ page._id }}" class="page__header-button">
Edit
</a>
</time>
</header>
<h1 class="page__title">
{{ page.title }}
</h1>
<section class="page__content">
{% for block in page.body.blocks %}
{# Skip first header, because it is already showed as a Title #}
{% if not (loop.first and block.type == 'header') %}
{% if block.type in ['paragraph', 'header'] %}
{% include './blocks/' ~ block.type ~ '.twig' with block.data %}
{% endif %}
{% endif %}
{% endfor %}
</section>
<footer>
</footer>
</article>
{% endblock %}

View file

@ -38,7 +38,7 @@ describe('Pages REST: ', () => {
};
const res = await agent
.put('/page')
.put('/api/page')
.send({body});
expect(res).to.have.status(200);
@ -63,7 +63,7 @@ describe('Pages REST: ', () => {
it('Page data validation on create', async () => {
const res = await agent
.put('/page')
.put('/api/page')
.send({someField: 'Some text'});
expect(res).to.have.status(400);
@ -88,7 +88,7 @@ describe('Pages REST: ', () => {
};
const put = await agent
.put('/page')
.put('/api/page')
.send({body});
expect(put).to.have.status(200);
@ -96,7 +96,7 @@ describe('Pages REST: ', () => {
const {result: {_id}} = put.body;
const get = await agent.get(`/page/${_id}`);
const get = await agent.get(`/api/page/${_id}`);
expect(get).to.have.status(200);
expect(get).to.be.json;
@ -115,7 +115,7 @@ describe('Pages REST: ', () => {
});
it('Finding page with not existing id', async () => {
const res = await agent.get('/page/not-existing-id');
const res = await agent.get('/api/page/not-existing-id');
expect(res).to.have.status(400);
expect(res).to.be.json;
@ -139,7 +139,7 @@ describe('Pages REST: ', () => {
};
let res = await agent
.put('/page')
.put('/api/page')
.send({body});
expect(res).to.have.status(200);
@ -159,7 +159,7 @@ describe('Pages REST: ', () => {
};
res = await agent
.post(`/page/${_id}`)
.post(`/api/page/${_id}`)
.send({body: updatedBody});
expect(res).to.have.status(200);
@ -187,7 +187,7 @@ describe('Pages REST: ', () => {
it('Updating page with not existing id', async () => {
const res = await agent
.post('/page/not-existing-id')
.post('/api/page/not-existing-id')
.send({body: {
blocks: [
{
@ -221,7 +221,7 @@ describe('Pages REST: ', () => {
};
let res = await agent
.put('/page')
.put('/api/page')
.send({body});
expect(res).to.have.status(200);
@ -230,7 +230,7 @@ describe('Pages REST: ', () => {
const {result: {_id}} = res.body;
res = await agent
.delete(`/page/${_id}`);
.delete(`/api/page/${_id}`);
expect(res).to.have.status(200);
expect(res).to.be.json;
@ -249,7 +249,7 @@ describe('Pages REST: ', () => {
it('Removing page with not existing id', async () => {
const res = await agent
.delete('/page/not-existing-id');
.delete('/api/page/not-existing-id');
expect(res).to.have.status(400);
expect(res).to.be.json;