mirror of
https://github.com/codex-team/codex.docs.git
synced 2025-08-09 23:45:25 +02:00
Merge branch 'main' into feat/styles
This commit is contained in:
commit
2ff1f0729a
12 changed files with 174 additions and 21 deletions
|
@ -18,6 +18,7 @@
|
|||
"editor-upgrade": "yarn add -D @editorjs/{editorjs,header,code,delimiter,list,link,image,table,inline-code,marker,warning,checklist,raw}@latest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codexteam/shortcuts": "^1.2.0",
|
||||
"@hawk.so/javascript": "^3.0.1",
|
||||
"@hawk.so/nodejs": "^3.1.2",
|
||||
"config": "^3.3.6",
|
||||
|
|
|
@ -14,10 +14,6 @@ class Aliases {
|
|||
public static async get(aliasName: string): Promise<Alias> {
|
||||
const alias = await Alias.get(aliasName);
|
||||
|
||||
if (!alias.id) {
|
||||
throw new Error('Entity with given alias does not exist');
|
||||
}
|
||||
|
||||
return alias;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ export interface PagesFlatArrayData {
|
|||
}
|
||||
|
||||
/**
|
||||
* @class PagesFlatArray model - flat array of pages, which are ordered like in sidebar
|
||||
* @class PagesFlatArray model - flat array of pages, which are ordered like in sidebar
|
||||
*/
|
||||
class PagesFlatArray {
|
||||
/**
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import express, { Request, Response } from 'express';
|
||||
import Aliases from '../controllers/aliases.js';
|
||||
import Pages from '../controllers/pages.js';
|
||||
import Alias from '../models/alias.js';
|
||||
import verifyToken from './middlewares/token.js';
|
||||
import PagesFlatArray from '../models/pagesFlatArray.js';
|
||||
import Aliases from '../controllers/aliases';
|
||||
import Pages from '../controllers/pages';
|
||||
import Alias from '../models/alias';
|
||||
import verifyToken from './middlewares/token';
|
||||
import PagesFlatArray from '../models/pagesFlatArray';
|
||||
import HttpException from '../exceptions/httpException';
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
@ -24,7 +26,7 @@ router.get('*', verifyToken, async (req: Request, res: Response) => {
|
|||
const alias = await Aliases.get(url);
|
||||
|
||||
if (alias.id === undefined) {
|
||||
throw new Error('Alias not found');
|
||||
throw new HttpException(404, 'Alias not found');
|
||||
}
|
||||
|
||||
switch (alias.type) {
|
||||
|
@ -46,11 +48,18 @@ router.get('*', verifyToken, async (req: Request, res: Response) => {
|
|||
}
|
||||
}
|
||||
} catch (err) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: err,
|
||||
});
|
||||
if (err instanceof HttpException && err.status === 404) {
|
||||
res.status(404).render('error', {
|
||||
message: 'Page not found',
|
||||
status: 404,
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: err,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
export default router;
|
|
@ -52,4 +52,8 @@
|
|||
</div>
|
||||
|
||||
</aside>
|
||||
|
||||
<div class="docs-sidebar__slider">
|
||||
{{ svg('arrow-left') }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
{% extends 'layout.twig' %}
|
||||
|
||||
{% block body %}
|
||||
<h1>{{message}}</h1>
|
||||
<h2>{{error.status}}</h2>
|
||||
<pre>{{error.stack}}</pre>
|
||||
<div class="error-page">
|
||||
<h1>
|
||||
┬┴┬┴┤ {{status}} ├┬┴┬┴
|
||||
</h1>
|
||||
<h1>{{message}}</h1>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { Storage } from '../utils/storage';
|
||||
import Shortcut from '@codexteam/shortcuts';
|
||||
|
||||
/**
|
||||
* Local storage key
|
||||
*/
|
||||
const LOCAL_STORAGE_KEY = 'docs_sidebar_state';
|
||||
|
||||
const SIDEBAR_VISIBILITY_KEY = 'docs_sidebar_visibility';
|
||||
|
||||
/**
|
||||
* Section list item height in px
|
||||
|
@ -31,6 +32,9 @@ export default class Sidebar {
|
|||
sectionList: 'docs-sidebar__section-list',
|
||||
sectionListItemActive: 'docs-sidebar__section-list-item--active',
|
||||
sidebarToggler: 'docs-sidebar__toggler',
|
||||
sidebarSlider: 'docs-sidebar__slider',
|
||||
sidebarCollapsed: 'docs-sidebar--collapsed',
|
||||
sidebarAnimated: 'docs-sidebar--animated',
|
||||
sidebarContent: 'docs-sidebar__content',
|
||||
sidebarContentHidden: 'docs-sidebar__content--hidden',
|
||||
sidebarContentInvisible: 'docs-sidebar__content--invisible',
|
||||
|
@ -45,14 +49,24 @@ export default class Sidebar {
|
|||
* Stores refs to HTML elements needed for correct sidebar work
|
||||
*/
|
||||
this.nodes = {
|
||||
sidebar: null,
|
||||
sections: [],
|
||||
sidebarContent: null,
|
||||
toggler: null,
|
||||
slider: null,
|
||||
};
|
||||
this.sidebarStorage = new Storage(LOCAL_STORAGE_KEY);
|
||||
const storedState = this.sidebarStorage.get();
|
||||
|
||||
this.sectionsState = storedState ? JSON.parse(storedState) : {};
|
||||
|
||||
// Initialize localStorage that contains sidebar visibility
|
||||
this.sidebarVisibilityStorage = new Storage(SIDEBAR_VISIBILITY_KEY);
|
||||
// Get current sidebar visibility from storage
|
||||
const storedVisibility = this.sidebarVisibilityStorage.get();
|
||||
|
||||
// Sidebar visibility
|
||||
this.isVisible = storedVisibility !== 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,11 +76,14 @@ export default class Sidebar {
|
|||
* @param {HTMLElement} moduleEl - module element
|
||||
*/
|
||||
init(settings, moduleEl) {
|
||||
this.nodes.sidebar = moduleEl;
|
||||
this.nodes.sections = Array.from(moduleEl.querySelectorAll('.' + Sidebar.CSS.section));
|
||||
this.nodes.sections.forEach(section => this.initSection(section));
|
||||
this.nodes.sidebarContent = moduleEl.querySelector('.' + Sidebar.CSS.sidebarContent);
|
||||
this.nodes.toggler = moduleEl.querySelector('.' + Sidebar.CSS.sidebarToggler);
|
||||
this.nodes.toggler.addEventListener('click', () => this.toggleSidebar());
|
||||
this.nodes.slider = moduleEl.querySelector('.' + Sidebar.CSS.sidebarSlider);
|
||||
this.nodes.slider.addEventListener('click', () => this.handleSliderClick());
|
||||
this.ready();
|
||||
}
|
||||
|
||||
|
@ -104,7 +121,7 @@ export default class Sidebar {
|
|||
|
||||
const itemsCount = sectionList.children.length;
|
||||
|
||||
sectionList.style.maxHeight = `${ itemsCount * ITEM_HEIGHT }px`;
|
||||
sectionList.style.maxHeight = `${itemsCount * ITEM_HEIGHT}px`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -168,12 +185,52 @@ export default class Sidebar {
|
|||
this.nodes.sidebarContent.classList.toggle(Sidebar.CSS.sidebarContentHidden);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes sidebar
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
initSidebar() {
|
||||
if (!this.isVisible) {
|
||||
this.nodes.sidebar.classList.add(Sidebar.CSS.sidebarCollapsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* prevent sidebar animation on page load
|
||||
* Since animated class contains transition, hiding will be animated with it
|
||||
* To prevent awkward animation when visibility is set to false, we need to remove animated class
|
||||
*/
|
||||
setTimeout(() => {
|
||||
this.nodes.sidebar.classList.add(Sidebar.CSS.sidebarAnimated);
|
||||
}, 200);
|
||||
|
||||
// add event listener to execute keyboard shortcut
|
||||
// eslint-disable-next-line no-new
|
||||
new Shortcut({
|
||||
name: 'CMD+.',
|
||||
on: document.body,
|
||||
callback: () => this.handleSliderClick(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Slides sidebar
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
handleSliderClick() {
|
||||
this.isVisible = !this.isVisible;
|
||||
this.sidebarVisibilityStorage.set(this.isVisible);
|
||||
this.nodes.sidebar.classList.toggle(Sidebar.CSS.sidebarCollapsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays sidebar when ready
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
ready() {
|
||||
this.initSidebar();
|
||||
this.nodes.sidebarContent.classList.remove(Sidebar.CSS.sidebarContentInvisible);
|
||||
}
|
||||
}
|
||||
|
|
29
src/frontend/styles/components/error.pcss
Normal file
29
src/frontend/styles/components/error.pcss
Normal file
|
@ -0,0 +1,29 @@
|
|||
.error-page {
|
||||
font-size: 15px;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 45%;
|
||||
left: 50%;
|
||||
|
||||
@media (--mobile) {
|
||||
position: relative;
|
||||
top: 30vh;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
@media (--tablet) {
|
||||
position: relative;
|
||||
top: 30vh;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@media (--mobile) {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 40px 0 20px;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,34 @@
|
|||
.docs-sidebar {
|
||||
width: 100vw;
|
||||
|
||||
&--animated {
|
||||
.docs-sidebar__content {
|
||||
transition: transform 200ms ease-in-out;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.docs-sidebar__slider {
|
||||
transition: transform 200ms ease-in-out;
|
||||
will-change: transform;
|
||||
}
|
||||
}
|
||||
|
||||
&--collapsed {
|
||||
@media (--desktop) {
|
||||
.docs-sidebar__content {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
.docs-sidebar__slider {
|
||||
transform: translateX(20px);
|
||||
|
||||
svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (--desktop) {
|
||||
width: var(--layout-sidebar-width);
|
||||
}
|
||||
|
@ -196,6 +224,24 @@
|
|||
}
|
||||
}
|
||||
|
||||
&__slider {
|
||||
display: none;
|
||||
position: fixed;
|
||||
transform: translateX(calc(var(--layout-sidebar-width) + 20px));
|
||||
bottom: 20px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
background-color: var(--color-link-hover);
|
||||
|
||||
@media (--desktop) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__logo {
|
||||
display: none;
|
||||
margin-top: auto;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
@import './components/page.pcss';
|
||||
@import './components/landing.pcss';
|
||||
@import './components/auth.pcss';
|
||||
@import './components/error.pcss';
|
||||
@import './components/button.pcss';
|
||||
@import './components/sidebar.pcss';
|
||||
@import './components/navigator.pcss';
|
||||
|
|
3
src/frontend/svg/arrow-left.svg
Normal file
3
src/frontend/svg/arrow-left.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="7" height="11" viewBox="0 0 7 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.95579 10.2458C6.14204 10.0584 6.24658 9.80498 6.24658 9.5408C6.24658 9.27661 6.14204 9.02316 5.95579 8.8358L2.41579 5.2458L5.95579 1.7058C6.14204 1.51844 6.24658 1.26498 6.24658 1.0008C6.24658 0.736612 6.14204 0.483161 5.95579 0.295798C5.86283 0.20207 5.75223 0.127675 5.63037 0.0769067C5.50851 0.026138 5.3778 0 5.24579 0C5.11378 0 4.98307 0.026138 4.86121 0.0769067C4.73935 0.127675 4.62875 0.20207 4.53579 0.295798L0.295789 4.5358C0.202061 4.62876 0.127667 4.73936 0.0768978 4.86122C0.0261292 4.98308 -9.32772e-06 5.11379 -9.32772e-06 5.2458C-9.32772e-06 5.37781 0.0261292 5.50852 0.0768978 5.63037C0.127667 5.75223 0.202061 5.86284 0.295789 5.9558L4.53579 10.2458C4.62875 10.3395 4.73935 10.4139 4.86121 10.4647C4.98307 10.5155 5.11378 10.5416 5.24579 10.5416C5.3778 10.5416 5.50851 10.5155 5.63037 10.4647C5.75223 10.4139 5.86283 10.3395 5.95579 10.2458Z" fill="#060C26"/>
|
||||
</svg>
|
After Width: | Height: | Size: 991 B |
|
@ -817,6 +817,10 @@
|
|||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@codexteam/misprints/-/misprints-1.0.0.tgz#e5a7dec7389fe0f176cd51a040d6dc9bdc252086"
|
||||
|
||||
"@codexteam/shortcuts@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@codexteam/shortcuts/-/shortcuts-1.2.0.tgz#b8dd7396962b0bd845a5c8f8f19bc6119b520e19"
|
||||
|
||||
"@cspotcode/source-map-support@^0.8.0":
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue