1
0
Fork 0
mirror of https://github.com/codex-team/codex.docs.git synced 2025-07-24 15:49: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

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,15 +7,28 @@
}
</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
<select name="parent">
<option value="0">Root</option>
{% for page in pagesAvailable %}
<option value="{{ page._id }}">{{ page.title }}</option>
{% endfor %}
</select>
{% 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 %}
{% 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>
<span class="writing-header__save" name="js-submit">
Save

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 %}