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
TIBI_PREFIX=tibi
TIBI_NAMESPACE=fontis
TIBI_NAMESPACE=fontis_v2
UID=100
GID=101
RELEASE_ORG_SLUG=webmakers-gmbh
RELEASE_PROJECT_SLUG=fontis
START_SCRIPT=:ssr

View File

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

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,47 @@
- type: table
mediaQuery: "(min-width: 0px)"
defaultSelect: false
selectionPriority: 2
fileDropArea:
label:
{
de: "Ziehen Sie Dateien per Drag and Drop hierher oder klicken Sie, um Dateien auszuwählen.",
en: "Drag and drop some files here, or click to upload.",
}
helperText: { de: "Maximale Uploadgröße: 1,54 MB", en: "Maximum upload size: 1.54MB" }
targetField: file
pageAsDropArea: false
columns:
- source: file
name: Datei
filter: true
- source: title
name: Titel
filter: true
- source: alt
name: Alternativtext
filter: true
- type: cardList
mediaQuery: "(min-width: 1200px)"
selectionPriority: 1
fileDropArea:
label:
{
de: "Ziehen Sie Dateien per Drag and Drop hierher oder klicken Sie, um Dateien auszuwählen.",
en: "Drag and drop some files here, or click to upload.",
}
helperText: { de: "Maximale Uploadgröße: 1,54 MB", en: "Maximum upload size: 1.54MB" }
targetField: file
pageAsDropArea: false
fields:
- source: file
name: Datei
filter: true
- source: title
name: Titel
filter: true
- source: alt
name: Alternativtext
filter: true

View File

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

View File

@@ -0,0 +1,29 @@
name: lighthouseSubpath
meta:
label: Lighthouse Subpaths
muiIcon: web
views:
- type: table
columns:
- source: lighthouseSubpath
permissions:
public:
methods:
get: false
post: false
put: false
delete: false
user:
methods:
get: true
post: true
put: true
delete: true
fields:
- type: string
name: lighthouseSubpath
meta:
label: PagespeedPaths

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

View File

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

Before

Width:  |  Height:  |  Size: 177 B

After

Width:  |  Height:  |  Size: 180 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

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

Before

Width:  |  Height:  |  Size: 371 B

After

Width:  |  Height:  |  Size: 374 B

View File

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

Before

Width:  |  Height:  |  Size: 395 B

After

Width:  |  Height:  |  Size: 401 B

View File

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

Before

Width:  |  Height:  |  Size: 366 B

After

Width:  |  Height:  |  Size: 369 B

View File

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

Before

Width:  |  Height:  |  Size: 297 B

After

Width:  |  Height:  |  Size: 300 B

View File

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

Before

Width:  |  Height:  |  Size: 599 B

After

Width:  |  Height:  |  Size: 602 B

View File

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

Before

Width:  |  Height:  |  Size: 213 B

After

Width:  |  Height:  |  Size: 216 B

View File

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

Before

Width:  |  Height:  |  Size: 421 B

After

Width:  |  Height:  |  Size: 424 B

View File

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

Before

Width:  |  Height:  |  Size: 292 B

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 981 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

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

View File

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

Before

Width:  |  Height:  |  Size: 667 B

After

Width:  |  Height:  |  Size: 673 B

View File

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

Before

Width:  |  Height:  |  Size: 423 B

After

Width:  |  Height:  |  Size: 426 B

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,32 +22,36 @@
nextpage = pages[nextIndex]
}
let blackBg = false
setInterval(() => {
if (location.pathname == "/") {
blackBg = true
} else {
blackBg = false
}
if (typeof window !== "undefined") {
setInterval(() => {
if (location.pathname == "/") {
blackBg = true
} else {
blackBg = false
}
getNextPage($navigation.pages)
getNextPage($navigation.pages)
if (location.pathname.split("/").filter((s) => s).length >= 2) {
showNext = false
} else {
showNext = true
}
}, 1000)
if (location.pathname.split("/").filter((s) => s).length >= 2) {
showNext = false
} else {
showNext = true
}
}, 1000)
}
let showNext = true
$: {
if ($rerender) {
if (location.pathname != "/") {
getNextPage($navigation.pages)
if (typeof window !== "undefined") {
if ($rerender) {
if (location.pathname != "/") {
getNextPage($navigation.pages)
}
}
if (location.pathname.split("/").filter((s) => s).length >= 2) {
showNext = false
} else {
showNext = true
}
}
if (location.pathname.split("/").filter((s) => s).length >= 2) {
showNext = false
} else {
showNext = true
}
}
</script>
@@ -79,6 +83,12 @@
navigate('/impressum')
}}">Impressum</button
>
<button
on:click="{() => {
$rerender = $rerender + 1
navigate('/contact')
}}">Contact (EN)</button
>
</div>
<div class="contact">
<button>+49 (0) 711 655 700-0</button>

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
import { mediaLibrary, pages, scrollToRowNr, team } from "../../store"
import Homepage from "./Homepage.svelte"
import Pagebuilder from "./Pagebuilder.svelte"
import { apiBaseURL } from "../../../config"
import { apiBaseURL, baseURL } from "../../../config"
import { onMount } from "svelte"
export let path: string
@@ -24,21 +24,23 @@
}
onMount(() => {
if ($scrollToRowNr !== -1) {
if (!$scrollToRowNr) {
$scrollToRowNr = -1
return
}
let element = document.getElementById("row-" + $scrollToRowNr)
if (!element) {
$scrollToRowNr = -1
return
}
if (typeof window !== "undefined") {
if ($scrollToRowNr !== -1) {
if (!$scrollToRowNr) {
$scrollToRowNr = -1
return
}
let element = document.getElementById("row-" + $scrollToRowNr)
if (!element) {
$scrollToRowNr = -1
return
}
element?.scrollIntoView({
behavior: "smooth",
})
$scrollToRowNr = -1
element?.scrollIntoView({
behavior: "smooth",
})
$scrollToRowNr = -1
}
}
})
@@ -49,6 +51,31 @@
}
</script>
<svelte:head>
{#key page}
<!-- Title -->
{#if page?.pageTitle}
<title>{page.pageTitle}</title>
{:else if page?.meta?.title}
<title>{page.meta.title}</title>
{/if}
<!-- Description -->
{#if page?.meta?.description}
<meta name="description" content="{page.meta.description}" />
{/if}
<!-- Keywords -->
{#if page?.meta?.keywords}
<meta name="keywords" content="{page.meta.keywords}" />
{/if}
{#if page?.active === false}
<meta name="robots" content="noindex" />
{/if}
<link rel="canonical" href="{baseURL + page?.path}" />
{/key}
</svelte:head>
<div class="rows" class:HP="{path == '/'}">
{#if page}
{#if path == "/"}<Homepage />{/if}
@@ -62,11 +89,11 @@
? 'margin-top: 0px; padding-top: 0px;'
: ''}"
>
{#if row.backgroundImage && mediaLibrary[row.backgroundImage]}
{#if row.backgroundImage && $mediaLibrary[row.backgroundImage]}
<div class="background-image">
<img
src="{`${apiBaseURL}medialib/${row?.backgroundImage}/${
mediaLibrary?.[row?.backgroundImage]?.file?.src
$mediaLibrary?.[row?.backgroundImage]?.file?.src
}`}"
alt="img"
/>

View File

@@ -24,7 +24,11 @@
</script>
<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
@@ -160,8 +164,8 @@
height: 1.8vw;
max-height: 25px;
border-radius: 15px;
border: 2px solid #4f4f4f;
color: #4f4f4f;
border: 2px solid #6b6868;
color: #6b6868;
background-color: @bg-color-secondary;
display: flex;
justify-content: center;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,7 +11,8 @@
<div class="icon">
<img
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 class="title">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

64
types/global.d.ts vendored
View File

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

View File

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