From 467dfd8c1bbbadf98d01d04a2b62fe42f4dd28b3 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Sat, 21 Dec 2024 16:01:18 +0100 Subject: [PATCH 01/19] template editor --- package-lock.json | 20 ++++- package.json | 1 + public/unlayerTool.js | 51 +++++++++++ src/helpers/unlayerEditor.ts | 28 ++++++ src/router/index.ts | 22 +++++ src/stores/admin/navigation.ts | 1 + .../admin/settings/template/Template.vue | 87 +++++++++++++++++++ 7 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 public/unlayerTool.js create mode 100644 src/helpers/unlayerEditor.ts create mode 100644 src/views/admin/settings/template/Template.vue diff --git a/package-lock.json b/package-lock.json index 06048b0..33b0fc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "fireportal-ui", + "name": "member-administration-ui", "version": "0.0.11", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "fireportal-ui", + "name": "member-administration-ui", "version": "0.0.11", "license": "GPL-3.0-only", "dependencies": { @@ -31,6 +31,7 @@ "socket.io-client": "^4.5.0", "uuid": "^9.0.0", "vue": "^3.4.29", + "vue-email-editor": "^2.1.4", "vue-router": "^4.3.3" }, "devDependencies": { @@ -9410,6 +9411,12 @@ "node": ">= 10.0.0" } }, + "node_modules/unlayer-types": { + "version": "1.188.0", + "resolved": "https://registry.npmjs.org/unlayer-types/-/unlayer-types-1.188.0.tgz", + "integrity": "sha512-tnn+FjUZv1qUOoRUYRFxSDz9kHfhy7dLxzMZgnU5+k6GDSBlpa8mA+r4+r0D83M+mUUd/XwuM+gvfRLGzrqZ+g==", + "license": "MIT" + }, "node_modules/upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", @@ -9700,6 +9707,15 @@ } } }, + "node_modules/vue-email-editor": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/vue-email-editor/-/vue-email-editor-2.1.4.tgz", + "integrity": "sha512-9H6P2zgjOx4XJmKyMb4ZzCpsnKAqFk74daD86l/MhUvucF/qizTMUhOFnIMU6u9jtiB64NFvPTTzkRTTYTGkFw==", + "dependencies": { + "unlayer-types": "latest", + "vue": "^3.2.13" + } + }, "node_modules/vue-eslint-parser": { "version": "9.4.3", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", diff --git a/package.json b/package.json index 66fbfab..0373442 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "socket.io-client": "^4.5.0", "uuid": "^9.0.0", "vue": "^3.4.29", + "vue-email-editor": "^2.1.4", "vue-router": "^4.3.3" }, "devDependencies": { diff --git a/public/unlayerTool.js b/public/unlayerTool.js new file mode 100644 index 0000000..8533f1a --- /dev/null +++ b/public/unlayerTool.js @@ -0,0 +1,51 @@ +unlayer.registerTool({ + name: "my_tool", + label: "My Tool", + icon: "fa-smile", + // supportedDisplayModes: ["web", "email", "document"], + options: { + colors: { + // Property Group + title: "Colors", // Title for Property Group + position: 1, // Position of Property Group + options: { + textColor: { + // Property: textColor + label: "Text Color", // Label for Property + defaultValue: "#FF0000", + widget: "color_picker", // Property Editor Widget: color_picker + }, + backgroundColor: { + // Property: backgroundColor + label: "Background Color", // Label for Property + defaultValue: "#FF0000", + widget: "color_picker", // Property Editor Widget: color_picker + }, + }, + }, + }, + values: {}, + renderer: { + Viewer: unlayer.createViewer({ + render(values) { + return `
I am a custom tool.
`; + }, + }), + exporters: { + web: function (values) { + return `
I am a custom tool.
`; + }, + email: function (values) { + return `
I am a custom tool.
`; + }, + }, + head: { + css: function (values) {}, + js: function (values) {}, + }, + }, + validator(data) { + const { defaultErrors, values } = data; + return []; + }, +}); diff --git a/src/helpers/unlayerEditor.ts b/src/helpers/unlayerEditor.ts new file mode 100644 index 0000000..1b5afb7 --- /dev/null +++ b/src/helpers/unlayerEditor.ts @@ -0,0 +1,28 @@ +import type { EmailEditorProps } from "vue-email-editor/dist/components/types"; + +export const options: EmailEditorProps["options"] = { + tools: { + image: { + enabled: false, + }, + menu: { + enabled: false, + }, + button: { + enabled: false, + }, + }, + displayMode: "document", + appearance: { + theme: "light", + panels: { + tools: { + dock: "left", + }, + }, + }, + features: { + preview: false, + }, + customJS: [window.location.origin + "/unlayerTool.js"], +}; diff --git a/src/router/index.ts b/src/router/index.ts index 777aa7c..e0d4bd8 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -403,6 +403,28 @@ const router = createRouter({ meta: { type: "read", section: "settings", module: "query_store" }, beforeEnter: [abilityAndNavUpdate], }, + { + path: "template", + name: "admin-settings-template-route", + component: () => import("@/views/RouterView.vue"), + // meta: { type: "read", section: "settings", module: "template" }, + beforeEnter: [abilityAndNavUpdate], + children: [ + { + path: "", + name: "admin-settings-template", + component: () => import("@/views/admin/settings/template/Template.vue"), + }, + { + path: ":id/edit", + name: "admin-settings-template-edit", + component: () => import("@/views/admin/settings/template/Template.vue"), + // meta: { type: "update", section: "settings", module: "template" }, + // beforeEnter: [abilityAndNavUpdate], + props: true, + }, + ], + }, ], }, { diff --git a/src/stores/admin/navigation.ts b/src/stores/admin/navigation.ts index f14502b..4385a8f 100644 --- a/src/stores/admin/navigation.ts +++ b/src/stores/admin/navigation.ts @@ -113,6 +113,7 @@ export const useNavigationStore = defineStore("navigation", { ? [{ key: "calendar_type", title: "Terminarten" }] : []), ...(abilityStore.can("read", "settings", "query") ? [{ key: "query_store", title: "Query Store" }] : []), + ...(true ? [{ key: "template", title: "Templates" }] : []), ], }, user: { diff --git a/src/views/admin/settings/template/Template.vue b/src/views/admin/settings/template/Template.vue new file mode 100644 index 0000000..44ff19f --- /dev/null +++ b/src/views/admin/settings/template/Template.vue @@ -0,0 +1,87 @@ + + + + + -- 2.45.2 From 78a9d206c3e6c5e1a803ec6771762faf5c9dcef5 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Sun, 22 Dec 2024 10:29:31 +0100 Subject: [PATCH 02/19] template storing --- .../settings/template/CreateTemplateModal.vue | 79 +++++++++ .../settings/template/DeleteTemplateModal.vue | 73 ++++++++ .../settings/template/TemplateListItem.vue | 54 ++++++ src/helpers/unlayerEditor.ts | 72 ++++++++ src/router/index.ts | 14 +- src/stores/admin/template.ts | 55 ++++++ src/types/permissionTypes.ts | 5 +- src/viewmodels/admin/template.models.ts | 24 +++ .../admin/settings/template/Template.vue | 86 ++++------ .../admin/settings/template/TemplateEdit.vue | 158 ++++++++++++++++++ .../admin/settings/template/UsageInfo.vue | 24 +++ 11 files changed, 581 insertions(+), 63 deletions(-) create mode 100644 src/components/admin/settings/template/CreateTemplateModal.vue create mode 100644 src/components/admin/settings/template/DeleteTemplateModal.vue create mode 100644 src/components/admin/settings/template/TemplateListItem.vue create mode 100644 src/stores/admin/template.ts create mode 100644 src/viewmodels/admin/template.models.ts create mode 100644 src/views/admin/settings/template/TemplateEdit.vue create mode 100644 src/views/admin/settings/template/UsageInfo.vue diff --git a/src/components/admin/settings/template/CreateTemplateModal.vue b/src/components/admin/settings/template/CreateTemplateModal.vue new file mode 100644 index 0000000..4f10366 --- /dev/null +++ b/src/components/admin/settings/template/CreateTemplateModal.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/src/components/admin/settings/template/DeleteTemplateModal.vue b/src/components/admin/settings/template/DeleteTemplateModal.vue new file mode 100644 index 0000000..cd42728 --- /dev/null +++ b/src/components/admin/settings/template/DeleteTemplateModal.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/src/components/admin/settings/template/TemplateListItem.vue b/src/components/admin/settings/template/TemplateListItem.vue new file mode 100644 index 0000000..a9ed5d4 --- /dev/null +++ b/src/components/admin/settings/template/TemplateListItem.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/src/helpers/unlayerEditor.ts b/src/helpers/unlayerEditor.ts index 1b5afb7..3fd1871 100644 --- a/src/helpers/unlayerEditor.ts +++ b/src/helpers/unlayerEditor.ts @@ -1,3 +1,4 @@ +import type { EmailEditor } from "vue-email-editor"; import type { EmailEditorProps } from "vue-email-editor/dist/components/types"; export const options: EmailEditorProps["options"] = { @@ -26,3 +27,74 @@ export const options: EmailEditorProps["options"] = { }, customJS: [window.location.origin + "/unlayerTool.js"], }; + +export function configureEditor(editor: typeof EmailEditor): void { + editor.editor.setBodyValues({ + contentWidth: "100%", + backgroundColor: "#ffffff", + linkStyle: { + linkColor: "#990b00", + linkHoverColor: "#bb1e10", + linkUnderline: false, + linkHoverUnderline: false, + }, + }); +} + +export function loadEditor(editor: typeof EmailEditor, design: object | undefined = undefined): void { + if (design === undefined) { + editor.editor.loadBlank(); + } else { + editor.editor.loadDesign(design); + } +} + +export function exportEditor(editor: typeof EmailEditor): { + design: object; + headerHTML: string; + bodyHTML: string; + footerHTML: string; +} { + let savedDesign: any = undefined; + let savedHeader: string = ""; + let savedBody: string = ""; + let savedFooter: string = ""; + + editor.editor.saveDesign((design: any) => { + savedDesign = design; + }); + + editor.editor.exportHtml( + (data: any) => { + savedHeader = data; + }, + { + minify: true, + onlyHeader: true, + } + ); + editor.editor.exportHtml( + (data: any) => { + savedBody = data; + }, + { + minify: true, + } + ); + editor.editor.exportHtml( + (data: any) => { + savedFooter = data; + }, + { + minify: true, + onlyFooter: true, + } + ); + + return { + design: savedDesign, + headerHTML: savedHeader, + bodyHTML: savedBody, + footerHTML: savedFooter, + }; +} diff --git a/src/router/index.ts b/src/router/index.ts index e0d4bd8..b520eeb 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -407,7 +407,7 @@ const router = createRouter({ path: "template", name: "admin-settings-template-route", component: () => import("@/views/RouterView.vue"), - // meta: { type: "read", section: "settings", module: "template" }, + meta: { type: "read", section: "settings", module: "template" }, beforeEnter: [abilityAndNavUpdate], children: [ { @@ -415,12 +415,18 @@ const router = createRouter({ name: "admin-settings-template", component: () => import("@/views/admin/settings/template/Template.vue"), }, + { + path: "info", + name: "admin-settings-template-info", + component: () => import("@/views/admin/settings/template/UsageInfo.vue"), + props: true, + }, { path: ":id/edit", name: "admin-settings-template-edit", - component: () => import("@/views/admin/settings/template/Template.vue"), - // meta: { type: "update", section: "settings", module: "template" }, - // beforeEnter: [abilityAndNavUpdate], + component: () => import("@/views/admin/settings/template/TemplateEdit.vue"), + meta: { type: "update", section: "settings", module: "template" }, + beforeEnter: [abilityAndNavUpdate], props: true, }, ], diff --git a/src/stores/admin/template.ts b/src/stores/admin/template.ts new file mode 100644 index 0000000..1dd0edc --- /dev/null +++ b/src/stores/admin/template.ts @@ -0,0 +1,55 @@ +import { defineStore } from "pinia"; +import { http } from "@/serverCom"; +import type { AxiosResponse } from "axios"; +import type { CreateTemplateViewModel, UpdateTemplateViewModel } from "../../viewmodels/admin/template.models"; + +export const useTemplateStore = defineStore("template", { + state: () => { + return { + templates: [] as Array, + loading: "loading" as "loading" | "fetched" | "failed", + }; + }, + actions: { + fetchTemplates() { + this.loading = "loading"; + http + .get("/admin/template") + .then((result) => { + this.templates = result.data; + this.loading = "fetched"; + }) + .catch((err) => { + this.loading = "failed"; + }); + }, + fetchTemplateById(id: number): Promise> { + return http.get(`/admin/template/${id}`); + }, + async createTemplate(template: CreateTemplateViewModel): Promise> { + const result = await http.post(`/admin/template`, { + template: template.template, + description: template.description, + }); + this.fetchTemplates(); + return result; + }, + async updateActiveTemplate(template: UpdateTemplateViewModel): Promise> { + const result = await http.patch(`/admin/template/${template.id}`, { + template: template.template, + description: template.description, + design: template.design, + headerHTML: template.headerHTML, + bodyHTML: template.bodyHTML, + footerHTML: template.footerHTML, + }); + this.fetchTemplates(); + return result; + }, + async deleteTemplate(template: number): Promise> { + const result = await http.delete(`/admin/template/${template}`); + this.fetchTemplates(); + return result; + }, + }, +}); diff --git a/src/types/permissionTypes.ts b/src/types/permissionTypes.ts index d5bb35e..62fbc51 100644 --- a/src/types/permissionTypes.ts +++ b/src/types/permissionTypes.ts @@ -14,7 +14,8 @@ export type PermissionModule = | "user" | "role" | "query" - | "query_store"; + | "query_store" + | "template"; export type PermissionType = "read" | "create" | "update" | "delete"; @@ -53,6 +54,7 @@ export const permissionModules: Array = [ "role", "query", "query_store", + "template", ]; export const permissionTypes: Array = ["read", "create", "update", "delete"]; export const sectionsAndModules: SectionsAndModulesObject = { @@ -65,6 +67,7 @@ export const sectionsAndModules: SectionsAndModulesObject = { "membership_status", "calendar_type", "query_store", + "template", ], user: ["user", "role"], }; diff --git a/src/viewmodels/admin/template.models.ts b/src/viewmodels/admin/template.models.ts new file mode 100644 index 0000000..65833c3 --- /dev/null +++ b/src/viewmodels/admin/template.models.ts @@ -0,0 +1,24 @@ +export interface TemplateViewModel { + id: number; + template: string; + description: string | null; + design: object; + headerHTML: string; + bodyHTML: string; + footerHTML: string; +} + +export interface CreateTemplateViewModel { + template: string; + description: string | null; +} + +export interface UpdateTemplateViewModel { + id: number; + template: string; + description: string | null; + design: object; + headerHTML: string; + bodyHTML: string; + footerHTML: string; +} diff --git a/src/views/admin/settings/template/Template.vue b/src/views/admin/settings/template/Template.vue index 44ff19f..56cea4b 100644 --- a/src/views/admin/settings/template/Template.vue +++ b/src/views/admin/settings/template/Template.vue @@ -3,22 +3,20 @@ @@ -26,61 +24,33 @@ + + diff --git a/src/views/admin/settings/template/UsageInfo.vue b/src/views/admin/settings/template/UsageInfo.vue new file mode 100644 index 0000000..7dc02b4 --- /dev/null +++ b/src/views/admin/settings/template/UsageInfo.vue @@ -0,0 +1,24 @@ + + + -- 2.45.2 From 395a6439ebe66419b6a34054d2e3f089a42e1a80 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Sun, 22 Dec 2024 17:28:56 +0100 Subject: [PATCH 03/19] changed editor to OpenSource grapesJS --- index.html | 2 +- package-lock.json | 121 +++++++++++++++--- package.json | 3 +- public/unlayerTool.js | 51 -------- src/helpers/grapesEditor.ts | 49 +++++++ src/helpers/unlayerEditor.ts | 100 --------------- src/stores/admin/template.ts | 4 +- src/viewmodels/admin/template.models.ts | 8 +- .../admin/settings/template/TemplateEdit.vue | 79 +++++++----- 9 files changed, 208 insertions(+), 209 deletions(-) delete mode 100644 public/unlayerTool.js create mode 100644 src/helpers/grapesEditor.ts delete mode 100644 src/helpers/unlayerEditor.ts diff --git a/index.html b/index.html index 28cd04b..c8331c5 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + Mitgliederverwaltung diff --git a/package-lock.json b/package-lock.json index 33b0fc3..4e27f60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,8 @@ "@heroicons/vue": "^2.1.5", "@vueup/vue-quill": "^1.2.0", "axios": "^0.26.1", + "grapesjs": "^0.22.4", + "grapesjs-preset-newsletter": "^1.0.2", "jwt-decode": "^4.0.0", "lodash.clonedeep": "^4.5.0", "lodash.difference": "^4.5.0", @@ -31,7 +33,6 @@ "socket.io-client": "^4.5.0", "uuid": "^9.0.0", "vue": "^3.4.29", - "vue-email-editor": "^2.1.4", "vue-router": "^4.3.3" }, "devDependencies": { @@ -3074,6 +3075,16 @@ "integrity": "sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==", "dev": true }, + "node_modules/@types/backbone": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@types/backbone/-/backbone-1.4.15.tgz", + "integrity": "sha512-WWeKtYlsIMtDyLbbhkb96taJMEbfQBnuz7yw1u0pkphCOtksemoWhIXhK74VRCY9hbjnsH3rsJu2uUiFtnsEYg==", + "license": "MIT", + "dependencies": { + "@types/jquery": "*", + "@types/underscore": "*" + } + }, "node_modules/@types/eslint": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.0.tgz", @@ -3090,6 +3101,15 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/jquery": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.32.tgz", + "integrity": "sha512-b9Xbf4CkMqS02YH8zACqN1xzdxc3cO735Qe5AbSUFmyOiaWAbcpqh9Wna+Uk0vgACvoQHpWDg2rGdHkYPLmCiQ==", + "license": "MIT", + "dependencies": { + "@types/sizzle": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3179,12 +3199,24 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "dev": true }, + "node_modules/@types/sizzle": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz", + "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==", + "license": "MIT" + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "dev": true }, + "node_modules/@types/underscore": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.13.0.tgz", + "integrity": "sha512-L6LBgy1f0EFQZ+7uSA57+n2g/s4Qs5r06Vwrwn0/nuK1de+adz00NWaztRQ30aEqw5qOaWbPI8u2cGQ52lj6VA==", + "license": "MIT" + }, "node_modules/@types/uuid": { "version": "9.0.8", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", @@ -4020,6 +4052,26 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/backbone": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.4.1.tgz", + "integrity": "sha512-ADy1ztN074YkWbHi8ojJVFe3vAanO/lrzMGZWUClIP7oDD/Pjy2vrASraUP+2EVCfIiTtCW4FChVow01XneivA==", + "license": "MIT", + "dependencies": { + "underscore": ">=1.8.3" + } + }, + "node_modules/backbone-undo": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/backbone-undo/-/backbone-undo-0.2.6.tgz", + "integrity": "sha512-AsfpNiljLXlk7TcffDUu3EAUq7CxWbyTNwARWrql5XTzN4vh6WzEEBZYaKK4kTTz+iW1tSzqUooaGRIwO83kWA==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", + "dependencies": { + "backbone": ">=1.0.0", + "underscore": ">=1.4.4" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -4384,6 +4436,18 @@ "node": ">=0.8" } }, + "node_modules/codemirror": { + "version": "5.63.0", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.63.0.tgz", + "integrity": "sha512-KlLWRPggDg2rBD1Mx7/EqEhaBdy+ybBCVh/efgjBDsPpMeEu6MbTAJzIT4TuCzvmbTEgvKOGzVT6wdBTNusqrg==", + "license": "MIT" + }, + "node_modules/codemirror-formatting": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/codemirror-formatting/-/codemirror-formatting-1.0.0.tgz", + "integrity": "sha512-br9yM6eJI3pJHekEnoyHaBEb1B7XxxDjju+vRyBe8QGLp5saTIXXkZ+eFCTqXSAtI8QEZDFVEX2/SOjH2sVWRQ==", + "license": "MIT" + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -5890,6 +5954,28 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/grapesjs": { + "version": "0.22.4", + "resolved": "https://registry.npmjs.org/grapesjs/-/grapesjs-0.22.4.tgz", + "integrity": "sha512-4ea7T5FguyPC2fLytpSBgPXcSGreRKKisknXUbsgHBCzv4G11Z0oBJNM5jRucupBr2CRxt/3U2zixeEHEisfbw==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/backbone": "1.4.15", + "backbone": "1.4.1", + "backbone-undo": "0.2.6", + "codemirror": "5.63.0", + "codemirror-formatting": "1.0.0", + "html-entities": "~1.4.0", + "promise-polyfill": "8.3.0", + "underscore": "1.13.1" + } + }, + "node_modules/grapesjs-preset-newsletter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/grapesjs-preset-newsletter/-/grapesjs-preset-newsletter-1.0.2.tgz", + "integrity": "sha512-z8KJ1ZrTXfASSJZ/tHOcnpcWu4AMr2F/ZfQit+QjimNi3UGowwl7+Yjefuh3R7lbDTrXMMaxhCannCaJo/kPJw==", + "license": "BSD-3-Clause" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -5988,6 +6074,12 @@ "dev": true, "license": "MIT" }, + "node_modules/html-entities": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz", + "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==", + "license": "MIT" + }, "node_modules/html-tags": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", @@ -7826,6 +7918,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/promise-polyfill": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", + "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==", + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -9331,6 +9429,12 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/underscore": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", + "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -9411,12 +9515,6 @@ "node": ">= 10.0.0" } }, - "node_modules/unlayer-types": { - "version": "1.188.0", - "resolved": "https://registry.npmjs.org/unlayer-types/-/unlayer-types-1.188.0.tgz", - "integrity": "sha512-tnn+FjUZv1qUOoRUYRFxSDz9kHfhy7dLxzMZgnU5+k6GDSBlpa8mA+r4+r0D83M+mUUd/XwuM+gvfRLGzrqZ+g==", - "license": "MIT" - }, "node_modules/upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", @@ -9707,15 +9805,6 @@ } } }, - "node_modules/vue-email-editor": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/vue-email-editor/-/vue-email-editor-2.1.4.tgz", - "integrity": "sha512-9H6P2zgjOx4XJmKyMb4ZzCpsnKAqFk74daD86l/MhUvucF/qizTMUhOFnIMU6u9jtiB64NFvPTTzkRTTYTGkFw==", - "dependencies": { - "unlayer-types": "latest", - "vue": "^3.2.13" - } - }, "node_modules/vue-eslint-parser": { "version": "9.4.3", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", diff --git a/package.json b/package.json index 0373442..dde1382 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,8 @@ "@heroicons/vue": "^2.1.5", "@vueup/vue-quill": "^1.2.0", "axios": "^0.26.1", + "grapesjs": "^0.22.4", + "grapesjs-preset-newsletter": "^1.0.2", "jwt-decode": "^4.0.0", "lodash.clonedeep": "^4.5.0", "lodash.difference": "^4.5.0", @@ -46,7 +48,6 @@ "socket.io-client": "^4.5.0", "uuid": "^9.0.0", "vue": "^3.4.29", - "vue-email-editor": "^2.1.4", "vue-router": "^4.3.3" }, "devDependencies": { diff --git a/public/unlayerTool.js b/public/unlayerTool.js deleted file mode 100644 index 8533f1a..0000000 --- a/public/unlayerTool.js +++ /dev/null @@ -1,51 +0,0 @@ -unlayer.registerTool({ - name: "my_tool", - label: "My Tool", - icon: "fa-smile", - // supportedDisplayModes: ["web", "email", "document"], - options: { - colors: { - // Property Group - title: "Colors", // Title for Property Group - position: 1, // Position of Property Group - options: { - textColor: { - // Property: textColor - label: "Text Color", // Label for Property - defaultValue: "#FF0000", - widget: "color_picker", // Property Editor Widget: color_picker - }, - backgroundColor: { - // Property: backgroundColor - label: "Background Color", // Label for Property - defaultValue: "#FF0000", - widget: "color_picker", // Property Editor Widget: color_picker - }, - }, - }, - }, - values: {}, - renderer: { - Viewer: unlayer.createViewer({ - render(values) { - return `
I am a custom tool.
`; - }, - }), - exporters: { - web: function (values) { - return `
I am a custom tool.
`; - }, - email: function (values) { - return `
I am a custom tool.
`; - }, - }, - head: { - css: function (values) {}, - js: function (values) {}, - }, - }, - validator(data) { - const { defaultErrors, values } = data; - return []; - }, -}); diff --git a/src/helpers/grapesEditor.ts b/src/helpers/grapesEditor.ts new file mode 100644 index 0000000..f843d4f --- /dev/null +++ b/src/helpers/grapesEditor.ts @@ -0,0 +1,49 @@ +import type { Editor } from "grapesjs"; + +export function configureEditor(editor: Editor): void { + editor.Panels.getPanel("devices-c")?.set("visible", false); + editor.Panels.removeButton("devices-c", "set-device-mobile"); + editor.Panels.removeButton("devices-c", "set-device-desktop"); + editor.Panels.removeButton("views", "open-tm"); + editor.Panels.removeButton("options", "export-template"); + editor.Panels.removeButton("options", "preview"); + // editor.Panels.removeButton("options", "fullscreen"); + editor.Panels.removeButton("options", "gjs-open-import-template"); + editor.Panels.removeButton("options", "gjs-toggle-images"); + editor.BlockManager.remove("button"); + editor.BlockManager.remove("image"); + editor.BlockManager.remove("link-block"); + editor.BlockManager.remove("list-items"); + editor.BlockManager.remove("grid-items"); + editor.BlockManager.remove("sect37"); + editor.BlockManager.remove("text-sect"); + + editor.DomComponents.addType("heading", { + model: { + defaults: { + tagName: "h1", + content: "Heading", + }, + }, + isComponent(el) { + return el.tagName === "H1"; + }, + }); + editor.BlockManager.add("heading-block", { + label: "Heading", + content: { type: "heading" }, + category: "Text", + media: ` + + + + `, + }); + editor.BlockManager.get("text").set("category", "Text"); + editor.BlockManager.get("quote").set("category", "Text"); + editor.BlockManager.get("link").set("category", "Text"); + editor.BlockManager.get("sect100").set("category", "Struktur"); + editor.BlockManager.get("sect50").set("category", "Struktur"); + editor.BlockManager.get("sect30").set("category", "Struktur"); + editor.BlockManager.get("divider").set("category", "Struktur"); +} diff --git a/src/helpers/unlayerEditor.ts b/src/helpers/unlayerEditor.ts deleted file mode 100644 index 3fd1871..0000000 --- a/src/helpers/unlayerEditor.ts +++ /dev/null @@ -1,100 +0,0 @@ -import type { EmailEditor } from "vue-email-editor"; -import type { EmailEditorProps } from "vue-email-editor/dist/components/types"; - -export const options: EmailEditorProps["options"] = { - tools: { - image: { - enabled: false, - }, - menu: { - enabled: false, - }, - button: { - enabled: false, - }, - }, - displayMode: "document", - appearance: { - theme: "light", - panels: { - tools: { - dock: "left", - }, - }, - }, - features: { - preview: false, - }, - customJS: [window.location.origin + "/unlayerTool.js"], -}; - -export function configureEditor(editor: typeof EmailEditor): void { - editor.editor.setBodyValues({ - contentWidth: "100%", - backgroundColor: "#ffffff", - linkStyle: { - linkColor: "#990b00", - linkHoverColor: "#bb1e10", - linkUnderline: false, - linkHoverUnderline: false, - }, - }); -} - -export function loadEditor(editor: typeof EmailEditor, design: object | undefined = undefined): void { - if (design === undefined) { - editor.editor.loadBlank(); - } else { - editor.editor.loadDesign(design); - } -} - -export function exportEditor(editor: typeof EmailEditor): { - design: object; - headerHTML: string; - bodyHTML: string; - footerHTML: string; -} { - let savedDesign: any = undefined; - let savedHeader: string = ""; - let savedBody: string = ""; - let savedFooter: string = ""; - - editor.editor.saveDesign((design: any) => { - savedDesign = design; - }); - - editor.editor.exportHtml( - (data: any) => { - savedHeader = data; - }, - { - minify: true, - onlyHeader: true, - } - ); - editor.editor.exportHtml( - (data: any) => { - savedBody = data; - }, - { - minify: true, - } - ); - editor.editor.exportHtml( - (data: any) => { - savedFooter = data; - }, - { - minify: true, - onlyFooter: true, - } - ); - - return { - design: savedDesign, - headerHTML: savedHeader, - bodyHTML: savedBody, - footerHTML: savedFooter, - }; -} diff --git a/src/stores/admin/template.ts b/src/stores/admin/template.ts index 1dd0edc..58ef452 100644 --- a/src/stores/admin/template.ts +++ b/src/stores/admin/template.ts @@ -39,9 +39,7 @@ export const useTemplateStore = defineStore("template", { template: template.template, description: template.description, design: template.design, - headerHTML: template.headerHTML, - bodyHTML: template.bodyHTML, - footerHTML: template.footerHTML, + html: template.html, }); this.fetchTemplates(); return result; diff --git a/src/viewmodels/admin/template.models.ts b/src/viewmodels/admin/template.models.ts index 65833c3..c470fd1 100644 --- a/src/viewmodels/admin/template.models.ts +++ b/src/viewmodels/admin/template.models.ts @@ -3,9 +3,7 @@ export interface TemplateViewModel { template: string; description: string | null; design: object; - headerHTML: string; - bodyHTML: string; - footerHTML: string; + html: string; } export interface CreateTemplateViewModel { @@ -18,7 +16,5 @@ export interface UpdateTemplateViewModel { template: string; description: string | null; design: object; - headerHTML: string; - bodyHTML: string; - footerHTML: string; + html: string; } diff --git a/src/views/admin/settings/template/TemplateEdit.vue b/src/views/admin/settings/template/TemplateEdit.vue index 50c0602..108648f 100644 --- a/src/views/admin/settings/template/TemplateEdit.vue +++ b/src/views/admin/settings/template/TemplateEdit.vue @@ -12,11 +12,11 @@

laden fehlgeschlagen

-
+
@@ -26,21 +26,11 @@
-
- -
-
-

- Der externe Editor ist nicht auf kleine Auflösungen optimiert. Wechseln Sie auf ein Desktop-Gerät, einen - größeren Bildschirm oder ändern Sie die Skalierung dieser Seite. -

+
+

Lade Template-Anzeige

+
+
- + diff --git a/src/stores/admin/navigation.ts b/src/stores/admin/navigation.ts index 7cd4604..e5add0b 100644 --- a/src/stores/admin/navigation.ts +++ b/src/stores/admin/navigation.ts @@ -46,7 +46,7 @@ export const useNavigationStore = defineStore("navigation", { resetNavigation() { this.$reset(); }, - updateTopLevel(first: boolean = false) { + updateTopLevel() { const abilityStore = useAbilityStore(); this.topLevel = [ ...(abilityStore.canSection("read", "club") @@ -82,7 +82,7 @@ export const useNavigationStore = defineStore("navigation", { router.push({ name: `admin-${this.topLevel[0]?.key ?? "club"}-default` }); } }, - updateNavigation(first: boolean = false) { + updateNavigation() { const abilityStore = useAbilityStore(); this.navigation = { club: { @@ -129,8 +129,12 @@ export const useNavigationStore = defineStore("navigation", { ], }, } as navigationModel; - if (this.activeNavigationObject.main.findIndex((e) => e.key == this.activeLink) == -1) { - router.push({ name: `admin-${this.activeNavigation}-default` }); + if ( + this.activeNavigationObject.main.findIndex((e) => e.key == this.activeLink) == -1 || + this.activeLink == "default" + ) { + let link = this.activeNavigationObject.main[0].key; + router.push({ name: `admin-${this.activeNavigation}-${link}` }); } }, }, diff --git a/src/views/admin/View.vue b/src/views/admin/View.vue index 8d1781b..6a22f18 100644 --- a/src/views/admin/View.vue +++ b/src/views/admin/View.vue @@ -58,8 +58,8 @@ export default defineComponent({ this.updateTopLevel(); this.updateNavigation(); }); - this.updateTopLevel(true); - this.updateNavigation(true); + this.updateTopLevel(); + this.updateNavigation(); }, beforeUnmount() { this.resetNavigation(); -- 2.45.2 From faa691b8349cdb8d5bbb1c891ac002577f667b78 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Tue, 24 Dec 2024 10:24:35 +0100 Subject: [PATCH 07/19] Template Duplication --- .../settings/template/TemplateListItem.vue | 42 +++++++++++++++++-- src/stores/admin/template.ts | 7 ++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/components/admin/settings/template/TemplateListItem.vue b/src/components/admin/settings/template/TemplateListItem.vue index a9ed5d4..e28d0ee 100644 --- a/src/components/admin/settings/template/TemplateListItem.vue +++ b/src/components/admin/settings/template/TemplateListItem.vue @@ -2,13 +2,19 @@

{{ template.template }}

-
+
- + + + + +
@@ -26,11 +32,14 @@ diff --git a/src/stores/admin/template.ts b/src/stores/admin/template.ts index 13c85bb..e9ef5bb 100644 --- a/src/stores/admin/template.ts +++ b/src/stores/admin/template.ts @@ -48,6 +48,13 @@ export const useTemplateStore = defineStore("template", { this.fetchTemplates(); return result; }, + async cloneTemplate(cloneId: number): Promise> { + const result = await http.post(`/admin/template/clone`, { + cloneId: cloneId, + }); + this.fetchTemplates(); + return result; + }, async deleteTemplate(template: number): Promise> { const result = await http.delete(`/admin/template/${template}`); this.fetchTemplates(); -- 2.45.2 From d6743f0d16ff3956e997a158cf1b4ae8f87c59e8 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Tue, 24 Dec 2024 13:53:00 +0100 Subject: [PATCH 08/19] preview template usage --- .../templateUsage/TemplatePreviewModal.vue | 50 +++++++++++++++++++ .../templateUsage/TemplateUsageListItem.vue | 18 +++++-- src/stores/admin/templateUsage.ts | 6 +++ 3 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 src/components/admin/settings/templateUsage/TemplatePreviewModal.vue diff --git a/src/components/admin/settings/templateUsage/TemplatePreviewModal.vue b/src/components/admin/settings/templateUsage/TemplatePreviewModal.vue new file mode 100644 index 0000000..fd27db6 --- /dev/null +++ b/src/components/admin/settings/templateUsage/TemplatePreviewModal.vue @@ -0,0 +1,50 @@ +