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:
parent
ff09f695d0
commit
730eff7995
18 changed files with 316 additions and 133 deletions
2
public/dist/editor.bundle.js
vendored
2
public/dist/editor.bundle.js
vendored
File diff suppressed because one or more lines are too long
2
public/dist/main.bundle.js
vendored
2
public/dist/main.bundle.js
vendored
File diff suppressed because one or more lines are too long
2
public/dist/main.css
vendored
2
public/dist/main.css
vendored
File diff suppressed because one or more lines are too long
|
@ -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',
|
||||
|
|
|
@ -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'
|
||||
},
|
||||
|
|
57
src/frontend/styles/components/page.pcss
Normal file
57
src/frontend/styles/components/page.pcss
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
8
src/routes/api/index.js
Normal 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
115
src/routes/api/pages.js
Normal 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;
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
3
src/views/pages/blocks/header.twig
Normal file
3
src/views/pages/blocks/header.twig
Normal file
|
@ -0,0 +1,3 @@
|
|||
<h{{ level }} class="block-header block-header--{{ level }}">
|
||||
{{ text }}
|
||||
</h{{ level }}>
|
3
src/views/pages/blocks/paragraph.twig
Normal file
3
src/views/pages/blocks/paragraph.twig
Normal file
|
@ -0,0 +1,3 @@
|
|||
<p class="block-paragraph">
|
||||
{{ text }}
|
||||
</p>
|
|
@ -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
38
src/views/pages/page.twig
Normal 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 %}
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue