Compare commits

...

50 Commits

Author SHA1 Message Date
9c35707718 reihenfolge
All checks were successful
deploy to production / deploy (push) Successful in 30s
2025-02-27 06:10:12 +00:00
86c066f48a contact
All checks were successful
deploy to production / deploy (push) Successful in 29s
2025-02-26 20:32:50 +00:00
829468d538 directions final
All checks were successful
deploy to production / deploy (push) Successful in 30s
2025-02-25 08:39:36 +00:00
5f00786003 modified file
All checks were successful
deploy to production / deploy (push) Successful in 29s
2025-02-24 15:54:33 +00:00
0567147690 space
All checks were successful
deploy to production / deploy (push) Successful in 28s
2025-02-24 08:33:04 +00:00
9b722b9c13 directions
All checks were successful
deploy to production / deploy (push) Successful in 30s
2025-02-24 07:50:19 +00:00
01a28b5e98 directions
All checks were successful
deploy to production / deploy (push) Successful in 31s
2025-02-24 07:46:35 +00:00
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
94 changed files with 1783 additions and 497 deletions

3
.env
View File

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

View File

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

Binary file not shown.

View File

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

View File

@@ -3,22 +3,41 @@ uploadPath: ../media/page
meta: meta:
label: Inhalt label: Inhalt
muiIcon: web muiIcon: tableOfContents
allowExportAll: true
backup: backup:
active: true active: true
collectionName: backups collectionName: backups
defaultSort:
field: sort
order: MANUALLY
views: views:
- type: simpleList
selectionPriority: 0
primaryText: pageTitle
secondaryText: path
mediaQuery: "(min-width: 0px)"
tertiaryText: type
- type: table - type: table
selectionPriority: 1
mediaQuery: "(min-width: 700px)"
columns: columns:
- source: type
name: Typ
filter: true
- source: path - source: path
name: Pfad name: Pfad
filter: true
- source: pageTitle - source: pageTitle
name: Titel name: Titel
filter: true
- source: active - source: active
name: Aktiv name: Aktiv
filter: true
tablist: tablist:
activeTab: site activeTab: general
tabs: tabs:
- name: general - name: general
label: Allgemein label: Allgemein
@@ -26,8 +45,8 @@ meta:
- source: path - source: path
- source: pageTitle - source: pageTitle
- source: type - source: type
- source: active - source: active
- source: sort
- name: teaser - name: teaser
label: Homepage Seitenteaser label: Homepage Seitenteaser
@@ -41,7 +60,7 @@ meta:
- source: personPreview - source: personPreview
- name: jobOffer - name: jobOffer
label: Job Angebote label: Job Angebot
subFields: subFields:
- source: jobOffer - source: jobOffer
@@ -50,6 +69,11 @@ meta:
subFields: subFields:
- source: rows - source: rows
- name: meta
label: Meta
subFields:
- source: meta
subNavigation: subNavigation:
- name: seite - name: seite
label: label:
@@ -57,17 +81,24 @@ meta:
en: pages en: pages
muiIcon: book-open-page-variant muiIcon: book-open-page-variant
defaultSort: defaultSort:
field: "pfad" field: "sort"
order: "ASC" order: "MANUALLY"
setDefault:
field: type
value: page
views: views:
- type: table - type: table
columns: columns:
- source: path - source: path
name: Pfad name: Pfad
filter: true
- source: pageTitle - source: pageTitle
name: Titel name: Titel
filter: true
- source: active - source: active
name: Aktiv name: Aktiv
filter: true
filter: filter:
type: page type: page
@@ -76,19 +107,28 @@ meta:
label: label:
de: Teammitglieder de: Teammitglieder
en: Team members en: Team members
muiIcon: book-open-page-variant muiIcon: accountGroup
setDefault:
field: type
value: teamMembers
defaultSort: defaultSort:
field: "pfad" field: "sort"
order: "ASC" order: "MANUALLY"
views: views:
- type: table - type: table
columns: columns:
- source: path - source: path
name: Pfad name: Pfad
fiter: true
- source: personType
name: Typ
filter: true
- source: pageTitle - source: pageTitle
name: Titel name: Titel
filter: true
- source: active - source: active
name: Aktiv name: Aktiv
filter: true
filter: filter:
type: teamMembers type: teamMembers
@@ -96,19 +136,25 @@ meta:
label: label:
de: Stellenanzeigen de: Stellenanzeigen
en: Job offers en: Job offers
muiIcon: book-open-page-variant muiIcon: briefcase
setDefault:
field: type
value: jobOffers
defaultSort: defaultSort:
field: "pfad" field: "sort"
order: "ASC" order: "MANUALLY"
views: views:
- type: table - type: table
columns: columns:
- source: path - source: path
name: Pfad name: Pfad
filter: true
- source: pageTitle - source: pageTitle
name: Titel name: Titel
filter: true
- source: active - source: active
name: Aktiv name: Aktiv
filter: true
filter: filter:
type: jobOffers type: jobOffers
@@ -159,6 +205,16 @@ permissions:
put: true put: true
delete: true delete: true
hooks:
post:
return:
type: javascript
file: hooks/clear_cache.js
put:
return:
type: javascript
file: hooks/clear_cache.js
projections: projections:
navigation: navigation:
select: select:
@@ -181,6 +237,8 @@ fields:
name: active name: active
meta: meta:
label: Aktiv label: Aktiv
helperText: Ist dies Aktiviert, so wird die Seite verfügbar.
defaultValue: true
containerProps: containerProps:
layout: layout:
size: size:
@@ -191,8 +249,9 @@ fields:
- type: string - type: string
name: type name: type
meta: meta:
label: Typ label: Inhaltstyp
widget: select widget: select
defaultValue: page
containerProps: containerProps:
layout: layout:
size: size:
@@ -226,9 +285,14 @@ fields:
meta: meta:
label: Typ label: Typ
widget: select widget: select
defaultValue: chef
dependsOn:
eval: $.type == "teamMembers"
choices: choices:
- name: Chef - name: Chef
id: chef id: chef
- name: Mitarbeiter - name: Mitarbeiter
id: employee id: employee
@@ -236,6 +300,8 @@ fields:
type: object type: object
meta: meta:
label: Personenvorschau label: Personenvorschau
dependsOn:
eval: $.type == "teamMembers"
subFields: subFields:
- name: initialImage - name: initialImage
type: string type: string
@@ -291,7 +357,9 @@ fields:
- name: jobOffer - name: jobOffer
type: object type: object
meta: meta:
label: Job Angebote label: Job Angebot
dependsOn:
eval: $.type == "jobOffers"
subFields: subFields:
- name: title - name: title
type: string type: string
@@ -312,7 +380,7 @@ fields:
- name: emailSubject - name: emailSubject
type: string type: string
meta: meta:
label: E-Mail default Betreff label: E-Mail Betreff
dependsOn: dependsOn:
eval: $parent.emailButton == true eval: $parent.emailButton == true
@@ -321,6 +389,8 @@ fields:
meta: meta:
label: Zeilen label: Zeilen
widget: grid widget: grid
dependsOn:
eval: $.type != "jobOffers" && ($.type != "teamMembers" || $.personType == "chef")
metaElements: metaElements:
- source: backgroundImage - source: backgroundImage
- source: noBottomMargin - source: noBottomMargin
@@ -328,7 +398,49 @@ fields:
- source: flexWrapNormal - source: flexWrapNormal
- source: twoToThree - source: twoToThree
- source: nextPage - source: nextPage
folding:
force: true
subFields: !include fieldLists/row.yml subFields: !include fieldLists/row.yml
- name: meta
type: object
meta:
label: Meta Agaben
dependsOn:
eval: $.type == "page"
subFields:
- 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

@@ -22,9 +22,6 @@
- name: Top-Down - name: Top-Down
id: topDown id: topDown
- name: Ausfahrbare Box
id: extendableBoxes
- name: Text mit Link - name: Text mit Link
id: textLink id: textLink
@@ -142,10 +139,23 @@
type: string type: string
meta: meta:
label: oberer text label: oberer text
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: smallText - name: smallText
type: string type: string
meta: meta:
label: unterer Text label: unterer Text
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: image - name: image
type: string type: string

View File

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

View File

@@ -2,5 +2,7 @@
type: object[] type: object[]
meta: meta:
label: Boxen label: Boxen
pathStep:
title: "icons im Rechteck"
widget: containerLessObjectArray widget: containerLessObjectArray
subFields: !include box.yml 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

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

@@ -4,6 +4,7 @@ name: medialib
uploadPath: ../media/medialib uploadPath: ../media/medialib
meta: meta:
allowExportAll: true
label: label:
de: Medienbibliothek de: Medienbibliothek
en: Media Library en: Media Library
@@ -20,69 +21,26 @@ meta:
enabled: true enabled: true
fields: fields:
- title - title
- description - alt
- file - file
# "defaultImageFilter" dient auch hier nur zur Reduzierung der # "defaultImageFilter" dient auch hier nur zur Reduzierung der
# Bildgröße bei der Anzeige im tibi-admin (Listen). # Bildgröße bei der Anzeige im tibi-admin (Listen).
# Die Bildgröße für die Einbindung ins erzeugte HTML des ContentBuilder # Die Bildgröße für die Einbindung ins erzeugte HTML des ContentBuilder
# hat hiermit nix zu tun. # hat hiermit nix zu tun.
defaultImageFilter: s defaultImageFilter: xs
multiupload: multiupload:
fields: [] fields: []
views: views: !include fieldLists/medialibViews.yml
- type: table
mediaQuery: "(min-width: 0px)"
defaultSelect: false
selectionPriority: 2
fileDropArea:
label:
{
de: "Ziehen Sie Dateien per Drag and Drop hierher oder klicken Sie, um Dateien auszuwählen.",
en: "Drag and drop some files here, or click to upload.",
}
helperText: { de: "Maximale Uploadgröße: 1,54 MB", en: "Maximum upload size: 1.54MB" }
targetField: file
pageAsDropArea: false
columns:
- source: file
name: Datei
- source: updateTime
type: datetime
label: letztes Update
- type: cardList
mediaQuery: "(min-width: 1200px)"
selectionPriority: 1
fileDropArea:
label:
{
de: "Ziehen Sie Dateien per Drag and Drop hierher oder klicken Sie, um Dateien auszuwählen.",
en: "Drag and drop some files here, or click to upload.",
}
helperText: { de: "Maximale Uploadgröße: 1,54 MB", en: "Maximum upload size: 1.54MB" }
targetField: file
pageAsDropArea: false
fields:
- source: file
name: Datei
- source: updateTime
type: datetime
label: letztes Update
subNavigation: subNavigation:
- name: modalForeign # Name des Eingabefelds oder der Ansicht. - name: modalForeign # Name des Eingabefelds oder der Ansicht.
defaultSort: # Standard-Sortierkriterien, die angewendet werden, wenn keine anderen Sortierkriterien spezifiziert sind. defaultSort: # Standard-Sortierkriterien, die angewendet werden, wenn keine anderen Sortierkriterien spezifiziert sind.
field: "path" # Standardmäßig wird nach dem "path"-Feld sortiert. field: "path" # Standardmäßig wird nach dem "path"-Feld sortiert.
order: "ASC" # Standardmäßig wird in aufsteigender Reihenfolge (ASC) sortiert. order: "ASC" # Standardmäßig wird in aufsteigender Reihenfolge (ASC) sortiert.
views: # Liste der Ansichten, die in diesem Feld angezeigt werden können. views: !include fieldLists/medialibViews.yml
- type: table # Es wird eine Tabellenansicht verwendet.
mediaQuery: "(min-width: 0px)" # Die Tabellenansicht wird nur angezeigt, wenn die Bildschirmbreite mindestens 0px beträgt.
columns: # Liste der Spalten, die in der Tabelle angezeigt werden.
- source: file
defaultCallback: # Standard-Callback-Funktion, die ausgeführt wird, wenn keine andere spezifiziert ist. defaultCallback: # Standard-Callback-Funktion, die ausgeführt wird, wenn keine andere spezifiziert ist.
eval: | # Der Code wird als JavaScript evaluiert. eval: | # Der Code wird als JavaScript evaluiert.
@@ -109,7 +67,47 @@ permissions:
projections: projections:
dashboard: dashboard:
select: 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: fields:
- name: file - name: file
type: file type: file
@@ -118,6 +116,26 @@ fields:
de: Datei de: Datei
en: File 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 - name: sort
type: number type: number
meta: meta:

View File

@@ -2,6 +2,7 @@ name: module
meta: meta:
label: Module label: Module
allowExportAll: true
backup: backup:
active: true active: true
collectionName: backups collectionName: backups
@@ -11,6 +12,7 @@ meta:
columns: columns:
- source: type - source: type
name: Typ name: Typ
filter: true
subNavigation: subNavigation:
- name: modal - name: modal
@@ -25,6 +27,15 @@ meta:
parent.selectEntry(entry) // Die Funktion selectEntry auf dem übergeordneten Objekt wird mit dem Eintrag als Argument aufgerufen. parent.selectEntry(entry) // Die Funktion selectEntry auf dem übergeordneten Objekt wird mit dem Eintrag als Argument aufgerufen.
} }
//!js //!js
hooks:
post:
return:
type: javascript
file: hooks/clear_cache.js
put:
return:
type: javascript
file: hooks/clear_cache.js
permissions: permissions:
public: public:
@@ -122,6 +133,7 @@ fields:
meta: meta:
label: Icons im Kreis label: Icons im Kreis
widget: containerLessObject widget: containerLessObject
dependsOn: dependsOn:
eval: $parent.type == 'iconCycleCircle' eval: $parent.type == 'iconCycleCircle'
subFields: !include fieldLists/iconCycleCircle.yml subFields: !include fieldLists/iconCycleCircle.yml

View File

@@ -4,6 +4,10 @@ uploadPath: ../media/navigation
meta: meta:
label: "Navigation" label: "Navigation"
muiIcon: navigation muiIcon: navigation
allowExportAll: true
backup:
active: true
collectionName: backups
views: views:
- type: simpleList - type: simpleList
mediaQuery: "(max-width:599px)" mediaQuery: "(max-width:599px)"
@@ -28,6 +32,15 @@ permissions:
post: false post: false
put: true put: true
delete: false delete: false
hooks:
post:
return:
type: javascript
file: hooks/clear_cache.js
put:
return:
type: javascript
file: hooks/clear_cache.js
fields: fields:
- name: tree - name: tree

View File

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

View File

@@ -1,4 +1,4 @@
namespace: fontis namespace: fontis_v2
meta: meta:
imageUrl: imageUrl:
@@ -7,9 +7,248 @@ meta:
servers: servers:
- url: https://tibi-admin-server.code.testversion.online/api/v1/_/demo - url: https://tibi-admin-server.code.testversion.online/api/v1/_/demo
description: code-server description: code-server
dashboard: dashboard:
majorItems: 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 - collection: navigation
type: reference type: reference
style: style:
@@ -22,7 +261,44 @@ meta:
upper: rgba(3, 50, 59, 0.7) upper: rgba(3, 50, 59, 0.7)
lower: rgba(3, 50, 59) 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: collections:
- !include collections/navigation.yml - !include collections/navigation.yml
@@ -30,7 +306,15 @@ collections:
- !include collections/module.yml - !include collections/module.yml
- !include collections/medialib.yml - !include collections/medialib.yml
- !include collections/backups.yml - !include collections/backups.yml
- !include collections/ssr.yml
- !include collections/lighthouse.yml
- !include collections/lighthouse-subpaths.yml
assets: assets:
- name: img - name: img
path: 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") { if (release && typeof context !== "undefined") {
context.response.header("X-Release", release) context.response.header("X-Release", release)
} }
const apiClientBaseURL = "/api/"
module.exports = { module.exports = {
release, release,
apiClientBaseURL,
} }

View File

@@ -1,21 +1,26 @@
const apiSsrBaseURL = "http://localhost:8080/api/v1/_/fontis_v2"
module.exports = { module.exports = {
apiSsrBaseURL,
ssrValidatePath: function (path) { ssrValidatePath: function (path) {
// validate if path ssr rendering is ok, -1 = NOTFOUND, 0 = NO SSR, 1 = SSR // 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 // pe. use context.readCollection("product", {filter: {path: path}}) ... to validate dynamic urls
// / is de home // // / is de home
if (path == "/") return 1 // if (path == "/") return 1
// all other sites are in db
path = path?.replace(/^\//, "")
// // all other sites are in db
//path = path?.replace(/^\//, "")
console.log("PATH:", path)
// filter for path or alternativePaths // filter for path or alternativePaths
const resp = context.db.find("content", { const resp = context.db.find("page", {
filter: { filter: {
$or: [{ path }, { "alternativePaths.path": path }], $and: [{ path }],
}, },
selector: { _id: 1 }, selector: { _id: 1 },
}) })
console.log("RESP:", resp?.length)
if (resp && resp.length) { if (resp && resp.length) {
return 1 return 1
} }
@@ -23,5 +28,6 @@ module.exports = {
// not found // not found
return -1 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) 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 = { module.exports = {
log, log,
clearSSRCache, clearSSRCache,
obj2str, 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 () { ;(function () {
/** @type {HookResponse} */ /** @type {HookResponse} */
var response = null let response = null
var request = context.request() const request = context.request()
var url = request.query("url") let url = request.query("url")
var noCache = request.query("noCache") const noCache = request.query("noCache")
// add sentry trace id to head // add sentry trace id to head
var trace_id = context.debug.sentryTraceId() const trace_id = context.debug.sentryTraceId()
function addSentryTrace(content) { function addSentryTrace(content) {
return content.replace("</head>", '<meta name="sentry-trace" content="' + trace_id + '" /></head>') return content.replace("</head>", '<meta name="sentry-trace" content="' + trace_id + '" /></head>')
} }
@@ -18,7 +21,9 @@ const { obj2str, log } = require("../lib/utils")
if (url) { if (url) {
// comment will be printed to html later // comment will be printed to html later
var comment = "" let comment = ""
/** @type {Date} */ // @ts-ignore
context.ssrCacheValidUntil = null
url = url.split("?")[0] url = url.split("?")[0]
comment += "url: " + url comment += "url: " + url
@@ -31,7 +36,8 @@ const { obj2str, log } = require("../lib/utils")
} }
// check if url is in cache // check if url is in cache
var cache = /** @type {Ssr[]} */ // @ts-ignore
const cache =
!noCache && !noCache &&
context.db.find("ssr", { context.db.find("ssr", {
filter: { filter: {
@@ -40,6 +46,7 @@ const { obj2str, log } = require("../lib/utils")
}) })
if (cache && cache.length) { if (cache && cache.length) {
// use cache // use cache
context.response.header("X-SSR-Cache", "true")
throw { throw {
status: 200, status: 200,
log: false, log: false,
@@ -48,84 +55,50 @@ const { obj2str, log } = require("../lib/utils")
} }
// validate url // validate url
var status = 200 let status = 200
var pNorender = false let pNorender = false
var pNotfound = false let pNotfound = false
var pR = ssrValidatePath(url) const pR = ssrValidatePath(url)
if (pR < 0) { if (pR < 0) {
pNotfound = true pNotfound = true
} else if (!pR) { } else if (!pR) {
pNorender = true pNorender = true
} }
var head = "" let head = ""
var html = "" let html = ""
var error = "" let error = ""
comment += ", path: " + url comment += ", path: " + url
var cacheIt = false let cacheIt = false
if (pNorender) { if (pNorender) {
html = "<!-- NO SSR RENDERING -->" html = "<!-- NO SSR RENDERING -->"
} else if (pNotfound) { } else if (pNotfound) {
status = 404 status = 404
html = "404 NOT FOUND" html = "404 NOT FOUND"
} else { } else {
// try rendering, if error output plain html
try {
// @ts-ignore // @ts-ignore
context.ssrCache = {} context.ssrCache = {}
// @ts-ignore // @ts-ignore
context.ssrFetch = function (endpoint, options) { context.ssrRequest = ssrRequest
var data
if (ssrAllowedAPIEndpoints.indexOf(endpoint) > -1) {
var _options = Object.assign({}, options)
if (_options.sort) _options.sort = [_options.sort]
// try rendering, if error output plain html
try { 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 // include App.svelte and render it
// @ts-ignore // @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, url: url,
}) })
head = rendered.head head = rendered.head
html = rendered.html html = rendered.html
// add ssrCache to head // add ssrCache to head, cache is built in ssr.js/apiRequest
head += head +=
"\n\n" + "\n\n" +
"<script>window.__SSR_CACHE__ = " + "<script>window.__SSR_CACHE__ = " +
@@ -136,6 +109,7 @@ const { obj2str, log } = require("../lib/utils")
// status from webapp // status from webapp
// @ts-ignore // @ts-ignore
if (context.is404) { if (context.is404) {
// console.log("########## 404")
status = 404 status = 404
} else { } else {
cacheIt = true cacheIt = true
@@ -149,7 +123,7 @@ const { obj2str, log } = require("../lib/utils")
} }
// read html template and replace placeholders // 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("<!--HEAD-->", head)
tpl = tpl.replace("<!--HTML-->", html) tpl = tpl.replace("<!--HTML-->", html)
tpl = tpl.replace("<!--SSR.ERROR-->", error ? "<!--" + error + "-->" : "") tpl = tpl.replace("<!--SSR.ERROR-->", error ? "<!--" + error + "-->" : "")
@@ -158,6 +132,7 @@ const { obj2str, log } = require("../lib/utils")
// save cache if adviced // save cache if adviced
if (cacheIt && !noCache) { if (cacheIt && !noCache) {
context.db.create("ssr", { context.db.create("ssr", {
// context.debug.dump("ssr", {
path: url, path: url,
content: tpl, content: tpl,
}) })
@@ -171,7 +146,7 @@ const { obj2str, log } = require("../lib/utils")
} }
} else { } else {
// only admins are allowed to get without url parameter // only admins are allowed to get without url parameter
var auth = context.user.auth() const auth = context.user.auth()
if (!auth || auth.role !== 0) { if (!auth || auth.role !== 0) {
throw { throw {
status: 403, 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/nonexistent:/nonexistent
- ./tmp/.npm:/.npm - ./tmp/.npm:/.npm
working_dir: /data 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: expose:
- 3000 - 3000
labels: labels:

View File

@@ -1,3 +1,5 @@
const fs = require("fs")
const resolvePlugin = { const resolvePlugin = {
name: "resolvePlugin", name: "resolvePlugin",
setup(build) { setup(build) {
@@ -68,7 +70,9 @@ const bsMiddleware = []
if (process.argv[2] == "start") { if (process.argv[2] == "start") {
const { createProxyMiddleware } = require("http-proxy-middleware") 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( bsMiddleware.push(
createProxyMiddleware("/api", { createProxyMiddleware("/api", {
target: apiBase, target: apiBase,
@@ -77,12 +81,39 @@ if (process.argv[2] == "start") {
logLevel: "debug", 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 = { module.exports = {
sveltePlugin: sveltePlugin, sveltePlugin: sveltePlugin,
resolvePlugin: resolvePlugin, resolvePlugin: resolvePlugin,
options: options, options: options,
distDir,
watch: { watch: {
path: [__dirname + "/" + frontendDir + "/src/**/*"], path: [__dirname + "/" + frontendDir + "/src/**/*"],
}, },
@@ -103,7 +134,6 @@ module.exports = {
}), }),
], ],
}, },
ghostMode: false,
open: false, open: false,
// logLevel: "debug", // logLevel: "debug",
}, },

View File

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

View File

@@ -1,16 +1,19 @@
AddType application/javascript .mjs 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> <ifModule mod_rewrite.c>
RewriteEngine On RewriteEngine On
RewriteBase / 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} !-f
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-d
# leitet initale request an backend und nicht an spa.html weiter
RewriteRule (.*) /spa.html [QSA,L] 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> </ifModule>

Binary file not shown.

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"> <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> </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"> <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"/> <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"/> <rect x="1" y="1" width="66" height="66" rx="33" stroke="#fff" stroke-width="2"/>
</svg> </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"> <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> </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"> <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> </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"> <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> </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"> <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> </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"> <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> </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"> <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#kvekca80oa)"> <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> </g>
<defs> <defs>
<clipPath id="kvekca80oa"> <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"> <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="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="#333"/> <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> </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"> <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.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="#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="#343a40"/>
</svg> </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"> <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#3k3057tu3a)"> <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> </g>
<defs> <defs>
<clipPath id="3k3057tu3a"> <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" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Fontis</title> <title>Fontis</title>
<base href="/" /> <base href="/" />
<link rel="stylesheet" href="/dist/index.css?t=__TIMESTAMP__" /> <link rel="stylesheet" href="/dist/index.css?t=__TIMESTAMP__" />
<script <link rel="apple-touch-icon" sizes="180x180" href="/media/favicon/apple-touch-icon.png" type="image/x-icon" />
type="text/javascript" <link rel="icon" type="image/png" sizes="32x32" href="/media/favicon/favicon-32x32.png" type="image/x-icon" />
src="https://unpkg.com/external-svg-loader@latest/svg-loader.min.js" <link rel="icon" type="image/png" sizes="16x16" href="/media/favicon/favicon-16x16.png" type="image/x-icon" />
async <link rel="manifest" href="/site.webmanifest" />
></script> <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--> <!--HEAD-->
<!--PRELOAD--> <!--PRELOAD-->

View File

@@ -23,6 +23,7 @@
import ScrollDown from "./lib/components/widgets/scrollDown.svelte" import ScrollDown from "./lib/components/widgets/scrollDown.svelte"
import { loadLibrary } from "./lib/functions/loadLibrary" import { loadLibrary } from "./lib/functions/loadLibrary"
import { loadModules } from "./lib/functions/loadModules" import { loadModules } from "./lib/functions/loadModules"
import "external-svg-loader"
export let url = "" export let url = ""
if (url) { if (url) {
@@ -50,13 +51,14 @@
} else if (e.type == "teamMembers") { } else if (e.type == "teamMembers") {
teamRes[e.path] = e teamRes[e.path] = e
} else if (e.type == "jobOffers") { } else if (e.type == "jobOffers") {
jobOffersRes[e.path] = e jobOffersRes[Math.random()] = e
} else { } else {
pagesRes[e.path] = e pagesRes[e.path] = e
} }
}) })
$pages = pagesRes $pages = pagesRes
$team = teamRes $team = teamRes
$jobOffers = jobOffersRes $jobOffers = jobOffersRes
} }
@@ -88,15 +90,17 @@
getPages() getPages()
getLibrary() getLibrary()
getModules() getModules()
console.log("TESTR")
let activeMenu = false let activeMenu = false
$: { $: {
if (typeof window !== "undefined") {
if (activeMenu) { if (activeMenu) {
document.body.classList.add("overflow") document.body.classList.add("overflow")
} else { } else {
document.body.classList.remove("overflow") document.body.classList.remove("overflow")
} }
} }
}
</script> </script>
<main class=""> <main class="">

View File

@@ -1,121 +1,12 @@
import { apiBaseURL } from "./config" import { apiRequest } from "../../api/hooks/lib/ssr"
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
export const api = async <T>( export const api = async <T>(
endpoint: string, endpoint: string,
options?: { options?: ApiOptions,
method?: string
filter?: any
sort?: string
limit?: number
offset?: number
projection?: string
headers?: {
[key: string]: string
}
params?: {
[key: string]: string
}
},
body?: any body?: any
): Promise<{ data: T; count: number } | any> => { ): Promise<{ data: T; count: number } | any> => {
if (typeof window === "undefined") { let data = await apiRequest(endpoint, options, body)
// ssr
// @ts-ignore // @ts-ignore
return context.ssrFetch(endpoint, options) console.log(data, "data")
} return data
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
// @ts-ignore
return { data }
} }

View File

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

View File

@@ -8,7 +8,7 @@ html {
background-color: black; background-color: black;
} }
body { body {
color: #333 !important; color: #343a40 !important;
height: 100%; height: 100%;
background-color: #f9f9f9; background-color: #f9f9f9;
} }
@@ -16,7 +16,14 @@ body {
ul { ul {
list-style-type: none; list-style-type: none;
} }
.boxes {
.content {
ul {
padding-left: 20px;
list-style-type: disc;
}
}
}
ol { ol {
list-style-type: decimal; list-style-type: decimal;
} }
@@ -27,6 +34,7 @@ a {
font-weight: 700; font-weight: 700;
color: inherit; color: inherit;
font-weight: normal; font-weight: normal;
cursor: pointer;
} }
/* Tabellen */ /* Tabellen */
@@ -56,11 +64,11 @@ button {
border: none; border: none;
cursor: pointer; cursor: pointer;
font-size: inherit; font-size: inherit;
color: #333; color: #343a40;
} }
input, input,
select { select {
color: #333; color: #343a40;
width: 100%; width: 100%;
} }
.underline { .underline {
@@ -108,7 +116,7 @@ select {
top: 0px; top: 0px;
bottom: 0px; bottom: 0px;
width: 0px; width: 0px;
background: #000000; background: #343a40;
transition: width 0.5s ease-in; transition: width 0.5s ease-in;
} }
.fill:hover:after, .fill:hover:after,
@@ -143,7 +151,7 @@ swiper-slide {
z-index: 10000; z-index: 10000;
left: 0px; left: 0px;
bottom: -10px; bottom: -10px;
background: #000000; background: @signal-color;
height: 5px; height: 5px;
width: 0; width: 0;
animation: underlineEffect 15s linear forwards; animation: underlineEffect 15s linear forwards;

View File

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

View File

@@ -22,6 +22,7 @@
nextpage = pages[nextIndex] nextpage = pages[nextIndex]
} }
let blackBg = false let blackBg = false
if (typeof window !== "undefined") {
setInterval(() => { setInterval(() => {
if (location.pathname == "/") { if (location.pathname == "/") {
blackBg = true blackBg = true
@@ -37,8 +38,10 @@
showNext = true showNext = true
} }
}, 1000) }, 1000)
}
let showNext = true let showNext = true
$: { $: {
if (typeof window !== "undefined") {
if ($rerender) { if ($rerender) {
if (location.pathname != "/") { if (location.pathname != "/") {
getNextPage($navigation.pages) getNextPage($navigation.pages)
@@ -50,6 +53,7 @@
showNext = true showNext = true
} }
} }
}
</script> </script>
<div class="footer" class:black-bg="{blackBg}"> <div class="footer" class:black-bg="{blackBg}">
@@ -79,6 +83,12 @@
navigate('/impressum') navigate('/impressum')
}}">Impressum</button }}">Impressum</button
> >
<button
on:click="{() => {
$rerender = $rerender + 1
navigate('/contact')
}}">Contact (EN)</button
>
</div> </div>
<div class="contact"> <div class="contact">
<button>+49 (0) 711 655 700-0</button> <button>+49 (0) 711 655 700-0</button>

View File

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

View File

@@ -27,15 +27,18 @@
Object.assign(swiper, params) Object.assign(swiper, params)
swiper.initialize() swiper.initialize()
if (typeof window !== "undefined") {
// Add the 'active' class to the h1 of the first slide // Add the 'active' class to the h1 of the first slide
const firstSlideH1 = document.querySelector(".swiper-slide-active .titles h1") const firstSlideH1 = document.querySelector(".swiper-slide-active .titles h1")
if (firstSlideH1) { if (firstSlideH1) {
firstSlideH1.classList.add("active") firstSlideH1.classList.add("active")
} }
} }
}
}) })
function handleSlideChange() { function handleSlideChange() {
if (typeof window !== "undefined") {
document.querySelectorAll(".titles h1").forEach((h1) => { document.querySelectorAll(".titles h1").forEach((h1) => {
h1.classList.remove("active") h1.classList.remove("active")
}) })
@@ -47,6 +50,7 @@
} }
}, 600) }, 600)
} }
}
let teaser = teasers[0] let teaser = teasers[0]
</script> </script>
@@ -135,6 +139,7 @@
line-height: 1; line-height: 1;
font-weight: 500; font-weight: 500;
position: relative; position: relative;
color: @signal-color;
} }
h2 { h2 {

View File

@@ -13,6 +13,11 @@
let module = $modules[col.moduleImport] || {} let module = $modules[col.moduleImport] || {}
$: module = $modules[col.moduleImport] || {} $: module = $modules[col.moduleImport] || {}
$: console.log(
"peron",
$team,
Object.values($team).filter((p) => p.personType == "employee")
)
</script> </script>
{#if module.type == "iconCycleCircle"} {#if module.type == "iconCycleCircle"}

View File

@@ -26,10 +26,11 @@
export let i: number export let i: number
export let page: Page export let page: Page
export let personPage: boolean export let personPage: boolean
console.log("page", page, personPage) if (typeof window !== "undefined") {
window.addEventListener("popstate", function (event) { window.addEventListener("popstate", function (event) {
$rerender = $rerender + 1 $rerender = $rerender + 1
}) })
}
</script> </script>
{#if Object.keys(row).length} {#if Object.keys(row).length}
@@ -56,6 +57,7 @@
on:keydown on:keydown
on:click="{() => { on:click="{() => {
let chefs = Object.values($team).filter((p) => p.personType == 'chef') 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) let i = chefs.findIndex((p) => p.path == page.path)
if (i == chefs.length - 1) i = 0 if (i == chefs.length - 1) i = 0
else i++ else i++
@@ -141,6 +143,7 @@
h1 { h1 {
font-weight: 500; font-weight: 500;
font-size: 2rem; font-size: 2rem;
color: @signal-color;
} }
.top-header { .top-header {
img { img {

View File

@@ -2,7 +2,7 @@
import { mediaLibrary, pages, scrollToRowNr, team } from "../../store" import { mediaLibrary, pages, scrollToRowNr, team } from "../../store"
import Homepage from "./Homepage.svelte" import Homepage from "./Homepage.svelte"
import Pagebuilder from "./Pagebuilder.svelte" import Pagebuilder from "./Pagebuilder.svelte"
import { apiBaseURL } from "../../../config" import { apiBaseURL, baseURL } from "../../../config"
import { onMount } from "svelte" import { onMount } from "svelte"
export let path: string export let path: string
@@ -24,6 +24,7 @@
} }
onMount(() => { onMount(() => {
if (typeof window !== "undefined") {
if ($scrollToRowNr !== -1) { if ($scrollToRowNr !== -1) {
if (!$scrollToRowNr) { if (!$scrollToRowNr) {
$scrollToRowNr = -1 $scrollToRowNr = -1
@@ -40,6 +41,7 @@
}) })
$scrollToRowNr = -1 $scrollToRowNr = -1
} }
}
}) })
$: { $: {
@@ -49,6 +51,31 @@
} }
</script> </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 == '/'}"> <div class="rows" class:HP="{path == '/'}">
{#if page} {#if page}
{#if path == "/"}<Homepage />{/if} {#if path == "/"}<Homepage />{/if}
@@ -62,11 +89,11 @@
? 'margin-top: 0px; padding-top: 0px;' ? 'margin-top: 0px; padding-top: 0px;'
: ''}" : ''}"
> >
{#if row.backgroundImage && mediaLibrary[row.backgroundImage]} {#if row.backgroundImage && $mediaLibrary[row.backgroundImage]}
<div class="background-image"> <div class="background-image">
<img <img
src="{`${apiBaseURL}medialib/${row?.backgroundImage}/${ src="{`${apiBaseURL}medialib/${row?.backgroundImage}/${
mediaLibrary?.[row?.backgroundImage]?.file?.src $mediaLibrary?.[row?.backgroundImage]?.file?.src
}`}" }`}"
alt="img" alt="img"
/> />

View File

@@ -24,7 +24,11 @@
</script> </script>
<div class="card"> <div class="card">
<img src="{apiBaseURL}medialib/{card.image}/{$mediaLibrary?.[card?.image]?.file?.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 class="content">
<div <div
@@ -160,8 +164,8 @@
height: 1.8vw; height: 1.8vw;
max-height: 25px; max-height: 25px;
border-radius: 15px; border-radius: 15px;
border: 2px solid #4f4f4f; border: 2px solid #6b6868;
color: #4f4f4f; color: #6b6868;
background-color: @bg-color-secondary; background-color: @bg-color-secondary;
display: flex; display: flex;
justify-content: center; justify-content: center;

View File

@@ -2,11 +2,13 @@
export let persons: Page[] export let persons: Page[]
let boxes = persons.map((p) => p.personPreview.name) let boxes = persons.map((p) => p.personPreview.name)
const sortByFirstName = (a, b) => { const sortByFirstName = (a, b) => {
const nameA = a.name.split(" ")[0] // Extracts the first name from "First Last" const nameA = a?.split(" ")[0] // Extracts the first name from "First Last"
const nameB = b.name.split(" ")[0] const nameB = b?.split(" ")[0]
return nameA.localeCompare(nameB) console.log("nameA", nameA, "nameB", nameB, "comp", nameA?.localeCompare(nameB))
return nameA?.localeCompare(nameB)
} }
boxes = boxes.sort(sortByFirstName) boxes = boxes.sort(sortByFirstName)
$: console.log(boxes, "boxes", persons)
</script> </script>
<div class="boxList"> <div class="boxList">
@@ -26,7 +28,7 @@
gap: 20px; gap: 20px;
.box { .box {
padding: 5px 10px; padding: 5px 10px;
background-color: @bg-color-secondary; background-color: @signal-color;
color: @font-color-secondary; color: @font-color-secondary;
font-weight: bold; font-weight: bold;
} }

View File

@@ -49,6 +49,7 @@
href="{apiBaseURL}medialib/{nE.file}/{$mediaLibrary?.[nE?.file]?.file?.src}" href="{apiBaseURL}medialib/{nE.file}/{$mediaLibrary?.[nE?.file]?.file?.src}"
style="text-decoration: none;" style="text-decoration: none;"
download="{apiBaseURL}medialib/{nE.file}/{$mediaLibrary?.[nE?.file]?.file?.src}" download="{apiBaseURL}medialib/{nE.file}/{$mediaLibrary?.[nE?.file]?.file?.src}"
> >
<button class="more">mehr</button></a <button class="more">mehr</button></a
> >

View File

@@ -6,7 +6,9 @@
export let opened = "" export let opened = ""
let jobOffers = pages.map((p) => p.jobOffer) let jobOffers = pages.map((p) => p.jobOffer)
onMount(() => { onMount(() => {
if (typeof window !== "undefined") {
opened = location.search.split("=").at(-1) opened = location.search.split("=").at(-1)
}
}) })
</script> </script>
@@ -35,7 +37,7 @@
{@html box.text} {@html box.text}
{#if box.emailButton} {#if box.emailButton}
<a <a
href="mailto:info@fontis.de?subject={box.emailSubject || 'Bewerbung'}" href="mailto:bewerbung@fontis.de?subject={box.emailSubject || 'Bewerbung'}"
style="text-decoration: none;" style="text-decoration: none;"
class="button" class="button"
> >
@@ -49,19 +51,19 @@
<style lang="less"> <style lang="less">
@import "../../assets/css/main.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 { .boxes {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-family: "Libre Franklin", "sans-serif"; 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 { .box {
border-bottom: 2px dotted @bg-color-secondary; border-bottom: 2px dotted @bg-color-secondary;
display: flex; display: flex;
@@ -85,9 +87,13 @@
justify-content: space-between; justify-content: space-between;
} }
.content { .content {
max-height: 1000px; max-height: 3000px;
overflow: hidden; overflow: hidden;
transition: max-height 1s ease-in; transition: max-height 1s ease-in;
ul {
padding-left: 20px;
list-style-type: disc;
}
&.closed { &.closed {
max-height: 0px; max-height: 0px;

View File

@@ -4,12 +4,17 @@
export let pageId: string export let pageId: string
export let col: Column export let col: Column
console.log("icons:", col.iconBlocks, $mediaLibrary, $mediaLibrary[col.iconBlocks[0].icon])
</script> </script>
<div class="iconBlock"> <div class="iconBlock">
{#each col.iconBlocks as icon} {#each col.iconBlocks as icon}
<div class="icon"> <div class="icon">
<img src="{`${apiBaseURL}medialib/${icon.icon}/${mediaLibrary?.[icon?.icon]?.file?.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"> <div class="text">
<em>{icon.bigText}</em> <em>{icon.bigText}</em>
<p>{icon.smallText}</p> <p>{icon.smallText}</p>

View File

@@ -5,10 +5,12 @@
export let pageId: string export let pageId: string
console.log("YEY") console.log("YEY")
let active = -1 let active = -1
if (typeof window !== "undefined") {
setInterval(() => { setInterval(() => {
active += 1 active += 1
if (active == iconCycleSquare.boxes.length) active = 0 if (active == iconCycleSquare.boxes.length) active = 0
}, 1250) }, 1250)
}
</script> </script>
<div class="iconCycleSquares"> <div class="iconCycleSquares">
@@ -17,8 +19,8 @@
<div class="content"> <div class="content">
<div class="icon"> <div class="icon">
<svg <svg
stroke="{i == active ? 'black' : 'white'}" stroke="{i == active ? '#5b6e98' : 'white'}"
fill="{i == active ? 'black' : 'white'}" fill="{i == active ? '#5b6e98' : 'white'}"
data-src="{apiBaseURL}medialib/{box?.icon}/{$mediaLibrary?.[box?.icon]?.file?.src}"></svg> data-src="{apiBaseURL}medialib/{box?.icon}/{$mediaLibrary?.[box?.icon]?.file?.src}"></svg>
</div> </div>
<div class="text"> <div class="text">
@@ -43,8 +45,8 @@
font-size: 1rem; font-size: 1rem;
} }
.box { .box {
border: 4px solid @bg-color-secondary; border: 4px solid @signal-color;
background-color: @bg-color-secondary; background-color: @signal-color;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -52,7 +54,7 @@
padding: 10px; padding: 10px;
&.active { &.active {
background-color: @bg-color; background-color: @bg-color;
color: @font-color; color: #5b6e98;
} }
aspect-ratio: 1/1; aspect-ratio: 1/1;
width: calc((100% / 2) - 10px); width: calc((100% / 2) - 10px);

View File

@@ -26,11 +26,12 @@
circles = circles circles = circles
}) })
let focused = -1 let focused = -1
if (typeof window !== "undefined") {
setInterval(() => { setInterval(() => {
focused += 1 focused += 1
if (focused == count) focused = 0 if (focused == count) focused = 0
const svgObject = document.getElementById("mySvgObject" + focused)
}, 1000) }, 1000)
}
</script> </script>
<div class="container"> <div class="container">
@@ -52,8 +53,8 @@
<div class="icon"> <div class="icon">
<svg <svg
id="mySvgObject{i}" id="mySvgObject{i}"
stroke="{i == focused ? 'white' : 'black'}" stroke="{i == focused ? 'white' : '#5b6e98'}"
fill="{i == focused ? 'white' : 'black'}" fill="{i == focused ? 'white' : '#5b6e98'}"
data-src="{apiBaseURL}medialib/{iconCycleCircle?.boxes[i]?.icon}/{$mediaLibrary[ data-src="{apiBaseURL}medialib/{iconCycleCircle?.boxes[i]?.icon}/{$mediaLibrary[
iconCycleCircle?.boxes[i]?.icon iconCycleCircle?.boxes[i]?.icon
]?.file?.src}"></svg> ]?.file?.src}"></svg>
@@ -111,7 +112,7 @@
width: 180px; width: 180px;
height: 180px; height: 180px;
margin: auto; margin: auto;
background: rgb(0, 0, 0); background: @signal-color;
border-radius: 50%; border-radius: 50%;
& > .content { & > .content {
font-weight: bold; font-weight: bold;
@@ -130,8 +131,8 @@
width: 180px; width: 180px;
overflow: hidden; overflow: hidden;
height: 180px; height: 180px;
background: rgba(255, 255, 255, 0); background: @signal-color;
border: 4px solid @bg-color-secondary; border: 4px solid @signal-color;
z-index: 100; z-index: 100;
transform-origin: center; transform-origin: center;
border-radius: 50%; border-radius: 50%;
@@ -182,7 +183,7 @@
&::before { &::before {
content: ""; content: "";
position: absolute; position: absolute;
background: rgb(0, 0, 0); background: @signal-color;
border-radius: 50%; border-radius: 50%;
top: -50%; top: -50%;
left: 0; left: 0;
@@ -194,13 +195,13 @@
&.focused { &.focused {
background: @bg-color-secondary !important; background: @bg-color-secondary !important;
.number { .number {
color: @font-color !important; color: @signal-color !important;
} }
.content { .content {
color: @font-color-secondary !important; color: @font-color-secondary !important;
} }
.half { .half {
background: @bg-color-secondary !important; background: @signal-color !important;
&::before { &::before {
background: @bg-color !important; background: @bg-color !important;
} }

View File

@@ -7,7 +7,11 @@
</script> </script>
<div class="image-container"> <div class="image-container">
<img src="{`${apiBaseURL}medialib/${image}/${$mediaLibrary[image]?.file?.src}`}" alt="img" /> <img
src="{`${apiBaseURL}medialib/${image}/${$mediaLibrary[image]?.file?.src}`}"
alt="{$mediaLibrary[image]?.alt || ''}"
title="{$mediaLibrary[image]?.title || ''}"
/>
</div> </div>
{#if col && col.icons} {#if col && col.icons}
<div class="icons"> <div class="icons">
@@ -16,7 +20,8 @@
<a href="{icon.link}" style="text-decoration: none;" target="_blank"> <a href="{icon.link}" style="text-decoration: none;" target="_blank">
<img <img
src="{`${apiBaseURL}medialib/${icon.icon}/${$mediaLibrary[icon.icon]?.file?.src}`}" src="{`${apiBaseURL}medialib/${icon.icon}/${$mediaLibrary[icon.icon]?.file?.src}`}"
alt="img" alt="{$mediaLibrary[icon.icon]?.alt || ''}"
title="{$mediaLibrary[icon.icon]?.title || ''}"
/> />
</a> </a>
</div> </div>
@@ -43,5 +48,8 @@
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
gap: 20px; gap: 20px;
img {
width: 48px;
}
} }
</style> </style>

View File

@@ -11,7 +11,8 @@
<div class="icon"> <div class="icon">
<img <img
src="{`${apiBaseURL}medialib/${col.infoBoard.icon}/${$mediaLibrary[col.infoBoard.icon]?.file?.src}`}" src="{`${apiBaseURL}medialib/${col.infoBoard.icon}/${$mediaLibrary[col.infoBoard.icon]?.file?.src}`}"
alt="img" alt="{$mediaLibrary[col.infoBoard.icon]?.alt || ''}"
title="{$mediaLibrary[col.infoBoard.icon]?.title || ''}"
/> />
</div> </div>
<div class="title"> <div class="title">

View File

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

View File

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

View File

@@ -11,13 +11,26 @@
{#each jobOffers as job, i} {#each jobOffers as job, i}
{#if i < 3} {#if i < 3}
<button <button
class="page-ref" class="row-ref fill"
on:click="{() => { on:click="{() => {
$rerender = $rerender + 1 $rerender = $rerender + 1
navigate(pageReference + '?elem=' + pages[i].id) navigate(pageReference + '?elem=' + pages[i].id)
}}" }}"
on:mouseenter="{() => {
focused = i
}}"
on:mouseleave="{() => {
focused = -1
}}"
> >
<div>
{job.title} {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> </button>
{:else} {:else}
<button <button
@@ -27,18 +40,13 @@
on:mouseleave="{() => { on:mouseleave="{() => {
focused = -1 focused = -1
}}" }}"
class="row-ref fill"
on:click="{() => { on:click="{() => {
$rerender = $rerender + 1 $rerender = $rerender + 1
navigate(pageReference) navigate(pageReference)
}}" }}"
class="page-ref"
> >
<div>Mehr offene Stellen</div> <div>Mehr offene Stellen</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>
</button> </button>
{/if} {/if}
{/each} {/each}
@@ -75,12 +83,15 @@
width: 20px; width: 20px;
height: auto; height: auto;
} }
&:hover {
color: white !important;
}
} }
.page-ref { .page-ref {
background-color: @bg-color-secondary; background-color: @signal-color;
color: @font-color-secondary; color: @font-color-secondary;
border: 2px solid @bg-color-secondary; border: 2px solid @signal-color;
} }
} }
</style> </style>

View File

@@ -5,6 +5,7 @@
export let pageId: string export let pageId: string
export let persons: Page[] export let persons: Page[]
persons = persons.sort((a, b) => a.sort - b.sort)
let hover = -1 let hover = -1
</script> </script>
@@ -22,24 +23,26 @@
<!-- Initial Image --> <!-- Initial Image -->
<img <img
class="initial" class="initial"
src="{`${apiBaseURL}medialib/${p.personPreview.initialImage}/${ src="{`${apiBaseURL}medialib/${p?.personPreview?.initialImage}/${
$mediaLibrary[p.personPreview.initialImage]?.file?.src $mediaLibrary[p?.personPreview?.initialImage]?.file?.src
}`}" }`}"
alt="img"
style="opacity: {hover == i ? 0 : 1}" style="opacity: {hover == i ? 0 : 1}"
alt="{$mediaLibrary[p?.personPreview?.initialImage]?.alt || ''}"
title="{$mediaLibrary[p?.personPreview?.initialImage]?.title || ''}"
/> />
<!-- Hover Image --> <!-- Hover Image -->
<img <img
class="hover" class="hover"
src="{`${apiBaseURL}medialib/${p.personPreview.hoverImage}/${ src="{`${apiBaseURL}medialib/${p?.personPreview?.hoverImage}/${
$mediaLibrary[p.personPreview.hoverImage]?.file?.src $mediaLibrary[p?.personPreview?.hoverImage]?.file?.src
}`}" }`}"
alt="img"
style="opacity: {hover == i ? 1 : 0}" style="opacity: {hover == i ? 1 : 0}"
alt="{$mediaLibrary[p?.personPreview?.hoverImage]?.alt || ''}"
title="{$mediaLibrary[p?.personPreview?.hoverImage]?.title || ''}"
/> />
</div> </div>
<div class="text"> <div class="text">
{p.personPreview.name} {p?.personPreview?.name}
</div> </div>
</button> </button>
{/each} {/each}
@@ -79,9 +82,8 @@
} }
.text { .text {
width: 100%; width: 100%;
background-color: @bg-color-secondary; background-color: @signal-color;
color: @font-color-secondary; color: @font-color-secondary;
border: 2px solid @bg-color-secondary;
padding: 2px 15px; padding: 2px 15px;
font-weight: bold; font-weight: bold;
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,8 +9,10 @@
"scripts": { "scripts": {
"validate": "svelte-check && tsc --noEmit", "validate": "svelte-check && tsc --noEmit",
"dev": "node scripts/esbuild-wrapper.js watch", "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": "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: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: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", "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", "autoprefixer": "^10.4.14",
"core-js": "3.30.1", "core-js": "3.30.1",
"cssnano": "^6.0.0", "cssnano": "^6.0.0",
"external-svg-loader": "^1.6.10",
"postcss-import": "^15.1.0", "postcss-import": "^15.1.0",
"postcss-load-config": "^4.0.1", "postcss-load-config": "^4.0.1",
"postcss-nested": "^6.0.1", "postcss-nested": "^6.0.1",

64
types/global.d.ts vendored
View File

@@ -3,10 +3,35 @@ interface FileField {
src: string src: string
type: string type: string
} }
interface Ssr {
id?: string
path: string
content: string
validUntil: any // go Time
}
interface Pages { interface Pages {
[key: string]: Page [key: string]: Page
} }
interface ApiResult<T> {
data: T
count: number
}
interface ApiOptions {
method?: string
filter?: any
sort?: string
lookup?: string
limit?: number
offset?: number
projection?: string
headers?: {
[key: string]: string
}
params?: {
[key: string]: string
}
}
interface Page { interface Page {
path: string path: string
@@ -19,6 +44,11 @@ interface Page {
jobOffer: jobOffer jobOffer: jobOffer
rows: Row[] rows: Row[]
id: string id: string
meta: {
title: string
description: string
keywords: string
}
} }
interface teaserHomepage { interface teaserHomepage {
@@ -55,17 +85,33 @@ type Column =
interface MediaLibrary { interface MediaLibrary {
file: FileField file: FileField
alt: string
title: string
id: string id: string
} }
type Module = type BaseModule = { id: string }
| { id: string; type: "iconCycleCircle"; iconCycleCircle: IconCycleCircle }
| { id: string; type: "iconCycleSquare"; iconCycleSquare: IconCycleSquare } type IconCycleModule = BaseModule & {
| { id: string; type: "worldCard"; worldCard: WorldCard } type: "iconCycleCircle" | "iconCycleSquare"
| { id: string; type: "chefTeam" } iconCycle?: IconCycleCircle | IconCycleSquare
| { id: string; type: "employeeTeam" } }
| { id: string; type: "jobOfferLink"; jobOfferPage: string }
| { id: string; type: "jobOffer" } type WorldCardModule = BaseModule & {
type: "worldCard"
worldCard?: WorldCard
}
type JobOfferModule = BaseModule & {
type: "jobOfferLink" | "jobOffer"
jobOfferPage?: string
}
type SimpleModule = BaseModule & {
type: "chefTeam" | "employeeTeam"
}
type Module = IconCycleModule | WorldCardModule | JobOfferModule | SimpleModule
interface Publication { interface Publication {
file: string file: string

View File

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