From fd5af432db6a54970004fa8e1249646ce03a78a1 Mon Sep 17 00:00:00 2001 From: robin Date: Thu, 7 Dec 2023 20:24:09 +0000 Subject: [PATCH] coloring --- .env | 1 + api/collections/backups.yml | 7 + api/collections/content.yml | 10 + api/collections/medialib.yml | 9 + api/collections/module.yml | 9 + api/collections/navigation.yml | 9 + api/collections/ssr.yml | 4 +- api/config.yml | 1 + api/config.yml.env | 3 +- api/hooks/clear_cache.js | 5 + api/hooks/config-client.js | 2 + api/hooks/config.js | 21 +- api/hooks/lib/ssr-server.js | 36 ++++ api/hooks/lib/ssr.js | 182 ++++++++++++++++++ api/hooks/ssr/get_read.js | 101 ++++------ api/templates/spa.html | 34 ++++ docker-compose-local.yml | 2 +- esbuild.config.js | 34 +++- esbuild.config.server.js | 1 - frontend/.htaccess | 11 +- frontend/media/BurgerMenu.svg | 2 +- frontend/media/Logo Quer.svg | 44 ++++- frontend/media/ToTop.svg | 2 +- frontend/media/arrow-l-fat.svg | 2 +- frontend/media/calendar.svg | 2 +- frontend/media/down.svg | 2 +- frontend/media/file.svg | 4 +- frontend/media/up.svg | 2 +- frontend/src/App.svelte | 12 +- frontend/src/api.ts | 119 +----------- frontend/src/index.ts | 2 +- frontend/src/lib/assets/css/base.less | 4 +- frontend/src/lib/assets/css/main.less | 5 +- frontend/src/lib/components/Footer.svelte | 46 +++-- .../components/Pagebuilder/Homepage.svelte | 31 +-- .../components/Pagebuilder/Pagebuilder.svelte | 9 +- .../lib/components/Pagebuilder/Rows.svelte | 30 +-- .../components/widgets/Worldcard/card.svelte | 4 +- .../src/lib/components/widgets/boxlist.svelte | 2 +- .../components/widgets/extendableBox.svelte | 8 +- .../components/widgets/iconCycleBox.svelte | 19 +- .../components/widgets/iconCycleCircle.svelte | 27 +-- .../src/lib/components/widgets/moreBtn.svelte | 6 +- .../lib/components/widgets/nestedCard.svelte | 2 +- .../components/widgets/pageLinkBlocks.svelte | 8 +- .../src/lib/components/widgets/persons.svelte | 2 +- .../lib/components/widgets/scrollDown.svelte | 36 ++-- .../lib/components/widgets/scrollTop.svelte | 18 +- .../src/lib/components/widgets/topDown.svelte | 2 +- frontend/src/lib/functions/loadNavigation.ts | 1 + package.json | 4 +- types/global.d.ts | 27 ++- 52 files changed, 638 insertions(+), 328 deletions(-) create mode 100644 api/hooks/clear_cache.js create mode 100644 api/hooks/lib/ssr-server.js create mode 100644 api/hooks/lib/ssr.js create mode 100644 api/templates/spa.html diff --git a/.env b/.env index 7540e26..fde0359 100644 --- a/.env +++ b/.env @@ -5,3 +5,4 @@ UID=100 GID=101 RELEASE_ORG_SLUG=webmakers-gmbh RELEASE_PROJECT_SLUG=fontis +START_SCRIPT=:ssr \ No newline at end of file diff --git a/api/collections/backups.yml b/api/collections/backups.yml index 1876bef..0d6051a 100644 --- a/api/collections/backups.yml +++ b/api/collections/backups.yml @@ -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 diff --git a/api/collections/content.yml b/api/collections/content.yml index bc0beb3..c6280f3 100644 --- a/api/collections/content.yml +++ b/api/collections/content.yml @@ -193,6 +193,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: diff --git a/api/collections/medialib.yml b/api/collections/medialib.yml index 5f4e3ee..111ef3d 100644 --- a/api/collections/medialib.yml +++ b/api/collections/medialib.yml @@ -111,6 +111,15 @@ permissions: projections: dashboard: select: +hooks: + post: + return: + type: javascript + file: hooks/clear_cache.js + put: + return: + type: javascript + file: hooks/clear_cache.js fields: - name: file diff --git a/api/collections/module.yml b/api/collections/module.yml index 5de4e47..c743861 100644 --- a/api/collections/module.yml +++ b/api/collections/module.yml @@ -27,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: diff --git a/api/collections/navigation.yml b/api/collections/navigation.yml index 224a9da..09a59e0 100644 --- a/api/collections/navigation.yml +++ b/api/collections/navigation.yml @@ -32,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 diff --git a/api/collections/ssr.yml b/api/collections/ssr.yml index f158c3f..fe76e10 100644 --- a/api/collections/ssr.yml +++ b/api/collections/ssr.yml @@ -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: diff --git a/api/config.yml b/api/config.yml index 27b0e15..0d712fd 100644 --- a/api/config.yml +++ b/api/config.yml @@ -48,6 +48,7 @@ collections: - !include collections/module.yml - !include collections/medialib.yml - !include collections/backups.yml + - !include collections/ssr.yml assets: - name: img diff --git a/api/config.yml.env b/api/config.yml.env index 78e7887..bccf632 100644 --- a/api/config.yml.env +++ b/api/config.yml.env @@ -1 +1,2 @@ -TOKEN=geheim \ No newline at end of file +TOKEN=geheim +SSR_TOKEN=owshwerNwoa \ No newline at end of file diff --git a/api/hooks/clear_cache.js b/api/hooks/clear_cache.js new file mode 100644 index 0000000..7491038 --- /dev/null +++ b/api/hooks/clear_cache.js @@ -0,0 +1,5 @@ +var utils = require("./lib/utils") + +;(function () { + utils.clearSSRCache() +})() diff --git a/api/hooks/config-client.js b/api/hooks/config-client.js index 0736b02..78ee77a 100644 --- a/api/hooks/config-client.js +++ b/api/hooks/config-client.js @@ -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, } diff --git a/api/hooks/config.js b/api/hooks/config.js index 50301c9..e76391d 100644 --- a/api/hooks/config.js +++ b/api/hooks/config.js @@ -1,21 +1,26 @@ +const apiSsrBaseURL = "http://localhost:8080/api/v1/_/fontis_v2" + module.exports = { + apiSsrBaseURL, ssrValidatePath: function (path) { // validate if path ssr rendering is ok, -1 = NOTFOUND, 0 = NO SSR, 1 = SSR // pe. use context.readCollection("product", {filter: {path: path}}) ... to validate dynamic urls - // / is de home - if (path == "/") return 1 - - // all other sites are in db - path = path?.replace(/^\//, "") + // // / is de home + // if (path == "/") return 1 + // // all other sites are in db + //path = path?.replace(/^\//, "") + console.log("PATH:", path) // filter for path or alternativePaths - const resp = context.db.find("content", { + const resp = context.db.find("page", { filter: { - $or: [{ path }, { "alternativePaths.path": path }], + $and: [{ path }], }, + selector: { _id: 1 }, }) + console.log("RESP:", resp?.length) if (resp && resp.length) { return 1 } @@ -23,5 +28,5 @@ module.exports = { // not found return -1 }, - ssrAllowedAPIEndpoints: ["content", "medialib"], + ssrPublishCheckCollections: ["page"], } diff --git a/api/hooks/lib/ssr-server.js b/api/hooks/lib/ssr-server.js new file mode 100644 index 0000000..14a87b5 --- /dev/null +++ b/api/hooks/lib/ssr-server.js @@ -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, +} diff --git a/api/hooks/lib/ssr.js b/api/hooks/lib/ssr.js new file mode 100644 index 0000000..0f1def6 --- /dev/null +++ b/api/hooks/lib/ssr.js @@ -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>} + */ +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, +} diff --git a/api/hooks/ssr/get_read.js b/api/hooks/ssr/get_read.js index 90bff2e..94784ab 100644 --- a/api/hooks/ssr/get_read.js +++ b/api/hooks/ssr/get_read.js @@ -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("", '') } @@ -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 = "" } 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" + " + + + + + +
+ + + + + + + diff --git a/docker-compose-local.yml b/docker-compose-local.yml index 3a08abc..bff889b 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -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: diff --git a/esbuild.config.js b/esbuild.config.js index c8be6d2..03c25e5 100644 --- a/esbuild.config.js +++ b/esbuild.config.js @@ -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", }, diff --git a/esbuild.config.server.js b/esbuild.config.server.js index 33d94ce..1ba3891 100644 --- a/esbuild.config.server.js +++ b/esbuild.config.server.js @@ -1,4 +1,3 @@ -module.exports = config const config = require("./esbuild.config.js") const svelteConfig = require("./svelte.config") diff --git a/frontend/.htaccess b/frontend/.htaccess index 629b731..06b1813 100644 --- a/frontend/.htaccess +++ b/frontend/.htaccess @@ -1,7 +1,8 @@ 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 RewriteEngine On @@ -11,6 +12,8 @@ DirectoryIndex spa.html 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] diff --git a/frontend/media/BurgerMenu.svg b/frontend/media/BurgerMenu.svg index 7171e7b..8626181 100644 --- a/frontend/media/BurgerMenu.svg +++ b/frontend/media/BurgerMenu.svg @@ -1,3 +1,3 @@ - + diff --git a/frontend/media/Logo Quer.svg b/frontend/media/Logo Quer.svg index e378916..5cfcc00 100644 --- a/frontend/media/Logo Quer.svg +++ b/frontend/media/Logo Quer.svg @@ -1,6 +1,38 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/media/ToTop.svg b/frontend/media/ToTop.svg index 3e16516..328a93b 100644 --- a/frontend/media/ToTop.svg +++ b/frontend/media/ToTop.svg @@ -1,5 +1,5 @@ - + diff --git a/frontend/media/arrow-l-fat.svg b/frontend/media/arrow-l-fat.svg index 676ffcd..7c99503 100644 --- a/frontend/media/arrow-l-fat.svg +++ b/frontend/media/arrow-l-fat.svg @@ -1,3 +1,3 @@ - + diff --git a/frontend/media/calendar.svg b/frontend/media/calendar.svg index 8cb5390..2012a94 100644 --- a/frontend/media/calendar.svg +++ b/frontend/media/calendar.svg @@ -1,3 +1,3 @@ - + diff --git a/frontend/media/down.svg b/frontend/media/down.svg index 1364407..211d4f2 100644 --- a/frontend/media/down.svg +++ b/frontend/media/down.svg @@ -1,6 +1,6 @@ - + diff --git a/frontend/media/file.svg b/frontend/media/file.svg index a186837..1a5c7d0 100644 --- a/frontend/media/file.svg +++ b/frontend/media/file.svg @@ -1,4 +1,4 @@ - - + + diff --git a/frontend/media/up.svg b/frontend/media/up.svg index f9af713..6878acd 100644 --- a/frontend/media/up.svg +++ b/frontend/media/up.svg @@ -1,6 +1,6 @@ - + diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index e6dbc91..a6ad42e 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -89,13 +89,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") + } } } diff --git a/frontend/src/api.ts b/frontend/src/api.ts index fc5308d..934069f 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -1,121 +1,12 @@ -import { apiBaseURL } from "./config" - -const _f = function (url, options): Promise { - 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 ( 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 } diff --git a/frontend/src/index.ts b/frontend/src/index.ts index 25ac559..a628b32 100644 --- a/frontend/src/index.ts +++ b/frontend/src/index.ts @@ -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 } diff --git a/frontend/src/lib/assets/css/base.less b/frontend/src/lib/assets/css/base.less index 395d890..9c0e7d1 100644 --- a/frontend/src/lib/assets/css/base.less +++ b/frontend/src/lib/assets/css/base.less @@ -108,7 +108,7 @@ select { top: 0px; bottom: 0px; width: 0px; - background: #000000; + background: #343a40; transition: width 0.5s ease-in; } .fill:hover:after, @@ -143,7 +143,7 @@ swiper-slide { z-index: 10000; left: 0px; bottom: -10px; - background: #000000; + background: @signal-color; height: 5px; width: 0; animation: underlineEffect 15s linear forwards; diff --git a/frontend/src/lib/assets/css/main.less b/frontend/src/lib/assets/css/main.less index e05bb2d..8e03f27 100644 --- a/frontend/src/lib/assets/css/main.less +++ b/frontend/src/lib/assets/css/main.less @@ -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)"; diff --git a/frontend/src/lib/components/Footer.svelte b/frontend/src/lib/components/Footer.svelte index 6406484..a7b2fe6 100644 --- a/frontend/src/lib/components/Footer.svelte +++ b/frontend/src/lib/components/Footer.svelte @@ -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 } } diff --git a/frontend/src/lib/components/Pagebuilder/Homepage.svelte b/frontend/src/lib/components/Pagebuilder/Homepage.svelte index c420b38..21aa895 100644 --- a/frontend/src/lib/components/Pagebuilder/Homepage.svelte +++ b/frontend/src/lib/components/Pagebuilder/Homepage.svelte @@ -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] @@ -135,6 +139,7 @@ line-height: 1; font-weight: 500; position: relative; + color: @signal-color; } h2 { diff --git a/frontend/src/lib/components/Pagebuilder/Pagebuilder.svelte b/frontend/src/lib/components/Pagebuilder/Pagebuilder.svelte index c289e3b..c150656 100644 --- a/frontend/src/lib/components/Pagebuilder/Pagebuilder.svelte +++ b/frontend/src/lib/components/Pagebuilder/Pagebuilder.svelte @@ -26,9 +26,11 @@ export let i: number export let page: Page export let personPage: boolean - window.addEventListener("popstate", function (event) { - $rerender = $rerender + 1 - }) + if (typeof window !== "undefined") { + window.addEventListener("popstate", function (event) { + $rerender = $rerender + 1 + }) + } {#if Object.keys(row).length} @@ -140,6 +142,7 @@ h1 { font-weight: 500; font-size: 2rem; + color: @signal-color; } .top-header { img { diff --git a/frontend/src/lib/components/Pagebuilder/Rows.svelte b/frontend/src/lib/components/Pagebuilder/Rows.svelte index c4c164d..5ad2f22 100644 --- a/frontend/src/lib/components/Pagebuilder/Rows.svelte +++ b/frontend/src/lib/components/Pagebuilder/Rows.svelte @@ -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 + } } }) diff --git a/frontend/src/lib/components/widgets/Worldcard/card.svelte b/frontend/src/lib/components/widgets/Worldcard/card.svelte index 39f0923..08cbc5f 100644 --- a/frontend/src/lib/components/widgets/Worldcard/card.svelte +++ b/frontend/src/lib/components/widgets/Worldcard/card.svelte @@ -164,8 +164,8 @@ height: 1.8vw; max-height: 25px; border-radius: 15px; - border: 2px solid #4f4f4f; - color: #4f4f4f; + border: 2px solid @signal-color; + color: @signal-color; background-color: @bg-color-secondary; display: flex; justify-content: center; diff --git a/frontend/src/lib/components/widgets/boxlist.svelte b/frontend/src/lib/components/widgets/boxlist.svelte index 1a92bc7..543cb6a 100644 --- a/frontend/src/lib/components/widgets/boxlist.svelte +++ b/frontend/src/lib/components/widgets/boxlist.svelte @@ -28,7 +28,7 @@ gap: 20px; .box { padding: 5px 10px; - background-color: @bg-color-secondary; + background-color: @signal-color; color: @font-color-secondary; font-weight: bold; } diff --git a/frontend/src/lib/components/widgets/extendableBox.svelte b/frontend/src/lib/components/widgets/extendableBox.svelte index 60b5ae2..9c02c25 100644 --- a/frontend/src/lib/components/widgets/extendableBox.svelte +++ b/frontend/src/lib/components/widgets/extendableBox.svelte @@ -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) + } }) @@ -51,9 +53,9 @@ @import "../../assets/css/main.less"; button { margin-top: 20px; - background-color: @bg-color-secondary; + background-color: @signal-color; color: @font-color-secondary; - border: 2px solid @bg-color-secondary; + border: 2px solid @signal-color; padding: 2px 15px; font-weight: bold; } diff --git a/frontend/src/lib/components/widgets/iconCycleBox.svelte b/frontend/src/lib/components/widgets/iconCycleBox.svelte index f4c4e94..b7c8dbd 100644 --- a/frontend/src/lib/components/widgets/iconCycleBox.svelte +++ b/frontend/src/lib/components/widgets/iconCycleBox.svelte @@ -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) + }
@@ -17,8 +19,7 @@
@@ -43,8 +44,8 @@ font-size: 1rem; } .box { - border: 4px solid @bg-color-secondary; - background-color: @bg-color-secondary; + border: 4px solid @signal-color; + background-color: @signal-color; display: flex; align-items: center; justify-content: center; @@ -52,7 +53,7 @@ padding: 10px; &.active { background-color: @bg-color; - color: @font-color; + color: #5b6e98; } aspect-ratio: 1/1; width: calc((100% / 2) - 10px); diff --git a/frontend/src/lib/components/widgets/iconCycleCircle.svelte b/frontend/src/lib/components/widgets/iconCycleCircle.svelte index 612c40b..dd4ac36 100644 --- a/frontend/src/lib/components/widgets/iconCycleCircle.svelte +++ b/frontend/src/lib/components/widgets/iconCycleCircle.svelte @@ -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) + }
@@ -52,8 +53,8 @@
@@ -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; } diff --git a/frontend/src/lib/components/widgets/moreBtn.svelte b/frontend/src/lib/components/widgets/moreBtn.svelte index 98321fb..5e1405c 100644 --- a/frontend/src/lib/components/widgets/moreBtn.svelte +++ b/frontend/src/lib/components/widgets/moreBtn.svelte @@ -10,7 +10,7 @@ diff --git a/frontend/src/lib/components/widgets/nestedCard.svelte b/frontend/src/lib/components/widgets/nestedCard.svelte index dfb7945..736eb83 100644 --- a/frontend/src/lib/components/widgets/nestedCard.svelte +++ b/frontend/src/lib/components/widgets/nestedCard.svelte @@ -54,7 +54,7 @@ height: 100px; width: 365px; z-index: 9; - background-color: @bg-color-secondary; + background-color: @signal-color; } .description { position: relative; diff --git a/frontend/src/lib/components/widgets/pageLinkBlocks.svelte b/frontend/src/lib/components/widgets/pageLinkBlocks.svelte index 0e15148..5b0e32d 100644 --- a/frontend/src/lib/components/widgets/pageLinkBlocks.svelte +++ b/frontend/src/lib/components/widgets/pageLinkBlocks.svelte @@ -28,8 +28,8 @@
{:else} @@ -89,9 +89,9 @@ } .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; } } diff --git a/frontend/src/lib/components/widgets/persons.svelte b/frontend/src/lib/components/widgets/persons.svelte index d405ce9..e7ae03a 100644 --- a/frontend/src/lib/components/widgets/persons.svelte +++ b/frontend/src/lib/components/widgets/persons.svelte @@ -81,7 +81,7 @@ } .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; diff --git a/frontend/src/lib/components/widgets/scrollDown.svelte b/frontend/src/lib/components/widgets/scrollDown.svelte index 873e4ec..8ccc24b 100644 --- a/frontend/src/lib/components/widgets/scrollDown.svelte +++ b/frontend/src/lib/components/widgets/scrollDown.svelte @@ -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 + } } } diff --git a/frontend/src/lib/components/widgets/scrollTop.svelte b/frontend/src/lib/components/widgets/scrollTop.svelte index db4ab13..13707a7 100644 --- a/frontend/src/lib/components/widgets/scrollTop.svelte +++ b/frontend/src/lib/components/widgets/scrollTop.svelte @@ -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) + } }) diff --git a/frontend/src/lib/components/widgets/topDown.svelte b/frontend/src/lib/components/widgets/topDown.svelte index f37320c..ad20e38 100644 --- a/frontend/src/lib/components/widgets/topDown.svelte +++ b/frontend/src/lib/components/widgets/topDown.svelte @@ -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; diff --git a/frontend/src/lib/functions/loadNavigation.ts b/frontend/src/lib/functions/loadNavigation.ts index ac122fb..2619617 100644 --- a/frontend/src/lib/functions/loadNavigation.ts +++ b/frontend/src/lib/functions/loadNavigation.ts @@ -2,5 +2,6 @@ import { api } from "../../api" export async function loadNavigation(): Promise { let nav = await api("navigation", {}) + console.log("NAV:", nav) return nav.data } diff --git a/package.json b/package.json index d6387e6..b11312a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/types/global.d.ts b/types/global.d.ts index e0b0cab..b76d8d5 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -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 { + 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