Big improvements
This commit is contained in:
parent
a6b162eddb
commit
3ee4418ef3
30
docs/.vitepress/config.ts
Normal file
30
docs/.vitepress/config.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { defineConfig } from 'vitepress';
|
||||
|
||||
export default defineConfig({
|
||||
lang: 'en-US',
|
||||
title: 'Schlechtenburg',
|
||||
description: 'Experimental WYSIWYG editor framework made with Vue 3 and TypeScript',
|
||||
lastUpdated: true,
|
||||
|
||||
themeConfig: {
|
||||
sidebar: [
|
||||
{
|
||||
text: 'User Guide',
|
||||
items: [
|
||||
{ text: 'Introduction', link: '/' },
|
||||
{ text: 'Try it out', link: '/try' },
|
||||
{ text: 'Why Schlechtenburg?', link: '/why' },
|
||||
{ text: 'Installation and usage', link: '/installation' },
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'Development',
|
||||
items: [
|
||||
{ text: 'Introduction', link: '/' },
|
||||
{ text: 'Installation and usage', link: '/installation' },
|
||||
{ text: 'Getting Started', link: '/getting-started' },
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
|
@ -2,16 +2,40 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&--title {
|
||||
&--mode {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: stretch;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&--mode { }
|
||||
&--mode-tab {
|
||||
display: flex;
|
||||
margin-top: 1px;
|
||||
padding: 0.25rem 1rem;
|
||||
border-top: 1px solid var(--vp-c-divider-light);
|
||||
border-right: 1px solid var(--vp-c-divider-light);
|
||||
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&_checked {
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
}
|
||||
|
||||
&--sb {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--vp-c-divider-light);
|
||||
}
|
||||
|
||||
&--json {
|
||||
margin: 0;
|
||||
border: 1px solid var(--vp-c-divider-light);
|
||||
|
||||
background-color: var(--vp-c-text-1);
|
||||
color: var(--vp-c-bg);
|
||||
overflow: scroll;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,11 +22,12 @@ export default defineComponent({
|
|||
setup() {
|
||||
const activeTab = ref('edit');
|
||||
const block: IBlockData<any> = reactive({ ...exampleData });
|
||||
const dateID = +(new Date());
|
||||
|
||||
const displayedElement = computed(() => {
|
||||
switch (activeTab.value) {
|
||||
case 'data':
|
||||
return <pre><code>{ JSON.stringify(block, null, 2) }</code></pre>;
|
||||
return <pre class="example-editor--json"><code>{ JSON.stringify(block, null, 2) }</code></pre>;
|
||||
default:
|
||||
return <SbMain
|
||||
class="example-editor--sb"
|
||||
|
@ -38,26 +39,62 @@ export default defineComponent({
|
|||
SbParagraph,
|
||||
]}
|
||||
mode={activeTab.value as SbMode}
|
||||
eventUpdate={(data:IBlockData<any>) => {
|
||||
block.id = data.id;
|
||||
block.name = data.name;
|
||||
block.data = data.data;
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
});
|
||||
|
||||
const onModeChange = ($event: Event) => {
|
||||
activeTab.value = ($event.target as HTMLSelectElement).value;
|
||||
};
|
||||
|
||||
return () => {
|
||||
return <div class="example-editor">
|
||||
<h2 class="example-editor--title">
|
||||
<span>Try it yourself</span>
|
||||
<select
|
||||
class="example-editor--mode"
|
||||
value={activeTab.value}
|
||||
onChange={($event: Event) => {
|
||||
activeTab.value = ($event.target as HTMLSelectElement).value;
|
||||
}}
|
||||
>
|
||||
<option value={SbMode.Edit}>Editor mode</option>
|
||||
<option value={SbMode.View}>Viewer mode</option>
|
||||
<option value="data">JSON Data structure</option>
|
||||
</select>
|
||||
</h2>
|
||||
<div class="example-editor--mode">
|
||||
<label class={{
|
||||
'example-editor--mode-tab': true,
|
||||
'example-editor--mode-tab_checked': activeTab.value === SbMode.Edit,
|
||||
}}>
|
||||
<input
|
||||
type="radio"
|
||||
name={`example-editor-${dateID}`}
|
||||
value={SbMode.Edit}
|
||||
checked={activeTab.value === SbMode.Edit}
|
||||
onChange={onModeChange}
|
||||
/>
|
||||
Editor mode
|
||||
</label>
|
||||
<label class={{
|
||||
'example-editor--mode-tab': true,
|
||||
'example-editor--mode-tab_checked': activeTab.value === SbMode.View,
|
||||
}}>
|
||||
<input
|
||||
type="radio"
|
||||
name={`example-editor-${dateID}`}
|
||||
value={SbMode.View}
|
||||
checked={activeTab.value === SbMode.View}
|
||||
onChange={onModeChange}
|
||||
/>
|
||||
View mode
|
||||
</label>
|
||||
<label class={{
|
||||
'example-editor--mode-tab': true,
|
||||
'example-editor--mode-tab_checked': activeTab.value === "data",
|
||||
}}>
|
||||
<input
|
||||
type="radio"
|
||||
name={`example-editor-${dateID}`}
|
||||
value="data"
|
||||
checked={activeTab.value === "data"}
|
||||
onChange={onModeChange}
|
||||
/>
|
||||
JSON Data structure
|
||||
</label>
|
||||
</div>
|
||||
{displayedElement.value}
|
||||
</div>;
|
||||
};
|
||||
|
|
13896
docs/api.json
Normal file
13896
docs/api.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1 +0,0 @@
|
|||
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.
|
|
@ -1,64 +0,0 @@
|
|||
:root {
|
||||
--light-hl-0: #000000;
|
||||
--dark-hl-0: #C8C8C8;
|
||||
--light-hl-1: #000000;
|
||||
--dark-hl-1: #D4D4D4;
|
||||
--light-hl-2: #0000FF;
|
||||
--dark-hl-2: #569CD6;
|
||||
--light-hl-3: #AF00DB;
|
||||
--dark-hl-3: #C586C0;
|
||||
--light-hl-4: #267F99;
|
||||
--dark-hl-4: #4EC9B0;
|
||||
--light-hl-5: #001080;
|
||||
--dark-hl-5: #9CDCFE;
|
||||
--light-code-background: #F5F5F5;
|
||||
--dark-code-background: #1E1E1E;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) { :root {
|
||||
--hl-0: var(--light-hl-0);
|
||||
--hl-1: var(--light-hl-1);
|
||||
--hl-2: var(--light-hl-2);
|
||||
--hl-3: var(--light-hl-3);
|
||||
--hl-4: var(--light-hl-4);
|
||||
--hl-5: var(--light-hl-5);
|
||||
--code-background: var(--light-code-background);
|
||||
} }
|
||||
|
||||
@media (prefers-color-scheme: dark) { :root {
|
||||
--hl-0: var(--dark-hl-0);
|
||||
--hl-1: var(--dark-hl-1);
|
||||
--hl-2: var(--dark-hl-2);
|
||||
--hl-3: var(--dark-hl-3);
|
||||
--hl-4: var(--dark-hl-4);
|
||||
--hl-5: var(--dark-hl-5);
|
||||
--code-background: var(--dark-code-background);
|
||||
} }
|
||||
|
||||
body.light {
|
||||
--hl-0: var(--light-hl-0);
|
||||
--hl-1: var(--light-hl-1);
|
||||
--hl-2: var(--light-hl-2);
|
||||
--hl-3: var(--light-hl-3);
|
||||
--hl-4: var(--light-hl-4);
|
||||
--hl-5: var(--light-hl-5);
|
||||
--code-background: var(--light-code-background);
|
||||
}
|
||||
|
||||
body.dark {
|
||||
--hl-0: var(--dark-hl-0);
|
||||
--hl-1: var(--dark-hl-1);
|
||||
--hl-2: var(--dark-hl-2);
|
||||
--hl-3: var(--dark-hl-3);
|
||||
--hl-4: var(--dark-hl-4);
|
||||
--hl-5: var(--dark-hl-5);
|
||||
--code-background: var(--dark-code-background);
|
||||
}
|
||||
|
||||
.hl-0 { color: var(--hl-0); }
|
||||
.hl-1 { color: var(--hl-1); }
|
||||
.hl-2 { color: var(--hl-2); }
|
||||
.hl-3 { color: var(--hl-3); }
|
||||
.hl-4 { color: var(--hl-4); }
|
||||
.hl-5 { color: var(--hl-5); }
|
||||
pre, code { background: var(--code-background); }
|
File diff suppressed because it is too large
Load diff
Binary file not shown.
Before Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 28 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
Binary file not shown.
Before Width: | Height: | Size: 480 B |
Binary file not shown.
Before Width: | Height: | Size: 855 B |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,3 +0,0 @@
|
|||
<!DOCTYPE html><html class="default"><head><meta charSet="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>IBlockLibrary | schlechtenburg</title><meta name="description" content="Documentation for schlechtenburg"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script async src="../assets/search.js" id="search-script"></script></head><body><script>document.body.classList.add(localStorage.getItem("tsd-theme") || "os")</script><header><div class="tsd-page-toolbar"><div class="container"><div class="table-wrap"><div class="table-cell" id="tsd-search" data-base=".."><div class="field"><label for="tsd-search-field" class="tsd-widget search no-caption">Search</label><input type="text" id="tsd-search-field"/></div><ul class="results"><li class="state loading">Preparing search index...</li><li class="state failure">The search index is not available</li></ul><a href="../index.html" class="title">schlechtenburg</a></div><div class="table-cell" id="tsd-widgets"><div id="tsd-filter"><a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a><div class="tsd-filter-group"><div class="tsd-select" id="tsd-filter-visibility"><span class="tsd-select-label">All</span><ul class="tsd-select-list"><li data-value="public">Public</li><li data-value="protected">Public/Protected</li><li data-value="private" class="selected">All</li></ul></div> <input type="checkbox" id="tsd-filter-inherited" checked/><label class="tsd-widget" for="tsd-filter-inherited">Inherited</label><input type="checkbox" id="tsd-filter-externals" checked/><label class="tsd-widget" for="tsd-filter-externals">Externals</label></div></div><a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a></div></div></div></div><div class="tsd-page-title"><div class="container"><ul class="tsd-breadcrumb"><li><a href="../index.html">schlechtenburg</a></li><li><a href="../modules/_schlechtenburg_core.html">@schlechtenburg/core</a></li><li><a href="_schlechtenburg_core.IBlockLibrary.html">IBlockLibrary</a></li></ul><h1>Interface IBlockLibrary</h1></div></div></header><div class="container container-main"><div class="row"><div class="col-8 col-content"><section class="tsd-panel tsd-comment"><div class="tsd-comment tsd-typography"><div class="lead">
|
||||
<p>Schlechtenburg maintains a library of blocks that are available</p>
|
||||
</div><dl class="tsd-comment-tags"><dt>internal</dt><dd></dd></dl></div></section><section class="tsd-panel tsd-hierarchy"><h3>Hierarchy</h3><ul class="tsd-hierarchy"><li><span class="target">IBlockLibrary</span></li></ul></section><section class="tsd-panel tsd-kind-interface tsd-parent-kind-module"><h3 class="tsd-before-signature">Indexable</h3><div class="tsd-signature tsd-kind-icon"><span class="tsd-signature-symbol">[</span>name: <span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">]: </span><a href="_schlechtenburg_core.IBlockDefinition.html" class="tsd-signature-type" data-tsd-kind="Interface">IBlockDefinition</a><span class="tsd-signature-symbol"><</span><span class="tsd-signature-type">any</span><span class="tsd-signature-symbol">></span></div></section></div><div class="col-4 col-menu menu-sticky-wrap menu-highlight"><nav class="tsd-navigation primary"><ul><li class=""><a href="../index.html">Modules</a></li><li class="current tsd-kind-module"><a href="../modules/_schlechtenburg_core.html">@schlechtenburg/core</a></li><li class=" tsd-kind-module"><a href="../modules/_schlechtenburg_heading.html">@schlechtenburg/heading</a></li><li class=" tsd-kind-module"><a href="../modules/_schlechtenburg_image.html">@schlechtenburg/image</a></li><li class=" tsd-kind-module"><a href="../modules/_schlechtenburg_layout.html">@schlechtenburg/layout</a></li><li class=" tsd-kind-module"><a href="../modules/_schlechtenburg_paragraph.html">@schlechtenburg/paragraph</a></li><li class=" tsd-kind-module"><a href="../modules/_schlechtenburg_standalone.html">@schlechtenburg/standalone</a></li></ul></nav><nav class="tsd-navigation secondary menu-sticky"><ul><li class="current tsd-kind-interface tsd-parent-kind-module"><a href="_schlechtenburg_core.IBlockLibrary.html" class="tsd-kind-icon">IBlock<wbr/>Library</a><ul></ul></li></ul></nav></div></div></div><footer class="with-border-bottom"><div class="container"><h2>Legend</h2><div class="tsd-legend-group"><ul class="tsd-legend"><li class="tsd-kind-variable"><span class="tsd-kind-icon">Variable</span></li><li class="tsd-kind-function"><span class="tsd-kind-icon">Function</span></li><li class="tsd-kind-function tsd-has-type-parameter"><span class="tsd-kind-icon">Function with type parameter</span></li><li class="tsd-kind-type-alias"><span class="tsd-kind-icon">Type alias</span></li><li class="tsd-kind-type-alias tsd-has-type-parameter"><span class="tsd-kind-icon">Type alias with type parameter</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-interface"><span class="tsd-kind-icon">Interface</span></li><li class="tsd-kind-interface tsd-has-type-parameter"><span class="tsd-kind-icon">Interface with type parameter</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-enum"><span class="tsd-kind-icon">Enumeration</span></li></ul></div><h2>Settings</h2><p>Theme <select id="theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></p></div></footer><div class="container tsd-generator"><p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></div><div class="overlay"></div><script src="../assets/main.js"></script></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
docs/creating-blocks/index.md
Normal file
1
docs/creating-blocks/index.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Creating blocks
|
|
@ -1,20 +1,19 @@
|
|||
<script setup>
|
||||
import { withBase } from 'vitepress';
|
||||
import ExampleEditor from './ExampleEditor';
|
||||
</script>
|
||||
---
|
||||
layout: home
|
||||
|
||||
# Yet another WYSIWYG editor
|
||||
hero:
|
||||
name: Schlechtenburg
|
||||
text: WYSIWYG that's fun.
|
||||
tagline: What-you-see-is-what-you-get editors are unfortunately still really hard to deal with on the web. The default functionality is usually quickly deployed, but the honeymoon does not last long.
|
||||
|
||||
Schlechtenburg is an experimental WYSIWYG editor framework made with Vue 3 and TypeScript. It takes cues from both Wordpress' Gutenberg editor and CKEditor, though it tries to become a best of both worlds; a very lightweight, easily extensible core, written with modern components and the accompanying state management.
|
||||
|
||||
It inputs and outputs a tree of JSON-serializable data.
|
||||
Changes to editor behavior are always needed in the real-world, a fact that Schlechtenburg embraces.
|
||||
|
||||
This is still in the Proof-of-concept phase.
|
||||
|
||||
<div class="cta-row">
|
||||
<a :href="withBase('/guide/why')" class="button button_cta">Why Schlechtenburg?</a>
|
||||
<a :href="withBase('guide/introduction')" class="button">Get Started</a>
|
||||
<a :href="withBase('api')" class="button">See the API docs</a>
|
||||
</div>
|
||||
|
||||
<ExampleEditor></ExampleEditor>
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Try it out
|
||||
link: /try
|
||||
- theme: alt
|
||||
text: View Code
|
||||
link: https://git.b12f.io/b12f/schlechtenburg
|
||||
---
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Installation
|
||||
# Installation and usage
|
||||
|
||||
Schlechtenburg is very modular; consisting of one core package and multiple blocks. All packages are versioned together,
|
||||
meaning that v2.0.3 of one package is guaranteed to work with v2.0.3 of another schlechtenburg package.
|
||||
|
@ -6,7 +6,7 @@ meaning that v2.0.3 of one package is guaranteed to work with v2.0.3 of another
|
|||
Schlechtenburg is basically one Vue component, so if you're already using Vue you can import and use it directly.
|
||||
Otherwise, there's the standalone version that comes prepackaged with Vue.
|
||||
|
||||
## You're not yet using Vue
|
||||
## Your project does not use Vue 3
|
||||
|
||||
### Install npm packages
|
||||
|
||||
|
@ -63,7 +63,6 @@ useSchlechtenburg(
|
|||
// This callback will be alled any time the block data gets updated
|
||||
onUpdate: (blockData) => {
|
||||
console.log('Got new block data', blockData);
|
||||
|
||||
}
|
||||
}, //
|
||||
)
|
||||
|
@ -72,7 +71,7 @@ useSchlechtenburg(
|
|||
|
||||
**Note:** You need to provide both a root node
|
||||
|
||||
## You're already using Vue
|
||||
## Your project uses Vue 3
|
||||
|
||||
|
||||
### Install npm packages
|
||||
|
|
|
@ -23,11 +23,50 @@ html {
|
|||
|
||||
--interact: #3f9cff;
|
||||
--interact-lite: #3f9cff;
|
||||
|
||||
--z-context-menu: 3000;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.sb-doc h1,
|
||||
.sb-doc h2,
|
||||
.sb-doc h3,
|
||||
.sb-doc h4,
|
||||
.sb-doc h5,
|
||||
.sb-doc h6 {
|
||||
position: relative;
|
||||
font-weight: 600;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.sb-doc h1 {
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 40px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.sb-doc h2 {
|
||||
margin: 48px 0 16px;
|
||||
border-top: 1px solid var(--vp-c-divider-light);
|
||||
padding-top: 24px;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 32px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.sb-doc h3 {
|
||||
margin: 32px 0 0;
|
||||
letter-spacing: -0.01em;
|
||||
line-height: 28px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sb-doc h1 {
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 40px;
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
|
16
docs/try.md
Normal file
16
docs/try.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
layout: page
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import { withBase } from 'vitepress';
|
||||
import ExampleEditor from './ExampleEditor';
|
||||
</script>
|
||||
|
||||
<div class="sb-doc">
|
||||
|
||||
# Example Schlechtenburg Editor
|
||||
|
||||
</div>
|
||||
|
||||
<ExampleEditor></ExampleEditor>
|
1
docs/usage.md
Normal file
1
docs/usage.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Usage
|
18
docs/vite.config.ts
Normal file
18
docs/vite.config.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import { join } from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vueJsx(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@schlechtenburg/core': join(__dirname, '../packages/core/lib/index.ts'),
|
||||
'@schlechtenburg/paragraph': join(__dirname, '../packages/paragraph/lib/index.ts'),
|
||||
'@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'),
|
||||
},
|
||||
},
|
||||
});
|
|
@ -17,10 +17,10 @@ in their architecture:
|
|||
* They input and output a string
|
||||
* They have one global toolbar
|
||||
|
||||
Gutenberg is a bit more involved, literally using building "blocks" to create it's editor. Instead
|
||||
Gutenberg is a bit more involved, literally using building "blocks" to create its editor. Instead
|
||||
of seeing the content as a long string it takes a more component-esque approach. For example, the
|
||||
following things are all their own blocks in the gutenberg editor, which a specific react component
|
||||
that handles the editing mode, and one that handles the display mode.
|
||||
that handles the editing mode, and one that handles the display mode:
|
||||
|
||||
* Paragraph
|
||||
* Heading
|
||||
|
|
14010
package-lock.json
generated
14010
package-lock.json
generated
File diff suppressed because it is too large
Load diff
13
package.json
13
package.json
|
@ -3,13 +3,20 @@
|
|||
"version": "0.0.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"scripts": {
|
||||
"docs:build": "npx typedoc --out ./docs/api --entryPointStrategy packages --readme none packages/core packages/heading packages/standalone packages/paragraph packages/layout packages/image",
|
||||
"typecheck": "lerna run --stream typecheck"
|
||||
"docs:type:build": "npx typedoc --json ./docs/api.json --entryPointStrategy packages --readme none packages/core packages/heading packages/standalone packages/paragraph packages/layout packages/image",
|
||||
"typecheck": "lerna run --stream typecheck",
|
||||
"docs:dev": "vitepress dev docs",
|
||||
"docs:build": "vitepress build docs",
|
||||
"docs:serve": "vitepress serve docs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue-jsx": "^2.0.0",
|
||||
"lerna": "^3.22.1",
|
||||
"sass": "^1.54.4",
|
||||
"typedoc": "^0.22.13",
|
||||
"typescript": "^4.6.2"
|
||||
"typescript": "^4.6.2",
|
||||
"vitepress": "^1.0.0-alpha.13",
|
||||
"vue": "^3.2.37"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash-es": "^4.17.21"
|
||||
|
|
|
@ -45,47 +45,54 @@ export const SbBlock = defineComponent({
|
|||
type: (null as unknown) as PropType<IBlockData<any>>,
|
||||
required: true,
|
||||
},
|
||||
/**
|
||||
* The state for the block.
|
||||
*/
|
||||
indexAsChild: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
},
|
||||
/**
|
||||
* Called when the block should be updated.
|
||||
*/
|
||||
onUpdate: {
|
||||
eventUpdate: {
|
||||
type: (null as unknown) as PropType<OnUpdateBlockCb>,
|
||||
default: () => {},
|
||||
default: () => () => {},
|
||||
},
|
||||
/**
|
||||
* Called when a sibling block should be inserted before the block
|
||||
*/
|
||||
onPrependBlock: {
|
||||
eventPrependBlock: {
|
||||
type: (null as unknown) as PropType<OnPrependBlockCb>,
|
||||
default: () => {},
|
||||
default: () => () => {},
|
||||
},
|
||||
/**
|
||||
* Called when a sibling block should be inserted after the block
|
||||
*/
|
||||
onAppendBlock: {
|
||||
eventAppendBlock: {
|
||||
type: (null as unknown) as PropType<OnAppendBlockCb>,
|
||||
default: () => {},
|
||||
default: () => () => {},
|
||||
},
|
||||
/**
|
||||
* Called when the block should be removed
|
||||
*/
|
||||
onRemoveSelf: {
|
||||
eventRemoveSelf: {
|
||||
type: (null as unknown) as PropType<OnRemoveSelfCb>,
|
||||
default: () => {},
|
||||
default: () => () => {},
|
||||
},
|
||||
/**
|
||||
* Called when the previous sibling block should be activated
|
||||
*/
|
||||
onActivatePrevious: {
|
||||
eventActivatePrevious: {
|
||||
type: (null as unknown) as PropType<OnActivatePreviousCb>,
|
||||
default: () => {},
|
||||
default: () => () => {},
|
||||
},
|
||||
/**
|
||||
* Called when the next sibling block should be activated
|
||||
*/
|
||||
onActivateNext: {
|
||||
eventActivateNext: {
|
||||
type: (null as unknown) as PropType<OnActivateNextCb>,
|
||||
default: () => {},
|
||||
default: () => () => {},
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -105,11 +112,11 @@ export const SbBlock = defineComponent({
|
|||
watch(() => props.block.data, triggerSizeCalculation);
|
||||
|
||||
const { register } = useBlockTree();
|
||||
register(props.block);
|
||||
watch(props.block, () => { register(props.block); });
|
||||
register(props.block, props.indexAsChild);
|
||||
watch(props.block, () => { register(props.block, props.indexAsChild); });
|
||||
|
||||
const onChildUpdate = (updated: {[key: string]: any}) => {
|
||||
props.onUpdate({
|
||||
const eventChildUpdate = (updated: {[key: string]: any}) => {
|
||||
props.eventUpdate({
|
||||
...props.block,
|
||||
data: {
|
||||
...props.block.data,
|
||||
|
@ -151,12 +158,12 @@ export const SbBlock = defineComponent({
|
|||
<BlockComponent
|
||||
data={props.block.data}
|
||||
blockId={props.block.id}
|
||||
onUpdate={onChildUpdate}
|
||||
onPrependBlock={props.onPrependBlock}
|
||||
onAppendBlock={props.onAppendBlock}
|
||||
onRemoveSelf={props.onRemoveSelf}
|
||||
onActivatePrevious={props.onActivatePrevious}
|
||||
onActivateNext={props.onActivateNext}
|
||||
eventUpdate={eventChildUpdate}
|
||||
eventPrependBlock={props.eventPrependBlock}
|
||||
eventAppendBlock={props.eventAppendBlock}
|
||||
eventRemoveSelf={props.eventRemoveSelf}
|
||||
eventActivatePrevious={props.eventActivatePrevious}
|
||||
eventActivateNext={props.eventActivateNext}
|
||||
|
||||
{...{
|
||||
onClick: ($event: MouseEvent) => {
|
||||
|
|
|
@ -19,9 +19,9 @@ export const SbBlockOrdering = defineComponent({
|
|||
type: String,
|
||||
default: null,
|
||||
},
|
||||
onRemove: { type: Function, default: () => {} },
|
||||
onMoveBackward: { type: Function, default: () => {} },
|
||||
onMoveForward: { type: Function, default: () => {} },
|
||||
eventRemove: { type: Function, default: () => {} },
|
||||
eventMoveBackward: { type: Function, default: () => {} },
|
||||
eventMoveForward: { type: Function, default: () => {} },
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
|
@ -55,9 +55,9 @@ export const SbBlockOrdering = defineComponent({
|
|||
style={styles}
|
||||
onClick={($event: MouseEvent) => $event.stopPropagation()}
|
||||
>
|
||||
<SbButton {...{onClick: props.onMoveBackward}}>{props.orientation === 'vertical' ? '↑' : '←'}</SbButton>
|
||||
<SbButton {...{onClick: props.onRemove}}>x</SbButton>
|
||||
<SbButton {...{onClick: props.onMoveForward}}>{props.orientation === 'vertical' ? '↓' : '→'}</SbButton>
|
||||
<SbButton {...{onClick: props.eventMoveBackward}}>{props.orientation === 'vertical' ? '↑' : '←'}</SbButton>
|
||||
<SbButton {...{onClick: props.eventRemove}}>x</SbButton>
|
||||
<SbButton {...{onClick: props.eventMoveForward}}>{props.orientation === 'vertical' ? '↓' : '→'}</SbButton>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -16,7 +16,7 @@ export const SbBlockPicker = defineComponent({
|
|||
name: 'sb-block-picker',
|
||||
|
||||
props: {
|
||||
onPickedBlock: { type: Function, default: () => {} },
|
||||
eventPickedBlock: { type: Function, default: () => {} },
|
||||
},
|
||||
|
||||
setup(props, context) {
|
||||
|
@ -28,7 +28,7 @@ export const SbBlockPicker = defineComponent({
|
|||
|
||||
const selectBlock = (block: IBlockDefinition<any>) => {
|
||||
open.value = false;
|
||||
props.onPickedBlock({
|
||||
props.eventPickedBlock({
|
||||
name: block.name,
|
||||
id: generateBlockId(),
|
||||
data: block.getDefaultData(),
|
||||
|
|
|
@ -17,14 +17,14 @@ export const SbBlockPlaceholder = defineComponent({
|
|||
/**
|
||||
* Called when the user picked a block that should be inserted here.
|
||||
*/
|
||||
onInsertBlock: { type: Function, default: () => {} },
|
||||
eventInsertBlock: { type: Function, default: () => {} },
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
return () => (
|
||||
<div class="sb-block-placeholder">
|
||||
<SbBlockPicker
|
||||
onPickedBlock={(block: IBlockData<any>) => props.onInsertBlock(block)}
|
||||
eventPickedBlock={(block: IBlockData<any>) => props.eventInsertBlock(block)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
top: 100%;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
padding: 0.5rem 0.25rem;
|
||||
z-index: var(--z-context-menu);
|
||||
|
||||
max-height: 70vh;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
.sb-main {
|
||||
position: relative;
|
||||
color: var(--fg);
|
||||
background-color: var(--bg);
|
||||
|
||||
--grey-0: white;
|
||||
|
@ -21,7 +22,10 @@
|
|||
--interact: #3f9cff;
|
||||
--interact-lite: #3f9cff;
|
||||
|
||||
--z-toolbar: 2000;
|
||||
--z-context-menu: 3000;
|
||||
--z-tree-block-select: 4000;
|
||||
--z-modal: 10000;
|
||||
|
||||
*,
|
||||
*::before,
|
||||
|
|
|
@ -32,7 +32,7 @@ import { SbBlock } from './Block';
|
|||
export interface ISbMainProps {
|
||||
availableBlocks: IBlockDefinition<any>[];
|
||||
block: IBlockData<any>;
|
||||
onUpdate: OnUpdateBlockCb;
|
||||
eventUpdate: OnUpdateBlockCb;
|
||||
mode: SbMode;
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@ export const SbMain = defineComponent({
|
|||
/**
|
||||
* Called when the block should be updated.
|
||||
*/
|
||||
onUpdate: {
|
||||
eventUpdate: {
|
||||
type: (null as unknown) as PropType<OnUpdateBlockCb>,
|
||||
default: () => {},
|
||||
},
|
||||
|
@ -76,7 +76,6 @@ export const SbMain = defineComponent({
|
|||
provide(SymMode, mode);
|
||||
|
||||
watch(() => props.mode, (newMode) => {
|
||||
console.log('Mode update', newMode);
|
||||
mode.value = newMode;
|
||||
});
|
||||
|
||||
|
@ -112,7 +111,7 @@ export const SbMain = defineComponent({
|
|||
}
|
||||
<SbBlock
|
||||
block={props.block}
|
||||
onUpdate={props.onUpdate}
|
||||
eventUpdate={props.eventUpdate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
.sb-main-menu {
|
||||
display: flex;
|
||||
padding-bottom: 4rem;
|
||||
background-color: var(--grey-0);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
&__overlay {
|
||||
background-color: var(--grey-3-t);
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
z-index: var(--z-modal);
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
|
|
|
@ -4,4 +4,5 @@
|
|||
height: auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
z-index: var(--z-toolbar);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
.sb-tree-block-select {
|
||||
&__list {
|
||||
list-style: none;
|
||||
color: var(--fg);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
|
@ -9,9 +10,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
&__node {
|
||||
}
|
||||
|
||||
&__block {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
@ -23,7 +21,7 @@
|
|||
border: 0;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
padding: 0.5rem 1rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
|
|
@ -40,8 +40,9 @@ export const SbTreeBlockSelect = defineComponent({
|
|||
}
|
||||
</li>;
|
||||
|
||||
|
||||
return () => (
|
||||
blockTree.value
|
||||
console.log(blockTree.value) || blockTree.value
|
||||
? <SbContextMenu
|
||||
class="sb-tree-block-select"
|
||||
v-slots={{
|
||||
|
|
|
@ -10,6 +10,8 @@ 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');
|
||||
|
@ -17,8 +19,8 @@ export const SymBlockTreeUnregister = Symbol('Schlechtenburg block tree unregist
|
|||
|
||||
export function useBlockTree() {
|
||||
const blockTree: Ref<ITreeNode|null> = inject(SymBlockTree, ref(null));
|
||||
const registerWithParent = inject(SymBlockTreeRegister, (_: ITreeNode) => {});
|
||||
const unregisterWithParent = inject(SymBlockTreeUnregister, (_: ITreeNode) => {});
|
||||
const registerWithParent = inject(SymBlockTreeRegister, (_b: ITreeNode, _i: number) => {});
|
||||
const unregisterWithParent = inject(SymBlockTreeUnregister, (_b: ITreeNode) => {});
|
||||
|
||||
const self: ITreeNode = reactive({
|
||||
id: '',
|
||||
|
@ -28,14 +30,20 @@ export function useBlockTree() {
|
|||
});
|
||||
|
||||
// Provide a registration function to child blocks
|
||||
provide(SymBlockTreeRegister, (block: ITreeNode) => {
|
||||
provide(SymBlockTreeRegister, (block: ITreeNode, index:number = -1) => {
|
||||
if (self.children.find((child: ITreeNode) => child.id === block.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (index < 0) {
|
||||
}
|
||||
|
||||
const normalizedIndex = index < 0 ? 0 : index;
|
||||
|
||||
self.children =[
|
||||
...self.children,
|
||||
...self.children.slice(0, normalizedIndex),
|
||||
block,
|
||||
...self.children.slice(normalizedIndex),
|
||||
];
|
||||
});
|
||||
|
||||
|
@ -44,16 +52,23 @@ export function useBlockTree() {
|
|||
self.children = self.children.filter((child: ITreeNode) => child.id !== id);
|
||||
});
|
||||
|
||||
const register = (block: IBlockData<any>) => {
|
||||
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;
|
||||
|
||||
// Register ourselves at the parent block
|
||||
registerWithParent(self);
|
||||
registerWithParent(self, index);
|
||||
}
|
||||
|
||||
// Unregister from parent when we get destroyed
|
||||
|
|
|
@ -39,23 +39,23 @@ export default defineComponent({
|
|||
type: (null as unknown) as PropType<IHeadingData>,
|
||||
default: getDefaultData,
|
||||
},
|
||||
onUpdate: {
|
||||
eventUpdate: {
|
||||
type: (null as unknown) as PropType<OnUpdateSelfCb<IHeadingData>>,
|
||||
default: () => {},
|
||||
},
|
||||
onAppendBlock: {
|
||||
eventAppendBlock: {
|
||||
type: (null as unknown) as PropType<OnAppendBlockCb>,
|
||||
default: () => {},
|
||||
},
|
||||
onRemoveSelf: {
|
||||
eventRemoveSelf: {
|
||||
type: (null as unknown) as PropType<OnRemoveSelfCb>,
|
||||
default: () => {},
|
||||
},
|
||||
onActivateNext: {
|
||||
eventActivateNext: {
|
||||
type: (null as unknown) as PropType<OnActivateNextCb>,
|
||||
default: () => {},
|
||||
},
|
||||
onActivatePrevious: {
|
||||
eventActivatePrevious: {
|
||||
type: (null as unknown) as PropType<OnActivatePreviousCb>,
|
||||
default: () => {},
|
||||
},
|
||||
|
@ -114,14 +114,14 @@ export default defineComponent({
|
|||
}));
|
||||
|
||||
const setLevel = ($event: Event) => {
|
||||
props.onUpdate({
|
||||
props.eventUpdate({
|
||||
...localData,
|
||||
level: parseInt(($event.target as HTMLSelectElement).value, 10),
|
||||
});
|
||||
};
|
||||
|
||||
const setAlignment = ($event: Event) => {
|
||||
props.onUpdate({
|
||||
props.eventUpdate({
|
||||
...localData,
|
||||
align: ($event.target as HTMLSelectElement).value,
|
||||
});
|
||||
|
@ -134,7 +134,7 @@ export default defineComponent({
|
|||
|
||||
const onBlur = () => {
|
||||
localData.focused = false;
|
||||
props.onUpdate({
|
||||
props.eventUpdate({
|
||||
value: localData.value,
|
||||
align: localData.align,
|
||||
level: localData.level,
|
||||
|
@ -144,7 +144,7 @@ export default defineComponent({
|
|||
const onKeydown = ($event: KeyboardEvent) => {
|
||||
if ($event.key === 'Enter' && !$event.shiftKey) {
|
||||
const id = generateBlockId();
|
||||
props.onAppendBlock({
|
||||
props.eventAppendBlock({
|
||||
id,
|
||||
name: 'sb-paragraph',
|
||||
data: getDefaultParagraphData(),
|
||||
|
@ -158,7 +158,7 @@ export default defineComponent({
|
|||
|
||||
const onKeyup = ($event: KeyboardEvent) => {
|
||||
if ($event.key === 'Backspace' && localData.value === '') {
|
||||
props.onRemoveSelf();
|
||||
props.eventRemoveSelf();
|
||||
}
|
||||
|
||||
const selection = window.getSelection();
|
||||
|
@ -168,10 +168,10 @@ export default defineComponent({
|
|||
if (node === inputEl.value || index === 0 || index === childNodes.length -1) {
|
||||
switch ($event.key) {
|
||||
case 'ArrowDown':
|
||||
props.onActivateNext();
|
||||
props.eventActivateNext();
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
props.onActivatePrevious();
|
||||
props.eventActivatePrevious();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
.sb-heading {
|
||||
flex-basis: 100%;
|
||||
font-weight: bold;
|
||||
line-height: 1.2;
|
||||
|
||||
&_1 {
|
||||
font-size: 4rem;
|
||||
|
|
|
@ -28,7 +28,7 @@ export default defineComponent({
|
|||
model,
|
||||
|
||||
props: {
|
||||
onUpdate: {
|
||||
eventUpdate: {
|
||||
type: (null as unknown) as PropType<OnUpdateSelfCb<IImageData>>,
|
||||
default: () => {},
|
||||
},
|
||||
|
@ -68,7 +68,7 @@ export default defineComponent({
|
|||
throw new Error('Couldn\'t load image src');
|
||||
}
|
||||
|
||||
props.onUpdate({
|
||||
props.eventUpdate({
|
||||
src,
|
||||
alt: props.data.alt,
|
||||
description: props.data.description,
|
||||
|
@ -80,7 +80,7 @@ export default defineComponent({
|
|||
};
|
||||
|
||||
const onDescriptionUpdate = (description: IBlockData<IParagraphData>) => {
|
||||
props.onUpdate({
|
||||
props.eventUpdate({
|
||||
...props.data,
|
||||
description,
|
||||
});
|
||||
|
|
|
@ -31,7 +31,7 @@ export default defineComponent({
|
|||
model,
|
||||
|
||||
props: {
|
||||
onUpdate: {
|
||||
eventUpdate: {
|
||||
type: (null as unknown) as PropType<OnUpdateSelfCb<ILayoutData>>,
|
||||
default: () => {},
|
||||
},
|
||||
|
@ -60,7 +60,7 @@ export default defineComponent({
|
|||
}));
|
||||
|
||||
const toggleOrientation = () => {
|
||||
props.onUpdate({
|
||||
props.eventUpdate({
|
||||
orientation: localData.orientation === 'vertical' ? 'horizontal' : 'vertical',
|
||||
});
|
||||
};
|
||||
|
@ -70,7 +70,7 @@ export default defineComponent({
|
|||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
props.onUpdate({
|
||||
props.eventUpdate({
|
||||
children: [
|
||||
...localData.children.slice(0, index),
|
||||
{
|
||||
|
@ -87,7 +87,7 @@ export default defineComponent({
|
|||
...localData.children,
|
||||
block,
|
||||
];
|
||||
props.onUpdate({ children: [...localData.children] });
|
||||
props.eventUpdate({ children: [...localData.children] });
|
||||
activate(block.id);
|
||||
};
|
||||
|
||||
|
@ -97,7 +97,7 @@ export default defineComponent({
|
|||
block,
|
||||
...localData.children.slice(index + 1),
|
||||
];
|
||||
props.onUpdate({ children: [...localData.children] });
|
||||
props.eventUpdate({ children: [...localData.children] });
|
||||
activate(block.id);
|
||||
};
|
||||
|
||||
|
@ -106,7 +106,7 @@ export default defineComponent({
|
|||
...localData.children.slice(0, index),
|
||||
...localData.children.slice(index + 1),
|
||||
];
|
||||
props.onUpdate({ children: [...localData.children] });
|
||||
props.eventUpdate({ children: [...localData.children] });
|
||||
|
||||
const newActiveIndex = Math.max(index - 1, 0);
|
||||
activate(localData.children[newActiveIndex].id);
|
||||
|
@ -138,7 +138,7 @@ export default defineComponent({
|
|||
...localData.children.slice(index + 1),
|
||||
];
|
||||
|
||||
props.onUpdate({ children: [...localData.children] });
|
||||
props.eventUpdate({ children: [...localData.children] });
|
||||
};
|
||||
|
||||
const moveForward = (index: number) => {
|
||||
|
@ -155,7 +155,7 @@ export default defineComponent({
|
|||
...localData.children.slice(index + 2),
|
||||
];
|
||||
|
||||
props.onUpdate({ children: [...localData.children] });
|
||||
props.eventUpdate({ children: [...localData.children] });
|
||||
};
|
||||
|
||||
return () => (
|
||||
|
@ -174,20 +174,21 @@ export default defineComponent({
|
|||
<SbBlock
|
||||
{...{ key: child.id }}
|
||||
data-order={index}
|
||||
indexAsChild={index}
|
||||
block={child}
|
||||
onUpdate={(updated: IBlockData<any>) => onChildUpdate(child, updated)}
|
||||
onRemoveSelf={() => removeBlock(index)}
|
||||
onPrependBlock={(block: IBlockData<any>) => insertBlock(index - 1, block)}
|
||||
onAppendBlock={(block: IBlockData<any>) => insertBlock(index, block)}
|
||||
onActivatePrevious={() => activateBlock(index - 1,)}
|
||||
onActivateNext={() => activateBlock(index + 1,)}
|
||||
eventUpdate={(updated: IBlockData<any>) => onChildUpdate(child, updated)}
|
||||
eventRemoveSelf={() => removeBlock(index)}
|
||||
eventPrependBlock={(block: IBlockData<any>) => insertBlock(index - 1, block)}
|
||||
eventAppendBlock={(block: IBlockData<any>) => insertBlock(index, block)}
|
||||
eventActivatePrevious={() => activateBlock(index - 1,)}
|
||||
eventActivateNext={() => activateBlock(index + 1,)}
|
||||
>
|
||||
{{
|
||||
'context-toolbar': () =>
|
||||
<SbBlockOrdering
|
||||
onMoveBackward={() => moveBackward(index)}
|
||||
onMoveForward={() => moveForward(index)}
|
||||
onRemove={() => removeBlock(index)}
|
||||
eventMoveBackward={() => moveBackward(index)}
|
||||
eventMoveForward={() => moveForward(index)}
|
||||
eventRemove={() => removeBlock(index)}
|
||||
orientation={localData.orientation}
|
||||
/>,
|
||||
}}
|
||||
|
@ -195,7 +196,7 @@ export default defineComponent({
|
|||
))}
|
||||
</>
|
||||
|
||||
<SbBlockPlaceholder onInsertBlock={appendBlock}></SbBlockPlaceholder>
|
||||
<SbBlockPlaceholder eventInsertBlock={appendBlock}></SbBlockPlaceholder>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
11
packages/paragraph/lib/contenteditable.ts
Normal file
11
packages/paragraph/lib/contenteditable.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
export const isEmptyContentEditable = (value:string) => {
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value === '<br>') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
|
@ -15,6 +15,7 @@ import {
|
|||
SbSelect,
|
||||
generateBlockId,
|
||||
} from '@schlechtenburg/core';
|
||||
import { isEmptyContentEditable } from './contenteditable';
|
||||
import {
|
||||
getDefaultData,
|
||||
IParagraphData,
|
||||
|
@ -33,23 +34,23 @@ export default defineComponent({
|
|||
type: (null as unknown) as PropType<IParagraphData>,
|
||||
default: getDefaultData,
|
||||
},
|
||||
onUpdate: {
|
||||
eventUpdate: {
|
||||
type: (null as unknown) as PropType<((block?: Partial<IParagraphData>) => void)>,
|
||||
default: () => {},
|
||||
},
|
||||
onAppendBlock: {
|
||||
eventAppendBlock: {
|
||||
type: (null as unknown) as PropType<((block?: any) => void)>,
|
||||
default: () => {},
|
||||
},
|
||||
onRemoveSelf: {
|
||||
eventRemoveSelf: {
|
||||
type: (null as unknown) as PropType<() => void>,
|
||||
default: () => {},
|
||||
},
|
||||
onActivateNext: {
|
||||
eventActivateNext: {
|
||||
type: (null as unknown) as PropType<() => void>,
|
||||
default: () => {},
|
||||
},
|
||||
onActivatePrevious: {
|
||||
eventActivatePrevious: {
|
||||
type: (null as unknown) as PropType<() => void>,
|
||||
default: () => {},
|
||||
},
|
||||
|
@ -104,7 +105,7 @@ export default defineComponent({
|
|||
}));
|
||||
|
||||
const setAlignment = ($event: Event) => {
|
||||
props.onUpdate({
|
||||
props.eventUpdate({
|
||||
value: localData.value,
|
||||
align: ($event.target as HTMLSelectElement).value,
|
||||
});
|
||||
|
@ -117,7 +118,7 @@ export default defineComponent({
|
|||
|
||||
const onBlur = () => {
|
||||
localData.focused = false;
|
||||
props.onUpdate({
|
||||
props.eventUpdate({
|
||||
value: localData.value,
|
||||
align: localData.align,
|
||||
});
|
||||
|
@ -126,7 +127,7 @@ export default defineComponent({
|
|||
const onKeydown = ($event: KeyboardEvent) => {
|
||||
if ($event.key === 'Enter' && !$event.shiftKey) {
|
||||
const id = generateBlockId();
|
||||
props.onAppendBlock({
|
||||
props.eventAppendBlock({
|
||||
id,
|
||||
name: 'sb-paragraph',
|
||||
data: getDefaultData(),
|
||||
|
@ -139,8 +140,8 @@ export default defineComponent({
|
|||
};
|
||||
|
||||
const onKeyup = ($event: KeyboardEvent) => {
|
||||
if ($event.key === 'Backspace' && localData.value === '') {
|
||||
props.onRemoveSelf();
|
||||
if ($event.key === 'Backspace' && isEmptyContentEditable(localData.value)) {
|
||||
props.eventRemoveSelf();
|
||||
}
|
||||
|
||||
const selection = window.getSelection();
|
||||
|
@ -150,10 +151,10 @@ export default defineComponent({
|
|||
if (node === inputEl.value || index === 0 || index === childNodes.length -1) {
|
||||
switch ($event.key) {
|
||||
case 'ArrowDown':
|
||||
props.onActivateNext();
|
||||
props.eventActivateNext();
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
props.onActivatePrevious();
|
||||
props.eventActivatePrevious();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue