Basic adding of pages works
This commit is contained in:
parent
27b7e3afec
commit
e3ddcefb30
|
@ -18,17 +18,16 @@
|
|||
"block": {
|
||||
"type": "json"
|
||||
},
|
||||
"public": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"required": false
|
||||
},
|
||||
"path": {
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"regex": "^(\\/|(\\/[A-z0-9\\-]+)+)$",
|
||||
"unique": true,
|
||||
"default": "/"
|
||||
"required": false,
|
||||
"regex": "[A-z0-9\\-]*",
|
||||
"unique": false
|
||||
},
|
||||
"parent": {
|
||||
"type": "relation",
|
||||
"relation": "oneToOne",
|
||||
"target": "api::page.page"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
.sb-block-ordering {
|
||||
font-family: 'Montserrat';
|
||||
display: flex;
|
||||
position: absolute;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
.sb-block-picker {
|
||||
font-family: 'Montserrat';
|
||||
|
||||
&__add-button {
|
||||
padding: 24px 32px;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
.sb-block-placeholder {
|
||||
flex-basis: 100%;
|
||||
flex-shrink: 2;
|
||||
font-family: 'Montserrat';
|
||||
position: relative;
|
||||
flex-basis: 1rem;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
overflow: visible;
|
||||
|
||||
&__add {
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
.sb-block-toolbar {
|
||||
font-family: 'Montserrat';
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
.sb-button {
|
||||
font-family: 'Montserrat';
|
||||
border: 0;
|
||||
padding: 8px 12px;
|
||||
background-color: var(--grey-0);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
}
|
||||
|
||||
.sb-context-menu {
|
||||
font-family: 'Montserrat';
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
background: var(--grey-0);
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
$sb-style-root: '@schlechtenburg/style';
|
||||
@import '@schlechtenburg/style/scss/montserrat.scss';
|
||||
|
||||
.sb-main {
|
||||
position: relative;
|
||||
color: var(--fg);
|
||||
background-color: var(--bg);
|
||||
padding: 0;
|
||||
transition: padding 0.2s ease;
|
||||
overflow: hidden;
|
||||
|
||||
--grey-0: white;
|
||||
--grey-1-t: rgba(0, 0, 0, 0.05);
|
||||
|
@ -25,6 +31,7 @@
|
|||
--z-toolbar: 2000;
|
||||
--z-context-menu: 3000;
|
||||
--z-tree-block-select: 4000;
|
||||
--z-main-menu: 5000;
|
||||
--z-modal: 10000;
|
||||
|
||||
*,
|
||||
|
@ -32,4 +39,32 @@
|
|||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&_edit {
|
||||
padding: 0rem 3rem 3rem 3rem;
|
||||
}
|
||||
|
||||
&--menu {
|
||||
opacity: 1;
|
||||
margin-bottom: 3rem;
|
||||
transform: none;
|
||||
|
||||
&-enter-active,
|
||||
&-leave-active {
|
||||
transition-property: opacity, margin-bottom, transform;
|
||||
transition-duration: 0.1s;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
|
||||
&-enter-active {
|
||||
transition-delay: 0s 0s 0.1s;
|
||||
}
|
||||
|
||||
&-enter-from,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
margin-bottom: 0;
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import {
|
|||
shallowReactive,
|
||||
ref,
|
||||
watch,
|
||||
computed,
|
||||
Transition,
|
||||
PropType,
|
||||
Ref,
|
||||
} from 'vue';
|
||||
|
@ -26,7 +28,6 @@ import { SymEditorDimensions, useResizeObserver } from '../use-resize-observer';
|
|||
import { SymActiveBlock } from '../use-activation';
|
||||
|
||||
import { SbMainMenu } from './MainMenu';
|
||||
import { SbBlockToolbar } from './BlockToolbar';
|
||||
import { SbBlock } from './Block';
|
||||
|
||||
export interface ISbMainProps {
|
||||
|
@ -79,6 +80,11 @@ export const SbMain = defineComponent({
|
|||
mode.value = newMode;
|
||||
});
|
||||
|
||||
const classes = computed(() => ({
|
||||
'sb-main': true,
|
||||
[`sb-main_${mode.value}`]: true,
|
||||
}));
|
||||
|
||||
const activeBlock = ref(null);
|
||||
provide(SymActiveBlock, activeBlock);
|
||||
|
||||
|
@ -98,18 +104,22 @@ export const SbMain = defineComponent({
|
|||
|
||||
return () => (
|
||||
<div
|
||||
class="sb-main"
|
||||
class={classes.value}
|
||||
ref={el}
|
||||
>
|
||||
{
|
||||
mode.value === SbMode.Edit
|
||||
? <>
|
||||
<SbMainMenu block={props.block} />
|
||||
<SbBlockToolbar />
|
||||
</>
|
||||
: null
|
||||
}
|
||||
<Transition
|
||||
name="sb-main--menu"
|
||||
mode="out-in"
|
||||
>
|
||||
{mode.value === SbMode.Edit
|
||||
? <SbMainMenu
|
||||
block={props.block}
|
||||
class="sb-main--menu"
|
||||
/>
|
||||
: null}
|
||||
</Transition>
|
||||
<SbBlock
|
||||
class="sb-main--block"
|
||||
block={props.block}
|
||||
eventUpdate={props.eventUpdate}
|
||||
/>
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
.sb-main-menu {
|
||||
font-family: 'Montserrat';
|
||||
display: flex;
|
||||
padding-bottom: 4rem;
|
||||
background-color: var(--grey-0);
|
||||
position: sticky;
|
||||
z-index: var(--z-main-menu);
|
||||
top: 0;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
PropType,
|
||||
} from 'vue';
|
||||
import { IBlockData } from '../types';
|
||||
import { SbBlockToolbar } from './BlockToolbar';
|
||||
import { SbTreeBlockSelect } from './TreeBlockSelect';
|
||||
|
||||
import './MainMenu.scss';
|
||||
|
@ -21,6 +22,7 @@ export const SbMainMenu = defineComponent({
|
|||
return () => (
|
||||
<div class="sb-main-menu">
|
||||
<SbTreeBlockSelect />
|
||||
<SbBlockToolbar />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
.sb-missing-block {
|
||||
font-family: 'Montserrat';
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
.sb-modal {
|
||||
font-family: 'Montserrat';
|
||||
|
||||
&__overlay {
|
||||
background-color: var(--grey-3-t);
|
||||
position: fixed;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
.sb-select {
|
||||
font-family: 'Montserrat';
|
||||
background-color: var(--grey-0);
|
||||
border: 1px solid var(--grey-2);
|
||||
position: relative;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
.sb-toolbar {
|
||||
font-family: 'Montserrat';
|
||||
position: absolute;
|
||||
width: auto;
|
||||
height: auto;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
.sb-tree-block-select {
|
||||
font-family: 'Montserrat';
|
||||
|
||||
&__list {
|
||||
list-style: none;
|
||||
color: var(--fg);
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
"url": "git@git.b12f.io:b12f/schlechtenburg.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@schlechtenburg/style": "^0.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
|
|
|
@ -9,8 +9,39 @@ body {
|
|||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
|
||||
--nav-width: 60px;
|
||||
--nav-expanded-width: 320px;
|
||||
--ex-nav-width: 60px;
|
||||
--ex-nav-expanded-width: 320px;
|
||||
|
||||
--interact: #3f9cff;
|
||||
--interact-lite: #3f9cff;
|
||||
|
||||
--grey-0: white;
|
||||
--grey-1-t: rgba(0, 0, 0, 0.05);
|
||||
--grey-1: rgb(242, 242, 242);
|
||||
--grey-2-t: rgba(0, 0, 0, 0.1);
|
||||
--grey-2: rgb(230, 230, 230);
|
||||
--grey-3-t: rgba(0, 0, 0, 0.2);
|
||||
--grey-3: rgb(205, 205, 205);
|
||||
--grey-4-t: rgba(0, 0, 0, 0.4);
|
||||
--grey-4: rgb(155, 155, 155);
|
||||
--grey-5-t: rgba(0, 0, 0, 0.7);
|
||||
--grey-5: rgb(75, 75, 75);
|
||||
--black: rgba(0, 0, 0, 0.9);
|
||||
|
||||
--bg: var(--grey-1);
|
||||
--fg: var(--black);
|
||||
|
||||
--interact: #3f9cff;
|
||||
--interact-lite: #3f9cff;
|
||||
|
||||
--z-toolbar: 2000;
|
||||
--z-context-menu: 3000;
|
||||
--z-tree-block-select: 4000;
|
||||
--z-modal: 10000;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&--admin-nav {
|
||||
z-index: 100;
|
||||
|
@ -22,15 +53,19 @@ body {
|
|||
|
||||
&--page {
|
||||
flex-basis: 100%;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: var(--ex-nav-width);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1000px) {
|
||||
--nav-width: var(--nav-expanded-width);
|
||||
--ex-nav-width: var(--ex-nav-expanded-width);
|
||||
|
||||
&--admin-nav {
|
||||
position: unset;
|
||||
width: unset;
|
||||
flex-basis: var(--nav-width);
|
||||
flex-basis: var(--ex-nav-width);
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
|
|
@ -12,50 +12,23 @@ export default defineComponent({
|
|||
|
||||
const loggedIn = computed(() => !!me.value?.id);
|
||||
|
||||
const { page, setPage } = usePage();
|
||||
const block = page.value?.attributes?.block;
|
||||
const { currentPage } = useCurrentPage();
|
||||
const block = computed(() => currentPage.value?.attributes?.block);
|
||||
|
||||
const {
|
||||
mode,
|
||||
draft,
|
||||
updateDraft,
|
||||
revision,
|
||||
} = useEditor();
|
||||
|
||||
watch(revision, async () => {
|
||||
const { data, error } = await useAsyncGql(
|
||||
'updatePage',
|
||||
{
|
||||
id: page.value?.id || '',
|
||||
data: {
|
||||
block: draft.value!
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (error.value) {
|
||||
console.error('Error updating page!');
|
||||
console.error('error:', error.value);
|
||||
console.error('data:', data.value);
|
||||
return;
|
||||
}
|
||||
|
||||
setPage(data.value?.updatePage?.data?.attributes?.block);
|
||||
updateDraft(data.value?.updatePage?.data?.attributes?.block);
|
||||
watchEffect(() => {
|
||||
updateDraft(block.value!);
|
||||
});
|
||||
|
||||
watch(mode, async (newMode) => {
|
||||
if (newMode === SbMode.View) {
|
||||
updateDraft(block!);
|
||||
}
|
||||
});
|
||||
|
||||
updateDraft(block!);
|
||||
|
||||
|
||||
if (!block) {
|
||||
console.error('No block!');
|
||||
console.error('page', page.value);
|
||||
console.error('page', currentPage.value);
|
||||
}
|
||||
|
||||
return () => (
|
||||
|
@ -74,7 +47,7 @@ export default defineComponent({
|
|||
SbImage,
|
||||
]}
|
||||
/>
|
||||
: <div class="ex-page ex-page_corrupt">Corrupt page: {page.value?.attributes?.path} ({page.value?.id})</div>}
|
||||
: <div class="ex-page ex-page_corrupt">Corrupt page: {currentPage.value?.attributes?.slug} ({currentPage.value?.id})</div>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
3
packages/example-site/components/PageBreadcrumb.scss
Normal file
3
packages/example-site/components/PageBreadcrumb.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
.ex-page-breadcrumb {
|
||||
display: flex;
|
||||
}
|
27
packages/example-site/components/PageBreadcrumb.tsx
Normal file
27
packages/example-site/components/PageBreadcrumb.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { ComputedRef, defineComponent } from 'vue';
|
||||
import { NuxtLink } from '#components';
|
||||
import { IPage } from '~~/composables/pages';
|
||||
|
||||
import './PageBreadcrumb.scss';
|
||||
|
||||
export default defineComponent({
|
||||
async setup() {
|
||||
const { currentPage } = useCurrentPage();
|
||||
const { pages } = usePages();
|
||||
|
||||
const parents:ComputedRef<IPage[]> = computed(() => currentPage.value ? getPageParents(currentPage.value, pages.value, [currentPage.value]) : []);
|
||||
|
||||
return () => {
|
||||
return (<div class="ex-page-breadcrumb">
|
||||
{...parents.value.map((parent) => (
|
||||
<div class="ex-page-breadcrumb--crumb">
|
||||
/
|
||||
<NuxtLink to={getPagePath(parent, pages.value)}>
|
||||
{parent?.attributes?.slug}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
))}
|
||||
</div>);
|
||||
};
|
||||
},
|
||||
});
|
7
packages/example-site/components/PageToolbar.scss
Normal file
7
packages/example-site/components/PageToolbar.scss
Normal file
|
@ -0,0 +1,7 @@
|
|||
.ex-page-toolbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
border-bottom: 1px solid var(--grey-2);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
|
@ -1,9 +1,18 @@
|
|||
import { defineComponent } from 'vue';
|
||||
import { SbButton, SbMode } from '@schlechtenburg/core';
|
||||
import PageBreadcrumb from '~~/components/PageBreadcrumb';
|
||||
|
||||
import './PageToolbar.scss';
|
||||
|
||||
export default defineComponent({
|
||||
async setup() {
|
||||
const { page } = usePage();
|
||||
const {
|
||||
currentPage,
|
||||
currentPageId,
|
||||
setCurrentPageId,
|
||||
} = useCurrentPage();
|
||||
|
||||
const { pages, insertPage } = usePages();
|
||||
|
||||
const {
|
||||
mode,
|
||||
|
@ -12,12 +21,38 @@ export default defineComponent({
|
|||
save,
|
||||
} = useEditor();
|
||||
|
||||
const addChildPage = () => {
|
||||
insertPage({
|
||||
id: 'draft',
|
||||
attributes: {
|
||||
title: 'New page',
|
||||
block: getNewPageBlock(),
|
||||
slug: 'new-page',
|
||||
parent: {
|
||||
data: {
|
||||
id: currentPage.value?.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setCurrentPageId('draft');
|
||||
edit(currentPage.value?.attributes?.block!);
|
||||
};
|
||||
|
||||
return () => (
|
||||
<div class="ex-page-toolbar">
|
||||
<PageBreadcrumb />
|
||||
{currentPageId.value !== 'draft'
|
||||
? <SbButton
|
||||
type="button"
|
||||
onClick={() => addChildPage()}
|
||||
>Add child page</SbButton>
|
||||
: null}
|
||||
{ mode.value === SbMode.View
|
||||
? <SbButton
|
||||
type="button"
|
||||
onClick={() => edit(page.value?.attributes?.block!)}
|
||||
onClick={() => edit(currentPage.value?.attributes?.block!)}
|
||||
>Edit</SbButton>
|
||||
: <>
|
||||
<SbButton
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
.ex-main-menu {
|
||||
|
||||
}
|
|
@ -1,12 +1,83 @@
|
|||
.ex-admin-nav {
|
||||
background-color: white;
|
||||
width: var(--nav-width);
|
||||
width: var(--ex-nav-width);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
transition: width 0.2s ease;
|
||||
|
||||
&_expanded {
|
||||
width: 80vw;
|
||||
max-width: 300px;
|
||||
width: 300px;
|
||||
max-width: 80vw;
|
||||
}
|
||||
|
||||
&--menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
flex-grow: 1;
|
||||
border-right: 1px solid var(--grey-2);
|
||||
|
||||
&-spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&--menu-item {
|
||||
&-action {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: var(--ex-nav-width);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
color: var(--fg);
|
||||
background-color: var(--bg);
|
||||
font-weight: bold;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
padding: 12px;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--bg);
|
||||
background-color: var(--interact);
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
opacity: 0.001;
|
||||
height: auto;
|
||||
width: calc(100% - 60px);
|
||||
overflow: hidden;
|
||||
margin: 0px;
|
||||
margin-left: 16px;
|
||||
transition: opacity 0.1s ease;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
position: absolute;
|
||||
left: calc((var(--ex-nav-width) / 2) - 8px);
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&_expanded &--menu-item {
|
||||
&-title {
|
||||
//width: calc(100% - (32px + 24px));
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1000px) {
|
||||
&--toggle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import { defineComponent } from 'vue';
|
||||
import { NuxtLink } from '~~/.nuxt/components';
|
||||
import { NuxtLink } from '#components';
|
||||
|
||||
import './Nav.scss';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const { setMe } = useMe();
|
||||
|
||||
const expanded = useState(() => false);
|
||||
const toggle = () => {
|
||||
expanded.value != expanded.value;
|
||||
expanded.value = !expanded.value;
|
||||
};
|
||||
|
||||
const classes = computed(() => ({
|
||||
|
@ -15,18 +17,54 @@ export default defineComponent({
|
|||
'ex-admin-nav_expanded': expanded.value,
|
||||
}));
|
||||
|
||||
const logout = () => {
|
||||
setMe(null);
|
||||
useGqlToken({
|
||||
token: null,
|
||||
config: { type: 'Bearer' },
|
||||
});
|
||||
};
|
||||
|
||||
return () => (
|
||||
<nav
|
||||
class={classes.value}
|
||||
>
|
||||
<nav class={classes.value}>
|
||||
<button
|
||||
class="ex-admin-nav--toggle"
|
||||
type="button"
|
||||
onClick={() => toggle()}
|
||||
>Toggle</button>
|
||||
aria-label="Toggle"
|
||||
>
|
||||
<font-awesome-icon
|
||||
icon={`fa-solid fa-arrow-${expanded.value ? 'left' : 'right'}`}
|
||||
/>
|
||||
</button>
|
||||
<ul class="ex-admin-nav--menu">
|
||||
<li class="ex-admin-nav--menu-item">
|
||||
<NuxtLink to="/">Logout</NuxtLink>
|
||||
<NuxtLink
|
||||
class="ex-admin-nav--menu-item-action"
|
||||
to="/"
|
||||
>
|
||||
<font-awesome-icon
|
||||
class="ex-admin-nav--menu-item-icon"
|
||||
icon="fa-solid fa-home"
|
||||
/>
|
||||
<span class="ex-admin-nav--menu-item-title">Website</span>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
|
||||
<li class="ex-admin-nav--menu-spacer"></li>
|
||||
|
||||
<li class="ex-admin-nav--menu-item">
|
||||
<button
|
||||
type="button"
|
||||
class="ex-admin-nav--menu-item-action"
|
||||
onClick={() => logout()}
|
||||
>
|
||||
<font-awesome-icon
|
||||
class="ex-admin-nav--menu-item-icon"
|
||||
icon="fa-solid fa-right-from-bracket"
|
||||
/>
|
||||
<span class="ex-admin-nav--menu-item-title">Logout</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
|
|
@ -1,8 +1,20 @@
|
|||
import { IBlockData, SbMode } from "@schlechtenburg/core";
|
||||
import { IPage } from "./pages";
|
||||
|
||||
export const useEditor = () => {
|
||||
const {
|
||||
currentPage,
|
||||
currentPageId,
|
||||
setCurrentPageId,
|
||||
} = useCurrentPage();
|
||||
|
||||
const {
|
||||
removePage,
|
||||
updatePage,
|
||||
pages,
|
||||
} = usePages();
|
||||
|
||||
const mode = useState<SbMode>('mode', () => SbMode.View);
|
||||
const revision = useState<number>('revision', () => 0);
|
||||
const draft = useState<IBlockData<any>|null>('draft', () => null);
|
||||
|
||||
const setMode = (newMode: SbMode) => {
|
||||
|
@ -16,13 +28,64 @@ export const useEditor = () => {
|
|||
const edit = (block: IBlockData<any>) => {
|
||||
draft.value = block;
|
||||
mode.value = SbMode.Edit;
|
||||
updateDraft(currentPage.value?.attributes?.block!);
|
||||
};
|
||||
const save = () => {
|
||||
revision.value = revision.value + 1;
|
||||
mode.value = SbMode.View;
|
||||
|
||||
const save = async () => {
|
||||
if (currentPageId.value === 'draft') {
|
||||
const { data, error } = await useAsyncGql(
|
||||
'createPage',
|
||||
{ data: {
|
||||
title: currentPage.value?.attributes?.title,
|
||||
slug: currentPage.value?.attributes?.slug,
|
||||
block: currentPage.value?.attributes?.block,
|
||||
parent: currentPage.value?.attributes?.parent?.data?.id,
|
||||
publishedAt: (new Date()).toISOString(),
|
||||
}}
|
||||
);
|
||||
|
||||
if (error.value) {
|
||||
console.error('Error creating page!');
|
||||
console.error('error:', error.value);
|
||||
console.error('data:', data.value);
|
||||
return;
|
||||
}
|
||||
|
||||
setMode(SbMode.View);
|
||||
updatePage(data.value?.createPage?.data?.attributes?.block);
|
||||
updateDraft(data.value?.createPage?.data?.attributes?.block);
|
||||
navigateTo(getPagePath(data.value?.createPage?.data! as IPage, pages.value));
|
||||
return;
|
||||
} else {
|
||||
const { data, error } = await useAsyncGql(
|
||||
'updatePage',
|
||||
{
|
||||
id: currentPage.value?.id || '',
|
||||
data: {
|
||||
block: draft.value!
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (error.value) {
|
||||
console.error('Error updating page!');
|
||||
console.error('error:', error.value);
|
||||
console.error('data:', data.value);
|
||||
return;
|
||||
}
|
||||
|
||||
setMode(SbMode.View);
|
||||
updatePage(data.value?.updatePage?.data?.attributes?.block);
|
||||
updateDraft(data.value?.updatePage?.data?.attributes?.block);
|
||||
}
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
mode.value = SbMode.View;
|
||||
setMode(SbMode.View);
|
||||
if (currentPageId.value === 'draft') {
|
||||
setCurrentPageId(currentPage.value?.attributes?.parent?.data?.id || null);
|
||||
removePage('draft');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -32,7 +95,6 @@ export const useEditor = () => {
|
|||
edit,
|
||||
cancel,
|
||||
save,
|
||||
revision,
|
||||
|
||||
draft,
|
||||
updateDraft,
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { IBlockData } from "@schlechtenburg/core";
|
||||
|
||||
export interface IRole {
|
||||
id?: string|null;
|
||||
attributes?: {
|
||||
|
@ -31,25 +29,3 @@ export const useMe = () => {
|
|||
setMe,
|
||||
};
|
||||
};
|
||||
|
||||
export interface IPage {
|
||||
id?: string|null;
|
||||
attributes?: {
|
||||
title?: string;
|
||||
block?: IBlockData<any>|null;
|
||||
path?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const usePage = () => {
|
||||
const page = useState<IPage|null>('page', () => null);
|
||||
|
||||
const setPage = (newPage: IPage|null) => {
|
||||
page.value = newPage;
|
||||
};
|
||||
|
||||
return {
|
||||
page,
|
||||
setPage,
|
||||
};
|
||||
};
|
104
packages/example-site/composables/pages.ts
Normal file
104
packages/example-site/composables/pages.ts
Normal file
|
@ -0,0 +1,104 @@
|
|||
import { IBlockData } from "@schlechtenburg/core";
|
||||
|
||||
export interface IPage {
|
||||
id?: string|null;
|
||||
attributes?: {
|
||||
title?: string;
|
||||
block?: IBlockData<any>|null;
|
||||
slug?: string;
|
||||
parent?: {
|
||||
data?: {
|
||||
id?: string|null;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const usePages = () => {
|
||||
const pages = useState<IPage[]|[]>('pages', () => []);
|
||||
|
||||
const getPage = (id:string) => pages.value.find(p => p.id === id);
|
||||
|
||||
const setPages = (newPages: IPage[] = []) => {
|
||||
pages.value = newPages;
|
||||
};
|
||||
|
||||
const updatePage = (page: Partial<IPage>) => {
|
||||
const existing = pages.value.find(p => p.id === page.id);
|
||||
if (!existing) {
|
||||
console.warn('Could not update page because it was not found in the store', page);
|
||||
return;
|
||||
}
|
||||
|
||||
setPages([
|
||||
...pages.value.filter(p => p.id !== page.id),
|
||||
{
|
||||
id: existing.id,
|
||||
attributes: {
|
||||
...existing.attributes,
|
||||
...page.attributes,
|
||||
},
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const removePage = (id: string) => {
|
||||
setPages(pages.value.filter(p => p.id !== id));
|
||||
};
|
||||
|
||||
const insertPage = (page: IPage) => {
|
||||
setPages([
|
||||
...pages.value,
|
||||
page,
|
||||
]);
|
||||
};
|
||||
|
||||
const fetchPages = async () => {
|
||||
const { data, error } = await useAsyncGql('pages');
|
||||
setPages(data.value?.pages?.data as IPage[]);
|
||||
}
|
||||
|
||||
return {
|
||||
pages,
|
||||
setPages,
|
||||
getPage,
|
||||
|
||||
fetchPages,
|
||||
|
||||
insertPage,
|
||||
updatePage,
|
||||
removePage,
|
||||
};
|
||||
};
|
||||
|
||||
export const useCurrentPage = () => {
|
||||
const { pages, insertPage } = usePages();
|
||||
|
||||
const currentPageId = useState<string|null>('currentPageId', () => null);
|
||||
|
||||
const setCurrentPage = (newPage: IPage|null) => {
|
||||
if (!newPage || !newPage.id) {
|
||||
currentPageId.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pages.value.find(p => p.id === newPage.id)) {
|
||||
insertPage(newPage);
|
||||
}
|
||||
|
||||
currentPageId.value = newPage.id;
|
||||
};
|
||||
|
||||
const setCurrentPageId = (newPageId: string|null) => {
|
||||
currentPageId.value = newPageId;
|
||||
};
|
||||
|
||||
const currentPage = computed(() => pages.value.find(p => p.id === currentPageId.value));
|
||||
|
||||
return {
|
||||
currentPage,
|
||||
setCurrentPage,
|
||||
currentPageId,
|
||||
setCurrentPageId,
|
||||
};
|
||||
};
|
|
@ -1,14 +1,29 @@
|
|||
import { IPage } from "~~/composables/states";
|
||||
import { IPage } from "~~/composables/pages";
|
||||
|
||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||
const { setPage } = usePage();
|
||||
export default defineNuxtRouteMiddleware(async (to) => {
|
||||
const { setCurrentPage } = useCurrentPage();
|
||||
const { fetchPages } = usePages();
|
||||
|
||||
const { data, error } = await useAsyncGql({
|
||||
operation: 'pages',
|
||||
variables: {
|
||||
filters: { path: { eq: to.path }},
|
||||
},
|
||||
});
|
||||
|
||||
const pathParts = to.path.split('/').filter(p => p !== '');
|
||||
pathParts.unshift('');
|
||||
|
||||
const filters = pathParts.reduce((total, part) => {
|
||||
return {
|
||||
id: { ne: null },
|
||||
slug: { eq: part === '' ? null : part },
|
||||
|
||||
parent: total,
|
||||
};
|
||||
}, {});
|
||||
|
||||
const [{ data, error }] = await Promise.all([
|
||||
useAsyncGql({
|
||||
operation: 'pages',
|
||||
variables: { filters },
|
||||
}),
|
||||
fetchPages(),
|
||||
]);
|
||||
|
||||
if (error.value) {
|
||||
console.error('Error getting pages!');
|
||||
|
@ -17,10 +32,9 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
|
|||
}
|
||||
|
||||
const newPage = (data.value?.pages?.data[0] as IPage) || null;
|
||||
|
||||
if (newPage?.attributes && !newPage?.attributes?.block) {
|
||||
newPage.attributes.block = getNewPageBlock();
|
||||
if (!newPage) {
|
||||
setResponseStatus(404)
|
||||
return;
|
||||
}
|
||||
|
||||
setPage(newPage);
|
||||
setCurrentPage(newPage);
|
||||
});
|
||||
|
|
26
packages/example-site/package-lock.json
generated
26
packages/example-site/package-lock.json
generated
|
@ -812,6 +812,32 @@
|
|||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.2.1.tgz",
|
||||
"integrity": "sha512-Sz07mnQrTekFWLz5BMjOzHl/+NooTdW8F8kDQxjWwbpOJcnoSg4vUDng8d/WR1wOxM0O+CY9Zw0nR054riNYtQ=="
|
||||
},
|
||||
"@fortawesome/fontawesome-svg-core": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.2.1.tgz",
|
||||
"integrity": "sha512-HELwwbCz6C1XEcjzyT1Jugmz2NNklMrSPjZOWMlc+ZsHIVk+XOvOXLGGQtFBwSyqfJDNgRq4xBCwWOaZ/d9DEA==",
|
||||
"requires": {
|
||||
"@fortawesome/fontawesome-common-types": "6.2.1"
|
||||
}
|
||||
},
|
||||
"@fortawesome/free-solid-svg-icons": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.2.1.tgz",
|
||||
"integrity": "sha512-oKuqrP5jbfEPJWTij4sM+/RvgX+RMFwx3QZCZcK9PrBDgxC35zuc7AOFsyMjMd/PIFPeB2JxyqDr5zs/DZFPPw==",
|
||||
"requires": {
|
||||
"@fortawesome/fontawesome-common-types": "6.2.1"
|
||||
}
|
||||
},
|
||||
"@fortawesome/vue-fontawesome": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.2.tgz",
|
||||
"integrity": "sha512-xHVtVY8ASUeEvgcA/7vULUesENhD+pi/EirRHdMBqooHlXBqK+yrV6d8tUye1m5UKQKVgYAHMhUBfOnoiwvc8Q=="
|
||||
},
|
||||
"@graphql-codegen/cli": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-2.16.1.tgz",
|
||||
|
|
|
@ -13,12 +13,16 @@
|
|||
"nuxt": "3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||
"@fortawesome/vue-fontawesome": "^3.0.2",
|
||||
"@graphql-codegen/cli": "^2.16.1",
|
||||
"@schlechtenburg/core": "^0.0.0",
|
||||
"@schlechtenburg/heading": "^0.0.0",
|
||||
"@schlechtenburg/image": "^0.0.0",
|
||||
"@schlechtenburg/layout": "^0.0.0",
|
||||
"@schlechtenburg/paragraph": "^0.0.0",
|
||||
"@schlechtenburg/style": "^0.0.0",
|
||||
"event-target-polyfill": "^0.0.3",
|
||||
"nuxt-graphql-client": "^0.2.23"
|
||||
}
|
||||
|
|
19
packages/example-site/plugins/fontawesome.ts
Normal file
19
packages/example-site/plugins/fontawesome.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
import {
|
||||
faRightFromBracket,
|
||||
faHome,
|
||||
faArrowRight,
|
||||
faArrowLeft,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
/* add icons to the library */
|
||||
library.add(faRightFromBracket);
|
||||
library.add(faArrowRight);
|
||||
library.add(faArrowLeft);
|
||||
library.add(faHome);
|
||||
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
nuxtApp.vueApp.component('font-awesome-icon', FontAwesomeIcon);
|
||||
})
|
|
@ -4,10 +4,13 @@ mutation createPage($data: PageInput!) {
|
|||
id
|
||||
attributes {
|
||||
title
|
||||
path
|
||||
slug
|
||||
block
|
||||
public
|
||||
publishedAt
|
||||
parent {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,13 @@ query page($id: ID) {
|
|||
id
|
||||
attributes {
|
||||
title
|
||||
path
|
||||
slug
|
||||
block
|
||||
public
|
||||
publishedAt
|
||||
parent {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,13 @@ query pages($filters: PageFiltersInput) {
|
|||
id
|
||||
attributes {
|
||||
title
|
||||
path
|
||||
slug
|
||||
block
|
||||
public
|
||||
publishedAt
|
||||
parent {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
mutation updatePage($id: ID!, $data: PageInput!) {
|
||||
updatePage(id: $id, data: $data) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
title
|
||||
slug
|
||||
block
|
||||
parent {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { generateBlockId, IBlockData } from "@schlechtenburg/core";
|
||||
import { getDefaultData as getDefaultLayoutData, ILayoutData, name as layoutName } from "@schlechtenburg/layout";
|
||||
import { getDefaultData as getDefaultHeadingData, name as headingName } from "@schlechtenburg/heading";
|
||||
import { IPage } from "~~/composables/pages";
|
||||
|
||||
export const getNewPageBlock: () => IBlockData<ILayoutData> = () => ({
|
||||
id: generateBlockId(),
|
||||
|
@ -15,3 +16,28 @@ export const getNewPageBlock: () => IBlockData<ILayoutData> = () => ({
|
|||
],
|
||||
}),
|
||||
});
|
||||
|
||||
export const getPageParents = (page: IPage, pages: IPage[], parents: IPage[]):IPage[] => {
|
||||
const parent = pages.find(p => p.id === page.attributes?.parent?.data?.id);
|
||||
if (!parent) {
|
||||
return parents;
|
||||
}
|
||||
|
||||
return getPageParents(
|
||||
parent,
|
||||
pages,
|
||||
[
|
||||
parent,
|
||||
...parents,
|
||||
],
|
||||
);
|
||||
};
|
||||
|
||||
export const getPagePath = (page: IPage, pages: IPage[]) => {
|
||||
const ancestors = [
|
||||
...getPageParents(page, pages, []),
|
||||
page,
|
||||
];
|
||||
|
||||
return ancestors.reduce((path, page) => page.attributes?.slug ? `${path}/${page.attributes?.slug}` : path, '');
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ export default defineComponent({
|
|||
<img
|
||||
src={localData.src}
|
||||
alt={localData.alt}
|
||||
class="sb-image__content"
|
||||
class="sb-image--content"
|
||||
/>
|
||||
<SbBlock
|
||||
block={localData.description}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.sb-image {
|
||||
margin: 0;
|
||||
|
||||
&__content {
|
||||
&--content {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
|
|
@ -9,11 +9,7 @@
|
|||
flex-direction: row;
|
||||
}
|
||||
|
||||
&__item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
> * {
|
||||
> *:not(.sb-block-placeholder) {
|
||||
flex-basis: auto;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
0
packages/style/lib/index.ts
Normal file
0
packages/style/lib/index.ts
Normal file
29
packages/style/package.json
Normal file
29
packages/style/package.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "@schlechtenburg/style",
|
||||
"version": "0.0.0",
|
||||
"description": "Styles for schlechtenburg",
|
||||
"author": "Benjamin Bädorf <hello@benjaminbaedorf.eu>",
|
||||
"homepage": "",
|
||||
"main": "lib/index.ts",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"directories": {
|
||||
"lib": "lib",
|
||||
"test": "__tests__"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
"fonts",
|
||||
"images",
|
||||
"scss"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@git.pub.solar:b12f/schlechtenburg.git"
|
||||
}
|
||||
}
|
360
packages/style/scss/montserrat.scss
Normal file
360
packages/style/scss/montserrat.scss
Normal file
|
@ -0,0 +1,360 @@
|
|||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUQjIg1_i6t8kCHKm459WxRxC7m0dR9pBOi.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUQjIg1_i6t8kCHKm459WxRzS7m0dR9pBOi.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUQjIg1_i6t8kCHKm459WxRxi7m0dR9pBOi.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUQjIg1_i6t8kCHKm459WxRxy7m0dR9pBOi.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUQjIg1_i6t8kCHKm459WxRyS7m0dR9pA.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUQjIg1_i6t8kCHKm459WxRxC7m0dR9pBOi.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUQjIg1_i6t8kCHKm459WxRzS7m0dR9pBOi.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUQjIg1_i6t8kCHKm459WxRxi7m0dR9pBOi.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUQjIg1_i6t8kCHKm459WxRxy7m0dR9pBOi.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUQjIg1_i6t8kCHKm459WxRyS7m0dR9pA.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUQjIg1_i6t8kCHKm459WxRxC7m0dR9pBOi.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUQjIg1_i6t8kCHKm459WxRzS7m0dR9pBOi.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUQjIg1_i6t8kCHKm459WxRxi7m0dR9pBOi.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUQjIg1_i6t8kCHKm459WxRxy7m0dR9pBOi.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUQjIg1_i6t8kCHKm459WxRyS7m0dR9pA.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WRhyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459W1hyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WZhyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WdhyyTh89ZNpQ.woff2) format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url(#{$sb-style-root}/fonts/montserrat/v25/JTUSjIg1_i6t8kCHKm459WlhyyTh89Y.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
Loading…
Reference in a new issue