Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
Benjamin Bädorf | d8b729edc3 | |
Benjamin Bädorf | 0fcb0d03a0 | |
Benjamin Bädorf | e3ddcefb30 | |
Benjamin Bädorf | 27b7e3afec | |
Benjamin Bädorf | 520b3a6753 | |
Benjamin Bädorf | b4eeb244bf | |
Benjamin Bädorf | f1ff6f75ad |
|
@ -11,6 +11,7 @@ import SbLayout from '@schlechtenburg/layout';
|
|||
import SbHeading from '@schlechtenburg/heading';
|
||||
import SbParagraph from '@schlechtenburg/paragraph';
|
||||
import SbImage from '@schlechtenburg/image';
|
||||
import SbItalicTool from '@schlechtenburg/italic';
|
||||
|
||||
import exampleData from './example-data';
|
||||
|
||||
|
@ -38,6 +39,9 @@ export default defineComponent({
|
|||
SbImage,
|
||||
SbParagraph,
|
||||
]}
|
||||
availableInlineTools={[
|
||||
SbItalicTool,
|
||||
]}
|
||||
mode={activeTab.value as SbMode}
|
||||
eventUpdate={(data:IBlockData<any>) => {
|
||||
block.id = data.id;
|
||||
|
|
|
@ -21,3 +21,7 @@ export default {
|
|||
view: defineAsyncComponent(() => import('./view')),
|
||||
} as IBlockDefinition<any>;
|
||||
```
|
||||
|
||||
## Go by example
|
||||
|
||||
As Schlechtenburg is still in active development, it's good to check out the official blocks to see what they look like.
|
||||
|
|
|
@ -13,6 +13,7 @@ export default defineConfig({
|
|||
'@schlechtenburg/heading': join(__dirname, '../packages/heading/lib/index.ts'),
|
||||
'@schlechtenburg/image': join(__dirname, '../packages/image/lib/index.ts'),
|
||||
'@schlechtenburg/layout': join(__dirname, '../packages/layout/lib/index.ts'),
|
||||
'@schlechtenburg/italic': join(__dirname, '../packages/italic/lib/index.ts'),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[{package.json,*.yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
|
@ -0,0 +1,6 @@
|
|||
HOST=0.0.0.0
|
||||
PORT=1337
|
||||
APP_KEYS="toBeModified1,toBeModified2"
|
||||
API_TOKEN_SALT=tobemodified
|
||||
ADMIN_JWT_SECRET=tobemodified
|
||||
JWT_SECRET=tobemodified
|
|
@ -0,0 +1,115 @@
|
|||
############################
|
||||
# OS X
|
||||
############################
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
._*
|
||||
|
||||
|
||||
############################
|
||||
# Linux
|
||||
############################
|
||||
|
||||
*~
|
||||
|
||||
|
||||
############################
|
||||
# Windows
|
||||
############################
|
||||
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
|
||||
############################
|
||||
# Packages
|
||||
############################
|
||||
|
||||
*.7z
|
||||
*.csv
|
||||
*.dat
|
||||
*.dmg
|
||||
*.gz
|
||||
*.iso
|
||||
*.jar
|
||||
*.rar
|
||||
*.tar
|
||||
*.zip
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.seed
|
||||
*.so
|
||||
*.swo
|
||||
*.swp
|
||||
*.swn
|
||||
*.swm
|
||||
*.out
|
||||
*.pid
|
||||
|
||||
|
||||
############################
|
||||
# Logs and databases
|
||||
############################
|
||||
|
||||
.tmp
|
||||
*.log
|
||||
*.sql
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
|
||||
############################
|
||||
# Misc.
|
||||
############################
|
||||
|
||||
*#
|
||||
ssl
|
||||
.idea
|
||||
nbproject
|
||||
public/uploads/*
|
||||
!public/uploads/.gitkeep
|
||||
|
||||
############################
|
||||
# Node.js
|
||||
############################
|
||||
|
||||
lib-cov
|
||||
lcov.info
|
||||
pids
|
||||
logs
|
||||
results
|
||||
node_modules
|
||||
.node_history
|
||||
|
||||
############################
|
||||
# Tests
|
||||
############################
|
||||
|
||||
testApp
|
||||
coverage
|
||||
|
||||
############################
|
||||
# Strapi
|
||||
############################
|
||||
|
||||
.env
|
||||
license.txt
|
||||
exports
|
||||
*.cache
|
||||
dist
|
||||
build
|
||||
.strapi-updater.json
|
|
@ -0,0 +1,57 @@
|
|||
# 🚀 Getting started with Strapi
|
||||
|
||||
Strapi comes with a full featured [Command Line Interface](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html) (CLI) which lets you scaffold and manage your project in seconds.
|
||||
|
||||
### `dev`
|
||||
|
||||
Start your Strapi application with autoReload enabled. [Learn more](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html#strapi-develop)
|
||||
|
||||
```
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
```
|
||||
|
||||
### `start`
|
||||
|
||||
Start your Strapi application with autoReload disabled. [Learn more](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html#strapi-start)
|
||||
|
||||
```
|
||||
npm run start
|
||||
# or
|
||||
yarn start
|
||||
```
|
||||
|
||||
### `build`
|
||||
|
||||
Build your admin panel. [Learn more](https://docs.strapi.io/developer-docs/latest/developer-resources/cli/CLI.html#strapi-build)
|
||||
|
||||
```
|
||||
npm run build
|
||||
# or
|
||||
yarn build
|
||||
```
|
||||
|
||||
## ⚙️ Deployment
|
||||
|
||||
Strapi gives you many possible deployment options for your project. Find the one that suits you on the [deployment section of the documentation](https://docs.strapi.io/developer-docs/latest/setup-deployment-guides/deployment.html).
|
||||
|
||||
## 📚 Learn more
|
||||
|
||||
- [Resource center](https://strapi.io/resource-center) - Strapi resource center.
|
||||
- [Strapi documentation](https://docs.strapi.io) - Official Strapi documentation.
|
||||
- [Strapi tutorials](https://strapi.io/tutorials) - List of tutorials made by the core team and the community.
|
||||
- [Strapi blog](https://docs.strapi.io) - Official Strapi blog containing articles made by the Strapi team and the community.
|
||||
- [Changelog](https://strapi.io/changelog) - Find out about the Strapi product updates, new features and general improvements.
|
||||
|
||||
Feel free to check out the [Strapi GitHub repository](https://github.com/strapi/strapi). Your feedback and contributions are welcome!
|
||||
|
||||
## ✨ Community
|
||||
|
||||
- [Discord](https://discord.strapi.io) - Come chat with the Strapi community including the core team.
|
||||
- [Forum](https://forum.strapi.io/) - Place to discuss, ask questions and find answers, show your Strapi project and get feedback or just talk with other Community members.
|
||||
- [Awesome Strapi](https://github.com/strapi/awesome-strapi) - A curated list of awesome things related to Strapi.
|
||||
|
||||
---
|
||||
|
||||
<sub>🤫 Psst! [Strapi is hiring](https://strapi.io/careers).</sub>
|
|
@ -0,0 +1,8 @@
|
|||
export default ({ env }) => ({
|
||||
auth: {
|
||||
secret: env('ADMIN_JWT_SECRET'),
|
||||
},
|
||||
apiToken: {
|
||||
salt: env('API_TOKEN_SALT'),
|
||||
},
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
export default {
|
||||
rest: {
|
||||
defaultLimit: 25,
|
||||
maxLimit: 100,
|
||||
withCount: true,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
import path from 'path';
|
||||
|
||||
export default ({ env }) => ({
|
||||
connection: {
|
||||
client: 'sqlite',
|
||||
connection: {
|
||||
filename: path.join(__dirname, '..', '..', env('DATABASE_FILENAME', '.tmp/data.db')),
|
||||
},
|
||||
useNullAsDefault: true,
|
||||
},
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
export default [
|
||||
'strapi::errors',
|
||||
'strapi::security',
|
||||
'strapi::cors',
|
||||
'strapi::poweredBy',
|
||||
'strapi::logger',
|
||||
'strapi::query',
|
||||
'strapi::body',
|
||||
'strapi::session',
|
||||
'strapi::favicon',
|
||||
'strapi::public',
|
||||
];
|
|
@ -0,0 +1,7 @@
|
|||
export default ({ env }) => ({
|
||||
host: env('HOST', '0.0.0.0'),
|
||||
port: env.int('PORT', 1337),
|
||||
app: {
|
||||
keys: env.array('APP_KEYS'),
|
||||
},
|
||||
});
|
Binary file not shown.
After Width: | Height: | Size: 497 B |
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "@schlechtenburg/cms",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"description": "A Strapi application",
|
||||
"scripts": {
|
||||
"dev": "strapi develop",
|
||||
"start": "strapi start",
|
||||
"build": "strapi build",
|
||||
"strapi": "strapi"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/plugin-graphql": "^4.5.3",
|
||||
"@strapi/plugin-i18n": "4.5.3",
|
||||
"@strapi/plugin-users-permissions": "4.5.3",
|
||||
"@strapi/strapi": "4.5.3",
|
||||
"better-sqlite3": "7.4.6"
|
||||
},
|
||||
"author": {
|
||||
"name": "A Strapi developer"
|
||||
},
|
||||
"strapi": {
|
||||
"uuid": "d70cca4c-887b-45a7-8b2d-afb95c49a0c8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.19.1 <=18.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# To prevent search engines from seeing the site altogether, uncomment the next two lines:
|
||||
# User-Agent: *
|
||||
# Disallow: /
|
|
@ -0,0 +1,35 @@
|
|||
export default {
|
||||
config: {
|
||||
locales: [
|
||||
// 'ar',
|
||||
// 'fr',
|
||||
// 'cs',
|
||||
// 'de',
|
||||
// 'dk',
|
||||
// 'es',
|
||||
// 'he',
|
||||
// 'id',
|
||||
// 'it',
|
||||
// 'ja',
|
||||
// 'ko',
|
||||
// 'ms',
|
||||
// 'nl',
|
||||
// 'no',
|
||||
// 'pl',
|
||||
// 'pt-BR',
|
||||
// 'pt',
|
||||
// 'ru',
|
||||
// 'sk',
|
||||
// 'sv',
|
||||
// 'th',
|
||||
// 'tr',
|
||||
// 'uk',
|
||||
// 'vi',
|
||||
// 'zh-Hans',
|
||||
// 'zh',
|
||||
],
|
||||
},
|
||||
bootstrap(app) {
|
||||
console.log(app);
|
||||
},
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"extends": "@strapi/typescript-utils/tsconfigs/admin",
|
||||
"include": [
|
||||
"../plugins/**/admin/src/**/*",
|
||||
"./"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules/",
|
||||
"build/",
|
||||
"dist/",
|
||||
"**/*.test.ts"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
module.exports = (config, webpack) => {
|
||||
// Note: we provide webpack above so you should not `require` it
|
||||
// Perform customizations to webpack config
|
||||
// Important: return the modified config
|
||||
return config;
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"kind": "collectionType",
|
||||
"collectionName": "pages",
|
||||
"info": {
|
||||
"singularName": "page",
|
||||
"pluralName": "pages",
|
||||
"displayName": "Page",
|
||||
"description": ""
|
||||
},
|
||||
"options": {
|
||||
"draftAndPublish": true
|
||||
},
|
||||
"pluginOptions": {},
|
||||
"attributes": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"block": {
|
||||
"type": "json"
|
||||
},
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"regex": "[A-z0-9\\-]*",
|
||||
"unique": false
|
||||
},
|
||||
"parent": {
|
||||
"type": "relation",
|
||||
"relation": "oneToOne",
|
||||
"target": "api::page.page"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* page controller
|
||||
*/
|
||||
|
||||
import { factories } from '@strapi/strapi'
|
||||
|
||||
export default factories.createCoreController('api::page.page');
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* page router
|
||||
*/
|
||||
|
||||
import { factories } from '@strapi/strapi';
|
||||
|
||||
export default factories.createCoreRouter('api::page.page');
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* page service
|
||||
*/
|
||||
|
||||
import { factories } from '@strapi/strapi';
|
||||
|
||||
export default factories.createCoreService('api::page.page');
|
|
@ -0,0 +1,18 @@
|
|||
export default {
|
||||
/**
|
||||
* An asynchronous register function that runs before
|
||||
* your application is initialized.
|
||||
*
|
||||
* This gives you an opportunity to extend code.
|
||||
*/
|
||||
register(/*{ strapi }*/) {},
|
||||
|
||||
/**
|
||||
* An asynchronous bootstrap function that runs before
|
||||
* your application gets started.
|
||||
*
|
||||
* This gives you an opportunity to set up your data model,
|
||||
* run jobs, or perform some special logic.
|
||||
*/
|
||||
bootstrap(/*{ strapi }*/) {},
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"extends": "@strapi/typescript-utils/tsconfigs/server",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": [
|
||||
"./",
|
||||
"./**/*.ts",
|
||||
"./**/*.js",
|
||||
"src/**/*.json"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules/",
|
||||
"build/",
|
||||
"dist/",
|
||||
".cache/",
|
||||
".tmp/",
|
||||
"src/admin/",
|
||||
"**/*.test.*",
|
||||
"src/plugins/**"
|
||||
]
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
$block: &;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-items: stretch;
|
||||
height: auto;
|
||||
|
|
|
@ -164,6 +164,7 @@ export const SbBlock = defineComponent({
|
|||
eventRemoveSelf={props.eventRemoveSelf}
|
||||
eventActivatePrevious={props.eventActivatePrevious}
|
||||
eventActivateNext={props.eventActivateNext}
|
||||
data-sb-block--content
|
||||
|
||||
{...{
|
||||
onClick: ($event: MouseEvent) => {
|
||||
|
|
|
@ -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,9 +1,14 @@
|
|||
.sb-button {
|
||||
font-family: 'Montserrat';
|
||||
border: 0;
|
||||
padding: 8px 12px;
|
||||
background-color: var(--grey-0);
|
||||
border: 1px solid var(--grey-2);
|
||||
|
||||
&_active {
|
||||
box-shadow: inset 5px 5px 5px 5px black;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border: 1px solid var(--interact);
|
||||
}
|
||||
|
|
|
@ -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%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,11 @@ import {
|
|||
defineComponent,
|
||||
provide,
|
||||
shallowReactive,
|
||||
shallowRef,
|
||||
ref,
|
||||
watch,
|
||||
computed,
|
||||
Transition,
|
||||
PropType,
|
||||
Ref,
|
||||
} from 'vue';
|
||||
|
@ -13,6 +16,8 @@ import {
|
|||
IBlockLibrary,
|
||||
ITreeNode,
|
||||
OnUpdateBlockCb,
|
||||
IFormattingTool,
|
||||
IFormattingToolLibrary,
|
||||
} from '../types';
|
||||
import { model } from '../block-helpers';
|
||||
import { SymMode, SbMode } from '../mode';
|
||||
|
@ -24,13 +29,20 @@ import {
|
|||
} from '../use-block-tree';
|
||||
import { SymEditorDimensions, useResizeObserver } from '../use-resize-observer';
|
||||
import { SymActiveBlock } from '../use-activation';
|
||||
import { SymRootElement } from '../use-root-element';
|
||||
import {
|
||||
SbFormattingToolbar,
|
||||
SymFormattingToolLibrary,
|
||||
SymFormattingToolbarClients,
|
||||
SymFormattingToolbarActiveClient,
|
||||
} from '../rich-text';
|
||||
|
||||
import { SbMainMenu } from './MainMenu';
|
||||
import { SbBlockToolbar } from './BlockToolbar';
|
||||
import { SbBlock } from './Block';
|
||||
|
||||
export interface ISbMainProps {
|
||||
availableBlocks: IBlockDefinition<any>[];
|
||||
availableFormattingTools: IFormattingTool[];
|
||||
block: IBlockData<any>;
|
||||
eventUpdate: OnUpdateBlockCb;
|
||||
mode: SbMode;
|
||||
|
@ -48,6 +60,10 @@ export const SbMain = defineComponent({
|
|||
type: Array as PropType<IBlockDefinition<any>[]>,
|
||||
default: () => [],
|
||||
},
|
||||
availableFormattingTools: {
|
||||
type: Array as PropType<IFormattingTool[]>,
|
||||
default: () => [],
|
||||
},
|
||||
block: {
|
||||
type: Object as PropType<IBlockData<any>>,
|
||||
required: true,
|
||||
|
@ -71,6 +87,12 @@ export const SbMain = defineComponent({
|
|||
setup(props: ISbMainProps) {
|
||||
const el: Ref<null|HTMLElement> = ref(null);
|
||||
useResizeObserver(el, SymEditorDimensions);
|
||||
provide(SymRootElement, el);
|
||||
|
||||
const inlineClients = shallowRef([]);
|
||||
provide(SymFormattingToolbarClients, inlineClients);
|
||||
|
||||
provide(SymFormattingToolbarActiveClient, ref(null));
|
||||
|
||||
const mode = ref(props.mode);
|
||||
provide(SymMode, mode);
|
||||
|
@ -79,6 +101,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);
|
||||
|
||||
|
@ -93,26 +120,38 @@ export const SbMain = defineComponent({
|
|||
{},
|
||||
),
|
||||
});
|
||||
|
||||
provide(SymBlockLibrary, blockLibrary);
|
||||
|
||||
const inlineToolLibrary: IFormattingToolLibrary = shallowReactive({
|
||||
...props.availableFormattingTools.reduce(
|
||||
(tools: IFormattingToolLibrary, tool: IFormattingTool) => ({ ...tools, [tool.name]: tool }),
|
||||
{},
|
||||
),
|
||||
});
|
||||
provide(SymFormattingToolLibrary, inlineToolLibrary);
|
||||
|
||||
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}
|
||||
/>
|
||||
<SbFormattingToolbar />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
export * from './mode';
|
||||
export * from './types';
|
||||
|
||||
export * from './rich-text';
|
||||
|
||||
export * from './block-helpers';
|
||||
|
||||
export * from './use-activation';
|
||||
|
@ -8,10 +10,12 @@ export * from './use-dynamic-blocks';
|
|||
export * from './use-resize-observer';
|
||||
|
||||
export * from './components/Main';
|
||||
|
||||
export * from './components/Block';
|
||||
export * from './components/BlockPicker';
|
||||
export * from './components/BlockOrdering';
|
||||
export * from './components/BlockPlaceholder';
|
||||
|
||||
export * from './components/Toolbar';
|
||||
export * from './components/Button';
|
||||
export * from './components/Select';
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
.sb-inline-toolbar {
|
||||
position: absolute;
|
||||
background-color: var(--bg);
|
||||
height: 2rem;
|
||||
min-width: 2rem;
|
||||
display: flex;
|
||||
z-index: var(--z-toolbar);
|
||||
|
||||
&_hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
import {
|
||||
ref,
|
||||
Ref,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
defineComponent,
|
||||
computed,
|
||||
} from 'vue';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { useRootElement } from '../use-root-element';
|
||||
import { useFormattingToolbar } from './use-formatting-toolbar';
|
||||
import {
|
||||
getSelection,
|
||||
getAnchorElementForSelection,
|
||||
getRangeFromSelection,
|
||||
getRect,
|
||||
} from './selection';
|
||||
|
||||
import './FormattingToolbar.scss';
|
||||
|
||||
export const SbFormattingToolbar = defineComponent({
|
||||
name: 'sb-inlinetoolbar',
|
||||
|
||||
setup() {
|
||||
const { rootElement } = useRootElement();
|
||||
const {
|
||||
activeClient,
|
||||
setActiveClient,
|
||||
clients,
|
||||
getAllTools,
|
||||
} = useFormattingToolbar();
|
||||
|
||||
const allTools = computed(() => getAllTools());
|
||||
|
||||
const selection: Ref<Selection|null>= ref(null);
|
||||
const selectionRect: Ref<DOMRect|null>= ref(null);
|
||||
const updateSelectionRect = () => {
|
||||
selectionRect.value = getRect(selection.value!);
|
||||
};
|
||||
|
||||
const showing: Ref<boolean> = ref(false);
|
||||
|
||||
const show = () => {
|
||||
showing.value = true;
|
||||
updateSelectionRect();
|
||||
};
|
||||
const hide = () => {
|
||||
showing.value = false;
|
||||
setActiveClient(null);
|
||||
};
|
||||
|
||||
const style = computed(() => {
|
||||
const rootRect = rootElement.value?.getBoundingClientRect();
|
||||
const x = (selectionRect.value?.x || 0)
|
||||
- (rootRect?.left || 0);
|
||||
const y = (selectionRect.value?.y || 0)
|
||||
+ (selectionRect.value?.height || 0)
|
||||
- (rootRect?.top || 0);
|
||||
return {
|
||||
left: Math.floor(x) + 'px',
|
||||
top: Math.floor(y) + 'px',
|
||||
};
|
||||
});
|
||||
|
||||
const classes = computed(() => ({
|
||||
'sb-inline-toolbar': true,
|
||||
}));
|
||||
|
||||
const onSelectionChanged = debounce(() => {
|
||||
selection.value = getSelection();
|
||||
if (!selection.value) {
|
||||
console.warn('Could not get selection');
|
||||
return
|
||||
}
|
||||
|
||||
const range = getRangeFromSelection(selection.value);
|
||||
// If we're not selecting anything, bail
|
||||
if (!range || range.endOffset === range.startOffset) {
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
|
||||
const focusedElement = document.activeElement;
|
||||
if (!focusedElement) {
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// If new selection is not in registered clients, close it
|
||||
if (!clients.value.find(client => client === focusedElement)) {
|
||||
hide();
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveClient(document.activeElement as HTMLElement|null);
|
||||
|
||||
show();
|
||||
}, 50);
|
||||
|
||||
onMounted(() => {
|
||||
rootElement.value?.addEventListener('click', close);
|
||||
document.addEventListener('selectionchange', onSelectionChanged, true);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
rootElement.value?.removeEventListener('click', close);
|
||||
document.removeEventListener('selectionchange', onSelectionChanged);
|
||||
});
|
||||
|
||||
return () => (allTools.value.length && showing.value)
|
||||
? <div
|
||||
style={style.value}
|
||||
class={classes.value}
|
||||
onClick={(event:MouseEvent) => { event.stopPropagation(); }}
|
||||
>{allTools.value.map((tool) => {
|
||||
const ToolUI = tool.ui as any;
|
||||
return <ToolUI
|
||||
activeClient={activeClient}
|
||||
selection={selection.value}
|
||||
></ToolUI>;
|
||||
})}</div>
|
||||
: null;
|
||||
},
|
||||
});
|
|
@ -0,0 +1,69 @@
|
|||
import {
|
||||
ref,
|
||||
Ref,
|
||||
h,
|
||||
defineComponent,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
PropType,
|
||||
computed,
|
||||
watchEffect,
|
||||
} from 'vue';
|
||||
import { useFormattingToolbar } from './use-formatting-toolbar';
|
||||
|
||||
export const SbRichText = defineComponent({
|
||||
name: 'sb-rich-text',
|
||||
|
||||
props: {
|
||||
inputRef: {
|
||||
type: (null as unknown) as PropType<Ref<HTMLElement|null>>,
|
||||
default: ref(null),
|
||||
},
|
||||
tag: { type: String, default: 'p' },
|
||||
|
||||
value: { type: String, default: '' },
|
||||
|
||||
onValueChange: {
|
||||
type: (null as unknown) as PropType<(value: string) => void>,
|
||||
default: (_:string) => {},
|
||||
},
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const {
|
||||
registerClient,
|
||||
unregisterClient,
|
||||
} = useFormattingToolbar();
|
||||
|
||||
const onKeydown = (event: KeyboardEvent) => {
|
||||
if (['Delete', 'Backspace'].indexOf(event.key) < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.inputRef.value?.textContent === '') {
|
||||
props.inputRef.value.innerHTML = '';
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
registerClient(props.inputRef.value!);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
unregisterClient(props.inputRef.value!);
|
||||
});
|
||||
|
||||
return () => h(
|
||||
props.tag,
|
||||
{
|
||||
class: 'sb-contenteditable',
|
||||
contenteditable: 'true',
|
||||
ref: props.inputRef,
|
||||
onKeydown,
|
||||
onInput: () => {
|
||||
props.onValueChange(props.inputRef.value?.innerHTML || '');
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright notice:
|
||||
*
|
||||
* Large parts of this file are heavily inspired if not downright copied from editor.js,
|
||||
* copyright MIT.
|
||||
* https://editorjs.io/
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if object is DOM node
|
||||
*
|
||||
* @param {*} node - object to check
|
||||
* @returns {boolean}
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const isElement = (node: any): node is Element => {
|
||||
if (node instanceof Number) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return node && node.nodeType && node.nodeType === Node.ELEMENT_NODE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if object is DocumentFragment node
|
||||
*
|
||||
* @param {object} node - object to check
|
||||
* @returns {boolean}
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const isFragment = (node: any): node is DocumentFragment => {
|
||||
if (node instanceof Number) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return node && node.nodeType && node.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export * from './FormattingToolbar';
|
||||
export * from './RichText';
|
||||
export * from './use-formatting-toolbar';
|
||||
export * from './selection';
|
||||
export * from './dom';
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright notice:
|
||||
*
|
||||
* Large parts of this file are heavily inspired if not downright copied from editor.js,
|
||||
* copyright MIT.
|
||||
* https://editorjs.io/
|
||||
*/
|
||||
|
||||
import { isElement } from './dom';
|
||||
|
||||
export const getSelection = globalThis.getSelection ? globalThis.getSelection : () => null;
|
||||
|
||||
/**
|
||||
* Returns range from passed Selection object
|
||||
*
|
||||
* @param selection - Selection object to get Range from
|
||||
*/
|
||||
export const getRangeFromSelection = (selection: Selection): Range|null => {
|
||||
return selection && selection.rangeCount ? selection.getRangeAt(0) : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns selected anchor element
|
||||
*
|
||||
* @returns {Element|null}
|
||||
*/
|
||||
export const getAnchorElementForSelection = (selection: Selection): Element | null => {
|
||||
const anchorNode = selection.anchorNode;
|
||||
|
||||
if (!anchorNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isElement(anchorNode)) {
|
||||
return anchorNode.parentElement;
|
||||
} else {
|
||||
return anchorNode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates position and size of selected text
|
||||
*
|
||||
* @returns {DOMRect}
|
||||
*/
|
||||
export const getRect = (selection: Selection): DOMRect => {
|
||||
const defaultRect = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
} as DOMRect;
|
||||
|
||||
if (selection.rangeCount === null || isNaN(selection.rangeCount)) {
|
||||
console.warn('Method SelectionUtils.rangeCount is not supported');
|
||||
return defaultRect;
|
||||
}
|
||||
|
||||
if (selection.rangeCount === 0) {
|
||||
return defaultRect;
|
||||
}
|
||||
|
||||
const range = selection.getRangeAt(0).cloneRange() as Range;
|
||||
|
||||
let rect = { ...defaultRect };
|
||||
|
||||
if (range.getBoundingClientRect) {
|
||||
rect = range.getBoundingClientRect() as DOMRect;
|
||||
}
|
||||
// Fall back to inserting a temporary element
|
||||
if (rect.x === 0 && rect.y === 0) {
|
||||
const span = document.createElement('span');
|
||||
|
||||
if (span.getBoundingClientRect) {
|
||||
// Ensure span has dimensions and position by
|
||||
// adding a zero-width space character
|
||||
span.appendChild(document.createTextNode('\u200b'));
|
||||
range.insertNode(span);
|
||||
rect = span.getBoundingClientRect() as DOMRect;
|
||||
|
||||
const spanParent = span.parentNode;
|
||||
if (spanParent) {
|
||||
spanParent.removeChild(span);
|
||||
|
||||
// Glue any broken text nodes back together
|
||||
spanParent.normalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rect;
|
||||
};
|
|
@ -0,0 +1,58 @@
|
|||
import {
|
||||
ref,
|
||||
Ref,
|
||||
inject,
|
||||
reactive,
|
||||
} from 'vue';
|
||||
import { IFormattingToolLibrary } from '../types';
|
||||
|
||||
export const SymFormattingToolbarActiveClient = Symbol('Schlechtenburg inline active client');
|
||||
export const SymFormattingToolbarClients = Symbol('Schlechtenburg inline toolbar client elements');
|
||||
export const SymFormattingToolLibrary = Symbol('Schlechtenburg inline tool library');
|
||||
|
||||
export const useFormattingToolbar = () => {
|
||||
const clients: Ref<HTMLElement[]> = inject(SymFormattingToolbarClients, ref([]));
|
||||
const activeClient: Ref<HTMLElement|null> = inject(SymFormattingToolbarActiveClient, ref(null));
|
||||
|
||||
const setActiveClient = (client: HTMLElement|null) => {
|
||||
activeClient.value = client;
|
||||
};
|
||||
|
||||
const setClients = (newClients: HTMLElement[]) => {
|
||||
clients.value = newClients;
|
||||
};
|
||||
|
||||
const clientIsRegistered = (el: HTMLElement) => !!clients.value.find(cl => cl === el);
|
||||
|
||||
const registerClient = (el: HTMLElement) => {
|
||||
if (clientIsRegistered(el)) {
|
||||
console.warn('Not reregistering toolbar client that is already registered:', el);
|
||||
return;
|
||||
}
|
||||
|
||||
setClients([
|
||||
...clients.value,
|
||||
el,
|
||||
]);
|
||||
};
|
||||
|
||||
const unregisterClient = (el: HTMLElement) => {
|
||||
setClients(clients.value.filter(cl => cl !== el));
|
||||
};
|
||||
|
||||
const tools: IFormattingToolLibrary = inject(SymFormattingToolLibrary, reactive({}));
|
||||
const getTool = (name: string) => tools[name];
|
||||
const getAllTools = () => Object.keys(tools).map(name => tools[name]);
|
||||
|
||||
return {
|
||||
clients,
|
||||
activeClient,
|
||||
setActiveClient,
|
||||
registerClient,
|
||||
unregisterClient,
|
||||
|
||||
tools,
|
||||
getTool,
|
||||
getAllTools,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,254 @@
|
|||
import defaultTo from 'lodash/defaultTo';
|
||||
import { IFormattingTool } from '../types';
|
||||
|
||||
export interface IRichTextFormat {
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface IRichTextValue {
|
||||
text: string;
|
||||
formats: IRichTextFormat[][];
|
||||
start: number|null;
|
||||
end: number|null;
|
||||
}
|
||||
|
||||
export const findToolForElement = (
|
||||
tools: IFormattingTool[],
|
||||
element: Element,
|
||||
): IFormattingTool|null => tools.find(tool => {
|
||||
if (tool.tagName && element.tagName !== tool.tagName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tool.className && !element.classList.contains(tool.className)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}) || null;
|
||||
|
||||
export const createFromString = (
|
||||
value: string = '',
|
||||
formats: IRichTextFormat[] = [],
|
||||
): IRichTextValue => ({
|
||||
text: value,
|
||||
formats: (new Array(value.length)).fill([...formats]),
|
||||
start: null,
|
||||
end: null,
|
||||
});
|
||||
|
||||
const createFromDOMNodeRecursively = (
|
||||
tools: IFormattingTool[],
|
||||
node: ChildNode,
|
||||
formats: IRichTextFormat[] = [],
|
||||
): IRichTextValue => {
|
||||
if (node.nodeType === node.TEXT_NODE) {
|
||||
return createFromString(node.textContent || '', formats);
|
||||
}
|
||||
|
||||
if (node.nodeType === node.ELEMENT_NODE) {
|
||||
return createFromDOMElement(tools, node as Element, formats);
|
||||
}
|
||||
|
||||
return createFromString('', []);
|
||||
};
|
||||
|
||||
export const createFromDOMElement = (
|
||||
tools: IFormattingTool[],
|
||||
element: Element,
|
||||
formats: IRichTextFormat[] = [],
|
||||
): IRichTextValue => {
|
||||
const tool = findToolForElement(tools, element);
|
||||
const subFormats = tool
|
||||
? [
|
||||
...formats.filter(f => f.type !== tool.name),
|
||||
{ type: tool.name },
|
||||
]
|
||||
: [ ...formats ];
|
||||
|
||||
const nodes = Array.from(element.childNodes);
|
||||
return concat(...nodes.map(node => createFromDOMNodeRecursively(tools, node, subFormats)));
|
||||
};
|
||||
|
||||
export const createFromDOMString = (
|
||||
tools: IFormattingTool[],
|
||||
value: string,
|
||||
formats: IRichTextFormat[] = [],
|
||||
): IRichTextValue => {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = value;
|
||||
return createFromDOMElement(tools, div, formats);
|
||||
};
|
||||
|
||||
export const create = (
|
||||
tools: IFormattingTool[],
|
||||
value: Element|string = '',
|
||||
): IRichTextValue => {
|
||||
if (typeof value === 'string') {
|
||||
return createFromDOMString(tools, value);
|
||||
}
|
||||
return createFromDOMElement(tools, value);
|
||||
}
|
||||
|
||||
export const toHTMLString = (
|
||||
tools: IFormattingTool[],
|
||||
value: IRichTextValue,
|
||||
) => {
|
||||
const tools = [...tools].sort((a, b) => {
|
||||
const tensionA = defaultTo(a.surfaceTension, Infinity);
|
||||
const tensionB = defaultTo(b.surfaceTension, Infinity);
|
||||
return tensionA - tensionB;
|
||||
});
|
||||
|
||||
const elementTree = [];
|
||||
const string = '';
|
||||
for (let i = 0; i < value.text.length; i++) {
|
||||
const c = value.text[i];
|
||||
const formats = value.formats[i];
|
||||
const activeFormats = value.formats[i - 1] || [];
|
||||
|
||||
const removedFormats = activeFormats
|
||||
.filter(a => !formats
|
||||
.find(f => f.type === a.type
|
||||
&& JSON.stringify(f) === JSON.stringify(a)));
|
||||
const addedFormats = formats
|
||||
.filter(f => !activeFormats
|
||||
.find(a => a.type === f.type
|
||||
&& JSON.stringify(a) === JSON.stringify(f)));
|
||||
|
||||
console.log(c);
|
||||
for (
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
export const getActiveFormats = (richTextValue: IRichTextValue): IRichTextFormat[] =>
|
||||
richTextValue.start !== null
|
||||
? richTextValue.formats[richTextValue.start]
|
||||
: [];
|
||||
|
||||
export const applyFormat = (
|
||||
value: IRichTextValue,
|
||||
format: IRichTextFormat,
|
||||
startIndex?: number,
|
||||
endIndex?: number,
|
||||
): IRichTextValue => {
|
||||
const start = defaultTo(defaultTo(startIndex, value.start), 0);
|
||||
const end = defaultTo(defaultTo(endIndex, value.end), value.text.length);
|
||||
|
||||
return {
|
||||
...value,
|
||||
formats: [
|
||||
...value.formats.slice(0, start),
|
||||
...value.formats.slice(start, end).map(letterFormatList => [
|
||||
...letterFormatList.filter(letterFormat => letterFormat.type === format.type),
|
||||
format,
|
||||
]),
|
||||
...value.formats.slice(end),
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
export const removeFormat = (
|
||||
value: IRichTextValue,
|
||||
format: IRichTextFormat,
|
||||
startIndex?: number,
|
||||
endIndex?: number,
|
||||
): IRichTextValue => {
|
||||
const start = defaultTo(defaultTo(startIndex, value.start), 0);
|
||||
const end = defaultTo(defaultTo(endIndex, value.end), value.text.length);
|
||||
|
||||
return {
|
||||
...value,
|
||||
formats: [
|
||||
...value.formats.slice(0, start),
|
||||
...value.formats.slice(start, end)
|
||||
.map(letterFormatList => letterFormatList.filter(letterFormat => letterFormat.type === format.type)),
|
||||
...value.formats.slice(end),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export const toggleFormat = (
|
||||
value: IRichTextValue,
|
||||
format: IRichTextFormat,
|
||||
): IRichTextValue => {
|
||||
const activeFormats = getActiveFormats(value);
|
||||
if (activeFormats.find(f => f.type === format.type)) {
|
||||
return removeFormat(value, format);
|
||||
}
|
||||
|
||||
return applyFormat(value, format);
|
||||
}
|
||||
|
||||
export const concat = (...richTextValues:IRichTextValue[]): IRichTextValue => richTextValues
|
||||
.reduce((newValue, value) => ({
|
||||
text: newValue.text + value.text,
|
||||
formats: [
|
||||
...newValue.formats,
|
||||
...value.formats,
|
||||
],
|
||||
start: newValue.start !== null ? newValue.start : value.start,
|
||||
end: value.end !== null ? value.end : newValue.end,
|
||||
}), { text: '', formats: [], start: null, end: null });
|
||||
|
||||
export const join = (
|
||||
richTextValues:IRichTextValue[],
|
||||
separator?: string|IRichTextValue,
|
||||
): IRichTextValue => richTextValues
|
||||
.reduce((total, value) => concat(
|
||||
total,
|
||||
...(separator
|
||||
? [typeof separator === 'string' ? createFromString(separator) : separator]
|
||||
: []),
|
||||
value,
|
||||
), createFromString());
|
||||
|
||||
export const slice = (
|
||||
value: IRichTextValue,
|
||||
startIndex?: number,
|
||||
endIndex?: number,
|
||||
): IRichTextValue => {
|
||||
const start = defaultTo(defaultTo(startIndex, value.start), 0);
|
||||
const end = defaultTo(defaultTo(endIndex, value.end), value.text.length);
|
||||
|
||||
return {
|
||||
text: value.text.slice(start, end),
|
||||
formats: value.formats.slice(start, end),
|
||||
start: value.start !== null ? (value.start >= start ? value.start - start : null) : null,
|
||||
end: value.end !== null ? (value.end <= end ? end - value.end : null) : null,
|
||||
};
|
||||
};
|
||||
|
||||
export const insert = (
|
||||
value: IRichTextValue,
|
||||
valueToInsert: IRichTextValue|string,
|
||||
startIndex?: number,
|
||||
endIndex?: number,
|
||||
) => {
|
||||
const start = defaultTo(defaultTo(startIndex, value.start), value.text.length);
|
||||
const end = defaultTo(defaultTo(endIndex, value.end), value.text.length);
|
||||
|
||||
return concat(
|
||||
slice(value, 0, start),
|
||||
typeof valueToInsert === 'string' ? createFromString(valueToInsert) : valueToInsert,
|
||||
slice(value, end, value.text.length),
|
||||
);
|
||||
}
|
||||
|
||||
export const split = (
|
||||
value: IRichTextValue,
|
||||
valueToInsert: IRichTextValue|string,
|
||||
startIndex?: number,
|
||||
endIndex?: number,
|
||||
) => {
|
||||
const start = defaultTo(defaultTo(startIndex, value.start), value.text.length);
|
||||
const end = defaultTo(defaultTo(endIndex, value.end), value.text.length);
|
||||
|
||||
return concat(
|
||||
slice(value, 0, start),
|
||||
typeof valueToInsert === 'string' ? createFromString(valueToInsert) : valueToInsert,
|
||||
slice(value, end, value.text.length),
|
||||
);
|
||||
}
|
|
@ -170,3 +170,21 @@ export interface IBlockDefinition<T> {
|
|||
export interface IBlockLibrary {
|
||||
[name: string]: IBlockDefinition<any>;
|
||||
}
|
||||
|
||||
export interface IFormattingTool {
|
||||
name: string;
|
||||
icon: string;
|
||||
tagName?: string;
|
||||
className?: string;
|
||||
edit: Component;
|
||||
surfaceTension?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schlechtenburg maintains a library of formatting tools that are available
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export interface IFormattingToolLibrary {
|
||||
[name: string]: IFormattingTool;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
} from 'vue';
|
||||
|
||||
export const SymActiveBlock = Symbol('Schlechtenburg active block');
|
||||
export function useActivation(currentBlockId: string|null = null) {
|
||||
export const useActivation = (currentBlockId: string|null = null) => {
|
||||
const activeBlockId: Ref<string|null> = inject(SymActiveBlock, ref(null));
|
||||
|
||||
const isActive = computed(() => activeBlockId.value === currentBlockId);
|
||||
|
@ -46,4 +46,4 @@ export function useActivation(currentBlockId: string|null = null) {
|
|||
deactivate,
|
||||
requestActivation,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -10,14 +10,12 @@ import {
|
|||
ITreeNode,
|
||||
IBlockData,
|
||||
} from './types';
|
||||
import { useDynamicBlocks } from './use-dynamic-blocks';
|
||||
import { SbMode } from './mode';
|
||||
|
||||
export const SymBlockTree= Symbol('Schlechtenburg block tree');
|
||||
export const SymBlockTreeRegister = Symbol('Schlechtenburg block tree register');
|
||||
export const SymBlockTreeUnregister = Symbol('Schlechtenburg block tree unregister');
|
||||
|
||||
export function useBlockTree() {
|
||||
export const useBlockTree = () => {
|
||||
const blockTree: Ref<ITreeNode|null> = inject(SymBlockTree, ref(null));
|
||||
const registerWithParent = inject(SymBlockTreeRegister, (_b: ITreeNode, _i: number) => {});
|
||||
const unregisterWithParent = inject(SymBlockTreeUnregister, (_b: ITreeNode) => {});
|
||||
|
@ -52,18 +50,11 @@ export function useBlockTree() {
|
|||
self.children = self.children.filter((child: ITreeNode) => child.id !== id);
|
||||
});
|
||||
|
||||
const { mode } = useDynamicBlocks();
|
||||
|
||||
const register = (block: IBlockData<any>, index: number = 0) => {
|
||||
if (!block.id) {
|
||||
throw new Error(`Cannot register a block without an id: ${JSON.stringify(block)}`);
|
||||
}
|
||||
|
||||
if (mode.value !== SbMode.Edit) {
|
||||
console.warn('Ignoring block tree registration requests outside of edit mode.');
|
||||
return;
|
||||
}
|
||||
|
||||
self.id = block.id;
|
||||
self.name = block.name;
|
||||
|
||||
|
@ -82,4 +73,4 @@ export function useBlockTree() {
|
|||
blockTree,
|
||||
register,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@ interface BlockRect {
|
|||
|
||||
export const SymBlockDimensions = Symbol('Schlechtenburg block dimensions');
|
||||
export const SymEditorDimensions = Symbol('Schlechtenburg editor dimensions');
|
||||
export function useResizeObserver(el: Ref<null|HTMLElement>, symbol: symbol) {
|
||||
export const useResizeObserver = (el: Ref<null|HTMLElement>, symbol: symbol) => {
|
||||
const dimensions: Ref<null|BlockRect> = ref(null);
|
||||
provide(symbol, dimensions);
|
||||
const triggerSizeCalculation = () => {
|
||||
|
@ -47,7 +47,7 @@ export function useResizeObserver(el: Ref<null|HTMLElement>, symbol: symbol) {
|
|||
})
|
||||
|
||||
return { triggerSizeCalculation, dimensions };
|
||||
}
|
||||
};
|
||||
|
||||
export function useBlockSizing() {
|
||||
const editorDimensions: Ref<BlockRect|null> = inject(SymEditorDimensions, ref(null));
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import {
|
||||
Ref,
|
||||
ref,
|
||||
inject,
|
||||
} from 'vue';
|
||||
|
||||
export const SymRootElement = Symbol('Schlechtenburg root element');
|
||||
export const useRootElement = () => {
|
||||
const rootElement: Ref<HTMLElement|null> = inject(SymRootElement, ref(null));
|
||||
return { rootElement };
|
||||
};
|
|
@ -173,6 +173,14 @@
|
|||
"integrity": "sha512-nEUfRiARCcaVo3ny3ZQjURjHQZUo/JkEw7rLlSZy/psWGnvwXFtPcr6jb7Yb41DVW5LTe6KRq9LGleRNsg1Frw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/runtime": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
|
||||
"integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
}
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz",
|
||||
|
@ -420,6 +428,33 @@
|
|||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@tannin/compile": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@tannin/compile/-/compile-1.1.0.tgz",
|
||||
"integrity": "sha512-n8m9eNDfoNZoxdvWiTfW/hSPhehzLJ3zW7f8E7oT6mCROoMNWCB4TYtv041+2FMAxweiE0j7i1jubQU4MEC/Gg==",
|
||||
"requires": {
|
||||
"@tannin/evaluate": "^1.2.0",
|
||||
"@tannin/postfix": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"@tannin/evaluate": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@tannin/evaluate/-/evaluate-1.2.0.tgz",
|
||||
"integrity": "sha512-3ioXvNowbO/wSrxsDG5DKIMxC81P0QrQTYai8zFNY+umuoHWRPbQ/TuuDEOju9E+jQDXmj6yI5GyejNuh8I+eg=="
|
||||
},
|
||||
"@tannin/plural-forms": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@tannin/plural-forms/-/plural-forms-1.1.0.tgz",
|
||||
"integrity": "sha512-xl9R2mDZO/qiHam1AgMnAES6IKIg7OBhcXqy6eDsRCdXuxAFPcjrej9HMjyCLE0DJ/8cHf0i5OQTstuBRhpbHw==",
|
||||
"requires": {
|
||||
"@tannin/compile": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"@tannin/postfix": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@tannin/postfix/-/postfix-1.1.0.tgz",
|
||||
"integrity": "sha512-oocsqY7g0cR+Gur5jRQLSrX2OtpMLMse1I10JQBm8CdGMrDkh1Mg2gjsiquMHRtBs4Qwu5wgEp5GgIYHk4SNPw=="
|
||||
},
|
||||
"@types/braces": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.1.tgz",
|
||||
|
@ -450,12 +485,73 @@
|
|||
"@types/braces": "*"
|
||||
}
|
||||
},
|
||||
"@types/mousetrap": {
|
||||
"version": "1.6.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/mousetrap/-/mousetrap-1.6.11.tgz",
|
||||
"integrity": "sha512-F0oAily9Q9QQpv9JKxKn0zMKfOo36KHCW7myYsmUyf2t0g+sBTbG3UleTPoguHdE1z3GLFr3p7/wiOio52QFjQ=="
|
||||
},
|
||||
"@types/prop-types": {
|
||||
"version": "15.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
|
||||
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "18.0.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz",
|
||||
"integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==",
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
"csstype": "^3.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"csstype": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
|
||||
"integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"version": "18.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
|
||||
"integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/scheduler": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
|
||||
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
|
||||
},
|
||||
"@types/uuid": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz",
|
||||
"integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/wordpress__data": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/wordpress__data/-/wordpress__data-6.0.1.tgz",
|
||||
"integrity": "sha512-jTLl9mbw54rUq4wuTEmg9bBUaV66MIbg7vVoutpf3Etdnk+kwsuucAdoW+mJTzKEVkGQB5LDIZ/vohd6QrtQ2w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*",
|
||||
"redux": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"@types/wordpress__rich-text": {
|
||||
"version": "3.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/wordpress__rich-text/-/wordpress__rich-text-3.4.6.tgz",
|
||||
"integrity": "sha512-MeLSATBHrcN3fp8cVylbpx+BKRJ1aootPNtbTblcUAHcuRo6avKu1kaDLxIZb/8YbsD+/3Wm8d1uldeNz9/lhw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*",
|
||||
"@types/wordpress__data": "*",
|
||||
"@types/wordpress__rich-text": "*"
|
||||
}
|
||||
},
|
||||
"@vue/compiler-core": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.0.7.tgz",
|
||||
|
@ -842,6 +938,183 @@
|
|||
"vscode-uri": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"@wordpress/a11y": {
|
||||
"version": "3.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-3.23.0.tgz",
|
||||
"integrity": "sha512-RgnnmI2u6DnechTYr8368LrQy2C/1HtFzXqnDjw9zzT+8kkL8Qa1W2l2JTzz0yHG/n/nR/UXZWXq0FSbnknKlw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"@wordpress/dom-ready": "^3.23.0",
|
||||
"@wordpress/i18n": "^4.23.0"
|
||||
}
|
||||
},
|
||||
"@wordpress/compose": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/compose/-/compose-6.0.0.tgz",
|
||||
"integrity": "sha512-7VPoISTbhIw5bWT7CmIr3XjX86gtI5mhwDrmHAW4Gdt7cd0wRjcJVT45R/PPbZwmAfxjeqVVFh1qTmfz/PxlYg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"@types/mousetrap": "^1.6.8",
|
||||
"@wordpress/deprecated": "^3.23.0",
|
||||
"@wordpress/dom": "^3.23.0",
|
||||
"@wordpress/element": "^5.0.0",
|
||||
"@wordpress/is-shallow-equal": "^4.23.0",
|
||||
"@wordpress/keycodes": "^3.23.0",
|
||||
"@wordpress/priority-queue": "^2.23.0",
|
||||
"change-case": "^4.1.2",
|
||||
"clipboard": "^2.0.8",
|
||||
"mousetrap": "^1.6.5",
|
||||
"use-memo-one": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"@wordpress/data": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/data/-/data-8.0.0.tgz",
|
||||
"integrity": "sha512-1O6DDp7cyHw5x9+M2Y3LZf/dAsVuz4+6PtIRf1EE3VVifDA60cd6lCMs87ljBYFn2hOWgfXL9SwcObfUXtW51g==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"@wordpress/compose": "^6.0.0",
|
||||
"@wordpress/deprecated": "^3.23.0",
|
||||
"@wordpress/element": "^5.0.0",
|
||||
"@wordpress/is-shallow-equal": "^4.23.0",
|
||||
"@wordpress/priority-queue": "^2.23.0",
|
||||
"@wordpress/redux-routine": "^4.23.0",
|
||||
"equivalent-key-map": "^0.2.2",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"is-promise": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"redux": "^4.1.2",
|
||||
"turbo-combine-reducers": "^1.0.2",
|
||||
"use-memo-one": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"@wordpress/deprecated": {
|
||||
"version": "3.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/deprecated/-/deprecated-3.23.0.tgz",
|
||||
"integrity": "sha512-dyliFqGFwaUueG9SLu8ugJ6Gr6eaXHiKg188udZjNt4J8WhC/nC03en4gmtCf38C9nTKbZZOzBAdijw5fdJh7g==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"@wordpress/hooks": "^3.23.0"
|
||||
}
|
||||
},
|
||||
"@wordpress/dom": {
|
||||
"version": "3.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/dom/-/dom-3.23.0.tgz",
|
||||
"integrity": "sha512-w1Fkb2K3ODBnZze5iZ7AX3h9QRryO0up7dTUAFKMuRQSFgLKHzhzF78bteA1EV0DDNdiNMzvxr5mSnE1vl1cpg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"@wordpress/deprecated": "^3.23.0"
|
||||
}
|
||||
},
|
||||
"@wordpress/dom-ready": {
|
||||
"version": "3.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-3.23.0.tgz",
|
||||
"integrity": "sha512-wJndB3ER9yn9Z//kc6VB1fu86m7ETwrU5P4uXslyqpGGGpgghWNhwtr0GU2gXguBIRSeAWhwcjYl30wciy6f+w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0"
|
||||
}
|
||||
},
|
||||
"@wordpress/element": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.0.0.tgz",
|
||||
"integrity": "sha512-u1DhVIdb6VEe8wzxKCrLfRWbeFLrlhYTFSOY6yrtl2z9vr9bCdlLp9aAppTxLNUs8cFcdoCEMpakVRYFyfZwTQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@wordpress/escape-html": "^2.23.0",
|
||||
"change-case": "^4.1.2",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"@wordpress/escape-html": {
|
||||
"version": "2.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.23.0.tgz",
|
||||
"integrity": "sha512-QmMGJVEoVu3+s46Ya7saYZI8D1jPOKN18eFJX21y59/99tAVvmcWWz0k0uTO5bciDQ7R6ACm9AJS6RiZycODkg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0"
|
||||
}
|
||||
},
|
||||
"@wordpress/hooks": {
|
||||
"version": "3.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.23.0.tgz",
|
||||
"integrity": "sha512-EYv8xXY0BEJVWu8YPv5LobxEMddFtdbuUVKSryPWHcLjSMrSYRT6j8qzjq8EP/UQolVOxs3THdOoMDySKsJT9g==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0"
|
||||
}
|
||||
},
|
||||
"@wordpress/i18n": {
|
||||
"version": "4.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.23.0.tgz",
|
||||
"integrity": "sha512-SF9aJ2gajHtYDLMA9nhnMPerLmKJofUM4qQypwrW5AYwi5JS7jxvZHNETIqqYq5UdhRwsubPjAUfFosIonmfnA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"@wordpress/hooks": "^3.23.0",
|
||||
"gettext-parser": "^1.3.1",
|
||||
"memize": "^1.1.0",
|
||||
"sprintf-js": "^1.1.1",
|
||||
"tannin": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"@wordpress/is-shallow-equal": {
|
||||
"version": "4.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/is-shallow-equal/-/is-shallow-equal-4.23.0.tgz",
|
||||
"integrity": "sha512-uA1RauILRs85Q864x5xrBfN588Pg0xDrbp4DYs3ktzBL9Jm4igI7+Say7fHpqGBVc8+ZuAEhGeoLt4gUfAJzKg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0"
|
||||
}
|
||||
},
|
||||
"@wordpress/keycodes": {
|
||||
"version": "3.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.23.0.tgz",
|
||||
"integrity": "sha512-CfvxhqwgVU2c3f2B1F09i8E+1/HMkgf4gmmf+0dyKFMmYesByY4GKuvOvKw5dklFWp96qECz6Jf+L2F4vw7//w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"@wordpress/i18n": "^4.23.0",
|
||||
"change-case": "^4.1.2",
|
||||
"lodash": "^4.17.21"
|
||||
}
|
||||
},
|
||||
"@wordpress/priority-queue": {
|
||||
"version": "2.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-2.23.0.tgz",
|
||||
"integrity": "sha512-5jf2EK2C/EzXjw7uQ5DwLxDuWP+TyrGdJzcjKXWwAm89H/tdgS6qbRkL6T7ZhARNkm1po88xmCvMarYzceXnBg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"requestidlecallback": "^0.3.0"
|
||||
}
|
||||
},
|
||||
"@wordpress/redux-routine": {
|
||||
"version": "4.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/redux-routine/-/redux-routine-4.23.0.tgz",
|
||||
"integrity": "sha512-uPWlYT28qsPKL1DOLcjU2hUZNsNb3uTSP5rPRD6rwZHtezQLYtg1jFM+HG6TAm0hPjnqMbcULmDNI+FA2fSdHg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"is-promise": "^4.0.0",
|
||||
"rungen": "^0.3.2"
|
||||
}
|
||||
},
|
||||
"@wordpress/rich-text": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@wordpress/rich-text/-/rich-text-6.0.0.tgz",
|
||||
"integrity": "sha512-vzEzUn4VtYdnf3i96HPaIwxYvebw1t+Pubwma6TfBYa2pBI2Q8L6E+LaWkFtjv6Pw8pCv/+5m9sQPnUPo0K7zw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.16.0",
|
||||
"@wordpress/a11y": "^3.23.0",
|
||||
"@wordpress/compose": "^6.0.0",
|
||||
"@wordpress/data": "^8.0.0",
|
||||
"@wordpress/deprecated": "^3.23.0",
|
||||
"@wordpress/element": "^5.0.0",
|
||||
"@wordpress/escape-html": "^2.23.0",
|
||||
"@wordpress/i18n": "^4.23.0",
|
||||
"@wordpress/keycodes": "^3.23.0",
|
||||
"memize": "^1.1.0",
|
||||
"rememo": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
|
@ -881,6 +1154,39 @@
|
|||
"fill-range": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"camel-case": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz",
|
||||
"integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==",
|
||||
"requires": {
|
||||
"pascal-case": "^3.1.2",
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"capital-case": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz",
|
||||
"integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==",
|
||||
"requires": {
|
||||
"no-case": "^3.0.4",
|
||||
"tslib": "^2.0.3",
|
||||
"upper-case-first": "^2.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
|
@ -932,6 +1238,32 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"change-case": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz",
|
||||
"integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==",
|
||||
"requires": {
|
||||
"camel-case": "^4.1.2",
|
||||
"capital-case": "^1.0.4",
|
||||
"constant-case": "^3.0.4",
|
||||
"dot-case": "^3.0.4",
|
||||
"header-case": "^2.0.4",
|
||||
"no-case": "^3.0.4",
|
||||
"param-case": "^3.0.4",
|
||||
"pascal-case": "^3.1.2",
|
||||
"path-case": "^3.0.4",
|
||||
"sentence-case": "^3.0.4",
|
||||
"snake-case": "^3.0.4",
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"cli-highlight": {
|
||||
"version": "2.1.11",
|
||||
"resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz",
|
||||
|
@ -946,6 +1278,16 @@
|
|||
"yargs": "^16.0.0"
|
||||
}
|
||||
},
|
||||
"clipboard": {
|
||||
"version": "2.0.11",
|
||||
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
|
||||
"integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
|
||||
"requires": {
|
||||
"good-listener": "^1.2.2",
|
||||
"select": "^1.1.2",
|
||||
"tiny-emitter": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"cliui": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||
|
@ -978,6 +1320,23 @@
|
|||
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
|
||||
"dev": true
|
||||
},
|
||||
"constant-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz",
|
||||
"integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==",
|
||||
"requires": {
|
||||
"no-case": "^3.0.4",
|
||||
"tslib": "^2.0.3",
|
||||
"upper-case": "^2.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
|
||||
|
@ -1005,12 +1364,46 @@
|
|||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"delegate": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
|
||||
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
|
||||
},
|
||||
"dot-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
|
||||
"integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
|
||||
"requires": {
|
||||
"no-case": "^3.0.4",
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
},
|
||||
"encoding": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||
"requires": {
|
||||
"iconv-lite": "^0.6.2"
|
||||
}
|
||||
},
|
||||
"equivalent-key-map": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/equivalent-key-map/-/equivalent-key-map-0.2.2.tgz",
|
||||
"integrity": "sha512-xvHeyCDbZzkpN4VHQj/n+j2lOwL0VWszG30X4cOrc9Y7Tuo2qCdZK/0AMod23Z5dCtNUbaju6p0rwOhHUk05ew=="
|
||||
},
|
||||
"escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
|
@ -1066,6 +1459,15 @@
|
|||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"dev": true
|
||||
},
|
||||
"gettext-parser": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/gettext-parser/-/gettext-parser-1.4.0.tgz",
|
||||
"integrity": "sha512-sedZYLHlHeBop/gZ1jdg59hlUEcpcZJofLq2JFwJT1zTqAU3l2wFv6IsuwFHGqbiT9DWzMUW4/em2+hspnmMMA==",
|
||||
"requires": {
|
||||
"encoding": "^0.1.12",
|
||||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
|
@ -1081,6 +1483,14 @@
|
|||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||
"dev": true
|
||||
},
|
||||
"good-listener": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
|
||||
"integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
|
||||
"requires": {
|
||||
"delegate": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
|
@ -1093,6 +1503,22 @@
|
|||
"integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==",
|
||||
"dev": true
|
||||
},
|
||||
"header-case": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz",
|
||||
"integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==",
|
||||
"requires": {
|
||||
"capital-case": "^1.0.4",
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"highlight.js": {
|
||||
"version": "10.7.3",
|
||||
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
|
||||
|
@ -1109,6 +1535,14 @@
|
|||
"debug": "4"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
}
|
||||
},
|
||||
"is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
|
@ -1136,11 +1570,20 @@
|
|||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true
|
||||
},
|
||||
"is-plain-object": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
|
||||
},
|
||||
"is-promise": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
|
||||
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"jsesc": {
|
||||
"version": "2.5.2",
|
||||
|
@ -1162,6 +1605,29 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"requires": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"lower-case": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
|
||||
"integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
|
||||
"requires": {
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
|
@ -1186,6 +1652,11 @@
|
|||
"sourcemap-codec": "^1.4.8"
|
||||
}
|
||||
},
|
||||
"memize": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/memize/-/memize-1.1.0.tgz",
|
||||
"integrity": "sha512-K4FcPETOMTwe7KL2LK0orMhpOmWD2wRGwWWpbZy0fyArwsyIKR8YJVz8+efBAh3BO4zPqlSICu4vsLTRRqtFAg=="
|
||||
},
|
||||
"merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
|
@ -1208,6 +1679,11 @@
|
|||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"dev": true
|
||||
},
|
||||
"mousetrap": {
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz",
|
||||
"integrity": "sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
@ -1231,6 +1707,22 @@
|
|||
"integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==",
|
||||
"dev": true
|
||||
},
|
||||
"no-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
|
||||
"integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
|
||||
"requires": {
|
||||
"lower-case": "^2.0.2",
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-unique-machine-id": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-unique-machine-id/-/node-unique-machine-id-1.1.0.tgz",
|
||||
|
@ -1254,6 +1746,22 @@
|
|||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
|
||||
"dev": true
|
||||
},
|
||||
"param-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
|
||||
"integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==",
|
||||
"requires": {
|
||||
"dot-case": "^3.0.4",
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"parse5": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
|
||||
|
@ -1277,6 +1785,38 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"pascal-case": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
|
||||
"integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==",
|
||||
"requires": {
|
||||
"no-case": "^3.0.4",
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"path-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz",
|
||||
"integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==",
|
||||
"requires": {
|
||||
"dot-case": "^3.0.4",
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
|
@ -1312,6 +1852,46 @@
|
|||
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
|
||||
"dev": true
|
||||
},
|
||||
"react": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"react-dom": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.0"
|
||||
}
|
||||
},
|
||||
"redux": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz",
|
||||
"integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
},
|
||||
"rememo": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rememo/-/rememo-4.0.2.tgz",
|
||||
"integrity": "sha512-NVfSP9NstE3QPNs/TnegQY0vnJnstKQSpcrsI2kBTB3dB2PkdfKdTa+abbjMIDqpc63fE5LfjLgfMst0ULMFxQ=="
|
||||
},
|
||||
"requestidlecallback": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/requestidlecallback/-/requestidlecallback-0.3.0.tgz",
|
||||
"integrity": "sha512-TWHFkT7S9p7IxLC5A1hYmAYQx2Eb9w1skrXmQ+dS1URyvR8tenMLl4lHbqEOUnpEYxNKpkVMXUgknVpBZWXXfQ=="
|
||||
},
|
||||
"require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
|
@ -1339,6 +1919,67 @@
|
|||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"rungen": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/rungen/-/rungen-0.3.2.tgz",
|
||||
"integrity": "sha512-zWl10xu2D7zoR8zSC2U6bg5bYF6T/Wk7rxwp8IPaJH7f0Ge21G03kNHVgHR7tyVkSSfAOG0Rqf/Cl38JftSmtw=="
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"scheduler": {
|
||||
"version": "0.23.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
|
||||
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
|
||||
"integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
|
||||
},
|
||||
"sentence-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz",
|
||||
"integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==",
|
||||
"requires": {
|
||||
"no-case": "^3.0.4",
|
||||
"tslib": "^2.0.3",
|
||||
"upper-case-first": "^2.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"snake-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz",
|
||||
"integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==",
|
||||
"requires": {
|
||||
"dot-case": "^3.0.4",
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
|
@ -1357,6 +1998,11 @@
|
|||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
|
||||
"dev": true
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
|
||||
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug=="
|
||||
},
|
||||
"string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
|
@ -1386,6 +2032,14 @@
|
|||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"tannin": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tannin/-/tannin-1.2.0.tgz",
|
||||
"integrity": "sha512-U7GgX/RcSeUETbV7gYgoz8PD7Ni4y95pgIP/Z6ayI3CfhSujwKEBlGFTCRN+Aqnuyf4AN2yHL+L8x+TCGjb9uA==",
|
||||
"requires": {
|
||||
"@tannin/plural-forms": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"thenify": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||
|
@ -1404,6 +2058,11 @@
|
|||
"thenify": ">= 3.1.0 < 4"
|
||||
}
|
||||
},
|
||||
"tiny-emitter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
|
||||
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
|
@ -1425,12 +2084,52 @@
|
|||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
||||
"dev": true
|
||||
},
|
||||
"turbo-combine-reducers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/turbo-combine-reducers/-/turbo-combine-reducers-1.0.2.tgz",
|
||||
"integrity": "sha512-gHbdMZlA6Ym6Ur5pSH/UWrNQMIM9IqTH6SoL1DbHpqEdQ8i+cFunSmSlFykPt0eGQwZ4d/XTHOl74H0/kFBVWw=="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz",
|
||||
"integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==",
|
||||
"dev": true
|
||||
},
|
||||
"upper-case": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz",
|
||||
"integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==",
|
||||
"requires": {
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"upper-case-first": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz",
|
||||
"integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==",
|
||||
"requires": {
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
|
||||
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"use-memo-one": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz",
|
||||
"integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ=="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
"license": "GPL-3.0-or-later",
|
||||
"main": "lib/index.ts",
|
||||
"scripts": {
|
||||
"dev": "npm run json-to-md:watch",
|
||||
"typecheck": "vuedx-typecheck --no-pretty ./lib",
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
|
@ -27,6 +26,8 @@
|
|||
"url": "git@git.b12f.io:b12f/schlechtenburg.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@schlechtenburg/style": "^0.0.0",
|
||||
"@wordpress/rich-text": "^6.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
|
@ -36,6 +37,7 @@
|
|||
"devDependencies": {
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"@types/wordpress__rich-text": "^3.4.6",
|
||||
"@vuedx/typecheck": "^0.6.3",
|
||||
"@vuedx/typescript-plugin-vue": "^0.6.3",
|
||||
"vue": "^3.2.31"
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
node_modules
|
||||
*.log*
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
.output
|
||||
.env
|
||||
dist
|
|
@ -0,0 +1,42 @@
|
|||
# Nuxt 3 Minimal Starter
|
||||
|
||||
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure to install the dependencies:
|
||||
|
||||
```bash
|
||||
# yarn
|
||||
yarn install
|
||||
|
||||
# npm
|
||||
npm install
|
||||
|
||||
# pnpm
|
||||
pnpm install --shamefully-hoist
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on http://localhost:3000
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Build the application for production:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
||||
|
||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
|
@ -0,0 +1,73 @@
|
|||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ex-app {
|
||||
display: flex;
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
|
||||
--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;
|
||||
}
|
||||
|
||||
&--edit-nav {
|
||||
z-index: 100;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
&--page {
|
||||
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;
|
||||
width: unset;
|
||||
flex-basis: var(--ex-nav-width);
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import { defineComponent } from 'vue';
|
||||
import { NuxtPage } from '#components';
|
||||
|
||||
import './app.scss';
|
||||
|
||||
const AdminNav = defineAsyncComponent(() => import('~~/components/_/Nav'));
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const { me } = useMe();
|
||||
return () => (
|
||||
<div class="ex-app">
|
||||
{me.value ? <AdminNav class="ex-app--edit-nav" /> : null}
|
||||
<NuxtPage class="ex-app--page" />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
import { defineComponent } from 'vue';
|
||||
import { SbMain, SbMode } from '@schlechtenburg/core';
|
||||
import PageToolbar from '~~/components/PageToolbar';
|
||||
import SbLayout from '@schlechtenburg/layout';
|
||||
import SbHeading from '@schlechtenburg/heading';
|
||||
import SbParagraph from '@schlechtenburg/paragraph';
|
||||
import SbImage from '@schlechtenburg/image';
|
||||
|
||||
export default defineComponent({
|
||||
async setup() {
|
||||
const { me } = useMe();
|
||||
|
||||
const loggedIn = computed(() => !!me.value?.id);
|
||||
|
||||
const { currentPage } = useCurrentPage();
|
||||
const block = computed(() => currentPage.value?.attributes?.block);
|
||||
|
||||
const {
|
||||
mode,
|
||||
draft,
|
||||
updateDraft,
|
||||
} = useEditor();
|
||||
|
||||
watchEffect(() => {
|
||||
updateDraft(block.value!);
|
||||
});
|
||||
|
||||
|
||||
if (!block) {
|
||||
console.error('No block!');
|
||||
console.error('page', currentPage.value);
|
||||
}
|
||||
|
||||
return () => (
|
||||
<div class="ex-page">
|
||||
{loggedIn.value ? <PageToolbar></PageToolbar> : null}
|
||||
{draft.value
|
||||
? <SbMain
|
||||
class="ex-page"
|
||||
mode={mode.value}
|
||||
eventUpdate={(updatedBlock) => updateDraft(updatedBlock)}
|
||||
block={draft.value}
|
||||
availableBlocks={[
|
||||
SbLayout,
|
||||
SbHeading,
|
||||
SbParagraph,
|
||||
SbImage,
|
||||
]}
|
||||
/>
|
||||
: <div class="ex-page ex-page_corrupt">Corrupt page: {currentPage.value?.attributes?.slug} ({currentPage.value?.id})</div>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
.ex-page-breadcrumb {
|
||||
display: flex;
|
||||
}
|
|
@ -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>);
|
||||
};
|
||||
},
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
.ex-page-toolbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
border-bottom: 1px solid var(--grey-2);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import { defineComponent } from 'vue';
|
||||
import { SbButton, SbMode } from '@schlechtenburg/core';
|
||||
import PageBreadcrumb from '~~/components/PageBreadcrumb';
|
||||
|
||||
import './PageToolbar.scss';
|
||||
|
||||
export default defineComponent({
|
||||
async setup() {
|
||||
const {
|
||||
currentPage,
|
||||
currentPageId,
|
||||
setCurrentPageId,
|
||||
} = useCurrentPage();
|
||||
|
||||
const { pages, insertPage } = usePages();
|
||||
|
||||
const {
|
||||
mode,
|
||||
edit,
|
||||
cancel,
|
||||
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(currentPage.value?.attributes?.block!)}
|
||||
>Edit</SbButton>
|
||||
: <>
|
||||
<SbButton
|
||||
type="button"
|
||||
onClick={() => cancel()}
|
||||
>Cancel</SbButton>
|
||||
<SbButton
|
||||
type="button"
|
||||
onClick={() => save()}
|
||||
>Save</SbButton>
|
||||
</>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,80 @@
|
|||
.ex-edit-nav {
|
||||
background-color: white;
|
||||
width: var(--ex-nav-width);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
.icon {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
&_expanded {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
&_expanded &--menu-item {
|
||||
&-action {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
&-title {
|
||||
opacity: 1;
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1000px) {
|
||||
&--toggle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import { defineComponent } from 'vue';
|
||||
import { NuxtLink } from '#components';
|
||||
|
||||
import './Nav.scss';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const { setMe } = useMe();
|
||||
|
||||
const expanded = useState(() => false);
|
||||
const toggle = () => {
|
||||
expanded.value = !expanded.value;
|
||||
};
|
||||
|
||||
const classes = computed(() => ({
|
||||
'ex-edit-nav': true,
|
||||
'ex-edit-nav_expanded': expanded.value,
|
||||
}));
|
||||
|
||||
const logout = () => {
|
||||
setMe(null);
|
||||
useGqlToken({
|
||||
token: null,
|
||||
config: { type: 'Bearer' },
|
||||
});
|
||||
};
|
||||
|
||||
return () => (
|
||||
<nav class={classes.value}>
|
||||
<button
|
||||
class="ex-edit-nav--toggle"
|
||||
type="button"
|
||||
onClick={() => toggle()}
|
||||
aria-label="Toggle"
|
||||
>
|
||||
{expanded.value
|
||||
? <Icon name="icon-park-solid:expand-right" class="ex-edit-nav--menu-item-icon" />
|
||||
: <Icon name="icon-park-solid:expand-left" class="ex-edit-nav--menu-item-icon" />}
|
||||
</button>
|
||||
<ul class="ex-edit-nav--menu">
|
||||
<li class="ex-edit-nav--menu-item">
|
||||
<NuxtLink
|
||||
class="ex-edit-nav--menu-item-action"
|
||||
to="/"
|
||||
>
|
||||
<Icon name="mdi:web" class="ex-edit-nav--menu-item-icon" />
|
||||
<span class="ex-edit-nav--menu-item-title">Website</span>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
|
||||
<li class="ex-edit-nav--menu-spacer"></li>
|
||||
|
||||
<li class="ex-edit-nav--menu-item">
|
||||
<button
|
||||
type="button"
|
||||
class="ex-edit-nav--menu-item-action"
|
||||
onClick={() => logout()}
|
||||
>
|
||||
<Icon name="mdi:logout" class="ex-edit-nav--menu-item-icon" />
|
||||
<span class="ex-edit-nav--menu-item-title">Logout</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,102 @@
|
|||
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 draft = useState<IBlockData<any>|null>('draft', () => null);
|
||||
|
||||
const setMode = (newMode: SbMode) => {
|
||||
mode.value = newMode;
|
||||
};
|
||||
|
||||
const updateDraft = (newDraft: IBlockData<any>) => {
|
||||
draft.value = newDraft;
|
||||
}
|
||||
|
||||
const edit = (block: IBlockData<any>) => {
|
||||
draft.value = block;
|
||||
mode.value = SbMode.Edit;
|
||||
updateDraft(currentPage.value?.attributes?.block!);
|
||||
};
|
||||
|
||||
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 = () => {
|
||||
setMode(SbMode.View);
|
||||
if (currentPageId.value === 'draft') {
|
||||
setCurrentPageId(currentPage.value?.attributes?.parent?.data?.id || null);
|
||||
removePage('draft');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
mode,
|
||||
setMode,
|
||||
|
||||
edit,
|
||||
cancel,
|
||||
save,
|
||||
|
||||
draft,
|
||||
updateDraft,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
export interface IRole {
|
||||
id?: string|null;
|
||||
attributes?: {
|
||||
description: string;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IUser {
|
||||
id?: string|null;
|
||||
attributes?: {
|
||||
blocked?: boolean;
|
||||
confirmed?: boolean;
|
||||
email?: string;
|
||||
role?: IRole;
|
||||
username?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const useMe = () => {
|
||||
const me = useState<IUser|null>('me', () => null);
|
||||
|
||||
const setMe = (user: IUser|null) => {
|
||||
me.value = user;
|
||||
};
|
||||
|
||||
return {
|
||||
me,
|
||||
setMe,
|
||||
};
|
||||
};
|
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
import { IUser } from "~~/composables/states";
|
||||
|
||||
export default defineNuxtRouteMiddleware(async () => {
|
||||
const { me, setMe } = useMe();
|
||||
|
||||
const { data } = await useAsyncGql('me');
|
||||
|
||||
setMe((data.value?.me as IUser) || null);
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
import { IPage } from "~~/composables/pages";
|
||||
|
||||
export default defineNuxtRouteMiddleware(async (to) => {
|
||||
const { setCurrentPage } = useCurrentPage();
|
||||
const { fetchPages } = usePages();
|
||||
|
||||
|
||||
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!');
|
||||
console.error(error.value);
|
||||
return;
|
||||
}
|
||||
|
||||
const newPage = (data.value?.pages?.data[0] as IPage) || null;
|
||||
if (!newPage) {
|
||||
setResponseStatus(404)
|
||||
return;
|
||||
}
|
||||
setCurrentPage(newPage);
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
import { defineNuxtConfig } from 'nuxt/config';
|
||||
|
||||
export default defineNuxtConfig({
|
||||
modules: [
|
||||
'nuxt-graphql-client',
|
||||
'nuxt-icon',
|
||||
],
|
||||
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
GQL_HOST: 'http://localhost:1337/graphql', // overwritten by process.env.GQL_HOST
|
||||
},
|
||||
},
|
||||
});
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "@schlechtenburg/example-site",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nuxt": "3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@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",
|
||||
"nuxt-icon": "^0.1.8"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { defineComponent } from 'vue';
|
||||
import Page from '~~/components/Page';
|
||||
|
||||
export default defineComponent({
|
||||
async setup() {
|
||||
definePageMeta({
|
||||
middleware: ['authenticate', 'page'],
|
||||
});
|
||||
|
||||
return () => <Page />;
|
||||
},
|
||||
});
|
|
@ -0,0 +1,68 @@
|
|||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
async setup() {
|
||||
definePageMeta({
|
||||
middleware: ['authenticate'],
|
||||
});
|
||||
|
||||
const credentials = useState(() => ({
|
||||
identifier: '',
|
||||
password: '',
|
||||
}));
|
||||
|
||||
const onSubmit = async (event:Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const { data, error } = await useAsyncGql('login', credentials.value);
|
||||
|
||||
const { setMe } = useMe();
|
||||
if (error.value) {
|
||||
console.error('Failed to log in!');
|
||||
console.error('error:', error.value);
|
||||
console.error('data:', data.value);
|
||||
setMe(null);
|
||||
return;
|
||||
}
|
||||
|
||||
useGqlToken({
|
||||
token: data.value?.login?.jwt || null,
|
||||
config: { type: 'Bearer' },
|
||||
});
|
||||
setMe(data.value?.login?.user || null);
|
||||
navigateTo('/');
|
||||
|
||||
};
|
||||
|
||||
return () => (<div class="sbcms-admin">
|
||||
<form
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<h1>Login</h1>
|
||||
<label>
|
||||
Username
|
||||
<input
|
||||
type="text"
|
||||
value={credentials.value.identifier}
|
||||
onChange={(event: Event) => {
|
||||
credentials.value.identifier = (event.currentTarget as HTMLInputElement).value;
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Password
|
||||
<input
|
||||
type="password"
|
||||
value={credentials.value.password}
|
||||
onChange={(event: Event) => {
|
||||
credentials.value.password = (event.currentTarget as HTMLInputElement).value;
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
type="submit"
|
||||
>Log in</button>
|
||||
</form>
|
||||
</div>);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
import { defineComponent } from 'vue';
|
||||
import Page from '~~/components/Page';
|
||||
|
||||
export default defineComponent({
|
||||
async setup() {
|
||||
definePageMeta({
|
||||
middleware: ['authenticate', 'page'],
|
||||
});
|
||||
|
||||
return () => <Page />;
|
||||
},
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
mutation createPage($data: PageInput!) {
|
||||
createPage(data: $data) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
title
|
||||
slug
|
||||
block
|
||||
parent {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
mutation login($identifier: String!, $password: String!) {
|
||||
login(input: { identifier: $identifier, password: $password }) {
|
||||
jwt
|
||||
user {
|
||||
id
|
||||
username
|
||||
blocked
|
||||
confirmed
|
||||
email
|
||||
role {
|
||||
id
|
||||
description
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
query me {
|
||||
me {
|
||||
id
|
||||
username
|
||||
blocked
|
||||
confirmed
|
||||
email
|
||||
role {
|
||||
id
|
||||
description
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
query page($id: ID) {
|
||||
page(id: $id) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
title
|
||||
slug
|
||||
block
|
||||
parent {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
query pages($filters: PageFiltersInput) {
|
||||
pages(filters: $filters) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
title
|
||||
slug
|
||||
block
|
||||
parent {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
mutation updatePage($id: ID!, $data: PageInput!) {
|
||||
updatePage(id: $id, data: $data) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
title
|
||||
slug
|
||||
block
|
||||
parent {
|
||||
data {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
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(),
|
||||
name: layoutName,
|
||||
data: getDefaultLayoutData({
|
||||
children: [
|
||||
{
|
||||
id: generateBlockId(),
|
||||
name: headingName,
|
||||
data: getDefaultHeadingData({ value: 'New page' }),
|
||||
}
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
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, '');
|
||||
}
|
|
@ -4,8 +4,9 @@ export interface IHeadingData {
|
|||
level: number;
|
||||
}
|
||||
|
||||
export const getDefaultData: () => IHeadingData = () => ({
|
||||
export const getDefaultData: (data?: Partial<IHeadingData>) => IHeadingData = (data = {}) => ({
|
||||
value: '',
|
||||
align: 'left',
|
||||
level: 1,
|
||||
...data,
|
||||
});
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
"url": "git@git.b12f.io:b12f/schlechtenburg.git"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "npm run json-to-md:watch",
|
||||
"typecheck": "vuedx-typecheck --no-pretty ./lib",
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ export interface IImageData {
|
|||
description: IBlockData<IParagraphData>;
|
||||
}
|
||||
|
||||
export const getDefaultData: () => IImageData = () => ({
|
||||
export const getDefaultData: (data?: Partial<IImageData>) => IImageData = (data = {}) => ({
|
||||
src: '',
|
||||
alt: '',
|
||||
description: {
|
||||
|
@ -22,4 +22,5 @@ export const getDefaultData: () => IImageData = () => ({
|
|||
name: paragraphName,
|
||||
data: getDefaultParagraphData(),
|
||||
},
|
||||
...data,
|
||||
});
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
"url": "git@git.b12f.io:b12f/schlechtenburg.git"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "npm run json-to-md:watch",
|
||||
"typecheck": "vuedx-typecheck --no-pretty ./lib",
|
||||
"test": "echo \"Error: run tests from root\" && exit 1"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import {
|
||||
defineComponent,
|
||||
computed,
|
||||
PropType,
|
||||
} from 'vue';
|
||||
import {
|
||||
SbButton,
|
||||
getSelection,
|
||||
getRangeFromSelection,
|
||||
} from '@schlechtenburg/core';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
isActive: {
|
||||
type: (null as unknown) as PropType<HTMLElement|null>,
|
||||
default: null,
|
||||
},
|
||||
|
||||
eventChange: {
|
||||
type: (null as unknown) as PropType<Selection|null>,
|
||||
default: null,
|
||||
},
|
||||
|
||||
value: {
|
||||
type: (null as unknown) as PropType<Range|null>,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
return () => <SbButton
|
||||
type="button"
|
||||
class={{ 'sb-button_active': isActive.value }}
|
||||
onClick={() => {
|
||||
}}
|
||||
>i</SbButton>;
|
||||
},
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
import { defineAsyncComponent } from 'vue';
|
||||
import { IFormattingTool } from '@schlechtenburg/core';
|
||||
|
||||
export const name = '@schechtenburg/italic';
|
||||
|
||||
export default {
|
||||
name,
|
||||
title: 'Italic',
|
||||
icon: 'I',
|
||||
edit: defineAsyncComponent(() => import('./edit')),
|
||||
} as IFormattingTool;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue