Change regular text form to be tip tap, revert old component

This commit is contained in:
Ramon Wenger 2022-02-24 17:54:51 +01:00
parent 031e4d58de
commit 233a2655f7
3 changed files with 75 additions and 184 deletions

View File

@ -52,7 +52,7 @@
const ImageForm = () => import(/* webpackChunkName: "content-forms" */'@/components/content-forms/ImageForm'); const ImageForm = () => import(/* webpackChunkName: "content-forms" */'@/components/content-forms/ImageForm');
const DocumentForm = () => import(/* webpackChunkName: "content-forms" */'@/components/content-forms/DocumentForm'); const DocumentForm = () => import(/* webpackChunkName: "content-forms" */'@/components/content-forms/DocumentForm');
const AssignmentForm = () => import(/* webpackChunkName: "content-forms" */'@/components/content-forms/AssignmentForm'); const AssignmentForm = () => import(/* webpackChunkName: "content-forms" */'@/components/content-forms/AssignmentForm');
const TextForm = () => import(/* webpackChunkName: "content-forms" */'@/components/content-forms/TextForm'); const TextForm = () => import(/* webpackChunkName: "content-forms" */'@/components/content-forms/TipTap.vue');
const SubtitleForm = () => import(/* webpackChunkName: "content-forms" */'@/components/content-forms/SubtitleForm'); const SubtitleForm = () => import(/* webpackChunkName: "content-forms" */'@/components/content-forms/SubtitleForm');
const CHOOSER = 'content-block-element-chooser-widget'; const CHOOSER = 'content-block-element-chooser-widget';

View File

@ -1,161 +1,37 @@
<template> <template>
<!-- eslint-disable vue/no-v-html -->
<div class="text-form"> <div class="text-form">
<toggle <textarea
:checked="isList" :value="text"
label="Liste" class="text-form__input skillbox-textarea"
@input="toggleList" data-cy="text-form-input"
/> placeholder="Text erfassen..."
@input="$emit('change-text', $event.target.value, index)"
<tip-tap
:value="value.text"
@input="$emit('change-text', $event)"
/> />
</div> </div>
</template> </template>
<script lang="ts"> <script>
import Vue, {PropType} from "vue"; export default {
import Toggle from "@/components/ui/Toggle.vue";
import TipTap from "@/components/content-forms/TipTap.vue";
interface Value {
text: string;
}
interface Data {
localOnlyText: string,
textToStoreWhileFocused: string,
textBuffer: string, // text that gets saved, but not updated, when the user is typing
htmlBuffer: string, // same as above, but this is the html that's generated
isList: boolean,
// listHTML: string
}
const ul = /<\/?ul>/;
const li = /<\/?li>/;
const br = /<br(\/)?>/;
const div = /(<\/?)div(>)/g;
const anythingElse = /<(?!\/?(?:ul|li|p))\/?([^>]*)>/;
// the non-breaking space is used so the cursor is positioned inside the element by default. We remove it later
const nonBreakingSpace = '&nbsp;';
const emptyParagraph = `<p>${nonBreakingSpace}</p>`;
const emptyList = `<ul><li>${nonBreakingSpace}</li></ul>`;
export default Vue.extend({
props: { props: {
value: { value: {
type: Object as PropType<Value>, type: Object,
validator(value: Value) { default: null,
validator(value) {
return Object.prototype.hasOwnProperty.call(value, 'text'); return Object.prototype.hasOwnProperty.call(value, 'text');
}
}, },
}, index: {
}, type: Number,
components: {TipTap, Toggle}, default: -1
}
data(): Data {
return {
// this is the text that's tracked by the input field. It will not update every time the prop updates, otherwise we keep losing focus
localOnlyText: '',
// this is the variable that stores our text while the user is typing and also updates the outer value
textToStoreWhileFocused: '', // we need to have a text that always updates for user input
textBuffer: '',
htmlBuffer: '',
isList: false,
// listHTML: `<ul>
// <li>Hello</li>
// <li>World</li>
// </ul>`
};
}, },
computed: { computed: {
text(): string { text() {
// if has ul / li, return as is return this.value.text ? this.value.text.replace(/<br(\/)?>/, '\n').replace(/(<([^>]+)>)/ig, '') : '';
if (this.isList) {
return this.value.text;
} else {
// need p tags
return this.value.text ? this.value.text.replace(/<br(\/)?>/, '\n').replace(/(<([^>]+)>)/ig, '') : emptyParagraph;
}
},
html(): string {
return this.value.text;
},
},
mounted() {
console.log('i was mounted now');
// sync this only once, when the component gets mounted
this.isList = ul.test(this.value.text);
console.log(this.text);
this.localOnlyText = this.text;
this.textBuffer = this.text;
this.htmlBuffer = this.text;
// when mounted, we also check if the text already contains some li or ul elements
// after loading, we use the local data property to determine if we want a list or now
},
methods: {
updateSomeText() {
console.log('htmlBuffer', this.htmlBuffer);
let newText = this.htmlBuffer.replace(div, '$1p$2')
.replace(nonBreakingSpace, '')
// .replace(br, '\n')
.replace(anythingElse, '');
if (newText === '') {
// if the new text would be empty, we replace it according to our needs
if (this.isList) {
newText = emptyList;
} else {
newText = emptyParagraph;
} }
} }
this.localOnlyText = newText; };
console.log(newText);
},
toggleList(checked: boolean) {
console.log(checked);
if (checked) {
this.makeList();
} else {
this.localOnlyText = this.textBuffer;
this.isList = false;
}
},
makeList() {
// this.localOnlyText = this.localOnlyText
let sep = /\n+/;
let lines = this.textBuffer.split(sep);
let listItems = lines.reduce((previous, current) => {
return `${previous}<li>${current}</li>`;
}, '');
let list = `
<ul>
${listItems}
</ul>`;
console.log(list);
this.localOnlyText = list;
this.isList = true;
},
changeSomeText(event: Event) {
const target = (<HTMLInputElement>event.target);
let newHtml = target.innerHTML;
let newText = target.innerText;
this.htmlBuffer = newHtml;
this.textBuffer = newText;
this.$emit('change-text', newHtml);
},
change(event: Event) {
const target = (<HTMLInputElement>event.target);
const newText = target.innerText;
this.$emit('change-text', newText);
},
},
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@ -165,26 +41,5 @@
&__input { &__input {
width: 100%; width: 100%;
} }
&__contenteditable {
width: 100%;
height: 300px;
}
/deep/ ul {
list-style: initial;
}
/deep/ li {
@include inputfont;
}
/deep/ div {
@include inputfont;
}
/deep/ p {
@include inputfont;
}
} }
</style> </style>

