1
0
Fork 0
mirror of https://github.com/codex-team/codex.docs.git synced 2025-08-06 22:15:23 +02:00

Merge branch 'main' into feature/favicon_changing

This commit is contained in:
slaveeks 2022-07-06 12:15:47 +03:00 committed by GitHub
commit 056f51a857
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 785 additions and 212 deletions

View file

@ -1,5 +1,8 @@
import Page, { PageData } from '../models/page';
import Alias from '../models/alias';
import PagesOrder from './pagesOrder';
import PageOrder from '../models/pageOrder';
import HttpException from "../exceptions/httpException";
type PageDataFields = keyof PageData;
@ -62,6 +65,121 @@ class Pages {
return nullFilteredPages;
}
/**
* Group all pages by their parents
* If the pageId is passed, it excludes passed page from result pages
*
* @param {string} pageId - pageId to exclude from result pages
* @returns {Page[]}
*/
public static async groupByParent(pageId = ''): Promise<Page[]> {
const result: Page[] = [];
const orderGroupedByParent: Record<string, string[]> = {};
const rootPageOrder = await PagesOrder.getRootPageOrder();
const childPageOrder = await PagesOrder.getChildPageOrder();
const orphanPageOrder: PageOrder[] = [];
/**
* If there is no root and child page order, then it returns an empty array
*/
if (!rootPageOrder || (!rootPageOrder && childPageOrder.length <= 0)) {
return [];
}
const pages = (await this.getAll()).reduce((map, _page) => {
map.set(_page._id, _page);
return map;
}, new Map);
const idsOfRootPages = rootPageOrder.order;
/**
* It groups root pages and 1 level pages by its parent
*/
idsOfRootPages.reduce((prev, curr, idx) => {
const childPages:PageOrder[] = [];
childPageOrder.forEach((pageOrder, _idx) => {
if (pageOrder.page === curr) {
childPages.push(pageOrder);
childPageOrder.splice(_idx, 1);
}
});
const hasChildPage = childPages.length > 0;
prev[curr] = [];
prev[curr].push(curr);
/**
* It attaches 1 level page id to its parent page id
*/
if (hasChildPage) {
prev[curr].push(...childPages[0].order);
}
/**
* If non-attached childPages which is not 1 level page still remains,
* It is stored as an orphan page so that it can be processed in the next statements
*/
if (idx === idsOfRootPages.length - 1 && childPageOrder.length > 0) {
orphanPageOrder.push(...childPageOrder);
}
return prev;
}, orderGroupedByParent);
let count = 0;
/**
* It groups remained ungrouped pages by its parent
*/
while (orphanPageOrder.length > 0) {
if (count >= 1000) {
throw new HttpException(500, `Page cannot be processed`);
}
orphanPageOrder.forEach((orphanOrder, idx) => {
// It loops each of grouped orders formatted as [root page id(1): corresponding child pages id(2)]
Object.entries(orderGroupedByParent).forEach(([parentPageId, value]) => {
// If (2) contains orphanOrder's parent id(page)
if (orphanOrder.page && orphanOrder.order && value.includes(orphanOrder.page)) {
// Append orphanOrder's id(order) into its parent id
orderGroupedByParent[parentPageId].splice(value.indexOf(orphanOrder.page) + 1, 0, ...orphanOrder.order);
// Finally, remove orphanOrder from orphanPageOrder
orphanPageOrder.splice(idx, 1);
}
});
});
count += 1;
}
/**
* It converts grouped pages(object) to array
*/
Object.values(orderGroupedByParent).flatMap(arr => [ ...arr ])
.forEach(arr => {
result.push(pages.get(arr));
});
/**
* If the pageId passed, it excludes itself from result pages
* Otherwise just returns result itself
*/
if (pageId) {
return this.removeChildren(result, pageId).reduce((prev, curr) => {
if (curr instanceof Page) {
prev.push(curr);
}
return prev;
}, Array<Page>());
} else {
return result;
}
}
/**
* Set all children elements to null
*
@ -69,7 +187,7 @@ class Pages {
* @param {string} parent - id of parent page
* @returns {Array<?Page>}
*/
public static removeChildren(pagesAvailable: Array<Page|null>, parent: string | undefined): Array<Page | null> {
public static removeChildren(pagesAvailable: Array<Page | null>, parent: string | undefined): Array<Page | null> {
pagesAvailable.forEach(async (item, index) => {
if (item === null || item._parent !== parent) {
return;

View file

@ -33,6 +33,24 @@ class PagesOrder {
return PageOrder.getAll();
}
/**
* Returns only root page's order
*
* @returns {Promise<PageOrder[]>}
*/
public static async getRootPageOrder(): Promise<PageOrder> {
return PageOrder.getRootPageOrder();
}
/**
* Returns only child page's order
*
* @returns {Promise<PageOrder[]>}
*/
public static async getChildPageOrder(): Promise<PageOrder[]> {
return PageOrder.getChildPageOrder();
}
/**
* Pushes the child page to the parent's order list
*

View file

@ -75,6 +75,28 @@ class PageOrder {
return Promise.all(docs.map(doc => new PageOrder(doc)));
}
/**
* Returns only root page's order
*
* @returns {Promise<PageOrder[]>}
*/
public static async getRootPageOrder(): Promise<PageOrder> {
const docs = await db.findOne({ 'page': '0' });
return new PageOrder(docs);
}
/**
* Returns only child page's order
*
* @returns {Promise<PageOrder[]>}
*/
public static async getChildPageOrder(): Promise<PageOrder[]> {
const docs = await this.getAll({ 'page': { $ne: '0' } });
return Promise.all(docs.map(doc => new PageOrder(doc)));
}
/**
* constructor data setter
*

View file

@ -12,10 +12,10 @@ const router = express.Router();
*/
router.get('/page/new', verifyToken, allowEdit, async (req: Request, res: Response, next: NextFunction) => {
try {
const pagesAvailable = await Pages.getAll();
const pagesAvailableGrouped = await Pages.groupByParent();
res.render('pages/form', {
pagesAvailable,
pagesAvailableGrouped,
page: null,
favicon: app.locals.favicon,
});
@ -34,6 +34,7 @@ router.get('/page/edit/:id', verifyToken, allowEdit, async (req: Request, res: R
try {
const page = await Pages.get(pageId);
const pagesAvailable = await Pages.getAllExceptChildren(pageId);
const pagesAvailableGrouped = await Pages.groupByParent(pageId);
if (!page._parent) {
throw new Error('Parent not found');

View file

@ -1,49 +0,0 @@
<div class="docs-aside-toggler" onclick="document.querySelector('.docs-aside').classList.toggle('docs-aside--toggled')">
{{ svg('menu') }} Table of contents
</div>
<div class="docs-aside">
{% for firstLevelPage in menu %}
<section class="docs-aside__section">
<a
{% if page is defined and page._id == firstLevelPage._id%}
class="docs-aside__section-title docs-aside__current"
{% else %}
class="docs-aside__section-title"
{% endif %}
{% if firstLevelPage.uri %}
href="/{{ firstLevelPage.uri }}"
{% else %}
href="/page/{{ firstLevelPage._id }}"
{% endif %}>
{{ firstLevelPage.title | striptags }}
</a>
{% if firstLevelPage.children is not empty %}
<ul class="docs-aside__section-list">
{% for child in firstLevelPage.children %}
<li>
<a
{% if page is defined and page._id == child._id %}
class="docs-aside__current"
{% endif %}
{% if child.uri %}
href="/{{ child.uri }}"
{% else %}
href="/page/{{ child._id }}"
{% endif %}>
{{ child.title | striptags }}
</a>
</li>
{% endfor %}
</ul>
{% endif %}
</section>
{% endfor %}
<a class="docs-aside__logo-wrapper" href="https://github.com/codex-team/codex.docs">
<div class="logo">
{{ svg('aside-logo') }}
</div>
<div class="caption">
<span>Powered by CodeX Docs</span>
</div>
</a>
</div>

View file

@ -4,9 +4,12 @@
</a>
<ul class="docs-header__menu">
{% if isAuthorized == true %}
<li class="docs-header__menu-add">
<li class="docs-header__menu-add docs-header__menu-add--desktop">
{% include 'components/button.twig' with {label: 'Add page', icon: 'plus', size: 'small', url: '/page/new'} %}
</li>
<li class="docs-header__menu-add docs-header__menu-add--mobile">
{% include 'components/button.twig' with {icon: 'plus', size: 'small', url: '/page/new'} %}
</li>
{% endif %}
{% for option in config.menu %}
<li>

View file

@ -0,0 +1,55 @@
<div data-module="sidebar" class="docs-sidebar">
<div class="docs-sidebar__toggler">
{{ svg('menu') }} Table of contents
</div>
<aside class="docs-sidebar__content docs-sidebar__content--invisible">
{% for firstLevelPage in menu %}
<section class="docs-sidebar__section" data-id="{{firstLevelPage._id}}">
<a class="docs-sidebar__section-title-wrapper"
href="{{firstLevelPage.uri ? '/' ~ firstLevelPage.uri : '/page/' ~ firstLevelPage._id }}"
>
<div class="docs-sidebar__section-title {{page is defined and page._id == firstLevelPage._id ? 'docs-sidebar__section-title--active' : ''}}">
<span>
{{ firstLevelPage.title | striptags }}
</span>
{% if firstLevelPage.children is not empty %}
<button class="docs-sidebar__section-toggler">
{{ svg('arrow-up') }}
</button>
{% endif %}
</div>
</a>
{% if firstLevelPage.children is not empty %}
<ul class="docs-sidebar__section-list">
{% for child in firstLevelPage.children %}
<li>
<a
class="docs-sidebar__section-list-item-wrapper"
href="{{ child.uri ? '/' ~ child.uri : '/page/' ~ child._id }}">
<div class="docs-sidebar__section-list-item {{page is defined and page._id == child._id ? 'docs-sidebar__section-list-item--active' : ''}}">
<span>{{ child.title | striptags }}</span>
</div>
</a>
</li>
{% endfor %}
</ul>
{% endif %}
</section>
{% endfor %}
<div class="docs-sidebar__logo">
<a class="docs-sidebar__logo-wrapper" href="https://github.com/codex-team/codex.docs">
<div class="docs-sidebar__logo-image">
{{ svg('aside-logo') }}
</div>
<p class="docs-sidebar__logo-caption">
Powered by CodeX Docs
</p>
</a>
</div>
</aside>
</div>

View file

@ -17,13 +17,18 @@
<body>
{% include "components/header.twig" with res.locals.isAuthorized %}
<div class="docs">
<aside class="docs__aside">
{% include "components/aside.twig" %}
</aside>
{% include "components/sidebar.twig" %}
<div class="docs__content">
<div class="docs__content-inner">
{% block body %}{% endblock %}
</div>
<aside class="docs__aside-right">
<div style="width: 100%; height: 100px; background: grey"/>
</aside>
</div>
</div>
<script src="/dist/main.bundle.js"></script>

View file

@ -22,7 +22,7 @@
{% endif %}
<select name="parent">
<option value="0">Root</option>
{% for _page in pagesAvailable %}
{% for _page in pagesAvailableGrouped %}
{% if _page._id != currentPageId %}
<option value="{{ _page._id }}" {{ page is not empty and page._parent == _page._id ? 'selected' : ''}}>
{% if _page._parent != "0" %}

View file

@ -16,6 +16,7 @@ import ModuleDispatcher from 'module-dispatcher';
import Writing from './modules/writing';
import Page from './modules/page';
import Extensions from './modules/extensions';
import Sidebar from './modules/sidebar';
/**
* Main app class
@ -28,6 +29,7 @@ class Docs {
this.writing = new Writing();
this.page = new Page();
this.extensions = new Extensions();
this.sidebar = new Sidebar();
document.addEventListener('DOMContentLoaded', (event) => {
this.docReady();

View file

@ -0,0 +1,179 @@
import { Storage } from '../utils/storage';
/**
* Local storage key
*/
const LOCAL_STORAGE_KEY = 'docs_sidebar_state';
/**
* Section list item height in px
*/
const ITEM_HEIGHT = 31;
/**
* Sidebar module
*/
export default class Sidebar {
/**
* CSS classes
*
* @returns {Record<string, string>}
*/
static get CSS() {
return {
toggler: 'docs-sidebar__section-toggler',
section: 'docs-sidebar__section',
sectionCollapsed: 'docs-sidebar__section--collapsed',
sectionAnimated: 'docs-sidebar__section--animated',
sectionTitle: 'docs-sidebar__section-title',
sectionTitleActive: 'docs-sidebar__section-title--active',
sectionList: 'docs-sidebar__section-list',
sectionListItemActive: 'docs-sidebar__section-list-item--active',
sidebarToggler: 'docs-sidebar__toggler',
sidebarContent: 'docs-sidebar__content',
sidebarContentHidden: 'docs-sidebar__content--hidden',
sidebarContentInvisible: 'docs-sidebar__content--invisible',
};
}
/**
* Creates base properties
*/
constructor() {
/**
* Stores refs to HTML elements needed for correct sidebar work
*/
this.nodes = {
sections: [],
sidebarContent: null,
toggler: null,
};
this.sidebarStorage = new Storage(LOCAL_STORAGE_KEY);
const storedState = this.sidebarStorage.get();
this.sectionsState = storedState ? JSON.parse(storedState) : {};
}
/**
* Called by ModuleDispatcher to initialize module from DOM
*
* @param {writingSettings} settings - module settings
* @param {HTMLElement} moduleEl - module element
*/
init(settings, 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.ready();
}
/**
* Initializes sidebar sections: applies stored state and adds event listeners
*
* @param {HTMLElement} section
* @returns {void}
*/
initSection(section) {
const id = section.dataset.id;
const togglerEl = section.querySelector('.' + Sidebar.CSS.toggler);
if (!togglerEl) {
return;
}
togglerEl.addEventListener('click', e => this.handleSectionTogglerClick(id, section, e));
if (typeof this.sectionsState[id] === 'undefined') {
this.sectionsState[id] = false;
}
if (this.sectionsState[id]) {
this.setSectionCollapsed(section, true, false);
}
/**
* Calculate and set sections list max height for smooth animation
*/
const sectionList = section.querySelector('.' + Sidebar.CSS.sectionList);
if (!sectionList) {
return;
}
const itemsCount = sectionList.children.length;
sectionList.style.maxHeight = `${ itemsCount * ITEM_HEIGHT }px`;
}
/**
* Toggles section expansion
*
* @param {number} sectionId - id of the section to toggle
* @param {HTMLElement} sectionEl - section html element
* @param {MouseEvent} event - click event
* @returns {void}
*/
handleSectionTogglerClick(sectionId, sectionEl, event) {
event.preventDefault();
this.sectionsState[sectionId] = !this.sectionsState[sectionId];
this.sidebarStorage.set(JSON.stringify(this.sectionsState));
this.setSectionCollapsed(sectionEl, this.sectionsState[sectionId]);
}
/**
* Updates section's collapsed state
*
* @param {HTMLElement} sectionEl - element of the section to toggle
* @param {boolean} collapsed - new collapsed state
* @param {boolean} [animated] - true if state should change with animation
*/
setSectionCollapsed(sectionEl, collapsed, animated = true) {
const sectionList = sectionEl.querySelector('.' + Sidebar.CSS.sectionList);
if (!sectionList) {
return;
}
sectionEl.classList.toggle(Sidebar.CSS.sectionAnimated, animated);
sectionEl.classList.toggle(Sidebar.CSS.sectionCollapsed, collapsed);
/**
* Highlight section item as active if active child item is collapsed.
*/
const activeSectionListItem = sectionList.querySelector('.' + Sidebar.CSS.sectionListItemActive);
const sectionTitle = sectionEl.querySelector('.' + Sidebar.CSS.sectionTitle);
if (!activeSectionListItem) {
return;
}
if (collapsed && animated) {
/**
* Highlights section title as active with a delay to let section collapse animation finish first
*/
setTimeout(() => {
sectionTitle.classList.toggle(Sidebar.CSS.sectionTitleActive, collapsed);
}, 200);
} else {
sectionTitle.classList.toggle(Sidebar.CSS.sectionTitleActive, collapsed);
}
}
/**
* Toggles sidebar visibility
*
* @returns {void}
*/
toggleSidebar() {
this.nodes.sidebarContent.classList.toggle(Sidebar.CSS.sidebarContentHidden);
}
/**
* Displays sidebar when ready
*
* @returns {void}
*/
ready() {
this.nodes.sidebarContent.classList.remove(Sidebar.CSS.sidebarContentInvisible);
}
}

View file

@ -0,0 +1,30 @@
/**
* Utility class to handle interaction with local storage
*/
export class Storage {
/**
* @param {string} key - storage key
*/
constructor(key) {
this.key = key;
}
/**
* Saves value to storage
*
* @param {string} value - value to be saved
*/
set(value) {
localStorage.setItem(this.key, value);
}
/**
* Retreives value from storage
*
* @returns {string}
*/
get() {
return localStorage.getItem(this.key);
}
}

View file

@ -1,92 +0,0 @@
.docs-aside {
font-size: 14px;
letter-spacing: 0.01em;
@media (--mobile) {
position: static;
overflow: visible;
max-height: none;
font-size: 13px;
display: none;
margin-top: 20px;
}
&--toggled {
display: block !important;
}
a {
color: inherit;
text-decoration: none;
&:hover {
color: var(--color-link-active);
}
}
&__section {
margin-bottom: 30px;
@media (--mobile) {
margin-bottom: 20px;
}
&:last-of-type {
margin-bottom: 0;
}
&-title {
display: inline-block;
margin-bottom: 10px;
font-size: 1.18em;
font-weight: 600;
@media (--mobile) {
margin-bottom: 10px;
}
}
&-list {
padding-left: 0;
margin: 0;
list-style: none;
a {
display: inline-block;
color: inherit;
padding: 8px 0;
line-height: 1.5em;
}
}
}
&__current {
color: var(--color-page-active) !important;
}
&__logo-wrapper {
margin-top: 5rem;
display: flex;
align-items: center;
gap: 18px;
.logo {
display: inline-flex;
}
}
}
.docs-aside-toggler {
display: none;
font-size: 13px;
cursor: pointer;
color: var(--color-text-second);
@media (--mobile) {
display: block;
}
svg {
margin-right: 10px;
}
}

View file

@ -12,12 +12,9 @@
cursor: pointer;
transition-property: background-color;
transition-duration: 0.1s;
border-radius: 8px;
@supports(-webkit-mask-box-image: url('')){
border-radius: 0;
-webkit-mask-box-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 10.3872C0 1.83334 1.83334 0 10.3872 0H13.6128C22.1667 0 24 1.83334 24 10.3872V13.6128C24 22.1667 22.1667 24 13.6128 24H10.3872C1.83334 24 0 22.1667 0 13.6128V10.3872Z' fill='black'/%3E%3C/svg%3E%0A") 48% 41% 37.9% 53.3%;;
}
@apply --squircle;
&__icon {
display: inline-flex;

View file

@ -7,6 +7,12 @@
font-size: 18px;
flex-wrap: wrap;
position: relative;
height: var(--layout-height-header);
box-sizing: border-box;
position: sticky;
top: 0;
background: white;
z-index: 10;
@media (--mobile){
line-height: 40px;
@ -29,9 +35,10 @@
&__menu-link {
padding: 4px 10px;
font-weight: 500;
border-radius: 8px;
transition: background-color .13s;
@apply --squircle;
&:hover {
background-color: var(--color-link-hover);
}
@ -60,25 +67,24 @@
}
}
&-add {
@media (--mobile) {
position: absolute;
right: 15px;
top: 15px;
line-height: 1em;
margin: 0 !important;
}
a {
li&-add {
flex-shrink: 0;
&--desktop {
@media (--mobile) {
font-size: 0;
padding: 0 4px;
margin-right: 0;
display: none;
}
}
.docs-button__icon {
&--mobile {
display: none;
@media (--mobile) {
margin-right: 0;
display: block;
position: absolute;
right: 15px;
top: 15px;
line-height: 1em;
}
}
}

View file

@ -0,0 +1,220 @@
.docs-sidebar {
width: 100vw;
@media (--desktop) {
width: var(--layout-sidebar-width);
}
&__content {
border-bottom: 1px solid var(--color-line-gray);
box-sizing: border-box;
padding: var(--layout-padding-vertical) var(--layout-padding-horizontal);
position: sticky;
top: var(--layout-height-header);
display: flex;
flex-direction: column;
overflow: auto;
@media (--desktop) {
height: calc(100vh - var(--layout-height-header));
border-right: 1px solid var(--color-line-gray);
border-bottom: 0;
padding-bottom: 0;
}
&--hidden {
display: none;
}
&--invisible {
visibility: hidden;
}
}
&__section {
overflow: hidden;
flex-shrink: 0;
&--animated {
.docs-sidebar__section-list {
transition: max-height 200ms ease-in-out;
}
.docs-sidebar__section-toggler {
svg {
transition: transform 200ms ease-in;
}
}
}
&--collapsed {
.docs-sidebar__section-list {
max-height: 0 !important;
}
.docs-sidebar__section-toggler {
svg {
transform: rotate(180deg);
}
}
}
}
&__section:not(:first-child) {
margin-top: 19px;
}
&__section-title {
font-size: 16px;
line-height: 24px;
font-weight: 700;
z-index: 2;
position: relative;
height: 34px;
}
&__section-list-item {
font-size: 14px;
line-height: 21px;
height: 29px;
}
&__section-title,
&__section-list-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 8px;
transition-property: background-color;
transition-duration: 0.1s;
@apply --squircle;
}
&__section-title > span,
&__section-list-item > span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&__section-title-wrapper {
&:not(:last-child) {
padding-bottom: 1px;
display: block;
}
}
&__section-list-item-wrapper {
padding: 1px 0;
display: block;
}
li:last-child {
.docs-sidebar__section-list-item-wrapper {
padding-bottom: 0;
}
}
&__section-title:not(&__section-title--active),
&__section-list-item:not(&__section-list-item--active) {
@media (--can-hover) {
&:hover {
background: var(--color-link-hover);
}
}
}
&__section-title--active,
&__section-list-item--active {
background: linear-gradient(270deg, #129BFF 0%, #8A53FF 100%);
color: white;
@media (--can-hover) {
.docs-sidebar__section-toggler:hover {
background: rgba(0,0,0,0.3);
}
}
}
&__section-list {
list-style: none;
padding: 0;
margin: 0;
z-index: 1;
position: relative;
}
&__section-toggler {
color: inherit;
background: transparent;
padding: 0;
border: 0;
cursor: pointer;
width: 24px;
height: 24px;
transition-property: background-color;
transition-duration: 0.1s;
@apply --squircle;
@media (--can-hover) {
&:hover {
background: white;
}
}
svg {
display: block;
}
}
&__toggler {
font-size: 13px;
cursor: pointer;
color: var(--color-text-second);
padding: 20px 15px;
border-bottom: 1px solid var(--color-line-gray);
@media (--desktop) {
display: none;
}
svg {
margin-right: 10px;
}
}
&__logo {
display: none;
margin-top: auto;
background: white;
z-index: 2;
padding-bottom: 20px;
padding-top: 60px;
font-size: 14px;
@media (--desktop) {
display: block;
}
&-image{
display: inline-flex;
}
&-caption {
margin: 0;
}
&-wrapper {
display: flex;
align-items: center;
gap: 18px;
padding: 8px;
}
}
}

View file

@ -53,10 +53,6 @@
line-height: 1.6;
letter-spacing: 0.005em;
@media (--desktop) {
margin: 0 -100px;
}
.ce-code__textarea {
color: #41314e;
line-height: 1.6em;

View file

@ -1,27 +1,15 @@
.docs {
display: flex;
padding: 0 var(--layout-padding-horizontal);
min-height: calc(100vh - var(--layout-height-header));
@media (--mobile) {
flex-wrap: wrap;
}
&__aside {
width: var(--layout-width-aside);
@media (--mobile) {
width: 100%;
flex-basis: 100%;
padding: 20px var(--layout-padding-horizontal) !important;
margin: 0 calc(-1 * var(--layout-padding-horizontal));
border-bottom: 1px solid var(--color-line-gray);
}
@media (--desktop) {
display: flex;
}
&__content {
flex-grow: 2;
word-wrap: break-word;
@media (--mobile) {
width: 100%;
flex-basis: 100%;
@ -30,15 +18,43 @@
&-inner {
max-width: var(--layout-width-main-col);
margin: 0 auto;
@media (--desktop) {
margin-right: var(--layout-padding-horizontal);
margin-left: 0;
}
}
}
&__aside,
&__content {
padding: var(--layout-padding-vertical) 0;
--max-space-between-cols: 160px;
padding: var(--layout-padding-vertical) var(--layout-padding-horizontal);
display: flex;
justify-content: space-between;
max-width: calc(var(--layout-width-main-col) + var(--max-space-between-cols) + var(--layout-sidebar-width));
margin-left: max(0px, calc(50vw - var(--layout-sidebar-width) - var(--layout-width-main-col) / 2));
margin-right: auto;
@media (--mobile) {
padding: 20px 0;
padding: 20px var(--layout-padding-horizontal);
}
}
&__aside-right {
width: var(--layout-sidebar-width);
min-width: 160px;
display: none;
position: sticky;
top: calc(var(--layout-height-header) + var(--layout-padding-vertical));
align-self: flex-start;
@media (--desktop) {
display: block;
}
}
&-sidebar {
flex-shrink: 0;
}
}

View file

@ -3,12 +3,12 @@
@import './layout.pcss';
@import './carbon.pcss';
@import './components/header.pcss';
@import './components/aside.pcss';
@import './components/writing.pcss';
@import './components/page.pcss';
@import './components/landing.pcss';
@import './components/auth.pcss';
@import './components/button.pcss';
@import './components/sidebar.pcss';
body {
font-family: system-ui, Helvetica, Arial, Verdana;

View file

@ -23,14 +23,20 @@
/**
* Site layout sizes
*/
--layout-padding-horizontal: 16px;
--layout-padding-horizontal: 22px;
--layout-padding-vertical: 30px;
--layout-width-aside: 300px;
--layout-sidebar-width: 290px;
--layout-width-main-col: 650px;
--layout-height-header: 56px;
@media (--mobile) {
--layout-padding-horizontal: 15px;
--layout-padding-vertical: 15px;
--layout-height-header: 88px;
}
@media (--wide-desktop) {
--layout-sidebar-width: 344px;
}
--font-mono: Menlo,Monaco,Consolas,Courier New,monospace;
@ -70,6 +76,15 @@
background: color-mod(var(--color-link-active) blackness(+10%));
}
}
--squircle {
border-radius: 8px;
@supports(-webkit-mask-box-image: url('')){
border-radius: 0;
-webkit-mask-box-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 10.3872C0 1.83334 1.83334 0 10.3872 0H13.6128C22.1667 0 24 1.83334 24 10.3872V13.6128C24 22.1667 22.1667 24 13.6128 24H10.3872C1.83334 24 0 22.1667 0 13.6128V10.3872Z' fill='black'/%3E%3C/svg%3E%0A") 48% 41% 37.9% 53.3%;;
}
}
}
/**
@ -80,3 +95,4 @@
@custom-media --tablet all and (min-width: 980px) and (max-width: 1050px);
@custom-media --mobile all and (max-width: 980px);
@custom-media --retina all and (-webkit-min-device-pixel-ratio: 1.5);
@custom-media --can-hover all and (hover:hover)

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M7.29571 15.3171C7.48307 15.5034 7.73652 15.6079 8.00071 15.6079C8.26489 15.6079 8.51834 15.5034 8.70571 15.3171L12.2957 11.7771L15.8357 15.3171C16.0231 15.5034 16.2765 15.6079 16.5407 15.6079C16.8049 15.6079 17.0583 15.5034 17.2457 15.3171C17.3394 15.2242 17.4138 15.1136 17.4646 14.9917C17.5154 14.8698 17.5415 14.7391 17.5415 14.6071C17.5415 14.4751 17.5154 14.3444 17.4646 14.2225C17.4138 14.1007 17.3394 13.9901 17.2457 13.8971L13.0057 9.65712C12.9127 9.56339 12.8021 9.48899 12.6803 9.43823C12.5584 9.38746 12.4277 9.36132 12.2957 9.36132C12.1637 9.36132 12.033 9.38746 11.9111 9.43823C11.7893 9.48899 11.6787 9.56339 11.5857 9.65712L7.29571 13.8971C7.20198 13.9901 7.12758 14.1007 7.07681 14.2225C7.02605 14.3444 6.99991 14.4751 6.99991 14.6071C6.99991 14.7391 7.02605 14.8698 7.07681 14.9917C7.12758 15.1136 7.20198 15.2242 7.29571 15.3171Z"/>
</svg>

After

Width:  |  Height:  |  Size: 954 B

View file

@ -146,4 +146,31 @@ describe('PageOrder model', () => {
await pageOrder.destroy();
});
it('Testing get parents and children order methods', async () => {
const parentTestData = {
page: '0',
order: ['1', '2', '3', '4', '5'],
};
const childTestData = {
page: 'child',
order: ['a', 'b', 'c', 'd', 'e'],
};
const parentOrder = new PageOrder(parentTestData);
const childOrder = new PageOrder(childTestData);
const insertedParentOrder = await parentOrder.save();
const insertedChildOrder = await childOrder.save();
const fetchedParentOrder = await PageOrder.getRootPageOrder();
const fetchedChildOrder = await PageOrder.getChildPageOrder();
expect(fetchedParentOrder.page).to.deep.equals(parentTestData.page);
expect(fetchedParentOrder.order).to.deep.equal(parentTestData.order);
expect(fetchedChildOrder).to.be.an('array').that.is.length(1);
expect(fetchedChildOrder[0].page).to.deep.equals(childTestData.page);
expect(fetchedChildOrder[0].order).to.deep.equals(childTestData.order);
await insertedParentOrder.destroy();
await insertedChildOrder.destroy();
});
});

View file

@ -1369,9 +1369,9 @@
form-data "^3.0.0"
"@types/node@*":
version "17.0.23"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da"
integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==
version "18.0.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.0.tgz#67c7b724e1bcdd7a8821ce0d5ee184d3b4dd525a"
integrity sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==
"@types/node@^16.4.1":
version "16.11.26"
@ -2212,7 +2212,7 @@ clone-deep@^4.0.1:
clone-response@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
integrity sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==
dependencies:
mimic-response "^1.0.0"
@ -2302,10 +2302,10 @@ component-emitter@^1.2.0:
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
compress-brotli@^1.3.6:
version "1.3.6"
resolved "https://registry.yarnpkg.com/compress-brotli/-/compress-brotli-1.3.6.tgz#64bd6f21f4f3e9841dbac392f4c29218caf5e9d9"
integrity sha512-au99/GqZtUtiCBliqLFbWlhnCxn+XSYjwZ77q6mKN4La4qOXDoLVPZ50iXr0WmAyMxl8yqoq3Yq4OeQNPPkyeQ==
compress-brotli@^1.3.8:
version "1.3.8"
resolved "https://registry.yarnpkg.com/compress-brotli/-/compress-brotli-1.3.8.tgz#0c0a60c97a989145314ec381e84e26682e7b38db"
integrity sha512-lVcQsjhxhIXsuupfy9fmZUFtAIdBmXA7EGY6GBdgZ++qkM9zG4YFT8iU7FoBxzryNDMOpD1HIFHUSX4D87oqhQ==
dependencies:
"@types/json-buffer" "~3.0.0"
json-buffer "~3.0.1"
@ -3586,9 +3586,9 @@ gonzales-pe@^4.0.3:
minimist "^1.2.5"
got@^11.8.2:
version "11.8.3"
resolved "https://registry.yarnpkg.com/got/-/got-11.8.3.tgz#f496c8fdda5d729a90b4905d2b07dbd148170770"
integrity sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==
version "11.8.5"
resolved "https://registry.yarnpkg.com/got/-/got-11.8.5.tgz#ce77d045136de56e8f024bebb82ea349bc730046"
integrity sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==
dependencies:
"@sindresorhus/is" "^4.0.0"
"@szmarczak/http-timer" "^4.0.5"
@ -4176,11 +4176,11 @@ jws@^3.2.2:
safe-buffer "^5.0.1"
keyv@^4.0.0:
version "4.2.2"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.2.2.tgz#4b6f602c0228ef4d8214c03c520bef469ed6b768"
integrity sha512-uYS0vKTlBIjNCAUqrjlxmruxOEiZxZIHXyp32sdcGmP+ukFrmWUnE//RcPXJH3Vxrni1H2gsQbjHE0bH7MtMQQ==
version "4.3.2"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.3.2.tgz#e839df676a0c7ee594c8835e7c1c83742558e5c2"
integrity sha512-kn8WmodVBe12lmHpA6W8OY7SNh6wVR+Z+wZESF4iF5FCazaVXGWOtnbnvX0tMQ1bO+/TmOD9LziuYMvrIIs0xw==
dependencies:
compress-brotli "^1.3.6"
compress-brotli "^1.3.8"
json-buffer "3.0.1"
kind-of@^6.0.2:
@ -4847,7 +4847,7 @@ on-headers@~1.0.2:
once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
dependencies:
wrappy "1"
@ -6877,7 +6877,7 @@ wrap-ansi@^7.0.0:
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
write-file-atomic@^2.4.2:
version "2.4.3"