Multiple changes
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
// controllers/menuDataController.js
|
const { MenuItem } = require('../models'); // Stellen Sie sicher, dass das Modell korrekt importiert wird
|
||||||
|
|
||||||
const fetchMenuData = require('../utils/fetchMenuData');
|
const fetchMenuData = require('../utils/fetchMenuData');
|
||||||
|
|
||||||
exports.getMenuData = async (req, res) => {
|
exports.getMenuData = async (req, res) => {
|
||||||
@@ -15,9 +14,10 @@ exports.saveMenuData = async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const menuData = req.body;
|
const menuData = req.body;
|
||||||
await MenuItem.destroy({ where: {} });
|
await MenuItem.destroy({ where: {} });
|
||||||
await MenuItem.bulkCreate(menuData);
|
await MenuItem.bulkCreate(menuData, { include: [{ model: MenuItem, as: 'submenu' }] });
|
||||||
res.status(200).send('Menü-Daten erfolgreich gespeichert');
|
res.status(200).send('Menü-Daten erfolgreich gespeichert');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Speichern der Menü-Daten:', error);
|
||||||
res.status(500).send('Fehler beim Speichern der Menü-Daten');
|
res.status(500).send('Fehler beim Speichern der Menü-Daten');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
15
migrations/20240616083105-add-page-title-to-menu-items.js
Normal file
15
migrations/20240616083105-add-page-title-to-menu-items.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// migrations/[timestamp]-add-page-title-to-menu-items.js
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: async (queryInterface, Sequelize) => {
|
||||||
|
await queryInterface.addColumn('menu_items', 'page_title', {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
allowNull: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
down: async (queryInterface, Sequelize) => {
|
||||||
|
await queryInterface.removeColumn('menu_items', 'page_title');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// models/MenuItem.js
|
||||||
const { DataTypes } = require('sequelize');
|
const { DataTypes } = require('sequelize');
|
||||||
|
|
||||||
module.exports = (sequelize) => {
|
module.exports = (sequelize) => {
|
||||||
@@ -32,6 +33,10 @@ module.exports = (sequelize) => {
|
|||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 0
|
defaultValue: 0
|
||||||
|
},
|
||||||
|
page_title: { // Neuer Eintrag
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
tableName: 'menu_items',
|
tableName: 'menu_items',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const router = express.Router();
|
|||||||
const pageController = require('../controllers/pageController');
|
const pageController = require('../controllers/pageController');
|
||||||
const authMiddleware = require('../middleware/authMiddleware')
|
const authMiddleware = require('../middleware/authMiddleware')
|
||||||
|
|
||||||
router.get('/', authMiddleware, pageController.getPageContent);
|
router.get('/', pageController.getPageContent);
|
||||||
router.post('/', authMiddleware, pageController.savePageContent);
|
router.post('/', authMiddleware, pageController.savePageContent);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
73
src/components/ContentComponent.vue
Normal file
73
src/components/ContentComponent.vue
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<div v-html="renderedContent"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from '@/axios';
|
||||||
|
import { mapState, mapGetters } from 'vuex';
|
||||||
|
import { render } from '@/utils/render';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ContentComponent',
|
||||||
|
props: {
|
||||||
|
link: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
content: '',
|
||||||
|
title: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['menuData']),
|
||||||
|
...mapGetters(['getMenuData']),
|
||||||
|
renderedContent() {
|
||||||
|
return render(this.content);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
link: {
|
||||||
|
immediate: true,
|
||||||
|
handler(newLink) {
|
||||||
|
this.fetchContent(newLink);
|
||||||
|
this.setTitle(newLink);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetchContent(link) {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`/page-content?link=${link}`);
|
||||||
|
this.content = response.data.content;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Abrufen des Inhalts:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setTitle(link) {
|
||||||
|
const findTitle = (menuItems, link) => {
|
||||||
|
for (const item of menuItems) {
|
||||||
|
if (item.link === link) {
|
||||||
|
return item.pageTitle || item.name;
|
||||||
|
}
|
||||||
|
if (item.submenu && item.submenu.length > 0) {
|
||||||
|
const found = findTitle(item.submenu, link);
|
||||||
|
if (found) {
|
||||||
|
return `${found}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
this.title = findTitle(this.menuData, link);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
91
src/components/WorshipDialog.vue
Normal file
91
src/components/WorshipDialog.vue
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="isOpen" class="dialog-overlay">
|
||||||
|
<div class="dialog-content">
|
||||||
|
<h3>Gottesdienst-Konfiguration</h3>
|
||||||
|
<div>
|
||||||
|
<label for="location-select">Bitte wählen Sie den Ort für den Gottestdienst aus:</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<select id="location-select" v-model="selectedLocation">
|
||||||
|
<option :value="-1">Alle Orte</option>
|
||||||
|
<option v-for="location in locations" :key="location.id" :value="location.id">
|
||||||
|
{{ location.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button @click="confirmWorshipConfiguration">Bestätigen</button>
|
||||||
|
<button @click="closeWorshipDialog">Schließen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import axios from '@/axios';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'WorshipDialog',
|
||||||
|
emits: ['confirm'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const isOpen = ref(false);
|
||||||
|
const locations = ref([]);
|
||||||
|
const selectedLocation = ref(-1);
|
||||||
|
|
||||||
|
const openWorshipDialog = () => {
|
||||||
|
isOpen.value = true;
|
||||||
|
fetchLocations();
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeWorshipDialog = () => {
|
||||||
|
isOpen.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchLocations = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get('/event-places');
|
||||||
|
locations.value = response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Laden der Orte:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmWorshipConfiguration = () => {
|
||||||
|
emit('confirm', selectedLocation.value);
|
||||||
|
closeWorshipDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
isOpen,
|
||||||
|
locations,
|
||||||
|
selectedLocation,
|
||||||
|
openWorshipDialog,
|
||||||
|
closeWorshipDialog,
|
||||||
|
confirmWorshipConfiguration,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dialog-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<label for="page-select">Wähle eine Seite:</label>
|
<label for="page-select">Wähle eine Seite:</label>
|
||||||
<select id="page-select" v-model="selectedPage" @change="loadPageContent">
|
<select id="page-select" v-model="selectedPage" @change="loadPageContent">
|
||||||
<option v-for="page in pages" :key="page.link" :value="page.link">{{ page.name }}</option>
|
<option v-for="page in sortedPages" :key="page.link" :value="page.link">{{ page.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
@@ -15,8 +15,7 @@
|
|||||||
<button @click="editor.chain().focus().toggleItalic().run()">Kursiv</button>
|
<button @click="editor.chain().focus().toggleItalic().run()">Kursiv</button>
|
||||||
<button @click="editor.chain().focus().toggleUnderline().run()">Unterstrichen</button>
|
<button @click="editor.chain().focus().toggleUnderline().run()">Unterstrichen</button>
|
||||||
<button @click="editor.chain().focus().toggleStrike().run()">Durchgestrichen</button>
|
<button @click="editor.chain().focus().toggleStrike().run()">Durchgestrichen</button>
|
||||||
<button
|
<button @click="editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()">Tabelle</button>
|
||||||
@click="editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()">Tabelle</button>
|
|
||||||
<button @click="editor.chain().focus().toggleBulletList().run()">Liste</button>
|
<button @click="editor.chain().focus().toggleBulletList().run()">Liste</button>
|
||||||
<button @click="editor.chain().focus().toggleOrderedList().run()">Nummerierte Liste</button>
|
<button @click="editor.chain().focus().toggleOrderedList().run()">Nummerierte Liste</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,12 +34,14 @@
|
|||||||
<button>Events</button>
|
<button>Events</button>
|
||||||
<button>Kontaktpersonen</button>
|
<button>Kontaktpersonen</button>
|
||||||
<button>Institutionen</button>
|
<button>Institutionen</button>
|
||||||
<button>Gottesdienste</button>
|
<button @click="openWorshipDialog">Gottesdienste</button>
|
||||||
</div>
|
</div>
|
||||||
<div :class="['htmleditor']">
|
<div :class="['htmleditor']">
|
||||||
<EditorContent :editor="editor" />
|
<EditorContent :editor="editor" />
|
||||||
</div>
|
</div>
|
||||||
<button @click="savePageContent">Speichern</button>
|
<button @click="savePageContent">Speichern</button>
|
||||||
|
|
||||||
|
<WorshipDialog ref="worshipDialog" @confirm="insertWorshipList" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -59,18 +60,21 @@ import Strike from '@tiptap/extension-strike';
|
|||||||
import BulletList from '@tiptap/extension-bullet-list';
|
import BulletList from '@tiptap/extension-bullet-list';
|
||||||
import OrderedList from '@tiptap/extension-ordered-list';
|
import OrderedList from '@tiptap/extension-ordered-list';
|
||||||
import Heading from '@tiptap/extension-heading';
|
import Heading from '@tiptap/extension-heading';
|
||||||
import { CustomTableCell, CustomTableHeader } from '../../extensions/CustomTableCell'; // Importiere die angepasste Erweiterung
|
import { CustomTableCell, CustomTableHeader } from '../../extensions/CustomTableCell';
|
||||||
|
import WorshipDialog from '@/components/WorshipDialog.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'EditPagesComponent',
|
name: 'EditPagesComponent',
|
||||||
components: {
|
components: {
|
||||||
EditorContent,
|
EditorContent,
|
||||||
|
WorshipDialog,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const pages = ref([]);
|
const pages = ref([]);
|
||||||
const selectedPage = ref('');
|
const selectedPage = ref('');
|
||||||
const pageHtmlContent = computed(() => store.state.pageContent);
|
const pageHtmlContent = computed(() => store.state.pageContent);
|
||||||
|
const worshipDialog = ref(null);
|
||||||
|
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
extensions: [
|
extensions: [
|
||||||
@@ -97,10 +101,23 @@ export default {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 () => {
|
const fetchPages = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('/menu-data');
|
const response = await axios.get('/menu-data');
|
||||||
pages.value = response.data;
|
const data = response.data;
|
||||||
|
const allPages = [];
|
||||||
|
flattenPages(data, allPages);
|
||||||
|
pages.value = allPages.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Abrufen der Seiten:', error);
|
console.error('Fehler beim Abrufen der Seiten:', error);
|
||||||
}
|
}
|
||||||
@@ -115,7 +132,7 @@ export default {
|
|||||||
if (editor.value && editor.value.commands) {
|
if (editor.value && editor.value.commands) {
|
||||||
editor.value.commands.setContent(content, false);
|
editor.value.commands.setContent(content, false);
|
||||||
} else {
|
} else {
|
||||||
setTimeout(setEditorContent, 100); // Try again after 100ms if not ready
|
setTimeout(setEditorContent, 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -146,13 +163,31 @@ export default {
|
|||||||
|
|
||||||
onMounted(fetchPages);
|
onMounted(fetchPages);
|
||||||
|
|
||||||
|
const sortedPages = computed(() => {
|
||||||
|
return pages.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const openWorshipDialog = () => {
|
||||||
|
worshipDialog.value.openWorshipDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
const insertWorshipList = (configuration) => {
|
||||||
|
if (editor.value) {
|
||||||
|
editor.value.chain().focus().insertContent(`{{ worshipslist:location=${configuration},order:"date asc" }}`).run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pages,
|
pages,
|
||||||
|
sortedPages,
|
||||||
selectedPage,
|
selectedPage,
|
||||||
editor,
|
editor,
|
||||||
loadPageContent,
|
loadPageContent,
|
||||||
savePageContent,
|
savePageContent,
|
||||||
pageHtmlContent,
|
pageHtmlContent,
|
||||||
|
openWorshipDialog,
|
||||||
|
insertWorshipList,
|
||||||
|
worshipDialog,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
272
src/content/admin/MenuManagement.vue
Normal file
272
src/content/admin/MenuManagement.vue
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
<template>
|
||||||
|
<div class="menu-management">
|
||||||
|
<h1>Menüverwaltung</h1>
|
||||||
|
<div class="button-container">
|
||||||
|
<button @click="addMenuItem">Hauptmenü hinzufügen</button>
|
||||||
|
<button @click="saveMenuData">Speichern</button>
|
||||||
|
</div>
|
||||||
|
<div v-if="selectedMenuItem" class="edit-form">
|
||||||
|
<h2>Menüpunkt bearbeiten</h2>
|
||||||
|
<form @submit.prevent="saveMenuData">
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<input id="name" v-model="selectedMenuItem.name" placeholder="Name" />
|
||||||
|
|
||||||
|
<label for="link">Link</label>
|
||||||
|
<input id="link" v-model="selectedMenuItem.link" placeholder="Link" />
|
||||||
|
|
||||||
|
<label for="order-id">Order ID</label>
|
||||||
|
<input id="order-id" v-model.number="selectedMenuItem.orderId" placeholder="Order ID" type="number" class="order-id" />
|
||||||
|
|
||||||
|
<div class="checkbox-container">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" v-model="selectedMenuItem.showInMenu" />
|
||||||
|
Im Menü anzeigen
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" v-model="selectedMenuItem.requiresAuth" />
|
||||||
|
Authentifizierung erforderlich
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label for="parent-id">Elternelement</label>
|
||||||
|
<select id="parent-id" v-model="selectedMenuItem.parent_id">
|
||||||
|
<option value="">Ohne Elternelement</option>
|
||||||
|
<option v-for="item in flattenedMenuData" :key="item.id" :value="item.id">
|
||||||
|
<span v-html="getIndentedName(item)"></span>
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="tree-view">
|
||||||
|
<ul>
|
||||||
|
<li v-for="menuItem in sortedMenuData" :key="menuItem.id">
|
||||||
|
<div class="menu-item">
|
||||||
|
<span @click="selectMenuItem(menuItem)">
|
||||||
|
{{ menuItem.name }} (ID: {{ menuItem.orderId }})
|
||||||
|
</span>
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button @click="addSubmenu(menuItem)" class="action-button">Untermenü hinzufügen</button>
|
||||||
|
<button @click="removeMenuItem(menuItem)" class="action-button">Löschen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul v-if="menuItem.submenu.length">
|
||||||
|
<li v-for="submenuItem in sortedSubmenu(menuItem)" :key="submenuItem.id">
|
||||||
|
<div class="menu-item">
|
||||||
|
<span @click="selectMenuItem(submenuItem)">
|
||||||
|
{{ submenuItem.name }} (ID: {{ submenuItem.orderId }})
|
||||||
|
</span>
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button @click="addSubmenu(menuItem)" class="action-button">Untermenü hinzufügen</button>
|
||||||
|
<button @click="removeSubmenu(menuItem, submenuItem)" class="action-button">Löschen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ref, onMounted, computed } from 'vue';
|
||||||
|
import axios from '../../axios';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'MenuManagement',
|
||||||
|
setup() {
|
||||||
|
const menuData = ref([]);
|
||||||
|
const selectedMenuItem = ref(null);
|
||||||
|
|
||||||
|
const fetchMenuData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get('/menu-data');
|
||||||
|
menuData.value = response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Abrufen der Menü-Daten:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveMenuData = async () => {
|
||||||
|
try {
|
||||||
|
const flatMenuData = flattenMenuData(menuData.value);
|
||||||
|
await axios.post('/menu-data', flatMenuData);
|
||||||
|
alert('Menü-Daten erfolgreich gespeichert');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Speichern der Menü-Daten:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const flattenMenuData = (data, parentId = null) => {
|
||||||
|
return data.reduce((acc, item) => {
|
||||||
|
const newItem = { ...item, parent_id: parentId };
|
||||||
|
const { submenu, ...rest } = newItem;
|
||||||
|
acc.push(rest);
|
||||||
|
if (submenu && submenu.length) {
|
||||||
|
acc.push(...flattenMenuData(submenu, newItem.id));
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addMenuItem = () => {
|
||||||
|
const newItem = {
|
||||||
|
name: '',
|
||||||
|
link: '',
|
||||||
|
component: '',
|
||||||
|
showInMenu: true,
|
||||||
|
requiresAuth: false,
|
||||||
|
orderId: 0,
|
||||||
|
submenu: [],
|
||||||
|
parent_id: null,
|
||||||
|
};
|
||||||
|
menuData.value.push(newItem);
|
||||||
|
selectMenuItem(newItem);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addSubmenu = (menuItem) => {
|
||||||
|
const newSubItem = {
|
||||||
|
name: '',
|
||||||
|
link: '',
|
||||||
|
component: '',
|
||||||
|
showInMenu: true,
|
||||||
|
requiresAuth: false,
|
||||||
|
orderId: 0,
|
||||||
|
parent_id: menuItem.id,
|
||||||
|
};
|
||||||
|
menuItem.submenu.push(newSubItem);
|
||||||
|
selectMenuItem(newSubItem);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeMenuItem = (menuItem) => {
|
||||||
|
const index = menuData.value.indexOf(menuItem);
|
||||||
|
if (index > -1) {
|
||||||
|
menuData.value.splice(index, 1);
|
||||||
|
}
|
||||||
|
selectedMenuItem.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeSubmenu = (menuItem, submenuItem) => {
|
||||||
|
const index = menuItem.submenu.indexOf(submenuItem);
|
||||||
|
if (index > -1) {
|
||||||
|
menuItem.submenu.splice(index, 1);
|
||||||
|
}
|
||||||
|
selectedMenuItem.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectMenuItem = (menuItem) => {
|
||||||
|
selectedMenuItem.value = menuItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortedMenuData = computed(() => {
|
||||||
|
return [...menuData.value].sort((a, b) => a.orderId - b.orderId);
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortedSubmenu = (menuItem) => {
|
||||||
|
return menuItem.submenu.slice().sort((a, b) => a.orderId - b.orderId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIndentedName = (item) => {
|
||||||
|
return ' '.repeat(item.indent * 2) + item.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(fetchMenuData);
|
||||||
|
|
||||||
|
return {
|
||||||
|
menuData,
|
||||||
|
sortedMenuData,
|
||||||
|
sortedSubmenu,
|
||||||
|
selectedMenuItem,
|
||||||
|
fetchMenuData,
|
||||||
|
saveMenuData,
|
||||||
|
addMenuItem,
|
||||||
|
addSubmenu,
|
||||||
|
removeMenuItem,
|
||||||
|
removeSubmenu,
|
||||||
|
selectMenuItem,
|
||||||
|
getIndentedName,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.menu-management {
|
||||||
|
width: 100%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-container {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view li {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view .menu-item {
|
||||||
|
display: inline-flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view span {
|
||||||
|
cursor: pointer;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view button {
|
||||||
|
border: none;
|
||||||
|
height: 1.6em;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
margin: 1px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-view span:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-form {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-form label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-form input:not([type="checkbox"]) {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-form .checkbox-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-form .order-id {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-form button {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -1,22 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="previewinfo">Dies ist eine Vorschau.</div>
|
<div class="previewinfo">Dies ist eine Vorschau.</div>
|
||||||
<div v-html="content"></div>
|
<div v-html="renderedContent"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
|
import { render } from '@/utils/render';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PagePreview',
|
name: 'PagePreview',
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const content = computed(() => store.state.pageContent);
|
const content = computed(() => store.state.pageContent);
|
||||||
|
const renderedContent = computed(() => render(content.value));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content,
|
renderedContent,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="some-page">
|
||||||
<h2>Gottesdienste in unserer Gemeinde</h2>
|
<ContentComponent :link="currentLink" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
import ContentComponent from '@/components/ContentComponent.vue';
|
||||||
name: 'AllWorshipsContent',
|
|
||||||
};
|
export default {
|
||||||
</script>
|
name: 'SomePage',
|
||||||
|
components: {
|
||||||
|
ContentComponent,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentLink() {
|
||||||
|
return this.$route.path;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
|
|||||||
24
src/content/worship/AmBuegelContent.vue
Normal file
24
src/content/worship/AmBuegelContent.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<div class="some-page">
|
||||||
|
<ContentComponent :link="currentLink" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ContentComponent from '@/components/ContentComponent.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SomePage',
|
||||||
|
components: {
|
||||||
|
ContentComponent,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentLink() {
|
||||||
|
return this.$route.path;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
@@ -1,11 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="some-page">
|
||||||
<h2>Gottesdienste in Bonames</h2>
|
<ContentComponent :link="currentLink" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ContentComponent from '@/components/ContentComponent.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'BonamesContent',
|
name: 'SomePage',
|
||||||
|
components: {
|
||||||
|
ContentComponent,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentLink() {
|
||||||
|
return this.$route.path;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
|
|||||||
24
src/content/worship/KalbachContent.vue
Normal file
24
src/content/worship/KalbachContent.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<div class="some-page">
|
||||||
|
<ContentComponent :link="currentLink" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ContentComponent from '@/components/ContentComponent.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'SomePage',
|
||||||
|
components: {
|
||||||
|
ContentComponent,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentLink() {
|
||||||
|
return this.$route.path;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
@@ -48,9 +48,8 @@ export default createStore({
|
|||||||
actions: {
|
actions: {
|
||||||
async loadMenuData({ commit }) {
|
async loadMenuData({ commit }) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/menu-data');
|
const response = await axios.get('/menu-data');
|
||||||
const menuData = await response.json();
|
commit('setMenuData', response.data);
|
||||||
commit('setMenuData', menuData);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Laden der Menü-Daten:', error);
|
console.error('Fehler beim Laden der Menü-Daten:', error);
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/utils/render.js
Normal file
15
src/utils/render.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export function render(content) {
|
||||||
|
console.log('do render', content);
|
||||||
|
const worshipsPattern = /{{ worshipslist:(.*?) }}/g;
|
||||||
|
const renderedContent = content.replace(worshipsPattern, (match, config) => {
|
||||||
|
return renderWorships(config);
|
||||||
|
});
|
||||||
|
|
||||||
|
return renderedContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderWorships(config) {
|
||||||
|
console.log('render worships', config);
|
||||||
|
return `<div class="worships-list">Worships at location ${location}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@ function buildMenuStructure(menuItems) {
|
|||||||
showInMenu: item.show_in_menu,
|
showInMenu: item.show_in_menu,
|
||||||
requiresAuth: item.requires_auth,
|
requiresAuth: item.requires_auth,
|
||||||
orderId: item.order_id,
|
orderId: item.order_id,
|
||||||
|
pageTitle: item.page_title,
|
||||||
submenu: []
|
submenu: []
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user