Compare commits

...

45 Commits

Author SHA1 Message Date
0acfcc63b3 Add medialibViews.yml and update medialib.yml
All checks were successful
deploy to production / deploy (push) Successful in 43s
2024-03-26 06:51:19 +00:00
0af187e51f styling
All checks were successful
deploy to production / deploy (push) Successful in 48s
2023-12-26 14:00:11 +00:00
8844a9f77d styling
All checks were successful
deploy to production / deploy (push) Successful in 50s
2023-12-26 13:56:53 +00:00
45e1104aa9 styling
All checks were successful
deploy to production / deploy (push) Successful in 50s
2023-12-26 13:54:56 +00:00
0f2411b0d2 styling
All checks were successful
deploy to production / deploy (push) Successful in 52s
2023-12-26 13:50:26 +00:00
3b63a366b9 styling
All checks were successful
deploy to production / deploy (push) Successful in 49s
2023-12-26 13:48:20 +00:00
2cf00896da styling
All checks were successful
deploy to production / deploy (push) Successful in 50s
2023-12-26 13:45:35 +00:00
b1d84bc633 lighthouse
All checks were successful
deploy to production / deploy (push) Successful in 57s
2023-12-26 13:19:56 +00:00
f5526e7907 anfahrt
All checks were successful
deploy to production / deploy (push) Successful in 54s
2023-12-13 15:01:39 +00:00
dcfe6bd632 anfahrt
All checks were successful
deploy to production / deploy (push) Successful in 47s
2023-12-12 13:31:57 +00:00
3f461c12d4 anfahrt
All checks were successful
deploy to production / deploy (push) Successful in 47s
2023-12-12 13:25:13 +00:00
50e1af5f18 anfahrt
Some checks failed
deploy to production / deploy (push) Has been cancelled
2023-12-12 13:24:41 +00:00
17917ddec5 bugfix
All checks were successful
deploy to production / deploy (push) Successful in 55s
2023-12-08 14:31:06 +00:00
bb4a9a2c55 bugfix
All checks were successful
deploy to production / deploy (push) Successful in 45s
2023-12-08 13:28:01 +00:00
6a94866368 sort 2023-12-08 13:24:32 +00:00
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
102 changed files with 2755 additions and 870 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,47 @@
- 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
filter: true
- source: title
name: Titel
filter: true
- source: alt
name: Alternativtext
filter: true
- 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
filter: true
- source: title
name: Titel
filter: true
- source: alt
name: Alternativtext
filter: true

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,29 @@
name: lighthouseSubpath
meta:
label: Lighthouse Subpaths
muiIcon: web
views:
- type: table
columns:
- source: lighthouseSubpath
permissions:
public:
methods:
get: false
post: false
put: false
delete: false
user:
methods:
get: true
post: true
put: true
delete: true
fields:
- type: string
name: lighthouseSubpath
meta:
label: PagespeedPaths

View File

@ -0,0 +1,120 @@
name: lighthouse
meta:
label: Lighthouse
muiIcon: web
views:
- type: table
mediaQuery: "(min-width: 600px)"
columns:
- source: insertTime
filter: true
- source: perfomance
filter: true
- source: accessibility
filter: true
- source: bestPractices
filter: true
- source: seo
filter: true
- type: simpleList
mediaQuery: "(max-width: 599px)"
primaryText: insertTime
secondaryText: performance
tertiaryText: accessibility
permissions:
public:
methods:
get: false
post: false
put: false
delete: false
user:
methods:
get: true
post: true
put: true
delete: true
projections:
dashboard:
hooks:
post:
create:
type: javascript
file: hooks/lighthouse/post_create.js
fields:
- name: analyzedPaths
type: string[]
meta:
label: Analyzed Paths
- name: performance
type: number
meta:
label: Performance
- name: accessibility
type: number
meta:
label: Accessibility
- name: bestPractices
type: number
meta:
label: Best Practices
- name: seo
type: number
meta:
label: SEO
- name: lighthouseMetrics
type: object
meta:
label: Lighthouse Metrics
subFields:
- name: FCPS
type: number
meta:
label: First Contentful Paint Score
- name: FCPV
type: number
meta:
label: First Contentful Paint Value
- name: FMPV
type: number
meta:
label: First Meaningful Paint Value
- name: FMPS
type: number
meta:
label: First Meaningful Paint Score
- name: SIS
type: number
meta:
label: Speed Index Score
- name: SIV
type: number
meta:
label: Speed Index Value
- name: TTIS
type: number
meta:
label: Time to Interactive Score
- name: TTIV
type: number
meta:
label: Time to Interactive Value
- name: FPIDS
type: number
meta:
label: First Potential Input Delay Score
- name: FPIDV
type: number
meta:
label: First Potential Input Delay Value