View File

@ -1,53 +1,70 @@
<template> <template>
<div class="tip-tap"> <div class="tip-tap">
<button @click="makeList"> <toggle
List :bordered="false"
</button> :checked="isList"
label="Als Liste formatieren"
@input="toggleList"
/>
<editor-content :editor="editor" /> <editor-content :editor="editor" />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue, {PropType} from 'vue';
import {Editor, EditorContent} from "@tiptap/vue-2"; import {Editor, EditorContent} from "@tiptap/vue-2";
import Document from '@tiptap/extension-document'; import Document from '@tiptap/extension-document';
import Paragraph from '@tiptap/extension-paragraph'; import Paragraph from '@tiptap/extension-paragraph';
import Text from '@tiptap/extension-text'; import Text from '@tiptap/extension-text';
import BulletList from '@tiptap/extension-bullet-list'; import BulletList from '@tiptap/extension-bullet-list';
import ListItem from '@tiptap/extension-list-item'; import ListItem from '@tiptap/extension-list-item';
import Toggle from "@/components/ui/Toggle.vue";
interface Data { interface Data {
editor: Editor | null; editor: Editor | undefined;
}
interface Value {
text: string;
} }
export default Vue.extend({ export default Vue.extend({
props: { props: {
value: { value: {
type: String, type: Object as PropType<Value>,
default: '' validator(value: Value) {
} return Object.prototype.hasOwnProperty.call(value, 'text');
}, },
},
},
components: { components: {
Toggle,
EditorContent EditorContent
}, },
data(): Data { data(): Data {
return { return {
editor: null, editor: undefined,
}; };
}, },
computed: {
isList(): boolean {
return this.editor?.isActive('bulletList') || false;
}
},
watch: { watch: {
value(newVal) { value({text}: Value) {
const editor = this.editor as Editor; // editor is always initialized on mount, cast it const editor = this.editor as Editor; // editor is always initialized on mount, cast it
const isSame = editor.getHTML() === newVal; const isSame = editor.getHTML() === text;
if (isSame) { if (isSame) {
return; return;
} }
editor.commands.setContent(newVal, false); editor.commands.setContent(text, false);
} }
}, },
@ -68,7 +85,10 @@
ListItem ListItem
], ],
onUpdate: () => { onUpdate: () => {
this.$emit('input', (<Editor>this.editor).getHTML()); const newValue = {
text:(<Editor>this.editor).getHTML()
};
this.$emit('input', newValue);
} }
}); });
}, },
@ -78,10 +98,10 @@
}, },
methods: { methods: {
makeList() { toggleList() {
const editor = this.editor as Editor; const editor = this.editor as Editor;
editor.chain().selectAll().toggleBulletList().run(); editor.chain().selectAll().toggleBulletList().run();
} },
} }
}); });
</script> </script>
@ -95,5 +115,21 @@
@include inputstyle; @include inputstyle;
flex-direction: column; flex-direction: column;
} }
/deep/ ul {
list-style: initial;
}
/deep/ li {
@include inputfont;
}
/deep/ div {
@include inputfont;
}
/deep/ p {
@include inputfont;
}
} }
</style> </style>