diff --git a/.prettierrc b/.prettierrc
index 9402e22..fc75096 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,16 +1,18 @@
{
- "printWidth": 80,
+ "printWidth": 120,
"tabWidth": 4,
"singleQuote": false,
"trailingComma": "es5",
"semi": false,
"newline-before-return": true,
- "no-duplicate-variable": [true, "check-parameters"],
+ "no-duplicate-variable": [
+ true,
+ "check-parameters"
+ ],
"no-var-keyword": true,
-
"svelteSortOrder": "scripts-styles-markup",
"svelteStrictMode": true,
"svelteBracketNewLine": true,
"svelteAllowShorthand": true,
"svelteIndentScriptAndStyle": true
-}
+}
\ No newline at end of file
diff --git a/api/collections/articles.yml b/api/collections/articles.yml
new file mode 100644
index 0000000..fbaefd9
--- /dev/null
+++ b/api/collections/articles.yml
@@ -0,0 +1,150 @@
+########################################################################
+# Articles
+########################################################################
+
+name: articles
+uploadPath: ../media/articles
+meta:
+ # Navigationseintrag in der Admin-UI
+ label: { de: "Artikel auf der Seite", en: "Page articles" }
+ # Icon (Material UI) für den Navigationseintrag
+ muiIcon: file-document-edit-outline
+ # Standardsortierung der Liste
+ defaultSort: { field: "name", order: "ASC" }
+ # Admin-Backend Ansichten
+ defaultImageFilter: s
+ views:
+ # Mobile Darstellung
+ - type: simpleList
+ mediaQuery: "(max-width:599px)"
+ primaryText: path
+ columns:
+ - public
+ - image
+ - title
+ - position
+ # Desktop
+ - type: table
+ mediaQuery: "(min-width:600px)"
+ columns:
+ - public
+ - image
+ - title
+ - position
+
+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
+
+permissions:
+ public:
+ methods:
+ get: true
+ post: false
+ put: false
+ delete: false
+ user:
+ methods:
+ get: true
+ post: true
+ put: true
+ delete: true
+ # token als Zusatzsicherung gegen Spam, mehr siehe Hook
+ "token:${PUBLIC_TOKEN}":
+ methods:
+ get: false
+ post: false
+ put: false
+ delete: false
+
+# hooks:
+# post:
+# create:
+# type: javascript
+# file: hooks/article/post_create.js
+# put:
+# update:
+# type: javascript
+# file: hooks/article/put_return.js
+# delete:
+# return:
+# type: javascript
+# file: hooks/article/delete_return.js
+
+fields:
+ - name: articleTabs
+ type: tabs
+ meta:
+ label:
+ de: Informationen zu einem Artikel
+ en: Article Information
+ activeTab: 0
+ subFields:
+ - name: articleTab
+ type: object
+ meta:
+ label:
+ de: Artikel
+ en: Article
+ css:
+ subFields:
+ - name: articleLayoutTab
+ type: object
+ meta:
+ label:
+ de: Layout
+ en: Layout
+ css:
+ subFields:
+ - name: articleMediaTab
+ type: object
+ meta:
+ label:
+ de: Bilder
+ en: Images
+ css:
+ subFields:
+ - name: articleAttachmentsTab
+ type: object
+ meta:
+ label:
+ de: Anhänge / Downloads
+ en: Attachments / Downloads
+ css:
+ subFields:
+ - name: articleMetaTab
+ type: object
+ meta:
+ label:
+ de: Meta
+ en: Meta
+ css:
+ subFields:
diff --git a/api/collections/general.yml b/api/collections/general.yml
new file mode 100644
index 0000000..2c1d8e1
--- /dev/null
+++ b/api/collections/general.yml
@@ -0,0 +1,306 @@
+########################################################################
+# General Information
+########################################################################
+
+name: general
+uploadPath: ../media/general
+meta:
+ # Navigationseintrag in der Admin-UI
+ label: { de: "Allgemeine Informationen", en: "General Information" }
+ # Icon (Material UI) für den Navigationseintrag
+ muiIcon: information-outline
+ # Identifizierung eines Eintrags für z.B. Select-Boxen in der Admin-UI
+ rowIdentTpl: { twig: "{{ email }} - {{ subject }}" }
+ # Standardsortierung der Liste
+ defaultSort: { field: "path", order: "ASC" }
+ # Admin-Backend Ansichten
+ defaultImageFilter: s
+ views:
+ # Mobile Darstellung
+ - type: simpleList
+ mediaQuery: "(max-width:599px)"
+ primaryText: firstname
+ columns:
+ - public
+ - firstname
+ - lastname
+ - companyName
+ # Desktop
+ - type: table
+ mediaQuery: "(min-width:600px)"
+ columns:
+ - public
+ - firstname
+ - lastname
+ - companyName
+
+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
+
+permissions:
+ public:
+ methods:
+ get: true
+ post: false
+ put: false
+ delete: false
+ user:
+ methods:
+ get: true
+ post: true
+ put: true
+ delete: true
+ # token als Zusatzsicherung gegen Spam, mehr siehe Hook
+ "token:${PUBLIC_TOKEN}":
+ methods:
+ get: false
+ post: false
+ put: false
+ delete: false
+
+fields:
+ - name: generalInformationTabs
+ type: tabs
+ meta:
+ label:
+ de: Allgemeine Information
+ en: General Information
+ activeTab: 0
+ subFields:
+ - name: generalInformationTab
+ type: object
+ meta:
+ label:
+ de: Allgemein
+ en: General
+ css:
+ subFields:
+ - name: public
+ type: boolean
+ meta:
+ label:
+ de: Veröffentlicht
+ en: Public
+ helperText:
+ de: "Alle allgemeinen Informationen werden auf der Seite angezeigt."
+ en: "All general information are displayed on the page."
+ - name: metaInformationTab
+ type: object
+ meta:
+ label:
+ de: Meta / SEO
+ en: Meta / SEO
+ css:
+ subFields:
+ - name: metaTitle
+ type: string
+ meta:
+ label: { de: "Titel der Webseite", en: "Page Title" }
+ - name: metaDescription
+ type: string
+ meta:
+ label: { de: "Beschreibung der Webseite", en: "Page Description" }
+ - name: metaTagRobots
+ type: string[]
+ meta:
+ widget: chipArray
+ label:
+ de: Robots
+ en: Robots
+ inputProps:
+ placeholder: "nofollow"
+ defaultValue: []
+ autocomplete: true
+ choices:
+ - { id: "noindex", name: "noindex" }
+ - { id: "index", name: "index" }
+ - { id: "follow", name: "follow" }
+ - { id: "nofollow", name: "nofollow" }
+ - { id: "noimageindex", name: "noimageindex" }
+ - { id: "none", name: "none" }
+ - { id: "noarchive", name: "noarchive" }
+ - { id: "nocache", name: "nocache" }
+ - { id: "nosnippet", name: "nosnippet" }
+ - { id: "nnavailable_after", name: "nnavailable_after" }
+ helperText:
+ de: "Noindex: Weist eine Suchmaschine an, eine Seite nicht zu indizieren.
index: Weist eine Suchmaschine an, eine Seite zu indizieren. Beachten Sie, dass Sie dieses Meta-Tag nicht hinzufügen müssen; es ist die Voreinstellung.
follow: Auch wenn die Seite nicht indexiert ist, sollte der Crawler allen Links auf einer Seite folgen und Eigenkapital an die verlinkten Seiten weitergeben.
nofollow: Weist einen Crawler an, keinen Links auf einer Seite zu folgen oder Link-Equity weiterzugeben.
noimageindex: Weist einen Crawler an, keine Bilder auf einer Seite zu indizieren.
none: Entspricht der gleichzeitigen Verwendung der noindex- und nofollow-Tags.
noarchive: Suchmaschinen sollten keinen zwischengespeicherten Link zu dieser Seite auf einem SERP anzeigen.
nocache: Wie noarchive, aber nur von Internet Explorer und Firefox verwendet.
nosnippet: Weist eine Suchmaschine an, kein Snippet dieser Seite (d. h. Meta-Beschreibung) dieser Seite auf einem SERP anzuzeigen.
nnavailable_after: Suchmaschinen sollen diese Seite nach einem bestimmten Datum nicht mehr indexieren.
"
+ en: "Noindex: Tells a search engine not to index a page.
index: Tells a search engine to index a page. Note that you don’t need to add this meta tag; it’s the default.
follow: Even if the page isn’t indexed, the crawler should follow all the links on a page and pass equity to the linked pages.
nofollow: Tells a crawler not to follow any links on a page or pass along any link equity.
noimageindex: Tells a crawler not to index any images on a page.
none: Equivalent to using both the noindex and nofollow tags simultaneously.
noarchive: Search engines should not show a cached link to this page on a SERP.
nocache: Same as noarchive, but only used by Internet Explorer and Firefox.
nosnippet: Tells a search engine not to show a snippet of this page (i.e. meta description) of this page on a SERP.
nnavailable_after: Search engines should no longer index this page after a particular date.
"
+ - name: metaKeywords
+ type: string
+ meta:
+ label: { de: "SEO / Schlüsselwörter", en: "SEO / Keywords" }
+ helperText:
+ de: "Beispiel: Stichwort1, Stichwort2, Stichwort3"
+ en: "Example: keyword1, keyword2, keyword3"
+ - name: personalInformationTab
+ type: object
+ meta:
+ label:
+ de: Personendaten
+ en: Personal Data
+ css:
+ subFields:
+ - name: firstname
+ type: string
+ meta:
+ label: { de: "Vorname", en: "Firstname" }
+ - name: lastname
+ type: string
+ meta:
+ label: { de: "Nachname", en: "Lastname" }
+ - name: street
+ type: string
+ meta:
+ label: { de: "Straße", en: "Street" }
+ - name: postcode
+ type: string
+ meta:
+ label: { de: "Postleitzahl", en: "Postcode" }
+ - name: city
+ type: string
+ meta:
+ label: { de: "Ort", en: "City" }
+ - name: tel
+ type: string
+ meta:
+ label: { de: "Telefonnummer", en: "Phone number" }
+ - name: mobile
+ type: string
+ meta:
+ label: { de: "Handynummer", en: "Mobile number" }
+ - name: email
+ type: string
+ meta:
+ label: { de: "E-Mail", en: "E-Mail" }
+ - name: companyInformationTab
+ type: object
+ meta:
+ label:
+ de: Unternehmensdaten
+ en: Company Data
+ css:
+ subFields:
+ - name: companyName
+ type: string
+ meta:
+ label: { de: "Name des Unternehmens", en: "Company Name" }
+ - name: companyWebUrl
+ type: string
+ meta:
+ label: { de: "URL zur Webseite", en: "Website URL" }
+ - name: companyAddresses
+ type: object[]
+ meta:
+ label:
+ de: Adresse
+ en: Adresse
+ css:
+ subFields:
+ - name: street
+ type: string
+ meta:
+ label: { de: "Straße", en: "Street" }
+ - name: houseNr
+ type: string
+ meta:
+ label: { de: "Hausnummer", en: "House number" }
+ - name: postcode
+ type: string
+ meta:
+ label: { de: "PLZ", en: "ZIP" }
+ - name: city
+ type: string
+ meta:
+ label: { de: "Ort", en: "City" }
+ - name: tel
+ type: string
+ meta:
+ label: { de: "Telefon", en: "Phone number" }
+ - name: fax
+ type: string
+ meta:
+ label: { de: "Fax", en: "Fax" }
+ - name: email
+ type: string
+ meta:
+ label: { de: "E-Mail", en: "E-Mail" }
+ - name: mediaInformationTab
+ type: object
+ meta:
+ label:
+ de: Media
+ en: Media
+ css:
+ subFields:
+ - name: favicon
+ type: file
+ meta:
+ label: { de: "Favicon", en: "Favicon" }
+ helperText:
+ de: "Ein Favicon ist ein kleines Icon, Symbol oder Logo, das von Webbrowsern verwendet wird, um eine Website auf wiedererkennbare Weise zu kennzeichnen."
+ en: "A favicon is a small icon, symbol, or logo used by web browsers to identify a website in a recognizable way."
+ - name: favicon
+ type: file
+ meta:
+ label: { de: "Logo / Brand", en: "Logo / Brand" }
+ helperText:
+ de: "Logo der Seite"
+ en: "Page Logo"
+ - name: mediaFiles
+ type: object[]
+ meta:
+ label: { de: "Dateien", en: "Files" }
+ subFields:
+ - name: title
+ type: string
+ meta:
+ label: { de: "Datei-Titel", en: "File Title" }
+ - name: id
+ type: string
+ meta:
+ label: { de: "Technischer Name / ID", en: "Technical name / ID" }
+ - name: file
+ type: file
+ meta:
+ label: { de: "", en: "" }
+ - name: copyrightInformationTab
+ type: object
+ meta:
+ label:
+ de: Copyright
+ en: Copyright
+ css:
+ subFields:
+ - name: copyright
+ type: string
+ meta:
+ label: { de: "Copyright Text", en: "Copyright Text" }
diff --git a/api/config.yml b/api/config.yml
index b679030..ef55585 100644
--- a/api/config.yml
+++ b/api/config.yml
@@ -7,6 +7,8 @@ meta:
# Liste aller möglichen Kollektionen (Listen, Seiten...) zum Projekt
collections:
+ - !include collections/general.yml
+ - !include collections/articles.yml
- !include collections/content.yml
- !include collections/contact_form.yml
- !include collections/ssr.yml
diff --git a/esbuild.config.js b/esbuild.config.js
index 6658b63..18bdac3 100644
--- a/esbuild.config.js
+++ b/esbuild.config.js
@@ -5,8 +5,7 @@ const resolvePlugin = {
// url in css does not resolve via esbuild-svelte correctly
build.onResolve({ filter: /.*/, namespace: "fakecss" }, (args) => {
// console.log(args)
- if (args.path.match(/^\./))
- return { path: path.dirname(args.importer) + "/" + args.path }
+ if (args.path.match(/^\./)) return { path: path.dirname(args.importer) + "/" + args.path }
// return { path: path.join(args.resolveDir, "public", args.path) }
})
},
@@ -54,6 +53,8 @@ const options = {
".eot": "file",
".svg": "file",
".ttf": "file",
+ ".png": "file",
+ ".jpg": "file",
},
sourcemap: true,
target: ["es2020", "chrome61", "firefox60", "safari11", "edge18"],
@@ -63,9 +64,7 @@ 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 apiBase = process.env.API_BASE || "http://localhost:8080/api/v1/_/" + process.env.NAMESPACE
bsMiddleware.push(
createProxyMiddleware("/api", {
target: apiBase,
diff --git a/package.json b/package.json
index ae9dd03..96e2a64 100644
--- a/package.json
+++ b/package.json
@@ -7,8 +7,8 @@
"license": "MIT",
"scripts": {
"validate": "svelte-check && tsc --noEmit",
- "start": "node scripts/esbuild-wrapper.js start",
- "start:localapi": "API_BASE=http://localhost:8080 node scripts/esbuild-wrapper.js start",
+ "start": "NAMESPACE=__NAMESPACE__ node scripts/esbuild-wrapper.js start",
+ "start:remoteapi": "API_BASE=https://login.tibicms.de/api/v1/_/__NAMESPACE__ node scripts/esbuild-wrapper.js start",
"dev": "node scripts/esbuild-wrapper.js watch",
"build": "node scripts/esbuild-wrapper.js build",
"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=dist/_dist_/index.es5.js --target=es5 --bundle --minify --sourcemap",
@@ -62,4 +62,4 @@
"live-server": "^1.2.1",
"mongodb": "^4.3.1"
}
-}
+}
\ No newline at end of file
diff --git a/src/api.ts b/src/api.ts
index 6b22d17..cde7833 100644
--- a/src/api.ts
+++ b/src/api.ts
@@ -43,9 +43,7 @@ const _f = function (url, options): Promise {
.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
+ headers[key] = headers[key] ? `${headers[key]},${value}` : value
})
resolve(response())
}
@@ -62,12 +60,7 @@ const _f = function (url, options): Promise {
})
}
-const _fetch =
- typeof fetch === "undefined"
- ? typeof window === "undefined"
- ? _f
- : window.fetch || _f
- : fetch
+const _fetch = typeof fetch === "undefined" ? (typeof window === "undefined" ? _f : window.fetch || _f) : fetch
export const api = async (
endpoint: string,
@@ -107,8 +100,7 @@ export const api = async (
let method = "GET"
let query = "&count=1"
- if (options?.filter)
- query += "&filter=" + encodeURIComponent(JSON.stringify(options.filter))
+ 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
@@ -149,8 +141,7 @@ export const api = async (
// @ts-ignore
let data = (await response?.json()) || null
- if (response?.status < 200 || response?.status >= 400)
- throw { response, data }
+ if (response?.status < 200 || response?.status >= 400) throw { response, data }
// @ts-ignore
return { data, count: response?.headers?.get("x-results-count") || 0 }
@@ -163,3 +154,34 @@ export const getContent = async (path: string): Promise => {
}
return null
}
+
+export const getGeneralInformation = async (): Promise => {
+ try {
+ let response = await api("general", {
+ method: "get",
+ offset: 0,
+ limit: 1,
+ filter: {
+ active: true,
+ },
+ })
+ return response.data
+ } catch (e) {
+ return null
+ }
+}
+
+export const getArticles = async (): Promise => {
+ try {
+ let response = await api("articles", {
+ method: "get",
+ offset: 0,
+ filter: {
+ active: true,
+ },
+ })
+ return response.data
+ } catch (e) {
+ return null
+ }
+}
diff --git a/src/store.ts b/src/store.ts
index a9abd06..c8342cc 100644
--- a/src/store.ts
+++ b/src/store.ts
@@ -1,4 +1,5 @@
import { writable, get } from "svelte/store"
+import { getGeneralInformation, getArticles } from "./api"
const initLoc = {
path: (typeof window !== "undefined" && window.location?.pathname) || "/",
@@ -10,3 +11,37 @@ const initLoc = {
initLoc.categoryPath = initLoc.path.replace(/\/\d{4,99}[^\/]+$/, "")
export const location = writable(initLoc)
+
+// General Information
+
+export const generalInformation = writable()
+const getGeneralProjectInformation = async () => {
+ const infos = await getGeneralInformation()
+ if (infos && infos.length) {
+ generalInformation.set(infos[0])
+ }
+}
+getGeneralProjectInformation()
+
+// Articles
+
+export const articles = writable()
+const getAllArticles = async () => {
+ const list = await getArticles()
+ articles.set(list)
+}
+getAllArticles()
+
+// Cookies - Webmakers Cookie Bar
+
+export const ccTags = writable([])
+
+const updateCcTtags = () => {
+ // @ts-ignore
+ ccTags.set(window.ccLoadedTags || [])
+}
+
+if (typeof window !== "undefined") {
+ window.addEventListener("ccInit", updateCcTtags)
+ window.addEventListener("ccAccept", updateCcTtags)
+}
diff --git a/types/global.d.ts b/types/global.d.ts
index 4416573..d1b8ece 100644
--- a/types/global.d.ts
+++ b/types/global.d.ts
@@ -8,9 +8,52 @@ interface ContentBlock {
images?: ImageEntry[]
}
-
interface Content {
id: string
path: string
blocks: ContentBlock[]
}
+
+interface GeneralInformation {
+ id: string
+ active: boolean
+ firstname: string
+ lastname: string
+ street: string
+ postcode: number
+ city: string
+ tel: string
+ mobile: string
+ email: string
+ images: GeneralImage[]
+ insertTime: string
+ updateTime: string
+ lastPageUpdate: string
+}
+
+interface GeneralImage {
+ file: File[]
+ id: string
+ label: string
+}
+
+interface TibiArticle {
+ id: string
+ active: boolean
+ content: string
+ details: string
+ image: File
+ insertTime: string
+ position: string
+ subtitle: string
+ title: string
+ updateTime: string
+}
+
+interface File {
+ lastModified?: number
+ path: string
+ size?: string
+ src: string
+ type: string
+}