Merge branch 'develop' into milestone/ff-admin-unit
This commit is contained in:
commit
b9b0381356
10 changed files with 239 additions and 48 deletions
151
src/components/QuillEditor.vue
Normal file
151
src/components/QuillEditor.vue
Normal file
|
@ -0,0 +1,151 @@
|
|||
<template>
|
||||
<div :id="id" :style="style" class="flex flex-col">
|
||||
<div ref="quill"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { markRaw, onMounted, onUnmounted, ref, useTemplateRef, watch, type PropType } from "vue";
|
||||
import Quill, { Delta, type QuillOptions } from "quill";
|
||||
import "quill/dist/quill.core.css";
|
||||
import "quill/dist/quill.snow.css";
|
||||
import isEqual from "lodash.isequal";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
|
||||
type RangeStatic = { index: number; length: number };
|
||||
|
||||
const quillElement = useTemplateRef("quill");
|
||||
|
||||
const instance = ref<undefined | Quill>();
|
||||
const model = ref<string | Delta>("");
|
||||
|
||||
const props = defineProps({
|
||||
id: String,
|
||||
style: [String, Object],
|
||||
options: {
|
||||
type: Object as PropType<QuillOptions>,
|
||||
default: {},
|
||||
},
|
||||
toolbar: {
|
||||
type: [String, Array, Object],
|
||||
default: "",
|
||||
},
|
||||
content: {
|
||||
type: [String, Object] as PropType<string | Delta | null>,
|
||||
default: "",
|
||||
},
|
||||
contentType: {
|
||||
type: String as PropType<"delta" | "html" | "text">,
|
||||
default: "text",
|
||||
validator: (value: string) => {
|
||||
return ["delta", "html", "text"].includes(value);
|
||||
},
|
||||
},
|
||||
readonly: { type: Boolean, default: false },
|
||||
placeholder: String,
|
||||
});
|
||||
|
||||
const emit = defineEmits({
|
||||
"update:content": (content: Delta | string) => true,
|
||||
textChange: (tc: { delta: Delta; oldDelta: Delta; source: string }) => true,
|
||||
selectionChange: (sc: { range: RangeStatic; oldRange: RangeStatic; source: string }) => true,
|
||||
focus: () => true,
|
||||
blur: () => true,
|
||||
ready: (quill: Quill) => true,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.content,
|
||||
(val, oldVal) => {
|
||||
if (!instance.value || !val || isEqual(val, model.value)) return;
|
||||
setContent(cloneDeep(val));
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
watch(
|
||||
() => props.readonly,
|
||||
(val, oldVal) => {
|
||||
instance.value?.enable(val);
|
||||
}
|
||||
);
|
||||
watch(model, (val, oldVal) => {
|
||||
emit("update:content", cloneDeep(val));
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (!quillElement.value) return;
|
||||
const quill = new Quill(quillElement.value, {
|
||||
theme: "snow",
|
||||
modules: {
|
||||
toolbar: props.toolbar,
|
||||
},
|
||||
placeholder: props.placeholder,
|
||||
readOnly: props.readonly,
|
||||
...props.options,
|
||||
});
|
||||
|
||||
quill.on(Quill.events.SELECTION_CHANGE, (range, oldRange, source) => {
|
||||
if (!range) {
|
||||
emit("blur");
|
||||
} else {
|
||||
emit("focus");
|
||||
}
|
||||
emit("selectionChange", { range, oldRange, source });
|
||||
});
|
||||
quill.on(Quill.events.TEXT_CHANGE, (delta, oldDelta, source) => {
|
||||
model.value = getContent();
|
||||
emit("textChange", { delta, oldDelta, source });
|
||||
});
|
||||
|
||||
emit("ready", quill);
|
||||
instance.value = markRaw(quill);
|
||||
setContent(cloneDeep(props.content ?? ""));
|
||||
});
|
||||
onUnmounted(() => {
|
||||
if (instance.value) {
|
||||
instance.value.off(Quill.events.SELECTION_CHANGE);
|
||||
instance.value.off(Quill.events.TEXT_CHANGE);
|
||||
instance.value = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
function getContent(): string | Delta {
|
||||
if (props.contentType === "delta") {
|
||||
return (instance.value?.getContents() ?? {}) as Delta;
|
||||
} else if (props.contentType === "html") {
|
||||
return instance.value?.getSemanticHTML().replace(/ /g, " ") ?? "";
|
||||
} else {
|
||||
return instance.value?.getText().replace(/ /g, " ") ?? "";
|
||||
}
|
||||
}
|
||||
function setContent(content: Delta | string) {
|
||||
if (content == "") return;
|
||||
if (props.contentType === "delta") {
|
||||
if (typeof content !== "string") {
|
||||
instance.value?.setContents(content);
|
||||
}
|
||||
} else if (props.contentType === "html") {
|
||||
if (typeof content === "string" && instance.value) {
|
||||
instance.value.clipboard.dangerouslyPasteHTML(content);
|
||||
let delta = instance.value.clipboard.convert({ html: content });
|
||||
instance.value?.setContents(delta);
|
||||
}
|
||||
} else {
|
||||
if (typeof content === "string") {
|
||||
instance.value?.setText(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.ql-container {
|
||||
border-bottom-left-radius: 0.5em;
|
||||
border-bottom-right-radius: 0.5em;
|
||||
}
|
||||
.ql-toolbar {
|
||||
background: var(--color-gray-100);
|
||||
border-top-left-radius: 0.5em;
|
||||
border-top-right-radius: 0.5em;
|
||||
}
|
||||
</style>
|
|
@ -52,8 +52,7 @@ import { defineComponent } from "vue";
|
|||
import { mapActions, mapState, mapWritableState } from "pinia";
|
||||
import Spinner from "@/components/Spinner.vue";
|
||||
import { useNewsletterStore } from "@/stores/admin/club/newsletter/newsletter";
|
||||
import { QuillEditor } from "@vueup/vue-quill";
|
||||
import "@vueup/vue-quill/dist/vue-quill.snow.css";
|
||||
import QuillEditor from "@/components/QuillEditor.vue";
|
||||
import { toolbarOptions } from "@/helpers/quillConfig";
|
||||
import { useAbilityStore } from "@/stores/ability";
|
||||
</script>
|
||||
|
|
|
@ -107,8 +107,7 @@
|
|||
import { defineComponent } from "vue";
|
||||
import { mapActions, mapState, mapWritableState } from "pinia";
|
||||
import Spinner from "@/components/Spinner.vue";
|
||||
import { QuillEditor } from "@vueup/vue-quill";
|
||||
import "@vueup/vue-quill/dist/vue-quill.snow.css";
|
||||
import QuillEditor from "@/components/QuillEditor.vue";
|
||||
import { toolbarOptions } from "@/helpers/quillConfig";
|
||||
import { useNewsletterDatesStore } from "@/stores/admin/club/newsletter/newsletterDates";
|
||||
import { useAbilityStore } from "@/stores/ability";
|
||||
|
|
|
@ -18,13 +18,12 @@
|
|||
<label for="summary">Zusammenfassung</label>
|
||||
<QuillEditor
|
||||
id="summary"
|
||||
theme="snow"
|
||||
placeholder="Zusammenfassung zum Newsletter..."
|
||||
style="height: 250px; max-height: 250px; min-height: 250px"
|
||||
contentType="html"
|
||||
:toolbar="toolbarOptions"
|
||||
v-model:content="activeNewsletterObj.description"
|
||||
:enable="can('create', 'club', 'newsletter')"
|
||||
:readonly="!can('create', 'club', 'newsletter')"
|
||||
:style="!can('create', 'club', 'newsletter') ? 'opacity: 75%; background: rgb(243 244 246)' : ''"
|
||||
/>
|
||||
</div>
|
||||
|
@ -42,8 +41,7 @@ import { defineComponent } from "vue";
|
|||
import { mapActions, mapState, mapWritableState } from "pinia";
|
||||
import Spinner from "@/components/Spinner.vue";
|
||||
import { useNewsletterStore } from "@/stores/admin/club/newsletter/newsletter";
|
||||
import { QuillEditor } from "@vueup/vue-quill";
|
||||
import "@vueup/vue-quill/dist/vue-quill.snow.css";
|
||||
import QuillEditor from "@/components/QuillEditor.vue";
|
||||
import { toolbarOptions } from "@/helpers/quillConfig";
|
||||
import { useAbilityStore } from "@/stores/ability";
|
||||
</script>
|
||||
|
|
|
@ -78,8 +78,7 @@
|
|||
import { defineComponent } from "vue";
|
||||
import { mapActions, mapState, mapWritableState } from "pinia";
|
||||
import Spinner from "@/components/Spinner.vue";
|
||||
import { QuillEditor } from "@vueup/vue-quill";
|
||||
import "@vueup/vue-quill/dist/vue-quill.snow.css";
|
||||
import QuillEditor from "@/components/QuillEditor.vue";
|
||||
import { toolbarOptions } from "@/helpers/quillConfig";
|
||||
import { useProtocolAgendaStore } from "@/stores/admin/club/protocol/protocolAgenda";
|
||||
import { useAbilityStore } from "@/stores/ability";
|
||||
|
|
|
@ -78,8 +78,7 @@
|
|||
import { defineComponent } from "vue";
|
||||
import { mapActions, mapState, mapWritableState } from "pinia";
|
||||
import Spinner from "@/components/Spinner.vue";
|
||||
import { QuillEditor } from "@vueup/vue-quill";
|
||||
import "@vueup/vue-quill/dist/vue-quill.snow.css";
|
||||
import QuillEditor from "@/components/QuillEditor.vue";
|
||||
import { toolbarOptions } from "@/helpers/quillConfig";
|
||||
import { useProtocolDecisionStore } from "@/stores/admin/club/protocol/protocolDecision";
|
||||
import { useAbilityStore } from "@/stores/ability";
|
||||
|
|
|
@ -64,8 +64,7 @@ import { defineComponent } from "vue";
|
|||
import { mapActions, mapState, mapWritableState } from "pinia";
|
||||
import Spinner from "@/components/Spinner.vue";
|
||||
import { useProtocolStore } from "@/stores/admin/club/protocol/protocol";
|
||||
import { QuillEditor } from "@vueup/vue-quill";
|
||||
import "@vueup/vue-quill/dist/vue-quill.snow.css";
|
||||
import QuillEditor from "@/components/QuillEditor.vue";
|
||||
import { toolbarOptions } from "@/helpers/quillConfig";
|
||||
import { useAbilityStore } from "@/stores/ability";
|
||||
</script>
|
||||
|
|
|
@ -95,8 +95,7 @@
|
|||
import { defineComponent } from "vue";
|
||||
import { mapActions, mapState, mapWritableState } from "pinia";
|
||||
import Spinner from "@/components/Spinner.vue";
|
||||
import { QuillEditor } from "@vueup/vue-quill";
|
||||
import "@vueup/vue-quill/dist/vue-quill.snow.css";
|
||||
import QuillEditor from "@/components/QuillEditor.vue";
|
||||
import { toolbarOptions } from "@/helpers/quillConfig";
|
||||
import { useProtocolVotingStore } from "@/stores/admin/club/protocol/protocolVoting";
|
||||
import { useAbilityStore } from "@/stores/ability";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue