Compare commits

...

30 Commits

Author SHA1 Message Date
fc612ad924 sort
All checks were successful
deploy to production / deploy (push) Successful in 49s
2023-12-08 13:18:42 +00:00
27ee7bbc83 sort
All checks were successful
deploy to production / deploy (push) Successful in 46s
2023-12-08 13:02:22 +00:00
9601a29db1 sort
All checks were successful
deploy to production / deploy (push) Successful in 55s
2023-12-08 12:54:26 +00:00
39a4fd938c sort
All checks were successful
deploy to production / deploy (push) Successful in 1m2s
2023-12-08 12:48:26 +00:00
7c793124a1 coloring
All checks were successful
deploy to production / deploy (push) Successful in 53s
2023-12-07 20:57:34 +00:00
fd5af432db coloring
All checks were successful
deploy to production / deploy (push) Successful in 42s
2023-12-07 20:24:09 +00:00
f85efb3c38 mail
All checks were successful
deploy to production / deploy (push) Successful in 32s
2023-11-20 15:04:35 +00:00
e3a7f36892 seo
All checks were successful
deploy to production / deploy (push) Successful in 29s
2023-11-19 18:25:56 +00:00
63fa3c2846 seo...
All checks were successful
deploy to production / deploy (push) Successful in 34s
2023-11-19 18:16:14 +00:00
86d8720c00 canoncial
All checks were successful
deploy to production / deploy (push) Successful in 47s
2023-11-19 18:04:16 +00:00
86d5449213 fix§
All checks were successful
deploy to production / deploy (push) Successful in 33s
2023-11-19 17:48:53 +00:00
e09d6eb2a9 meta
All checks were successful
deploy to production / deploy (push) Successful in 34s
2023-11-19 17:46:01 +00:00
ff3f635277 svg-loader
All checks were successful
deploy to production / deploy (push) Successful in 32s
2023-11-19 17:37:52 +00:00
4505435bf7 favicon
All checks were successful
deploy to production / deploy (push) Successful in 32s
2023-11-19 17:32:26 +00:00
a38ed26cd6 favicon
All checks were successful
deploy to production / deploy (push) Successful in 29s
2023-11-19 17:24:19 +00:00
f74dd92429 filter
All checks were successful
deploy to production / deploy (push) Successful in 34s
2023-11-19 17:17:06 +00:00
0e9b5baed7 fix
All checks were successful
deploy to production / deploy (push) Successful in 34s
2023-11-19 11:08:52 +00:00
c1b4882e5b fix
All checks were successful
deploy to production / deploy (push) Successful in 30s
2023-11-18 17:03:19 +00:00
baf4a4b7b2 fix
All checks were successful
deploy to production / deploy (push) Successful in 29s
2023-11-18 16:54:11 +00:00
c0209910e2 fix
All checks were successful
deploy to production / deploy (push) Successful in 29s
2023-11-18 16:32:41 +00:00
99be954f5c fix
All checks were successful
deploy to production / deploy (push) Successful in 28s
2023-11-18 16:22:42 +00:00
296458a7cf fix
All checks were successful
deploy to production / deploy (push) Successful in 34s
2023-11-18 16:20:51 +00:00
dfa8496c93 export
All checks were successful
deploy to production / deploy (push) Successful in 30s
2023-11-18 15:03:18 +00:00
572d71d7dd backup nav
All checks were successful
deploy to production / deploy (push) Successful in 27s
2023-11-12 18:45:04 +00:00
859d7b0e2d enhancements
All checks were successful
deploy to production / deploy (push) Successful in 32s
2023-11-12 17:55:27 +00:00
9258759c01 namespace
All checks were successful
deploy to production / deploy (push) Successful in 30s
2023-11-12 12:14:49 +00:00
b3a74a985b namespace
All checks were successful
deploy to production / deploy (push) Successful in 28s
2023-11-12 12:09:07 +00:00
f67794c63b test
All checks were successful
deploy to production / deploy (push) Successful in 31s
2023-11-12 12:01:18 +00:00
9d58febf4d version two
Some checks failed
deploy to production / deploy (push) Failing after 31s
2023-11-12 11:45:32 +00:00
92ca030e6c first version 2023-11-12 10:02:26 +00:00
91 changed files with 2169 additions and 852 deletions

3
.env
View File

@@ -1,7 +1,8 @@
PROJECT_NAME=fontis
TIBI_PREFIX=tibi
TIBI_NAMESPACE=fontis
TIBI_NAMESPACE=fontis_v2
UID=100
GID=101
RELEASE_ORG_SLUG=webmakers-gmbh
RELEASE_PROJECT_SLUG=fontis
START_SCRIPT=:ssr

View File

@@ -58,11 +58,11 @@ jobs:
run: |
yarn build
#- name: build ssr
# env:
# FORCE_COLOR: "true"
# run: |
# yarn build:server
- name: build ssr
env:
FORCE_COLOR: "true"
run: |
yarn build:server
- name: build legacy
env:
@@ -85,9 +85,9 @@ jobs:
# docker compose -p ${GITHUB_REF_NAME}-${GITHUB_REPOSITORY_NAME}-${GITHUB_REPOSITORY_OWNER} up -d --build --remove-orphans
- name: deploy
if: github.ref == 'refs/heads/master'
if: github.ref == 'refs/heads/tibi-restructure'
env:
RSYNC_USER: "fontis_rsync_master"
RSYNC_PASS: ${{ secrets.rsync_master }}
RSYNC_USER: "fontis_rsync_restructure"
RSYNC_PASS: ${{ secrets.RSYNC_RESTRUCTURE }}
run: |
scripts/deploy.sh ftp1.webmakers.de $RSYNC_USER $RSYNC_PASS

Binary file not shown.

View File

@@ -29,6 +29,13 @@ hooks:
create:
type: javascript
file: hooks/backups/post_create.js
return:
type: javascript
file: hooks/clear_cache.js
put:
return:
type: javascript
file: hooks/clear_cache.js
fields:
- name: collectionName

View File

@@ -3,32 +3,162 @@ uploadPath: ../media/page
meta:
label: Inhalt
muiIcon: web
muiIcon: tableOfContents
allowExportAll: true
backup:
active: true
collectionName: backups
defaultSort:
field: sort
order: MANUALLY
views:
- type: simpleList
selectionPriority: 0
primaryText: pageTitle
secondaryText: path
mediaQuery: "(min-width: 0px)"
tertiaryText: type
- type: table
selectionPriority: 1
mediaQuery: "(min-width: 700px)"
columns:
- source: type
name: Typ
filter: true
- source: path
name: Pfad
filter: true
- source: pageTitle
name: Titel
filter: true
- source: active
name: Aktiv
filter: true
tablist:
activeTab: site
activeTab: general
tabs:
- name: general
label: Allgemein
subFields:
- source: path
- source: pageTitle
- source: type
- source: active
- source: sort
- name: teaser
label: Teaser
label: Homepage Seitenteaser
subFields:
- source: teaser
- name: personPreview
label: Personenvorschau
subFields:
- source: personType
- source: personPreview
- name: jobOffer
label: Job Angebot
subFields:
- source: jobOffer
- name: site
label: Seite
subFields:
- source: rows
- name: meta
label: Meta
subFields:
- source: meta
subNavigation:
- name: seite
label:
de: Seiten
en: pages
muiIcon: book-open-page-variant
defaultSort:
field: "sort"
order: "MANUALLY"
setDefault:
field: type
value: page
views:
- type: table
columns:
- source: path
name: Pfad
filter: true
- source: pageTitle
name: Titel
filter: true
- source: active
name: Aktiv
filter: true
filter:
type: page
- name: teamMembers
label:
de: Teammitglieder
en: Team members
muiIcon: accountGroup
setDefault:
field: type
value: teamMembers
defaultSort:
field: "sort"
order: "MANUALLY"
views:
- type: table
columns:
- source: path
name: Pfad
fiter: true
- source: personType
name: Typ
filter: true
- source: pageTitle
name: Titel
filter: true
- source: active
name: Aktiv
filter: true
filter:
type: teamMembers
- name: jobOffers
label:
de: Stellenanzeigen
en: Job offers
muiIcon: briefcase
setDefault:
field: type
value: jobOffers
defaultSort:
field: "sort"
order: "MANUALLY"
views:
- type: table
columns:
- source: path
name: Pfad
filter: true
- source: pageTitle
name: Titel
filter: true
- source: active
name: Aktiv
filter: true
filter:
type: jobOffers
imageFilter:
xs:
- fit: true
@@ -75,6 +205,16 @@ permissions:
put: true
delete: true
hooks:
post:
return:
type: javascript
file: hooks/clear_cache.js
put:
return:
type: javascript
file: hooks/clear_cache.js
projections:
navigation:
select:
@@ -86,15 +226,221 @@ fields:
meta:
label: Pfad
helperText: "Ein Pfad sollte mit einem / starten und ohne eins enden."
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- type: boolean
name: active
meta:
label: Aktiv
helperText: Ist dies Aktiviert, so wird die Seite verfügbar.
defaultValue: true
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- type: string
name: type
meta:
label: Inhaltstyp
widget: select
defaultValue: page
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
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-12"
large: "col-6"
- name: personType
type: string
meta:
label: Typ
widget: select
defaultValue: chef
dependsOn:
eval: $.type == "teamMembers"
choices:
- name: Chef
id: chef
- name: Mitarbeiter
id: employee
- name: personPreview
type: object
meta:
label: Personenvorschau
dependsOn:
eval: $.type == "teamMembers"
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 Angebot
dependsOn:
eval: $.type == "jobOffers"
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 Betreff
dependsOn:
eval: $parent.emailButton == true
- name: rows
type: object[]
meta:
label: Zeilen
widget: containerLessObjectArray
folding:
force: true
widget: grid
dependsOn:
eval: $.type != "jobOffers" && ($.type != "teamMembers" || $.personType == "chef")
metaElements:
- source: backgroundImage
- source: noBottomMargin
- source: noTopMargin
- source: flexWrapNormal
- source: twoToThree
- source: nextPage
subFields: !include fieldLists/row.yml
- name: meta
type: object
meta:
label: Meta Agaben
dependsOn:
eval: $.type == "page"
subFields:
- !include fields/row.yml
- name: title
type: string
meta:
label: Titel
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: description
type: string
meta:
label: Beschreibung
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: keywords
type: string
meta:
label: Schlüsselwörter
- 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.

View File

@@ -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"

View File

@@ -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

View File

@@ -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,33 +16,18 @@
- 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
- name: Text mit Link
id: textLink
- name: Icon block
id: iconBlocks
- name: Seitenlinks
id: pageLinkBlocks
- name: Netzwerk Veranstaltungen
id: networkEvents
@@ -58,16 +40,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 +71,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 +90,8 @@
label: Publikationen
dependsOn:
eval: $parent.contentType == 'publications'
widget: containerLessObjectArray
direction: row
subFields:
- name: content
type: string
@@ -93,9 +100,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,96 +119,102 @@
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:
label: oberer text
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: smallText
type: string
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)
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- 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 +228,7 @@
type: object
meta:
label: Informationsbrett
widget: containerLessObject
dependsOn:
eval: $parent.contentType == 'infoBoard'
subFields:
@@ -223,36 +246,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 +285,7 @@
type: object
meta:
label: Top-Down
widget: containerLessObject
dependsOn:
eval: $parent.contentType == 'topDown'
subFields:
@@ -279,110 +293,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 +349,5 @@
sort: path
projection: navigation
mapping:
id: id
id: id
name: path

View File

@@ -2,6 +2,9 @@
type: object[]
meta:
label: Boxen
pathStep:
title: "icons im Kreis"
widget: containerLessObjectArray
subFields: !include box.yml
- name: innerText

View File

@@ -2,4 +2,7 @@
type: object[]
meta:
label: Boxen
pathStep:
title: "icons im Rechteck"
widget: containerLessObjectArray
subFields: !include box.yml

View File

@@ -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

View File

@@ -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

View File

