Initial commit
This commit is contained in:
parent
76df0f29e9
commit
3062bce106
17
package-lock.json
generated
17
package-lock.json
generated
|
@ -2226,6 +2226,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@vue/composition-api": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@vue/composition-api/-/composition-api-0.5.0.tgz",
|
||||
"integrity": "sha512-9QDFWq7q839G1CTTaxeruPOTrrVOPSaVipJ2TxxK9QAruePNTHOGbOOFRpc8WLl4YPsu1/c29yBhMVmrXz8BZw==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@vue/eslint-config-airbnb": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@vue/eslint-config-airbnb/-/eslint-config-airbnb-5.0.2.tgz",
|
||||
|
@ -14771,8 +14779,7 @@
|
|||
"tslib": {
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
|
||||
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
|
||||
},
|
||||
"tslint": {
|
||||
"version": "5.20.1",
|
||||
|
@ -15323,6 +15330,12 @@
|
|||
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
|
||||
"dev": true
|
||||
},
|
||||
"vuex": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.4.0.tgz",
|
||||
"integrity": "sha512-ajtqwEW/QhnrBZQsZxCLHThZZaa+Db45c92Asf46ZDXu6uHXgbfVuBaJ4gzD2r4UX0oMJHstFwd2r2HM4l8umg==",
|
||||
"dev": true
|
||||
},
|
||||
"w3c-hr-time": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/composition-api": "^0.5.0",
|
||||
"core-js": "^3.6.4",
|
||||
"vue": "^2.6.11"
|
||||
},
|
||||
|
@ -34,6 +35,10 @@
|
|||
"sass": "^1.26.3",
|
||||
"sass-loader": "^8.0.2",
|
||||
"typescript": "~3.8.3",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vuex": "^3.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vuex": "^3.4.0"
|
||||
}
|
||||
}
|
||||
|
|
7
src/App.scss
Normal file
7
src/App.scss
Normal file
|
@ -0,0 +1,7 @@
|
|||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
}
|
36
src/App.tsx
Normal file
36
src/App.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import {
|
||||
defineComponent,
|
||||
reactive,
|
||||
watchEffect,
|
||||
} from '@vue/composition-api';
|
||||
import Schlechtenburg from '@components/Schlechtenburg';
|
||||
|
||||
import './App.scss';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'App',
|
||||
|
||||
setup() {
|
||||
const tree = reactive({
|
||||
component: 'sb-layout',
|
||||
orientation: 'vertical',
|
||||
children: [],
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
console.log(tree);
|
||||
});
|
||||
|
||||
return { tree };
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id="app">
|
||||
<Schlechtenburg vModel={this.tree} />
|
||||
|
||||
<pre><code>{JSON.stringify(this.tree, null, 2)}</code></pre>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
29
src/App.vue
29
src/App.vue
|
@ -1,29 +0,0 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<img alt="Vue logo" src="./assets/logo.png">
|
||||
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import HelloWorld from './components/HelloWorld.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'App',
|
||||
components: {
|
||||
HelloWorld,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
}
|
||||
</style>
|
|
@ -1,63 +0,0 @@
|
|||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<p>
|
||||
For a guide and recipes on how to configure / customize this project,<br>
|
||||
check out the
|
||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
||||
</p>
|
||||
<h3>Installed CLI Plugins</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">typescript</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-unit-jest" target="_blank" rel="noopener">unit-jest</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-e2e-nightwatch" target="_blank" rel="noopener">e2e-nightwatch</a></li>
|
||||
</ul>
|
||||
<h3>Essential Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
||||
</ul>
|
||||
<h3>Ecosystem</h3>
|
||||
<ul>
|
||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'HelloWorld',
|
||||
props: {
|
||||
msg: String,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="scss">
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
58
src/components/Schlechtenburg.tsx
Normal file
58
src/components/Schlechtenburg.tsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
import {
|
||||
defineComponent,
|
||||
computed,
|
||||
reactive,
|
||||
} from '@vue/composition-api';
|
||||
import { model, useDynamicComponents } from '@components/TreeElement';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'schlechtenburg-main',
|
||||
|
||||
model,
|
||||
|
||||
props: {
|
||||
components: { type: Object, default: () => ({}) },
|
||||
tree: { type: Object, required: true },
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const userComponents = computed(() => ({
|
||||
...props.components,
|
||||
}));
|
||||
|
||||
const { getComponent } = useDynamicComponents(userComponents);
|
||||
|
||||
const state = reactive({
|
||||
activeBlockId: null,
|
||||
});
|
||||
const activate = (id) => {
|
||||
this.state.activeBlockId = id;
|
||||
};
|
||||
|
||||
return {
|
||||
getComponent,
|
||||
userComponents,
|
||||
activate,
|
||||
state,
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
const Component = this.getComponent(this.tree.component);
|
||||
console.log(this.tree, Component);
|
||||
return (
|
||||
<Component
|
||||
class="sb-main"
|
||||
user-components={this.components}
|
||||
tree={this.tree}
|
||||
active-block-id={this.state.activeBlockId}
|
||||
{...{
|
||||
on: {
|
||||
tree: (tree) => this.$emit('tree', { ...tree }),
|
||||
activate: this.activate,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
42
src/components/TreeElement.ts
Normal file
42
src/components/TreeElement.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import {
|
||||
reactive,
|
||||
watchEffect,
|
||||
PropType,
|
||||
} from '@vue/composition-api';
|
||||
|
||||
type IComponentDefinition = { [name: string]: () => Promise<any> };
|
||||
|
||||
type ITreeElement = {
|
||||
component: string;
|
||||
id: string;
|
||||
data: { [name: string]: any };
|
||||
}
|
||||
|
||||
type ITreeElementProps = {
|
||||
userComponents: IComponentDefinition;
|
||||
id: string;
|
||||
data: { [key: string]: any};
|
||||
};
|
||||
|
||||
export const model = {
|
||||
prop: 'tree',
|
||||
event: 'tree-update',
|
||||
};
|
||||
|
||||
export const treeElementProps = {
|
||||
userComponents: {
|
||||
type: (Object as unknown) as PropType<IComponentDefinition>,
|
||||
required: true,
|
||||
},
|
||||
component: { type: Object, required: true },
|
||||
id: { type: String, required: true },
|
||||
data: { type: Object, default: () => ({}) },
|
||||
};
|
||||
|
||||
// export function useActivation
|
||||
|
||||
export function useDynamicComponents(components: IComponentDefinition) {
|
||||
const getComponent = (name: string) => components[name];
|
||||
|
||||
return { getComponent };
|
||||
}
|
25
src/components/internal/Block.scss
Normal file
25
src/components/internal/Block.scss
Normal file
|
@ -0,0 +1,25 @@
|
|||
.sb-block {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-items: stretch;
|
||||
min-height: 50px;
|
||||
|
||||
> .sb-toolbar {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
outline: 1px solid var(--grey-2);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
> .sb-toolbar {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
outline: 1px solid var(--grey-2);
|
||||
}
|
||||
}
|
||||
}
|
48
src/components/internal/Block.tsx
Normal file
48
src/components/internal/Block.tsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { computed, defineComponent } from '@vue/composition-api';
|
||||
|
||||
import './Block.scss';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'sb-block',
|
||||
|
||||
props: {
|
||||
active: { type: Boolean, default: false },
|
||||
},
|
||||
|
||||
setup(props, context) {
|
||||
const classes = computed(() => ({
|
||||
'sb-block': true,
|
||||
'sb-block_active': props.active,
|
||||
}));
|
||||
|
||||
const activate = () => {
|
||||
if (props.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.emit('activate');
|
||||
};
|
||||
|
||||
return {
|
||||
classes,
|
||||
activate,
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
class="sb-block"
|
||||
tabindex="0"
|
||||
{...{
|
||||
on: {
|
||||
click: this.activate,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{this.$slots.toolbar}
|
||||
{this.$slots.default ? this.$slots.default : <div>Your content here</div>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
33
src/components/internal/BlockChooser.tsx
Normal file
33
src/components/internal/BlockChooser.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { defineComponent } from '@vue/composition-api';
|
||||
import { treeElementProps, useDynamicComponents } from './TreeElement';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
...treeElementProps,
|
||||
orientation: String,
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const getComponent = useDynamicComponents(props.components || {});
|
||||
|
||||
return {
|
||||
getComponent,
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="sb-layout">
|
||||
{{ orientation }}
|
||||
|
||||
<component
|
||||
class="sb-main"
|
||||
v-for="child in children"
|
||||
:key="child.id"
|
||||
:is="getComponent(child.name)"
|
||||
v-bind="child"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
19
src/components/internal/BlockPlaceholder.scss
Normal file
19
src/components/internal/BlockPlaceholder.scss
Normal file
|
@ -0,0 +1,19 @@
|
|||
.sb-block-placeholder {
|
||||
width: 100%;
|
||||
height: 0px;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
|
||||
&__add {
|
||||
background-color: var(--grey-1);
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
}
|
29
src/components/internal/BlockPlaceholder.tsx
Normal file
29
src/components/internal/BlockPlaceholder.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { defineComponent } from '@vue/composition-api';
|
||||
|
||||
import './BlockPlaceholder.scss';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'sb-block-placeholder',
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="sb-block-placeholder">
|
||||
<button
|
||||
class="sb-block-placeholder__add"
|
||||
type="button"
|
||||
{...{
|
||||
on: {
|
||||
click: () => this.$emit('add-block', {
|
||||
component: 'sb-paragraph',
|
||||
id: +(new Date()),
|
||||
value: '',
|
||||
}),
|
||||
},
|
||||
}}
|
||||
>
|
||||
{this.$slots.default ? this.$slots.default : 'Add a block'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
7
src/components/internal/Toolbar.scss
Normal file
7
src/components/internal/Toolbar.scss
Normal file
|
@ -0,0 +1,7 @@
|
|||
.sb-toolbar {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background-color: var(--grey-1);
|
||||
}
|
15
src/components/internal/Toolbar.tsx
Normal file
15
src/components/internal/Toolbar.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { defineComponent } from '@vue/composition-api';
|
||||
|
||||
import './Toolbar.scss';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'sb-toolbar',
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="sb-toolbar">
|
||||
{this.$slots.default}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
0
src/components/user/Heading.tsx
Normal file
0
src/components/user/Heading.tsx
Normal file
0
src/components/user/Image.tsx
Normal file
0
src/components/user/Image.tsx
Normal file
11
src/components/user/Layout.scss
Normal file
11
src/components/user/Layout.scss
Normal file
|
@ -0,0 +1,11 @@
|
|||
.sb-layout {
|
||||
display: flex;
|
||||
|
||||
&_vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&_horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
124
src/components/user/Layout.tsx
Normal file
124
src/components/user/Layout.tsx
Normal file
|
@ -0,0 +1,124 @@
|
|||
import {
|
||||
reactive,
|
||||
defineComponent,
|
||||
watchEffect,
|
||||
} from '@vue/composition-api';
|
||||
import {
|
||||
model,
|
||||
treeElementProps,
|
||||
useDynamicComponents,
|
||||
} from '@components/TreeElement';
|
||||
|
||||
import SbBlock from '@internal/Block';
|
||||
import SbToolbar from '@internal/Toolbar';
|
||||
import SbBlockPlaceholder from '@internal/BlockPlaceholder';
|
||||
|
||||
import './Layout.scss';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'sb-layout',
|
||||
|
||||
model,
|
||||
|
||||
props: {
|
||||
...treeElementProps,
|
||||
},
|
||||
|
||||
setup(props, context) {
|
||||
const { getComponent } = useDynamicComponents(props.userComponents);
|
||||
|
||||
const localData = reactive({
|
||||
orientation: props.tree.data.orientation,
|
||||
children: [...props.tree.data.children],
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
localData.orientation = props.tree.data.orientation;
|
||||
localData.children = [...props.tree.data.children];
|
||||
});
|
||||
|
||||
const classes = {
|
||||
'sb-layout': true,
|
||||
[`sb-layout_${localData.orientation}`]: true,
|
||||
};
|
||||
|
||||
const toggleOrientation = () => {
|
||||
context.emit('data', {
|
||||
id: props.blockId,
|
||||
|
||||
...localData,
|
||||
orientation: localData.orientation === 'vertical' ? 'horizontal' : 'vertical',
|
||||
});
|
||||
};
|
||||
|
||||
const onChildUpdate = (child, updated) => {
|
||||
const index = localData.children.indexOf(child);
|
||||
context.emit('data', {
|
||||
...localData,
|
||||
children: [
|
||||
...localData.children.slice(0, index),
|
||||
updated,
|
||||
...localData.children.slice(index + 1),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
const addBlock = (block) => {
|
||||
context.emit('tree', {
|
||||
...localData,
|
||||
children: [
|
||||
...localData.children,
|
||||
block,
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
classes,
|
||||
onChildUpdate,
|
||||
toggleOrientation,
|
||||
localData,
|
||||
getComponent,
|
||||
addBlock,
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SbBlock class={this.classes}>
|
||||
<SbToolbar slot="toolbar">
|
||||
<button
|
||||
type="button"
|
||||
{...{
|
||||
on: {
|
||||
click: this.toggleOrientation,
|
||||
},
|
||||
}}
|
||||
>{this.localTree.orientation}</button>
|
||||
</SbToolbar>
|
||||
|
||||
{...this.localTree.children.map((child) => {
|
||||
const Component = this.getComponent(child.component);
|
||||
return <Component
|
||||
class="sb-main"
|
||||
key={child.id}
|
||||
components={this.components}
|
||||
tree={child}
|
||||
{...{
|
||||
on: {
|
||||
tree: (updated) => this.onChildUpdate(child, updated),
|
||||
},
|
||||
}}
|
||||
/>;
|
||||
})}
|
||||
<SbBlockPlaceholder
|
||||
{...{
|
||||
on: {
|
||||
'add-block': this.addBlock,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</SbBlock>
|
||||
);
|
||||
},
|
||||
});
|
4
src/components/user/Paragraph.scss
Normal file
4
src/components/user/Paragraph.scss
Normal file
|
@ -0,0 +1,4 @@
|
|||
.sb-paragraph {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
90
src/components/user/Paragraph.tsx
Normal file
90
src/components/user/Paragraph.tsx
Normal file
|
@ -0,0 +1,90 @@
|
|||
import {
|
||||
defineComponent,
|
||||
reactive,
|
||||
ref,
|
||||
onMounted,
|
||||
} from '@vue/composition-api';
|
||||
import {
|
||||
model,
|
||||
treeElementProps,
|
||||
useTree,
|
||||
} from '@components/TreeElement';
|
||||
|
||||
import SbBlock from '@internal/Block';
|
||||
import SbToolbar from '@internal/Toolbar';
|
||||
|
||||
import './Paragraph.scss';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'sb-paragraph',
|
||||
|
||||
model,
|
||||
|
||||
props: {
|
||||
...treeElementProps,
|
||||
},
|
||||
|
||||
setup(props, context) {
|
||||
const { localTree } = useTree(props);
|
||||
|
||||
const onTextUpdate = ($event: InputEvent) => {
|
||||
localTree.value = $event.target.innerHTML;
|
||||
};
|
||||
|
||||
const focused = ref(false);
|
||||
|
||||
const classes = reactive({
|
||||
'sb-paragraph': true,
|
||||
'sb-paragraph_focused': focused,
|
||||
});
|
||||
|
||||
const onFocus = () => {
|
||||
console.log('focus');
|
||||
focused.value = true;
|
||||
};
|
||||
const onBlur = () => {
|
||||
console.log('blur');
|
||||
focused.value = false;
|
||||
context.emit('tree', {
|
||||
value: localTree.value.value,
|
||||
});
|
||||
};
|
||||
|
||||
const inputEl = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
console.log(inputEl);
|
||||
inputEl.value.innerHTML = localTree.value;
|
||||
});
|
||||
|
||||
return {
|
||||
classes,
|
||||
localTree,
|
||||
onTextUpdate,
|
||||
focused,
|
||||
onFocus,
|
||||
onBlur,
|
||||
inputEl,
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<SbBlock>
|
||||
<SbToolbar>Paragraph editing</SbToolbar>
|
||||
<p
|
||||
class={this.classes}
|
||||
ref="inputEl"
|
||||
contenteditable
|
||||
{...{
|
||||
on: {
|
||||
input: this.onTextUpdate,
|
||||
focus: this.onFocus,
|
||||
blur: this.onBlur,
|
||||
},
|
||||
}}
|
||||
></p>
|
||||
</SbBlock>
|
||||
);
|
||||
},
|
||||
});
|
15
src/lib.ts
Normal file
15
src/lib.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import Vuex from 'vuex';
|
||||
import storeModule from './store';
|
||||
/* eslint no-param-reassign: 0 */
|
||||
|
||||
export default {
|
||||
install(Vue, { store }: { store: Vuex }) {
|
||||
|
||||
store.registerModule('sb', storeModule);
|
||||
|
||||
Vue.component('sb-layout', () => import('@user/Layout'));
|
||||
Vue.component('sb-image', () => import('@user/Image'));
|
||||
Vue.component('sb-paragraph', () => import('@user/Paragraph'));
|
||||
Vue.component('sb-heading', () => import('@user/Heading'));
|
||||
},
|
||||
};
|
18
src/main.scss
Normal file
18
src/main.scss
Normal file
|
@ -0,0 +1,18 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
--bg: 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);
|
||||
}
|
11
src/main.ts
11
src/main.ts
|
@ -1,8 +1,17 @@
|
|||
import Vue from 'vue';
|
||||
import App from './App.vue';
|
||||
import Vuex from 'vuex';
|
||||
import VueCompositionApi from '@vue/composition-api';
|
||||
import VueSchlechtenburg from './lib';
|
||||
import App from './App';
|
||||
|
||||
import './main.scss';
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
const store = new Vuex.Store({});
|
||||
Vue.use(VueCompositionApi);
|
||||
Vue.use(VueSchlechtenburg);
|
||||
|
||||
new Vue({
|
||||
render: (h) => h(App),
|
||||
}).$mount('#app');
|
||||
|
|
44
src/shims-app.d.ts
vendored
Normal file
44
src/shims-app.d.ts
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
declare module '*.bmp' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.gif' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.jpg' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.jpeg' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.png' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.webp' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.module.css' {
|
||||
const classes: { readonly [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
|
||||
declare module '*.module.scss' {
|
||||
const classes: { readonly [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
|
||||
declare module '*.module.sass' {
|
||||
const classes: { readonly [key: string]: string };
|
||||
export default classes;
|
||||
}
|
7
src/shims-tsx.d.ts
vendored
7
src/shims-tsx.d.ts
vendored
|
@ -1,11 +1,16 @@
|
|||
// file: shim-tsx.d.ts
|
||||
import Vue, { VNode } from 'vue';
|
||||
import { ComponentRenderProxy } from '@vue/composition-api';
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
// tslint:disable no-empty-interface
|
||||
interface Element extends VNode {}
|
||||
// tslint:disable no-empty-interface
|
||||
interface ElementClass extends Vue {}
|
||||
interface ElementClass extends ComponentRenderProxy {}
|
||||
interface ElementAttributesProperty {
|
||||
$props: any; // specify the property name to use
|
||||
}
|
||||
interface IntrinsicElements {
|
||||
[elem: string]: any;
|
||||
}
|
||||
|
|
35
src/store.ts
Normal file
35
src/store.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { GetterTree, MutationTree, ActionTree } from 'vuex';
|
||||
|
||||
interface State {
|
||||
activeBlockId: number|null;
|
||||
};
|
||||
|
||||
const state: State = {
|
||||
activeBlockId: null,
|
||||
};
|
||||
|
||||
const getters = {
|
||||
activeBlockId: (s) => s.activeBlockId,
|
||||
} as GetterTree<State, any>;
|
||||
|
||||
const actions = {
|
||||
setActiveBlock({ commit }, id) {
|
||||
commit('setActiveBlock', id);
|
||||
},
|
||||
} as ActionTree<State, any>;
|
||||
|
||||
const mutations = {
|
||||
setActiveBlock(s, id) {
|
||||
s.activeBlockId = id;
|
||||
},
|
||||
} as MutationTree<State>;
|
||||
|
||||
const SchlechtenburgModule = {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
mutations,
|
||||
actions,
|
||||
};
|
||||
|
||||
export default SchlechtenburgModule;
|
14
vue.config.js
Normal file
14
vue.config.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
const path = require('path');
|
||||
|
||||
const ROOT_DIR = path.resolve(__dirname);
|
||||
const SRC_DIR = path.resolve(ROOT_DIR, 'src');
|
||||
|
||||
module.exports = {
|
||||
chainWebpack(config) {
|
||||
config.resolve.alias
|
||||
.set('@', SRC_DIR)
|
||||
.set('@components', path.join(SRC_DIR, 'components'))
|
||||
.set('@internal', path.join(SRC_DIR, 'components/internal'))
|
||||
.set('@user', path.join(SRC_DIR, 'components/user'));
|
||||
},
|
||||
};
|
Loading…
Reference in a new issue