Remove everything unnecessary for minimal reproducation

This commit is contained in:
Benjamin Bädorf 2021-03-08 18:03:18 +01:00
parent 3f93f77602
commit 88dcf940b8
No known key found for this signature in database
GPG key ID: 4406E80E13CD656C
103 changed files with 42 additions and 2587 deletions

View file

@ -1 +0,0 @@
.sb-missing-block{flex-basis:100%}

View file

@ -1,2 +0,0 @@
import{y as a,m as s,z as e,D as t,e as l}from"./index.a48301fd.js";/* empty css */import"./vendor.a029424f.js";var d=a({name:"sb-image-display",model:s,props:{data:{type:null,default:e}},setup:a=>()=>t("figure",{class:"sb-image"},[t("img",{class:"sb-image__content",src:a.data.src,alt:a.data.alt},null),t(l,{block:a.data.description},null)])});export default d;
//# sourceMappingURL=display.54a23f84.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"display.54a23f84.js","sources":["../../packages/image/lib/display.tsx"],"sourcesContent":["import { defineComponent, PropType } from 'vue';\nimport {\n model,\n SbBlock,\n} from '@schlechtenburg/core';\nimport {\n getDefaultData,\n ImageData,\n} from './util';\n\nimport './style.scss';\n\nexport default defineComponent({\n name: 'sb-image-display',\n\n model,\n\n props: {\n data: {\n type: (null as unknown) as PropType<ImageData>,\n default: getDefaultData,\n },\n },\n\n setup(props) {\n return () => <figure class=\"sb-image\">\n <img\n class=\"sb-image__content\"\n src={props.data.src}\n alt={props.data.alt}\n />\n <SbBlock block={props.data.description} />\n </figure>;\n },\n});\n"],"names":["defineComponent","name","model","props","data","type","default","getDefaultData","setup","src","alt","description"],"mappings":"6HAYA,MAAeA,EAAgB,CAC7BC,KAAM,mBAENC,MAAAA,EAEAC,MAAO,CACLC,KAAM,CACJC,KAAO,KACPC,QAASC,IAIbC,MAAML,GACG,sBAAoB,4BAEf,wBACDA,EAAMC,KAAKK,QACXN,EAAMC,KAAKM,sBAEFP,EAAMC,KAAKO"}

View file

@ -1,2 +0,0 @@
var e=Object.assign;import{G as t,m as a,H as n,I as s,J as l}from"./index.a48301fd.js";import"./vendor.a029424f.js";var o=t({name:"sb-missing-block",model:a,props:e(e({},n),{name:String,data:{type:null,default:null},eventUpdate:{type:Function,default:()=>{}},eventAppendBlock:{type:Function,default:()=>{}},eventRemoveBlock:{type:Function,default:()=>{}}}),setup:e=>()=>s("div",{class:"sb-missing-block"},[l("Missing block: "),e.name])});export default o;
//# sourceMappingURL=display.82dfc9cb.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"display.82dfc9cb.js","sources":["../../packages/core/lib/components/MissingBlock/display.tsx"],"sourcesContent":["import { defineComponent, PropType } from 'vue';\nimport {\n model,\n blockProps,\n} from '../../block-helpers';\n\nimport './style.scss';\n\nexport default defineComponent({\n name: 'sb-missing-block',\n\n model,\n\n props: {\n ...blockProps,\n name: String,\n data: {\n type: (null as unknown) as PropType<any>,\n default: null,\n },\n eventUpdate: { type: Function, default: () => {} },\n eventAppendBlock: { type: Function, default: () => {} },\n eventRemoveBlock: { type: Function, default: () => {} },\n },\n\n setup(props) {\n return () => (\n <div class=\"sb-missing-block\">Missing block: {props.name}</div>\n );\n },\n});\n"],"names":["defineComponent","name","model","props","__assign","blockProps","String","data","type","default","eventUpdate","Function","eventAppendBlock","eventRemoveBlock","setup"],"mappings":"2HAQeA,EAAgB,CAC7BC,KAAM,mBAENC,MAAAA,EAEAC,MAAOC,OACFC,GADE,CAELJ,KAAMK,OACNC,KAAM,CACJC,KAAO,KACPC,QAAS,MAEXC,YAAa,CAAEF,KAAMG,SAAUF,QAAS,QACxCG,iBAAkB,CAAEJ,KAAMG,SAAUF,QAAS,QAC7CI,iBAAkB,CAAEL,KAAMG,SAAUF,QAAS,UAG/CK,MAAMX,GACG,mBACM,0CAAmCA,EAAMF"}

View file

@ -1,2 +0,0 @@
import{d as a,m as s,g as t,c as e,a as l,e as o}from"./index.a48301fd.js";/* empty css */import"./vendor.a029424f.js";var d=a({name:"sb-layout-display",model:s,props:{data:{type:null,default:t}},setup(a){const s=e((()=>({"sb-layout":!0,[`sb-layout_${a.data.orientation}`]:!0})));return()=>l("div",{class:s.value},[...a.data.children.map((a=>l(o,{key:a.id,block:a},null)))])}});export default d;
//# sourceMappingURL=display.a9bb8ea2.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"display.a9bb8ea2.js","sources":["../../packages/layout/lib/display.tsx"],"sourcesContent":["import {\n defineComponent,\n computed,\n PropType,\n} from 'vue';\nimport {\n model,\n SbBlock,\n} from '@schlechtenburg/core';\nimport {\n LayoutData,\n getDefaultData,\n} from './util';\n\nimport './style.scss';\n\nexport default defineComponent({\n name: 'sb-layout-display',\n\n model,\n\n props: {\n data: {\n type: (null as unknown) as PropType<LayoutData>,\n default: getDefaultData,\n },\n },\n\n setup(props) {\n const classes = computed(() => ({\n 'sb-layout': true,\n [`sb-layout_${props.data.orientation}`]: true,\n }));\n\n return () => (\n <div class={classes.value}>\n {...props.data.children.map((child) => (\n <SbBlock\n key={child.id}\n block={child}\n />\n ))}\n </div>\n );\n },\n});\n"],"names":["defineComponent","name","model","props","data","type","default","getDefaultData","setup","classes","computed","orientation","value","children","map","child","id"],"mappings":"oIAgBA,MAAeA,EAAgB,CAC7BC,KAAM,oBAENC,MAAAA,EAEAC,MAAO,CACLC,KAAM,CACJC,KAAO,KACPC,QAASC,IAIbC,MAAML,SACEM,EAAUC,GAAS,KAAO,cACjB,GACX,aAAYP,EAAMC,KAAKO,gBAAgB,YAGpC,mBACOF,EAAQG,WACdT,EAAMC,KAAKS,SAASC,iBAEfC,EAAMC,SACJD"}

View file

@ -1,2 +0,0 @@
import{j as a,m as s,k as r,q as p,s as e,x as t}from"./index.a48301fd.js";/* empty css */import"./vendor.a029424f.js";var l=a({name:"sb-paragraph-display",model:s,props:{data:{type:Object,default:r}},setup(a){const s=p((()=>({"sb-paragraph":!0,[`sb-paragraph_align-${a.data.align}`]:!0})));return()=>e("p",t({class:s.value},{innerHTML:a.data.value}),null)}});export default l;
//# sourceMappingURL=display.cf8b58ad.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"display.cf8b58ad.js","sources":["../../packages/paragraph/lib/display.tsx"],"sourcesContent":["import {\n defineComponent,\n computed,\n PropType,\n} from 'vue';\nimport {\n model,\n} from '@schlechtenburg/core';\nimport {\n getDefaultData,\n ParagraphData,\n} from './util';\n\nimport './style.scss';\n\nexport default defineComponent({\n name: 'sb-paragraph-display',\n\n model,\n\n props: {\n data: {\n type: Object as PropType<ParagraphData>,\n default: getDefaultData,\n },\n },\n\n setup(props) {\n const classes = computed(() => ({\n 'sb-paragraph': true,\n [`sb-paragraph_align-${props.data.align}`]: true,\n }));\n\n return () => <p\n class={classes.value}\n {...{\n innerHTML: props.data.value,\n }}\n ></p>;\n },\n});\n"],"names":["defineComponent","name","model","props","data","type","Object","default","getDefaultData","setup","classes","computed","align","value","innerHTML"],"mappings":"oIAeA,MAAeA,EAAgB,CAC7BC,KAAM,uBAENC,MAAAA,EAEAC,MAAO,CACLC,KAAM,CACJC,KAAMC,OACNC,QAASC,IAIbC,MAAMN,SACEO,EAAUC,GAAS,KAAO,iBACd,GACd,sBAAqBR,EAAMC,KAAKQ,UAAU,YAGvC,mBACEF,EAAQG,QAEbC,UAAWX,EAAMC,KAAKS"}

View file

@ -0,0 +1,2 @@
import{d as a,g as e,r,a as s,o as l,c as p,b as t}from"./index.863ba482.js";import"./vendor.41d82814.js";var n=a({name:"sb-paragraph-edit",model:{prop:"block",event:"update"},props:{blockId:{type:String,required:!0},data:{type:null,default:e}},setup(a){const e=r({value:a.data.value,align:a.data.align,focused:!1}),n=s(null);l((()=>{n.value&&(n.value.innerHTML=e.value)}));const d=p((()=>({"sb-paragraph":!0,"sb-paragraph_focused":e.focused,[`sb-paragraph_align-${e.align}`]:!0})));return()=>t("div",{class:d.value},[t("p",{class:"sb-paragraph__input",ref:n,contenteditable:!0},null)])}});export default n;
//# sourceMappingURL=edit.2536c400.js.map

View file

@ -0,0 +1 @@
{"version":3,"file":"edit.2536c400.js","sources":["../../packages/paragraph/lib/edit.tsx"],"sourcesContent":["import {\n defineComponent,\n reactive,\n computed,\n ref,\n Ref,\n onMounted,\n PropType,\n} from 'vue';\nimport {\n getDefaultData,\n ParagraphData,\n} from './util';\n\nimport './style.scss';\n\nexport default defineComponent({\n name: 'sb-paragraph-edit',\n\n model: {\n prop: 'block',\n event: 'update',\n },\n\n props: {\n blockId: { type: String, required: true },\n data: {\n type: (null as unknown) as PropType<ParagraphData>,\n default: getDefaultData,\n },\n },\n\n setup(props) {\n const localData = (reactive({\n value: props.data.value,\n align: props.data.align,\n focused: false,\n }) as unknown) as {\n value: string;\n align: string;\n focused: boolean;\n };\n\n const inputEl: Ref<null|HTMLElement> = ref(null);\n onMounted(() => {\n if (inputEl.value) {\n inputEl.value.innerHTML = localData.value;\n }\n });\n\n const classes = computed(() => ({\n 'sb-paragraph': true,\n 'sb-paragraph_focused': localData.focused,\n [`sb-paragraph_align-${localData.align}`]: true,\n }));\n\n return () => (\n <div class={classes.value}>\n <p\n class=\"sb-paragraph__input\"\n ref={inputEl}\n contenteditable\n ></p>\n </div>\n );\n },\n});\n"],"names":["defineComponent","name","model","prop","event","props","blockId","type","String","required","data","default","getDefaultData","setup","localData","reactive","value","align","focused","inputEl","ref","innerHTML","classes","computed"],"mappings":"gHAgBeA,EAAgB,CAC7BC,KAAM,oBAENC,MAAO,CACLC,KAAM,QACNC,MAAO,UAGTC,MAAO,CACLC,QAAS,CAAEC,KAAMC,OAAQC,UAAU,GACnCC,KAAM,CACJH,KAAO,KACPI,QAASC,IAIbC,MAAMR,SACES,EAAaC,EAAS,CAC1BC,MAAOX,EAAMK,KAAKM,MAClBC,MAAOZ,EAAMK,KAAKO,MAClBC,SAAS,IAOLC,EAAiCC,EAAI,SACjC,KACJD,EAAQH,UACFA,MAAMK,UAAYP,EAAUE,gBAIlCM,EAAUC,GAAS,KAAO,iBACd,yBACQT,EAAUI,SAChC,sBAAqBJ,EAAUG,UAAU,YAGtC,mBACOK,EAAQN,qBAEV,0BACDG"}

View file

@ -1,2 +0,0 @@
var a=Object.assign;import{y as t,m as e,z as l,A as s,B as n,C as i,D as d,S as r,b as o,E as c,F as u,e as p}from"./index.a48301fd.js";/* empty css */import"./vendor.a029424f.js";var f=t({name:"sb-image-edit",model:e,props:{onUpdate:{type:Function,default:()=>{}},data:{type:null,default:l}},setup(t){const e=s({src:t.data.src,alt:t.data.alt,description:t.data.description}),l=n(null);i((()=>t.data),(()=>{e.src=t.data.src,e.alt=t.data.alt,e.description=t.data.description}));const f=()=>{l.value&&l.value.click()},v=()=>{if(l.value&&l.value.files&&l.value.files.length){const a=new FileReader;a.addEventListener("load",(()=>{var e;const l=null==(e=null==a?void 0:a.result)?void 0:e.toString();if(!l)throw new Error("Couldn't load image src");t.onUpdate({src:l,alt:t.data.alt,description:t.data.description})})),a.readAsDataURL(l.value.files[0])}};return()=>d("figure",{class:"sb-image"},[d(r,null,{default:()=>[e.src?d(o,{onClick:f},{default:()=>[c("Select Image")]}):null,d("input",{type:"file",ref:l,style:"display: none;",onInput:v},null)]}),e.src?d(u,null,[d("img",{src:e.src,alt:e.alt,class:"sb-image__content"},null),d(p,{block:e.description,onUpdate:e=>{return l=e,void t.onUpdate(a(a({},t.data),{description:l}));var l}},null)]):d(o,{onClick:f},{default:()=>[c("Select Image")]})])}});export default f;
//# sourceMappingURL=edit.79e9c821.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"edit.79e9c821.js","sources":["../../packages/image/lib/edit.tsx"],"sourcesContent":["import {\n defineComponent,\n reactive,\n ref,\n Ref,\n watch,\n PropType,\n} from 'vue';\nimport {\n model,\n SbToolbar,\n SbButton,\n SbBlock,\n BlockData,\n} from '@schlechtenburg/core';\nimport { ParagraphData } from '@schlechtenburg/paragraph';\nimport {\n getDefaultData,\n ImageData,\n} from './util';\n\nimport './style.scss';\n\nexport default defineComponent({\n name: 'sb-image-edit',\n\n model,\n\n props: {\n onUpdate: { type: Function, default: () => {} },\n data: {\n type: (null as unknown) as PropType<ImageData>,\n default: getDefaultData,\n },\n },\n\n setup(props) {\n const localData = reactive({\n src: props.data.src,\n alt: props.data.alt,\n description: props.data.description,\n });\n\n const fileInput: Ref<null|HTMLInputElement> = ref(null);\n\n watch(() => props.data, () => {\n localData.src = props.data.src;\n localData.alt = props.data.alt;\n localData.description = props.data.description;\n });\n\n const selectImage = () => {\n if (fileInput.value) {\n fileInput.value.click();\n }\n };\n\n const onImageSelect = () => {\n if (fileInput.value && fileInput.value.files && fileInput.value.files.length) {\n const reader = new FileReader();\n reader.addEventListener('load', () => {\n const src = reader?.result?.toString();\n if (!src) {\n throw new Error('Couldn\\'t load image src');\n }\n\n props.onUpdate({\n src,\n alt: props.data.alt,\n description: props.data.description,\n });\n });\n\n reader.readAsDataURL(fileInput.value.files[0]);\n }\n };\n\n const onDescriptionUpdate = (description: BlockData<ParagraphData>) => {\n props.onUpdate({\n ...props.data,\n description,\n });\n };\n\n return () => (\n <figure class=\"sb-image\">\n <SbToolbar>\n {localData.src\n ? <SbButton {...{ onClick: selectImage }}>Select Image</SbButton>\n : null}\n <input\n type=\"file\"\n ref={fileInput}\n style=\"display: none;\"\n onInput={onImageSelect}\n />\n </SbToolbar>\n {localData.src\n ? <>\n <img\n src={localData.src}\n alt={localData.alt}\n class=\"sb-image__content\"\n />\n <SbBlock\n block={localData.description}\n onUpdate={(updated: BlockData<ParagraphData>) => onDescriptionUpdate(updated)}\n />\n </>\n : <SbButton {...{ onClick: selectImage }}>Select Image</SbButton>\n }\n </figure>\n );\n },\n});\n"],"names":["defineComponent","name","model","props","onUpdate","type","Function","default","data","getDefaultData","setup","localData","reactive","src","alt","description","fileInput","ref","selectImage","value","click","onImageSelect","files","length","reader","FileReader","addEventListener","result","toString","Error","readAsDataURL","onClick","onDescriptionUpdate","updated","__assign"],"mappings":"kMAuBA,MAAeA,EAAgB,CAC7BC,KAAM,gBAENC,MAAAA,EAEAC,MAAO,CACLC,SAAU,CAAEC,KAAMC,SAAUC,QAAS,QACrCC,KAAM,CACJH,KAAO,KACPE,QAASE,IAIbC,MAAMP,SACEQ,EAAYC,EAAS,CACzBC,IAAKV,EAAMK,KAAKK,IAChBC,IAAKX,EAAMK,KAAKM,IAChBC,YAAaZ,EAAMK,KAAKO,cAGpBC,EAAwCC,EAAI,SAE5C,IAAMd,EAAMK,OAAM,OACZK,IAAMV,EAAMK,KAAKK,MACjBC,IAAMX,EAAMK,KAAKM,MACjBC,YAAcZ,EAAMK,KAAKO,qBAG/BG,EAAc,KACdF,EAAUG,SACFA,MAAMC,SAIdC,EAAgB,QAChBL,EAAUG,OAASH,EAAUG,MAAMG,OAASN,EAAUG,MAAMG,MAAMC,OAAQ,OACtEC,EAAS,IAAIC,aACZC,iBAAiB,QAAQ,iBACxBb,EAAMW,0BAAQG,iBAAQC,eACvBf,QACG,IAAIgB,MAAM,6BAGZzB,SAAS,CACbS,IAAAA,EACAC,IAAKX,EAAMK,KAAKM,IAChBC,YAAaZ,EAAMK,KAAKO,mBAIrBe,cAAcd,EAAUG,MAAMG,MAAM,YAWxC,sBACS,oCAETX,EAAUE,SACSkB,QAASb,sCACzB,qBAEG,WACAF,QACC,yBACGK,YAGZV,EAAUE,2BAGEF,EAAUE,QACVF,EAAUG,UACT,sCAGCH,EAAUI,yBACgCiB,SAAoBC,SA5BzE7B,SAAS8B,OACV/B,EAAMK,MADI,CAEbO,YAAAA,KAHyBA,qBAgCLgB,QAASb"}

View file

@ -1,2 +0,0 @@
var e=Object.assign;import{d as n,m as i,g as t,u as l,r as a,w as d,c as r,a as o,S as c,b as h,e as s,f as p,h as u,i as v}from"./index.a48301fd.js";/* empty css */import"./vendor.a029424f.js";var f=n({name:"sb-layout-edit",model:i,props:{onUpdate:{type:Function,default:()=>{}},data:{type:null,default:t}},setup(n){const{activate:i}=l(),t=a({orientation:n.data.orientation,children:[...n.data.children]});d((()=>n.data),(()=>{t.orientation=n.data.orientation,t.children=[...n.data.children]}));const f=r((()=>({"sb-layout":!0,[`sb-layout_${t.orientation}`]:!0}))),m=()=>{n.onUpdate({orientation:"vertical"===t.orientation?"horizontal":"vertical"})},U=e=>{t.children=[...t.children,e],n.onUpdate({children:[...t.children]}),i(e.id)},b=(e,l)=>{t.children=[...t.children.slice(0,e+1),l,...t.children.slice(e+1)],n.onUpdate({children:[...t.children]}),i(l.id)},y=e=>{t.children=[...t.children.slice(0,e),...t.children.slice(e+1)],n.onUpdate({children:[...t.children]});const l=Math.max(e-1,0);i(t.children[l].id)},k=e=>{const n=Math.max(Math.min(t.children.length-1,e),0);i(t.children[n].id)};return()=>o("div",{class:f.value},[o(c,null,{default:()=>[o(h,{type:"button",onClick:m},{default:()=>[t.orientation]})]}),...t.children.map(((i,l)=>o(s,p({key:i.id},{"data-order":l,block:i,onUpdate:l=>((i,l)=>{const a=t.children.indexOf(i);-1!==a&&n.onUpdate({children:[...t.children.slice(0,a),e(e({},i),l),...t.children.slice(a+1)]})})(i,l),onRemoveSelf:()=>y(l),onPrependBlock:e=>b(l-1,e),onAppendBlock:e=>b(l,e),onActivatePrevious:()=>k(l-1),onActivateNext:()=>k(l+1)}),{"context-toolbar":()=>o(u,{onMoveBackward:()=>(e=>{if(0===e)return;const i=t.children[e],l=t.children[e-1];t.children=[...t.children.slice(0,e-1),i,l,...t.children.slice(e+1)],n.onUpdate({children:[...t.children]})})(l),onMoveForward:()=>(e=>{if(e===t.children.length-1)return;const i=t.children[e],l=t.children[e+1];t.children=[...t.children.slice(0,e),l,i,...t.children.slice(e+2)],n.onUpdate({children:[...t.children]})})(l),onRemove:()=>y(l),orientation:t.orientation},null)}))),o(v,{onInsertBlock:U},null)])}});export default f;
//# sourceMappingURL=edit.85b1500c.js.map

File diff suppressed because one or more lines are too long

View file

@ -1,2 +0,0 @@
import{j as a,m as e,k as n,l as t,n as l,u as o,o as u,p as i,q as s,s as r,S as d,t as p,v}from"./index.a48301fd.js";/* empty css */import"./vendor.a029424f.js";var c=a({name:"sb-paragraph-edit",model:e,props:{blockId:{type:String,required:!0},data:{type:null,default:n},onUpdate:{type:Function,default:()=>{}},onAppendBlock:{type:Function,default:()=>{}},onRemoveSelf:{type:Function,default:()=>{}},onActivateNext:{type:Function,default:()=>{}},onActivatePrevious:{type:Function,default:()=>{}}},setup(a){const e=t({value:a.data.value,align:a.data.align,focused:!1}),c=l(null),{isActive:f,activate:g}=o(a.blockId),y=()=>{c.value&&f.value&&c.value.focus()};u((()=>{y(),c.value&&(c.value.innerHTML=e.value)})),i(f,y),i((()=>a.data),(()=>{e.value=a.data.value,e.align=a.data.align,c.value&&(c.value.innerHTML=e.value)}));const h=a=>{e.value=a.target.innerHTML},b=s((()=>({"sb-paragraph":!0,"sb-paragraph_focused":e.focused,[`sb-paragraph_align-${e.align}`]:!0}))),m=n=>{a.onUpdate({value:e.value,align:n.target.value})},k=()=>{e.focused=!0,g()},A=()=>{e.focused=!1,a.onUpdate({value:e.value,align:e.align})},w=e=>{if("Enter"===e.key&&!e.shiftKey){const t=""+ +new Date;a.onAppendBlock({id:t,name:"sb-paragraph",data:n()}),g(t),e.preventDefault()}},F=n=>{var t;"Backspace"===n.key&&""===e.value&&a.onRemoveSelf();const l=window.getSelection(),o=null==l?void 0:l.focusNode,u=Array.from((null==(t=null==c?void 0:c.value)?void 0:t.childNodes)||[]),i=o?u.indexOf(o):-1;if(o===c.value||0===i||i===u.length-1)switch(n.key){case"ArrowDown":a.onActivateNext();break;case"ArrowUp":a.onActivatePrevious()}};return()=>r("div",{class:b.value},[r(d,null,{default:()=>[r(p,{value:e.align,onChange:m},{default:()=>[r("option",null,[v("left")]),r("option",null,[v("center")]),r("option",null,[v("right")])]})]}),r("p",{class:"sb-paragraph__input",ref:c,contenteditable:!0,onInput:h,onFocus:k,onBlur:A,onKeydown:w,onKeyup:F},null)])}});export default c;
//# sourceMappingURL=edit.b0a76e09.js.map

File diff suppressed because one or more lines are too long

View file

@ -1,2 +0,0 @@
export default{};
//# sourceMappingURL=edit.bd5075df.js.map

View file

@ -1 +0,0 @@
{"version":3,"file":"edit.bd5075df.js","sources":["../../packages/heading/lib/edit.tsx"],"sourcesContent":["export default {};\n"],"names":[],"mappings":"cAAe"}

View file

@ -1 +0,0 @@
@charset "UTF-8";.sb-button{border:0;padding:8px 12px;background-color:var(--grey-0);border:1px solid var(--grey-2)}.sb-button:hover{border:1px solid var(--interact)}.sb-context{position:relative}.sb-context-menu{display:none;flex-direction:column;background:var(--grey-0);border:1px solid var(--grey-3);top:100%;left:0;margin:0;z-index:var(--z-context-menu);max-height:70vh;max-width:100vw;overflow:auto}.sb-context-menu[open]{display:flex}.sb-tree-block-select__list{list-style:none;margin:0;padding:0}.sb-tree-block-select__list_base{padding-right:1rem}.sb-tree-block-select__block{padding:0;margin:0;padding-left:1rem}.sb-tree-block-select__block-name{display:block;background:0 0;border:0;font:inherit;color:inherit;padding:.5rem 1rem;width:100%;text-align:left}.sb-tree-block-select__block_active>.sb-tree-block-select__block-name{outline:1px solid var(--interact)}.sb-block{display:flex;align-items:stretch;justify-items:stretch;height:auto}.sb-block>*>.sb-toolbar{opacity:0;pointer-events:none}.sb-block>.sb-block-ordering{opacity:0;pointer-events:none}.sb-block_active{outline:4px solid var(--interact)}.sb-block_active>*>.sb-toolbar{opacity:1;pointer-events:all;outline:1px solid var(--grey-2)}.sb-block_active>.sb-block-ordering{opacity:1;pointer-events:all}.sb-block_highlighted{outline:2px solid var(--interact)}.sb-main{position:relative;background-color:var(--bg);padding:50px 40px}.sb-modal__overlay{background-color:var(--grey-3-t);position:fixed;z-index:10;top:0;left:0;bottom:0;right:0;padding:10vh 10vw;display:flex;justify-content:center;align-items:center;opacity:0;pointer-events:none}.sb-modal__content{width:900px;max-width:100%;height:auto;max-height:100%;background-color:var(--grey-0);padding:24px 32px}.sb-modal_open .sb-modal__overlay{opacity:1;pointer-events:all}.sb-block-picker{display:flex;justify-content:center;align-items:center;height:100%;width:100%}.sb-block-picker__add-button{padding:24px 32px}.sb-block-ordering{display:flex;position:absolute;flex-direction:column}.sb-block-placeholder{width:100%;position:relative;overflow:visible}.sb-block-placeholder__add{background-color:var(--grey-1);width:100%}.sb-toolbar{position:absolute;width:auto;height:auto}.sb-select{background-color:var(--grey-0);border:1px solid var(--grey-2);position:relative}.sb-select:hover{border:1px solid var(--interact)}.sb-select::after{position:absolute;content:"⯆";top:6px;height:100%;right:12px;pointer-events:none}.sb-select__input{background:0 0;appearance:none;border:0;padding:8px 32px 8px 12px}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#2c3e50}*,::after,::before{box-sizing:border-box}html{--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;--z-context-menu:3000}body{margin:0;min-height:100vh}

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

View file

@ -0,0 +1 @@
.sb-block{display:flex;align-items:stretch;justify-items:stretch;height:auto}.sb-block>*>.sb-toolbar{opacity:0;pointer-events:none}.sb-block>.sb-block-ordering{opacity:0;pointer-events:none}.sb-block_active{outline:4px solid var(--interact)}.sb-block_active>*>.sb-toolbar{opacity:1;pointer-events:all;outline:1px solid var(--grey-2)}.sb-block_active>.sb-block-ordering{opacity:1;pointer-events:all}.sb-block_highlighted{outline:2px solid var(--interact)}.sb-main{position:relative;background-color:var(--bg);padding:50px 40px}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#2c3e50}*,::after,::before{box-sizing:border-box}html{--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;--z-context-menu:3000}body{margin:0;min-height:100vh}

View file

@ -1 +0,0 @@
.sb-layout{display:flex}.sb-layout_vertical{flex-direction:column}.sb-layout_horizontal{flex-direction:row}.sb-layout__item{position:relative}.sb-layout>*{flex-basis:auto;flex-grow:1;flex-shrink:1}

View file

@ -1 +0,0 @@
.sb-image{margin:0}.sb-image__content{width:100%;height:auto}

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

View file

@ -5,9 +5,9 @@
<link rel="icon" href="./favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
<script type="module" crossorigin src="./assets/index.a48301fd.js"></script>
<link rel="modulepreload" href="./assets/vendor.a029424f.js">
<link rel="stylesheet" href="./assets/index.62d1366f.css">
<script type="module" crossorigin src="./assets/index.863ba482.js"></script>
<link rel="modulepreload" href="./assets/vendor.41d82814.js">
<link rel="stylesheet" href="./assets/index.e3cfcab8.css">
</head>
<body>
<div id="app"></div>

File diff suppressed because one or more lines are too long

View file

@ -1,242 +0,0 @@
/**
* The **ResizeObserver** interface reports changes to the dimensions of an
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element)'s content
* or border box, or the bounding box of an
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement).
*
* > **Note**: The content box is the box in which content can be placed,
* > meaning the border box minus the padding and border width. The border box
* > encompasses the content, padding, and border. See
* > [The box model](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/The_box_model)
* > for further explanation.
*
* `ResizeObserver` avoids infinite callback loops and cyclic dependencies that
* are often created when resizing via a callback function. It does this by only
* processing elements deeper in the DOM in subsequent frames. Implementations
* should, if they follow the specification, invoke resize events before paint
* and after layout.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver
*/
declare class ResizeObserver {
/**
* The **ResizeObserver** constructor creates a new `ResizeObserver` object,
* which can be used to report changes to the content or border box of an
* `Element` or the bounding box of an `SVGElement`.
*
* @example
* var ResizeObserver = new ResizeObserver(callback)
*
* @param callback
* The function called whenever an observed resize occurs. The function is
* called with two parameters:
* * **entries**
* An array of
* [ResizeObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry)
* objects that can be used to access the new dimensions of the element
* after each change.
* * **observer**
* A reference to the `ResizeObserver` itself, so it will definitely be
* accessible from inside the callback, should you need it. This could be
* used for example to automatically unobserve the observer when a certain
* condition is reached, but you can omit it if you don't need it.
*
* The callback will generally follow a pattern along the lines of:
* ```js
* function(entries, observer) {
* for (let entry of entries) {
* // Do something to each entry
* // and possibly something to the observer itself
* }
* }
* ```
*
* The following snippet is taken from the
* [resize-observer-text.html](https://mdn.github.io/dom-examples/resize-observer/resize-observer-text.html)
* ([see source](https://github.com/mdn/dom-examples/blob/master/resize-observer/resize-observer-text.html))
* example:
* @example
* const resizeObserver = new ResizeObserver(entries => {
* for (let entry of entries) {
* if(entry.contentBoxSize) {
* h1Elem.style.fontSize = Math.max(1.5, entry.contentBoxSize.inlineSize/200) + 'rem';
* pElem.style.fontSize = Math.max(1, entry.contentBoxSize.inlineSize/600) + 'rem';
* } else {
* h1Elem.style.fontSize = Math.max(1.5, entry.contentRect.width/200) + 'rem';
* pElem.style.fontSize = Math.max(1, entry.contentRect.width/600) + 'rem';
* }
* }
* });
*
* resizeObserver.observe(divElem);
*/
constructor(callback: ResizeObserverCallback);
/**
* The **disconnect()** method of the
* [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)
* interface unobserves all observed
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement)
* targets.
*/
disconnect: () => void;
/**
* The `observe()` method of the
* [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)
* interface starts observing the specified
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement).
*
* @example
* resizeObserver.observe(target, options);
*
* @param target
* A reference to an
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement)
* to be observed.
*
* @param options
* An options object allowing you to set options for the observation.
* Currently this only has one possible option that can be set.
*/
observe: (target: Element, options?: ResizeObserverObserveOptions) => void;
/**
* The **unobserve()** method of the
* [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)
* interface ends the observing of a specified
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement).
*/
unobserve: (target: Element) => void;
}
interface ResizeObserverObserveOptions {
/**
* Sets which box model the observer will observe changes to. Possible values
* are `content-box` (the default), and `border-box`.
*
* @default "content-box"
*/
box?: "content-box" | "border-box";
}
/**
* The function called whenever an observed resize occurs. The function is
* called with two parameters:
*
* @param entries
* An array of
* [ResizeObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry)
* objects that can be used to access the new dimensions of the element after
* each change.
*
* @param observer
* A reference to the `ResizeObserver` itself, so it will definitely be
* accessible from inside the callback, should you need it. This could be used
* for example to automatically unobserve the observer when a certain condition
* is reached, but you can omit it if you don't need it.
*
* The callback will generally follow a pattern along the lines of:
* @example
* function(entries, observer) {
* for (let entry of entries) {
* // Do something to each entry
* // and possibly something to the observer itself
* }
* }
*
* @example
* const resizeObserver = new ResizeObserver(entries => {
* for (let entry of entries) {
* if(entry.contentBoxSize) {
* h1Elem.style.fontSize = Math.max(1.5, entry.contentBoxSize.inlineSize/200) + 'rem';
* pElem.style.fontSize = Math.max(1, entry.contentBoxSize.inlineSize/600) + 'rem';
* } else {
* h1Elem.style.fontSize = Math.max(1.5, entry.contentRect.width/200) + 'rem';
* pElem.style.fontSize = Math.max(1, entry.contentRect.width/600) + 'rem';
* }
* }
* });
*
* resizeObserver.observe(divElem);
*/
type ResizeObserverCallback = (
entries: ResizeObserverEntry[],
observer: ResizeObserver,
) => void;
/**
* The **ResizeObserverEntry** interface represents the object passed to the
* [ResizeObserver()](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/ResizeObserver)
* constructor's callback function, which allows you to access the new
* dimensions of the
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement)
* being observed.
*/
interface ResizeObserverEntry {
/**
* An object containing the new border box size of the observed element when
* the callback is run.
*/
readonly borderBoxSize: ResizeObserverEntryBoxSize;
/**
* An object containing the new content box size of the observed element when
* the callback is run.
*/
readonly contentBoxSize: ResizeObserverEntryBoxSize;
/**
* A [DOMRectReadOnly](https://developer.mozilla.org/en-US/docs/Web/API/DOMRectReadOnly)
* object containing the new size of the observed element when the callback is
* run. Note that this is better supported than the above two properties, but
* it is left over from an earlier implementation of the Resize Observer API,
* is still included in the spec for web compat reasons, and may be deprecated
* in future versions.
*/
// node_modules/typescript/lib/lib.dom.d.ts
readonly contentRect: DOMRectReadOnly;
/**
* A reference to the
* [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) or
* [SVGElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement)
* being observed.
*/
readonly target: Element;
}
/**
* The **borderBoxSize** read-only property of the
* [ResizeObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry)
* interface returns an object containing the new border box size of the
* observed element when the callback is run.
*/
interface ResizeObserverEntryBoxSize {
/**
* The length of the observed element's border box in the block dimension. For
* boxes with a horizontal
* [writing-mode](https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode),
* this is the vertical dimension, or height; if the writing-mode is vertical,
* this is the horizontal dimension, or width.
*/
blockSize: number;
/**
* The length of the observed element's border box in the inline dimension.
* For boxes with a horizontal
* [writing-mode](https://developer.mozilla.org/en-US/docs/Web/CSS/writing-mode),
* this is the horizontal dimension, or width; if the writing-mode is
* vertical, this is the vertical dimension, or height.
*/
inlineSize: number;
}
interface Window {
ResizeObserver: typeof ResizeObserver;
}

View file

@ -1,15 +0,0 @@
import { v4 as uuidv4 } from 'uuid';
export const generateBlockId = uuidv4;
export const model = {
prop: 'block',
event: 'update',
};
export const blockProps = {
blockId: {
type: String,
default: generateBlockId,
},
};

View file

@ -1,20 +1,13 @@
import {
defineComponent,
computed,
watch,
PropType,
ref,
Ref,
} from 'vue';
import { BlockData } from '../types';
import { SbMode } from '../mode';
import { useResizeObserver, SymBlockDimensions } from '../use-resize-observer';
import { useActivation } from '../use-activation';
import { useBlockTree } from '../use-block-tree';
import { useDynamicBlocks } from '../use-dynamic-blocks';
import SbMissingBlock from './MissingBlock';
import './Block.scss';
export const SbBlock = defineComponent({
@ -25,88 +18,24 @@ export const SbBlock = defineComponent({
type: (null as unknown) as PropType<BlockData<any>>,
required: true,
},
sortable: {
type: String,
default: null,
},
onUpdate: { type: Function, default: () => {} },
onPrependBlock: { type: Function, default: () => {} },
onAppendBlock: { type: Function, default: () => {} },
onRemoveSelf: { type: Function, default: () => {} },
onActivatePrevious: { type: Function, default: () => {} },
onActivateNext: { type: Function, default: () => {} },
},
setup(props, context) {
const el: Ref<null|HTMLElement> = ref(null);
const { mode, getBlock } = useDynamicBlocks();
const {
isActive,
activate,
} = useActivation(props.block.id);
const classes = computed(() => ({
'sb-block': true,
'sb-block_active': isActive.value,
}));
const { triggerSizeCalculation } = useResizeObserver(el, SymBlockDimensions);
watch(() => props.block.data, triggerSizeCalculation);
const { register } = useBlockTree();
register(props.block);
watch(props.block, () => { register(props.block); });
const onChildUpdate = (updated: {[key: string]: any}) => {
props.onUpdate({
...props.block,
data: {
...props.block.data,
...updated,
},
});
};
const { getBlock } = useDynamicBlocks();
const classes = computed(() => ({ 'sb-block': true }));
return () => {
const BlockComponent = getBlock(props.block.name)?.[mode.value] as any;
if (!BlockComponent) {
const MissingBlock = SbMissingBlock[mode.value];
return <MissingBlock
name={props.block.name}
blockId={props.block.id}
/>;
}
if (mode.value === SbMode.Display) {
return <BlockComponent
data={props.block.data}
blockId={props.block.id}
/>;
}
const BlockComponent = getBlock(props.block.name)?.component as any;
return <div
ref={el}
class={classes.value}
>
<div class="sb-block__edit-cover"></div>
{context.slots['context-toolbar'] ? context.slots['context-toolbar']() : null}
<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}
{...{
onClick: ($event: MouseEvent) => {
$event.stopPropagation();
activate();
},
...context.attrs,
}}
{...context.attrs}
/>
</div>;
};

View file

@ -1,5 +0,0 @@
.sb-block-ordering {
display: flex;
position: absolute;
flex-direction: column;
}

View file

@ -1,64 +0,0 @@
import { debounce } from 'lodash-es';
import {
watch,
reactive,
computed,
defineComponent,
} from 'vue';
import { useBlockSizing } from '../use-resize-observer';
import { SbButton } from './Button';
import './BlockOrdering.scss';
export const SbBlockOrdering = defineComponent({
name: 'sb-block-ordering',
props: {
orientation: {
type: String,
default: null,
},
onRemove: { type: Function, default: () => {} },
onMoveBackward: { type: Function, default: () => {} },
onMoveForward: { type: Function, default: () => {} },
},
setup(props) {
const styles = reactive({
top: '',
right: '',
});
const classes = computed(() => ({
'sb-block-ordering': true,
[`sb-block-ordering_${props.orientation}`]: !!props.orientation,
}));
const { editorDimensions, blockDimensions } = useBlockSizing();
const resetStyles = debounce(() => {
if (!editorDimensions.value || !blockDimensions.value) {
return;
}
const right = editorDimensions.value.width - blockDimensions.value.left;
styles.top = `${blockDimensions.value.top}px`;
styles.right = `${right}px`;
});
watch(editorDimensions, resetStyles);
watch(blockDimensions, resetStyles);
watch(() => props.orientation, resetStyles);
return () => (
<div
class={classes.value}
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>
</div>
);
},
});

View file

@ -1,11 +0,0 @@
.sb-block-picker {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
&__add-button {
padding: 24px 32px;
}
}

View file

@ -1,67 +0,0 @@
import {
computed,
ref,
defineComponent,
} from 'vue';
import { useDynamicBlocks } from '../use-dynamic-blocks';
import { BlockDefinition } from '../types';
import { SbButton } from './Button';
import { SbModal } from './Modal';
import './BlockPicker.scss';
export const SbBlockPicker = defineComponent({
name: 'sb-block-picker',
props: {
onPickedBlock: { type: Function, default: () => {} },
},
setup(props) {
const open = ref(false);
const { customBlocks } = useDynamicBlocks();
const blockList = computed(() => Object.keys(customBlocks).map((key) => customBlocks[key]));
const selectBlock = (block: BlockDefinition<any>) => () => {
open.value = false;
props.onPickedBlock({
name: block.name,
id: `${+(new Date())}`,
data: block.getDefaultData(),
});
};
return () => (
<div class="sb-block-picker">
<SbButton
class="sb-block-picker__add-button"
{...{
type: 'button',
onClick: ($event: MouseEvent) => {
open.value = true;
$event.stopPropagation();
},
}}
>+</SbButton>
<SbModal
open={open.value}
onClose={() => {
open.value = false;
}}
{...{ onClick: ($event: MouseEvent) => $event.stopPropagation() }}
>
{...blockList.value.map((block: BlockDefinition<any>) => (
<SbButton
{...{
type: 'button',
onClick: () => selectBlock(block),
}}
>{block.name}</SbButton>
))}
</SbModal>
</div>
);
},
});

View file

@ -1,10 +0,0 @@
.sb-block-placeholder {
width: 100%;
position: relative;
overflow: visible;
&__add {
background-color: var(--grey-1);
width: 100%;
}
}

View file

@ -1,24 +0,0 @@
import { defineComponent } from 'vue';
import { BlockData } from '../types';
import { SbBlockPicker } from './BlockPicker';
import './BlockPlaceholder.scss';
export const SbBlockPlaceholder = defineComponent({
name: 'sb-block-placeholder',
props: {
onInsertBlock: { type: Function, default: () => {} },
},
setup(props) {
return () => (
<div class="sb-block-placeholder">
<SbBlockPicker
onPickedBlock={(block: BlockData<any>) => props.onInsertBlock(block)}
/>
</div>
);
},
});

View file

@ -1,13 +0,0 @@
import { defineComponent } from 'vue';
import './BlockToolbar.scss';
export const SbBlockToolbar = defineComponent({
name: 'sb-block-toolbar',
setup() {
return () => (
<div class="sb-block-toolbar"></div>
);
},
});

View file

@ -1,10 +0,0 @@
.sb-button {
border: 0;
padding: 8px 12px;
background-color: var(--grey-0);
border: 1px solid var(--grey-2);
&:hover {
border: 1px solid var(--interact);
}
}

View file

@ -1,22 +0,0 @@
import { defineComponent } from 'vue';
import './Button.scss';
export const SbButton = defineComponent({
name: 'sb-button',
inheritAttrs: false,
setup(_, context) {
return () => (
<button
{...{
...context.attrs,
class: (context.attrs.class || '') + ' sb-button',
}}
>
{context.slots.default?.()}
</button>
);
},
});

View file

@ -1,22 +0,0 @@
.sb-context {
position: relative;
}
.sb-context-menu {
display: none;
flex-direction: column;
background: var(--grey-0);
border: 1px solid var(--grey-3);
top: 100%;
left: 0;
margin: 0;
z-index: var(--z-context-menu);
max-height: 70vh;
max-width: 100vw;
overflow: auto;
&[open] {
display: flex;
}
}

View file

@ -1,76 +0,0 @@
import {
watch,
defineComponent,
ref,
} from 'vue';
import { SbButton } from './Button';
import './ContextMenu.scss';
export const SbContextMenu = defineComponent({
name: 'sb-context-menu',
props: {
onClose: { type: Function, default: () => {} },
onOpen: { type: Function, default: () => {} },
},
setup(props, context) {
const opened = ref(false);
const open = () => { opened.value = true; };
const close = () => { opened.value = false; };
const closeOnEscape = ($event: KeyboardEvent) => {
if ($event.key === 'Escape') {
close();
}
};
const toggle = () => { opened.value ? close() : open() };
watch(opened, (curr, prev) => {
if (curr === prev) {
return;
}
if (!curr) {
document.body.removeEventListener('click', close);
document.body.removeEventListener('keypress', closeOnEscape);
props.onClose();
} else {
setTimeout(() => {
document.body.addEventListener('click', close);
document.body.addEventListener('keypress', closeOnEscape);
props.onOpen();
});
}
});
return () => (
<div class="sb-context">
{
context.slots.context?.({
opened,
toggle,
close,
open,
}) || <SbButton {...{ onClick: toggle }}>Menu</SbButton>
}
<dialog
class="sb-context-menu"
open={opened.value ? true : undefined}
onClick={($event: Event) => {
// Make sure clicks inside do not autoclose this
$event.stopPropagation();
}}
{...{ onClose: close /* TODO: DialogHTMLAttributes needs an onClose handler type */ }}
>
{context.slots.default?.({
opened,
toggle,
close,
open,
}) || null}
</dialog>
</div>
);
},
});

View file

@ -4,27 +4,14 @@ import {
shallowReactive,
ref,
PropType,
Ref,
} from 'vue';
import {
BlockData,
BlockDefinition,
BlockLibrary,
TreeNode,
} from '../types';
import { model } from '../block-helpers';
import { Mode, SbMode } from '../mode';
import { SymBlockLibrary} from '../use-dynamic-blocks';
import {
SymBlockTree,
SymBlockTreeRegister,
SymBlockTreeUnregister,
} from '../use-block-tree';
import { SymEditorDimensions, useResizeObserver } from '../use-resize-observer';
import { SymActiveBlock } from '../use-activation';
import { SbMainMenu } from './MainMenu';
import { SbBlockToolbar } from './BlockToolbar';
import { SbBlock } from './Block';
import './Main.scss';
@ -32,7 +19,10 @@ import './Main.scss';
export const SbMain = defineComponent({
name: 'sb-main',
model,
model: {
prop: 'block',
event: 'update',
},
props: {
customBlocks: {
@ -44,30 +34,9 @@ export const SbMain = defineComponent({
required: true,
},
onUpdate: { type: Function, default: () => {} },
mode: {
type: String as PropType<SbMode>,
validator(value: any) {
return Object.values(SbMode).includes(value);
},
default: SbMode.Edit,
},
},
setup(props: any) { // TODO: why does the typing of props not work here?
const el: Ref<null|HTMLElement> = ref(null);
useResizeObserver(el, SymEditorDimensions);
const mode = ref(props.mode);
provide(Mode, mode);
const activeBlock = ref(null);
provide(SymActiveBlock, activeBlock);
const blockTree: Ref<TreeNode|null> = ref(null);
provide(SymBlockTree, blockTree);
provide(SymBlockTreeRegister, (block: TreeNode) => { blockTree.value = block; });
provide(SymBlockTreeUnregister, () => { blockTree.value = null; });
const blockLibrary: BlockLibrary = shallowReactive({
...props.customBlocks.reduce(
(blocks: BlockLibrary, block: BlockDefinition<any>) => ({ ...blocks, [block.name]: block }),
@ -78,18 +47,7 @@ export const SbMain = defineComponent({
provide(SymBlockLibrary, blockLibrary);
return () => (
<div
class="sb-main"
ref={el}
>
{
mode.value === SbMode.Edit
? <>
<SbMainMenu block={props.block} />
<SbBlockToolbar />
</>
: null
}
<div class="sb-main">
<SbBlock
block={props.block}
onUpdate={props.onUpdate}

View file

@ -1,2 +0,0 @@
.sb-main-menu {
}

View file

@ -1,27 +0,0 @@
import {
defineComponent,
PropType,
} from 'vue';
import { BlockData } from '../types';
import { SbTreeBlockSelect } from './TreeBlockSelect';
import './MainMenu.scss';
export const SbMainMenu = defineComponent({
name: 'sb-main-menu',
props: {
block: {
type: (null as unknown) as PropType<BlockData<any>>,
required: true,
},
},
setup() {
return () => (
<div class="sb-main-menu">
<SbTreeBlockSelect />
</div>
);
},
});

View file

@ -1,31 +0,0 @@
import { defineComponent, PropType } from 'vue';
import {
model,
blockProps,
} from '../../block-helpers';
import './style.scss';
export default defineComponent({
name: 'sb-missing-block',
model,
props: {
...blockProps,
name: String,
data: {
type: (null as unknown) as PropType<any>,
default: null,
},
eventUpdate: { type: Function, default: () => {} },
eventAppendBlock: { type: Function, default: () => {} },
eventRemoveBlock: { type: Function, default: () => {} },
},
setup(props) {
return () => (
<div class="sb-missing-block">Missing block: {props.name}</div>
);
},
});

View file

@ -1,7 +0,0 @@
import { defineAsyncComponent } from 'vue';
export default {
name: 'sb-missing-block',
edit: defineAsyncComponent(() => import('./display')),
display: defineAsyncComponent(() => import('./display')),
};

View file

@ -1,3 +0,0 @@
.sb-missing-block {
flex-basis: 100%;
}

View file

@ -1,32 +0,0 @@
.sb-modal {
&__overlay {
background-color: var(--grey-3-t);
position: fixed;
z-index: 10;
top: 0;
left: 0;
bottom: 0;
right: 0;
padding: 10vh 10vw;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
pointer-events: none;
}
&__content {
width: 900px;
max-width: 100%;
height: auto;
max-height: 100%;
background-color: var(--grey-0);
padding: 24px 32px;
}
&_open #{&}__overlay {
opacity: 1;
pointer-events: all;
}
}

View file

@ -1,41 +0,0 @@
import {
defineComponent,
computed,
} from 'vue';
import './Modal.scss';
export const SbModal = defineComponent({
name: 'sb-modal',
props: {
open: {
type: Boolean,
default: false,
},
onClose: { type: Function, default: () => {} },
},
setup(props, context) {
const classes = computed(() => ({
'sb-modal': true,
'sb-modal_open': props.open,
}));
return () => (
<div class={classes.value}>
<div
class="sb-modal__overlay"
onClick={($event: MouseEvent) => {
$event.stopPropagation();
props.onClose();
}}
>
<div class="sb-modal__content">
{context.slots.default?.()}
</div>
</div>
</div>
);
},
});

View file

@ -1,25 +0,0 @@
.sb-select {
background-color: var(--grey-0);
border: 1px solid var(--grey-2);
position: relative;
&:hover {
border: 1px solid var(--interact);
}
&::after {
position: absolute;
content: '';
top: 6px;
height: 100%;
right: 12px;
pointer-events: none;
}
&__input {
background: transparent;
appearance: none;
border: 0;
padding: 8px 32px 8px 12px;
}
}

View file

@ -1,21 +0,0 @@
import { defineComponent } from 'vue';
import './Select.scss';
export const SbSelect = defineComponent({
name: 'sb-select',
inheritAttrs: false,
setup(_, context) {
return () => (
<div class="sb-select">
<select
class="sb-select__input"
{...context.attrs}
>
{context.slots.default?.()}
</select>
</div>
);
},
});

View file

@ -1,5 +0,0 @@
.sb-toolbar {
position: absolute;
width: auto;
height: auto;
}

View file

@ -1,45 +0,0 @@
import { debounce } from 'lodash-es';
import {
defineComponent,
watch,
reactive,
} from 'vue';
import { useBlockSizing } from '../use-resize-observer';
import './Toolbar.scss';
export const SbToolbar = defineComponent({
name: 'sb-toolbar',
setup(_, context) {
const styles = reactive({
bottom: '',
left: '',
maxWidth: '',
});
const { editorDimensions, blockDimensions } = useBlockSizing();
const resetStyles = debounce(() => {
if (!editorDimensions.value || !blockDimensions.value) {
return;
}
const bottom = editorDimensions.value.height - blockDimensions.value.top;
styles.bottom = `${bottom}px`;
styles.left = `${blockDimensions.value.left}px`;
styles.maxWidth = `${blockDimensions.value.width}px`;
});
watch(editorDimensions, resetStyles);
watch(blockDimensions, resetStyles);
return () => (
<div
class="sb-toolbar"
style={styles}
onClick={($event: MouseEvent) => $event.stopPropagation()}
>
{context.slots?.default?.()}
</div>
);
},
});

View file

@ -1,38 +0,0 @@
.sb-tree-block-select {
&__list {
list-style: none;
margin: 0;
padding: 0;
&_base {
padding-right: 1rem;
}
}
&__node {
}
&__block {
padding: 0;
margin: 0;
padding-left: 1rem;
&-name {
display: block;
background: transparent;
border: 0;
font: inherit;
color: inherit;
padding: 0.5rem 1rem;
width: 100%;
text-align: left;
}
&_active {
& > .sb-tree-block-select__block-name {
outline: 1px solid var(--interact);
}
}
}
}

View file

@ -1,57 +0,0 @@
import { defineComponent } from 'vue';
import { TreeNode } from '../types';
import { useBlockTree } from '../use-block-tree';
import { useActivation } from '../use-activation';
import { SbContextMenu } from './ContextMenu';
import { SbButton } from './Button';
import './TreeBlockSelect.scss';
export const SbTreeBlockSelect = defineComponent({
name: 'sb-main-menu',
setup() {
const { blockTree } = useBlockTree();
const {
activate,
activeBlockId,
} = useActivation();
const treeToHtml = (tree: TreeNode, close: Function) => <li
class={{
'sb-tree-block-select__block': true,
'sb-tree-block-select__block_active': activeBlockId.value === tree.id,
}}
>
<button
class="sb-tree-block-select__block-name"
onClick={() => {
activate(tree.id);
close();
}}
onMouseenter={() => activate(tree.id)}
>{tree.name}</button>
{tree.children?.length
? <ul class="sb-tree-block-select__list">
{tree.children?.map((child: TreeNode) => treeToHtml(child, close))}
</ul>
: null
}
</li>;
return () => (
blockTree.value
? <SbContextMenu
class="sb-tree-block-select"
v-slots={{
context: ({ toggle }: { toggle: Function }) => <SbButton {...{ onClick: toggle }}>Tree</SbButton>,
default: ({ close }: { close: Function }) => <ul
class="sb-tree-block-select__list sb-tree-block-select__list_base"
>{treeToHtml(blockTree.value as TreeNode, close)}</ul>,
}}
/>
: ''
);
},
});

View file

@ -1,3 +0,0 @@
export default {
};

View file

@ -1,19 +1,6 @@
export * from './mode';
export * from './types';
export * from './block-helpers';
export * from './use-activation';
export * from './use-dynamic-blocks';
export * from './use-resize-observer';
export * from './directives/activation-cover.js';
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';

View file

@ -1,5 +0,0 @@
export enum SbMode {
Edit = 'edit',
Display = 'display',
}
export const Mode = Symbol('Schlechtenburg mode');

View file

@ -28,8 +28,7 @@ export interface BlockDefinition<T> {
name: string;
icon?: string;
getDefaultData: T;
edit: Component<BlockProps<T>>;
display: Component<BlockProps<T>>;
component: Component<BlockProps<T>>;
}
export interface BlockLibrary {

View file

@ -1,29 +0,0 @@
import {
Ref,
ref,
inject,
computed,
} from 'vue';
export const SymActiveBlock = Symbol('Schlechtenburg active block');
export function useActivation(currentBlockId: string|null = null) {
const activeBlockId: Ref<string|null> = inject(SymActiveBlock, ref(null));
const isActive = computed(() => activeBlockId.value === currentBlockId);
const activate = (id?: string|null) => {
activeBlockId.value = id !== undefined ? id : currentBlockId;
};
const requestActivation = () => {
if (activeBlockId.value) {
return;
}
activate();
};
return {
activeBlockId,
isActive,
activate,
requestActivation,
};
}

View file

@ -1,70 +0,0 @@
import {
ref,
Ref,
reactive,
inject,
provide,
onUnmounted,
} from 'vue';
import {
TreeNode,
BlockData,
} from './types';
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() {
const blockTree: Ref<TreeNode|null> = inject(SymBlockTree, ref(null));
const registerWithParent = inject(SymBlockTreeRegister, (_: TreeNode) => {});
const unregisterWithParent = inject(SymBlockTreeUnregister, (_: TreeNode) => {});
const self: TreeNode = reactive({
id: '',
name: '',
icon: '',
children: [],
});
// Provide a registration function to child blocks
provide(SymBlockTreeRegister, (block: TreeNode) => {
if (self.children.find((child: TreeNode) => child.id === block.id)) {
return;
}
self.children = [
...self.children,
block,
];
});
// Provide an unregistration function to child blocks
provide(SymBlockTreeUnregister, ({ id }: TreeNode) => {
self.children = self.children.filter((child: TreeNode) => child.id !== id);
});
const register = (block: BlockData<any>) => {
if (!block.id) {
throw new Error(`Cannot register a block without an id: ${JSON.stringify(block)}`);
}
self.id = block.id;
self.name = block.name;
// Register ourselves at the parent block
registerWithParent(self);
}
// Unregister from parent when we get destroyed
onUnmounted(() => {
if (self.id) {
unregisterWithParent(self);
}
});
return {
blockTree,
register,
};
}

View file

@ -1,19 +1,15 @@
import {
ref,
inject,
reactive,
} from 'vue';
import { BlockLibrary } from './types';
import { Mode, SbMode } from './mode';
export const SymBlockLibrary = Symbol('Schlechtenburg block library');
export function useDynamicBlocks() {
const mode = inject(Mode, ref(SbMode.Edit));
const customBlocks: BlockLibrary = inject(SymBlockLibrary, reactive({}));
const getBlock = (name: string) => customBlocks[name];
return {
mode,
customBlocks,
getBlock,
};

View file

@ -1,56 +0,0 @@
/// <reference types="resize-observer-browser" />
import {
Ref,
ref,
inject,
watch,
provide,
} from 'vue';
interface BlockRect {
height: number;
width: number;
left: number;
top: number;
}
export const SymBlockDimensions = Symbol('Schlechtenburg block dimensions');
export const SymEditorDimensions = Symbol('Schlechtenburg editor dimensions');
export function useResizeObserver(el: Ref<null|HTMLElement>, symbol: symbol) {
const dimensions: Ref<null|BlockRect> = ref(null);
provide(symbol, dimensions);
const triggerSizeCalculation = () => {
if (!el.value) {
return;
}
const clientRect = el.value.getBoundingClientRect();
dimensions.value = {
width: clientRect.width,
height: clientRect.height,
left: el.value.offsetLeft,
top: el.value.offsetTop,
};
};
const resizeObserver = new ResizeObserver(triggerSizeCalculation);
const mutationObserver = new MutationObserver(triggerSizeCalculation);
watch(el, () => {
if (!el.value) {
return;
}
resizeObserver.observe(el.value);
mutationObserver.observe(el.value, { attributes: true, childList: false, subtree: false });
});
return { triggerSizeCalculation, dimensions };
}
export function useBlockSizing() {
const editorDimensions: Ref<BlockRect|null> = inject(SymEditorDimensions, ref(null));
const blockDimensions: Ref<BlockRect|null> = inject(SymBlockDimensions, ref(null));
return { editorDimensions, blockDimensions };
}

View file

@ -1,11 +0,0 @@
# `@schlechtenburg/heading`
> TODO: description
## Usage
```
const heading = require('@schlechtenburg/heading');
// TODO: DEMONSTRATE API
```

View file

@ -1,7 +0,0 @@
'use strict';
const heading = require('..');
describe('@schlechtenburg/heading', () => {
it('needs tests');
});

View file

@ -1 +0,0 @@
export default {};

View file

@ -1,12 +0,0 @@
import { defineAsyncComponent } from 'vue';
import { getDefaultData } from './util';
export * from './util';
export const name = 'sb-heading';
export default {
name,
getDefaultData,
edit: defineAsyncComponent(() => import('./edit')),
display: defineAsyncComponent(() => import('./edit')),
};

View file

@ -1,2 +0,0 @@
export const a = 1;
export const getDefaultData = () => ({});

View file

@ -1,116 +0,0 @@
{
"name": "@schlechtenburg/heading",
"version": "0.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/helper-validator-identifier": {
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="
},
"@babel/parser": {
"version": "7.13.9",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.9.tgz",
"integrity": "sha512-nEUfRiARCcaVo3ny3ZQjURjHQZUo/JkEw7rLlSZy/psWGnvwXFtPcr6jb7Yb41DVW5LTe6KRq9LGleRNsg1Frw=="
},
"@babel/types": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz",
"integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==",
"requires": {
"@babel/helper-validator-identifier": "^7.12.11",
"lodash": "^4.17.19",
"to-fast-properties": "^2.0.0"
}
},
"@vue/compiler-core": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.0.7.tgz",
"integrity": "sha512-JFohgBXoyUc3mdeI2WxlhjQZ5fakfemJkZHX8Gu/nFbEg3+lKVUZmNKWmmnp9aOzJQZKoj77LjmFxiP+P+7lMQ==",
"requires": {
"@babel/parser": "^7.12.0",
"@babel/types": "^7.12.0",
"@vue/shared": "3.0.7",
"estree-walker": "^2.0.1",
"source-map": "^0.6.1"
}
},
"@vue/compiler-dom": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.0.7.tgz",
"integrity": "sha512-VnIH9EbWQm/Tkcp+8dCaNVsVvhm/vxCrIKWRkXY9215hTqOqQOvejT8IMjd2kc++nIsYMsdQk6H9qqBvoLe/Cw==",
"requires": {
"@vue/compiler-core": "3.0.7",
"@vue/shared": "3.0.7"
}
},
"@vue/reactivity": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.0.7.tgz",
"integrity": "sha512-FotWcNNaKhqpFZrdgsUOZ1enlJ5lhTt01CNTtLSyK7jYFgZBTuw8vKsEutZKDYZ1XKotOfoeO8N3pZQqmM6Etw==",
"requires": {
"@vue/shared": "3.0.7"
}
},
"@vue/runtime-core": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.0.7.tgz",
"integrity": "sha512-DBAZAwVvdmMXuyd6/9qqj/kYr/GaLTmn1L2/QLxLwP+UfhIboiTSBc/tUUb8MRk7Bb98GzNeAWkkT6AfooS3dQ==",
"requires": {
"@vue/reactivity": "3.0.7",
"@vue/shared": "3.0.7"
}
},
"@vue/runtime-dom": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.0.7.tgz",
"integrity": "sha512-Oij4ruOtnpQpCj+/Q3JPzgpTJ1Q7+N67pA53A8KVITEtxfvKL46NN6dhAZ5NGqwX6RWZpYqWQNewITeF0pHr8g==",
"requires": {
"@vue/runtime-core": "3.0.7",
"@vue/shared": "3.0.7",
"csstype": "^2.6.8"
}
},
"@vue/shared": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.0.7.tgz",
"integrity": "sha512-dn5FyfSc4ky424jH4FntiHno7Ss5yLkqKNmM/NXwANRnlkmqu74pnGetexDFVG5phMk9/FhwovUZCWGxsotVKg=="
},
"csstype": {
"version": "2.6.16",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.16.tgz",
"integrity": "sha512-61FBWoDHp/gRtsoDkq/B1nWrCUG/ok1E3tUrcNbZjsE9Cxd9yzUirjS3+nAATB8U4cTtaQmAHbNndoFz5L6C9Q=="
},
"estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
},
"vue": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.0.7.tgz",
"integrity": "sha512-8h4TikD+JabbMK9aRlBO4laG0AtNHRPHynxYgWZ9sq1YUPfzynd9Jeeb27XNyZytC7aCQRX9xe1+TQJuc181Tw==",
"requires": {
"@vue/compiler-dom": "3.0.7",
"@vue/runtime-dom": "3.0.7",
"@vue/shared": "3.0.7"
}
}
}
}

View file

@ -1,30 +0,0 @@
{
"name": "@schlechtenburg/heading",
"version": "0.0.0",
"description": "> TODO: description",
"author": "Benjamin Bädorf <hello@benjaminbaedorf.eu>",
"homepage": "",
"license": "GPL-3.0-or-later",
"main": "lib/index.ts",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git@git.b12f.io:b12f/schlechtenburg.git"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
},
"dependencies": {
"@schlechtenburg/core": "^0.0.0",
"vue": "^3.0.7"
}
}

View file

@ -1,11 +0,0 @@
# `@schlechtenburg/image`
> TODO: description
## Usage
```
const image = require('@schlechtenburg/image');
// TODO: DEMONSTRATE API
```

View file

@ -1,7 +0,0 @@
'use strict';
const image = require('..');
describe('@schlechtenburg/image', () => {
it('needs tests');
});

View file

@ -1,35 +0,0 @@
import { defineComponent, PropType } from 'vue';
import {
model,
SbBlock,
} from '@schlechtenburg/core';
import {
getDefaultData,
ImageData,
} from './util';
import './style.scss';
export default defineComponent({
name: 'sb-image-display',
model,
props: {
data: {
type: (null as unknown) as PropType<ImageData>,
default: getDefaultData,
},
},
setup(props) {
return () => <figure class="sb-image">
<img
class="sb-image__content"
src={props.data.src}
alt={props.data.alt}
/>
<SbBlock block={props.data.description} />
</figure>;
},
});

View file

@ -1,115 +0,0 @@
import {
defineComponent,
reactive,
ref,
Ref,
watch,
PropType,
} from 'vue';
import {
model,
SbToolbar,
SbButton,
SbBlock,
BlockData,
} from '@schlechtenburg/core';
import { ParagraphData } from '@schlechtenburg/paragraph';
import {
getDefaultData,
ImageData,
} from './util';
import './style.scss';
export default defineComponent({
name: 'sb-image-edit',
model,
props: {
onUpdate: { type: Function, default: () => {} },
data: {
type: (null as unknown) as PropType<ImageData>,
default: getDefaultData,
},
},
setup(props) {
const localData = reactive({
src: props.data.src,
alt: props.data.alt,
description: props.data.description,
});
const fileInput: Ref<null|HTMLInputElement> = ref(null);
watch(() => props.data, () => {
localData.src = props.data.src;
localData.alt = props.data.alt;
localData.description = props.data.description;
});
const selectImage = () => {
if (fileInput.value) {
fileInput.value.click();
}
};
const onImageSelect = () => {
if (fileInput.value && fileInput.value.files && fileInput.value.files.length) {
const reader = new FileReader();
reader.addEventListener('load', () => {
const src = reader?.result?.toString();
if (!src) {
throw new Error('Couldn\'t load image src');
}
props.onUpdate({
src,
alt: props.data.alt,
description: props.data.description,
});
});
reader.readAsDataURL(fileInput.value.files[0]);
}
};
const onDescriptionUpdate = (description: BlockData<ParagraphData>) => {
props.onUpdate({
...props.data,
description,
});
};
return () => (
<figure class="sb-image">
<SbToolbar>
{localData.src
? <SbButton {...{ onClick: selectImage }}>Select Image</SbButton>
: null}
<input
type="file"
ref={fileInput}
style="display: none;"
onInput={onImageSelect}
/>
</SbToolbar>
{localData.src
? <>
<img
src={localData.src}
alt={localData.alt}
class="sb-image__content"
/>
<SbBlock
block={localData.description}
onUpdate={(updated: BlockData<ParagraphData>) => onDescriptionUpdate(updated)}
/>
</>
: <SbButton {...{ onClick: selectImage }}>Select Image</SbButton>
}
</figure>
);
},
});

View file

@ -1,12 +0,0 @@
import { defineAsyncComponent } from 'vue';
import { getDefaultData } from './util';
export * from './util';
export const name = 'sb-image';
export default {
name,
getDefaultData,
edit: defineAsyncComponent(() => import('./edit')),
display: defineAsyncComponent(() => import('./display')),
};

View file

@ -1,8 +0,0 @@
.sb-image {
margin: 0;
&__content {
width: 100%;
height: auto;
}
}

View file

@ -1,25 +0,0 @@
import {
BlockData,
generateBlockId,
} from '@schlechtenburg/core';
import {
name as paragraphName,
ParagraphData,
getDefaultData as getDefaultParagraphData
} from '@schlechtenburg/paragraph';
export interface ImageData {
src: string;
alt: string;
description: BlockData<ParagraphData>;
}
export const getDefaultData: () => ImageData = () => ({
src: '',
alt: '',
description: {
id: generateBlockId(),
name: paragraphName,
data: getDefaultParagraphData(),
},
});

View file

@ -1,116 +0,0 @@
{
"name": "@schlechtenburg/image",
"version": "0.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/helper-validator-identifier": {
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="
},
"@babel/parser": {
"version": "7.13.9",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.9.tgz",
"integrity": "sha512-nEUfRiARCcaVo3ny3ZQjURjHQZUo/JkEw7rLlSZy/psWGnvwXFtPcr6jb7Yb41DVW5LTe6KRq9LGleRNsg1Frw=="
},
"@babel/types": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz",
"integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==",
"requires": {
"@babel/helper-validator-identifier": "^7.12.11",
"lodash": "^4.17.19",
"to-fast-properties": "^2.0.0"
}
},
"@vue/compiler-core": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.0.7.tgz",
"integrity": "sha512-JFohgBXoyUc3mdeI2WxlhjQZ5fakfemJkZHX8Gu/nFbEg3+lKVUZmNKWmmnp9aOzJQZKoj77LjmFxiP+P+7lMQ==",
"requires": {
"@babel/parser": "^7.12.0",
"@babel/types": "^7.12.0",
"@vue/shared": "3.0.7",
"estree-walker": "^2.0.1",
"source-map": "^0.6.1"
}
},
"@vue/compiler-dom": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.0.7.tgz",
"integrity": "sha512-VnIH9EbWQm/Tkcp+8dCaNVsVvhm/vxCrIKWRkXY9215hTqOqQOvejT8IMjd2kc++nIsYMsdQk6H9qqBvoLe/Cw==",
"requires": {
"@vue/compiler-core": "3.0.7",
"@vue/shared": "3.0.7"
}
},
"@vue/reactivity": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.0.7.tgz",
"integrity": "sha512-FotWcNNaKhqpFZrdgsUOZ1enlJ5lhTt01CNTtLSyK7jYFgZBTuw8vKsEutZKDYZ1XKotOfoeO8N3pZQqmM6Etw==",
"requires": {
"@vue/shared": "3.0.7"
}
},
"@vue/runtime-core": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.0.7.tgz",
"integrity": "sha512-DBAZAwVvdmMXuyd6/9qqj/kYr/GaLTmn1L2/QLxLwP+UfhIboiTSBc/tUUb8MRk7Bb98GzNeAWkkT6AfooS3dQ==",
"requires": {
"@vue/reactivity": "3.0.7",
"@vue/shared": "3.0.7"
}
},
"@vue/runtime-dom": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.0.7.tgz",
"integrity": "sha512-Oij4ruOtnpQpCj+/Q3JPzgpTJ1Q7+N67pA53A8KVITEtxfvKL46NN6dhAZ5NGqwX6RWZpYqWQNewITeF0pHr8g==",
"requires": {
"@vue/runtime-core": "3.0.7",
"@vue/shared": "3.0.7",
"csstype": "^2.6.8"
}
},
"@vue/shared": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.0.7.tgz",
"integrity": "sha512-dn5FyfSc4ky424jH4FntiHno7Ss5yLkqKNmM/NXwANRnlkmqu74pnGetexDFVG5phMk9/FhwovUZCWGxsotVKg=="
},
"csstype": {
"version": "2.6.16",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.16.tgz",
"integrity": "sha512-61FBWoDHp/gRtsoDkq/B1nWrCUG/ok1E3tUrcNbZjsE9Cxd9yzUirjS3+nAATB8U4cTtaQmAHbNndoFz5L6C9Q=="
},
"estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
},
"vue": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.0.7.tgz",
"integrity": "sha512-8h4TikD+JabbMK9aRlBO4laG0AtNHRPHynxYgWZ9sq1YUPfzynd9Jeeb27XNyZytC7aCQRX9xe1+TQJuc181Tw==",
"requires": {
"@vue/compiler-dom": "3.0.7",
"@vue/runtime-dom": "3.0.7",
"@vue/shared": "3.0.7"
}
}
}
}

View file

@ -1,31 +0,0 @@
{
"name": "@schlechtenburg/image",
"version": "0.0.0",
"description": "> TODO: description",
"author": "Benjamin Bädorf <hello@benjaminbaedorf.eu>",
"homepage": "",
"license": "GPL-3.0-or-later",
"main": "lib/index.ts",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git@git.b12f.io:b12f/schlechtenburg.git"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
},
"dependencies": {
"@schlechtenburg/core": "^0.0.0",
"@schlechtenburg/paragraph": "^0.0.0",
"vue": "^3.0.7"
}
}

View file

@ -1,11 +0,0 @@
# `@schlechtenburg/layout`
> TODO: description
## Usage
```
const layout = require('@schlechtenburg/layout');
// TODO: DEMONSTRATE API
```

View file

@ -1,7 +0,0 @@
'use strict';
const layout = require('..');
describe('@schlechtenburg/layout', () => {
it('needs tests');
});

View file

@ -1,46 +0,0 @@
import {
defineComponent,
computed,
PropType,
} from 'vue';
import {
model,
SbBlock,
} from '@schlechtenburg/core';
import {
LayoutData,
getDefaultData,
} from './util';
import './style.scss';
export default defineComponent({
name: 'sb-layout-display',
model,
props: {
data: {
type: (null as unknown) as PropType<LayoutData>,
default: getDefaultData,
},
},
setup(props) {
const classes = computed(() => ({
'sb-layout': true,
[`sb-layout_${props.data.orientation}`]: true,
}));
return () => (
<div class={classes.value}>
{...props.data.children.map((child) => (
<SbBlock
key={child.id}
block={child}
/>
))}
</div>
);
},
});

