This commit is contained in:
b12f 2024-10-09 14:56:50 +02:00
parent d8b729edc3
commit a9e2ef03fe
Signed by: b12f
GPG key ID: 729956E1124F8F26
23 changed files with 26604 additions and 159 deletions

View file

@ -23,6 +23,11 @@ html {
--interact: #3f9cff; --interact: #3f9cff;
--interact-lite: #3f9cff; --interact-lite: #3f9cff;
--info: var(--interact);
--success: green;
--warning: orange;
--error: red;
} }
body { body {

View file

@ -117,7 +117,14 @@ export const toHTMLString = (
&& JSON.stringify(a) === JSON.stringify(f))); && JSON.stringify(a) === JSON.stringify(f)));
console.log(c); console.log(c);
for ( for (let removedFormat of removedFormats) {
const tool = tools.find(tool => tool.name === removedFormat.type);
if (!tool) {
continue;
}
tool.
}
} }
return string; return string;

File diff suppressed because it is too large Load diff

View file

@ -9,8 +9,10 @@ body {
justify-content: space-between; justify-content: space-between;
align-items: stretch; align-items: stretch;
--ex-nav-width: 60px; --ex-nav-mobile-width: 60px;
--ex-nav-expanded-width: 320px; --ex-nav-desktop-width: 320px;
--ex-nav-width: var(--ex-nav-mobile-width);
--interact: #3f9cff; --interact: #3f9cff;
--interact-lite: #3f9cff; --interact-lite: #3f9cff;
@ -39,6 +41,10 @@ body {
--z-tree-block-select: 4000; --z-tree-block-select: 4000;
--z-modal: 10000; --z-modal: 10000;
@media screen and (min-width: 1000px) {
--ex-nav-width: var(--ex-nav-desktop-width);
}
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
@ -49,20 +55,8 @@ body {
top: 0; top: 0;
left: 0; left: 0;
bottom: 0; bottom: 0;
}
&--page { @media screen and (min-width: 1000px) {
flex-basis: 100%;
&:not(:first-child) {
margin-left: var(--ex-nav-width);
}
}
@media screen and (min-width: 1000px) {
--ex-nav-width: var(--ex-nav-expanded-width);
&--edit-nav {
position: unset; position: unset;
width: unset; width: unset;
flex-basis: var(--ex-nav-width); flex-basis: var(--ex-nav-width);
@ -70,4 +64,16 @@ body {
flex-shrink: 0; flex-shrink: 0;
} }
} }
&--page {
flex-basis: 100%;
&:not(:first-child) {
margin-left: var(--ex-nav-width);
@media screen and (min-width: 1000px) {
margin-left: 0;
}
}
}
} }

View file

@ -4,6 +4,7 @@ import { NuxtPage } from '#components';
import './app.scss'; import './app.scss';
const AdminNav = defineAsyncComponent(() => import('~~/components/_/Nav')); const AdminNav = defineAsyncComponent(() => import('~~/components/_/Nav'));
const Toaster = defineAsyncComponent(() => import('~~/components/_/Toaster'));
export default defineComponent({ export default defineComponent({
setup() { setup() {
@ -12,6 +13,7 @@ export default defineComponent({
<div class="ex-app"> <div class="ex-app">
{me.value ? <AdminNav class="ex-app--edit-nav" /> : null} {me.value ? <AdminNav class="ex-app--edit-nav" /> : null}
<NuxtPage class="ex-app--page" /> <NuxtPage class="ex-app--page" />
{me.value ? <Toaster /> : null}
</div> </div>
); );
}, },

View file

@ -0,0 +1,12 @@
.ex-page {
display: flex;
flex-direction: column;
&--editor {
flex-grow: 1;
}
&_missing {
margin: 4rem;
}
}

View file

@ -1,42 +1,69 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { SbMain, SbMode } from '@schlechtenburg/core'; import {
SbMain,
SbButton,
} from '@schlechtenburg/core';
import PageToolbar from '~~/components/PageToolbar'; import PageToolbar from '~~/components/PageToolbar';
import SbLayout from '@schlechtenburg/layout'; import SbLayout from '@schlechtenburg/layout';
import SbHeading from '@schlechtenburg/heading'; import SbHeading from '@schlechtenburg/heading';
import SbParagraph from '@schlechtenburg/paragraph'; import SbParagraph from '@schlechtenburg/paragraph';
import SbImage from '@schlechtenburg/image'; import SbImage from '@schlechtenburg/image';
import './Page.scss';
export default defineComponent({ export default defineComponent({
async setup() { async setup() {
const { me } = useMe(); const { me } = useMe();
const loggedIn = computed(() => !!me.value?.id); const loggedIn = computed(() => !!me.value?.id);
const { currentPage } = useCurrentPage(); const { setCurrentPageId, currentPage } = useCurrentPage();
const block = computed(() => currentPage.value?.attributes?.block); const block = computed(() => currentPage.value?.attributes?.block);
if (!block) {
console.error('No block!');
console.error('page', currentPage.value);
}
const { const {
mode, mode,
draft, draft,
updateDraft, updateDraft,
} = useEditor(); } = useEditor();
const { edit } = useEditor();
watchEffect(() => { watchEffect(() => {
updateDraft(block.value!); updateDraft(block.value!);
}); });
const { insertPage } = usePages();
if (!block) { const createPageHere = () => {
console.error('No block!'); insertPage({
console.error('page', currentPage.value); id: 'draft',
} attributes: {
title: 'New page',
block: getNewPageBlock(),
slug: '',
parent: {
data: {
id: currentPage.value?.id,
},
},
},
});
setCurrentPageId('draft');
edit(currentPage.value?.attributes?.block!);
};
return () => ( return () => (
<div class="ex-page"> <div class="ex-page">
{loggedIn.value ? <PageToolbar></PageToolbar> : null} {loggedIn.value ? <PageToolbar></PageToolbar> : null}
{draft.value {draft.value
? <SbMain ? <SbMain
class="ex-page" class="ex-page--editor"
mode={mode.value} mode={mode.value}
eventUpdate={(updatedBlock) => updateDraft(updatedBlock)} eventUpdate={(updatedBlock) => updateDraft(updatedBlock)}
block={draft.value} block={draft.value}
@ -47,7 +74,14 @@ export default defineComponent({
SbImage, SbImage,
]} ]}
/> />
: <div class="ex-page ex-page_corrupt">Corrupt page: {currentPage.value?.attributes?.slug} ({currentPage.value?.id})</div>} : <div class="ex-page ex-page_missing">
<h1>Ooops!</h1>
<p>This page does not exist yet. However, you can create it right now!</p>
<SbButton
type="button"
onClick={() => createPageHere()}
>Create a page here</SbButton>
</div>}
</div> </div>
); );
}, },

View file

@ -43,27 +43,29 @@ export default defineComponent({
return () => ( return () => (
<div class="ex-page-toolbar"> <div class="ex-page-toolbar">
<PageBreadcrumb /> <PageBreadcrumb />
{currentPageId.value !== 'draft' {currentPageId.value !== 'draft' && !!currentPageId.value
? <SbButton ? <SbButton
type="button" type="button"
onClick={() => addChildPage()} onClick={() => addChildPage()}
>Add child page</SbButton> >Add child page</SbButton>
: null} : null}
{ mode.value === SbMode.View {!!currentPageId.value
? <SbButton ? (mode.value === SbMode.View
type="button" ? <SbButton
onClick={() => edit(currentPage.value?.attributes?.block!)}
>Edit</SbButton>
: <>
<SbButton
type="button" type="button"
onClick={() => cancel()} onClick={() => edit(currentPage.value?.attributes?.block!)}
>Cancel</SbButton> >Edit</SbButton>
<SbButton : <>
type="button" <SbButton
onClick={() => save()} type="button"
>Save</SbButton> onClick={() => cancel()}
</>} >Cancel</SbButton>
<SbButton
type="button"
onClick={() => save()}
>Save</SbButton>
</>)
: null}
</div> </div>
); );
}, },

View file

@ -15,6 +15,12 @@
max-width: 80vw; max-width: 80vw;
} }
&--toggle {
@media screen and (min-width: 1000px) {
display: none;
}
}
&--menu { &--menu {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -33,7 +39,7 @@
&-action { &-action {
position: relative; position: relative;
width: 100%; width: 100%;
height: var(--ex-nav-width); height: var(--ex-nav-mobile-width);
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -58,6 +64,10 @@
width: calc(100% - 60px); width: calc(100% - 60px);
overflow: hidden; overflow: hidden;
margin: 0px; margin: 0px;
@media screen and (min-width: 1000px) {
opacity: 1;
}
} }
} }
@ -71,10 +81,4 @@
margin-left: 16px; margin-left: 16px;
} }
} }
@media screen and (min-width: 1000px) {
&--toggle {
display: none;
}
}
} }

View file

@ -0,0 +1,28 @@
.ex-toaster {
position: fixed;
bottom: 0;
right: 0;
display: flex;
flex-direction: column;
&--toast {
margin: 1rem 2rem;
border-radius: 4px;
&_info {
background-color: var(--info);
}
&_success {
background-color: var(--success);
}
&_warning {
background-color: var(--warning);
}
&_error {
background-color: var(--error);
}
}
}

View file

@ -0,0 +1,15 @@
import { defineComponent } from 'vue';
import './Toaster.scss';
export default defineComponent({
setup() {
const { toasts } = useToaster();
return () => (
<nav class="ex-toaster">
{toasts.value.map(toast => <div class={`ex-toaster--toast ex-toaster--toast_${toast.type}`}>{toast.content}</div>)}
</nav>
);
},
});

View file

@ -11,6 +11,7 @@ export const useEditor = () => {
const { const {
removePage, removePage,
updatePage, updatePage,
insertPage,
pages, pages,
} = usePages(); } = usePages();
@ -25,6 +26,10 @@ export const useEditor = () => {
draft.value = newDraft; draft.value = newDraft;
} }
const removeDraft = () => {
draft.value = null;
}
const edit = (block: IBlockData<any>) => { const edit = (block: IBlockData<any>) => {
draft.value = block; draft.value = block;
mode.value = SbMode.Edit; mode.value = SbMode.Edit;
@ -52,8 +57,8 @@ export const useEditor = () => {
} }
setMode(SbMode.View); setMode(SbMode.View);
updatePage(data.value?.createPage?.data?.attributes?.block); insertPage(data.value?.createPage?.data! as IPage);
updateDraft(data.value?.createPage?.data?.attributes?.block); removeDraft();
navigateTo(getPagePath(data.value?.createPage?.data! as IPage, pages.value)); navigateTo(getPagePath(data.value?.createPage?.data! as IPage, pages.value));
return; return;
} else { } else {
@ -74,7 +79,7 @@ export const useEditor = () => {
return; return;
} }
setMode(SbMode.View); setMode(SbMode.View);
updatePage(data.value?.updatePage?.data?.attributes?.block); updatePage(data.value?.updatePage?.data?.attributes?.block);
updateDraft(data.value?.updatePage?.data?.attributes?.block); updateDraft(data.value?.updatePage?.data?.attributes?.block);
} }

View file

@ -0,0 +1,48 @@
export enum ToastType {
SUCCESS = 'success',
INFO = 'info',
WARNING = 'warning',
ERROR = 'error',
}
export interface IToaster {
type: ToastType,
content: string;
}
interface IToastedToaster extends IToaster {
id: number;
}
export const DEFAULT_TOAST_TIME = 5000;
export const useToaster = () => {
const toasts = useState<IToastedToaster[]>('toasts', () => []);
const removeToast = (id: number) => {
toasts.value = toasts.value.filter(toast => toast.id !== id);
};
const showToast = (toast: IToaster, time = DEFAULT_TOAST_TIME) => {
const id = +(new Date());
toasts.value = [
...toasts.value,
{
...toast,
id,
},
];
setTimeout(() => {
removeToast(id);
}, time);
return id;
};
return {
toasts,
showToast,
removeToast,
};
};

View file

@ -11,8 +11,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
const filters = pathParts.reduce((total, part) => { const filters = pathParts.reduce((total, part) => {
return { return {
id: { ne: null }, id: { ne: null },
slug: { eq: part === '' ? null : part }, slug: { eq: part },
parent: total, parent: total,
}; };
}, {}); }, {});

File diff suppressed because it is too large Load diff

View file

@ -22,6 +22,7 @@
"@schlechtenburg/style": "^0.0.0", "@schlechtenburg/style": "^0.0.0",
"event-target-polyfill": "^0.0.3", "event-target-polyfill": "^0.0.3",
"nuxt-graphql-client": "^0.2.23", "nuxt-graphql-client": "^0.2.23",
"nuxt-icon": "^0.1.8" "nuxt-icon": "^0.1.8",
"sass": "^1.57.1"
} }
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
{ pkgs ? import <nixpkgs> {} }: { pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/293a28df6d7ff3dec1e61e37cc4ee6e6c0fb0847.tar.gz") {} }:
pkgs.mkShell { pkgs.mkShell {
buildInputs = [ buildInputs = [
pkgs.nodejs pkgs.nodejs