@@ -2,8 +2,8 @@ name: teaser
type: object
meta:
label: Teaser
metaElements:
- showTeaser
dependsOn:
eval: $.type == "page"
subFields:
- name: showTeaser
type: boolean
@@ -14,8 +14,8 @@ subFields:
- name: subTitle
type: string
meta:
label: Untertitel
helperText: "Dieser Untertitel wird in der Startseite angezeigt."
label: Übertitel
helperText: "Dieser Übertitel wird in der Startseite über dem Titel angezeigt."
- name: teaserTitle
type: string
@@ -28,4 +28,4 @@ subFields:
meta:
widget: richtext
label: Beschreibung
helperText: "Diese Beschreibung wird in der Startseite angezeigt."
helperText: "Diese Beschreibung wird in der Startseite unter dem Titel angezeigt."

View File

@@ -0,0 +1,193 @@
# Der Name der Kollektion ist beliebig, aber wird in unserem
# Beispiel vom ContentBuilder als "medialib" referenziert.
name: medialib
uploadPath: ../media/medialib
meta:
allowExportAll: true
label:
de: Medienbibliothek
en: Media Library
muiIcon: multimedia
defaultSort:
field: sort
order: MANUALLY
backup:
active: true
collectionName: backups
quickEdit:
enabled: true
fields:
- title
- alt
- 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: xs
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
name: Datei
- 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
name: Datei
- 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:
hooks:
post:
return:
type: javascript
file: hooks/clear_cache.js
put:
return:
type: javascript
file: hooks/clear_cache.js
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: 1200
width: 1200
resampling: lanczos
quality: 60
xl:
- fit: true
height: 2000
width: 2000
resampling: lanczos
quality: 60
fields:
- name: file
type: file
meta:
label:
de: Datei
en: File
- name: alt
type: string
meta:
label:
de: Alternativtext
en: Alternative text
helperText:
de: Der Alternativtext wird angezeigt, wenn die Datei nicht geladen werden kann.
en: The alternative text is displayed if the file cannot be loaded.
- name: title
type: string
meta:
label:
de: Titel
en: Title
helperText:
de: Der Titel wird angezeigt, wenn die Datei geladen wird.
en: The title is displayed when the file is loaded.
- 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.

172
api/collections/module.yml Normal file
View File

@@ -0,0 +1,172 @@
name: module
meta:
label: Module
allowExportAll: true
backup:
active: true
collectionName: backups
views:
- type: table
columns:
- source: type
name: Typ
filter: true
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
hooks:
post:
return:
type: javascript
file: hooks/clear_cache.js
put:
return:
type: javascript
file: hooks/clear_cache.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: jobOfferPage
type: string
meta:
label: Stellenanzeigen
widget: select
dependsOn:
eval: $parent.type == 'jobOfferLink'
choices:
endpoint: page
params:
sort: path
projection: navigation
mapping:
id: path
name: path
- 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.type == '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

View File

@@ -4,6 +4,10 @@ uploadPath: ../media/navigation
meta:
label: "Navigation"
muiIcon: navigation
allowExportAll: true
backup:
active: true
collectionName: backups
views:
- type: simpleList
mediaQuery: "(max-width:599px)"
@@ -13,6 +17,7 @@ meta:
mediaQuery: "(min-width:600px)"
columns:
- source: tree
name: Navigationsbaum
permissions:
public:
@@ -27,8 +32,15 @@ permissions:
post: false
put: true
delete: false
hooks:
post:
return:
type: javascript
file: hooks/clear_cache.js
put:
return:
type: javascript
file: hooks/clear_cache.js
fields:
- name: tree
@@ -56,6 +68,8 @@ fields:
folding:
previewUnfolded: name
previewFolded: name
widget: containerLessObjectArray
subFields:
- name: name
type: string

View File

@@ -7,6 +7,7 @@ meta:
label: { de: "SSR Dummy", en: "ssr dummy" }
muiIcon: server
rowIdentTpl: { twig: "{{ id }}" }
views:
- type: simpleList
mediaQuery: "(max-width: 600px)"
@@ -32,8 +33,6 @@ permissions:
post: false
put: false
delete: false
"token:${SSR_TOKEN}":
methods:
# only via url=
@@ -57,6 +56,7 @@ fields:
- name: path
type: string
index: [single, unique]
- name: content
type: string
meta:

View File

@@ -1,4 +1,4 @@
namespace: fontis
namespace: fontis_v2
meta:
imageUrl:
@@ -22,12 +22,33 @@ meta:
upper: rgba(3, 50, 59, 0.7)
lower: rgba(3, 50, 59)
minorItems: []
- collection: module
type: reference
style:
upper: rgba(3, 50, 59, 0.7)
lower: rgba(3, 50, 59)
- collection: medialib
type: reference
style:
upper: rgba(3, 50, 59, 0.7)
lower: rgba(3, 50, 59)
minorItems:
- collection: page
subNavigation: 0
- collection: page
subNavigation: 1
- collection: page
subNavigation: 2
collections:
- !include collections/navigation.yml
- !include collections/content.yml
- !include collections/module.yml
- !include collections/medialib.yml
- !include collections/backups.yml
- !include collections/ssr.yml
assets:
- name: img

View File

@@ -1 +1,2 @@
TOKEN=geheim
TOKEN=geheim
SSR_TOKEN=owshwerNwoa

5
api/hooks/clear_cache.js Normal file
View File

@@ -0,0 +1,5 @@
var utils = require("./lib/utils")
;(function () {
utils.clearSSRCache()
})()

View File

@@ -4,7 +4,9 @@ const release = "tibi-docs.dirty"
if (release && typeof context !== "undefined") {
context.response.header("X-Release", release)
}
const apiClientBaseURL = "/api/"
module.exports = {
release,
apiClientBaseURL,
}

View File

@@ -1,21 +1,26 @@
const apiSsrBaseURL = "http://localhost:8080/api/v1/_/fontis_v2"
module.exports = {
apiSsrBaseURL,
ssrValidatePath: function (path) {
// validate if path ssr rendering is ok, -1 = NOTFOUND, 0 = NO SSR, 1 = SSR
// pe. use context.readCollection("product", {filter: {path: path}}) ... to validate dynamic urls
// / is de home
if (path == "/") return 1
// all other sites are in db
path = path?.replace(/^\//, "")
// // / is de home
// if (path == "/") return 1
// // all other sites are in db
//path = path?.replace(/^\//, "")
console.log("PATH:", path)
// filter for path or alternativePaths
const resp = context.db.find("content", {
const resp = context.db.find("page", {
filter: {
$or: [{ path }, { "alternativePaths.path": path }],
$and: [{ path }],
},
selector: { _id: 1 },
})
console.log("RESP:", resp?.length)
if (resp && resp.length) {
return 1
}
@@ -23,5 +28,5 @@ module.exports = {
// not found
return -1
},
ssrAllowedAPIEndpoints: ["content", "medialib"],
ssrPublishCheckCollections: ["page"],
}

View File

@@ -0,0 +1,36 @@
const { apiSsrBaseURL, ssrPublishCheckCollections } = require("../config")
/**
* api request via server, cache result in context.ssrCache
* should be elimated in client code via tree shaking
*
* @param {string} cacheKey
* @param {string} endpoint
* @param {ApiOptions} options
* @returns {ApiResult}
*/
function ssrRequest(cacheKey, endpoint, query, options) {
let url = endpoint + (query ? "?" + query : "")
// console.log("############ FETCHING ", apiSsrBaseURL + url)
const response = context.http.fetch(apiSsrBaseURL + "/" + url, {
method: options.method,
headers: options.headers,
})
const json = response.body.json()
const count = parseInt(response.headers["x-results-count"] || "0")
// json is go data structure and incompatible with js, so we need to convert it
const r = { data: JSON.parse(JSON.stringify(json)), count: count }
// @ts-ignore
context.ssrCache[cacheKey] = r
return r
}
module.exports = {
ssrRequest,
}

182
api/hooks/lib/ssr.js Normal file
View File

@@ -0,0 +1,182 @@
const { apiClientBaseURL } = require("../config-client")
/**
* convert object to string
* @param {any} obj object
*/
function obj2str(obj) {
if (Array.isArray(obj)) {
return JSON.stringify(
obj.map(function (idx) {
return obj2str(idx)
})
)
} else if (typeof obj === "object" && obj !== null) {
var elements = Object.keys(obj)
.sort()
.map(function (key) {
var val = obj2str(obj[key])
if (val) {
return key + ":" + val
}
})
var elementsCleaned = []
for (var i = 0; i < elements.length; i++) {
if (elements[i]) elementsCleaned.push(elements[i])
}
return "{" + elementsCleaned.join("|") + "}"
}
if (obj) return obj
}
// fetch polyfill
// [MIT License](LICENSE.md) © [Jason Miller](https://jasonformat.com/)
const _f = function (
/** @type {string | URL} */ url,
/** @type {{ method?: any; credentials?: any; headers?: any; body?: any; }} */ options
) {
if (typeof XMLHttpRequest === "undefined") {
return Promise.resolve(null)
}
options = options || {}
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest()
const keys = []
const all = []
const headers = {}
const response = () => ({
ok: ((request.status / 100) | 0) == 2, // 200-299
statusText: request.statusText,
status: request.status,
url: request.responseURL,
text: () => Promise.resolve(request.responseText),
json: () => Promise.resolve(request.responseText).then(JSON.parse),
blob: () => Promise.resolve(new Blob([request.response])),
clone: response,
headers: {
// @ts-ignore
keys: () => keys,
// @ts-ignore
entries: () => all,
get: (n) => headers[n.toLowerCase()],
has: (n) => n.toLowerCase() in headers,
},
})
request.open(options.method || "get", url, true)
request.onload = () => {
request
.getAllResponseHeaders()
// @ts-ignore
.replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm, (m, key, value) => {
keys.push((key = key.toLowerCase()))
all.push([key, value])
headers[key] = headers[key] ? `${headers[key]},${value}` : value
})
resolve(response())
}
request.onerror = reject
request.withCredentials = options.credentials == "include"
for (const i in options.headers) {
request.setRequestHeader(i, options.headers[i])
}
request.send(options.body || null)
})
}
const _fetch = typeof fetch === "undefined" ? (typeof window === "undefined" ? _f : window.fetch || _f) : fetch
/**
* api request via client or server
* server function ssrRequest is called via context.ssrRequest, binded in ssr hook
*
* @param {string} endpoint
* @param {ApiOptions} options
* @param {any} body
* @returns {Promise<ApiResult<any>>}
*/
function apiRequest(endpoint, options, body) {
// TODO cache only for GET
// first check cache if on client
const cacheKey = obj2str({ endpoint: endpoint, options: options })
options.method = options?.method || "GET"
// @ts-ignore
if (typeof window !== "undefined" && window.__SSR_CACHE__ && options?.method === "GET") {
// @ts-ignore
const cache = window.__SSR_CACHE__[cacheKey]
console.log("SSR:", cacheKey, cache)
if (cache) {
return Promise.resolve(cache)
}
}
let method = options?.method || "GET"
let query = "&count=1"
if (options?.filter) query += "&filter=" + encodeURIComponent(JSON.stringify(options.filter))
if (options?.sort) query += "&sort=" + options.sort + "&sort=_id"
if (options?.limit) query += "&limit=" + options.limit
if (options?.offset) query += "&offset=" + options.offset
if (options?.projection) query += "&projection=" + options.projection
if (options?.lookup) query += "&lookup=" + options.lookup
if (options?.params) {
Object.keys(options.params).forEach((p) => {
query += "&" + p + "=" + encodeURIComponent(options.params[p])
})
}
let headers = {
"Content-Type": "application/json",
}
if (options?.headers) headers = { ...headers, ...options.headers }
if (typeof window === "undefined" && method === "GET") {
// server
// reference via context from get hook to tree shake in client
// @ts-ignore
const d = context.ssrRequest(cacheKey, endpoint, query, Object.assign({}, options, { method, headers }))
return d
} else {
// client
let url = endpoint + (query ? "?" + query : "")
console.log("URL:", url)
const requestOptions = {
method,
mode: "cors",
headers,
}
if (method === "POST" || method === "PUT") {
requestOptions.body = JSON.stringify(body)
}
return _fetch(apiClientBaseURL + url, requestOptions).then((response) => {
return response?.json().then((json) => {
if (response?.status < 200 || response?.status >= 400) {
return Promise.reject({ response, data: json })
}
return Promise.resolve({ data: json || null, count: response.headers?.get("x-results-count") || 0 })
})
})
}
}
module.exports = {
obj2str,
apiRequest,
}

View File

@@ -1,16 +1,19 @@
const { ssrValidatePath, ssrAllowedAPIEndpoints } = require("../config")
// TODO: add query string functionality to cache
const { ssrValidatePath } = require("../config")
const { log } = require("../lib/utils")
const { ssrRequest } = require("../lib/ssr-server.js")
const { obj2str, log } = require("../lib/utils")
;(function () {
/** @type {HookResponse} */
var response = null
let response = null
var request = context.request()
var url = request.query("url")
var noCache = request.query("noCache")
const request = context.request()
let url = request.query("url")
const noCache = request.query("noCache")
// add sentry trace id to head
var trace_id = context.debug.sentryTraceId()
const trace_id = context.debug.sentryTraceId()
function addSentryTrace(content) {
return content.replace("</head>", '<meta name="sentry-trace" content="' + trace_id + '" /></head>')
}
@@ -18,7 +21,9 @@ const { obj2str, log } = require("../lib/utils")
if (url) {
// comment will be printed to html later
var comment = ""
let comment = ""
/** @type {Date} */ // @ts-ignore
context.ssrCacheValidUntil = null
url = url.split("?")[0]
comment += "url: " + url
@@ -31,7 +36,8 @@ const { obj2str, log } = require("../lib/utils")
}
// check if url is in cache
var cache =
/** @type {Ssr[]} */ // @ts-ignore
const cache =
!noCache &&
context.db.find("ssr", {
filter: {
@@ -40,6 +46,7 @@ const { obj2str, log } = require("../lib/utils")
})
if (cache && cache.length) {
// use cache
context.response.header("X-SSR-Cache", "true")
throw {
status: 200,
log: false,
@@ -48,84 +55,50 @@ const { obj2str, log } = require("../lib/utils")
}
// validate url
var status = 200
let status = 200
var pNorender = false
var pNotfound = false
let pNorender = false
let pNotfound = false
var pR = ssrValidatePath(url)
const pR = ssrValidatePath(url)
if (pR < 0) {
pNotfound = true
} else if (!pR) {
pNorender = true
}
var head = ""
var html = ""
var error = ""
let head = ""
let html = ""
let error = ""
comment += ", path: " + url
var cacheIt = false
let cacheIt = false
if (pNorender) {
html = "<!-- NO SSR RENDERING -->"
} else if (pNotfound) {
status = 404
html = "404 NOT FOUND"
} else {
// @ts-ignore
context.ssrCache = {}
// @ts-ignore
context.ssrRequest = ssrRequest
// try rendering, if error output plain html
try {
// @ts-ignore
context.ssrCache = {}
// @ts-ignore
context.ssrFetch = function (endpoint, options) {
var data
if (ssrAllowedAPIEndpoints.indexOf(endpoint) > -1) {
var _options = Object.assign({}, options)
if (_options.sort) _options.sort = [_options.sort]
try {
/*console.log(
"SSR",
endpoint,
JSON.stringify(_options)
)*/
var goSlice = context.db.find(endpoint, _options || {})
// need to deep copy, so shift and delete on pure js is possible
data = JSON.parse(JSON.stringify(goSlice))
} catch (e) {
console.log("ERROR", JSON.stringify(e))
data = []
}
} else {
console.log("SSR forbidden", endpoint)
data = []
}
var count = (data && data.length) || 0
if (options && count == options.limit) {
// read count from db
count = context.db.count(endpoint, _options || {})
}
var r = { data: data, count: count }
// @ts-ignore
context.ssrCache[obj2str({ endpoint: endpoint, options: options })] = r
return r
}
// include App.svelte and render it
// @ts-ignore
var app = require("../lib/app.server")
var rendered = app.default.render({
// console.log("####### RENDERING ", url)
const app = require("../lib/app.server")
const rendered = app.default.render({
url: url,
})
head = rendered.head
html = rendered.html
// add ssrCache to head
// add ssrCache to head, cache is built in ssr.js/apiRequest
head +=
"\n\n" +
"<script>window.__SSR_CACHE__ = " +
@@ -136,6 +109,7 @@ const { obj2str, log } = require("../lib/utils")
// status from webapp
// @ts-ignore
if (context.is404) {
// console.log("########## 404")
status = 404
} else {
cacheIt = true
@@ -149,7 +123,7 @@ const { obj2str, log } = require("../lib/utils")
}
// read html template and replace placeholders
var tpl = context.fs.readFile("templates/spa.html")
let tpl = context.fs.readFile("templates/spa.html")
tpl = tpl.replace("<!--HEAD-->", head)
tpl = tpl.replace("<!--HTML-->", html)
tpl = tpl.replace("<!--SSR.ERROR-->", error ? "<!--" + error + "-->" : "")
@@ -158,6 +132,7 @@ const { obj2str, log } = require("../lib/utils")
// save cache if adviced
if (cacheIt && !noCache) {
context.db.create("ssr", {
// context.debug.dump("ssr", {
path: url,
content: tpl,
})
@@ -171,7 +146,7 @@ const { obj2str, log } = require("../lib/utils")
}
} else {
// only admins are allowed to get without url parameter
var auth = context.user.auth()
const auth = context.user.auth()
if (!auth || auth.role !== 0) {
throw {
status: 403,

34
api/templates/spa.html Normal file
View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Fontis</title>
<base href="/" />
<link rel="stylesheet" href="/dist/index.css?t=__TIMESTAMP__" />
<link rel="apple-touch-icon" sizes="180x180" href="/media/favicon/apple-touch-icon.png" type="image/x-icon" />
<link rel="icon" type="image/png" sizes="32x32" href="/media/favicon/favicon-32x32.png" type="image/x-icon" />
<link rel="icon" type="image/png" sizes="16x16" href="/media/favicon/favicon-16x16.png" type="image/x-icon" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#343a40" />
<meta name="apple-mobile-web-app-title" content="Fontis" />
<meta name="application-name" content="Fontis" />
<meta name="msapplication-TileColor" content="#343a40" />
<meta name="theme-color" content="#ffffff" />
<script type="text/javascript" src="svg-loader.min.js" async></script>
<!--HEAD-->
<!--PRELOAD-->
</head>
<body>
<div id="appContainer"><!--HTML--></div>
<script type="module" src="/dist/index.mjs?t=__TIMESTAMP__"></script>
<script nomodule src="/dist/index.es5.js?t=__TIMESTAMP__"></script>
</body>
<!--SSR.ERROR-->
<!--SSR.COMMENT-->
</html>

View File

@@ -30,7 +30,7 @@ services:
- ./tmp/nonexistent:/nonexistent
- ./tmp/.npm:/.npm
working_dir: /data
command: sh -c "yarn install && API_BASE=http://tibiserver:8080/api/v1/_/${TIBI_NAMESPACE} yarn start"
command: sh -c "yarn install && API_BASE=http://tibiserver:8080/api/v1/_/${TIBI_NAMESPACE} yarn start${START_SCRIPT}"
expose:
- 3000
labels:

View File

@@ -1,3 +1,5 @@
const fs = require("fs")
const resolvePlugin = {
name: "resolvePlugin",
setup(build) {
@@ -68,7 +70,9 @@ const bsMiddleware = []
if (process.argv[2] == "start") {
const { createProxyMiddleware } = require("http-proxy-middleware")
const apiBase = process.env.API_BASE || "http://localhost:8080/api/v1/_/" + process.env.NAMESPACE
const dotEnv = fs.readFileSync(__dirname + "/.env", "utf8")
const TIBI_NAMESPACE = dotEnv.match(/TIBI_NAMESPACE=(.*)/)[1]
const apiBase = process.env.API_BASE || "http://localhost:8080/api/v1/_/" + TIBI_NAMESPACE
bsMiddleware.push(
createProxyMiddleware("/api", {
target: apiBase,
@@ -77,12 +81,39 @@ if (process.argv[2] == "start") {
logLevel: "debug",
})
)
// if SSR env variable is set
console.log(process.env, "=========================ENV")
if (process.env.SSR) {
// read api/config.yml.env and read SSR_TOKEN variable from it
const configEnv = fs.readFileSync(__dirname + "/api/config.yml.env", "utf8")
const SSR_TOKEN = configEnv.match(/SSR_TOKEN=(.*)/)[1]
// redirect all other requests to /api/ssr?token=owshwerNwoa&url=...
bsMiddleware.push(
createProxyMiddleware(
function (path, req) {
return !path.match(/\./)
},
{
target: apiBase,
changeOrigin: true,
logLevel: "debug",
pathRewrite: function (path, req) {
console.log(path)
return "/ssr?token=" + SSR_TOKEN + "&url=" + encodeURIComponent(path)
},
}
)
)
}
}
module.exports = {
sveltePlugin: sveltePlugin,
resolvePlugin: resolvePlugin,
options: options,
distDir,
watch: {
path: [__dirname + "/" + frontendDir + "/src/**/*"],
},
@@ -103,7 +134,6 @@ module.exports = {
}),
],
},
ghostMode: false,
open: false,
// logLevel: "debug",
},

View File

@@ -1,4 +1,3 @@
module.exports = config
const config = require("./esbuild.config.js")
const svelteConfig = require("./svelte.config")

View File

@@ -1,16 +1,19 @@
AddType application/javascript .mjs
#DirectoryIndex index.html spa.html
DirectoryIndex spa.html
#DirectoryIndex spa.html
# notwendig, da sonst über normale url spa.html aufgerufen wird, muss nur datei name sei, der nicht existiert
DirectoryIndex noindex
<ifModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^/?api/(.*)$ http://tibi-server:8080/api/v1/_/fontis/$1 [P,QSA,L]
RewriteRule ^/?api/(.*)$ http://tibi-server:8080/api/v1/_/fontis_v2/$1 [P,QSA,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule (.*) /spa.html [QSA,L]
# leitet initale request an backend und nicht an spa.html weiter
RewriteRule ^/?(.*)$ http://tibi-server:8080/api/v1/_/fontis_v2/ssr?token=owshwerNwoa&url=/$1 [P,QSA,L]
# standardmäßig wegen deeplink aus google notwendig, da sonst 404
#RewriteRule (.*) /spa.html [QSA,L]
</ifModule>

View File

@@ -1,3 +1,3 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill="#000" d="M10 14h30v2H10zM15 24h25v2H15zM20 34h20v2H20z"/>
<path fill="#343a40" d="M10 14h30v2H10zM15 24h25v2H15zM20 34h20v2H20z"/>
</svg>

Before

Width:  |  Height:  |  Size: 177 B

After

Width:  |  Height:  |  Size: 180 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -1,5 +1,5 @@
<svg width="68" height="68" viewBox="0 0 68 68" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="1" width="66" height="66" rx="33" fill="#000"/>
<rect x="1" y="1" width="66" height="66" rx="33" fill="#343a40"/>
<path d="M44.91 39.965 34 29.066 23.09 39.965l-1.055-1.055L34 26.934 45.965 38.91l-1.055 1.055z" fill="#fff"/>
<rect x="1" y="1" width="66" height="66" rx="33" stroke="#fff" stroke-width="2"/>
</svg>

Before

Width:  |  Height:  |  Size: 371 B

After

Width:  |  Height:  |  Size: 374 B

View File

@@ -1,3 +1,3 @@
<svg width="75" height="75" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M88 50c0 1.38-1.12 2.5-2.5 2.5H20.608l25.644 25.218a2.502 2.502 0 1 1-3.504 3.564L12.772 50.805a2.49 2.49 0 0 1-.758-2.07c.064-.6.342-1.16.786-1.57L42.748 16.718a2.5 2.5 0 1 1 3.504 3.564L20.608 47.5H85.5c1.38 0 2.5 1.12 2.5 2.5z" fill="#000" stroke="#000" stroke-width="10"/>
<path d="M88 50c0 1.38-1.12 2.5-2.5 2.5H20.608l25.644 25.218a2.502 2.502 0 1 1-3.504 3.564L12.772 50.805a2.49 2.49 0 0 1-.758-2.07c.064-.6.342-1.16.786-1.57L42.748 16.718a2.5 2.5 0 1 1 3.504 3.564L20.608 47.5H85.5c1.38 0 2.5 1.12 2.5 2.5z" fill="#5b6e98" stroke="#5b6e98" stroke-width="10"/>
</svg>

Before

Width:  |  Height:  |  Size: 395 B

After

Width:  |  Height:  |  Size: 401 B

View File

@@ -1,3 +1,3 @@
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M45.5 7.875h-6.125V5.25a.875.875 0 0 0-1.75 0v2.625h-19.25V5.25a.875.875 0 0 0-1.75 0v2.625H10.5A2.625 2.625 0 0 0 7.875 10.5v35a2.625 2.625 0 0 0 2.625 2.625h35a2.625 2.625 0 0 0 2.625-2.625v-35A2.625 2.625 0 0 0 45.5 7.875zm-35 1.75h6.125v2.625a.875.875 0 1 0 1.75 0V9.625h19.25v2.625a.875.875 0 1 0 1.75 0V9.625H45.5a.875.875 0 0 1 .875.875v7.875H9.625V10.5a.875.875 0 0 1 .875-.875zm35 36.75h-35a.875.875 0 0 1-.875-.875V20.125h36.75V45.5a.875.875 0 0 1-.875.875z" fill="#000"/>
<path d="M45.5 7.875h-6.125V5.25a.875.875 0 0 0-1.75 0v2.625h-19.25V5.25a.875.875 0 0 0-1.75 0v2.625H10.5A2.625 2.625 0 0 0 7.875 10.5v35a2.625 2.625 0 0 0 2.625 2.625h35a2.625 2.625 0 0 0 2.625-2.625v-35A2.625 2.625 0 0 0 45.5 7.875zm-35 1.75h6.125v2.625a.875.875 0 1 0 1.75 0V9.625h19.25v2.625a.875.875 0 1 0 1.75 0V9.625H45.5a.875.875 0 0 1 .875.875v7.875H9.625V10.5a.875.875 0 0 1 .875-.875zm35 36.75h-35a.875.875 0 0 1-.875-.875V20.125h36.75V45.5a.875.875 0 0 1-.875.875z" fill="#343a40"/>
</svg>

Before

Width:  |  Height:  |  Size: 599 B

After

Width:  |  Height:  |  Size: 602 B

View File

@@ -1,6 +1,6 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#kvekca80oa)">
<path d="m30.797 7.297 1.406 1.406L16.5 24.406.797 8.703l1.406-1.406L16.5 21.594 30.797 7.297z" fill="#000"/>
<path d="m30.797 7.297 1.406 1.406L16.5 24.406.797 8.703l1.406-1.406L16.5 21.594 30.797 7.297z" fill="#343a40"/>
</g>
<defs>
<clipPath id="kvekca80oa">

Before

Width:  |  Height:  |  Size: 421 B

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#343a40</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 981 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1330 5856 l0 -253 108 -12 c201 -23 358 -70 529 -160 387 -204 661
-568 755 -1004 30 -139 32 -429 4 -557 -71 -329 -211 -582 -446 -803 -231
-218 -558 -362 -868 -384 l-82 -6 2 -260 3 -260 90 2 c111 3 260 24 385 56
658 166 1189 660 1399 1301 91 278 119 553 86 839 -109 932 -852 1656 -1791
1745 -60 5 -124 10 -141 10 l-33 0 0 -254z"/>
<path d="M5500 6080 c-786 -59 -1476 -596 -1726 -1344 -304 -914 93 -1910 946
-2369 235 -127 484 -200 765 -227 61 -5 122 -10 138 -10 l27 0 -2 262 -3 263
-85 7 c-236 18 -473 103 -690 246 -119 78 -308 270 -393 398 -108 163 -175
323 -219 522 -31 140 -31 434 0 574 87 393 301 712 626 929 189 127 432 217
632 234 38 3 84 8 102 11 l32 5 -2 252 c-3 244 -4 252 -23 253 -11 1 -67 -2
-125 -6z"/>
<path d="M2090 4859 c-846 -81 -1553 -699 -1745 -1524 -32 -141 -46 -271 -46
-440 -1 -332 64 -604 212 -896 323 -635 957 -1046 1672 -1082 l87 -5 0 264 0
264 -47 0 c-69 0 -215 25 -318 55 -389 113 -723 395 -900 760 -233 479 -196
1013 101 1463 78 118 271 310 393 391 211 140 447 223 704 246 l67 7 0 254 0
254 -47 -1 c-27 -1 -86 -5 -133 -10z"/>
<path d="M4730 4593 l0 -253 23 -4 c12 -3 57 -8 100 -11 44 -3 130 -19 192
-35 522 -131 932 -547 1060 -1073 121 -495 -25 -1008 -390 -1372 -247 -248
-563 -397 -895 -423 l-85 -7 -3 -264 -2 -264 102 8 c649 45 1195 360 1547 891
214 322 322 686 322 1079 0 327 -65 604 -209 890 -100 199 -220 361 -389 526
-120 117 -171 159 -293 239 -297 195 -653 310 -997 322 l-83 3 0 -252z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,19 @@
{
"name": "Fontis",
"short_name": "Fontis",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

View File

@@ -1,4 +1,4 @@
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.208 47.65a.875.875 0 0 0 .875-.874V11.958a.875.875 0 0 1 .875-.875h20.125v11.375a.875.875 0 0 0 .875.875h11.375v23.334a.875.875 0 0 0 1.75 0V22.458a.875.875 0 0 0-.256-.619L33.577 9.59a.875.875 0 0 0-.619-.256h-21a2.625 2.625 0 0 0-2.625 2.625v34.818c0 .232.256.618.256.618s.387.257.619.257zm23.625-35.33 9.262 9.263h-9.262V12.32z" fill="#000"/>
<path d="M10.102 48.523a2.625 2.625 0 0 1-.769-1.856h1.75a.875.875 0 0 0 .875.875h31.5a.875.875 0 0 0 .875-.875h1.75a2.625 2.625 0 0 1-2.625 2.625h-31.5a2.625 2.625 0 0 1-1.856-.77z" fill="#000"/>
<path d="M10.208 47.65a.875.875 0 0 0 .875-.874V11.958a.875.875 0 0 1 .875-.875h20.125v11.375a.875.875 0 0 0 .875.875h11.375v23.334a.875.875 0 0 0 1.75 0V22.458a.875.875 0 0 0-.256-.619L33.577 9.59a.875.875 0 0 0-.619-.256h-21a2.625 2.625 0 0 0-2.625 2.625v34.818c0 .232.256.618.256.618s.387.257.619.257zm23.625-35.33 9.262 9.263h-9.262V12.32z" fill="#343a40"/>
<path d="M10.102 48.523a2.625 2.625 0 0 1-.769-1.856h1.75a.875.875 0 0 0 .875.875h31.5a.875.875 0 0 0 .875-.875h1.75a2.625 2.625 0 0 1-2.625 2.625h-31.5a2.625 2.625 0 0 1-1.856-.77z" fill="#343a40"/>
</svg>

Before

Width:  |  Height:  |  Size: 667 B

After

Width:  |  Height:  |  Size: 673 B

View File

@@ -1,6 +1,6 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#3k3057tu3a)">
<path d="M31.047 23.953 16.5 9.422 1.953 23.953.547 22.547 16.5 6.578l15.953 15.969-1.406 1.406z" fill="#000"/>
<path d="M31.047 23.953 16.5 9.422 1.953 23.953.547 22.547 16.5 6.578l15.953 15.969-1.406 1.406z" fill="#343a40"/>
</g>
<defs>
<clipPath id="3k3057tu3a">

Before

Width:  |  Height:  |  Size: 423 B

After

Width:  |  Height:  |  Size: 426 B

View File

@@ -6,12 +6,19 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Fontis</title>
<base href="/" />
<link rel="stylesheet" href="/dist/index.css?t=__TIMESTAMP__" />
<script
type="text/javascript"
src="https://unpkg.com/external-svg-loader@latest/svg-loader.min.js"
async
></script>
<link rel="apple-touch-icon" sizes="180x180" href="/media/favicon/apple-touch-icon.png" type="image/x-icon" />
<link rel="icon" type="image/png" sizes="32x32" href="/media/favicon/favicon-32x32.png" type="image/x-icon" />
<link rel="icon" type="image/png" sizes="16x16" href="/media/favicon/favicon-16x16.png" type="image/x-icon" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#343a40" />
<meta name="apple-mobile-web-app-title" content="Fontis" />
<meta name="application-name" content="Fontis" />
<meta name="msapplication-TileColor" content="#343a40" />
<meta name="theme-color" content="#ffffff" />
<script type="text/javascript" src="svg-loader.min.js" async></script>
<!--HEAD-->
<!--PRELOAD-->

View File

@@ -4,13 +4,26 @@
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"
import "external-svg-loader"
export let url = ""
if (url) {
@@ -28,11 +41,24 @@
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.active) return
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,15 +67,37 @@
$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()
getModules()
console.log("TESTR")
let activeMenu = false
$: {
if (activeMenu) {
document.body.classList.add("overflow")
} else {
document.body.classList.remove("overflow")
if (typeof window !== "undefined") {
if (activeMenu) {
document.body.classList.add("overflow")
} else {
document.body.classList.remove("overflow")
}
}
}
</script>

View File

@@ -1,121 +1,12 @@
import { apiBaseURL } from "./config"
const _f = function (url, options): Promise<Response> {
if (typeof XMLHttpRequest === "undefined") {
return Promise.resolve(null)
}
options = options || {}
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest()
const keys = []
const all = []
const headers = {}
const response = (): Response => ({
ok: ((request.status / 100) | 0) == 2, // 200-299
statusText: request.statusText,
status: request.status,
url: request.responseURL,
text: () => Promise.resolve(request.responseText),
json: () => Promise.resolve(request.responseText).then(JSON.parse),
blob: () => Promise.resolve(new Blob([request.response])),
clone: response,
headers: {
// @ts-ignore
keys: () => keys,
// @ts-ignore
entries: () => all,
get: (n) => headers[n.toLowerCase()],
has: (n) => n.toLowerCase() in headers,
},
})
request.open(options.method || "get", url, true)
request.onload = () => {
request
.getAllResponseHeaders()
// @ts-ignore
.replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm, (m, key, value) => {
keys.push((key = key.toLowerCase()))
all.push([key, value])
headers[key] = headers[key] ? `${headers[key]},${value}` : value
})
resolve(response())
}
request.onerror = reject
request.withCredentials = options.credentials == "include"
for (const i in options.headers) {
request.setRequestHeader(i, options.headers[i])
}
request.send(options.body || null)
})
}
const _fetch = typeof fetch === "undefined" ? (typeof window === "undefined" ? _f : window.fetch || _f) : fetch
import { apiRequest } from "../../api/hooks/lib/ssr"
export const api = async <T>(
endpoint: string,
options?: {
method?: string
filter?: any
sort?: string
limit?: number
offset?: number
projection?: string
headers?: {
[key: string]: string
}
params?: {
[key: string]: string
}
},
options?: ApiOptions,
body?: any
): Promise<{ data: T; count: number } | any> => {
if (typeof window === "undefined") {
// ssr
// @ts-ignore
return context.ssrFetch(endpoint, options)
}
let method = options?.method || "GET"
let query = "&count=1"
if (options?.filter) query += "&filter=" + encodeURIComponent(JSON.stringify(options.filter))
if (options?.sort) query += "&sort=" + options.sort + "&sort=_id"
if (options?.limit) query += "&limit=" + options.limit
if (options?.offset) query += "&offset=" + options.offset
if (options?.projection) query += "&projection=" + options.projection
if (options?.params) {
Object.keys(options.params).forEach((p) => {
query += "&" + p + "=" + encodeURIComponent(options.params[p])
})
}
let headers: any = {
"Content-Type": "application/json",
}
if (options?.headers) headers = { ...headers, ...options.headers }
let url = apiBaseURL + endpoint + (query ? "?" + query : "")
const requestOptions: any = {
method,
mode: "cors",
headers,
}
if (method === "POST" || method === "PUT") {
requestOptions.body = JSON.stringify(body)
}
let response = await _fetch(url, requestOptions)
if (response.status == 409 || response.status == 401) return response
let data = (await response?.json()) || null
let data = await apiRequest(endpoint, options, body)
// @ts-ignore
return { data }
console.log(data, "data")
return data
}

View File

@@ -1,5 +1,6 @@
import configClient from "../../api/hooks/config-client"
export const apiBaseURL = "/api/"
export const baseURL = "https://www.fontis.de"
export const release = configClient.release
console.log("Release: ", release)

View File

@@ -11,7 +11,7 @@ const publishLocation = (_p?: string) => {
if (_h) _h = "#" + _h
const parts2 = _p.split("?")
_p = parts2.shift()
_p = parts2.shift()
_s = parts2.join()
if (_s) _s = "?" + _s
}

View File

@@ -108,7 +108,7 @@ select {
top: 0px;
bottom: 0px;
width: 0px;
background: #000000;
background: #343a40;
transition: width 0.5s ease-in;
}
.fill:hover:after,
@@ -143,7 +143,7 @@ swiper-slide {
z-index: 10000;
left: 0px;
bottom: -10px;
background: #000000;
background: @signal-color;
height: 5px;
width: 0;
animation: underlineEffect 15s linear forwards;

View File

@@ -1,7 +1,8 @@
@bg-color: #fff;
@bg-color-secondary: #000;
@font-color: #000;
@bg-color-secondary: #343a40;
@font-color: #343a40;
@font-color-secondary: #fff;
@signal-color: #5b6e98;
@desktop_large:~ "only screen and (min-width: 1200px)";
@desktop:~ "only screen and (min-width: 1024px)";

View File

@@ -4,9 +4,10 @@
let nextpage = $navigation?.pages[0]
$: nextpage = $navigation?.pages[0]
function getNextPage(pages) {
console.log(pages, "pages")
if (location.pathname == "/" || location.pathname == "") return
if (location.pathname == "/" || location.pathname == "") {
$navigation?.pages?.length > 1 ? (nextpage = $navigation?.pages[1]) : (nextpage = $navigation?.pages[0])
return
}
let currPage = pages.find(
(page) => Object.values($pages)?.find((o) => o.id == page.page)?.path == location.pathname
)
@@ -21,32 +22,36 @@
nextpage = pages[nextIndex]
}
let blackBg = false
setInterval(() => {
if (location.pathname == "/") {
blackBg = true
} else {
blackBg = false
}
if (typeof window !== "undefined") {
setInterval(() => {
if (location.pathname == "/") {
blackBg = true
} else {
blackBg = false
}
getNextPage($navigation.pages)
getNextPage($navigation.pages)
if (location.pathname.split("/").filter((s) => s).length >= 2) {
showNext = false
} else {
showNext = true
}
}, 1000)
if (location.pathname.split("/").filter((s) => s).length >= 2) {
showNext = false
} else {
showNext = true
}
}, 1000)
}
let showNext = true
$: {
if ($rerender) {
if (location.pathname != "/") {
getNextPage($navigation.pages)
if (typeof window !== "undefined") {
if ($rerender) {
if (location.pathname != "/") {
getNextPage($navigation.pages)
}
}
if (location.pathname.split("/").filter((s) => s).length >= 2) {
showNext = false
} else {
showNext = true
}
}
if (location.pathname.split("/").filter((s) => s).length >= 2) {
showNext = false
} else {
showNext = true
}
}
</script>

View File

@@ -8,57 +8,75 @@
<div class="menu" class:active="{active}">
<div class="menu-container">
<Header bind:active="{active}" opened="{true}" />
<div class="menu-content">
<nav class="menu-content">
{#if $navigation}
<div class="container">
<div class="inner-container">
<div class="pages">
<ul class="pages">
{#each $navigation.pages as page}
{#if Object.values($pages)?.find((o) => o.id == page.page)?.path !== "/"}
<button
class="page underline"
on:click="{() => {
active = false
$rerender = $rerender + 1
navigate(Object.values($pages)?.find((o) => o.id == page.page)?.path || '/')
}}"
>
{page.name}
</button>{/if}
<li>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-missing-attribute -->
<a
style="text-decoration: none;"
class="page underline"
on:click="{() => {
active = false
$rerender = $rerender + 1
navigate(
Object.values($pages)?.find((o) => o.id == page.page)?.path || '/'
)
}}"
>
{page.name}
</a>
</li>
{/if}
{/each}
</div>
</ul>
<div class="footer-infos">
<div class="upper">
<button
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-missing-attribute -->
<a
style="text-decoration: none;"
class="underline"
on:click="{() => {
active = false
$rerender = $rerender + 1
navigate('/datenschutz')
}}">Datenschutz</button
}}">Datenschutz</a
>
<button
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-missing-attribute -->
<a
style="text-decoration: none;"
class="underline"
on:click="{() => {
active = false
$rerender = $rerender + 1
navigate('/impressum')
}}">Impressum</button
}}">Impressum</a
>
</div>
<!-- svelte-ignore a11y-missing-attribute -->
<div class="lower">
<button>+49 (0) 711 655 700-0</button>
<button>
<!-- svelte-ignore a11y-missing-attribute -->
<a>+49 (0) 711 655 700-0</a>
<a>
<a href="mailto:info@fontis.de" style="text-decoration: none;" class="button">
info@fontis.de
</a>
</button>
</a>
</div>
</div>
</div>
</div>
{/if}
</div>
</nav>
</div>
</div>
@@ -155,12 +173,10 @@
}
gap: 20px;
}
button {
a {
color: @font-color-secondary;
a {
color: @font-color-secondary;
font-weight: normal;
}
font-weight: normal;
text-decoration: initial;
}
@media @desktop {
.lower {

View File

@@ -5,6 +5,8 @@
import TextLink from "../widgets/textLink.svelte"
let teasers = Object.values($pages)?.map((page) => ({ teaser: page.teaser, path: page.path }))
$: teasers = Object.values($pages)?.map((page) => ({ teaser: page.teaser, path: page.path }))
console.log("teasers:", teasers)
register(false)
let swiper
@@ -25,25 +27,29 @@
Object.assign(swiper, params)
swiper.initialize()
// Add the 'active' class to the h1 of the first slide
const firstSlideH1 = document.querySelector(".swiper-slide-active .titles h1")
if (firstSlideH1) {
firstSlideH1.classList.add("active")
if (typeof window !== "undefined") {
// Add the 'active' class to the h1 of the first slide
const firstSlideH1 = document.querySelector(".swiper-slide-active .titles h1")
if (firstSlideH1) {
firstSlideH1.classList.add("active")
}
}
}
})
function handleSlideChange() {
document.querySelectorAll(".titles h1").forEach((h1) => {
h1.classList.remove("active")
})
if (typeof window !== "undefined") {
document.querySelectorAll(".titles h1").forEach((h1) => {
h1.classList.remove("active")
})
setTimeout(() => {
const activeSlideUnderline = document.querySelector(".swiper-slide-active .titles h1")
if (activeSlideUnderline) {
activeSlideUnderline.classList.add("active")
}
}, 600)
setTimeout(() => {
const activeSlideUnderline = document.querySelector(".swiper-slide-active .titles h1")
if (activeSlideUnderline) {
activeSlideUnderline.classList.add("active")
}
}, 600)
}
}
let teaser = teasers[0]
</script>
@@ -133,6 +139,7 @@
line-height: 1;
font-weight: 500;
position: relative;
color: @signal-color;
}
h2 {

View File

@@ -0,0 +1,37 @@
<script lang="ts">
import { jobOffers, modules, team } from "../../store"
import Worldcard from "../widgets/Worldcard/worldcard.svelte"
import Boxlist from "../widgets/boxlist.svelte"
import ExtendableBox from "../widgets/extendableBox.svelte"
import IconCycleBox from "../widgets/iconCycleBox.svelte"
import IconCycleCircle from "../widgets/iconCycleCircle.svelte"
import PageLinkBlocks from "../widgets/pageLinkBlocks.svelte"
import Persons from "../widgets/persons.svelte"
export let col: { contentType: "moduleImport"; moduleImport: string }
export let pageId: string
let module = $modules[col.moduleImport] || {}
$: module = $modules[col.moduleImport] || {}
$: console.log(
"peron",
$team,
Object.values($team).filter((p) => p.personType == "employee")
)
</script>
{#if module.type == "iconCycleCircle"}
<IconCycleCircle iconCycleCircle="{module.iconCycleCircle}" pageId="{pageId}" />
{:else if module.type == "iconCycleSquare"}
<IconCycleBox iconCycleSquare="{module.iconCycleSquare}" pageId="{pageId}" />
{:else if module.type == "worldCard"}
<Worldcard worldCard="{module.worldCard}" pageId="{pageId}" />
{:else if module.type == "chefTeam"}
<Persons persons="{Object.values($team).filter((p) => p.personType == 'chef')}" pageId="{pageId}" />
{:else if module.type == "employeeTeam"}
<Boxlist persons="{Object.values($team).filter((p) => p.personType == 'employee')}" />
{:else if module.type == "jobOffer"}
<ExtendableBox pages="{Object.values($jobOffers)}" />
{:else if module.type == "jobOfferLink"}
<PageLinkBlocks pages="{Object.values($jobOffers)}" pageReference="{module.jobOfferPage}" />
{/if}

View File

@@ -14,45 +14,40 @@
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
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 ""
export let i: number
export let page: Page
export let personPage: boolean
if (typeof window !== "undefined") {
window.addEventListener("popstate", function (event) {
$rerender = $rerender + 1
})
}
let nestedPath = checkNestedPath()
window.addEventListener("popstate", function (event) {
$rerender = $rerender + 1
})
</script>
{#if Object.keys(row).length}
{#if row.topTitle}
<h3 class="{row.topTitleUpperCase ? 'hph3' : 'nmh3'}" class:red="{row.topTitleRed}">
<h3 class="{row.topTitleUpperCase ? 'hph3' : 'nmh3'}">
{row.topTitle}
</h3>
{/if}
{#if nestedPath}
{#if personPage}
<div class="top-header" style="display: flex; width: 100%; justify-content: space-between;">
<h3
style="cursor: pointer; display: flex; align-items: center; gap: 10px; line-height: 1.4;"
on:keydown
on:click="{() => {
navigate('/' + location.pathname.split('/').at(1))
$rerender = $rerender + 1
navigate(nestedPath)
}}"
>
<img src="/media/arrow-l.svg" alt="arrow" /> Zurück zur Übersicht
@@ -61,16 +56,21 @@
style="cursor: pointer; display: flex; align-items: center; gap: 10px; line-height: 1.4;"
on:keydown
on:click="{() => {
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)
$rerender = $rerender + 1
navigate(row?.nextPage || nestedPath)
}}"
>
Zum nächsten Profil <img src="/media/arrowr.svg" alt="arrow" />
</h3>
</div>
{/if}
{#if row.pageTitle}
<h1>{row.pageTitle}</h1>
{#if page.pageTitle && i == 0}
<h1>{page.pageTitle}</h1>
{/if}
{#if row.title}
<h2 class="">{row.title}</h2>
@@ -83,10 +83,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}
<div class="col" class:dominant="{col.contentType == 'iconCycleCircle'}">
<div
class="col"
class:dominant="{col.contentType == 'moduleImport' &&
$modules?.[col.moduleImport]?.type == 'iconCycleCircle'}"
>
{#if col?.contentType == "text"}
<Text text="{col?.text}" />
{:else if col?.contentType == "textLink"}
@@ -95,12 +101,12 @@
path="{Object.values($pages)?.find((o) => o.id == col.textLink.link)?.path || '/'}"
bright="{bright}"
/>
{:else if col.contentType == "moduleImport"}
<Module col="{col}" pageId="{pageId}" />
{:else if col.contentType == "image"}
<Image image="{col?.image}" col="{col}" pageId="{pageId}" />
{:else if col.contentType == "iconBlocks"}
<IconBlock pageId="{pageId}" col="{col}" />
{:else if col.contentType == "pageLinkBlocks"}
<PageLinkBlocks col="{col}" />
{:else if col.contentType == "networkEvents"}
<Events col="{col}" pageId="{pageId}" />
{:else if col.contentType == "publications"}
@@ -111,18 +117,6 @@
<InfoBoard col="{col}" pageId="{pageId}" />
{:else if col.contentType == "nestedCard"}
<NestedCard col="{col}" />
{:else if col.contentType == "boxlist"}
<Boxlist col="{col}" />
{:else if col.contentType == "extendableBoxes"}
<ExtendableBox col="{col}" />
{:else if col.contentType == "personPreview"}
<Persons col="{col}" pageId="{pageId}" />
{:else if col.contentType == "iconCycleCircle"}
<IconCycleCircle col="{col}" pageId="{pageId}" />
{:else if col.contentType == "iconCycleSquare"}
<IconCycleBox col="{col}" pageId="{pageId}" />
{:else if col.contentType == "worldCard"}
<WorldCard col="{col}" pageId="{pageId}" />
{/if}
</div>
{/each}
@@ -148,6 +142,7 @@
h1 {
font-weight: 500;
font-size: 2rem;
color: @signal-color;
}
.top-header {
img {

View File

@@ -1,46 +1,81 @@
<script lang="ts">
import { pages, scrollToRowNr } from "../../store"
import { mediaLibrary, pages, scrollToRowNr, team } from "../../store"
import Homepage from "./Homepage.svelte"
import Pagebuilder from "./Pagebuilder.svelte"
import { apiBaseURL } from "../../../config"
import { apiBaseURL, baseURL } from "../../../config"
import { onMount } from "svelte"
export let path
export let path: string
export let homepage = false
export let image: FileField
let page: Page
let personPage = false
function initPage() {
page = $pages[path]
if ($pages[path]) {
page = $pages[path]
} else if (
Object.values($team)
.map((p) => p.path == path)
.includes(true)
) {
page = Object.values($team).find((p) => p.path == path)
personPage = true
}
}
onMount(() => {
if ($scrollToRowNr !== -1) {
console.log("test321-", $scrollToRowNr)
if (!$scrollToRowNr) {
$scrollToRowNr = -1
return
}
let element = document.getElementById("row-" + $scrollToRowNr)
console.log(element)
if (!element) {
$scrollToRowNr = -1
return
}
if (typeof window !== "undefined") {
if ($scrollToRowNr !== -1) {
if (!$scrollToRowNr) {
$scrollToRowNr = -1
return
}
let element = document.getElementById("row-" + $scrollToRowNr)
if (!element) {
$scrollToRowNr = -1
return
}
element?.scrollIntoView({
behavior: "smooth",
})
$scrollToRowNr = -1
element?.scrollIntoView({
behavior: "smooth",
})
$scrollToRowNr = -1
}
}
})
$: {
if (Object.keys($pages).length) {
if (Object.keys($pages).length || Object.keys($team).length) {
initPage()
}
}
</script>
<svelte:head>
{#key page}
<!-- Title -->
{#if page?.pageTitle}
<title>{page.pageTitle}</title>
{:else if page?.meta?.title}
<title>{page.meta.title}</title>
{/if}
<!-- Description -->
{#if page?.meta?.description}
<meta name="description" content="{page.meta.description}" />
{/if}
<!-- Keywords -->
{#if page?.meta?.keywords}
<meta name="keywords" content="{page.meta.keywords}" />
{/if}
{#if page?.active === false}
<meta name="robots" content="noindex" />
{/if}
<link rel="canonical" href="{baseURL + page?.path}" />
{/key}
</svelte:head>
<div class="rows" class:HP="{path == '/'}">
{#if page}
{#if path == "/"}<Homepage />{/if}
@@ -50,22 +85,29 @@
id="row-{i}"
style="{path == '/' && i == page.rows.length - 1
? 'padding-bottom: 300px; margin-bottom: -40px;'
: ''} {row.row.noBottomMargin ? 'margin-bottom: 0px; padding-bottom: 0px;' : ''} {row.row
.noTopMargin
: ''} {row.noBottomMargin ? 'margin-bottom: 0px; padding-bottom: 0px;' : ''} {row.noTopMargin
? 'margin-top: 0px; padding-top: 0px;'
: ''}"
>
{#if row.row.backgroundImage}
{#if row.backgroundImage && $mediaLibrary[row.backgroundImage]}
<div class="background-image">
<img src="{`${apiBaseURL}page/${page.id}/${row.row.backgroundImage?.src}`}" alt="img" />
<img
src="{`${apiBaseURL}medialib/${row?.backgroundImage}/${
$mediaLibrary?.[row?.backgroundImage]?.file?.src
}`}"
alt="img"
/>
</div>
{/if}
<div class="content" class:bright="{row.row.backgroundImage}">
<div class="content" class:bright="{row.backgroundImage}">
<Pagebuilder
personPage="{personPage}"
isHP="{path == '/'}"
row="{row.row}"
i="{i}"
row="{row}"
page="{page}"
pageId="{page.id}"
bright="{!!row.row.backgroundImage}"
bright="{!!row.backgroundImage}"
/>
</div>
</div>

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { apiBaseURL } from "../../../../config"
import { mediaLibrary } from "../../../store"
export let card: Card
export let properties: string[][]
@@ -23,7 +24,11 @@
</script>
<div class="card">
<img src="{apiBaseURL}page/{pageId}/{card.image.src}" alt="card" />
<img
src="{apiBaseURL}medialib/{card.image}/{$mediaLibrary?.[card?.image]?.file?.src}"
alt="{$mediaLibrary[card?.image]?.alt || ''}"
title="{$mediaLibrary[card?.image]?.title || ''}"
/>
<div class="content">
<div
@@ -159,8 +164,8 @@
height: 1.8vw;
max-height: 25px;
border-radius: 15px;
border: 2px solid #4f4f4f;
color: #4f4f4f;
border: 2px solid @signal-color;
color: @signal-color;
background-color: @bg-color-secondary;
display: flex;
justify-content: center;

View File

@@ -2,7 +2,7 @@
import Card from "./card.svelte"
import Selectbox from "./selectbox.svelte"
export let col: Column
export let worldCard: WorldCard
export let pageId: string
let availableProperties = [
@@ -38,9 +38,9 @@
<div style="display: flex; flex-direction: column; width: 100%; align-items: center;">
<div class="worldcard">
<div class="worldcard">
{#each col.worldCard.row as row}
{#each worldCard?.row as row}
<div class="wc-row">
{#each row.cards as card}
{#each row?.cards as card}
<Card
card="{card}"
properties="{availableProperties}"

View File

@@ -1,20 +1,20 @@
<script lang="ts">
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)
const nameA = a?.split(" ")[0] // Extracts the first name from "First Last"
const nameB = b?.split(" ")[0]
console.log("nameA", nameA, "nameB", nameB, "comp", nameA?.localeCompare(nameB))
return nameA?.localeCompare(nameB)
}
col.boxList.boxes.sort(sortByFirstName) // Sorts the array in place
boxes = boxes.sort(sortByFirstName)
$: console.log(boxes, "boxes", persons)
</script>
<div class="boxList">
{#each col.boxList.boxes as name}
{#each boxes as name}
<div class="box">
{name.name}
{name}
</div>
{/each}
</div>
@@ -28,7 +28,7 @@
gap: 20px;
.box {
padding: 5px 10px;
background-color: @bg-color-secondary;
background-color: @signal-color;
color: @font-color-secondary;
font-weight: bold;
}

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { apiBaseURL } from "../../../config"
import { mediaLibrary } from "../../store"
export let col: Column
export let pageId: string
@@ -44,7 +45,12 @@
</div>
<div class="details">
<em>{nE.title}</em>
<a href="{apiBaseURL}page/{pageId}/{nE.file.src}" style="text-decoration: none;" download="{apiBaseURL}page/{pageId}/{nE.file.src}">
<a
href="{apiBaseURL}medialib/{nE.file}/{$mediaLibrary?.[nE?.file]?.file?.src}"
style="text-decoration: none;"
download="{apiBaseURL}medialib/{nE.file}/{$mediaLibrary?.[nE?.file]?.file?.src}"
>
<button class="more">mehr</button></a
>
</div>

View File

@@ -2,43 +2,42 @@
import { onMount } from "svelte"
import { openExtendableNr } from "../../store"
export let col: Column
export let opened = -1
export let pages: Page[]
export let opened = ""
let jobOffers = pages.map((p) => p.jobOffer)
onMount(() => {
if (!isNaN($openExtendableNr) && $openExtendableNr !== -1) {
opened = $openExtendableNr
$openExtendableNr = -1
if (typeof window !== "undefined") {
opened = location.search.split("=").at(-1)
}
})
</script>
<div class="boxes">
{#each col.extendableBoxes as box, i}
<div class="box" class:opened="{i == opened}">
{#each jobOffers as box, i}
<div class="box" class:opened="{pages[i].id == opened}">
<div
class="upper"
on:keydown
on:click="{() => {
if (opened == i) opened = -1
else opened = i
if (opened == pages[i].id) opened = ''
else opened = pages[i].id
}}"
>
<h4>
{box.title}
</h4>
<div>
{#if i !== opened}<img src="/media/down.svg" alt="arrow" />{:else}<img
{#if pages[i].id !== opened}<img src="/media/down.svg" alt="arrow" />{:else}<img
src="/media/up.svg"
alt="arrow"
/>{/if}
</div>
</div>
<div class="content" class:closed="{i !== opened}">
<div class="content" class:closed="{pages[i].id !== opened}">
{@html box.text}
{#if box.emailButton}
<a
href="mailto:info@fontis.de?subject={box.emailSubject || 'Bewerbung'}"
href="mailto:bewerbung@fontis.de?subject={box.emailSubject || 'Bewerbung'}"
style="text-decoration: none;"
class="button"
>
@@ -54,9 +53,9 @@
@import "../../assets/css/main.less";
button {
margin-top: 20px;
background-color: @bg-color-secondary;
background-color: @signal-color;
color: @font-color-secondary;
border: 2px solid @bg-color-secondary;
border: 2px solid @signal-color;
padding: 2px 15px;
font-weight: bold;
}

View File

@@ -1,15 +1,20 @@
<script lang="ts">
import { apiBaseURL } from "../../../config"
import { mediaLibrary } from "../../store"
export let pageId: string
export let col: Column
console.log("icons:", col.iconBlocks, $mediaLibrary, $mediaLibrary[col.iconBlocks[0].icon])
</script>
<div class="iconBlock">
{#each col.iconBlocks as icon}
<div class="icon">
<img src="{`${apiBaseURL}page/${pageId}/${icon.icon?.src}`}" alt="img" />
<img
alt="{$mediaLibrary[icon.icon]?.alt || ''}"
title="{$mediaLibrary[icon.icon]?.title || ''}"
src="{`${apiBaseURL}medialib/${icon.icon}/${$mediaLibrary?.[icon?.icon]?.file?.src}`}"
/>
<div class="text">
<em>{icon.bigText}</em>
<p>{icon.smallText}</p>

View File

@@ -1,24 +1,26 @@
<script lang="ts">
import { apiBaseURL } from "../../../config"
export let col: Column
import { mediaLibrary } from "../../store"
export let iconCycleSquare: IconCycleSquare
export let pageId: string
console.log("YEY")
let active = -1
setInterval(() => {
active += 1
if (active == col.iconCycleSquare.boxes.length) active = 0
}, 1250)
if (typeof window !== "undefined") {
setInterval(() => {
active += 1
if (active == iconCycleSquare.boxes.length) active = 0
}, 1250)
}
</script>
<div class="iconCycleSquares">
{#each col?.iconCycleSquare?.boxes as box, i}
{#each iconCycleSquare?.boxes as box, i}
<div class="box" id="box{i}" class:active="{i == active}">
<div class="content">
<div class="icon">
<svg
stroke="{i == active ? 'black' : 'white'}"
fill="{i == active ? 'black' : 'white'}"
data-src="{apiBaseURL}page/{pageId}/{box.icon?.src}"></svg>
stroke="{i == active ? '#5b6e98' : 'white'}"
data-src="{apiBaseURL}medialib/{box?.icon}/{$mediaLibrary?.[box?.icon]?.file?.src}"></svg>
</div>
<div class="text">
{box.text}
@@ -42,8 +44,8 @@
font-size: 1rem;
}
.box {
border: 4px solid @bg-color-secondary;
background-color: @bg-color-secondary;
border: 4px solid @signal-color;
background-color: @signal-color;
display: flex;
align-items: center;
justify-content: center;
@@ -51,7 +53,7 @@
padding: 10px;
&.active {
background-color: @bg-color;
color: @font-color;
color: #5b6e98;
}
aspect-ratio: 1/1;
width: calc((100% / 2) - 10px);

View File

@@ -1,11 +1,13 @@
<script lang="ts">
import { apiBaseURL } from "../../../config"
import { onMount } from "svelte"
import { mediaLibrary } from "../../store"
export let iconCycleCircle: IconCycleCircle
export let col: Column
export let pageId: string
let count = col.iconCycleCircle.boxes.length // The number of surrounding circles.
let count = iconCycleCircle.boxes.length // The number of surrounding circles.
let angleStep = 360 / count
let radius = 310
@@ -24,17 +26,18 @@
circles = circles
})
let focused = -1
setInterval(() => {
focused += 1
if (focused == count) focused = 0
const svgObject = document.getElementById("mySvgObject" + focused)
}, 1000)
if (typeof window !== "undefined") {
setInterval(() => {
focused += 1
if (focused == count) focused = 0
}, 1000)
}
</script>
<div class="container">
<div class="main-circle">
<div class="content">
{col.iconCycleCircle.innerText}
{iconCycleCircle.innerText}
</div>
{#each circles as { x, y, rotation }, i}
<div
@@ -50,12 +53,14 @@
<div class="icon">
<svg
id="mySvgObject{i}"
stroke="{i == focused ? 'white' : 'black'}"
fill="{i == focused ? 'white' : 'black'}"
data-src="{apiBaseURL}page/{pageId}/{col.iconCycleCircle?.boxes[i]?.icon?.src}"></svg>
stroke="{i == focused ? 'white' : '#5b6e98'}"
fill="{i == focused ? 'white' : '#5b6e98'}"
data-src="{apiBaseURL}medialib/{iconCycleCircle?.boxes[i]?.icon}/{$mediaLibrary[
iconCycleCircle?.boxes[i]?.icon
]?.file?.src}"></svg>
</div>
<div class="text" style="text-align: center;">
{@html col.iconCycleCircle?.boxes[i]?.text}
{@html iconCycleCircle?.boxes[i]?.text}
</div>
</div>
</div>
@@ -107,7 +112,7 @@
width: 180px;
height: 180px;
margin: auto;
background: rgb(0, 0, 0);
background: @signal-color;
border-radius: 50%;
& > .content {
font-weight: bold;
@@ -126,8 +131,8 @@
width: 180px;
overflow: hidden;
height: 180px;
background: rgba(255, 255, 255, 0);
border: 4px solid @bg-color-secondary;
background: @signal-color;
border: 4px solid @signal-color;
z-index: 100;
transform-origin: center;
border-radius: 50%;
@@ -178,7 +183,7 @@
&::before {
content: "";
position: absolute;
background: rgb(0, 0, 0);
background: @signal-color;
border-radius: 50%;
top: -50%;
left: 0;
@@ -190,13 +195,13 @@
&.focused {
background: @bg-color-secondary !important;
.number {
color: @font-color !important;
color: @signal-color !important;
}
.content {
color: @font-color-secondary !important;
}
.half {
background: @bg-color-secondary !important;
background: @signal-color !important;
&::before {
background: @bg-color !important;
}

View File

@@ -1,19 +1,28 @@
<script lang="ts">
import { apiBaseURL } from "../../../config"
export let image: FileField
import { mediaLibrary } from "../../store"
export let image: string
export let pageId: string
export let col: Column
export let col: { contentType: "image"; image: string; icons: { icon: string; link: string }[] }
</script>
<div class="image-container">
<img src="{`${apiBaseURL}page/${pageId}/${image?.src}`}" alt="img" />
<img
src="{`${apiBaseURL}medialib/${image}/${$mediaLibrary[image]?.file?.src}`}"
alt="{$mediaLibrary[image]?.alt || ''}"
title="{$mediaLibrary[image]?.title || ''}"
/>
</div>
{#if col && col.icons}
<div class="icons">
{#each col.icons as icon}
<div class="icon">
<a href="{icon.link}" style="text-decoration: none;" target="_blank">
<img src="{`${apiBaseURL}page/${pageId}/${icon.icon?.src}`}" alt="img" />
<img
src="{`${apiBaseURL}medialib/${icon.icon}/${$mediaLibrary[icon.icon]?.file?.src}`}"
alt="{$mediaLibrary[icon.icon]?.alt || ''}"
title="{$mediaLibrary[icon.icon]?.title || ''}"
/>
</a>
</div>
{/each}
@@ -39,5 +48,8 @@
display: flex;
justify-content: flex-start;
gap: 20px;
img {
width: 48px;
}
}
</style>

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { apiBaseURL } from "../../../config"
import { mediaLibrary } from "../../store"
export let col: Column
export let pageId: string
@@ -8,7 +9,11 @@
<div class="infoBoard">
<div class="header">
<div class="icon">
<img src="{`${apiBaseURL}page/${pageId}/${col.infoBoard.icon?.src}`}" alt="img" />
<img
src="{`${apiBaseURL}medialib/${col.infoBoard.icon}/${$mediaLibrary[col.infoBoard.icon]?.file?.src}`}"
alt="{$mediaLibrary[col.infoBoard.icon]?.alt || ''}"
title="{$mediaLibrary[col.infoBoard.icon]?.title || ''}"
/>
</div>
<div class="title">
{col.infoBoard.title}

View File

@@ -10,7 +10,7 @@
<style lang="less">
@import "../../assets/css/main.less";
.more {
background-color: @bg-color-secondary;
background-color: @signal-color;
color: @font-color-secondary;
border: none;
height: 36px;
@@ -20,5 +20,9 @@
}
.bright {
border: 2px solid @bg-color;
background-color: @bg-color-secondary;
&:hover {
background-color: @signal-color;
}
}
</style>

View File

@@ -54,7 +54,7 @@
height: 100px;
width: 365px;
z-index: 9;
background-color: @bg-color-secondary;
background-color: @signal-color;
}
.description {
position: relative;

View File

@@ -1,22 +1,36 @@
<script lang="ts">
import { navigate } from "svelte-routing/src/history"
import { openExtendableNr, pages, rerender, scrollToRowNr } from "../../store"
export let col: Column
import { openExtendableNr, rerender, scrollToRowNr } from "../../store"
export let pages: Page[]
export let pageReference: string
let jobOffers = pages.map((p) => p.jobOffer)
let focused = -1
</script>
<div class="link-container">
{#each col.pageLinkBlocks as link, i}
{#if isNaN(link.extendableRowNr)}
{#each jobOffers as job, i}
{#if i < 3}
<button
class="page-ref"
class="row-ref fill"
on:click="{() => {
$rerender = $rerender + 1
$scrollToRowNr = link.rowNr
navigate(Object.values($pages)?.find((o) => o.id == link.page)?.path || '/')
navigate(pageReference + '?elem=' + pages[i].id)
}}"
on:mouseenter="{() => {
focused = i
}}"
on:mouseleave="{() => {
focused = -1
}}"
>
{link.name}
<div>
{job.title}
</div>
<svg
data-src="/media/arrow-r.svg"
stroke="{i == focused ? '#fff' : '#343a40'}"
fill="{i == focused ? '#fff' : '#343a40'}"
style="z-index: 9999; position: relative;"></svg>
</button>
{:else}
<button
@@ -26,22 +40,13 @@
on:mouseleave="{() => {
focused = -1
}}"
class="row-ref fill"
on:click="{() => {
$rerender = $rerender + 1
$scrollToRowNr = link.rowNr
$openExtendableNr = link.extendableRowNr
navigate(Object.values($pages)?.find((o) => o.id == link.page)?.path || '/')
navigate(pageReference)
}}"
class="page-ref"
>
<div>
{link.name}
</div>
<svg
data-src="/media/arrow-r.svg"
stroke="{i == focused ? '#fff' : 'black'}"
fill="{i == focused ? '#fff' : 'black'}"
style="z-index: 9999; position: relative;"></svg>
<div>Mehr offene Stellen</div>
</button>
{/if}
{/each}
@@ -78,12 +83,15 @@
width: 20px;
height: auto;
}
&:hover {
color: white !important;
}
}
.page-ref {
background-color: @bg-color-secondary;
background-color: @signal-color;
color: @font-color-secondary;
border: 2px solid @bg-color-secondary;
border: 2px solid @signal-color;
}
}
</style>

View File

@@ -1,39 +1,47 @@
<script lang="ts">
import { apiBaseURL } from "../../../config"
import { pages, rerender } from "../../store"
import { mediaLibrary, pages, rerender } from "../../store"
import { navigate } from "svelte-routing/src/history"
export let pageId: string
export let col: Column
export let persons: Page[]
let hover = -1
</script>
<div class="persons">
{#each col.personPreview as pp, i}
{#each persons as p, i}
<button
class="person"
on:click="{() => {
$rerender = $rerender + 1
navigate(Object.values($pages)?.find((o) => o.id == pp.link)?.path || '/')
navigate(p.path)
}}"
>
<div class="image" on:mouseover="{() => (hover = i)}" on:focus on:blur on:mouseout="{() => (hover = -1)}">
<!-- Initial Image -->
<img
class="initial"
src="{`${apiBaseURL}page/${pageId}/${pp.initialImage?.src}`}"
alt="img"
src="{`${apiBaseURL}medialib/${p?.personPreview?.initialImage}/${
$mediaLibrary[p?.personPreview?.initialImage]?.file?.src
}`}"
style="opacity: {hover == i ? 0 : 1}"
alt="{$mediaLibrary[p?.personPreview?.initialImage]?.alt || ''}"
title="{$mediaLibrary[p?.personPreview?.initialImage]?.title || ''}"
/>
<!-- Hover Image -->
<img
class="hover"
src="{`${apiBaseURL}page/${pageId}/${pp.hoverImage?.src}`}"
alt="img"
src="{`${apiBaseURL}medialib/${p?.personPreview?.hoverImage}/${
$mediaLibrary[p?.personPreview?.hoverImage]?.file?.src
}`}"
style="opacity: {hover == i ? 1 : 0}"
alt="{$mediaLibrary[p?.personPreview?.hoverImage]?.alt || ''}"
title="{$mediaLibrary[p?.personPreview?.hoverImage]?.title || ''}"
/>
</div>
<div class="text">
{pp.name}
{p?.personPreview?.name}
</div>
</button>
{/each}
@@ -68,12 +76,12 @@
position: absolute;
top: 0;
left: 0;
transition: opacity 0.3s;
transition: opacity 0.3s;
}
}
.text {
width: 100%;
background-color: @bg-color-secondary;
background-color: @signal-color;
color: @font-color-secondary;
border: 2px solid @bg-color-secondary;
padding: 2px 15px;

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { apiBaseURL } from "../../../config"
import { mediaLibrary } from "../../store"
export let col: Column
export let pageId: string
@@ -20,8 +21,8 @@
<div class="content">{@html publication.content}</div>
<div class="download">
<a
href="{apiBaseURL}page/{pageId}/{publication.file.src}"
download="{apiBaseURL}page/{pageId}/{publication.file.src}"
href="{apiBaseURL}medialib/{publication.file}/{$mediaLibrary[publication.file]?.file.src}"
download="{apiBaseURL}medialib/{publication.file}/{$mediaLibrary[publication.file]?.file.src}"
>
<button class="download-button">zur Publikation</button>
</a>

View File

@@ -10,30 +10,40 @@
}
const jumpDown = () => {
// Jump down by 100vh
window.scrollTo({ top: window.innerHeight, behavior: "smooth" })
if (typeof window !== "undefined") {
// Jump down by 100vh
window.scrollTo({ top: window.innerHeight, behavior: "smooth" })
}
}
onMount(() => {
// Attach scroll event listener when component is mounted
window.addEventListener("scroll", checkScroll)
if (typeof window !== "undefined") {
// Attach scroll event listener when component is mounted
window.addEventListener("scroll", checkScroll)
}
})
onDestroy(() => {
// Remove scroll event listener when component is destroyed
window.removeEventListener("scroll", checkScroll)
if (typeof window !== "undefined") {
// Remove scroll event listener when component is destroyed
window.removeEventListener("scroll", checkScroll)
}
})
let force = true
setInterval(() => {
if (location.pathname != "/") {
force = false
} else force = true
}, 1000)
$: {
if ($rerender) {
if (typeof window !== "undefined") {
setInterval(() => {
if (location.pathname != "/") {
force = false
} else force = true
}, 1000)
}
$: {
if (typeof window !== "undefined") {
if ($rerender) {
if (location.pathname != "/") {
force = false
} else force = true
}
}
}
</script>

View File

@@ -9,18 +9,24 @@
}
const scrollToTop = () => {
// Scroll smoothly to the top
window.scrollTo({ top: 0, behavior: "smooth" })
if (typeof window !== "undefined") {
// Scroll smoothly to the top
window.scrollTo({ top: 0, behavior: "smooth" })
}
}
onMount(() => {
// Attach scroll event listener when component is mounted
window.addEventListener("scroll", checkScroll)
if (typeof window !== "undefined") {
// Attach scroll event listener when component is mounted
window.addEventListener("scroll", checkScroll)
}
})
onDestroy(() => {
// Remove scroll event listener when component is destroyed
window.removeEventListener("scroll", checkScroll)
if (typeof window !== "undefined") {
// Remove scroll event listener when component is destroyed
window.removeEventListener("scroll", checkScroll)
}
})
</script>

View File

@@ -31,7 +31,7 @@
min-width: 60px;
min-height: 100px;
padding: 10px;
background-color: @bg-color-secondary;
background-color: @signal-color;
color: @font-color-secondary;
font-family: "LibreCaslonText";
font-size: 1.7rem;

View File

@@ -0,0 +1,6 @@
import { api } from "../../api"
export async function loadLibrary(): Promise<MediaLibrary[]> {
let lib = await api<MediaLibrary[]>("medialib", {})
return lib.data
}

View File

@@ -0,0 +1,6 @@
import { api } from "../../api"
export async function loadModules(): Promise<Module[]> {
let module = await api<Module[]>("module", {})
return module.data
}

View File

@@ -2,5 +2,6 @@ import { api } from "../../api"
export async function loadNavigation(): Promise<Navigation[]> {
let nav = await api<Navigation[]>("navigation", {})
console.log("NAV:", nav)
return nav.data
}

View File

@@ -12,6 +12,10 @@ export const location = writable(initLoc)
export let navigation = writable<Navigation>()
export let pages = writable<Pages>({})
export let team = writable<Pages>({})
export let jobOffers = writable<Pages>({})
export let modules = writable<{ [id: string]: Module }>({})
export let mediaLibrary = writable<{ [id: string]: MediaLibrary }>({})
export let serviceNavigation = writable<Navigation>()
export let rerender = writable(0)
export let scrollToRowNr = writable(-1)

View File

@@ -9,8 +9,10 @@
"scripts": {
"validate": "svelte-check && tsc --noEmit",
"dev": "node scripts/esbuild-wrapper.js watch",
"start": "NAMESPACE=renz_shop node scripts/esbuild-wrapper.js start",
"start": "node scripts/esbuild-wrapper.js start",
"start:ssr": "SSR=1 node scripts/esbuild-wrapper.js start",
"build": "node scripts/esbuild-wrapper.js build",
"build:admin": "node scripts/esbuild-wrapper.js build esbuild.config.admin.js",
"build:legacy": "node scripts/esbuild-wrapper.js build esbuild.config.legacy.js && babel _temp/index.js -o _temp/index.babeled.js && esbuild _temp/index.babeled.js --outfile=frontend/dist/index.es5.js --target=es5 --bundle --minify --sourcemap",
"build:server": "node scripts/esbuild-wrapper.js build esbuild.config.server.js && babel _temp/app.server.js -o _temp/app.server.babeled.js && esbuild _temp/app.server.babeled.js --outfile=api/hooks/lib/app.server.js --target=es5 --bundle --sourcemap --platform=node",
"build:test": "node scripts/esbuild-wrapper.js build esbuild.config.test.js && babel --config-file ./babel.config.test.json _temp/hook.test.js -o _temp/hook.test.babeled.js && esbuild _temp/hook.test.babeled.js --outfile=api/hooks/lib/hook.test.js --target=es5 --bundle --sourcemap --platform=node",
@@ -52,6 +54,7 @@
"autoprefixer": "^10.4.14",
"core-js": "3.30.1",
"cssnano": "^6.0.0",
"external-svg-loader": "^1.6.10",
"postcss-import": "^15.1.0",
"postcss-load-config": "^4.0.1",
"postcss-nested": "^6.0.1",

164
types/global.d.ts vendored
View File

@@ -3,17 +3,52 @@ interface FileField {
src: string
type: string
}
interface Ssr {
id?: string
path: string
content: string
validUntil: any // go Time
}
interface Pages {
[key: string]: Page
}
interface ApiResult<T> {
data: T
count: number
}
interface ApiOptions {
method?: string
filter?: any
sort?: string
lookup?: string
limit?: number
offset?: number
projection?: string
headers?: {
[key: string]: string
}
params?: {
[key: string]: string
}
}
interface Page {
path: string
active: boolean
type: "page" | "teamMembers" | "jobOffers"
pageTitle: string
personType: string
personPreview: PersonPreview
teaser: teaserHomepage
sectionHomepage: Row
rows: outerRow[]
jobOffer: jobOffer
rows: Row[]
id: string
meta: {
title: string
description: string
keywords: string
}
}
interface teaserHomepage {
@@ -22,62 +57,64 @@ interface teaserHomepage {
teaserTitle: string
teaserDescription: string
}
interface outerRow {
row: Row
}
interface Row {
topTitle: string
topTitleRed: boolean
subTitle: string
topTitleUpperCase: boolean
title: string
pageTitle: string
subTitle: string
backgroundImage: string
noBottomMargin: boolean
noTopMargin: boolean
flexWrapNormal: boolean
twoToThree: boolean
columns: Column[]
backgroundImage: FileField
}
interface Column {
contentType:
| "image"
| "iconCycleSquare"
| "iconCycleCircle"
| "text"
| "infoBoard"
| "worldCard"
| "nestedCard"
| "topDown"
| "personPreview"
| "boxlist"
| "extendableBoxes"
| "textLink"
| "iconBlocks"
| "pageLinkBlocks"
| "publications"
| "networkEvents"
type Column =
| { contentType: "image"; image: string; icons: { icon: string; link: string }[] }
| { contentType: "moduleImport"; moduleImport: string }
| { contentType: "text"; text: string }
| { contentType: "infoBoard"; infoBoard: InfoBoard }
| { contentType: "nestedCard"; nestedCard: NestedCard[] }
| { contentType: "topDown"; topDown: TopDown }
| { contentType: "textLink"; textLink: TextLink }
| { contentType: "iconBlocks"; iconBlocks: IconBlock[] }
| { contentType: "networkEvents"; networkEvents: NetworkEvent[] }
| { contentType: "publications"; publications: Publication[] }
image?: FileField
icons: {
icon: FileField
link: string
}[]
iconCycleSquare?: IconCycleSquare
iconCycleCircle?: IconCycleCircle
text: string
textLink: TextLink
infoBoard: InfoBoard
worldCard: WorldCard
nestedCard: NestedCard[]
topDown: TopDown
personPreview: PersonPreview[]
boxList: BoxList
extendableBoxes: ExtendableBox[]
iconBlocks: iconBlock[]
pageLinkBlocks: pageLinkBlock[]
networkEvents: NetworkEvent[]
publications: Publication[]
interface MediaLibrary {
file: FileField
alt: string
title: string
id: string
}
type BaseModule = { id: string }
type IconCycleModule = BaseModule & {
type: "iconCycleCircle" | "iconCycleSquare"
iconCycle?: IconCycleCircle | IconCycleSquare
}
type WorldCardModule = BaseModule & {
type: "worldCard"
worldCard?: WorldCard
}
type JobOfferModule = BaseModule & {
type: "jobOfferLink" | "jobOffer"
jobOfferPage?: string
}
type SimpleModule = BaseModule & {
type: "chefTeam" | "employeeTeam"
}
type Module = IconCycleModule | WorldCardModule | JobOfferModule | SimpleModule
interface Publication {
file: FileField
file: string
content: string
}
@@ -85,23 +122,15 @@ interface NetworkEvent {
beginDate: Date
endDate: Date
title: string
file: FileField
file: string
}
interface iconBlock {
icon: FileField
iconFocused: FileField
icon: string
bigText: string
smallText: string
}
interface pageLinkBlock {
name: string
rowNr: number
page: string
extendableRowNr: number
}
interface IconCycleSquare {
boxes: Box[]
}
@@ -112,19 +141,19 @@ interface IconCycleCircle {
}
interface Box {
icon: FileField
icon: string
text: string
circle: boolean
}
interface InfoBoard {
title: string
icon: FileField
icon: string
text: string
}
interface WorldCard {
rows: WorldCardRow[]
row: WorldCardRow[]
}
interface worldCardRow {
cards: Card[]
@@ -133,7 +162,7 @@ interface worldCardRow {
interface Card {
verticalAlignment: string
horizontalAlignment: string
image: FileField
image: string
title: string
properties: number[]
}
@@ -154,10 +183,9 @@ interface TopDownRow {
}
interface PersonPreview {
initialImage: FileField
hoverImage: FileField
initialImage: string
hoverImage: string
name: string
link: string
}
interface BoxList {
@@ -166,14 +194,14 @@ interface BoxList {
}[]
}
interface ExtendableBox {
interface jobOffer {
title: string
text: string
emailButton: boolean
emailSubject: string
}
interface FileField {
interface string {
path: string
src: string
type: string

View File

@@ -3416,6 +3416,15 @@ __metadata:
languageName: node
linkType: hard
"external-svg-loader@npm:^1.6.10":
version: 1.6.10
resolution: "external-svg-loader@npm:1.6.10"
dependencies:
idb-keyval: ^6.2.0
checksum: 99545f4bcd0ad488092c234a3468ba8ac0265ab2646dc4d7d0e5807cf10136d80edd73b5f09bc65f6e80f96558527a75afbca74b780d91d57d15077d5080af78
languageName: node
linkType: hard
"fast-glob@npm:^3.2.7":
version: 3.2.12
resolution: "fast-glob@npm:3.2.12"
@@ -3784,6 +3793,13 @@ __metadata:
languageName: node
linkType: hard
"idb-keyval@npm:^6.2.0":
version: 6.2.1
resolution: "idb-keyval@npm:6.2.1"
checksum: 7c0836f832096086e99258167740181132a71dd2694c8b8454a4f5ec69114ba6d70983115153306f0b6de1c8d3bad04f67eed3dff8f50c96815b9985d6d78470
languageName: node
linkType: hard
"image-size@npm:~0.5.0":
version: 0.5.5
resolution: "image-size@npm:0.5.5"
@@ -6294,6 +6310,7 @@ __metadata:
cssnano: ^6.0.0
esbuild: ^0.17.18
esbuild-svelte: ^0.7.3
external-svg-loader: ^1.6.10
http-proxy-middleware: ^2.0.6
less: ^4.1.3
morgan: ^1.10.0