View File

@ -0,0 +1,149 @@
# 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: !include fieldLists/medialibViews.yml
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: !include fieldLists/medialibViews.yml
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:
@ -7,9 +7,248 @@ meta:
servers:
- url: https://tibi-admin-server.code.testversion.online/api/v1/_/demo
description: code-server
dashboard:
majorItems:
- type: "sectionTitle"
title: { de: "Website Perfomance", en: "Website Perfomance" }
appendix:
collection: lighthouse
eval: |
(function(){
return " " + new Date($date).toLocaleDateString() + ""
})()
- type: graph
filter: false
graphType: radialBar
until: "lastYear"
value: total
containerProps:
#optional class prop
layout:
breakBefore: false
breakAfter: false
size:
default: "col-6"
small: "col-12"
large: "col-3"
options:
{
property: plotOptions,
value:
{
radialBar:
{
hollow: { margin: 0, size: "70%" },
track: { dropShadow: { enabled: true, top: 2, left: 0, blur: 4, opacity: 0.15 } },
dataLabels:
{
name: { offsetY: -10, color: "#000", fontSize: "13px" },
value: { color: "#000", fontSize: "30px", show: true },
},
},
},
}
graphs:
- collection: lighthouse
field: performance
yAxis: latestValue
graphName: { de: "Perfomance Score", en: "Perfomance Score" }
dateTimeField: insertTime
- type: graph
filter: false
graphType: radialBar
until: "lastYear"
value: total
containerProps:
#optional class prop
layout:
breakBefore: false
breakAfter: false
size:
default: "col-6"
small: "col-12"
large: "col-3"
options:
{
property: plotOptions,
value:
{
radialBar:
{
hollow: { margin: 0, size: "70%" },
track: { dropShadow: { enabled: true, top: 2, left: 0, blur: 4, opacity: 0.15 } },
dataLabels:
{
name: { offsetY: -10, color: "#000", fontSize: "13px" },
value: { color: "#000", fontSize: "30px", show: true },
},
},
},
}
graphs:
- collection: lighthouse
field: accessibility
yAxis: latestValue
graphName: { de: "Accessibility Score", en: "Accessibility Score" }
dateTimeField: insertTime
- type: graph
filter: false
graphType: radialBar
until: "lastYear"
value: total
containerProps:
#optional class prop
layout:
breakBefore: false
breakAfter: false
size:
default: "col-6"
small: "col-12"
large: "col-3"
options:
{
property: plotOptions,
value:
{
radialBar:
{
hollow: { margin: 0, size: "70%" },
track: { dropShadow: { enabled: true, top: 2, left: 0, blur: 4, opacity: 0.15 } },
dataLabels:
{
name: { offsetY: -10, color: "#000", fontSize: "13px" },
value: { color: "#000", fontSize: "30px", show: true },
},
},
},
}
graphs:
- collection: lighthouse
field: bestPractices
yAxis: latestValue
graphName: { de: "Best Practices Score", en: "Best Practices Score" }
dateTimeField: insertTime
- type: graph
filter: false
graphType: radialBar
until: "lastYear"
value: total
containerProps:
#optional class prop
layout:
breakBefore: false
breakAfter: false
size:
default: "col-6"
small: "col-12"
large: "col-3"
options:
{
property: plotOptions,
value:
{
radialBar:
{
hollow: { margin: 0, size: "70%" },
track: { dropShadow: { enabled: true, top: 2, left: 0, blur: 4, opacity: 0.15 } },
dataLabels:
{
name: { offsetY: -10, color: "#000", fontSize: "13px" },
value: { color: "#000", fontSize: "30px", show: true },
},
},
},
}
graphs:
- collection: lighthouse
field: seo
yAxis: latestValue
graphName: { de: "SEO Score", en: "SEO Score" }
dateTimeField: insertTime
- type: swiper # Art des Elements, hier ein Swiper
containerProps:
#optional class prop
layout:
breakBefore: false
breakAfter: false
size:
default: "col-12"
small: "col-12"
large: "col-6 row-2-4"
elements: # Liste der Elemente in diesem Swiper
- type: graph
title:
value: { de: "Ladezeit (Score)", en: "Loadtime (Score)" }
xAxis: manual
until: "lastYear"
filter: false #deaktiviert die Filter möglichkeit für den Nutzer beim diagramm, normalerweise aktiviert. Hierbei sind alle kombinationen x >= until möglich
columns:
- name: { de: '["Erstes sichtbares", "Element"]', en: '["First Contentful", "Paint"]' }
field: lighthouseMetrics.FCPS
- name: { de: '["Erstes bedeutsames", "Element"]', en: '["First Meaningful", "Paint"]' }
field: lighthouseMetrics.FMPS
- name:
{
de: '["Maximale potenzielle", "erste", "ingabeverzögerung"]',
en: '["Max Potential", "First Input", "Delay"]',
}
field: lighthouseMetrics.FPIDS
- name: { de: '["Zeit bis", "zur", "Interaktivität"]', en: '["Time to", "Interactive"]' }
field: lighthouseMetrics.TTIS
- name: { de: '["Geschwindigkeitsindex"]', en: '["Speed Index"]' }
field: lighthouseMetrics.SIS
graphType: "bar"
graphs:
- graphName: { de: "Lighthouse Metriken", en: "Lighthouse Metrics" }
yAxis: latestValue
collection: lighthouse
dateTimeField: insertTime
- type: graph
title:
value: { de: "Ladezeit (Sekunden)", en: "Loadtime (seconds)" }
xAxis: manual
until: "lastYear"
filter: false #deaktiviert die Filter möglichkeit für den Nutzer beim diagramm, normalerweise aktiviert. Hierbei sind alle kombinationen x >= until möglich
columns:
- name: { de: '["Erstes sichtbares", "Element"]', en: '["First Contentful", "Paint"]' }
field: lighthouseMetrics.FCPV
- name: { de: '["Erstes bedeutsames", "Element"]', en: '["First Meaningful", "Paint"]' }
field: lighthouseMetrics.FMPV
- name:
{
de: '["Maximale potenzielle", "erste", "ingabeverzögerung"]',
en: '["Max Potential", "First Input", "Delay"]',
}
field: lighthouseMetrics.FPIDV
- name: { de: '["Zeit bis", "zur", "Interaktivität"]', en: '["Time to", "Interactive"]' }
field: lighthouseMetrics.TTIV
- name: { de: '["Geschwindigkeitsindex"]', en: '["Speed Index"]' }
field: lighthouseMetrics.SIV
graphType: "bar"
graphs:
- graphName: { de: "Lighthouse Metriken", en: "Lighthouse Metrics" }
yAxis: latestValue
collection: lighthouse
dateTimeField: insertTime
- type: "sectionTitle"
title: { de: "Seiteninhalte", en: "Page content" }
- collection: navigation
type: reference
style:
@ -22,13 +261,60 @@ 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)
- type: "sectionTitle"
title: { de: "Aktionen", en: "Actions" }
- collection: lighthouse
type: action
action: "Lighthouse Durchlauf starten"
backgroundAction: true
modalText:
{
de: "Zur Analyse der Website werden einige Zeitintensive prozesse gestartet, daher wird dies im Hintergrund ausgeführt. Es kann einige Minuten dauern bis das Dashboard aktuallisiert wird, bitte haben Sie etwas Geduld.",
en: "To analyze the website, some time-intensive processes are started, so this is done in the background. It may take a few minutes for the dashboard to be updated, please be patient.",
}
properties:
url: https://www.fontis.de
type: post
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
- !include collections/lighthouse.yml
- !include collections/lighthouse-subpaths.yml
assets:
- name: img
path: img
jobs:
- cron: "0 0 * * 1"
type: javascript
file: jobs/lighthouse.js

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,6 @@ module.exports = {
// not found
return -1
},
ssrAllowedAPIEndpoints: ["content", "medialib"],
ssrPublishCheckCollections: ["page"],
LIGHTHOUSE_TOKEN: "AIzaSyC0UxHp3-MpJiDL3ws7pEV6lj57bfIc7GQ",
}

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

@ -46,8 +46,103 @@ function clearSSRCache() {
context.response.header("X-SSR-Cleared", info.removed)
}
var { LIGHTHOUSE_TOKEN } = require("../config")
function calculateAverageDynamically(dbObjs) {
const sumObj = {}
let count = 0
dbObjs.forEach((obj) => {
accumulate(obj, sumObj)
count++
})
function accumulate(sourceObj, targetObj) {
for (const key in sourceObj) {
if (typeof sourceObj[key] === "number") {
targetObj[key] = (targetObj[key] || 0) + sourceObj[key]
} else if (typeof sourceObj[key] === "object" && sourceObj[key] !== null) {
targetObj[key] = targetObj[key] || {}
accumulate(sourceObj[key], targetObj[key])
}
}
}
function average(targetObj) {
for (const key in targetObj) {
if (typeof targetObj[key] === "number") {
targetObj[key] = targetObj[key] / count
} else if (typeof targetObj[key] === "object") {
average(targetObj[key])
}
}
}
average(sumObj)
return sumObj
}
function run(url) {
const response = context.http
.fetch(url, {
timeout: 300,
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
.body.json()
// needs enough traffic to be collected
const cruxMetrics = {
"First Contentful Paint": response?.loadingExperience?.metrics?.FIRST_CONTENTFUL_PAINT_MS?.category,
"First Input Delay": response?.loadingExperience?.metrics?.FIRST_INPUT_DELAY_MS?.category,
}
const lighthouse = response.lighthouseResult
const lighthouseMetrics = {
FCPS: lighthouse.audits["first-contentful-paint"].score * 100,
FCPV: lighthouse.audits["first-contentful-paint"].numericValue / 1000,
FMPS: lighthouse.audits["first-meaningful-paint"].score * 100,
FMPV: lighthouse.audits["first-meaningful-paint"].numericValue / 1000,
SIS: lighthouse.audits["speed-index"].score * 100,
SIV: lighthouse.audits["speed-index"].numericValue / 1000,
TTIS: lighthouse.audits["interactive"].score * 100,
TTIV: lighthouse.audits["interactive"].numericValue / 1000,
FPIDS: lighthouse.audits["max-potential-fid"].score * 100,
FPIDV: lighthouse.audits["max-potential-fid"].numericValue / 1000,
}
let dbObject = {
cruxMetrics,
lighthouseMetrics,
performance: Math.round(lighthouse.categories.performance.score * 100),
accessibility: Math.round(lighthouse.categories.accessibility.score * 100),
bestPractices: Math.round(lighthouse.categories["best-practices"].score * 100),
seo: Math.round(lighthouse.categories.seo.score * 100),
}
return dbObject
}
function setUpQuery(subPath = "/") {
const api = "https://www.googleapis.com/pagespeedonline/v5/runPagespeed"
let params = `category=performance&category=accessibility&category=best-practices&category=seo`
const parameters = {
url: encodeURIComponent(`https://www.fontis.de/${subPath}`),
key: LIGHTHOUSE_TOKEN,
}
let query = `${api}?`
for (let key in parameters) {
query += `${key}=${parameters[key]}&`
}
query += params // Append other parameters without URL encoding
return query
}
module.exports = {
log,
clearSSRCache,
obj2str,
run,
setUpQuery,
calculateAverageDynamically,
}

View File

@ -0,0 +1,16 @@
var { setUpQuery, calculateAverageDynamically, run } = require("../lib/utils")
;(function () {
let subPaths = context.db.find("lighthouseSubpath")
let urls = []
for (let i = 0; i < subPaths.length; i++) {
urls.push(setUpQuery(subPaths[i].lighthouseSubpath))
}
let dbObjs = []
urls.forEach((url) => {
console.log("URL:", url)
dbObjs.push(run(url))
})
let dbObject = calculateAverageDynamically(dbObjs)
dbObject.analyzedPaths = [...subPaths].map((subPath) => subPath.lighthouseSubpath)
return { data: dbObject }
})()

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,

17
api/jobs/lighthouse.js Normal file
View File

@ -0,0 +1,17 @@
var { setUpQuery, calculateAverageDynamically, run } = require("../hooks/lib/utils")
;(function () {
console.log("Running lighthouse job")
let subPaths = context.db.find("lighthouseSubpath")
let urls = []
for (let i = 0; i < subPaths.length; i++) {
urls.push(setUpQuery(subPaths[i].lighthouseSubpath))
}
let dbObjs = []
urls.forEach((url) => {
console.log("URL:", url)
dbObjs.push(run(url))
})
let dbObject = calculateAverageDynamically(dbObjs)
dbObject.analyzedPaths = [...subPaths].map((subPath) => subPath.lighthouseSubpath)
context.db.create("lighthouse", dbObject)
})()

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>

Binary file not shown.

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="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M44 24c0 .69-.56 1.25-1.25 1.25H10.304l12.822 12.609a1.251 1.251 0 1 1-1.752 1.782L6.386 24.903a1.245 1.245 0 0 1-.379-1.035c.032-.3.171-.58.393-.785L21.374 8.359a1.25 1.25 0 1 1 1.752 1.782L10.304 22.75H42.75c.69 0 1.25.56 1.25 1.25z" fill="#333"/>
<path d="M44 24c0 .69-.56 1.25-1.25 1.25H10.304l12.822 12.609a1.251 1.251 0 1 1-1.752 1.782L6.386 24.903a1.245 1.245 0 0 1-.379-1.035c.032-.3.171-.58.393-.785L21.374 8.359a1.25 1.25 0 1 1 1.752 1.782L10.304 22.75H42.75c.69 0 1.25.56 1.25 1.25z" fill="#343A40"/>
</svg>

Before

Width:  |  Height:  |  Size: 366 B

After

Width:  |  Height:  |  Size: 369 B

View File

@ -1,3 +1,3 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.268 4.21a.75.75 0 0 0-1.04 1.08l8.275 7.96H3.75a.75.75 0 1 0 0 1.5h18.752l-8.273 7.959a.75.75 0 0 0 1.04 1.08l9.428-9.069a1 1 0 0 0 0-1.441l-9.428-9.07-.001.001z" fill="#333"/>
<path d="M15.268 4.21a.75.75 0 0 0-1.04 1.08l8.275 7.96H3.75a.75.75 0 1 0 0 1.5h18.752l-8.273 7.959a.75.75 0 0 0 1.04 1.08l9.428-9.069a1 1 0 0 0 0-1.441l-9.428-9.07-.001.001z" fill="#343A40"/>
</svg>

Before

Width:  |  Height:  |  Size: 297 B

After

Width:  |  Height:  |  Size: 300 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,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="m22.723 5.473 1.054 1.054L12 18.305.223 6.527l1.054-1.054L12 16.195 22.723 5.473z" fill="#333"/>
<path d="m22.723 5.473 1.054 1.054L12 18.305.223 6.527l1.054-1.054L12 16.195 22.723 5.473z" fill="#343A40"/>
</svg>

Before

Width:  |  Height:  |  Size: 213 B

After

Width:  |  Height:  |  Size: 216 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

View File

@ -1,4 +1,4 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="m37.304 11.282 1.414 1.414-26.022 26.02-1.414-1.413 26.022-26.021z" fill="#333"/>
<path d="m12.696 11.282 26.022 26.02-1.414 1.415-26.022-26.02 1.414-1.415z" fill="#333"/>
<path d="m37.304 11.282 1.414 1.414-26.022 26.02-1.414-1.413 26.022-26.021z" fill="#343A40"/>
<path d="m12.696 11.282 26.022 26.02-1.414 1.415-26.022-26.02 1.414-1.415z" fill="#343A40"/>
</svg>

Before

Width:  |  Height:  |  Size: 292 B

After

Width:  |  Height:  |  Size: 298 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,25 @@
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[Math.random()] = e
} else {
pagesRes[e.path] = e
}
})
$pages = pagesRes
$team = teamRes
$jobOffers = jobOffersRes
}
async function getNavigation() {
@ -41,15 +68,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

@ -8,7 +8,7 @@ html {
background-color: black;
}
body {
color: #333 !important;
color: #343a40 !important;
height: 100%;
background-color: #f9f9f9;
}
@ -16,7 +16,14 @@ body {
ul {
list-style-type: none;
}
.boxes {
.content {
ul {
padding-left: 20px;
list-style-type: disc;
}
}
}
ol {
list-style-type: decimal;
}
@ -27,6 +34,7 @@ a {
font-weight: 700;
color: inherit;
font-weight: normal;
cursor: pointer;
}
/* Tabellen */
@ -56,11 +64,11 @@ button {
border: none;
cursor: pointer;
font-size: inherit;
color: #333;
color: #343a40;
}
input,
select {
color: #333;
color: #343a40;
width: 100%;
}
.underline {
@ -108,7 +116,7 @@ select {
top: 0px;
bottom: 0px;
width: 0px;
background: #000000;
background: #343a40;
transition: width 0.5s ease-in;
}
.fill:hover:after,
@ -143,7 +151,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,82 @@
<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
>
<a
style="text-decoration: none;"
class="underline"
download
href="/media/Anfahrtsbeschreibung_FONTIS-4.pdf"
target="_blank">Anfahrt</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 +180,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,22 @@
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')
chefs = chefs.sort((a, b) => a.sort - b.sort)
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 +84,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 +102,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 +118,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 +143,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 #6b6868;
color: #6b6868;
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"
>
@ -52,19 +51,19 @@
<style lang="less">
@import "../../assets/css/main.less";
button {
margin-top: 20px;
background-color: @bg-color-secondary;
color: @font-color-secondary;
border: 2px solid @bg-color-secondary;
padding: 2px 15px;
font-weight: bold;
}
.boxes {
display: flex;
flex-direction: column;
font-family: "Libre Franklin", "sans-serif";
button {
margin-top: 20px;
background-color: @signal-color;
color: @font-color-secondary;
border: 2px solid @signal-color;
padding: 2px 15px;
font-weight: bold;
}
.box {
border-bottom: 2px dotted @bg-color-secondary;
display: flex;
@ -88,9 +87,13 @@
justify-content: space-between;
}
.content {
max-height: 1000px;
max-height: 3000px;
overflow: hidden;
transition: max-height 1s ease-in;
ul {
padding-left: 20px;
list-style-type: disc;
}
&.closed {
max-height: 0px;

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,27 @@
<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'}"
fill="{i == active ? '#5b6e98' : 'white'}"
data-src="{apiBaseURL}medialib/{box?.icon}/{$mediaLibrary?.[box?.icon]?.file?.src}"></svg>
</div>
<div class="text">
{box.text}
@ -42,8 +45,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 +54,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,48 @@
<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[]
persons = persons.sort((a, b) => a.sort - b.sort)
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,14 +77,13 @@
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;
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
@ -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",

Some files were not shown because too many files have changed in this diff Show More