Basic adding of pages works

This commit is contained in:
Benjamin Bädorf 2022-12-29 03:47:45 +01:00
parent 27b7e3afec
commit e3ddcefb30
No known key found for this signature in database
GPG key ID: 4406E80E13CD656C
55 changed files with 1022 additions and 136 deletions

View file

@ -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"
}
}
}

View file

@ -1,4 +1,5 @@
.sb-block-ordering {
font-family: 'Montserrat';
display: flex;
position: absolute;
flex-direction: column;

View file

@ -1,4 +1,6 @@
.sb-block-picker {
font-family: 'Montserrat';
&__add-button {
padding: 24px 32px;
}

View file

@ -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 {

View file

@ -0,0 +1,3 @@
.sb-block-toolbar {
font-family: 'Montserrat';
}

View file

@ -1,4 +1,5 @@
.sb-button {
font-family: 'Montserrat';
border: 0;
padding: 8px 12px;
background-color: var(--grey-0);

View file

@ -3,6 +3,7 @@
}
.sb-context-menu {
font-family: 'Montserrat';
display: none;
flex-direction: column;
background: var(--grey-0);

View file

@ -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%);
}
}
}

View file

@ -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}
/>

View file

@ -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;
}

View file

@ -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>
);
},

View file

@ -1,3 +1,4 @@
.sb-missing-block {
font-family: 'Montserrat';
flex-basis: 100%;
}

View file

@ -1,4 +1,6 @@
.sb-modal {
font-family: 'Montserrat';
&__overlay {
background-color: var(--grey-3-t);
position: fixed;

View file

@ -1,4 +1,5 @@
.sb-select {
font-family: 'Montserrat';
background-color: var(--grey-0);
border: 1px solid var(--grey-2);
position: relative;

View file

@ -1,4 +1,5 @@
.sb-toolbar {
font-family: 'Montserrat';
position: absolute;
width: auto;
height: auto;

View file

@ -1,4 +1,6 @@
.sb-tree-block-select {
font-family: 'Montserrat';
&__list {
list-style: none;
color: var(--fg);

View file

@ -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"
},

View file

@ -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;
}

View file

@ -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>
);
},

View file

@ -0,0 +1,3 @@
.ex-page-breadcrumb {
display: flex;
}

View 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>);
};
},
});

View file

@ -0,0 +1,7 @@
.ex-page-toolbar {
position: sticky;
top: 0;
border-bottom: 1px solid var(--grey-2);
display: flex;
justify-content: center;
}

View file

@ -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

View file

@ -1,3 +0,0 @@
.ex-main-menu {
}

View file

@ -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;
}
}
}

View file

@ -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>

View file

@ -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,

View file

@ -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,
};
};

View 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,
};
};

View file

@ -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);
});

View file

@ -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",

View file

@ -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"
}

View 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);
})

View file

@ -4,10 +4,13 @@ mutation createPage($data: PageInput!) {
id
attributes {
title
path
slug
block
public
publishedAt
parent {
data {
id
}
}
}
}
}

View file

@ -4,10 +4,13 @@ query page($id: ID) {
id
attributes {
title
path
slug
block
public
publishedAt
parent {
data {
id
}
}
}
}
}

View file

@ -4,10 +4,13 @@ query pages($filters: PageFiltersInput) {
id
attributes {
title
path
slug
block
public
publishedAt
parent {
data {
id
}
}
}
}
}

View file

@ -1,8 +1,16 @@
mutation updatePage($id: ID!, $data: PageInput!) {
updatePage(id: $id, data: $data) {
data {
id
attributes {
title
slug
block
parent {
data {
id
}
}
}
}
}

View file

@ -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, '');
}

View file

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

View file

@ -1,7 +1,7 @@
.sb-image {
margin: 0;
&__content {
&--content {
width: 100%;
height: auto;
}

View file

@ -9,11 +9,7 @@
flex-direction: row;
}
&__item {
position: relative;
}
> * {
> *:not(.sb-block-placeholder) {
flex-basis: auto;
flex-grow: 1;
flex-shrink: 1;

View file

View 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"
}
}

View 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;
}

View file

@ -1,5 +1,4 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = [
pkgs.nodejs