Files
miriamgemeinde/src/content/admin/EditPagesComponent.vue
Torsten Schulz 97c72540cf extended editor
2024-06-21 18:06:17 +02:00

468 lines
13 KiB
Vue

<template>
<div class="edit-pages">
<h2>Webseiten bearbeiten</h2>
<div>
<label for="page-select">Wähle eine Seite:</label>
<select id="page-select" v-model="selectedPage" @change="loadPageContent">
<option v-for="page in sortedPages" :key="page.link" :value="page.link">{{ page.name }}</option>
</select>
</div>
<div class="toolbar">
<button @click="toggleHeading(1)">H1</button>
<button @click="toggleHeading(2)">H2</button>
<button @click="toggleHeading(3)">H3</button>
<button @click="toggleBold()" width="24" height="24">
<BoldIcon width="24" height="24" />
</button>
<button @click="toggleItalic()">
<ItalicIcon width="24" height="24" />
</button>
<button @click="toggleUnderline()">
<UnderlineIcon width="24" height="24" />
</button>
<button @click="toggleStrike()">
<StrikethroughIcon width="24" height="24" />
</button>
<button @click="insertTable()">
<TableIcon width="24" height="24" />
</button>
<button @click="toggleBulletList()">
<ListIcon width="24" height="24" />
</button>
<button @click="toggleOrderedList()">
<NumberedListLeftIcon width="24" height="24" />
</button>
<button @click="openAddImageDialog">
<StatsReportIcon width="24" height="24" />
</button>
<button @click="openAddLinkDialog">
<OpenInWindowIcon width="24" height="24" />
</button>
<button @click="openAddDownloadDialog">
<DownloadIcon width="24" height="24" />
</button>
<button @click="openColorPicker">Schriftfarbe</button>
<input type="color" ref="colorPicker" @input="setColor" style="display: none;" />
</div>
<div class="table-toolbar">
<button @click="addColumnBefore()">
<ArrowDownIcon width="10" height="10" class="align-top" />
<Table2ColumnsIcon width="24" height="24" />
</button>
<button @click="addColumnAfter()">
<Table2ColumnsIcon width="24" height="24" />
<ArrowDownIcon width="10" height="10" class="align-top" />
</button>
<button @click="addRowBefore()">
<ArrowRightIcon width="10" height="10" class="align-top" />
<TableRowsIcon width="24" height="24" />
</button>
<button @click="addRowAfter()">
<ArrowRightIcon width="10" height="10" />
<TableRowsIcon width="24" height="24" />
</button>
<button @click="deleteColumn()">
<Table2ColumnsIcon width="24" height="24" class="delete-icon" />
</button>
<button @click="deleteRow()">
<TableRowsIcon width="24" height="24" class="delete-icon" />
</button>
<button @click="toggleHeaderColumn()">
<AlignTopBoxIcon width="24" height="24" />
</button>
<button @click="toggleHeaderRow()">
<AlignLeftBoxIcon width="24" height="24" />
</button>
</div>
<div class="additional-toolbar">
<button @click="openAddEventsDialog">Events</button>
<button>Kontaktpersonen</button>
<button>Institutionen</button>
<button @click="openWorshipDialog">Gottesdienste</button>
</div>
<div :class="['htmleditor']">
<EditorContent :editor="editor" />
</div>
<button @click="savePageContent">Speichern</button>
<WorshipDialog ref="worshipDialog" @confirm="insertWorshipList" />
<AddImageDialog ref="addImageDialog" @confirm="insertImage" />
<AddEventDialog ref="addEventDialog" @confirm="insertEvent" />
<AddLinkDialog ref="addLinkDialog" @confirm="insertLink" />
<AddDownloadDialog ref="addDownloadDialog" @confirm="insertDownload" />
<input type="color" ref="colorPicker" @input="setColor" style="display: none;" />
</div>
</template>
<script>
import { ref, computed, onMounted, watch } from 'vue';
import { useStore } from 'vuex';
import axios from '../../axios';
import { EditorContent, useEditor } from '@tiptap/vue-3';
import StarterKit from '@tiptap/starter-kit';
import Table from '@tiptap/extension-table';
import TableRow from '@tiptap/extension-table-row';
import Bold from '@tiptap/extension-bold';
import Italic from '@tiptap/extension-italic';
import Underline from '@tiptap/extension-underline';
import Strike from '@tiptap/extension-strike';
import BulletList from '@tiptap/extension-bullet-list';
import OrderedList from '@tiptap/extension-ordered-list';
import Heading from '@tiptap/extension-heading';
import Link from '@tiptap/extension-link';
import TextStyle from '@tiptap/extension-text-style';
import Color from '@tiptap/extension-color';
import { CustomTableCell, CustomTableHeader } from '../../extensions/CustomTableCell';
import WorshipDialog from '@/components/WorshipDialog.vue';
import AddImageDialog from '@/components/AddImageDialog.vue';
import AddEventDialog from '@/components/AddEventDialog.vue';
import AddLinkDialog from '@/components/AddLinkDialog.vue';
import AddDownloadDialog from '@/components/AddDownloadDialog.vue';
import { BoldIcon, ItalicIcon, UnderlineIcon, StrikethroughIcon, ListIcon, NumberedListLeftIcon, TableIcon,
Table2ColumnsIcon, ArrowDownIcon, ArrowRightIcon, TableRowsIcon, AlignTopBoxIcon, AlignLeftBoxIcon, StatsReportIcon,
OpenInWindowIcon, DownloadIcon
} from '@/icons';
export default {
name: 'EditPagesComponent',
components: {
EditorContent,
WorshipDialog,
AddImageDialog,
BoldIcon,
ItalicIcon,
UnderlineIcon,
StrikethroughIcon,
ListIcon,
NumberedListLeftIcon,
TableIcon,
Table2ColumnsIcon,
ArrowDownIcon,
ArrowRightIcon,
TableRowsIcon,
AlignTopBoxIcon,
AlignLeftBoxIcon,
StatsReportIcon,
AddEventDialog,
AddLinkDialog,
AddDownloadDialog,
OpenInWindowIcon,
DownloadIcon,
},
setup() {
const store = useStore();
const pages = ref([]);
const selectedPage = ref('');
const pageHtmlContent = computed(() => store.state.pageContent);
const worshipDialog = ref(null);
const addImageDialog = ref(null);
const addEventDialog = ref(null);
const addLinkDialog = ref(null);
const addDownloadDialog = ref(null);
const colorPicker = ref(null);
const editor = useEditor({
extensions: [
StarterKit,
Table.configure({
resizable: true,
}),
TableRow,
CustomTableCell,
CustomTableHeader,
Bold,
Italic,
Underline,
Strike,
BulletList,
OrderedList,
Heading.configure({
levels: [1, 2, 3],
}),
Link.configure({
openOnClick: false,
}),
TextStyle,
Color,
],
content: '',
onUpdate: ({ editor }) => {
store.commit('SET_PAGE_CONTENT', editor.getHTML());
},
});
const flattenPages = (pages, allPages, parentName = '') => {
pages.forEach(page => {
const pageName = parentName ? `${parentName} -> ${page.name}` : page.name;
allPages.push({ ...page, name: pageName });
if (page.submenu && page.submenu.length) {
flattenPages(page.submenu, allPages, pageName);
}
});
};
const fetchPages = async () => {
try {
const response = await axios.get('/menu-data');
const data = response.data;
const allPages = [];
flattenPages(data, allPages);
pages.value = allPages.sort((a, b) => a.name.localeCompare(b.name));
store.commit('setMenuData', data);
} catch (error) {
console.error('Fehler beim Abrufen der Seiten:', error);
}
};
const loadPageContent = async () => {
try {
await store.dispatch('loadPageContent', selectedPage.value);
const content = store.getters.pageContent;
const setEditorContent = () => {
if (editor.value && editor.value.commands) {
editor.value.commands.setContent(content, false);
} else {
setTimeout(setEditorContent, 100);
}
};
setEditorContent();
} catch (error) {
console.error('Fehler beim Laden des Seiteninhalts:', error);
}
};
const savePageContent = async () => {
try {
const selectedPageName = pages.value.find(page => page.link === selectedPage.value)?.name || '';
if (!selectedPageName) {
return;
}
const contentToSave = editor.value.getHTML();
store.commit('SET_PAGE_CONTENT', contentToSave);
await store.dispatch('savePageContent', {
link: selectedPage.value,
name: selectedPageName,
});
} catch (error) {
console.error('Fehler beim Speichern des Seiteninhalts:', error);
}
};
onMounted(fetchPages);
const sortedPages = computed(() => {
return pages.value;
});
watch(selectedPage, (newPage) => {
store.dispatch('setSelectedPage', newPage);
const page = pages.value.find(page => page.link === newPage);
if (page) {
store.dispatch('setPageTitle', page.name);
}
loadPageContent();
});
const openWorshipDialog = () => {
worshipDialog.value.openWorshipDialog();
};
const openAddImageDialog = () => {
addImageDialog.value.openAddImageDialog();
};
const insertWorshipList = (selectedLocations) => {
if (editor.value) {
const configuration = `location=${selectedLocations},order:"date asc"`;
editor.value.chain().focus().insertContent(`{{ worshipslist:${configuration} }}`).run();
}
};
const insertImage = (selectedImage) => {
if (editor.value) {
editor.value.chain().focus().insertContent(`{{ image:${selectedImage} }}`).run();
}
};
const openAddEventDialog = () => {
addEventDialog.value.openAddEventDialog();
};
const insertEvent = (configString) => {
if (editor.value) {
editor.value.chain().focus().insertContent(configString).run();
}
};
const openAddLinkDialog = () => {
addLinkDialog.value.openAddLinkDialog();
};
const insertLink = ({ url, text }) => {
if (url && text && editor.value) {
editor.value.chain().focus().extendMarkRange('link').setLink({ href: url }).insertContent(text).run();
}
};
const openAddDownloadDialog = () => {
addDownloadDialog.value.openAddDownloadDialog();
};
const insertDownload = ({ title, hash, extension }) => {
if (title && hash && extension && editor.value) {
const url = `/files/download/${hash}`;
editor.value.chain().focus().extendMarkRange('link').setLink({ href: url }).insertContent(title).run();
}
};
const openColorPicker = () => {
colorPicker.value.click();
};
const setColor = (event) => {
const color = event.target.value;
if (editor.value) {
editor.value.chain().focus().setColor(color).run();
}
};
const toggleHeading = (level) => {
editor.value.chain().focus().toggleHeading({level: level}).run();
};
const toggleItalic = () => {
editor.value.chain().focus().toggleItalic().run();
};
const toggleBold = () => {
editor.value.chain().focus().toggleBold().run();
};
const toggleUnderline = () => {
editor.value.chain().focus().toggleUnderline().run();
};
const toggleStrike = () => {
editor.value.chain().focus().toggleStrike().run();
};
const insertTable = () => {
editor.value.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run();
};
const toggleBulletList = () => {
editor.value.chain().focus().toggleBulletList().run();
};
const toggleOrderedList = () => {
editor.value.chain().focus().toggleOrderedList().run();
};
return {
pages,
sortedPages,
selectedPage,
editor,
loadPageContent,
savePageContent,
pageHtmlContent,
openWorshipDialog,
insertWorshipList,
worshipDialog,
addImageDialog,
openAddImageDialog,
insertImage,
addEventDialog,
openAddEventsDialog: openAddEventDialog,
insertEvent,
addLinkDialog,
openAddLinkDialog,
insertLink,
addDownloadDialog,
openAddDownloadDialog,
insertDownload,
colorPicker,
openColorPicker,
setColor,
toggleHeading,
toggleBold,
toggleItalic,
toggleUnderline,
toggleStrike,
insertTable,
toggleBulletList,
toggleOrderedList,
};
},
};
</script>
<style scoped>
.edit-pages {
width: 100%;
margin: auto;
}
#page-select {
margin-bottom: 20px;
}
.toolbar {
margin-bottom: 10px;
}
.toolbar button {
margin-right: 5px;
}
.table-toolbar {
margin-bottom: 10px;
}
.table-toolbar button {
margin-right: 5px;
}
.additional-toolbar {
margin-bottom: 10px;
}
.additional-toolbar button {
margin-right: 5px;
}
.ql-container {
background-color: #fff !important;
}
.ql-editor {
background-color: #fff !important;
}
.edit-pages div>button {
border: none;
padding: 0.25em;
margin: 2px;
}
.button-icon {
background-image: url(@/assets/icons/Bold_Italic_Underline.png);
background-position: left top;
background-size: 10px 24px;
background-repeat: no-repeat;
width: 24px;
height: 24px;
}
.delete-icon {
fill: #ff0000;
}
.align-top {
vertical-align: top;
}
</style>