cms: go
This commit is contained in:
parent
d8b729edc3
commit
a9e2ef03fe
23 changed files with 26604 additions and 159 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
2745
packages/core/package-lock.json
generated
2745
packages/core/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
12
packages/example-site/components/Page.scss
Normal file
12
packages/example-site/components/Page.scss
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
.ex-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&--editor {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_missing {
|
||||||
|
margin: 4rem;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
28
packages/example-site/components/_/Toaster.scss
Normal file
28
packages/example-site/components/_/Toaster.scss
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
packages/example-site/components/_/Toaster.tsx
Normal file
15
packages/example-site/components/_/Toaster.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
48
packages/example-site/composables/toaster.ts
Normal file
48
packages/example-site/composables/toaster.ts
Normal 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,
|
||||||
|
};
|
||||||
|
};
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}, {});
|
}, {});
|
||||||
|
|
12242
packages/example-site/package-lock.json
generated
12242
packages/example-site/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1953
packages/heading/package-lock.json
generated
1953
packages/heading/package-lock.json
generated
File diff suppressed because it is too large
Load diff
1953
packages/image/package-lock.json
generated
1953
packages/image/package-lock.json
generated
File diff suppressed because it is too large
Load diff
1737
packages/italic/package-lock.json
generated
1737
packages/italic/package-lock.json
generated
File diff suppressed because it is too large
Load diff
1953
packages/layout/package-lock.json
generated
1953
packages/layout/package-lock.json
generated
File diff suppressed because it is too large
Load diff
1953
packages/paragraph/package-lock.json
generated
1953
packages/paragraph/package-lock.json
generated
File diff suppressed because it is too large
Load diff
1953
packages/standalone/package-lock.json
generated
1953
packages/standalone/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue