diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 987e834..0000000 --- a/.drone.yml +++ /dev/null @@ -1,44 +0,0 @@ -kind: pipeline -type: docker -name: default - -steps: - - name: build - image: node - commands: - - npm install - - npm run build - - name: deploy-dev - image: alpine - environment: - FTP_HOST: - from_secret: FTP_HOST - FTP_USERNAME: - from_secret: FTP_USERNAME - FTP_PASSWORD: - from_secret: FTP_PASSWORD - commands: - - which lftp || ( apk --update add lftp ) - - lftp -e "set ftp:ssl-force true; set ssl:verify-certificate no; mirror -R ./dist/ dev.sp-codes.de/; bye" -u $FTP_USERNAME,$FTP_PASSWORD $FTP_HOST - when: - branch: - - develop - event: - - push - - name: deploy - image: alpine - environment: - FTP_HOST: - from_secret: FTP_HOST - FTP_USERNAME: - from_secret: FTP_USERNAME - FTP_PASSWORD: - from_secret: FTP_PASSWORD - commands: - - which lftp || ( apk --update add lftp ) - - lftp -e "set ftp:ssl-force true; set ssl:verify-certificate no; mirror -R ./dist/ sp-codes.de/; bye" -u $FTP_USERNAME,$FTP_PASSWORD $FTP_HOST - when: - branch: - - main - event: - - push diff --git a/.eleventy.js b/.eleventy.js index 9a02e4b..2287334 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -1,22 +1,84 @@ -const eleventyNavigationPlugin = require("@11ty/eleventy-navigation"); +const pluginRev = require("eleventy-plugin-rev"); +const pluginSass = require("eleventy-sass"); +const pluginTinyHtml = require("@sardine/eleventy-plugin-tinyhtml"); +const pluginNavigation = require("@11ty/eleventy-navigation"); -module.exports = function(eleventyConfig) { - eleventyConfig.addWatchTarget("./src/scss/"); +module.exports = function (eleventyConfig) { + eleventyConfig.addPlugin(pluginRev); + eleventyConfig.addPlugin(pluginTinyHtml); + eleventyConfig.addPlugin(pluginSass, { + sass: { + loadPaths: ["node_modules"], + style: "compressed", + sourceMap: false, + }, + compileOptions: { + permalink: function (contents, inputPath) { + return (data) => { + return data.page.filePathStem.replace(/^\/scss\//, "/css/") + ".css"; + }; + } + }, + rev: true + }); + eleventyConfig.addPlugin(pluginNavigation); - eleventyConfig.addPlugin(eleventyNavigationPlugin); eleventyConfig.setUseGitIgnore(false); eleventyConfig.addPassthroughCopy({ "src/img": "img", "src/font": "font", - "node_modules/@fortawesome/fontawesome-free/webfonts/": "font", - "node_modules/flag-icon-css/flags/4x3/(de|us)*": "flags" + "src/favicon.*": "", + }); + eleventyConfig.addShortcode("translatedUrl", function (currentLocale, newLocale) { + return this.page.url.replace(new RegExp(`\/${currentLocale}\/`), `/${newLocale}/`); + }); + + eleventyConfig.addFilter('year', function (value) { + return value * 12; + }); + + eleventyConfig.addFilter("sum", function (value) { + return value.map(d => d.amount).reduce((a, b) => a + b, 0); + }); + + eleventyConfig.addFilter("amount", function (value) { + // TODO update language dynamically + return value.toLocaleString('de', {minimumFractionDigits: 2}); + }); + + eleventyConfig.addFilter("banktransfers", function (donations) { + return donations + .flatMap(y => y.donations) + .filter(d => d.via === 'banktransfer') + .filter(d => d.first) + .length; + }); + + eleventyConfig.addFilter("cash", function (donations) { + return donations + .flatMap(y => y.donations) + .filter(d => d.via === 'cash') + .filter(d => d.first) + .length; + }); + + eleventyConfig.addFilter('getServiceById', (services, serviceId) => { + return services.find(s => s.id === serviceId); }); return { + // Pre-process *.md files with: (default: `liquid`) + markdownTemplateEngine: "njk", + // Pre-process *.html files with: (default: `liquid`) + htmlTemplateEngine: "njk", + // Opt-out of pre-processing global data JSON files: (default: `liquid`) + dataTemplateEngine: false, + dir: { input: "src", includes: "_includes", layouts: "_includes/layouts", + data: "_data", output: "dist" } }; diff --git a/.forgejo/workflows/build-deploy.yml b/.forgejo/workflows/build-deploy.yml new file mode 100644 index 0000000..b153732 --- /dev/null +++ b/.forgejo/workflows/build-deploy.yml @@ -0,0 +1,54 @@ +name: Build and Deploy Website + +on: [push] + +jobs: + build: + name: Build Website + runs-on: docker + container: + image: node:lts + steps: + - uses: actions/checkout@v3 + - name: Install Dependencies + run: npm install + - name: Build Website + run: npm run build + - uses: actions/upload-artifact@v3 + with: + name: build + path: dist/ + deploy-dev: + name: Deploy Dev Website + runs-on: docker + container: + image: node:lts-alpine + needs: [build] + if: github.ref == 'refs/heads/develop' + steps: + - uses: actions/download-artifact@v3 + - name: Install Dependencies + run: which lftp || ( apk --update add lftp ) + - name: Deploy Website + run: lftp -e "set ftp:ssl-force true; set ssl:verify-certificate no; mirror -R ./build/ dev.sp-codes.de/; bye" -u $FTP_USERNAME,$FTP_PASSWORD $FTP_HOST + env: + FTP_HOST: ${{ secrets.FTP_HOST }} + FTP_USERNAME: ${{ secrets.FTP_USERNAME }} + FTP_PASSWORD: ${{ secrets.FTP_PASSWORD }} + deploy: + name: Deploy Website + runs-on: docker + container: + image: node:lts-alpine + needs: [build] + if: github.ref == 'refs/heads/main' + steps: + - uses: actions/download-artifact@v3 + - name: Install Dependencies + run: which lftp || ( apk --update add lftp ) + - name: Deploy Website + run: lftp -e "set ftp:ssl-force true; set ssl:verify-certificate no; mirror -R ./build/ sp-codes.de/; bye" -u $FTP_USERNAME,$FTP_PASSWORD $FTP_HOST + env: + FTP_HOST: ${{ secrets.FTP_HOST }} + FTP_USERNAME: ${{ secrets.FTP_USERNAME }} + FTP_PASSWORD: ${{ secrets.FTP_PASSWORD }} diff --git a/README.md b/README.md index 7eed4db..d8ff99d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # sp-codes.de -[](https://ci.sp-codes.de/samuel-p/sp-codes.de) +[](https://ci.sp-codes.de/samuel-p/sp-codes.de) Website for [sp-codes.de](https://sp-codes.de) @@ -9,7 +9,7 @@ Website for [sp-codes.de](https://sp-codes.de) The following Parameters are set directly on the Web-Server. ``` -Content-Security-Policy: default-src 'none'; script-src 'self' https://plausible.sp-codes.de; object-src 'none'; style-src 'self'; img-src 'self' https://shields.sp-codes.de; media-src 'none'; frame-src 'none'; font-src 'self'; connect-src 'self' https://plausible.sp-codes.de +Content-Security-Policy: default-src 'none'; script-src 'self' https://umami.sp-codes.de; object-src 'none'; style-src 'self'; img-src 'self' https://status.sp-codes.de https://shields.sp-codes.de; media-src 'none'; frame-src 'none'; font-src 'self'; connect-src 'self' https://umami.sp-codes.de Referrer-Policy: strict-origin-when-cross-origin Feature-Policy: sync-xhr 'self' Strict-Transport-Security: max-age=31536000; includeSubDomains; preload diff --git a/package.json b/package.json index 0780854..0b9272e 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,9 @@ "version": "1.0.0", "description": "website for sp-codes.de", "scripts": { - "compile-sass": "node-sass --output-style compressed --importer=node_modules/node-sass-tilde-importer src/scss/main.scss dist/css/main.css", - "watch:eleventy": "eleventy --serve", - "watch:sass": "npm run compile-sass -- --watch", - "start": "npm-run-all compile-sass --parallel watch:*", - "build": "npm run compile-sass && eleventy && npm run move-index", + "minify-css": "uncss -n -H dist/ -o dist/css/main-*.css dist/**/*.html dist/**/**/*.html dist/**/**/**/*.html dist/**/**/**/**/*.html dist/**/**/**/**/**/*.html", + "start": "eleventy --serve --watch", + "build": "eleventy && npm run move-index && npm run minify-css", "move-index": "cpx dist/de/index.html dist/" }, "author": "samuel-p", @@ -16,19 +14,25 @@ "url": "https://git.sp-codes.de/samuel-p/sp-codes.de" }, "optionalDependencies": { - "browser-sync": "^2.26.14" + "browser-sync": "^2.29.3" }, "devDependencies": { - "@11ty/eleventy": "^0.12.1", - "@11ty/eleventy-navigation": "^0.1.6", + "@11ty/eleventy": "^2.0.1", + "@11ty/eleventy-navigation": "^0.3.5", + "@node-minify/core": "^8.0.6", + "@node-minify/crass": "^8.0.6", + "@node-minify/html-minifier": "^8.0.6", + "@sardine/eleventy-plugin-tinyhtml": "^0.2.0", "cpx": "^1.5.0", - "node-sass": "^5.0.0", - "node-sass-tilde-importer": "^1.0.2", - "npm-run-all": "^4.1.5" + "eleventy-plugin-rev": "^2.0.0", + "eleventy-sass": "^2.2.4", + "glob": "^10.3.10", + "minify": "^10.5.2", + "postcss": "^8.4.32", + "sass": "^1.69.5", + "uncss": "^0.17.3" }, "dependencies": { - "@fortawesome/fontawesome-free": "^5.15.3", - "bootstrap": "^4.6.0", - "flag-icon-css": "^3.5.0" + "bootstrap": "^5.3.2" } } diff --git a/src/_data/donations.json b/src/_data/donations.json index 87a2ddf..30cff4e 100644 --- a/src/_data/donations.json +++ b/src/_data/donations.json @@ -1,10 +1,281 @@ -{ - "banktransfer": { - "number": 3, - "color": "success" +[ + { + "year": 2023, + "donations": [ + { + "date": "16.11.2023", + "amount": 25, + "via": "banktransfer", + "from": null, + "first": false + }, + { + "date": "26.10.2023", + "amount": 20, + "via": "banktransfer", + "from": "ub1x", + "first": false + }, + { + "date": "26.10.2023", + "amount": 20, + "via": "banktransfer", + "from": "ub1x", + "first": false + }, + { + "date": "17.07.2023", + "amount": 20, + "via": "banktransfer", + "from": "ub1x", + "first": false + }, + { + "date": "27.02.2023", + "amount": 40, + "via": "banktransfer", + "from": "xhoff637", + "first": true + }, + { + "date": "20.02.2023", + "amount": 50, + "via": "banktransfer", + "from": null, + "first": false + }, + { + "date": "16.01.2023", + "amount": 12, + "via": "banktransfer", + "from": null, + "first": true + } + ] }, - "cash": { - "number": 0, - "color": "inactive" + { + "year": 2022, + "donations": [ + { + "date": "15.12.2022", + "amount": 20, + "via": "banktransfer", + "from": "ub1x", + "first": false + }, + { + "date": "16.11.2022", + "amount": 25, + "via": "banktransfer", + "from": null, + "first": false + }, + { + "date": "02.11.2022", + "amount": 30, + "via": "banktransfer", + "from": null, + "first": false + }, + { + "date": "11.08.2022", + "amount": 10, + "via": "banktransfer", + "from": "TeomaHK", + "first": false + }, + { + "date": "11.07.2022", + "amount": 10, + "via": "banktransfer", + "from": "TeomaHK", + "first": false + }, + { + "date": "03.06.2022", + "amount": 10, + "via": "banktransfer", + "from": "TeomaHK", + "first": false + }, + { + "date": "11.05.2022", + "amount": 20, + "via": "banktransfer", + "from": "ub1x", + "first": true + }, + { + "date": "03.05.2022", + "amount": 10, + "via": "banktransfer", + "from": "TeomaHK", + "first": false + }, + { + "date": "04.04.2022", + "amount": 10, + "via": "banktransfer", + "from": "TeomaHK", + "first": false + }, + { + "date": "15.03.2022", + "amount": 1, + "via": "banktransfer", + "from": "Jonathan Klatt", + "first": false + }, + { + "date": "10.03.2022", + "amount": 1.42, + "via": "banktransfer", + "from": "Jonathan Klatt", + "first": false + }, + { + "date": "03.03.2022", + "amount": 10, + "via": "banktransfer", + "from": "TeomaHK", + "first": false + }, + { + "date": "14.02.2022", + "amount": 10, + "via": "banktransfer", + "from": "TeomaHK", + "first": false + }, + { + "date": "17.01.2022", + "amount": 1, + "via": "banktransfer", + "from": "Jonathan Klatt", + "first": true + }, + { + "date": "03.01.2022", + "amount": 10, + "via": "banktransfer", + "from": "TeomaHK", + "first": true + } + ] + }, + { + "year": 2021, + "donations": [ + { + "date": "30.11.2021", + "amount": 5, + "via": "banktransfer", + "from": "Rumo", + "first": false + }, + { + "date": "16.11.2021", + "amount": 120, + "via": "banktransfer", + "from": "poetaster", + "first": true + }, + { + "date": "16.11.2021", + "amount": 25, + "via": "banktransfer", + "from": null, + "first": true + }, + { + "date": "20.09.2021", + "amount": 20, + "via": "banktransfer", + "from": "Clemi", + "first": true + }, + { + "date": "26.08.2021", + "amount": 18.27, + "via": "opencollective", + "from": "Skoop", + "first": true + }, + { + "date": "18.08.2021", + "amount": 30, + "via": "banktransfer", + "from": null, + "first": true + }, + { + "date": "02.08.2021", + "amount": 10, + "via": "banktransfer", + "from": null, + "first": false + }, + { + "date": "01.06.2021", + "amount": 20, + "via": "banktransfer", + "from": "Rumo", + "first": true + }, + { + "date": "26.04.2021", + "amount": 8.96, + "via": "banktransfer", + "from": null, + "first": false + }, + { + "date": "23.03.2021", + "amount": 30, + "via": "banktransfer", + "from": null, + "first": true + }, + { + "date": "19.02.2021", + "amount": 20, + "via": "banktransfer", + "from": null, + "first": true + }, + { + "date": "02.02.2021", + "amount": 9.01, + "via": "opencollective", + "from": "Dennis H.", + "first": true + }, + { + "date": "20.01.2021", + "amount": 10, + "via": "banktransfer", + "from": null, + "first": true + }, + { + "date": "17.01.2021", + "amount": 4.41, + "via": "opencollective", + "from": "Michael Haak", + "first": true + } + ] + }, + { + "year": 2020, + "donations": [ + { + "date": "04.12.2020", + "amount": 10, + "via": "banktransfer", + "from": null, + "first": true + } + ] } -} +] diff --git a/src/_data/expenses.json b/src/_data/expenses.json new file mode 100644 index 0000000..0d30bc9 --- /dev/null +++ b/src/_data/expenses.json @@ -0,0 +1,33 @@ +[ + { + "name": "Services", + "type": "Dedicated Server", + "provider": "Hetzner", + "location": "Falkenstein", + "amount": 75.8 + }, + { + "name": "Monitoring", + "type": "Cloud Server", + "provider": "Hetzner", + "location": "Nürnberg", + "amount": 4.51 + }, + { + "name": "Backup", + "type": "Storage Box", + "provider": "Hetzner", + "location": "Helsinki", + "amount": 12.97 + }, + { + "name": { + "en": "Websites", + "de": "Webseiten" + }, + "type": "Webhosting", + "provider": "netcup", + "location": "Nürnberg", + "amount": 2.17 + } +] diff --git a/src/_data/services.json b/src/_data/services.json index 2f3272a..a7b6c30 100644 --- a/src/_data/services.json +++ b/src/_data/services.json @@ -1,111 +1,119 @@ [ - { - "id": "searx", - "name": "Searx", - "icon": "fas fa-search", - "url": "https://searx.sp-codes.de", - "status": "https://searx.sp-codes.de", - "summary": { - "de": "Eine privatsphären-respektierende, hackbare Metasuchmaschine.", - "en": "A privacy-respecting, hackable metasearch engine." - } - }, { "id": "matrix", "name": "Matrix", - "icon": "fas fa-comments", + "icon": "i-comments", "url": "https://chat.sp-codes.de", - "status": "https://matrix.sp-codes.de/_matrix/static/", + "status": "1", "summary": { - "de": "Ein offenes Netzwerk für sichere, dezentralisierte Kommunikation.", - "en": "An open network for secure, decentralized communication." + "de": "Die offene Plattform für sichere und dezentrale Kommunikation.", + "en": "The open platform for secure and decentralized communication." + }, + "ossrox": { + "url": "https://ossrox.org/store/matrix", + "description": { + "de": "Wenn du einen eigenen Matrix-Server für dich, deine Familie oder deine Firma betreiben willst, unterstütze ich dich gerne mit meiner Firma Ossrox damit. Schau dir gerne unser Angebot dazu auf unserer Webseite an oder schreib mir eine Nachricht dazu.", + "en": "If you want to have your own matrix server for you, your family or your company, I would be happy to support you with my company Ossrox. Please have a look at our services on our website or send me a message." + } } }, { "id": "jitsi", "name": "Jitsi Meet", - "icon": "fas fa-users", + "icon": "i-users", "url": "https://jitsi.sp-codes.de", - "status": "https://jitsi.sp-codes.de", + "status": "2", "summary": { - "de": "Eine sichere, einfache und skalierbare Plattform für Videokonferenzen.", - "en": "A secure, simple and scalable platform for video conferencing." + "de": "Einfache Videokonferenzen mit Leichtigkeit.", + "en": "Easy video conferencing with ease." } }, { - "id": "gitea", - "name": "Gitea", - "icon": "fas fa-code", - "url": "https://git.sp-codes.de", - "status": "https://git.sp-codes.de", + "id": "mastodon", + "name": "Mastodon", + "icon": "i-mastodon", + "url": "https://social.sp-codes.de", + "status": "7", "summary": { - "de": "Eine leichtgewichtige Code-Hosting-Plattform für git.", - "en": "A lightweight code hosting platform for git." + "de": "Das soziale Netzwerk für dezentrale und sichere Interaktion.", + "en": "The social network for decentralized and secure interaction." + } + }, + { + "id": "peertube", + "name": "PeerTube", + "icon": "i-peertube", + "url": "https://tube.sp-codes.de", + "status": "9", + "summary": { + "de": "Die freie und dezentrale Plattform für gemeinsames Video-Streaming.", + "en": "The free and decentralized platform for collaborative video streaming." + } + }, + { + "id": "pixelfed", + "name": "Pixelfed", + "icon": "i-pixelfed", + "url": "https://pixel.sp-codes.de", + "status": "11", + "beta": true, + "summary": { + "de": "Das soziale Netzwerk für den Austausch von Bildern und Fotografie.", + "en": "The social network for sharing images and photography." + } + }, + { + "id": "forgejo", + "name": "Forgejo", + "icon": "i-git", + "url": "https://git.sp-codes.de", + "status": "13", + "summary": { + "de": "Die Plattform für einfaches und sicheres Code-Hosting.", + "en": "The platform for easy and secure code hosting." } }, { "id": "connectivitycheck", "name": "Captive Portal Check", - "icon": "fas fa-wifi", - "status": "https://connectivitycheck.sp-codes.de/generate204", + "icon": "i-wifi", + "status": "19", "summary": { - "de": "Eine datenschutzfreundliches Tool, um Anmeldeseiten in WLAN-Netzwerken zu erkennen.", - "en": "A privacy friendly Service to detect captive portals in WIFI networks." + "de": "Überprüfung der Verfügbarkeit von Internetzugang.", + "en": "Verification of Internet access availability." } }, { - "id": "firefox-sync", - "name": "Firefox Sync", - "icon": "fab fa-firefox-browser", - "status": "https://sync.firefox.sp-codes.de/token/", + "id": "ntfy", + "name": "ntfy", + "icon": "i-cloud-download", + "url": "https://ntfy.sp-codes.de", + "status": "18", "summary": { - "de": "Ein Service um Firefox Einstellungen, Lesezeichen, offene Tabs und vieles mehr über verschiedene Geräte zu synchronisieren.", - "en": "A service to sync Firefox settings, bookmarks, open tabs and much more between multiple devices." + "de": "Echtzeitbenachrichtigungen mit UnifiedPush-Unterstützung.", + "en": "Real-time notifications with UnifiedPush support." + } + }, + { + "id": "etherpad", + "name": "Etherpad", + "icon": "i-pencil-square", + "url": "https://pad.sp-codes.de", + "status": "17", + "summary": { + "de": "Gemeinsame Echtzeit-Textbearbeitung für effektive Zusammenarbeit.", + "en": "Collaborative real-time text editing for effective collaboration." } }, { "id": "shields", "name": "Shields", - "icon": "fas fa-tags", + "icon": "i-tags", "url": "https://shields.sp-codes.de", - "status": "https://shields.sp-codes.de", + "status": "20", "summary": { - "de": "Prägnante, konsistente und lesbare Badges im SVG- und Rasterformat.", - "en": "Concise, consistent, and legible badges in SVG and raster format." - } - }, - { - "id": "invidious", - "name": "Invidious", - "icon": "fab fa-youtube", - "url": "https://invidious.sp-codes.de", - "status": "https://invidious.sp-codes.de", - "summary": { - "de": "Ein alternatives YouTube-Frontend.", - "en": "An alternative YouTube-Frontend." - } - }, - { - "id": "nitter", - "name": "Nitter", - "icon": "fab fa-twitter", - "url": "https://nitter.sp-codes.de", - "status": "https://nitter.sp-codes.de", - "summary": { - "de": "Ein alternatives Twitter-Frontend.", - "en": "An alternative Twitter-Frontend." - } - }, - { - "id": "yotter", - "name": "Yotter", - "icon": "fas fa-desktop", - "url": "https://yotter.sp-codes.de", - "status": "https://yotter.sp-codes.de", - "beta": true, - "summary": { - "de": "Ein alternatives Twitter- und YouTube-Frontend.", - "en": "An alternative Twitter- and YouTube-Frontend." + "de": "Visuelle Badges zur Anzeige von Projektinformationen und Status.", + "en": "Visual badges for displaying project information and status." } } ] diff --git a/src/_data/strings.json b/src/_data/strings.json index 5748020..2fe4cff 100644 --- a/src/_data/strings.json +++ b/src/_data/strings.json @@ -32,11 +32,59 @@ "en": "Online" }, "outage": { - "de": "Fehler", - "en": "Error" + "de": "Ausfall", + "en": "Outage" }, "maintenance": { "de": "Wartung", "en": "Maintenance" + }, + "date": { + "de": "Datum", + "en": "Date" + }, + "amount": { + "de": "Betrag", + "en": "Amount" + }, + "via": { + "de": "Via", + "en": "Via" + }, + "from": { + "de": "Von", + "en": "Form" + }, + "banktransfer": { + "de": "Überweisung", + "en": "Bank transfer" + }, + "total": { + "de": "Gesamt", + "en": "Total" + }, + "name": { + "de": "Name", + "en": "Name" + }, + "type": { + "de": "Typ", + "en": "Type" + }, + "provider": { + "de": "Anbieter", + "en": "Provider" + }, + "location": { + "de": "Standort", + "en": "Location" + }, + "month": { + "de": "Monat", + "en": "Month" + }, + "year": { + "de": "Jahr", + "en": "Year" } } diff --git a/src/_includes/donations-current.html b/src/_includes/donations-current.html new file mode 100644 index 0000000..16e3174 --- /dev/null +++ b/src/_includes/donations-current.html @@ -0,0 +1,36 @@ +
{{strings.date[locale]}} | +{{strings.via[locale]}} | +{{strings.from[locale]}} | +{{strings.amount[locale]}} | +||
---|---|---|---|---|---|
{{donation.date}} | + {% if donation.via == 'opencollective' %} +Open Collective | + {% else %} +{{strings[donation.via][locale]}} | + {% endif %} + {% if donation.from %} +{{donation.from}} | + {% else %} +*** | + {% endif %} +{{donation.amount | amount}} € | +
{{strings.total[locale]}} | +{{donations[0].donations | sum | amount}} € | +
{{strings.date[locale]}} | +{{strings.via[locale]}} | +{{strings.from[locale]}} | +{{strings.amount[locale]}} | +||
---|---|---|---|---|---|
{{donation.date}} | + {% if donation.via == 'opencollective' %} +Open Collective | + {% else %} +{{strings[donation.via][locale]}} | + {% endif %} + {% if donation.from %} +{{donation.from}} | + {% else %} +*** | + {% endif %} +{{donation.amount | amount}} € | +
{{strings.total[locale]}} | +{{year.donations | sum | amount}} € | +
{{strings.name[locale]}} | +{{strings.type[locale]}} | +{{strings.provider[locale]}} | +{{strings.location[locale]}} | +{{strings.amount[locale]}} / {{strings.month[locale]}} | +{{strings.amount[locale]}} / {{strings.year[locale]}} | +|
---|---|---|---|---|---|---|
{{expense.name[locale]}} | + {% else %} +{{expense.name}} | + {% endif %} +{{expense.type}} | +{{expense.provider}} | +{{expense.location}} | +{{expense.amount | amount}} € | +{{expense.amount | year | amount}} € | +
{{strings.total[locale]}} | +{{expenses | sum | amount}} € | +{{expenses | sum | year | amount}} € | +
+ Komm gerne in der Matrix-Gruppe vorbei und lass uns diskutieren. Ich freue mich auf dein Feedback! +
+ #sp-codes:matrix.sp-codes.de ++ Mein Name ist Samuel Philipp und ich bin ein Software Engineer aus Magdeburg. In meiner Freizeit + hoste ich verschiedene freie Dienste. Hier schreibe ich Artikel rund um + Sicherheit und Datenschutz. +
+ Mehr erfahren +