View file

@ -1,196 +0,0 @@
import {
defineComponent,
reactive,
computed,
watch,
PropType,
} from 'vue';
import {
model,
BlockData,
useActivation,
SbBlock,
SbButton,
SbToolbar,
SbBlockPlaceholder,
SbBlockOrdering,
} from '@schlechtenburg/core';
import {
LayoutData,
getDefaultData,
} from './util';
import './style.scss';
export default defineComponent({
name: 'sb-layout-edit',
model,
props: {
onUpdate: { type: Function, default: () => {} },
data: {
type: (null as unknown) as PropType<LayoutData>,
default: getDefaultData,
},
},
setup(props) {
const { activate } = useActivation();
const localData: LayoutData = reactive({
orientation: props.data.orientation,
children: [...props.data.children],
});
watch(() => props.data, () => {
localData.orientation = props.data.orientation;
localData.children = [...props.data.children];
});
const classes = computed(() => ({
'sb-layout': true,
[`sb-layout_${localData.orientation}`]: true,
}));
const toggleOrientation = () => {
props.onUpdate({
orientation: localData.orientation === 'vertical' ? 'horizontal' : 'vertical',
});
};
const onChildUpdate = (child: BlockData<any>, updated: BlockData<any>) => {
const index = localData.children.indexOf(child);
if (index === -1) {
return;
}
props.onUpdate({
children: [
...localData.children.slice(0, index),
{
...child,
...updated,
},
...localData.children.slice(index + 1),
],
});
};
const appendBlock = (block: BlockData<any>) => {
localData.children = [
...localData.children,
block,
];
props.onUpdate({ children: [...localData.children] });
activate(block.id);
};
const insertBlock = (index: number, block: BlockData<any>) => {
localData.children = [
...localData.children.slice(0, index + 1),
block,
...localData.children.slice(index + 1),
];
props.onUpdate({ children: [...localData.children] });
activate(block.id);
};
const removeBlock = (index: number) => {
localData.children = [
...localData.children.slice(0, index),
...localData.children.slice(index + 1),
];
props.onUpdate({ children: [...localData.children] });
const newActiveIndex = Math.max(index - 1, 0);
activate(localData.children[newActiveIndex].id);
};
const activateBlock = (index: number) => {
const safeIndex =
Math.max(
Math.min(
localData.children.length - 1,
index,
),
0,
);
activate(localData.children[safeIndex].id);
};
const moveBackward = (index: number) => {
if (index === 0) {
return;
}
const curr = localData.children[index];
const prev = localData.children[index - 1];
localData.children = [
...localData.children.slice(0, index - 1),
curr,
prev,
...localData.children.slice(index + 1),
];
props.onUpdate({ children: [...localData.children] });
};
const moveForward = (index: number) => {
if (index === localData.children.length - 1) {
return;
}
const curr = localData.children[index];
const next = localData.children[index + 1];
localData.children = [
...localData.children.slice(0, index),
next,
curr,
...localData.children.slice(index + 2),
];
props.onUpdate({ children: [...localData.children] });
};
return () => (
<div class={classes.value}>
<SbToolbar>
<SbButton
{...{
type: 'button',
onClick: toggleOrientation,
}}
>{localData.orientation}</SbButton>
</SbToolbar>
{...localData.children.map((child, index) => (
<SbBlock
{...{ key: child.id }}
data-order={index}
block={child}
onUpdate={(updated: BlockData<any>) => onChildUpdate(child, updated)}
onRemoveSelf={() => removeBlock(index)}
onPrependBlock={(block: BlockData<any>) => insertBlock(index - 1, block)}
onAppendBlock={(block: BlockData<any>) => insertBlock(index, block)}
onActivatePrevious={() => activateBlock(index - 1,)}
onActivateNext={() => activateBlock(index + 1,)}
>
{{
'context-toolbar': () =>
<SbBlockOrdering
onMoveBackward={() => moveBackward(index)}
onMoveForward={() => moveForward(index)}
onRemove={() => removeBlock(index)}
orientation={localData.orientation}
/>,
}}
</SbBlock>
))}
<SbBlockPlaceholder onInsertBlock={appendBlock} />
</div>
);
},
});

View file

@ -1,12 +0,0 @@
import { defineAsyncComponent } from 'vue';
import { getDefaultData } from './util';
export * from './util';
export const name = 'sb-layout';
export default {
name,
getDefaultData,
edit: defineAsyncComponent(() => import('./edit')),
display: defineAsyncComponent(() => import('./display')),
};

View file

@ -1,21 +0,0 @@
.sb-layout {
display: flex;
&_vertical {
flex-direction: column;
}
&_horizontal {
flex-direction: row;
}
&__item {
position: relative;
}
> * {
flex-basis: auto;
flex-grow: 1;
flex-shrink: 1;
}
}

View file

@ -1,11 +0,0 @@
import { BlockData } from '@schlechtenburg/core';
export interface LayoutData {
orientation: string;
children: BlockData<any>[];
}
export const getDefaultData: () => LayoutData = () => ({
orientation: 'vertical',
children: [],
});

View file

@ -1,116 +0,0 @@
{
"name": "@schlechtenburg/layout",
"version": "0.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/helper-validator-identifier": {
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="
},
"@babel/parser": {
"version": "7.13.9",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.9.tgz",
"integrity": "sha512-nEUfRiARCcaVo3ny3ZQjURjHQZUo/JkEw7rLlSZy/psWGnvwXFtPcr6jb7Yb41DVW5LTe6KRq9LGleRNsg1Frw=="
},
"@babel/types": {
"version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz",
"integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==",
"requires": {
"@babel/helper-validator-identifier": "^7.12.11",
"lodash": "^4.17.19",
"to-fast-properties": "^2.0.0"
}
},
"@vue/compiler-core": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.0.7.tgz",
"integrity": "sha512-JFohgBXoyUc3mdeI2WxlhjQZ5fakfemJkZHX8Gu/nFbEg3+lKVUZmNKWmmnp9aOzJQZKoj77LjmFxiP+P+7lMQ==",
"requires": {
"@babel/parser": "^7.12.0",
"@babel/types": "^7.12.0",
"@vue/shared": "3.0.7",
"estree-walker": "^2.0.1",
"source-map": "^0.6.1"
}
},
"@vue/compiler-dom": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.0.7.tgz",
"integrity": "sha512-VnIH9EbWQm/Tkcp+8dCaNVsVvhm/vxCrIKWRkXY9215hTqOqQOvejT8IMjd2kc++nIsYMsdQk6H9qqBvoLe/Cw==",
"requires": {
"@vue/compiler-core": "3.0.7",
"@vue/shared": "3.0.7"
}
},
"@vue/reactivity": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.0.7.tgz",
"integrity": "sha512-FotWcNNaKhqpFZrdgsUOZ1enlJ5lhTt01CNTtLSyK7jYFgZBTuw8vKsEutZKDYZ1XKotOfoeO8N3pZQqmM6Etw==",
"requires": {
"@vue/shared": "3.0.7"
}
},
"@vue/runtime-core": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.0.7.tgz",
"integrity": "sha512-DBAZAwVvdmMXuyd6/9qqj/kYr/GaLTmn1L2/QLxLwP+UfhIboiTSBc/tUUb8MRk7Bb98GzNeAWkkT6AfooS3dQ==",
"requires": {
"@vue/reactivity": "3.0.7",
"@vue/shared": "3.0.7"
}
},
"@vue/runtime-dom": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.0.7.tgz",
"integrity": "sha512-Oij4ruOtnpQpCj+/Q3JPzgpTJ1Q7+N67pA53A8KVITEtxfvKL46NN6dhAZ5NGqwX6RWZpYqWQNewITeF0pHr8g==",
"requires": {
"@vue/runtime-core": "3.0.7",
"@vue/shared": "3.0.7",
"csstype": "^2.6.8"
}
},
"@vue/shared": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.0.7.tgz",
"integrity": "sha512-dn5FyfSc4ky424jH4FntiHno7Ss5yLkqKNmM/NXwANRnlkmqu74pnGetexDFVG5phMk9/FhwovUZCWGxsotVKg=="
},
"csstype": {
"version": "2.6.16",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.16.tgz",
"integrity": "sha512-61FBWoDHp/gRtsoDkq/B1nWrCUG/ok1E3tUrcNbZjsE9Cxd9yzUirjS3+nAATB8U4cTtaQmAHbNndoFz5L6C9Q=="
},
"estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
},
"vue": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.0.7.tgz",
"integrity": "sha512-8h4TikD+JabbMK9aRlBO4laG0AtNHRPHynxYgWZ9sq1YUPfzynd9Jeeb27XNyZytC7aCQRX9xe1+TQJuc181Tw==",
"requires": {
"@vue/compiler-dom": "3.0.7",
"@vue/runtime-dom": "3.0.7",
"@vue/shared": "3.0.7"
}
}
}
}

View file

@ -1,30 +0,0 @@
{
"name": "@schlechtenburg/layout",
"version": "0.0.0",
"description": "> TODO: description",
"author": "Benjamin Bädorf <hello@benjaminbaedorf.eu>",
"homepage": "",
"license": "GPL-3.0-or-later",
"main": "lib/index.ts",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git@git.b12f.io:b12f/schlechtenburg.git"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
},
"dependencies": {
"@schlechtenburg/core": "^0.0.0",
"vue": "^3.0.7"
}
}

View file

@ -1,41 +0,0 @@
import {
defineComponent,
computed,
PropType,
} from 'vue';
import {
model,
} from '@schlechtenburg/core';
import {
getDefaultData,
ParagraphData,
} from './util';
import './style.scss';
export default defineComponent({
name: 'sb-paragraph-display',
model,
props: {
data: {
type: Object as PropType<ParagraphData>,
default: getDefaultData,
},
},
setup(props) {
const classes = computed(() => ({
'sb-paragraph': true,
[`sb-paragraph_align-${props.data.align}`]: true,
}));
return () => <p
class={classes.value}
{...{
innerHTML: props.data.value,
}}
></p>;
},
});

View file

@ -5,15 +5,8 @@ import {
ref,
Ref,
onMounted,
watch,
PropType,
} from 'vue';
import {
model,
useActivation,
SbToolbar,
SbSelect,
} from '@schlechtenburg/core';
import {
getDefaultData,
ParagraphData,
@ -24,7 +17,10 @@ import './style.scss';
export default defineComponent({
name: 'sb-paragraph-edit',
model,
model: {
prop: 'block',
event: 'update',
},
props: {
blockId: { type: String, required: true },
@ -32,11 +28,6 @@ export default defineComponent({
type: (null as unknown) as PropType<ParagraphData>,
default: getDefaultData,
},
onUpdate: { type: Function, default: () => {} },
onAppendBlock: { type: Function, default: () => {} },
onRemoveSelf: { type: Function, default: () => {} },
onActivateNext: { type: Function, default: () => {} },
onActivatePrevious: { type: Function, default: () => {} },
},
setup(props) {
@ -51,121 +42,24 @@ export default defineComponent({
};
const inputEl: Ref<null|HTMLElement> = ref(null);
const { isActive, activate } = useActivation(props.blockId);
const focusInput = () => {
if (inputEl.value && isActive.value) {
inputEl.value.focus();
}
};
onMounted(() => {
focusInput();
if (inputEl.value) {
inputEl.value.innerHTML = localData.value;
}
});
watch(isActive, focusInput);
watch(() => props.data, () => {
localData.value = props.data.value;
localData.align = props.data.align;
if (inputEl.value) {
inputEl.value.innerHTML = localData.value;
}
});
const onTextUpdate = ($event: Event) => {
localData.value = ($event.target as HTMLElement).innerHTML;
};
const classes = computed(() => ({
'sb-paragraph': true,
'sb-paragraph_focused': localData.focused,
[`sb-paragraph_align-${localData.align}`]: true,
}));
const setAlignment = ($event: Event) => {
props.onUpdate({
value: localData.value,
align: ($event.target as HTMLSelectElement).value,
});
};
const onFocus = () => {
localData.focused = true;
activate();
};
const onBlur = () => {
localData.focused = false;
props.onUpdate({
value: localData.value,
align: localData.align,
});
};
const onKeydown = ($event: KeyboardEvent) => {
if ($event.key === 'Enter' && !$event.shiftKey) {
const id = `${+(new Date())}`;
props.onAppendBlock({
id,
name: 'sb-paragraph',
data: getDefaultData(),
});
activate(id);
$event.preventDefault();
}
};
const onKeyup = ($event: KeyboardEvent) => {
if ($event.key === 'Backspace' && localData.value === '') {
props.onRemoveSelf();
}
const selection = window.getSelection();
const node = selection?.focusNode;
const childNodes = Array.from(inputEl?.value?.childNodes || []);
const index = node ? childNodes.indexOf(node as ChildNode) : -1;
if (node === inputEl.value || index === 0 || index === childNodes.length -1) {
switch ($event.key) {
case 'ArrowDown':
props.onActivateNext();
break;
case 'ArrowUp':
props.onActivatePrevious();
break;
}
}
};
return () => (
<div class={classes.value}>
<SbToolbar>
<SbSelect
{...{
value: localData.align,
onChange: setAlignment,
}}
>
<option>left</option>
<option>center</option>
<option>right</option>
</SbSelect>
</SbToolbar>
<p
class="sb-paragraph__input"
ref={inputEl}
contenteditable
onInput={onTextUpdate}
onFocus={onFocus}
onBlur={onBlur}
onKeydown={onKeydown}
onKeyup={onKeyup}
></p>
</div>
);

Some files were not shown because too many files have changed in this diff Show more