From 92ca030e6ce72113dc1484ebd66a2637d09a5543 Mon Sep 17 00:00:00 2001 From: robin Date: Sun, 12 Nov 2023 10:02:26 +0000 Subject: [PATCH] first version --- api/collections/content.yml | 204 +++++++++++- api/collections/fieldLists/box.yml | 25 +- api/collections/fieldLists/cards.yml | 9 +- api/collections/fieldLists/column.yml | 290 +++++++----------- .../fieldLists/iconCycleCircle.yml | 1 + .../fieldLists/iconCycleSquare.yml | 1 + api/collections/fieldLists/row.yml | 118 +++++++ api/collections/fields/row.yml | 90 ------ api/collections/medialib.yml | 129 ++++++++ api/collections/module.yml | 143 +++++++++ api/collections/navigation.yml | 4 +- api/config.yml | 2 + frontend/src/App.svelte | 47 ++- .../lib/components/Pagebuilder/Module.svelte | 34 ++ .../components/Pagebuilder/Pagebuilder.svelte | 61 ++-- .../lib/components/Pagebuilder/Rows.svelte | 39 ++- .../components/widgets/Worldcard/card.svelte | 3 +- .../widgets/Worldcard/worldcard.svelte | 4 +- .../src/lib/components/widgets/boxlist.svelte | 12 +- .../src/lib/components/widgets/events.svelte | 7 +- .../components/widgets/extendableBox.svelte | 6 +- .../lib/components/widgets/iconBlock.svelte | 4 +- .../components/widgets/iconCycleBox.svelte | 9 +- .../components/widgets/iconCycleCircle.svelte | 14 +- .../src/lib/components/widgets/image.svelte | 10 +- .../lib/components/widgets/infoBoard.svelte | 6 +- .../components/widgets/pageLinkBlocks.svelte | 20 +- .../src/lib/components/widgets/persons.svelte | 22 +- .../components/widgets/publications.svelte | 5 +- frontend/src/lib/functions/loadLibrary.ts | 6 + frontend/src/lib/functions/loadModules.ts | 6 + frontend/src/lib/store.ts | 4 + types/global.d.ts | 114 +++---- 33 files changed, 1005 insertions(+), 444 deletions(-) create mode 100644 api/collections/fieldLists/row.yml delete mode 100644 api/collections/fields/row.yml create mode 100644 api/collections/medialib.yml create mode 100644 api/collections/module.yml create mode 100644 frontend/src/lib/components/Pagebuilder/Module.svelte create mode 100644 frontend/src/lib/functions/loadLibrary.ts create mode 100644 frontend/src/lib/functions/loadModules.ts diff --git a/api/collections/content.yml b/api/collections/content.yml index 0759ff7..91a6dc4 100644 --- a/api/collections/content.yml +++ b/api/collections/content.yml @@ -18,17 +18,81 @@ meta: label: Allgemein subFields: - source: path + - source: type + - source: pageTitle + - source: topTitleUpperCase + - source: active - name: teaser - label: Teaser + label: Homepage Seitenteaser subFields: - source: teaser + - name: personPreview + label: Personenvorschau + subFields: + - source: personType + - source: personPreview + + - name: jobOffer + label: Job Angebote + subFields: + - source: jobOffer + - name: site label: Seite subFields: - source: rows + subNavigation: + - name: seite + label: + de: Seiten + en: pages + muiIcon: book-open-page-variant + defaultSort: + field: "pfad" + order: "ASC" + views: + - type: table + columns: + - source: path + + filter: + type: page + + - name: teamMembers + label: + de: Teammitglieder + en: Team members + muiIcon: book-open-page-variant + defaultSort: + field: "pfad" + order: "ASC" + views: + - type: table + columns: + - source: path + + filter: + type: teamMember + + - name: jobOffers + label: + de: Stellenanzeigen + en: Job offers + muiIcon: book-open-page-variant + defaultSort: + field: "pfad" + order: "ASC" + views: + - type: table + columns: + - source: path + + filter: + type: jobOffer + imageFilter: xs: - fit: true @@ -87,14 +151,146 @@ fields: label: Pfad helperText: "Ein Pfad sollte mit einem / starten und ohne eins enden." + - type: boolean + name: active + meta: + label: Aktiv + + - type: string + name: type + meta: + label: Typ + widget: select + choices: + - name: Seite + id: page + + - name: Teammitglieder + id: teamMembers + + - name: Stellenanzeigen + id: jobOffers + + - name: pageTitle + type: string + meta: + label: Titel der Seite + helperText: "Dieser Titel wird in der Seite als h1 angezeigt." + containerProps: + layout: + size: + default: "col-6" + small: "col-6" + large: "col-6" + + - name: personType + type: string + meta: + label: Typ + widget: select + choices: + - name: Chef + id: chef + - name: Mitarbeiter + id: employee + + - name: personPreview + type: object + meta: + label: Personenvorschau + subFields: + - name: initialImage + type: string + meta: + label: Bild + containerProps: + layout: + size: + default: "col-6" + small: "col-12" + large: "col-6" + dependsOn: + eval: $.personType == 'chef' + widget: foreignKey # Verwendetes Widget. + foreign: + collection: medialib # Name der Sammlung, in der die ausgewählten Daten gespeichert sind. + id: id # Feldname, das als eindeutige Kennung für die ausgewählten Daten verwendet wird. + subNavigation: 0 # Bestimmt, welche Navigation für die Auswahl der ausgewählten Daten angezeigt wird. + #projection: xyz + #sort: "title" + render: + defaultCollectionViews: true + + - name: hoverImage + type: string + meta: + label: Bild beim Hover + containerProps: + layout: + size: + default: "col-6" + small: "col-12" + large: "col-6" + dependsOn: + eval: $.personType == 'chef' + widget: foreignKey # Verwendetes Widget. + foreign: + collection: medialib # Name der Sammlung, in der die ausgewählten Daten gespeichert sind. + id: id # Feldname, das als eindeutige Kennung für die ausgewählten Daten verwendet wird. + subNavigation: 0 # Bestimmt, welche Navigation für die Auswahl der ausgewählten Daten angezeigt wird. + #projection: xyz + #sort: "title" + render: + defaultCollectionViews: true + + - name: name + type: string + meta: + label: Name + - !include fields/teaserHomepage.yml + - name: jobOffer + type: object + meta: + label: Job Angebote + subFields: + - name: title + type: string + meta: + label: Titel + + - name: text + type: string + meta: + widget: richtext + label: Text + + - name: emailButton + type: boolean + meta: + label: E-Mail Button Anzeigen + + - name: emailSubject + type: string + meta: + label: E-Mail default Betreff + dependsOn: + eval: $parent.emailButton == true + - name: rows type: object[] meta: label: Zeilen - widget: containerLessObjectArray + widget: grid + metaElements: + - source: backgroundImage + - source: noBottomMargin + - source: noTopMargin + - source: flexWrapNormal + - source: twoToThree + - source: nextPage folding: force: true - subFields: - - !include fields/row.yml + + subFields: !include fieldLists/row.yml diff --git a/api/collections/fieldLists/box.yml b/api/collections/fieldLists/box.yml index c90af28..5c1c242 100644 --- a/api/collections/fieldLists/box.yml +++ b/api/collections/fieldLists/box.yml @@ -1,11 +1,32 @@ - name: icon - type: file + type: string meta: label: Icon helperText: "Das Icon wird in der Box angezeigt." - + widget: foreignKey # Verwendetes Widget. + foreign: + collection: medialib # Name der Sammlung, in der die ausgewählten Daten gespeichert sind. + id: id # Feldname, das als eindeutige Kennung für die ausgewählten Daten verwendet wird. + subNavigation: 0 # Bestimmt, welche Navigation für die Auswahl der ausgewählten Daten angezeigt wird. + #projection: xyz + #sort: "title" + render: + defaultCollectionViews: true + containerProps: + layout: + size: + default: "col-6" + small: "col-12" + large: "col-6" + - name: text type: string meta: label: Text helperText: "Der Text wird in der Box angezeigt." + containerProps: + layout: + size: + default: "col-6" + small: "col-12" + large: "col-6" diff --git a/api/collections/fieldLists/cards.yml b/api/collections/fieldLists/cards.yml index 0502281..07e108b 100644 --- a/api/collections/fieldLists/cards.yml +++ b/api/collections/fieldLists/cards.yml @@ -1,8 +1,15 @@ - name: image - type: file + type: string meta: label: Kartenausschnitt helperText: "Der Kartenausschnitt wird als Hintergrundbild angezeigt." + widget: foreignKey # Verwendetes Widget. + foreign: + collection: medialib # Name der Sammlung, in der die ausgewählten Daten gespeichert sind. + id: id # Feldname, das als eindeutige Kennung für die ausgewählten Daten verwendet wird. + subNavigation: 0 # Bestimmt, welche Navigation für die Auswahl der ausgewählten Daten angezeigt wird. + render: + defaultCollectionViews: true - name: title type: string diff --git a/api/collections/fieldLists/column.yml b/api/collections/fieldLists/column.yml index e5c9fd1..3355f5a 100644 --- a/api/collections/fieldLists/column.yml +++ b/api/collections/fieldLists/column.yml @@ -7,11 +7,8 @@ - name: Bild id: image - - name: Icons im Rechteck - id: iconCycleSquare - - - name: Icons im Kreis - id: iconCycleCircle + - name: Modul Import + id: moduleImport - name: Text id: text @@ -19,21 +16,12 @@ - name: Informationsbrett id: infoBoard - - name: Weltkarte - id: worldCard - - name: Verschatelte Karte id: nestedCard - name: Top-Down id: topDown - - name: Personenvorschau - id: personPreview - - - name: Boxliste - id: boxlist - - name: Ausfahrbare Box id: extendableBoxes @@ -43,9 +31,6 @@ - name: Icon block id: iconBlocks - - name: Seitenlinks - id: pageLinkBlocks - - name: Netzwerk Veranstaltungen id: networkEvents @@ -58,16 +43,30 @@ label: Netzwerkveranstaltungen dependsOn: eval: $parent.contentType == 'networkEvents' + widget: containerLessObjectArray + subFields: - name: beginDate type: date meta: label: Beginn + containerProps: + layout: + size: + default: "col-6" + small: "col-6" + large: "col-6" - name: endDate type: date meta: label: Ende + containerProps: + layout: + size: + default: "col-6" + small: "col-6" + large: "col-6" - name: title type: string @@ -75,9 +74,18 @@ label: Titel - name: file - type: file + type: string meta: label: downloadDatei + widget: foreignKey # Verwendetes Widget. + foreign: + collection: medialib # Name der Sammlung, in der die ausgewählten Daten gespeichert sind. + id: id # Feldname, das als eindeutige Kennung für die ausgewählten Daten verwendet wird. + subNavigation: 0 # Bestimmt, welche Navigation für die Auswahl der ausgewählten Daten angezeigt wird. + #projection: xyz + #sort: "title" + render: + defaultCollectionViews: true - name: publications type: object[] @@ -85,6 +93,8 @@ label: Publikationen dependsOn: eval: $parent.contentType == 'publications' + widget: containerLessObjectArray + direction: row subFields: - name: content type: string @@ -93,9 +103,18 @@ widget: richtext - name: file - type: file + type: string meta: label: downloadDatei + widget: foreignKey # Verwendetes Widget. + foreign: + collection: medialib # Name der Sammlung, in der die ausgewählten Daten gespeichert sind. + id: id # Feldname, das als eindeutige Kennung für die ausgewählten Daten verwendet wird. + subNavigation: 0 # Bestimmt, welche Navigation für die Auswahl der ausgewählten Daten angezeigt wird. + #projection: xyz + #sort: "title" + render: + defaultCollectionViews: true - name: iconBlocks type: object[] @@ -103,11 +122,22 @@ label: Icon block dependsOn: eval: $parent.contentType == 'iconBlocks' + widget: containerLessObjectArray + direction: row subFields: - name: icon - type: file + type: string meta: label: Icon + widget: foreignKey # Verwendetes Widget. + foreign: + collection: medialib # Name der Sammlung, in der die ausgewählten Daten gespeichert sind. + id: id # Feldname, das als eindeutige Kennung für die ausgewählten Daten verwendet wird. + subNavigation: 0 # Bestimmt, welche Navigation für die Auswahl der ausgewählten Daten angezeigt wird. + #projection: xyz + #sort: "title" + render: + defaultCollectionViews: true - name: bigText type: string meta: @@ -117,82 +147,64 @@ meta: label: unterer Text -- name: pageLinkBlocks - type: object[] - meta: - label: Seitenlinks - dependsOn: - eval: $parent.contentType == 'pageLinkBlocks' - subFields: - - name: page - type: string - meta: - label: Seite - widget: select - choices: - endpoint: page - params: - sort: path - projection: navigation - mapping: - id: id - name: path - - - name: name - type: string - meta: - label: Name - - - name: rowNr - type: number - meta: - label: Zeilen Nr (0 Basiert) - - - name: extendableRowNr - type: number - meta: - label: Ausfahrbare boxreihe (0 Basiert) - - name: image - type: file + type: string meta: label: Bild dependsOn: eval: $parent.contentType == 'image' + widget: foreignKey # Verwendetes Widget. + foreign: + collection: medialib # Name der Sammlung, in der die ausgewählten Daten gespeichert sind. + id: id # Feldname, das als eindeutige Kennung für die ausgewählten Daten verwendet wird. + subNavigation: 0 # Bestimmt, welche Navigation für die Auswahl der ausgewählten Daten angezeigt wird. + #projection: xyz + #sort: "title" + render: + defaultCollectionViews: true - name: icons type: object[] meta: label: Icons helperText: "Für Personpreview xing und linkedin icons gedacht." + widget: containerLessObjectArray + direction: row dependsOn: eval: $parent.contentType == 'image' subFields: - name: icon - type: file + type: string meta: label: Icon + widget: foreignKey # Verwendetes Widget. + foreign: + collection: medialib # Name der Sammlung, in der die ausgewählten Daten gespeichert sind. + id: id # Feldname, das als eindeutige Kennung für die ausgewählten Daten verwendet wird. + subNavigation: 0 # Bestimmt, welche Navigation für die Auswahl der ausgewählten Daten angezeigt wird. + #projection: xyz + #sort: "title" + render: + defaultCollectionViews: true - name: link type: string meta: label: Link -- name: iconCycleSquare - type: object +- name: moduleImport + type: string meta: - label: Icons im Rechteck + label: Modul Import dependsOn: - eval: $parent.contentType == 'iconCycleSquare' - subFields: !include iconCycleSquare.yml - -- name: iconCycleCircle - type: object - meta: - label: Icons im Kreis - dependsOn: - eval: $parent.contentType == 'iconCycleCircle' - subFields: !include iconCycleCircle.yml + eval: $parent.contentType == 'moduleImport' + widget: foreignKey + foreign: + collection: module + id: id + subNavigation: 0 + render: + defaultCollectionViews: true - name: text type: string @@ -206,6 +218,7 @@ type: object meta: label: Informationsbrett + widget: containerLessObject dependsOn: eval: $parent.contentType == 'infoBoard' subFields: @@ -223,36 +236,26 @@ helperText: "Dieser Text wird im Infobrett angezeigt." - name: icon - type: file + type: string meta: label: Icon helperText: "Das Icon wird im Infobrett angezeigt." - -- name: worldCard - type: object - meta: - label: Weltkarte - dependsOn: - eval: $parent.contentType == 'worldCard' - subFields: - - name: row - type: object[] - meta: - label: Zeilen - subFields: - - name: cards - type: object[] - meta: - label: Karten - metaElements: - - verticalAlignment - - horizontalAlignment - subFields: !include cards.yml + widget: foreignKey # Verwendetes Widget. + foreign: + collection: medialib # Name der Sammlung, in der die ausgewählten Daten gespeichert sind. + id: id # Feldname, das als eindeutige Kennung für die ausgewählten Daten verwendet wird. + subNavigation: 0 # Bestimmt, welche Navigation für die Auswahl der ausgewählten Daten angezeigt wird. + #projection: xyz + #sort: "title" + render: + defaultCollectionViews: true - name: nestedCard type: object[] meta: label: Verschatelte Karte + widget: containerLessObjectArray + direction: row dependsOn: eval: $parent.contentType == 'nestedCard' subFields: @@ -272,6 +275,7 @@ type: object meta: label: Top-Down + widget: containerLessObject dependsOn: eval: $parent.contentType == 'topDown' subFields: @@ -279,110 +283,40 @@ type: object[] meta: label: Zeilen + widget: containerLessObjectArray subFields: - name: inital type: string meta: label: Großbuchstabe + containerProps: + layout: + size: + default: "col-6" + small: "col-12" + large: "col-6" - name: rest type: string meta: label: Rest + containerProps: + layout: + size: + default: "col-6" + small: "col-12" + large: "col-6" - name: description type: string meta: label: Beschreibung -- name: personPreview - type: object[] - meta: - label: Personenvorschau - dependsOn: - eval: $parent.contentType == 'personPreview' - metaElements: - - initialImage - - hoverImage - subFields: - - name: initialImage - type: file - meta: - label: Bild - - name: hoverImage - type: file - meta: - label: Bild beim Hover - - name: name - type: string - meta: - label: Name - - - name: link - type: string - meta: - label: - de: Seite - en: page - widget: select - choices: - endpoint: page - params: - sort: path - projection: navigation - mapping: - id: id - name: path - -- name: boxList - type: object - meta: - label: Boxenliste - dependsOn: - eval: $parent.contentType == 'boxlist' - subFields: - - name: boxes - type: object[] - meta: - label: Boxen - subFields: - - name: name - type: string - meta: - label: Name - -- name: extendableBoxes - type: object[] - meta: - label: Ausklappbare Box - dependsOn: - eval: $parent.contentType == 'extendableBoxes' - subFields: - - name: title - type: string - meta: - label: Titel - - - name: text - type: string - meta: - widget: richtext - label: Text - - name: emailButton - type: boolean - meta: - label: E-Mail Button Anzeigen - - - name: emailSubject - type: string - meta: - label: E-Mail default Betreff - dependsOn: - eval: $parent.emailButton == true - name: textLink type: object meta: label: Text Link + widget: containerLessObject dependsOn: eval: $parent.contentType == 'textLink' subFields: @@ -405,5 +339,5 @@ sort: path projection: navigation mapping: - id: id + id: id name: path diff --git a/api/collections/fieldLists/iconCycleCircle.yml b/api/collections/fieldLists/iconCycleCircle.yml index c1eeca1..ea08196 100644 --- a/api/collections/fieldLists/iconCycleCircle.yml +++ b/api/collections/fieldLists/iconCycleCircle.yml @@ -2,6 +2,7 @@ type: object[] meta: label: Boxen + widget: containerLessObjectArray subFields: !include box.yml - name: innerText diff --git a/api/collections/fieldLists/iconCycleSquare.yml b/api/collections/fieldLists/iconCycleSquare.yml index d083a0b..6f4dcca 100644 --- a/api/collections/fieldLists/iconCycleSquare.yml +++ b/api/collections/fieldLists/iconCycleSquare.yml @@ -2,4 +2,5 @@ type: object[] meta: label: Boxen + widget: containerLessObjectArray subFields: !include box.yml diff --git a/api/collections/fieldLists/row.yml b/api/collections/fieldLists/row.yml new file mode 100644 index 0000000..532f436 --- /dev/null +++ b/api/collections/fieldLists/row.yml @@ -0,0 +1,118 @@ +- name: topTitle + type: string + meta: + label: Oberer Titel + helperText: "Dieser Titel wird in der Zeile oben angezeigt." + containerProps: + layout: + size: + default: "col-6" + small: "col-12" + large: "col-6" + +- name: topTitleUpperCase + type: boolean + meta: + label: Oberer Titel in Großbuchstaben + helperText: "Ist dies aktiviert, so wird der obere Titel in Großbuchstaben angezeigt." + containerProps: + layout: + size: + default: "col-6" + small: "col-6" + large: "col-6" + +- name: title + type: string + meta: + label: Titel + helperText: "Dieser Titel wird in der Zeile angezeigt." + containerProps: + layout: + size: + default: "col-6" + small: "col-6" + large: "col-6" + +- name: subTitle + type: string + meta: + label: Untertitel + helperText: "Dieser Untertitel wird in der Zeile angezeigt." + containerProps: + layout: + size: + default: "col-6" + small: "col-6" + large: "col-6" + +- name: backgroundImage + type: string + meta: + label: Hintergrundbild + helperText: "Dieses Bild wird als Hintergrundbild der Zeile angezeigt." + widget: foreignKey # Verwendetes Widget. + foreign: + collection: medialib # Name der Sammlung, in der die ausgewählten Daten gespeichert sind. + id: id # Feldname, das als eindeutige Kennung für die ausgewählten Daten verwendet wird. + subNavigation: 0 # Bestimmt, welche Navigation für die Auswahl der ausgewählten Daten angezeigt wird. + #projection: xyz + #sort: "title" + render: + defaultCollectionViews: true + +- name: noBottomMargin + type: boolean + meta: + label: Kein unterer Abstand + helperText: "Ist dies aktiviert, so wird kein Abstand unter der Zeile angezeigt." + containerProps: + layout: + size: + default: "col-4" + small: "col-6" + large: "col-4" + +- name: noTopMargin + type: boolean + meta: + label: Kein oberer Abstand + helperText: "Ist dies aktiviert, so wird kein Abstand über der Zeile angezeigt." + containerProps: + layout: + size: + default: "col-4" + small: "col-6" + large: "col-4" + +- name: flexWrapNormal + type: boolean + meta: + label: Zeile normal umbrechen + helperText: "Ist dies aktiviert, so wird die Zeile normal und nicht reverse umgebrochen." + containerProps: + layout: + size: + default: "col-4" + small: "col-6" + large: "col-4" + +- name: twoToThree + type: boolean + meta: + label: Zwei zu drei + helperText: "Ist dies aktiviert, so wird die Zeile in zwei zu drei Spalten aufgeteilt." + containerProps: + layout: + size: + default: "col-4" + small: "col-6" + large: "col-4" + +- name: columns + type: object[] + meta: + label: Spalten + direction: row + widget: grid + subFields: !include ../fieldLists/column.yml diff --git a/api/collections/fields/row.yml b/api/collections/fields/row.yml deleted file mode 100644 index 9c76f9e..0000000 --- a/api/collections/fields/row.yml +++ /dev/null @@ -1,90 +0,0 @@ -name: row -type: object -meta: - label: Zeile - metaElements: - - topTitle - - topTitleRed - - title - - subTitle - - pageTitle -subFields: - - name: topTitle - type: string - meta: - label: Oberer Titel - helperText: "Dieser Titel wird in der Zeile oben angezeigt." - - - name: topTitleUpperCase - type: boolean - meta: - label: Oberer Titel in Großbuchstaben - helperText: "Ist dies aktiviert, so wird der obere Titel in Großbuchstaben angezeigt." - - - name: title - type: string - meta: - label: Titel - helperText: "Dieser Titel wird in der Zeile angezeigt." - - - name: subTitle - type: string - meta: - label: Untertitel - helperText: "Dieser Untertitel wird in der Zeile angezeigt." - - - name: pageTitle - type: string - meta: - label: Titel der Seite - helperText: "Dieser Titel wird in der Seite als h1 angezeigt." - - - name: backgroundImage - type: file - meta: - label: Hintergrundbild - helperText: "Dieses Bild wird als Hintergrundbild der Zeile angezeigt." - - - name: noBottomMargin - type: boolean - meta: - label: Kein unterer Abstand - helperText: "Ist dies aktiviert, so wird kein Abstand unter der Zeile angezeigt." - - name: noTopMargin - type: boolean - meta: - label: Kein oberer Abstand - helperText: "Ist dies aktiviert, so wird kein Abstand über der Zeile angezeigt." - - - name: flexWrapNormal - type: boolean - meta: - label: Zeile normal umbrechen - helperText: "Ist dies aktiviert, so wird die Zeile normal und nicht reverse umgebrochen." - - - name: twoToThree - type: boolean - meta: - label: Zwei zu drei - helperText: "Ist dies aktiviert, so wird die Zeile in zwei zu drei Spalten aufgeteilt." - - - name: nextPage - type: string - meta: - label: Nächste Seite - widget: select - choices: - endpoint: page - params: - sort: path - projection: navigation - mapping: - id: path - name: path - - - name: columns - type: object[] - meta: - label: Spalten - direction: row - subFields: !include ../fieldLists/column.yml diff --git a/api/collections/medialib.yml b/api/collections/medialib.yml new file mode 100644 index 0000000..4f1d030 --- /dev/null +++ b/api/collections/medialib.yml @@ -0,0 +1,129 @@ +# Der Name der Kollektion ist beliebig, aber wird in unserem +# Beispiel vom ContentBuilder als "medialib" referenziert. +name: medialib +uploadPath: ../media/medialib + +meta: + label: + de: Medienbibliothek + en: Media Library + muiIcon: multimedia + defaultSort: + field: sort + order: MANUALLY + + backup: + active: true + collectionName: backups + + quickEdit: + enabled: true + fields: + - title + - description + - file + + # "defaultImageFilter" dient auch hier nur zur Reduzierung der + # Bildgröße bei der Anzeige im tibi-admin (Listen). + # Die Bildgröße für die Einbindung ins erzeugte HTML des ContentBuilder + # hat hiermit nix zu tun. + defaultImageFilter: s + + multiupload: + fields: [] + + views: + - type: table + mediaQuery: "(min-width: 0px)" + defaultSelect: false + selectionPriority: 2 + fileDropArea: + label: + { + de: "Ziehen Sie Dateien per Drag and Drop hierher oder klicken Sie, um Dateien auszuwählen.", + en: "Drag and drop some files here, or click to upload.", + } + helperText: { de: "Maximale Uploadgröße: 1,54 MB", en: "Maximum upload size: 1.54MB" } + targetField: file + pageAsDropArea: false + columns: + - source: file + - source: updateTime + type: datetime + label: letztes Update + + - type: cardList + mediaQuery: "(min-width: 1200px)" + selectionPriority: 1 + fileDropArea: + label: + { + de: "Ziehen Sie Dateien per Drag and Drop hierher oder klicken Sie, um Dateien auszuwählen.", + en: "Drag and drop some files here, or click to upload.", + } + helperText: { de: "Maximale Uploadgröße: 1,54 MB", en: "Maximum upload size: 1.54MB" } + targetField: file + pageAsDropArea: false + + fields: + - source: file + - source: updateTime + type: datetime + label: letztes Update + + subNavigation: + - name: modalForeign # Name des Eingabefelds oder der Ansicht. + defaultSort: # Standard-Sortierkriterien, die angewendet werden, wenn keine anderen Sortierkriterien spezifiziert sind. + field: "path" # Standardmäßig wird nach dem "path"-Feld sortiert. + order: "ASC" # Standardmäßig wird in aufsteigender Reihenfolge (ASC) sortiert. + views: # Liste der Ansichten, die in diesem Feld angezeigt werden können. + - type: table # Es wird eine Tabellenansicht verwendet. + mediaQuery: "(min-width: 0px)" # Die Tabellenansicht wird nur angezeigt, wenn die Bildschirmbreite mindestens 0px beträgt. + columns: # Liste der Spalten, die in der Tabelle angezeigt werden. + - source: file + + defaultCallback: # Standard-Callback-Funktion, die ausgeführt wird, wenn keine andere spezifiziert ist. + eval: | # Der Code wird als JavaScript evaluiert. + //js + (entry) => { + parent.selectEntry(entry) + } + //!js + +permissions: + public: + methods: + get: true + post: false + put: false + delete: false + user: + methods: + get: true + post: true + put: true + delete: true + +projections: + dashboard: + select: + +fields: + - name: file + type: file + meta: + label: + de: Datei + en: File + + - name: sort + type: number + meta: + label: + de: Manuelle Sortierung + en: Manual Sorting + inputProps: + { readonly: true, placeholder: { de: "Wert wird automatisch gesetzt", en: "Value is set automatically" } } + helperText: + de: Dieses Feld wird für die manuelle Sortierung benötigt. Sobald ein Eintrag per Drag&Drop verschoben wurde, wird die neue Position innerhalb der Liste eingetragen. + en: This field is required for manual sorting. As soon as an entry is moved using Drag&Drop, the new position is entered in the list. diff --git a/api/collections/module.yml b/api/collections/module.yml new file mode 100644 index 0000000..5a28fbe --- /dev/null +++ b/api/collections/module.yml @@ -0,0 +1,143 @@ +name: module + +meta: + label: Module + backup: + active: true + collectionName: backups + + views: + - type: table + columns: + - source: type + + subNavigation: + - name: modal + views: + - type: table + columns: + - source: type + defaultCallback: # Standard-Callback-Funktion, die ausgeführt wird, wenn keine andere spezifiziert ist. + eval: | # Der Code wird als JavaScript evaluiert. + //js + (entry) => { // Diese Funktion nimmt den Eintrag (entry) als Argument. + parent.selectEntry(entry) // Die Funktion selectEntry auf dem übergeordneten Objekt wird mit dem Eintrag als Argument aufgerufen. + } + //!js + +permissions: + public: + methods: + get: true + post: false + put: false + delete: false + user: + methods: + get: true + post: true + put: true + delete: true + +imageFilter: + xs: + - fit: true + height: 90 + width: 90 + resampling: lanczos + quality: 60 + s: + - fit: true + height: 300 + width: 300 + resampling: lanczos + quality: 60 + m: + - fit: true + height: 600 + width: 600 + resampling: lanczos + quality: 60 + l: + - fit: true + height: 1240 + width: 1240 + resampling: lanczos + quality: 60 + xl: + - fit: true + height: 2000 + width: 2000 + resampling: lanczos + quality: 60 + +fields: + - name: type + type: string + meta: + label: Modultyp + helperText: "Wählen Sie den Typ des Moduls aus." + widget: select + choices: + - name: Arbeitskreislauf + id: iconCycleCircle + + - name: Icons im Rechteck + id: iconCycleSquare + + - name: Weltkarte + id: worldCard + + - name: Chef Team + id: chefTeam + + - name: Mitarbeiter Team + id: employeeTeam + + - name: Stellenanzeigen Verlinkungen + id: jobOfferLink + + - name: Stellenanzeigen + id: jobOffer + + - name: iconCycleCircle + type: object + meta: + label: Icons im Kreis + widget: containerLessObject + dependsOn: + eval: $parent.type == 'iconCycleCircle' + subFields: !include fieldLists/iconCycleCircle.yml + + - name: iconCycleSquare + type: object + meta: + label: Icons im Rechteck + dependsOn: + eval: $parent.contentType == 'iconCycleSquare' + subFields: !include fieldLists/iconCycleSquare.yml + + - name: worldCard + type: object + meta: + label: Weltkarte + widget: containerLessObject + dependsOn: + eval: $parent.type == 'worldCard' + subFields: + - name: row + type: object[] + meta: + label: Weltkartenreihe + widget: grid + subFields: + - name: cards + type: object[] + meta: + label: Kartenspalten + widget: grid + direction: row + metaElements: + - verticalAlignment + - horizontalAlignment + subFields: !include fieldLists/cards.yml diff --git a/api/collections/navigation.yml b/api/collections/navigation.yml index 323b6aa..3b71e5f 100644 --- a/api/collections/navigation.yml +++ b/api/collections/navigation.yml @@ -28,8 +28,6 @@ permissions: put: true delete: false - - fields: - name: tree type: number @@ -56,6 +54,8 @@ fields: folding: previewUnfolded: name previewFolded: name + + widget: containerLessObjectArray subFields: - name: name type: string diff --git a/api/config.yml b/api/config.yml index c3a58bd..d815f0a 100644 --- a/api/config.yml +++ b/api/config.yml @@ -27,6 +27,8 @@ meta: collections: - !include collections/navigation.yml - !include collections/content.yml + - !include collections/module.yml + - !include collections/medialib.yml - !include collections/backups.yml assets: diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index ffe316b..26f57c0 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -4,13 +4,25 @@ import Menu from "./lib/components/Menu/Menu.svelte" import NotFound from "./lib/components/NotFound.svelte" import Rows from "./lib/components/Pagebuilder/Rows.svelte" - import { location, navigation, pages, serviceNavigation, rerender } from "./lib/store" + import { + location, + navigation, + pages, + serviceNavigation, + rerender, + mediaLibrary, + team, + jobOffers, + modules, + } from "./lib/store" import { onMount, onDestroy } from "svelte" import { Route, Router } from "svelte-routing" import { loadPages } from "./lib/functions/getPages" import { loadNavigation } from "./lib/functions/loadNavigation" import ScrollTop from "./lib/components/widgets/scrollTop.svelte" import ScrollDown from "./lib/components/widgets/scrollDown.svelte" + import { loadLibrary } from "./lib/functions/loadLibrary" + import { loadModules } from "./lib/functions/loadModules" export let url = "" if (url) { @@ -28,11 +40,23 @@ async function getPages() { let pagesArray = await loadPages() let pagesRes: Pages = {} + let teamRes: Pages = {} + let jobOffersRes: Pages = {} pagesArray.forEach((e) => { - pagesRes[e.path] = e + if (e.type == "page") { + pagesRes[e.path] = e + } else if (e.type == "teamMembers") { + teamRes[e.path] = e + } else if (e.type == "jobOffers") { + jobOffersRes[e.path] = e + } else { + pagesRes[e.path] = e + } }) $pages = pagesRes + $team = teamRes + $jobOffers = jobOffersRes } async function getNavigation() { @@ -41,8 +65,27 @@ $serviceNavigation = nav[1] } + async function getLibrary() { + let library: MediaLibrary[] = await loadLibrary() + let lib = {} + library.forEach((e) => { + lib[e.id] = e + }) + $mediaLibrary = lib + } + + async function getModules() { + let moduleArray: Module[] = await loadModules() + let mod = {} + moduleArray.forEach((e) => { + mod[e.id] = e + }) + $modules = mod + } + getNavigation() getPages() + getLibrary() let activeMenu = false $: { diff --git a/frontend/src/lib/components/Pagebuilder/Module.svelte b/frontend/src/lib/components/Pagebuilder/Module.svelte new file mode 100644 index 0000000..cb4d51c --- /dev/null +++ b/frontend/src/lib/components/Pagebuilder/Module.svelte @@ -0,0 +1,34 @@ + + +{#if module.type == "iconCycleCircle"} + +{:else if module.type == "iconCycleSquare"} + +{:else if module.type == "worldCard"} + +{:else if module.type == "chefTeam"} + +{:else if module.type == "employeeTeam"} + +{:else if module.type == "jobOffer"} + +{:else if module.type == "jobOfferLink"} + +{/if} diff --git a/frontend/src/lib/components/Pagebuilder/Pagebuilder.svelte b/frontend/src/lib/components/Pagebuilder/Pagebuilder.svelte index 8c55dbd..449993e 100644 --- a/frontend/src/lib/components/Pagebuilder/Pagebuilder.svelte +++ b/frontend/src/lib/components/Pagebuilder/Pagebuilder.svelte @@ -14,26 +14,19 @@ import TextLink from "../widgets/textLink.svelte" import TopDown from "../widgets/topDown.svelte" import WorldCard from "../widgets/Worldcard/worldcard.svelte" - import { pages, rerender } from "../../store" + import { modules, pages, rerender, team } from "../../store" import IconCycleCircle from "../widgets/iconCycleCircle.svelte" import IconCycleBox from "../widgets/iconCycleBox.svelte" + import Module from "./Module.svelte" export let row: Row export let pageId: string export let bright: boolean export let isHP: boolean + export let i: number + export let page: Page + export let personPage: boolean - function checkNestedPath() { - const pathSegments = location.pathname.split("/").filter((segment) => segment.length) - - if (pathSegments.length > 1) { - pathSegments.pop() // remove the last segment - return "/" + pathSegments.join("/") - } - - return "" - } - let nestedPath = checkNestedPath() window.addEventListener("popstate", function (event) { $rerender = $rerender + 1 }) @@ -41,18 +34,18 @@ {#if Object.keys(row).length} {#if row.topTitle} -

+

{row.topTitle}

{/if} - {#if nestedPath} + {#if personPage}

arrow Zurück zur Übersicht @@ -62,15 +55,19 @@ on:keydown on:click="{() => { $rerender = $rerender + 1 - navigate(row?.nextPage || nestedPath) + let chefs = Object.values($team).filter((p) => p.personType == 'chef') + let i = chefs.findIndex((p) => p.path == page.path) + if (i == chefs.length - 1) i = 0 + else i++ + navigate(chefs[i].path) }}" > Zum nächsten Profil arrow

{/if} - {#if row.pageTitle} -

{row.pageTitle}

+ {#if page.pageTitle && i == 0} +

{page.pageTitle}

{/if} {#if row.title}

{row.title}

@@ -83,10 +80,16 @@ class="row" class:twoToThree="{row.twoToThree}" class:normalWrap="{row.flexWrapNormal}" - class:dominant="{row.columns.some((col) => col.contentType == 'iconCycleCircle')}" + class:dominant="{row.columns.some( + (col) => col.contentType == 'moduleImport' && $modules?.[col.moduleImport]?.type == 'iconCycleCircle' + )}" > {#each row?.columns as col} -
+
{#if col?.contentType == "text"} {:else if col?.contentType == "textLink"} @@ -95,12 +98,12 @@ path="{Object.values($pages)?.find((o) => o.id == col.textLink.link)?.path || '/'}" bright="{bright}" /> + {:else if col.contentType == "moduleImport"} + {:else if col.contentType == "image"} {:else if col.contentType == "iconBlocks"} - {:else if col.contentType == "pageLinkBlocks"} - {:else if col.contentType == "networkEvents"} {:else if col.contentType == "publications"} @@ -111,18 +114,8 @@ {:else if col.contentType == "nestedCard"} - {:else if col.contentType == "boxlist"} - - {:else if col.contentType == "extendableBoxes"} - - {:else if col.contentType == "personPreview"} - - {:else if col.contentType == "iconCycleCircle"} - - {:else if col.contentType == "iconCycleSquare"} - - {:else if col.contentType == "worldCard"} - + + {/if}
{/each} diff --git a/frontend/src/lib/components/Pagebuilder/Rows.svelte b/frontend/src/lib/components/Pagebuilder/Rows.svelte index dac5844..1760455 100644 --- a/frontend/src/lib/components/Pagebuilder/Rows.svelte +++ b/frontend/src/lib/components/Pagebuilder/Rows.svelte @@ -1,27 +1,35 @@
- card + card
- {#each col.worldCard.row as row} + {#each worldCard.rows as row}
{#each row.cards as card} - export let col: Column - - // A function to compare first names and sort the array + export let persons: Page[] + let boxes = persons.map((p) => p.personPreview.name) const sortByFirstName = (a, b) => { const nameA = a.name.split(" ")[0] // Extracts the first name from "First Last" const nameB = b.name.split(" ")[0] return nameA.localeCompare(nameB) } - - col.boxList.boxes.sort(sortByFirstName) // Sorts the array in place + boxes = boxes.sort(sortByFirstName)
- {#each col.boxList.boxes as name} + {#each boxes as name}
- {name.name} + {name}
{/each}
diff --git a/frontend/src/lib/components/widgets/events.svelte b/frontend/src/lib/components/widgets/events.svelte index dbae0c9..02e88f3 100644 --- a/frontend/src/lib/components/widgets/events.svelte +++ b/frontend/src/lib/components/widgets/events.svelte @@ -1,5 +1,6 @@
- {#each col.extendableBoxes as box, i} + {#each jobOffers as box, i}
import { apiBaseURL } from "../../../config" + import { mediaLibrary } from "../../store" export let pageId: string export let col: Column
- {#each col.iconBlocks as icon}
- img + img
{icon.bigText}

{icon.smallText}

diff --git a/frontend/src/lib/components/widgets/iconCycleBox.svelte b/frontend/src/lib/components/widgets/iconCycleBox.svelte index 0a15425..47527cd 100644 --- a/frontend/src/lib/components/widgets/iconCycleBox.svelte +++ b/frontend/src/lib/components/widgets/iconCycleBox.svelte @@ -1,24 +1,25 @@
- {#each col?.iconCycleSquare?.boxes as box, i} + {#each iconCycleSquare?.boxes as box, i}
+ data-src="{apiBaseURL}medialib/{box?.icon}/{$mediaLibrary?.[box?.icon]?.file?.src}">
{box.text} diff --git a/frontend/src/lib/components/widgets/iconCycleCircle.svelte b/frontend/src/lib/components/widgets/iconCycleCircle.svelte index 5c2105b..612c40b 100644 --- a/frontend/src/lib/components/widgets/iconCycleCircle.svelte +++ b/frontend/src/lib/components/widgets/iconCycleCircle.svelte @@ -1,11 +1,13 @@
- img + img
{#if col && col.icons}
{#each col.icons as icon} {/each} diff --git a/frontend/src/lib/components/widgets/infoBoard.svelte b/frontend/src/lib/components/widgets/infoBoard.svelte index 265cbee..64180fe 100644 --- a/frontend/src/lib/components/widgets/infoBoard.svelte +++ b/frontend/src/lib/components/widgets/infoBoard.svelte @@ -1,5 +1,6 @@