Revert "Initial Commit for Astro Rewrite"
/ Build and Deploy Website (push) Successful in 2m5s Details

This reverts commit cc22cb0ec8.
This commit is contained in:
Brayd 2024-01-04 15:50:12 +01:00
parent cc22cb0ec8
commit dfd9ffe6e3
Signed by: brayd
SSH Key Fingerprint: SHA256:qfebJ1zYUipSSbdcVk/qygkt0xr4VSzCKk7LXw6DGe0
134 changed files with 1072 additions and 22461 deletions

View File

@ -1,9 +0,0 @@
root = true
[*]
end_of_line = lf
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

View File

@ -1,4 +0,0 @@
dist
node_modules
.github
.changeset

View File

@ -1,43 +0,0 @@
/** @type {import("@types/eslint").Linter.Config} */
module.exports = {
ignorePatterns: ["node_modules", "dist"],
root: true,
env: {
node: true,
},
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint", "prettier"],
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier",
"plugin:astro/recommended",
"plugin:astro/jsx-a11y-recommended",
],
rules: {
"@typescript-eslint/no-var-requires": "warn",
"@typescript-eslint/no-unused-vars": [
"warn",
{ varsIgnorePattern: "Props", ignoreRestSiblings: true },
],
},
overrides: [
{
files: ["*.astro"],
parser: "astro-eslint-parser",
parserOptions: {
parser: "@typescript-eslint/parser",
extraFileExtensions: [".astro"],
},
rules: {
"astro/jsx-a11y/no-redundant-roles": [
"error",
{
ul: ["list"],
},
],
},
},
],
};

View File

@ -1 +0,0 @@
WEBMENTION_API_KEY=

View File

@ -0,0 +1,43 @@
on: [push]
jobs:
Build and Deploy Website:
runs-on: docker
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Update Repositories
run: apt-get update && apt-get upgrade -y
- name: Install Dependencies
run: apt-get install gcc git wget -y
- name: Download Golang v1.21.5
run: wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz
- name: Unpack Golang
run: rm -rf /usr/local/go && tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz
- name: Remove downloaded archive
run: rm ./go1.21.5.linux-amd64.tar.gz
- name: Download Hugo v0.111.3 binary
run: wget https://github.com/gohugoio/hugo/releases/download/v0.111.3/hugo_0.111.3_linux-amd64.deb
- name: Install hugo
run: apt-get install ./hugo_0.111.3_linux-amd64.deb
- name: Remove downloaded .deb package
run: rm ./hugo_0.111.3_linux-amd64.deb
- name: Building Hugo Site
run: hugo --debug
- name: Deploy built site
uses: https://github.com/Dylan700/sftp-upload-action@latest
with:
server: ${{secrets.SSH_URL}}
username: ${{secrets.SSH_USER}}
password: ${{secrets.SSH_PASSWORD}}
port: 22
uploads: |
./public => /www/
ignore: |
*.git
*.forgejo
delete: 'true'

32
.gitignore vendored
View File

@ -1,29 +1,3 @@
# build output
dist/
.output/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
# Linux files
lost+found/
# misc
*.pem
.cache
.astro
.
public/
.hugo_build.lock
.DS_Store

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "themes/blowfish"]
path = themes/blowfish
url = https://github.com/nunocoracao/blowfish.git

1
.npmrc
View File

@ -1 +0,0 @@
enable-pre-post-scripts=true

View File

@ -1,8 +0,0 @@
*.min.js
node_modules
# cache-dirs
**/.cache
pnpm-lock.yaml
dist

View File

@ -1,17 +0,0 @@
/** @type {import("@types/prettier").Options} */
module.exports = {
printWidth: 100,
semi: true,
singleQuote: false,
tabWidth: 2,
useTabs: true,
plugins: ["prettier-plugin-astro", "prettier-plugin-tailwindcss" /* Must come last */],
overrides: [
{
files: "*.astro",
options: {
parser: "astro",
},
},
],
};

View File

@ -1,4 +0,0 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

11
.vscode/launch.json vendored
View File

@ -1,11 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

17
.vscode/settings.json vendored
View File

@ -1,17 +0,0 @@
{
"editor.formatOnSave": true,
"[markdown]": {
"editor.wordWrap": "on",
"editor.codeActionsOnSave": {
"source.fixAll.markdownlint": "explicit"
}
},
"prettier.documentSelectors": ["**/*.astro"],
"eslint.validate": [
"javascript",
"javascriptreact",
"astro", // Enable .astro
"typescript", // Enable .ts
"typescriptreact"
]
}

27
LICENSE
View File

@ -1,26 +1 @@
MIT License
Copyright (c) 2022 Chris Williams
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
The Blog Posts itself (their content) are licensed under CC BY 4.0 DEED (https://creativecommons.org/licenses/by/4.0/)
Graphics are created by myself or from Unsplash.com if not declared otherwise.
<a rel="license" href="http://creativecommons.org/licenses/by/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/4.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.

View File

@ -1,76 +1,6 @@
# Braydmedia Web
This is the repository of the website hosted at https://braydmedia.de. The website was built using [Astro](https://astro.build/) and the theme [Astro Cactus](https://github.com/chrismwilliams/astro-theme-cactus).
This is the repository of the website at https://braydmedia.de. The website was built using [Hugo](https://gohugo.io/).
## Commands
Replace pnpm with your choice of npm / yarn
| Command | Action |
| :--------------- | :------------------------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm dev` | Starts local dev server at `localhost:3000` |
| `npm build` | Build your production site to `./dist/` |
| `npm postbuild` | Pagefind script to build the static search of your blog posts |
| `npm preview` | Preview your build locally, before deploying |
| `npm sync` | Generate types based on your config in `src/content/config.ts` |
## Configure
- Edit the config file `src/site.config.ts` for basic site meta data
- Read [this post](http://astro-cactus.chriswilliams.dev/posts/webmentions/) for adding webmentions to your site, otherwise set `siteConfig.webmentions.link` to empty value.
- Update file `astro.config.ts` site property with your own domain
- Replace & update files within the `/public` folder:
- favicon.ico & other social icons
- robots.txt - update the Sitemap url to your own domain
- manifest.webmanifest
- Modify file `src/styles/global.css` with your own light and dark styles
- Edit social links in `src/components/SocialList.astro` to add/replace your media profile. Icons can be found @ [icones.js.org](https://icones.js.org/)
- Create / edit posts for your blog within `src/content/post/` with .md/mdx file(s). See [below](#adding-posts) for more details.
- OG Image:
- If you would like to change the style of the generated image the Satori library creates, open up `src/pages/og-image/[slug].png.ts` to the markup function where you can edit the html/tailwind-classes as necessary. You can also use this [satori playground](https://og-playground.vercel.app/) to aid your design.
- If you would like to generate svg og images rather than the default .png ones, you will need to remove the @resvg/resvg-js library, and return the svg within the body of the get function from the file `src/pages/og-image/[slug].png.ts`.
- You can also create your own og images and skip satori generating if for you by adding an ogImage property in the frontmatter with a link to the asset, an example can be found in `src/content/post/social-image.md`. More info on frontmatter can be found [here](#frontmatter)
- Optional:
- Fonts: This theme sets the body element to the font family `font-mono`, located in the global css file `src/styles/global.css`. You can change fonts by removing the variant `font-mono`, after which TailwindCSS will default to the `font-sans` [font family stack](https://tailwindcss.com/docs/font-family).
## Adding posts
This theme utilises [Content Collections](https://docs.astro.build/en/guides/content-collections/) to organise Markdown and/or MDX files, as well as type-checking frontmatter with a schema -> `src/content/config.ts`.
Adding a post is as simple as adding your .md(x) files to the `src/content/post` folder, the filename of which will be used as the slug/url. The posts included with this template are there as an example of how to structure your frontmatter. Additionally, the [Astro docs](https://docs.astro.build/en/guides/markdown-content/) has a detailed section on markdown pages.
### Frontmatter
| Property (\* required) | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| title \* | Self explanatory. Used as the text link to the post, the h1 on the posts' page, and the pages title property. Has a max length of 60 chars, set in `src/content/config.ts` |
| description \* | Similar to above, used as the seo description property. Has a min length of 50 and a max length of 160 chars, set in the post schema. |
| publishDate \* | Again pretty simple. To change the date format/locale, currently **en-GB**, update the date option in `src/site.config.ts`. Note you can also pass additional options to the component `<FormattedDate>` if required. |
| updatedDate | This is an optional date representing when a post has been updated, in the same format as the publishDate. Note that by providing this field, the sorting function, found in `src/utils/post.ts`, `sortMDByDate` will order by this field rather than its published date. |
| tags | Tags are optional with any created post. Any new tag(s) will be shown in `yourdomain.com/posts` & `yourdomain.com/tags`, and generate the page(s) `yourdomain.com/tags/[yourTag]` |
| coverImage | This is an optional object that will add a cover image to the top of a post. Include both a `src`: "_path-to-image_" and `alt`: "_image alt_". You can view an example in `src/content/post/cover-image.md`. |
| ogImage | This is an optional property. An OG Image will be generated automatically for every post where this property **isn't** provided. If you would like to create your own for a specific post, include this property and a link to your image, the theme will then skip automatically generating one. |
| draft | This is an optional property as it is set to false by default in the schema. By adding true, the post will be filtered out of the production build in a number of places, inc. getAllPosts() calls, og-images, rss feeds, and generated page[s]. You can view an example in `src/content/post/draft-post.md` |
## Pagefind search
This integration brings a static search feature for searching blog posts. In its current form, pagefind only works once the site has been built. This theme adds a postbuild script that should be run after Astro has built the site. You can preview locally by running both build && postbuild.
Search results only includes blog posts. If you would like to include other/all your pages, remove/re-locate the attribute `data-pagefind-body` to the article tag found in `src/layouts/BlogPost.astro`.
It also allows you to filter posts by tags added in the frontmatter of blog posts. If you would rather remove this, remove the data attribute `data-pagefind-filter="tag"` from the link in `src/components/blog/Hero.astro`.
If you would rather not include this integration, simply remove the component `src/components/Search.astro`, and uninstall both `@pagefind/default-ui` & `pagefind` from package.json. You will also need to remove the postbuild script from here as well.
You can reduce the initial css payload of your css, as demonstrated [here](https://github.com/chrismwilliams/astro-theme-cactus/pull/145#issue-1943779868), by lazy loading the web components styles.
## Acknowledgment
The Astro Cactus Theme this website is using was inspired by [Hexo Theme Cactus](https://github.com/probberechts/hexo-theme-cactus)
## License
MIT (For the theme)
CC-BY-4.0 DEED for the blog posts contents and graphics
See more in the LICENSE file in this repository.
<a rel="license" href="http://creativecommons.org/licenses/by/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/4.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.

6
archetypes/default.md Normal file
View File

@ -0,0 +1,6 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---

BIN
assets/Brayd.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

BIN
assets/dienste/calckey.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 KiB

BIN
assets/homepageImage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 MiB

View File

@ -1,60 +0,0 @@
import { defineConfig } from "astro/config";
import fs from "fs";
import mdx from "@astrojs/mdx";
import tailwind from "@astrojs/tailwind";
import sitemap from "@astrojs/sitemap";
import remarkUnwrapImages from "remark-unwrap-images";
import rehypeExternalLinks from "rehype-external-links";
import { remarkReadingTime } from "./src/utils/remark-reading-time";
// https://astro.build/config
export default defineConfig({
// ! Please remember to replace the following site property with your own domain
site: "https://astro-cactus.chriswilliams.dev/",
markdown: {
remarkPlugins: [remarkUnwrapImages, remarkReadingTime],
rehypePlugins: [
[rehypeExternalLinks, { target: "_blank", rel: ["nofollow, noopener, noreferrer"] }],
],
remarkRehype: { footnoteLabelProperties: { className: [""] } },
shikiConfig: {
theme: "dracula",
wrap: true,
},
},
integrations: [
mdx({}),
tailwind({
applyBaseStyles: false,
}),
sitemap(),
],
image: {
domains: ["webmention.io"],
},
// https://docs.astro.build/en/guides/prefetch/
prefetch: true,
vite: {
plugins: [rawFonts([".ttf"])],
optimizeDeps: {
exclude: ["@resvg/resvg-js"],
},
},
});
function rawFonts(ext: Array<string>) {
return {
name: "vite-plugin-raw-fonts",
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore:next-line
transform(_, id) {
if (ext.some((e) => id.endsWith(e))) {
const buffer = fs.readFileSync(id);
return {
code: `export default ${JSON.stringify(buffer)}`,
map: null,
};
}
},
};
}

View File

@ -0,0 +1,80 @@
# -- Site Configuration --
# Refer to the theme docs for more details about each of these parameters.
# https://blowfish.page/docs/getting-started/
theme = "blowfish"
baseURL = "https://braydmedia.de/"
defaultContentLanguage = "de"
# pluralizeListTitles = "true" # hugo function useful for non-english languages, find out more in https://gohugo.io/getting-started/configuration/#pluralizelisttitles
enableRobotsTXT = true
paginate = 10
summaryLength = 0
buildDrafts = false
buildFuture = false
# googleAnalytics = "G-XXXXXXXXX"
[imaging]
anchor = 'Center'
[taxonomies]
tag = "tags"
category = "categories"
author = "authors"
series = "series"
[sitemap]
changefreq = 'daily'
filename = 'sitemap.xml'
priority = 0.5
[outputs]
home = ["HTML", "RSS", "JSON"]
[related]
threshold = 0
toLower = false
[[related.indices]]
name = "tags"
weight = 100
[[related.indices]]
name = "categories"
weight = 100
[[related.indices]]
name = "series"
weight = 50
[[related.indices]]
name = "authors"
weight = 20
[[related.indices]]
name = "date"
weight = 10
[[related.indices]]
applyFilter = false
name = 'fragmentrefs'
type = 'fragments'
weight = 10
[privacy]
[privacy.disqus]
disable = true
[privacy.googleAnalytics]
disable = true
[privacy.instagram]
disable = true
[privacy.twitter]
disable = true
[privacy.vimeo]
disable = true
[privacy.youtube]
disable = true

View File

@ -0,0 +1,27 @@
languageCode = "de"
languageName = "Deutsch"
weight = 1
title = "Braydmedia"
[params]
displayName = "DE"
isoCode = "de"
rtl = false
dateFormat = "2 January 2006"
logo = "img/blowfish_logo_transparent.png"
# secondaryLogo = "img/secondary-logo.png"
# description = "My awesome website"
copyright = "Falls nicht anders notiert, unterliegt der Inhalt dieser Seite der [Creative Commons Attribution 4.0 International license](https://creativecommons.org/licenses/by/4.0/). Grafiken sind wenn nicht anders desklariert selbst erstellt oder von [Unsplash.com](https://unsplash.com)"
[author]
name = "Brayd"
image = "Brayd.png"
headline = "Admin von Braydmedia. Ich arbeite was mit Computern und so."
bio = "Vegan, Buddhist und Informatiker"
links = [
{ email = "mailto:info@braydmedia.de" },
{ code = "https://code.braydmedia.de/brayd" },
{ mastodon = "https://connect.braydmedia.de/@brayd" },
{ youtube = "https://tube.tchncs.de/a/braydofficial" },
{ envelope = "https://matrix.to/#/@brayd:chat.braydmedia.de" }
]

View File

@ -0,0 +1,13 @@
# -- Markup --
# These settings are required for the theme to function.
[goldmark]
[goldmark.renderer]
unsafe = true
[highlight]
noClasses = false
[tableOfContents]
startLevel = 2
endLevel = 4

View File

@ -0,0 +1,108 @@
# -- Main Menu --
# The main menu is displayed in the header at the top of the page.
# Acceptable parameters are name, pageRef, page, url, title, weight.
#
# The simplest menu configuration is to provide:
# name = The name to be displayed for this menu link
# pageRef = The identifier of the page or section to link to
#
# By default the menu is ordered alphabetically. This can be
# overridden by providing a weight value. The menu will then be
# ordered by weight from lowest to highest.
[[main]]
name = "Blog"
pageRef = "blog"
weight = 20
[[main]]
name = "Tags"
pageRef = "tags"
weight = 15
[[main]]
name = "Über mich"
pageRef = "about"
weight = 10
[[main]]
name = "Dienste"
pageRef = "dienste"
weight = 10
[[main]]
name = "Matrix Raum"
url = "https://matrix.to/#/#braydmedia:chat.braydmedia.de"
weight = 5
[[main]]
name = "Status"
url = "https://status.braydmedia.de/status/braydmedia"
parent = "Dienste"
weight = 10
[[main]]
name = "Matrix"
pageRef = "matrix-regeln"
parent = "Dienste"
weight = 20
[[main]]
name = "Connect"
pageRef = "connect"
parent = "Dienste"
weight = 30
#[[main]]
# name = "Parent"
# weight = 20
#[[main]]
# name = "example sub-menu 1"
# parent = "Parent"
# pageRef = "posts"
# weight = 20
#[[main]]
# name = "example sub-menu 2"
# parent = "Parent"
# pageRef = "posts"
# weight = 20
#[[subnavigation]]
# name = "An interesting topic"
# pageRef = "tags/interesting-topic"
# weight = 10
#[[subnavigation]]
# name = "My Awesome Category"
# pre = "github"
# pageRef = "categories/awesome"
# weight = 20
#[[main]]
# name = "Categories"
# pageRef = "categories"
# weight = 20
#[[main]]
# name = "Tags"
# pageRef = "tags"
# weight = 30
# -- Footer Menu --
# The footer menu is displayed at the bottom of the page, just before
# the copyright notice. Configure as per the main menu above.
[[footer]]
name = "Impressum"
pageRef = "impressum"
weight = 10
[[footer]]
name = "Datenschutzerklärung"
pageRef = "datenschutz"
weight = 20

View File

@ -0,0 +1,3 @@
[hugoVersion]
extended = false
min = "0.87.0"

141
config/_default/params.toml Normal file
View File

@ -0,0 +1,141 @@
# -- Theme Options --
# These options control how the theme functions and allow you to
# customise the display of your website.
#
# Refer to the theme docs for more details about each of these parameters.
# https://blowfish.page/docs/configuration/#theme-parameters
colorScheme = "forest"
defaultAppearance = "dark" # valid options: light or dark
autoSwitchAppearance = true
enableSearch = true
enableCodeCopy = true
# mainSections = ["section1", "section2"]
# robots = ""
disableImageOptimization = true
defaultBackgroundImage = "homepageImage.png" # used as default for background images
# defaultFeaturedImage = "IMAGE.jpg" # used as default for featured images in all articles
highlightCurrentMenuArea = true
smartTOC = true
# smartTOCHideUnfocusedChildren = true
[header]
layout = "fixed-fill-blur" # valid options: basic, fixed, fixed-fill, fixed-fill-blur
[footer]
showMenu = true
showCopyright = true
showThemeAttribution = false
showAppearanceSwitcher = true
showScrollToTop = true
[homepage]
layout = "background" # valid options: page, profile, hero, card, background, custom
homepageImage = "homepageImage.png" # used in: hero, and card
showRecent = true
showRecentItems = 6
showMoreLink = true
showMoreLinkDest = "/blog"
cardView = true
cardViewScreenWidth = false
layoutBackgroundBlur = false # only used when layout equals background
[article]
showDate = true
showViews = false
showLikes = false
showDateUpdated = true
showAuthor = true
showHero = true
heroStyle = "basic" # valid options: basic, big, background, thumbAndBackground
layoutBackgroundBlur = true # only used when heroStyle equals background or thumbAndBackground
layoutBackgroundHeaderSpace = true # only used when heroStyle equals background
showBreadcrumbs = false
showDraftLabel = true
showEdit = false
editURL = "https://code.braydmedia.de/brayd/braydmedia-web/"
editAppendPath = true
seriesOpened = false
showHeadingAnchors = true
showPagination = true
invertPagination = false
showReadingTime = true
showTableOfContents = true
showRelatedContent = false
relatedContentLimit = 3
showTaxonomies = true
showAuthorsBadges = true
showWordCount = true
showSummary = true
# sharingLinks = [ "linkedin", "twitter", "reddit", "pinterest", "facebook", "email", "whatsapp", "telegram"]
[list]
showHero = true
heroStyle = "basic" # valid options: basic, big, background, thumbAndBackground
layoutBackgroundBlur = true # only used when heroStyle equals background or thumbAndBackground
layoutBackgroundHeaderSpace = true # only used when heroStyle equals background
showBreadcrumbs = false
showSummary = false
showViews = false
showLikes = false
showTableOfContents = false
showCards = false
groupByYear = true
cardView = false
cardViewScreenWidth = false
constrainItemsWidth = false
[sitemap]
excludedKinds = ["taxonomy", "term"]
[taxonomy]
showTermCount = true
showHero = false
# heroStyle = "background" # valid options: basic, big, background, thumbAndBackground
showBreadcrumbs = false
showViews = false
showLikes = false
showTableOfContents = false
cardView = false
[term]
showHero = false
# heroStyle = "background" # valid options: basic, big, background, thumbAndBackground
showBreadcrumbs = false
showViews = false
showLikes = false
showTableOfContents = true
groupByYear = false
cardView = false
cardViewScreenWidth = false
[firebase]
# apiKey = "XXXXXX"
# authDomain = "XXXXXX"
# projectId = "XXXXXX"
# storageBucket = "XXXXXX"
# messagingSenderId = "XXXXXX"
# appId = "XXXXXX"
# measurementId = "XXXXXX"
[fathomAnalytics]
# site = "ABC12345"
# domain = "llama.yoursite.com"
[buymeacoffee]
# identifier = ""
# globalWidget = true
# globalWidgetMessage = "Hello"
# globalWidgetColor = "#FFDD00"
# globalWidgetPosition = "Right"
[verification]
# google = ""
# bing = ""
# pinterest = ""
# yandex = ""

20
content/about.md Normal file
View File

@ -0,0 +1,20 @@
---
title: "Über Mich"
date: 2023-03-30T00:18:35+02:00
draft: false
---
## Hey, ich bin Brayd!
Ich finde es toll, dass du dich für meinen Blog interessierst und deinen Weg hier her gefunden hast! Auf meinem Blog wirst du alles finden, was mich selbst interessiert. Das kann von Themen aus der IT, über Veganismus bis hin zu vielen anderen Dingen reichen.
Privat beschäftige ich mich neben der Administration von Servern auch mit dem Buddhismus, dem Kung Fu, Qi Gong, Tai Chi und dem Daoismus.
Folg mir doch gerne auf meinen Social Media-Seiten, die du auf der [Startseite](https://braydmedia.de/) finden kannst!
## Mein Werdegang
| **Zeitangabe** | **Beschreibung** |
|:-----------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------|
| 02/2023 - Heute | Junior IT-Systemadministrator |
| 04/2022 - Heute | Betrieb verschiedener Dienste als Hobby unter dem Namen "Braydmedia". Unter anderem Matrix, Calckey, Nextcloud und mehr |
| 09/2020 - 01/2023 | Ausbildung zum Fachinformatiker für Systemintegration |
| 2014 - 04/2022 | Auseinandersetzung mit der Administration von UNIX(like)-Systemen, dem aufsetzen verschiedener Dienste, Docker, Grundlagen der Netzwerktechnik und mehr. |

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 KiB

View File

@ -0,0 +1,13 @@
---
title: "Willkommen in meinem Blog"
date: 2023-03-29T22:49:48+02:00
draft: false
tags:
- Veganismus
- Informatik
- Info
---
Willkommen in meinem Blog. Ich nenne mich online Brayd und möchte diesen Blog nutzen, um mein Wissen und meine Interessen mit euch zu teilen. Du kannst diesen Blog natürlich auch als RSS-Feed abonnieren (z.B. mit Thunderbird), um keine neuen Posts zu verpassen.
Ich möchte hier überwiegend über die Themen schreiben, die mich selbst interessieren, wie beispielsweise Veganismus, Informatik und andere Themen.
Beruflich arbeite ich als Systemadministrator und arbeite täglich mit Windows, MacOS und Linux.
Privat beschäftige ich mich eher mit dem Programmieren in Rust, Python, Go(lang) und anderen Sprachen, sowie mit dem Buddhismus, Kung Fu, Qi Gong, Tai Chi, Daoismus und mehr!

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -0,0 +1,29 @@
---
title: "IT ist cool, bis irgendwas nicht funktioniert"
date: 2023-03-30T00:50:53+02:00
draft: false
toc: false
images:
tags:
- Informatik
- Matrix
- Messenger
- Synapse
---
Ich arbeite beruflich als IT-Systemadministrator und verbringe dadurch natürlich auch privat Zeit damit irgendein IT-Zeug zu machen. Ich selbst habe zum Beispiel meinen eigenen Matrix-Server, der mit Synapse läuft und noch ein paar andere Dienste.
Matrix ist mein persönlicher Go-To-Messenger zusammen mit Signal. WhatsApp nutze ich selbst zum Beispiel quasi gar nicht. Entsprechend ärgerlich war es, dass ich meinen Server letzten Freitag nach der Arbeit neugestartet habe und aus dem Nichts mein Setup mit Synapse nicht mehr funktioniert hat.
Synapse war nur noch per HTTP erreichbar und mein Docker-Container, der mir SSL-Zertifikate für HTTPS automatisch mit LetsEncrypt validiert brachte einen Error, dass die validierung nicht möglich ist. Warum? Keine Ahnung um ehrlich zu sein. Ich habe den Server und auch die Container schon dutzende male geupdated, neugestartet, usw.
Also habe ich mein ganzes Wochenende damit verbracht zu versuchen meinen Server wieder funktionell zu bekommen. Mehrere Leute konnten mir nicht weiterhelfen, weil effektiv nichts falsch konfiguriert war. Besser noch: Es wurde nichts an der seit Monaten bestehenden Konfiguration geändert.
Ärgerlich war das vor allem, weil meinen Matrix-Server mehrere Personen nutzen, die dort natürlich auch mit den Leuten schreiben können wollen. Funktionierte aber dadurch nicht mehr.
Aus Verzweiflung habe ich als letzte Lösungsmöglichkeit vorhin meinen kompletten Nginx Container sowie den LetsEncrypt Proxy Companion komplett gelöscht. Alle Configs, alle Volumes alles. Danach habe ich natürlich alles wieder 1:1 genauso angelegt.
Siehe da: Komischerweise funktioniert es wieder...
Ich habe nach wie vor keine Ahnung was das Problem war und warum es mysteriöserweise durch neu anlegen mit identischer Konfiguration wieder funktioniert hat. Ich bin nur froh, dass es funktioniert. Das ganze hat mir natürlich mein Wochenende geraubt, sodass ich Morgen wieder in den Arbeitsalltag zurück muss und nicht sonderlich erholt bin. Aber naja, was soll man machen. ^^
Technologie und IT ist super und man kann sehr coole Dinge machen. Nur wenn es nicht funktioniert ist das sehr frustrierend, vor allem, wenn man nicht rausfindet, was das Problem war.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@ -0,0 +1,36 @@
---
title: "Warum Mastodon und das Fediverse so gut sind"
date: 2023-03-30T12:54:13+02:00
draft: false
tags:
- Informatik
- Open Source
- Mastodon
- Fediverse
- Social Media
---
Mastodon und das Fediverse sind seit dem Elon Musk Twitter aufgekauft haben immer präsenter in der Öffentlichkeit. Was früher als Plattform für "Nerds" abgestempelt wurde, wird heute von Personen mit vollständig unterschiedlichen Interessen genutzt.
Vielleicht sollte ich jedoch erstmal erklären, was Mastodon und was das Fediverse ist, damit der Artikel nicht verwirrend für diejenigen ist, die keine Ahnung davon haben, was dezentralität ist, oder wieso Mastodon das Fediverse ist, jedoch das Fediverse nicht Mastodon ist.
## Das Fediverse
Mastodon selbst ist ein Bestandteil des Fediverse. Als Fediverse bezeichnet man einen Zusammenschluss verschiedener Server, die verschiedene Softwares betreiben, die jedoch alle miteinander über das gleiche Protokoll (Activity Pub) kommunizieren. Im Fediverse gibt es mehrere verschiedene Softwares wie Mastodon, Pixelfed, PeerTube und mehr. Alle haben einen Fokus auf einen bestimmten Bereich. Durch das gleiche Protokoll kann man jedoch auch von Mastodon aus Nutzern folgen, die ihren Account auf Pixelfed haben und anders herum.
Die verschiedenen Softwares sind auch untereinander kompatibel und durch die Aufteilung des gesamten Netzwerkes auf mehrere Server ist das Fediverse dezentral und kann nicht von einer Gruppierung, einer Regierung oder einem Unternehmen kontrolliert oder aufgekauft werden. Jede einzelne Person kann sich mit dem nötigen Know-How einen eigenen Server aufsetzen.
## Dezentral? Was ist das überhaupt?
Dezentral bedeutet, dass das Netzwerk (Mastodon bzw. das Fediverse - also auch PeerTube, Pixelfed, etc.) nicht von einer zentralen Stelle verwaltet werden und sich selbst moderieren. Gleichzeitig ist eine Interaktion von einem Mastodon-Server zu einem anderen Mastodon-Server oder auch von einem Mastodon-Server zu einem anderen Pixelfed-Server ohne Probleme möglich. Das ganze wird im Fall von Mastodon bzw. dem Fediverse auch oft mit dem Prinzip der E-Mail verglichen. Jeder kann sich bei einem beliebigen E-Mail-Provider registrieren aber dadurch, dass E-Mail auf dem gleichen Protokoll basiert kann man auch einer Person Mails schicken, die nicht bei dem selben Provider wie man selbst registriert ist.
## Was sind die Vorteile von Mastodon / dem Fediverse gegenüber von Twitter & Co?
Es gibt mehrere Vorteile. Zum einen sind Dienste im Fediverse besser in Hinsicht des Datenschutzes. Da die meisten Server sich durch Spenden finanzieren findet kein Datenverkauf statt. Ein Mastodon-Server muss keinen Gewinn erzielen, wie es ein Unternehmen tun muss. Stattdessen reicht es vollkommen aus, wenn die Administratoren des Server die Kosten decken können, die sie selbst haben um die Instanz online zu halten. Meine Instanz [Braydmedia](https://braydmedia.de) kostet mich beispielsweise 19€ pro Monat (+ ca. 10€ pro Jahr für die Domain). Währendessen kann ich ca. 100 User beherbergen. Das bedeutet, dass wenn jeder 1€ an die Administratoren seiner eigenen Instanz spenden würde, jede Instanz sich problemlos finanzieren könnte.
Abgesehen davon gibt es natürlich noch weitere Vorteile, wie beispielsweise nicht vorhandene Werbung oder der Tatsache, dass jede Instanz eigene Regeln für sich und seine Nutzer:innen definieren kann und somit Instanzen, die Trolle dulden oder keine Personen bannen sehr schnell von den Instanzen "ausgegrenzt" sind, die Wert darauf legen, dass keine Trolle in den Feeds der Nutzer:innen unterwegs sind. Es lohnt sich daher sich selbst bei einer Instanz / einem Server zu registrieren, bei dem einem selbst die Regeln passen. Findet man keinen, kann man sich auch einfach eine eigene Instanz aufsetzen. - Es gibt im Fediverse etliche "Single-User-Instanzen", sprich Instanzen, auf denen nur eine Person registriert ist.
Auch Algorithmen findet man im Fediverse nicht. Die Timeline ist chronologisch. Das bedeutet, dass das Nutzerverhalten der meisten Nutzer:innen in sozialen Medien im Fediverse auch eher zum sozialen Verhalten tendiert, statt zu einem "Oh verdammt, meine Reichweite geht runter, ich muss den Algorithmus austricksen!". Das sorgt von alleine für eine sehr angenehme Atomosphäre. Außerdem hat die Vergangenheit und damit verbundene Erfahrungen auch gezeigt, dass Instanzen, die gut moderiert sind (was die meisten im Fediverse sind) sehr schnell auch Meldungen reagieren. Eigentlich fast immer schneller als es bei Twitter & co je der Fall war. Sehr kritische Posts, die einfach nur Hass verbreiten und nicht mehr unter die Meinungsfreiheit fallen sind daher auch sehr schnell weg, wenn sie durch Nutzer:innen gemeldet werden. Es lohnt sich daher auch im Fediverse die Meldefunktion zu nutzen, wenn man Beiträge sieht, die gegen die Regeln der eigenen Instanz verstoßen.
Neben diesen Tools, die Administratoren in die Hand gegeben werden, hat auch jede:r Nutzer:in die Möglichkeit für sich selbst Nutzer zu blockieren, auszublenden ohne sie zu blockieren oder gar gesamte Instanzen für sich selbst zu blockieren. Man kann sich also nach und nach seinen eigenen Feed sehr sehr gut individualisieren und anpassen an das, was einen selbst interessiert.
## Fazit
Das Fediverse ist eine geniale Technologie unserer Zeit und ich fühle mich selbst als Nutzer deutlich wohler auf Mastodon, Pixelfed, PeerTube & co, als ich es auf Twitter, Instagram, YouTube, etc. getan habe. Es macht einfach wieder Spaß Social Media zu nutzen und man kann selbst entscheiden, ob man einen öffentlichen Account haben möchte und sich eine Reichweite auf gesunde Art und Weise aufbauen möchte, mit Follower:innen, die sich auch wirklich für das interessieren, das man postet oder ob man doch lieber einen Account haben möchte, mit dem man nur Leuten folgt, die man privat kennt.
## Bonus
Mastodon ist so flexibel, dass man theoretisch sogar eine Instanz erstellen kann, die nicht föderiert. Man sieht dann also nur die Beiträge der Nutzer:innen, die auf der eigenen Instanz registriert sind. Das kann für Unternehmen, Vereine, Schulen oder Ähnliches nützlich sein. Dadurch, dass alles Open Source ist und sich jeder den Programmcode von Mastodon ansehen kann und an seine Bedürfnisse anpassen kann, kann man wirklich alles daran anpassen. Vom Design bis hin zu den Funktionen. Wer das macht, sollte jedoch bitte die entsprechenden Open Source Lizenzen der Projekte wie beispielsweise Mastodon respektieren.

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -0,0 +1,63 @@
---
title: "Obsidian vs Logseq vs Obsidian - Welches Tool für ein Second Brain?"
date: 2023-06-02T12:18:48+02:00
draft: false
toc: true
images:
tags:
- Informatik
- Produktivität
- Obsidian
featured_image: /img/2023/06/obsidian-logo-reductions.png
---
Es gibt verschiedene Softwares, die einem das Anlegen eines "Seoncd Brains" oder einer Knowledgebase um einiges vereinfachen. Man muss nicht wie früher alle seine Notizen auf einen Zettel schreiben sondern man kann heutzutage ohne Probleme alles in eine Software schreiben, kategorisieren, durchsuchen und vieles mehr!
Ich selbst habe lange Zeit nach der perfekten Lösung für eine solche "Note Taking Software" gesucht und endlich das - für mich - perfekte Tool gefunden. In diesem Artikel mächte ich euch näher bringen was das für mich beste Tool ist und warum ich es nicht mehr missen möchte.
## Logseq
Meiner Meinung nach ist Logseq eine extreeeem gute Software! Die offizielle Webseite findet ihr [hier](https://logseq.com/). Es wurde extra dafür entwickelt sein eigenes Wissen zu bewahren und sich selbst eine Knowledgebase aufzubauen. Die Software ist Open Source, was ein riesiger Pluspunkt ist. Sie ist jedoch auch noch sehr neu und dadurch an manchen Stellen noch nicht perfekt ausgereift.
Die Software nutzt im Hintergrund Markdown. Die Dateien können daher mit jeglicher anderen Software ebenfalls geöffnet werden. Darüber hinaus werden die Dateien lokal gespeichert. Man besitzt seine Daten daher auch selbst und hat sie nicht auf irgendeinem Server liegen.
Es gibt aktuell (Stand heute) noch keinen offiziellen Sync der Dateien für Logseq, allerdings kann man das Projekt freiwillig mit 15$ monatlich unterstützen und erhält dadurch auch Zugriff zur Closed-Beta von Logseq-Sync.
### Wie funktioniert Logseq?
Logseq arbeitet mit "Indentations" also Einrückungen und Blöcken. Jeder Block, den man schreibt ist also eingerückt und man kann alles mögliche zusammenklappen, filtern, mit Queries suchen und vieles mehr. Das ganze ist am Anfang etwas ungewohnt aber in sich sehr mächtig. Die iOS-App ist leider noch etwas merkwürdig in der Bedienung beim schreiben.
Wenn man etwas in Logseq schreibt, sieht das so aus:
![Logseq Screenshot](2023-06-02%20Logseq%20Screenshot.png)
Für mich persönlich ist diese Software **eigentlich** mein absoluter Favorit, **aber** leider musste ich mich dagegen entscheiden, da die Performance unter Linux (in meinem Setup) leider etwas schlechter war. Die Software ist jedoch noch sehr neu und ich bin davon überzeugt, dass sie noch deutlich besser wird. Insbesondere, weil sie Open Source ist. Ich würde mir außerdem wünschen, dass Logseq einen Account im Fediverse, beispielsweise auf Mastodon hätte. Aber das hat nicht ganz so direkt etwas mit der Software zu tun und ist vielmehr ein kleiner "Seitenhieb" meinerseits.
Auch wenn ich Logseq aktuell (noch) nicht als meine Knowledgebase nutze, ist sie mein eigener Favorit und ich unterstütze daher das Projekt weiterhin jeden Monat mit 15$.
## Notion
Notion wird von sehr vielen Personen als Second Brain genutzt. Die Software basiert auf Seiten und Datenbanken. Datenbanken können verschiedene Werte haben und Seiten können in Datenbanken liegen, miteinander verlinkt werden uvm.
Vom Funktionsumfang ist die Software wirklich gut und ich persönlich habe noch keine brauchbare Alternative gefunden.
Aussehen tut Notion in etwa so, wenn man kein bestimmtes Layout oder Dashboard anlegt:
![Notion Screenshot](2023-06-02%20Notion%20Screenshot.png)
Notion ist aber auch hier in der Hinsicht sehr anpassungsfähig und bietet einen guten Funktionsumfang, um sich eigene Dashboards zu erstellen.
### Es gibt jedoch einige Punkte, die für mich selbst dagegensprechen Notion zu nutzen
- Notizen sind in der Cloud und dadurch zwar von überall erreichbar aber nur, wenn mfalsean Internet hat
- Dateien sind nicht Ende-zu-Ende-Verschlüsselt in der Cloud. Notion hält die Keys zum entschlüsseln und **kann** diese auch einsetzen, um auf Userdaten zuzugreifen. -> Man kann dem Suppport durch eine Einstellung erlauben auf die Daten temporär zuzugreifen, was bedeutet, dass hier quasi eine "Backdoor" (die nicht wirklich versteckt und transparent ist) eingebaut ist. Ich kann mir jedoch vorstellen, dass diese irgendwann auch missbraucht werden könnte, damit eine Dritte Partei an die Userdaten kommt. Entsprechend sollte man hier **auf keinen Fall** persönliche oder sensible Daten abspeichern. Da ich selbst jedoch teilweise vertrauliche Notizen habe, ist das ein K.O.-Kriterium für mich. Ich möchte nicht erst abwiegen können, ob ich eine Notiz dort speichern kann oder doch wieder auf eine andere Software zurückgreifen muss...
- Notion nutzt kein einheitliches Dateiformat wie es beispielsweise Logseq tut. Man kann zwar jederzeit alle seine Daten sichern, allerdings bekommt man seine Notizen dann beispielsweise als HTML oder als PDF. Das reicht vollkommen aus, um jederzeit seine vergangenen Notizen lesen zu können aber es ist natürlich nicht so flexibel wie Markdown, was einfacher Text ist und von jedem Computer in absehbarer Zukunft ohne jegliche Probleme gelesen werden kann.
## Obsidian
Nun kommen wir zu meinem aktuellen Favoriten: [Obsidian](https://obsidian.md).
![Ein Screenshot von Obsidian](2023-06-02%20Obsidian%20Screenshot.png)
Obsidian ist extrem schnell, extrem flexibel, extrem anpassungsfähig und kann sehr krass mit Community Plugins ausgebaut werden.
- Datenbanken wie in Notion? -> Kein Problem es gibt ein Plugin
- Outlines wie in Logseq -> Kein Problem es gibt ein Plugin
- Templates automatisch anhand des Ordners, in dem man eine Notiz erstellt? -> Kein Problem es gibt ein Plugin
Ich glaube es gibt so ziemlich für alles ein Plugin bei Obsidian aber auch ohne Plugins ist Obsidian extrem flexibel. Für mich persönlich sind Obsidian und Logseq eigentlich gleich auf. Obsidian ist deutlich schneller als Logseq (zumindest bei mir), weshalb ich Obsidian nutze. Allerdings ist Obsidian anders als Logseq nicht Open Source, was ich persönlich schade finde. Obsidian geht jedoch auch hier so langsam in die richtige Richtung. Vor kurzem wurden beispielsweise Canvas eingeführt und das Format hiervon wurde von Obsidian Open Source gemacht, damit andere Apps und Programme das gleiche Format nutzen können. Ansonsten nutzt Obsidian für normale Notizen genauso wie Logseq Markdown und die Dateien sind lokal gespeichert. Obsidian bietet für relativ wenig Geld Obsidian Sync an, was jedoch nur bedingt geeignet ist, wenn man wirklich ein sehr großen "Vault" hat, da man beispielsweise viele Screenshots hat, etc. -> Der Sync von Obsidian funktioniert nur bis 10 GB. Ansonsten kann man jedoch auf seinen eigenen Sync-Dienst zurückgreifen. Beispielsweise Nextcloud, Synology Drive oder ähnliches. Damit geht dann natürlich auch mehr als 10 GB...Außer man hat ein iPhone, da Apple mit iOS hier restriktionen setzt und Obsidian beispielsweise nicht auf den Ordner von Nextcloud zugreifen kann in der Form, wie es es müsste. Das gleiche trifft unter iOS übrigens auch auf Logseq zu. Wer also sehr große Vaults hat sollte diese entweder nicht mit dem Handy syncen oder sich ein Android besorgen.
## Fazit
Notion ist für mich persönlich komplett raus, solange hier keine E2EE umgesetzt wird und ein zukunftsicheres Exportformat angeboten wird.
Logseq und Obsidian sind für mich die klaren Gewinner. Es gibt noch etliche andere Note Taking Softwares, wie beispielsweise Evernote, Standard Notes (E2EE und Open Source) und co, allerdings finde ich nicht, dass diese sich anbieten um eine Knowledgebase aufzubauen, bei der die Notizen übersichtlich miteinander verknüpft sind und man direkt sehen kann, welche "Connections" die verschiedenen Themen miteinander haben ohne, dass es direkt offensichtlich ist.
Aktuell nutze ich Obsidian aufgrund der Performance und unterstütze die Entwicklung von Logseq mit. Sobald Logseq auch hier mit Obsidian mithalten kann, werde ich wahrscheinlich zu Logseq wechseln, da ich es befürworte, dass es Open Source ist.
Falls dir der Artikel gefallen hat, würde ich mich freuen, wenn du ihn mit interessierten teilst oder mich auf [Liberapay](https://liberapay.com/Braydofficial/) unterstützt. Vielen Dank! 🙏

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

View File

@ -0,0 +1,29 @@
---
title: "Wie man sein Tagebuch nicht überkompliziert schreibt"
date: 2023-06-26T12:58:48+02:00
draft: false
images:
tags:
- Produktivität
- Journaling
- Selbstreflektion
---
Jeder kennt sie - Tagebuchapps wie Day One, Journey und andere. Auch Apple hat nun auf der WWDC 2023 eine eigene App fürs Journaling bzw. für das schreiben eines Tagebuchs angekündigt, welche ihren Weg wohl mit iOS 17 auf die Geräte finden wird.
Diese Apps sind auch oft sehr gut und haben definitiv ihren Platz. Ich bin mir sicher, dass sehr viele Leute erst durch solche Apps dazu kommen ein Journal zu führen - mich eingeschlossen. Allerdings haben solche Apps auch einen Nachteil.
## Don't overcomplicate it
Ich hatte bei all den Apps immer das Problem, dass ich nie die perfekte App gefunden habe. Mich hat immer irgendwas gestört. Sei es das Design oder die Art der Menüführung. Der Export oder die monatlichen Kosten. Nie war es perfekt. So habe ich 4 Jahre damit verbracht von der einen App auf die andere zu wechseln, meine ganzen Einräge zu re-importieren, sowie die App auszutesten. Nur um am Ende festzustellen, dass die neue App auch wieder Dinge an sich hat, die ich nicht mag.
## Was ist also mein Weg?
Mein Weg muss natürlich nicht der richtige Weg für andere sein. Allerdings habe ich gemerkt, dass ich so viel Zeit damit verschwendet habe *die perfekte App* zu finden und dadurch oft unmotiviert war und einfach keine Einträge geschrieben habe. Daraufhin kam mir der Gedanke "Was ist, wenn ich das ganze mal altmodisch mache?". Gesagt getan: Ich bestellte mir ein Pocket-Size Notizbuch und ein A5 Notizbuch. Am Anfang hatte ich immer das Pocket-Size Notizbuch dabei (in meiner Tasche) und habe im Alltag alle meine Notizen darauf gemacht. Am Ende des Tages habe ich daraus dann zusammengefasste Einträge in mein eigentliches Journal gemacht, welches das A5 Notizbuch darstellt.
Ich habe direkt gemerkt, dass meine Konzentration beim handschriftlichen schreiben deutlich besser ist. Ich habe viel mehr über die Dinge, die ich schreibe reflektiert und dadurch teilweise Probleme schon nur durch das bloße aufschreiben gelöst. Es war wunderbar und 1000x besser im Vergleich zum digitalen journaling.
Eine Sache war da aber noch, die mich gestört hat: Die abendliche Zusammenfassung. Ich hatte am Ende des Tages einfach nicht die Motivation mich hinzusetzen und Dinge aufzuschreiben, die ich vorher schon aufgeschrieben habe (in Form meiner Notizen).
Aber auch hier fiel mit die Entscheidung leicht. Das kleine Tagebuch über den Haufen geworfen und fertig. Ich nutze nun also mein A5 Notizbuch als Journal, das ich fast immer im Rucksack dabei habe, wenn ich z.B. arbeiten bin oder ähnliches. So kann ich jederzeit meine Gedanken aufschreiben. Manchmal auch nur Abends, manchmal zwischendrin. Es ist sehr flexibel.
Ich ging also von super modernem Journaling über zu altmodischem Jornaling, indem ich von modernen Apps zu einem klassischen Notizbuch gewechselt bin. - Und es war die beste Entscheidung, die ich hätte treffen können.
## Backup? Wie?
Digitale Journale haben natürlich den Vorteil, dass sie nicht so schnell mal in Wasser fallen können, durch ein Feuer kaputt gehen können, von Käfern im Laufe der Jahre angeknabbert werden können usw. Bei einem Journal aus Papier sieht das ganze anders aus. Wir haben jedoch 2023 und können uns daher die Technik zu nutzen machen ohne "zu viel" davon zu nutzen, was mich selbst wiederum ablenken würde. Ich gehe daher nun hin und scanne meine Einträge jeden Monat ein. So habe ich für jeden Monat eine PDF. Verliere ich mein Journal nach einer Weile, bevor ich es beendet habe, habe ich es in Zwischenschritten gesichert und verliere nicht viel.
Die physischen Notizbücher archiviere ich dann in einer Schutzfolie für Bücher und lege sie in eine feuerfeste Kiste. Das sollte hoffentlich dafür sorgen, dass die Originale nicht direkt kaputt gehen durch den Lauf der Zeit. Und trotzdem, würden sie kaputt gehen, hätte ich ein digitales Backup aller Einträge, die ich geschrieben habe und somit eine Sicherung meiner Erinnerungen und Erlebnisse.

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,19 @@
---
title: "Braydmedia hat nun einen eigenen Matrix Space"
date: 2023-07-04T18:05:00+02:00
draft: false
images:
tags:
- Braydmedia
- Matrix
---
Braydmedia hat nun neben dieser Website einen eigenen Matrix Space. Ihr könnt dort beitreten und euch austauschen, bzw. natürlich auch um Hilfe fragen, wenn ihr irgendwas bezüglich der Services nicht verstehehn solltet oder anderweitig Hilfe benötigt.
Der Space besteht aus dem Space an sich und natürlich mehreren öffentlichen Räumen. Ihr könnt dem Space [hier beitreten](https://matrix.to/#/#braydmediaspace:chat.braydmedia.de). Die wichtigsten Räume sind der [Community Chat](https://matrix.to/#/#braydmedia:chat.braydmedia.de), sowie der [Support Chat](https://matrix.to/#/#braydmediasupport:chat.braydmedia.de).
Für den Space und die angebundenen Räume gelten die gleichen Regeln wie für den Matrix-Server an sich. Diese sind unter https://braydmedia.de/matrix-regeln einsehbar.
Ich wünsche euch viel Spaß dort!
---
Titelbild des Posts ist von https://matrix.org

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

View File

@ -0,0 +1,34 @@
---
title: "Instagram's Threads App und das Fediverse"
date: 2023-07-04T20:03:00+02:00
draft: false
images:
tags:
- Fediverse
- Datenschutz
---
Erstmal vorab: Meta = Facebook. Die damalige Namensänderung verschleiert das nicht.
Meta hat mit Instagram vor am Donnerstag den 06. Juli 2023 die App "Threads" zu releasen. Diese App soll quasi als eine Alternative zu Twitter an den Start gehen und genauso wie Mastodon oder Sharkey das Activity Pub Protokoll nutzen, wodurch man hierdurch auch Mastodon-Usern folgen könnte und anders herum.
## Was ist das Problem?
Naja...Meta hat nicht das Interesse, dass ihre eigenen Dienste neben anderen Diensten co-existieren. Immerhin ist Meta ein profitorientiertes Unternehmen. Sobald Meta's Threads-App an den Start geht, wäre es wahrscheinlich eine der größten Instanzen im Fediverse, wenn nicht gar die größte. Nutzer:innen die Instagram haben, werden sehr wahrscheinlich eine Info bekommen (in Instagram), dass man doch die neue, tolle App runterladen kann. Threads wird also von Beginn an mit einer erstaunlichen Anzahl an Nutzer:innen an den Start gehen.
Generell macht es natürlich keinen Unterschied, ob ihr auf beispielsweise Sharkey.braydmedia.de registriert seid und Meta's Threads über das Activity Pub-Protokoll eure Posts erhält, weil euch Nutzer:innen von der neuen Plattform aus folgen, oder ob ihr diese Posts mit dem gleichen Inhalt direkt auf Threads posted.
**ABER...** wer Threads nutzen will, wird deren App nutzen müssen. Und deren App zeigt bereits jetzt, dass dort enorme Mengen an Daten gespeichert werden. Während die offizielle Mastodon-App keinerlei Daten erfasst, erfasst die neue App von Meta unter anderem (aber nicht ausschließlich) Dinge wie Gesundheitsdaten, Zahlungsinfos, Standort und vieles mehr. Warum? Ja weiß ich nicht. Ist halt Meta (Facebook) 🫡
## Also sollte ich lieber Mastodon oder Sharkey nutzen?
Auf jeden Fall! Threads wird ggf. am Donnerstag noch nicht erscheinen können, weil es in Irland und der EU einfach nicht Datenschutzkonform ist. Natürlich wird man im Fediverse wahrscheinlich trotzdem schon was davon sehen, da die Plattform ja so wie es aussieht föderieren wird.
Mehr dazu gibt es in diesem sehr guten Artikel von Independend Ireland (Englisch): https://m.independent.ie/business/technology/no-instagram-threads-app-in-the-eu-irish-dpc-says-metas-new-twitter-rival-wont-be-launched-here/a1927220337.html
## Wird Threads isoliert im Fediverse?
Ich denke viele andere Instanzen werden sich erstmal anschauen, wie das ganze von statten geht. Ich weiß jedoch bereits jetzt von vielen Instanzen, die die neue Plattform deföderieren, bevor sie überhaupt draußen ist. Es ist daher absehbar, dass sehr viele Instanzen sich von Threads abkapseln werden. - Was absolut nachvollziehbar ist.
Wenn ihr auf das Fediverse durch Threads gestoßen seid, macht euch lieber einen Account auf einer Mastodon-Instanz oder einer Sharkey-Instanz. Ihr habt im Kern das selbe (eigentlich sogar etwas besseres und ausgereiftereres) und müsst dabei keine Abstriche machen. Im Gegenteil: Ihr gewinnt sogar etwas, da euch Meta nicht eure Daten noch mehr aussaugt.
## Fazit
Ich sehe "Threads" sehr sehr kritisch gegenüber. Die Tatsache, dass die App so nah mit Instagram verbunden sein wird ist für mich persönlich ein Wettbewerbsvorteil. Darüber hinaus ist sie datenschutzrechtlich anscheinend - wie quasi alles von Meta - eine Katastrophe. Ich selbst werde sie auch nicht nutzen und ich gehe auch davon aus, dass die Mehrheit der Nutzer:innen, die aktuell auf meiner Sharkey-Instanz registriert sind, sich dafür entscheiden werden Threads zu deföderieren.
> Just use Mastodon or Sharkey.
Meine Sharkey-Instanz findet ihr unter https://connect.braydmedia.de

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 KiB

View File

@ -0,0 +1,18 @@
---
title: "Braydmedia Social wird zu Braydmedia Connect"
date: 2023-12-25T17:31:00+01:00
draft: false
images:
tags:
- Fediverse
- Misskey
- Sharkey
- Mastodon
---
Ich wünsche allen von euch, die es feiern besinnliche und fröhliche Weihnachten!
Ich habe die letzte Zeit damit verbracht Braydmedia Social, welches auf [Mastodon](https://joinmastodon.org) basierte anzupassen und zu [Braydmedia Connect](https://connect.braydmedia.de) umzuziehen, welches nun auf [Sharkey](https://joinsharkey.org) einem Fork von [Misskey](https://misskey-hub.net/en/) basiert.
Mir persönlich gefällt Sharkey einfach besser als Mastodon, da es mehr Funktionen bietet. Der einzige "Nachteil" ist, dass es nicht wirklich viele Apps für Sharkey gibt. Die Website funktioniert jedoch sehr gut als Webapp, die man auf seinem Handy installieren kann.
Ihr könnt euch gerne auf Braydmedia Connect unter https://connect.braydmedia.de registrieren! Ich freue mich auf euch. Bitte beachtet die Infos unter der [entsprechenden Seite über Braydmedia Connect](https://braydmedia.de/connect/).

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

View File

@ -0,0 +1,34 @@
---
title: "Forgejo Actions ist was sehr geiles"
date: 2023-12-26T21:36:00+01:00
draft: false
images:
tags:
- Forgejo
- Informatik
- Programmieren
---
[Forgejo](https://forgejo.org/) ist eine Alternative zu GitHub, die man selbst hosten kann, auf seinem eigenen Server.
An sich ist das ganze sehr praktisch. Ich habe ebenfalls eine Instanz laufen unter https://code.braydmedia.de.
Ich liebe die Software aber darum soll es gar nicht generell gehen. Vielmehr möchte ich hier über Forgejo Actions schreiben, welche denen auf GitHub auch sehr ähnlich sind. Basically kann man dadurch den Code, den man ins Repository committed direkt ausführen lassen und so beispielsweise seine Software compilen lassen.
In meinem Fall habe ich das ganze im [Repository für diesen Blog](https://code.braydmedia.de/Braydmedia/braydmedia-web) aufgesetzt. Das ganze erleichtert meine Arbeit, wenn ich hier einen neuen Blogpost schreiben möchte enorm. In der Vergangenheit war das immer sehr viel Aufwand, weshalb ich nicht so oft neue Blogposts geschrieben habe.
Nachfolgend findet ihr mal einen Vergleich zwischen früher vs heute dank Forgejo Actions.
| **Früher** | **Heute** |
|---------------------------------------------------------------|------------------------------|
| Änderungen am Code vornehmen | Änderungen am Code vornehmen |
| Änderungen mit git committen | Änderungen mit git committen |
| Änderungen mit git pushen | Änderungen mit git pushen |
| Mit SFTP auf den Webserver verbinden | |
| Die lokalen Änderungen mit Hugo als statische Website builden | |
| Alte Dateien auf dem Webserver via SFTP löschen | |
| Neue statische Dateien via SFTP auf den Webserver hochladen | |
Das besondere an Forgejo Actions ist, dass ich alle Schritte, die ich früher machen musste, die nach dem pushen ins Repository stattgefunden haben, heute automatisieren kann. Hierzu ist eine Datei im Repository unter `.forgejo/workflows/deploy-to-website.yml` definiert. Diese Datei hat alle Infos darüber, was die Forgejo-Instanz jetzt machen soll, wenn ein neuer Commit zum Repository kommt.
Aussehen tut die Datei vom Inhalt aktuell so (die neuste Version findest du immer [im Repository](https://code.braydmedia.de/Braydmedia/braydmedia-web/src/branch/develop/.forgejo/workflows/deploy-to-website.yml)):
Generell geht die Datei vereinfacht gesagt alle vorher manuellen Schritte jetzt nach und nach automatisch durch, sobald ein neuer Commit zum Repository kommt. Ich liebe das, weil es mir die Arbeit so sehr vereinfacht. Forgejo Actions werden seit v1.19 von Forgejo unterstützt. Wenn ihr selbst eine Instanz habt, würde ich euch definitiv empfehlen das ganze zu nutzen. 👀
Ihr könnt daher jetzt auch damit rechnen, dass ich hier häufiger mal einen Blogpost bringen werde. Es ist nun wie gesagt deutlich einfacher für mich!

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@ -0,0 +1,41 @@
---
title: "Object Storage und CDN bei Sharkey"
date: 2024-01-03T22:09:00+01:00
draft: false
images:
tags:
- Fediverse
- Sharkey
---
Ich habe die letzten Tage damit verbracht rauszufinden, wie ich bei [Sharkey](https://joinsharkey.org) den Object Storage für [meine Instanz](https://connect.braydmedia.de) aktiviere. Das ganze hat mich mehr Zeit und Nerven gedachtet, als ich dachte, einfach weil ich absolut gar keine Ahnung von der Thematik hatte und mich dort etwas reinarbeiten musste. Dank der Hilfe Maintainerinnen des Projekts und einigen Personen aus der Community habe ich es dann schließlich aber doch hinbekommen.
## Der Vorteil
Standardmäßig werden Dateien auf der Festplatte des Servers gespeichert, auf dem die Instanz läuft. Wenn aber die Festplatte irgendwann volläuft, wird es schwierig. Ein Object Storage erlaubt es jedoch den Speicher für die Dateien, die User hochladen auszulagern und den Speicherverbrauch pro GB pro Stunde zu bezahlen. Das ist bei großen Datenmengen oft die bessere Option.
## Meine Erfahrungen
### Hinzufügen des Object Storages in Sharkey
Das hinzufügen des Storages war relativ einfach. Man muss als Administrator einfach bei Sharkey in die Systemeinstellungen gehen, dort zu Object Storage navigieren und die entsprechenden Daten eingeben, die man benötigt, um sich bei seinem Object Storage-Provider zu authentifizieren. In meinem Fall nutze ich [Backblaze](https://backblaze.com).
![Ein Screenshot der Einstellungen zum Hinzufügen des Object Storage innerhalb von Sharkey](object-storage-settings.png)
### Erstellen des Object Storages bei Backblaze
Ich weiß nicht exakt, wie dieser Prozess bei anderen Anbietern abläuft, aber bei Backblaze funktioniert der Prozess wie folgt.
1. Account dort erstellen (duh!)
2. Unter "Buckets" auf "Einen Bucket erstellen" klicken
![Ein Screenshot des erwähnten Buttons](Einen-Bucket-erstellen.png)
3. Dem Bucket einen einzigartigen Namen geben, die Dateien auf Öffentlich stellen **(wichtig!)** und Verschlüsselung, sowie Object Lock deaktivieren.
![Ein Screenshot des Fensters, welches sich öffnet, wenn man sich bei Backblaze einen Bucket anlegt](Bucket-creation.png)
4. Nachdem der Bucket erstellt wurde, auf die Lifecycle-Einstellungen klicken und dort auswählen, dass nur die letzte Version der Dateien aufbewaht werden soll
5. Nun sollte man unter "Application Keys" sich einen neuen Application Key erstellen und sich die **keyID** sowie **applicationKey** notieren. **keyID** ist das, was bei Sharkey in den Einstellungen unter **Access Key** reinkommt und der **applicationKey** ist das, was bei Sharkey unter **Secret Key** reinkommt.
6. Ich habe bei Sharkey als Präfix auch `storage` eingetragen. Somit landen alle Dateien von Sharkey im Bucket im Unterordner "storage".
### Ich sehe nichts :O Was nun?
In meinem Fall habe ich das alles eingestellt, die erste Testdatei hochgeladen und mich dann gewundert, wieso die Datei zwar im Bucket bei [Backblaze](https://backblaze.com) landet, jedoch nicht in Sharkey angezeigt werden kann. Nachdem ich dann eine Ewigkeit rumgesucht habe und irgendwann auch mal so schlau war in die Logs von Sharkey zu schauen...habe ich gesehen, dass Sharkey den Error 401 wirft, wenn es versucht die Dateien aus dem Bucket zu laden. Ich weiß nicht genau warum Sharkey das tut (das scheint aber auch beim Upstream Misskey der Fall zu sein), aber auf jeden Fall bekommt Sharkey es irgendwie nicht hin, sich beim Download zu authentifizieren, obwohl es eigentlich die entsprechenden Keys für den Bucket hätte.
### Die Hilfe in Matrix
Nachdem ich das herausgefunden habe, hat mich ein User im Matrix-Channel von Sharkey darauf aufmerksam gemacht, dass man wohl einen CDN wie beispielsweise Cloudflare nutzen kann, um dieses Problem zu beheben. Cloudflare schreibt die URLs quasi um. Dafür gibt es auch eine sehr sehr gute Anleitung auf Englisch direkt von Backblaze. Man findet das ganze hier: https://www.backblaze.com/blog/free-image-hosting-with-cloudflare-transform-rules-and-backblaze-b2/
**:exclamation: ACHTUNG**
Ihr solltet für diesen Prozess eine extra Domain benutzen und nicht eure Hauptdomain. Denkt insbesondere dran, dass wenn ihr eure Domain zu Cloudflare übertragt, alle eure bereits eingestellten DNS-Records auf dieser Domain zurückgesetzt werden!
## Fazit
Der Prozess war für einen Noob in dem Bereich nicht unbedingt einfach aber mit der tollen Hilfe von anderen Leuten zumindest einfacher. Die Anleitung von Backblaze für die Geschichte mit dem CDN hat das Setup auch nochmal vereinfacht. Ohne die Anleitung hätte ich das wahrscheinlich nicht zum Laufen bekommen. Ich bin aber froh, dass es inzwischen funktioniert und finde es sehr gut, dass [Sharkey](https://joinsharkey.org) die Möglichkeit hat einen Object Storage zu nutzen.

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

6
content/blog/_index.md Normal file
View File

@ -0,0 +1,6 @@
---
title: Brayd's Blog
---
Es freut mich, dass du hierher gefunden hast! Hier findest du Posts, die ich bisher veröffentlicht habe. Falls du mein Hobby unterstützen möchtest, kannst du das tun, indem du meine Posts teilst oder mich auf [Liberapay](https://liberapay.com/Braydofficial/) unterstützt.
Ich wünsche dir viel Spaß beim lesen!

157
content/connect.md Normal file
View File

@ -0,0 +1,157 @@
---
title: "Connect"
date: 2023-06-19T09:47:14+02:00
draft: false
lastmod: 2024-01-03T21:45:00+01:00
---
Dieses Dokument beinhält Regeln, Impressum und Datenschutzerklärung für die Sharkey-Instanz auf https://connect.braydmedia.de
{{< button href="https://connect.braydmedia.de" target="_blank" >}}Zur Instanz{{< /button >}}
Aufgrund technischer Limitationen der Software ist es nicht möglich dedizierte Seiten hierfür zu erstellen. Die deutschen Gesetze erfordern jedoch die Angabe eines Impressums, sowie einer Datenschutzerklärung.
## Impressum / Imprint
**Angaben gemäß §5 TMG**
Byron Fröhlich, Braydmedia
c/o Block Services
Stuttgarter Str. 106
70736 Fellbach
Tel: (+49) 160 94194282
E-Mail: info@braydmedia.de
---
## Regeln auf der Instanz
Bitte beachtet, dass die nachfolgenden Regeln jederzeit geändert werden können, wenn die Community gemeinsam entscheidet, dass bestimmte Änderungen vorgenommen werden sollten. Auf die Art und Weise sind die Regeln demokratisch. Aktueller Stand der Regeln ist der 19. Juni 2023.
1. Keine Diskriminierung in Form von Rassismus, Homophobie, Transphobie, und ähnlichem
2. Keine Inhalte, die in Deutschland illegal sind
3. Die Verbreitung von nachweislichen Verschwörungsideologien ist auf dieser Instanz nicht gestattet
4. Wenn dich ein Post einer anderen Person wütend macht, jedoch nicht gegen die Regeln verstößt, nimm dir ein wenig Zeit um runterzukommen, statt dich auf Endlosdiskussionen einzulassen, die niemandem helfen
5. Wir tolerieren keine Gore-Inhalte
6. Nutzt Inhaltswarnungen -> insbesondere für NSFW-Inhalte oder Themen, die sensibel sind
7. Mindestalter 18+
---
## Rules on this instance
Please note that the rules below can be changed at any time if the community collectively decides that certain changes should be made. In that way, the rules are democratic. Current status of the rules is June 19, 2023.
1. no discrimination in the form of racism, homophobia, transphobia, and the like
2. no content that is illegal in Germany
3. the spreading of demonstrable conspiracy ideologies is not allowed on this instance
4. if a post of another person makes you angry, but not against the rules, take some time to calm down, instead of getting involved in endless discussions that help nobody
5. we do not tolerate gore content
6. use content warnings -> especially for NSFW content or topics that are sensitive
7. This instance is for people older than 18 years
---
## Data protection notice
Last updated: 3rd January 2024
### 1. Who we are
connect.braydmedia.de (hereafter “we”, “us” or “the service”) is a non-profit [donation-based](https://liberapay.com/Braydofficial) service that provides Sharkey social media accounts to the connect.braydmedia.de Community (“you”). For the purpose of connecting and interacting with other Sharkey or Fediverse accounts, connect.braydmedia.de processes personal data from its users and users of other instances with whom they interact. This data protection notice describes what kind of personal data we process and on what legal basis, how long we keep it and why, as well as your rights with respect to your data.
Please do not hesitate to [contact us via email](mailto:info@braydmedia.de) to for any question you might have with regard to this document or the processing of your personal data.
### 2. Data protection summary
We dedicate our Sharkey instance connect.braydmedia.de to the connect.braydmedia.de Community. Our small team in provides the non-profit [donation-based](https://liberapay.com/Braydofficial) service on a voluntary basis to offer privacy-friendly micro-blogging accounts that our users typically employ for networking, socialising and discussing ideas .
For the purpose of ensuring a secure interaction, the website of connect.braydmedia.de stores the cookie 'igi' with an identifier in the browser of registered and unregistered website visitors until they close their browser. For registered website visitors, the cookie token stores their login status until logout. Based on user consent, the website stores as well push notification settings in the browser. For security and debugging purposes, our server logs and stores visitor IP addresses for a maximum of 14 days. After that time, all IP addresses are removed.
connect.braydmedia.de processes profile data in the form of posts (notes, pages, clips), subscriptions (following), subscribers (follower), content appreciations (likes and reactions) and promotions (renotes) for publication in the context of profile and post pages. For registered users we process your profile data to deliver the service. For users of other instances, we store and display public profile data and rely here on our legitimate interest until they object and in any case when they delete their post or other data (unsubscribe, unlike, unboost).
If you contact connect.braydmedia.de via email or a (private) post, we use any personal data that your message may contain (such as your email address or name) only to respond to your message. We archive your message for at most 12 months. You are of course free to use a nickname and a pseudonymous email address. We process messages from our registered users to deliver the service and rely for users of other instances on their consent. We may also process messages to comply with our legal obligations.
The following information is provided according to Articles 12, 13 and 14 of the [GDPR](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=uriserv:OJ.L_.2016.119.01.0001.01.ENG&toc=OJ:L:2016:119:TOC).
### 3. Data protection notice
For the purposes of this notice:
**“User”** means the natural person who interacts with connect.braydmedia.de directly via the website or indirectly via third-party applications compatible with ActivityPub.
**“Registered user”** means the users with a Sharkey/ActivityPub profile.
**“Profile data”** means their posts (notes, pages, clips), subscriptions (following), subscribers (follower) content appreciations (likes and reactions) and promotions (renotes), bookmarks and profile settings.
**“Subscribers”** mean the accounts who follow a registered user.
**“Subscriptions”** mean the accounts followed by a registered user.
**Scope and purpose** of the processing This data protection notice applies to the processing of personal data for the provision of the microblogging service connect.braydmedia.de. It offers information on what personal data is processed and how it is processed, and on your data subject rights.
**Responsible for the processing** The data controller is connect.braydmedia.de in its capacity as the provider of the service.
#### Processing of personal data
Personal data processed by connect.braydmedia.de is accessible to its administration team and, where necessary, to moderators on a need-to-know basis to ensure a secure operation. User content is published or delivered according to the user settings. For the provision of the service, connect.braydmedia.de employs the data processors listed below that process personal data linked to the service solely on the written instruction from connect.braydmedia.de:
- Server hosting from [netcup GmbH, Karlsruhe](https://www.netcup.de/) (Server hosted in Austria)
- Server hosting (Storage) from [Backblaze Inc., San Mateo, USA](https://backblaze.com)
- Content Delivery Network (CDN) from [Cloudflare Germany GmbH, München](https://cloudflare.com)
- Email notifications delivery from [Sendinblue GmbH, Berlin](https://www.brevo.com/de/)
- Email mailbox from [Proton AG, Geneva, Switzerland](https://proton.me)
- Donations processing from [Liberapay](https://liberapay.com)
- Captcha processing from [hCaptcha, a trademark of Intuition Machines, Inc.](https://www.hcaptcha.com/)
**(a) Website Visitors**
The connect.braydmedia.de website and APIs process the IP addresses and other metadata (as specified below) of its visitors. When accessing the service, an encrypted connection to its web server is established. To display the content correctly on the visitors computer or other terminal devices, the following data is processed in accordance with the HTTP and TCP/IP protocol:
- IP address of the visitors internet connection
- Operating system and operating system version of the visitors terminal
- Web browser and browser version
- Date of access to the website
- HTTP cookie igi (for the duration of the website visit)
This is required for the request, processing, and display of profile data and other content on the service. After each page visit, some of the data are stored in the account profile (if logged in) and server logs. These logs serve the purpose of maintenance and security of the server and personal data herein is deleted after 14 days. Furthermore, the website employs the cookie _session_id to store the login status of registered users until logout or until a year after the last website visit. The website also stores the notifications settings in the browser. This processing is based on Article 6 (1) (b) of the GDPR (processing is necessary for the performance of a contract). This includes processing carried out in order to comply with the necessary technical and organisational protection measures.
**(b) Contributors from third-party services**
connect.braydmedia.de processes personal data when users of third-party services with ActivityPub support interact with its accounts. To enrich public profile pages with profile data, the following data is processed in accordance with the requirements of the [ActivityPub protocol](https://www.w3.org/TR/activitypub/):
- IP address of the third-party service
- Name of the users terminal software
- Display name, account name, and profile picture
- Current date and time
- Profile data
Private messages are not end-to-end encrypted and are therefore in principle accessible to the connect.braydmedia.de administrators.
This processing is necessary to provide a federated Sharkey instance and therefore based on Article 6 (1) (f) GDPR (processing is in our legitimate interest) with the exception of personal data that is not required such as the display name and profile picture, the processing of which is based on Article 6 (1) (a) GDPR (consent). connect.braydmedia.de stores profile data from subscriptions from compatible third-party services until it receives via that service or directly from the user a request for deletion or objection (unsubscribe, unlike, unboost).
**(c) Registered users**
connect.braydmedia.de limits registrations to users it assumes to be part of the connect.braydmedia.de Community. connect.braydmedia.de reserves the right to refuse the provision of the service to any given user for any reason. To set up accounts and manage them subsequently, the following data from registered users is processed:
- Display name, account name, profile picture and header image
- Login credentials consisting of an email address
- Account description/biography
- Content (notes, pages, clips), promoted, and appreciated content
- Private messages (sent and received)
- Subscriptions and their recent content
- Logged-in sessions (terminal software, time and date, IP address)
If registered users post profile data, the previous section applies accordingly. Note that updating subscribers and posting profile data (including profile mentions) requires disclosure of personal data to the service of the recipients. Depending on their Sharkey servers geographic location, the disclosure can possibly involve international data transfers that are outside of connect.braydmedia.des control.
The registered users name and display name, profile picture and header, description, subscriptions, the own and promoted content, the content of their subscriptions, as well as their given feedback is published on their profile page.
This processing is based on Article 6 (1) (b) of the GDPR (processing is necessary for the performance of a contract) with the exception of personal data that is not required such as the display name and profile picture, the processing of which is based on Article 6 (1) (a) GDPR (consent). Profile data is retained until the account is deleted.
Registered users are responsible for the use of their accounts and their own compliance with the GDPR as separate controllers when they post personal data of other people.
**(d) Contacting us by email**
If you contact connect.braydmedia.de via email or a Sharkey private message, any personal data that your message may contain (such as your email address or name) will only be used to respond to your message and may be stored as part of an email archive. You are of course free to use a nickname and a pseudonymous email address. Such personal data will be deleted after 12 months.
**(e) Donations via Liberapay**
Users can make donations for the operation of connect.braydmedia.de via Liberapay, which processes personal data according to their own data protection notice.
**(f) Cloudflare**
We use the Content Delivery Network (CDN) of Cloudflare Germany GmbH, Rosental 7, c/o Mindspace, 80331 Munich Germany (Cloudflare) to increase the security and delivery speed of our website. This corresponds to our legitimate interest (Art. 6 para. 1 lit. f GDPR). A CDN is a network of [globally] distributed servers that is able to deliver optimized content to the website user. For this purpose, data may be processed in server log files by Cloudflare.
The functionality of the website is not possible without this processing. Files that are made available in this way are provided by the CDN via the domain braydmedia-cdn.com.
**Exercise your rights**
You have the right to request from us access to and rectification or erasure of your personal data or restriction of processing concerning you or, where applicable, the right to object to processing or the right to data portability. Where applicable, you also have the right to withdraw your consent at any time. Please note that withdrawing your consent does not affect the lawfulness of processing based on consent before its withdrawal.
Please find more information on your rights on the website of the [European Commission](https://commission.europa.eu/law/law-topic/data-protection/reform/rights-citizens/my-rights/what-are-my-rights_en).
You have, in any case, the right to lodge a complaint with the [data protection authority](https://edpb.europa.eu/about-edpb/about-edpb/members_en) as a supervisory authority.
Acknowledgments
These terms are based on the [terms initially published by eupolicy.social](https://eupolicy.social/privacy-policy) and made more accessible by the [Mastodon Privacy Policy Generator](https://blog.riemann.cc/projects/Mastodon-privacy-policy-generator/) in its version v1.1 as of 22/11/2022. This text is free to be adapted and remixed under the terms of the [CC-BY (Attribution 4.0 International) license](https://creativecommons.org/licenses/by/4.0/) and has been remixed by us to be appliable to this service.

19
content/datenschutz.md Normal file
View File

@ -0,0 +1,19 @@
---
title: "Datenschutzerklärung"
date: 2023-03-29T23:13:10+02:00
draft: false
---
Diese Webseite speichert Informationen im Rahmen der Server-Logfiles, die bei jeder Anfrage einer Datei an den Server gespeichert werden. Dies ist technisch bedingt durch HTTPS so.
Die dabei erfassten Informationen sind:
- Browsertyp und Browserversion
- verwendetes Betriebssystem
- Referrer URL
- Hostname des zugreifenden Rechners
- Uhrzeit der Serveranfrage
Da es sich bei dem Hostnamen des zugreifenden Rechners (=IP-Adresse) um eine potenziell personenbezogene Information handelt, wird auch das hier erwähnt. Die Logfiles hierzu werden maximal 90 Tage zu technischen Zwecken vorgehalten.
Damit ein paar Dinge wie z.B. Codeblöcke innerhalb von Blogposts funktionieren wird JavaScript benötigt. Aber auch hier findet keine Datenweitergabe, Speicherung, Tracking oder Ähnliches statt.
Das war es auch schon. Kein Tracking, keine Cookies, kein Bullshit.

36
content/dienste.md Normal file
View File

@ -0,0 +1,36 @@
---
title: "Dienste"
date: 2023-06-26T15:40:35+02:00
draft: false
---
## [Statusseite aller Dienste](https://status.braydmedia.de/status/braydmedia)
## Über Braydmedia
Ich als Privatperson stelle unter dem Namen "Braydmedia" verschiedene Dienste bereit. Das Projekt finanziert sich vollständig durch mich selbst und durch Spenden und wird als Hobby betrieben.
## Die verschiedenen Dienste
### Connect
[Sharkey](https://joinsharkey.org/) ist eine Microblogging-Software im [Fediverse](https://de.wikipedia.org/wiki/Fediverse).
Die Registrierungen sind geöffnet, sodass sich jede:r dort registrieren kann.
{{< button href="https://connect.braydmedia.de" target="_blank" >}}Zu Braydmedia Connect{{< /button >}}
### Matrix
Wir haben auch einen [Matrix](https://matrix.org)-Server, welcher unter https://chat.braydmedia.de läuft. Allerdings ist dieser nicht für Registrierungen geöffnet. Solltest du einen Account benötigen, kannst du mir gerne eine Mail unter [info@braydmedia.de](mailto:info@braydmedia.de) schreiben.
Matrix ist ein dezentrales Messenger-Protokoll, welches Ende-zu-Ende-Verschlüsselung bietet. Anders als beispielsweise [Signal](https://signal.org) ist der Dienst nicht auf eine zentrale Stelle / Organisation angewiesen. Jede:r kann sich einen eigenen Server aufsetzen und mit anderen Leuten von anderen Servern schreiben.
### Braydmedia Code
Braydmedia Code ist von [Forgejo](https://forgejo.org/) angetrieben. Es ist eine Alternative zu GitHub, welche self-hosted durch mich ist. Ich benutze Braydmedia Code primär für eigene Zwecke, um meine eigenen Projekte zu hosten. Die Registrierung ist aktiv, jedoch kann man dort keine eigenen Repositories anlegen. Die Registrierung wird benötigt, um beispielsweise Pull-Requests für meine eigenen Projekte zu erstellen. Sobald Forgejo föderiert wird dies wahrscheinlich nicht mehr notwendig sein. Auch hier gilt jedoch: Falls du einen Forgejo Account möchtest, mit dem du auch eigene Repositories erstellen kannst, kann Braydmedia Code die richtige Anlaufstelle für dich sein. Schreib mir einfach auch hier eine Mail unter [info@braydmedia.de](mailto:info@braydmedia.de)!
{{< button href="https://code.braydmedia.de" target="_blank" >}}Zu Braydmedia Code{{< /button >}}
### Braydmedia Cloud
Braydmedia Cloud läuft unter https://cloud.braydmedia.de und nutzt [Nextcloud](https://nextcloud.com/) als Software. Die Registrierungen sind hier nicht geöffnet und neue Accounts werden aktuell nicht akzeptiert.
{{< button href="https://cloud.braydmedia.de" target="_blank" >}}Zu Braydmedia Cloud{{< /button >}}
### Gameserver
Unter Braydmedia laufen aktuell zwei private Gameserver, die nicht für die Allgemeinheit geöffnet sind:
- Valheim Server
- Minecraft Vanilla SMP

16
content/impressum.md Normal file
View File

@ -0,0 +1,16 @@
---
title: "Impressum"
date: 2023-03-29T23:25:14+02:00
draft: false
---
Angaben gemäß §5 TMG
Byron Fröhlich, Braydmedia
c/o Block Services
Stuttgarter Str. 106
70736 Fellbach
Tel: (+49) 160 94194282
E-Mail: info@braydmedia.de
**Anmerkung**:
Dieser Blog wird vollkommen privat betrieben. Er benutzt keine Cookies, keine Tracker oder Ähnliches und ich bereichere mich nicht finanziell. Wenn ich Marken, Produkte oder Ähnliches erwähne, dann beruht dies auf persönlichen Erfahrungen, Empfehlungen, etc., jedoch nicht auf bezahlten Namensnennungen, Werbungen oder sonstigem. Dieser Blog ist daher auch vollkommen frei von kommerziellen Dingen, wie Werbung und Ähnlichem. Es besteht keine Gewinnerzielungsabsicht. I just wanna share my thoughts with y'all! :)

67
content/matrix-regeln.md Normal file
View File

@ -0,0 +1,67 @@
---
title: "Matrix Regeln"
date: 2023-03-30T00:18:35+02:00
draft: false
lastmod: 2023-07-04T17:55:00+02:00
---
Nachfolgend findet ihr die Regeln des [Matrix](https://matrix.org)-Servers (chat.braydmedia.de), sowie der Räume, die von mir betrieben werden, wie beispielsweise [#braydmediasupport:chat.braydmedia.de](https://matrix.to/#/#braydmediasupport:chat.braydmedia.de).
Solltet ihr User von unserer Matrix-Instanz sehen, die gegen die nachfolgenden Regeln verstoßen, könnt ihr euch gerne [bei mir auf Matrix](https://matrix.to/#/@brayd:chat.braydmedia.de) melden. Ich werde dann entsprechende Maßnahmen ergreifen.
Matrix Space: https://matrix.to/#/#braydmediaspace:chat.braydmedia.de
Community Chat: https://matrix.to/#/#braydmedia:chat.braydmedia.de
## Regeln
### 1 - Keine Diskriminierung
Wir tolerieren keine Diskriminierung jeglicher Art. Dies inkludiert (jedoch nicht ausschließlich) Rassismus, Homophobie, Transphobie, Ableismus und ähnlichem
### 2 - Kein Doxxing
Doxxing ist auf diesem Server absolut nicht gestattet und führt zu einem sofortigen Ban vom Server.
### 3 - Kein Scamming
Versucht nicht andere Leute zu scammen. Jegliche Betrugsversuche, die gegen deutsches Recht verstoßen und von Nutzer:innen unserer Instanz kommen, werden zur Anzeige gebracht.
### 4 - Kein Versand bestimmter Inhalte
Der Versand von bestimmten Inhalten ist auf dieser Instanz nicht gestattet. Dies beinhaltet den Versand kinderpornographischer Inhalte, den Versand von Malware, Viren und ähnlichem, sowie dem Versand von anderen in Deutschland illegalen Inhalten. Sollten wir Beweise dafür haben, dass jemand von euch solche Inhalte über unsere Dienste versendet, werden wir sofort rechtliche Schritte einleiten müssen. Wir bitten euch außerdem vom versenden von Gore-Inhalten abzusehen.
### 5 - Achtet auf Verschlüsselung
Matrix ist Ende-zu-Ende-Verschlüsselt. Dies trifft jedoch nicht auf jeden Raum zu. Öffentliche Räume sind sowieso für jeden einsehbar und daher im Normalfall auf Matrix nicht verschlüsselt. Ihr habt zwar technisch die Möglichkeit öffentliche Räume zu verschlüsseln, die ihr erstellt, es macht jedoch keinen Sinn und führt eher dazu, dass ihr es Leuten schwieriger macht einen Raum zu verfolgen, der von jedem verfolgt werden können soll. Private Chats oder geschlossene, nicht öffentliche Gruppenchats sind **immer** verschlüsselt, wenn sie von einem Nutzer auf chat.braydmedia.de erstellt wurden. Wenn ihr selbst in andere Räume eingeladen werdet achtet darauf, ob diese verschlüsselt sind.
**Versendet keine persönlichen Daten oder kritische Dokumente unverschlüsselt! Versendet keine Inhalte, von denen ihr nicht wollt, dass diese veröffentlicht werden an Personen, denen ihr nicht 100% vertraut! - Auch nicht verschlüsselt!** Die Verschlüsselung verhindert nicht, dass der Empfänger eure Nachrichten veröffentlicht. Sie verhindert, dass Dritte (wie ich als Admin oder fremde Personen) eure Nachrichten lesen können.
### 6 - Keine Cyberangriffe auf Braydmedia-Infrastuktur
Das hier sollte eigentlich selbstverständlich sein, jedoch wird es trotzdem erwähnt. Wir haben kein Bug & Bounty-Programm. Cyberangriffe auf Braydmedia sind nicht gestattet. Findet ihr Sicherheitslücken (unbeabsichtigt) gebt uns unter [info@braydmedia.de](mailto:info@braydmedia.de) bescheid und nutzt die Sicherheitslücke nicht aus.
### 7 - Benutzt Menschenverstand
Wir können hier nicht jedes kleine Detail ausschließen. Benutzt Menschenverstand und nervt andere nicht. Spammt nicht, seid höflich, benehmt euch nicht daneben. Was ihr in euren eigenen Räumen macht, die verschlüsselt sind, mit euren Freunden, ist mir relativ egal aber baut keinen Mist in öffentlichen Räumen.
> Use your brain. Don't do stupid things.
Und achtet auf euren Datenschutz...Klatscht nicht eure Adresse in unverschlüsselte oder gar öffentliche Räume.
## Rules
### 1 - No discrimination
We do not tolerate discrimination of any kind. This includes (but is not limited to) racism, homophobia, transphobia, ableism and the like.
### 2 - No doxxing
Doxxing is absolutely not allowed on this server and will result in an immediate ban from the server.
### 3 - No scamming
Do not try to scam other people. Any scamming attempts that violate German law and come from users of our instance will be reported to the police.
### 4 - No sending of certain content
The sending of certain content is not permitted on this instance. This includes the sending of child pornographic content, the sending of malware, viruses and the like, as well as the sending of other content that is illegal in Germany. If we have evidence that any of you are sending such content through our services, we will have to take legal action immediately. We also ask you to refrain from sending gore content.
### 5 - Pay attention to encryption
Matrix is end-to-end encrypted. However, this does not apply to every room. Public rooms are visible to everyone anyway and are therefore not normally encrypted on Matrix. While you technically have the ability to encrypt public rooms that you create, it makes no sense and tends to make it harder for people to track a room that you want everyone to be able to track. Private chats or closed, non-public group chats are **always** encrypted if they are created by a user on chat.braydmedia.de. If you are invited to other rooms yourself, make sure they are encrypted.
**Do not send personal data or critical documents unencrypted! Don't send content you don't want to be published to people you don't trust 100%! - Also not encrypted!** Encryption does not prevent the recipient from publishing your messages. It prevents third parties (like me as admin or strangers) from reading your messages.
### 6 - No cyber attacks on Braydmedia infrastructure
This one should go without saying, but it's mentioned anyway. We do not have a bug & bounty program. Cyberattacks on Braydmedia are not allowed. If you find security holes (unintentionally) let us know at [info@braydmedia.de](mailto:info@braydmedia.de) and do not exploit the security hole.
### 7 - Use common sense
We can't eliminate every little detail here. Use common sense and don't annoy others. Don't spam, be polite, don't misbehave. What you do in your own rooms, which are encrypted, with your friends, I don't care relatively but don't mess up in public rooms.
> Use your brain. Don't do stupid things.
And watch your privacy...don't slap your address in unencrypted or even public rooms.

4
content/tags/_index.md Normal file
View File

@ -0,0 +1,4 @@
---
title: Tags
---
Nachfolgend findest du eine Auflistung aller im Blog verwendeten Tags. Die Seite aktualisiert sich regelmäßig.

View File

@ -1,2 +0,0 @@
[build]
command = 'pnpm build'

12323
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +0,0 @@
{
"name": "",
"version": "3.6.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build",
"postbuild": "pagefind --site dist",
"preview": "astro preview",
"format": "prettier -w --cache . && prettier -w --cache **/*.astro",
"check": "astro check"
},
"dependencies": {
"@astrojs/mdx": "2.0.0",
"@astrojs/rss": "4.0.1",
"@astrojs/sitemap": "3.0.3",
"@astrojs/tailwind": "5.0.3",
"astro": "4.0.3",
"astro-icon": "^0.8.2",
"rehype-external-links": "^3.0.0",
"satori": "0.10.11",
"satori-html": "^0.3.2",
"sharp": "^0.33.0"
},
"devDependencies": {
"@astrojs/check": "^0.3.4",
"@pagefind/default-ui": "^1.0.4",
"@resvg/resvg-js": "^2.6.0",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/typography": "^0.5.10",
"@types/eslint": "^8.44.8",
"@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.2",
"astro-eslint-parser": "^0.16.0",
"autoprefixer": "^10.4.16",
"eslint": "^8.55.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-astro": "^0.30.0",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-prettier": "^5.0.1",
"mdast-util-to-string": "^4.0.0",
"pagefind": "^1.0.4",
"postcss": "^8.4.32",
"postcss-html": "^1.5.0",
"postcss-import": "^15.1.0",
"prettier": "^3.1.0",
"prettier-plugin-astro": "0.12.2",
"prettier-plugin-tailwindcss": "^0.5.9",
"reading-time": "^1.5.0",
"remark-unwrap-images": "^4.0.0",
"tailwindcss": "^3.3.6",
"typescript": "^5.3.3"
},
"packageManager": "pnpm@8.6.1"
}

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
module.exports = {
plugins: {
"postcss-import": {},
"tailwindcss/nesting": {},
tailwindcss: {},
autoprefixer: {},
},
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 500 500"><path fill="#B04304" d="M295.334 103.333v-40L340.667 90v40l-45.333-26.667ZM250.001 63.333l-45.334-26.666v426.666L250.001 490V63.333Z"/><path fill="#FF5D01" d="m250.001 129.944 45.333-26.667 45.333 26.667-45.333 26.667-45.333-26.667ZM204.667 36.667 250.001 10l45.333 26.667-45.333 26.666-45.334-26.666ZM295.334 63.277l45.333-26.666L386 63.277l-45.333 26.667-45.333-26.667ZM114 223.277l45.333-26.667 45.334 26.667-45.334 26.667L114 223.277ZM250 249.944l-45.333-26.667v53.333L250 249.944Z"/><path fill="#53C68C" d="m250 63.333 45.333-26.666v120L340.667 130V90L386 63.333V170l-90.667 53.333v240L250 490V316.667L159.333 370V250l45.334-26.667v53.334L250 250V63.333Z"/><path fill="#B04304" d="M159.333 250 114 223.334v120L159.333 370V250Z"/></svg>

Before

Width:  |  Height:  |  Size: 814 B

View File

@ -1,21 +0,0 @@
{
"name": "Astro Theme Cactus Demo",
"short_name": "Astro_Cactus",
"description": "Starter blog site for Astro",
"icons": [
{
"src": "/192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "/",
"background_color": "#1d1f21",
"theme_color": "#2bbc8a",
"display": "standalone"
}

View File

@ -1,4 +0,0 @@
User-agent: *
Allow: /
Sitemap: https://astro-cactus.chriswilliams.dev/sitemap-index.xml

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Binary file not shown.

View File

@ -1,80 +0,0 @@
---
import type { SiteMeta } from "@/types";
import { siteConfig } from "@/site-config";
import "../styles/global.css";
type Props = SiteMeta;
const { title, description, ogImage, articleDate } = Astro.props;
const titleSeparator = "•";
const siteTitle = `${title} ${titleSeparator} ${siteConfig.title}`;
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const socialImageURL = new URL(ogImage ? ogImage : "/social-card.png", Astro.url).href;
---
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>{siteTitle}</title>
{/* Icons / Favicon */}
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="canonical" href={canonicalURL} />
{/* Primary Meta Tags */}
<meta name="title" content={siteTitle} />
<meta name="description" content={description} />
<meta name="author" content={siteConfig.author} />
{/* Theme Colour */}
<meta name="theme-color" content="" />
{/* Open Graph / Facebook */}
<meta property="og:type" content={articleDate ? "article" : "website"} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonicalURL} />
<meta property="og:site_name" content={siteConfig.title} />
<meta property="og:locale" content={siteConfig.ogLocale} />
<meta property="og:image" content={socialImageURL} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
{
articleDate && (
<>
<meta property="article:author" content={siteConfig.author} />
<meta property="article:published_time" content={articleDate} />
</>
)
}
{/* Twitter */}
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={canonicalURL} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={socialImageURL} />
{/* Sitemap */}
<link rel="sitemap" href="/sitemap-index.xml" />
{/* RSS auto-discovery */}
<link rel="alternate" type="application/rss+xml" title={siteConfig.title} href="/rss.xml" />
{/* Webmentions */}
{
siteConfig.webmentions && (
<>
<link rel="webmention" href={siteConfig.webmentions.link} />
{siteConfig.webmentions.pingback && (
<link rel="pingback" href={siteConfig.webmentions.pingback} />
)}
</>
)
}
<meta name="generator" content={Astro.generator} />

View File

@ -1,17 +0,0 @@
---
import type { HTMLAttributes } from "astro/types";
import { getFormattedDate } from "@/utils";
type Props = HTMLAttributes<"time"> & {
date: Date;
dateTimeOptions?: Intl.DateTimeFormatOptions;
};
const { date, dateTimeOptions, ...attrs } = Astro.props;
const postDate = getFormattedDate(date, dateTimeOptions);
---
<time datetime={date.toISOString()} {...attrs}>
{postDate}
</time>

View File

@ -1,29 +0,0 @@
---
import type { PaginationLink } from "@/types";
interface Props {
prevUrl?: PaginationLink;
nextUrl?: PaginationLink;
}
const { prevUrl, nextUrl } = Astro.props;
---
{
(prevUrl || nextUrl) && (
<nav class="mt-8 flex items-center gap-x-4">
{prevUrl && (
<a class="me-auto py-2 sm:hover:text-accent" href={prevUrl.url} data-astro-prefetch>
{prevUrl.srLabel && <span class="sr-only">{prevUrl.srLabel}</span>}
{prevUrl.text ? prevUrl.text : "Previous"}
</a>
)}
{nextUrl && (
<a class="ms-auto py-2 sm:hover:text-accent" href={nextUrl.url} data-astro-prefetch>
{nextUrl.srLabel && <span class="sr-only">{nextUrl.srLabel}</span>}
{nextUrl.text ? nextUrl.text : "Next"}
</a>
)}
</nav>
)
}

View File

@ -1,205 +0,0 @@
---
// Heavy inspiration taken from Astro Starlight -> https://github.com/withastro/starlight/blob/main/packages/starlight/components/Search.astro
import "@pagefind/default-ui/css/ui.css";
---
<site-search id="search" class="ms-auto">
<button
data-open-modal
disabled
class="flex h-9 w-9 items-center justify-center rounded-md ring-zinc-400 transition-all hover:ring-2"
>
<svg
aria-label="search"
class="h-7 w-7"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
>
<path stroke="none" d="M0 0h24v24H0z"></path>
<path d="M3 10a7 7 0 1 0 14 0 7 7 0 1 0-14 0M21 21l-6-6"></path>
</svg>
</button>
<dialog
aria-label="search"
class="h-full max-h-full w-full max-w-full border border-zinc-400 bg-bgColor shadow backdrop:backdrop-blur sm:mx-auto sm:mb-auto sm:mt-16 sm:h-max sm:max-h-[calc(100%-8rem)] sm:min-h-[15rem] sm:w-5/6 sm:max-w-[48rem] sm:rounded-md"
>
<div class="dialog-frame flex flex-col gap-4 p-6 pt-12 sm:pt-6">
<button
data-close-modal
class="ms-auto cursor-pointer rounded-md bg-zinc-200 p-2 font-semibold dark:bg-zinc-700"
>Close</button
>
{
import.meta.env.DEV ? (
<div class="mx-auto text-center">
<p>
Search is only available in production builds. <br />
Try building and previewing the site to test it out locally.
</p>
</div>
) : (
<div class="search-container">
<div id="cactus__search" />
</div>
)
}
</div>
</dialog>
</site-search>
<script>
class SiteSearch extends HTMLElement {
private openBtn: HTMLButtonElement;
private closeBtn: HTMLButtonElement;
private dialog: HTMLDialogElement;
private dialogFrame: HTMLDivElement;
constructor() {
super();
this.openBtn = this.querySelector<HTMLButtonElement>("button[data-open-modal]")!;
this.closeBtn = this.querySelector<HTMLButtonElement>("button[data-close-modal]")!;
this.dialog = this.querySelector("dialog")!;
this.dialogFrame = this.querySelector(".dialog-frame")!;
this.openBtn.addEventListener("click", this.openModal);
this.openBtn.disabled = false;
this.closeBtn.addEventListener("click", this.closeModal);
}
connectedCallback() {
// Listen for keyboard shortcut
window.addEventListener("keydown", this.onWindowKeydown);
// only add pagefind in production
if (import.meta.env.DEV) return;
const onIdle = window.requestIdleCallback || ((cb) => setTimeout(cb, 1));
onIdle(async () => {
const { PagefindUI } = await import("@pagefind/default-ui");
new PagefindUI({
element: "#cactus__search",
baseUrl: import.meta.env.BASE_URL,
bundlePath: import.meta.env.BASE_URL.replace(/\/$/, "") + "/pagefind/",
showImages: false,
showSubResults: true,
});
});
}
disconnectedCallback() {
window.removeEventListener("keydown", this.onWindowKeydown);
}
onWindowClick = (event: MouseEvent) => {
// check if it's a link
const isLink = "href" in (event.target || {});
// make sure the click is either a link or outside of the dialog
if (
isLink ||
(document.body.contains(event.target as Node) &&
!this.dialogFrame.contains(event.target as Node))
)
this.closeModal();
};
onWindowKeydown = (e: KeyboardEvent) => {
// check if it's the / key
if (e.key === "/" && !this.dialog.open) {
this.openModal();
e.preventDefault();
}
};
openModal = (event?: MouseEvent) => {
this.dialog.showModal();
this.querySelector("input")?.focus();
event?.stopPropagation();
window.addEventListener("click", this.onWindowClick);
};
closeModal = () => {
if (this.dialog.open) {
this.dialog.close();
window.removeEventListener("click", this.onWindowClick);
}
};
}
customElements.define("site-search", SiteSearch);
</script>
<style is:global>
:root {
--pagefind-ui-font: inherit;
}
#cactus__search .pagefind-ui__search-clear {
width: calc(60px * var(--pagefind-ui-scale));
padding: 0;
background-color: transparent;
overflow: hidden;
}
#cactus__search .pagefind-ui__search-clear:focus {
outline: 1px solid theme("colors.accent-2");
}
#cactus__search .pagefind-ui__search-clear::before {
content: "";
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' %3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M6 18L18 6M6 6l12 12'%3E%3C/path%3E%3C/svg%3E")
center / 60% no-repeat;
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='currentColor' %3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M6 18L18 6M6 6l12 12'%3E%3C/path%3E%3C/svg%3E")
center / 60% no-repeat;
background-color: theme("colors.accent");
display: block;
width: 100%;
height: 100%;
}
#cactus__search .pagefind-ui__result {
border: 0;
}
#cactus__search .pagefind-ui__result-link {
background-size: 100% 6px;
background-position: bottom;
background-repeat: repeat-x;
background-image: linear-gradient(
transparent,
transparent 5px,
theme("colors.textColor") 5px,
theme("colors.textColor")
);
}
#cactus__search .pagefind-ui__result-link:hover {
text-decoration: none;
background-image: linear-gradient(
transparent,
transparent 4px,
theme("colors.link") 4px,
theme("colors.link")
);
}
#cactus__search mark {
color: theme("colors.quote");
background-color: transparent;
font-weight: 600;
}
</style>
<style>
#cactus__search {
--pagefind-ui-primary: theme("colors.accent");
--pagefind-ui-text: theme("colors.textColor");
--pagefind-ui-background: theme("colors.bgColor");
--pagefind-ui-border: theme("colors.zinc.400");
--pagefind-ui-border-width: 1px;
}
</style>

View File

@ -1,3 +0,0 @@
<a href="#main" class="sr-only focus:not-sr-only focus:fixed focus:start-1 focus:top-1.5"
>skip to content
</a>

View File

@ -1,57 +0,0 @@
---
import { Icon } from "astro-icon";
/**
Uses https://github.com/natemoo-re/astro-icon#readme
Find icons @ https://icones.js.org/
*/
const socialLinks: Array<{
name: string;
friendlyName: string;
link: string;
isWebmention?: boolean;
}> = [
{
name: "mdi:github",
friendlyName: "Github",
link: "https://github.com/chrismwilliams/astro-cactus",
},
{
name: "mdi:mastodon",
friendlyName: "Mastodon",
link: "#",
},
{
name: "mdi:linkedin",
friendlyName: "LinkedIn",
link: "#",
},
{
name: "mdi:email",
friendlyName: "email",
link: "mailto:astro-cactus@chriswilliams.dev",
isWebmention: true,
},
];
---
<div class="flex flex-wrap items-end gap-x-4">
<p>Find me on</p>
<ul class="flex flex-1 items-center gap-x-2 sm:flex-initial">
{
socialLinks.map(({ link, name, friendlyName, isWebmention }) => (
<li class="flex">
<a
class="inline-block p-1 sm:hover:text-link"
href={link}
target="_blank"
rel={`noopener noreferrer ${isWebmention ? "me authn" : ""}`}
>
<Icon class="h-6 w-6" name={name} aria-hidden="true" focusable="false" />
<span class="sr-only">{friendlyName}</span>
</a>
</li>
))
}
</ul>
</div>

View File

@ -1,47 +0,0 @@
{/* Inlined to avoid FOUC. */}
<script is:inline>
function getUserPref() {
const storedTheme = typeof localStorage !== "undefined" && localStorage.getItem("theme");
return (
storedTheme || (window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark")
);
}
function setTheme(newTheme) {
if (!newTheme || (newTheme !== "light" && newTheme !== "dark")) {
return console.warn(
`Incorrect theme value received. Expected 'light' or 'dark', received ${newTheme}`,
);
}
const root = document.documentElement;
// class already set to newTheme, exit early
if (
(newTheme === "dark" && root.classList.contains("dark")) ||
(newTheme === "light" && !root.classList.contains("dark"))
) {
return;
}
const colorThemeMetaTag = document.querySelector("meta[name='theme-color']");
document.documentElement.classList.toggle("dark", newTheme === "dark");
const bgColour = getComputedStyle(document.body).getPropertyValue("--theme-bg");
colorThemeMetaTag.setAttribute("content", `hsl(${bgColour})`);
if (typeof localStorage !== "undefined") {
localStorage.setItem("theme", newTheme);
}
}
// initial setup
setTheme(getUserPref());
// View Transitions hook to restore theme
document.addEventListener("astro:after-swap", () => setTheme(getUserPref()));
// listen for theme-change custom event, fired in src/components/ThemeToggle.astro
document.addEventListener("theme-change", (e) => {
setTheme(e.detail.theme);
});
</script>

View File

@ -1,112 +0,0 @@
<theme-toggle class="ms-2 sm:ms-4">
<button
type="button"
id="toggle-theme"
class="relative h-9 w-9 rounded-md p-2 ring-zinc-400 transition-all hover:ring-2"
aria-label="Toggle Dark Mode"
>
<svg
id="sun-svg"
class="absolute start-1/2 top-1/2 h-7 w-7 -translate-x-1/2 -translate-y-1/2 scale-100 opacity-100 transition-all dark:scale-0 dark:opacity-0"
aria-hidden="true"
focusable="false"
stroke-width="1.5"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 18C15.3137 18 18 15.3137 18 12C18 8.68629 15.3137 6 12 6C8.68629 6 6 8.68629 6 12C6 15.3137 8.68629 18 12 18Z"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"></path>
<path d="M22 12L23 12" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
></path>
<path d="M12 2V1" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M12 23V22" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
></path>
<path d="M20 20L19 19" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
></path>
<path d="M20 4L19 5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
></path>
<path d="M4 20L5 19" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
></path>
<path d="M4 4L5 5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
></path>
<path d="M1 12L2 12" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
></path>
</svg>
<svg
id="moon-svg"
class="absolute start-1/2 top-1/2 h-7 w-7 -translate-x-1/2 -translate-y-1/2 scale-0 opacity-0 transition-all dark:scale-100 dark:opacity-100"
aria-hidden="true"
focusable="false"
stroke="currentColor"
stroke-width="1.5"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"
></path>
<path d="M17 4a2 2 0 0 0 2 2a2 2 0 0 0 -2 2a2 2 0 0 0 -2 -2a2 2 0 0 0 2 -2"></path>
<path d="M19 11h2m-1 -1v2"></path>
</svg>
</button>
</theme-toggle>
<script>
import { rootHasDarkClass } from "@/utils";
class ThemeToggle extends HTMLElement {
private button: HTMLButtonElement;
private observer: MutationObserver | null;
constructor() {
super();
this.button = this.querySelector("button") as HTMLButtonElement;
this.observer = null;
// set inital aria-pressed value
this.button.setAttribute("aria-pressed", String(rootHasDarkClass()));
// button clicked, fire event
this.button.addEventListener("click", this.handleThemeBtnClick);
}
connectedCallback() {
const root = document.documentElement;
// MutationObserver for html class changes, as the theme could be changed elsewhere
this.observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === "attributes" && mutation.attributeName === "class") {
const rootIsDark = (mutation.target as Element).classList.contains("dark");
this.button.setAttribute("aria-pressed", String(rootIsDark));
}
}
});
this.observer.observe(root, { attributeFilter: ["class"] });
}
disconnectedCallback() {
this.observer?.disconnect();
}
handleThemeBtnClick = () => {
let isDark = rootHasDarkClass();
// invert theme
let themeChangeEvent = new CustomEvent("theme-change", {
detail: {
theme: isDark ? "light" : "dark",
},
});
// dispatch event -> ThemeProvider.astro
document.dispatchEvent(themeChangeEvent);
};
}
customElements.define("theme-toggle", ThemeToggle);
</script>

View File

@ -1,87 +0,0 @@
---
import { Image } from "astro:assets";
import type { CollectionEntry } from "astro:content";
import FormattedDate from "../FormattedDate.astro";
interface Props {
content: CollectionEntry<"post">;
}
const {
content: { data, render },
} = Astro.props;
const { remarkPluginFrontmatter } = await render();
const dateTimeOptions: Intl.DateTimeFormatOptions = {
month: "long",
};
---
{
data.coverImage && (
<div class="aspect-h-9 aspect-w-16 mb-6">
<Image
src={data.coverImage.src}
alt={data.coverImage.alt}
class="object-cover"
loading="eager"
fetchpriority="high"
/>
</div>
)
}
{data.draft ? <span class="text-base text-red-500">(Draft)</span> : null}
<h1 class="title mb-3 sm:mb-1">
{data.title}
</h1>
<div class="flex flex-wrap items-center gap-x-3 gap-y-2">
<p class="font-semibold">
<FormattedDate date={data.publishDate} dateTimeOptions={dateTimeOptions} /> /{" "}
{remarkPluginFrontmatter.minutesRead}
</p>
{
data.updatedDate && (
<span class="rounded-lg bg-quote/10 p-1 text-quote">
Last Updated:
<FormattedDate class="ms-1" date={data.updatedDate} dateTimeOptions={dateTimeOptions} />
</span>
)
}
</div>
{
!!data.tags?.length && (
<div class="mt-3">
<svg
aria-hidden="true"
focusable="false"
xmlns="http://www.w3.org/2000/svg"
class="me-1 inline-block h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M7.859 6h-2.834a2.025 2.025 0 0 0 -2.025 2.025v2.834c0 .537 .213 1.052 .593 1.432l6.116 6.116a2.025 2.025 0 0 0 2.864 0l2.834 -2.834a2.025 2.025 0 0 0 0 -2.864l-6.117 -6.116a2.025 2.025 0 0 0 -1.431 -.593z" />
<path d="M17.573 18.407l2.834 -2.834a2.025 2.025 0 0 0 0 -2.864l-7.117 -7.116" />
<path d="M6 9h-.01" />
</svg>
{data.tags.map((tag, i) => (
<>
<a
class="cactus-link inline-block before:content-['#']"
aria-label={`View more blogs with the tag ${tag}`}
href={`/tags/${tag}/`}
data-pagefind-filter="tag"
>
{tag}
</a>
{i < data.tags.length - 1 && ", "}
</>
))}
</div>
)
}

View File

@ -1,22 +0,0 @@
---
import type { CollectionEntry } from "astro:content";
import type { HTMLTag, Polymorphic } from "astro/types";
import FormattedDate from "../FormattedDate.astro";
type Props<Tag extends HTMLTag> = Polymorphic<{ as: Tag }> & {
post: CollectionEntry<"post">;
withDesc?: boolean;
};
const { post, as: Tag = "div", withDesc = false } = Astro.props;
const postDate = post.data.updatedDate ?? post.data.publishDate;
---
<FormattedDate date={postDate} class="min-w-[120px] text-gray-600 dark:text-gray-400" />
<Tag>
{post.data.draft && <span class="text-red-500">(Draft) </span>}
<a href={`/posts/${post.slug}/`} class="cactus-link" data-astro-prefetch>
{post.data.title}
</a>
</Tag>
{withDesc && <q class="line-clamp-3 block italic">{post.data.description}</q>}

View File

@ -1,21 +0,0 @@
---
import type { MarkdownHeading } from "astro";
import { generateToc } from "src/utils/generateToc";
import TOCHeading from "./TOCHeading.astro";
interface Props {
headings: Array<MarkdownHeading>;
}
const { headings } = Astro.props;
const toc = generateToc(headings);
---
<aside class="sticky top-20 order-2 -me-32 hidden basis-64 lg:block">
<h2 class="font-semibold">Table of Contents</h2>
<ul class="mt-4 text-xs">
{toc.map((heading) => <TOCHeading heading={heading} />)}
</ul>
</aside>

View File

@ -1,28 +0,0 @@
---
import type { TocItem } from "@/utils";
interface Props {
heading: TocItem;
}
const {
heading: { slug, text, depth, subheadings },
} = Astro.props;
---
<li class={`${depth > 2 ? "ms-2" : ""}`}>
<a
class={`block line-clamp-2 hover:text-accent ${depth <= 2 ? "mt-3" : "mt-2 text-[0.6875rem]"}`}
href={`#${slug}`}
aria-label={`Scroll to section: ${text}`}><span class="me-0.5">#</span>{text}</a
>
{
!!subheadings.length && (
<ul>
{subheadings.map((subheading) => (
<Astro.self heading={subheading} />
))}
</ul>
)
}
</li>

View File

@ -1,85 +0,0 @@
---
import { Image } from "astro:assets";
import { Icon } from "astro-icon";
import type { WebmentionsChildren } from "@/types";
interface Props {
mentions: WebmentionsChildren[];
}
const { mentions } = Astro.props;
const validComments = ["mention-of", "in-reply-to"];
const comments = mentions.filter(
(mention) => validComments.includes(mention["wm-property"]) && mention.content?.text,
);
/**
! show a link to the mention
*/
---
{
!!comments.length && (
<div>
<p class="mb-0 text-accent-2">
<strong>{comments.length}</strong> Mention{comments.length > 1 ? "s" : ""}
</p>
<ul class="mt-0 divide-y divide-textColor/20 ps-0" role="list">
{comments.map((mention) => (
<li class="my-0 flex items-start gap-x-5 py-5">
{mention.author?.photo && mention.author.photo !== "" ? (
mention.author.url && mention.author.url !== "" ? (
<a
href={mention.author.url}
class="not-prose shrink-0 overflow-hidden rounded-full outline-none ring-2 ring-textColor hover:ring-4 hover:ring-link focus-visible:ring-4 focus-visible:ring-link"
target="_blank"
rel="noopener noreferrer"
title={mention.author.name}
>
<Image
class="my-0 h-12 w-12"
src={mention.author?.photo}
alt={mention.author?.name}
width={48}
height={48}
/>
</a>
) : (
<Image
class="my-0 h-12 w-12 rounded-full"
src={mention.author?.photo}
alt={mention.author?.name}
width={48}
height={48}
/>
)
) : null}
<div class="flex-auto">
<div class="flex items-center justify-between gap-x-2">
<p class="my-0 line-clamp-1 font-semibold text-accent-2">{mention.author?.name}</p>
<a
href={mention.url}
class="not-prose hover:text-link"
target="_blank"
rel="noopener noreferrer"
title="Vist the source of this mention"
>
<Icon
class="h-5 w-5"
name="mdi:open-in-new"
aria-hidden="true"
focusable="false"
/>
</a>
</div>
<p class="mb-0 mt-1 break-words [word-break:break-word]">{mention.content?.text}</p>
</div>
</li>
))}
</ul>
</div>
)
}

View File

@ -1,50 +0,0 @@
---
import { Image } from "astro:assets";
import type { WebmentionsChildren } from "@/types";
interface Props {
mentions: WebmentionsChildren[];
}
const { mentions } = Astro.props;
const MAX_LIKES = 10;
const likes = mentions.filter((mention) => mention["wm-property"] == "like-of");
const likesToShow = likes
.filter((like) => like.author?.photo && like.author.photo !== "")
.slice(0, MAX_LIKES);
---
{
!!likes.length && (
<div>
<p class="mb-0 text-accent-2">
<strong>{likes.length}</strong>
{likes.length > 1 ? " People" : " Person"} liked this
</p>
{!!likesToShow.length && (
<ul class="flex list-none flex-wrap overflow-hidden ps-2" role="list">
{likesToShow.map((like) => (
<li class="-ms-2">
<a
href={like.author?.url}
class="not-prose relative inline-block overflow-hidden rounded-full outline-none ring-2 ring-textColor hover:z-10 hover:ring-4 hover:ring-link focus-visible:z-10 focus-visible:ring-4 focus-visible:ring-link"
target="_blank"
rel="noopener noreferrer"
title={like.author?.name}
>
<Image
class="my-0 inline-block h-12 w-12"
src={like.author!.photo}
alt={like.author!.name}
width={48}
height={48}
/>
</a>
</li>
))}
</ul>
)}
</div>
)
}

View File

@ -1,30 +0,0 @@
---
import { getWebmentionsForUrl } from "@/utils";
import Likes from "./Likes.astro";
import Comments from "./Comments.astro";
const url = new URL(Astro.url.pathname, Astro.site);
const webMentions = await getWebmentionsForUrl(`${url}`);
if (!webMentions.length) return;
---
{
!!webMentions.length && (
<>
<hr class="border-solid" />
<h2 class="mb-8 before:hidden">Webmentions for this post</h2>
<div class="space-y-10">
<Likes mentions={webMentions} />
<Comments mentions={webMentions} />
</div>
<p class="mt-8">
Responses powered by{" "}
<a href="https://webmention.io" target="_blank" rel="noopener noreferrer">
Webmentions
</a>
</p>
</>
)
}

View File

@ -1,29 +0,0 @@
---
import { menuLinks, siteConfig } from "@/site-config";
const year = new Date().getFullYear();
---
<footer
class="mt-auto flex w-full flex-col items-center justify-center gap-y-2 pb-4 pt-20 text-center align-top font-semibold text-gray-600 dark:text-gray-400 sm:flex-row sm:justify-between sm:text-xs"
>
<div class="me-0 sm:me-4">
Copyright &copy; {year}{" "}
{siteConfig.author}
</div>
<nav
aria-label="More on this site"
class="flex gap-x-2 sm:gap-x-0 sm:divide-x sm:divide-gray-500"
>
{
menuLinks.map((link) => (
<a
href={link.path}
class="px-4 py-2 sm:px-2 sm:py-0 sm:hover:text-textColor sm:hover:underline"
>
{link.title}
</a>
))
}
</nav>
</footer>

View File

@ -1,125 +0,0 @@
---
import ThemeToggle from "../ThemeToggle.astro";
import Search from "../Search.astro";
import { menuLinks } from "@/site-config";
const url = new URL(Astro.request.url);
---
<header id="main-header" class="group relative mb-28 flex items-center sm:ps-[4.5rem]">
<div class="flex sm:flex-col">
<a
href="/"
class="inline-flex items-center grayscale hover:filter-none sm:relative sm:inline-block"
aria-current={url.pathname === "/" ? "page" : false}
>
<svg
class="me-3 h-10 w-6 sm:absolute sm:start-[-4.5rem] sm:me-0 sm:h-20 sm:w-12"
aria-hidden="true"
focusable="false"
fill="none"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 272 480"
>
<title>Logo</title>
<path
d="M181.334 93.333v-40L226.667 80v40l-45.333-26.667ZM136.001 53.333 90.667 26.667v426.666L136.001 480V53.333Z"
fill="#B04304"></path>
<path
d="m136.001 119.944 45.333-26.667 45.333 26.667-45.333 26.667-45.333-26.667ZM90.667 26.667 136.001 0l45.333 26.667-45.333 26.666-45.334-26.666ZM181.334 53.277l45.333-26.666L272 53.277l-45.333 26.667-45.333-26.667ZM0 213.277l45.333-26.667 45.334 26.667-45.334 26.667L0 213.277ZM136 239.944l-45.333-26.667v53.333L136 239.944Z"
fill="#FF5D01"></path>
<path
d="m136 53.333 45.333-26.666v120L226.667 120V80L272 53.333V160l-90.667 53.333v240L136 480V306.667L45.334 360V240l45.333-26.667v53.334L136 240V53.333Z"
fill="#53C68C"></path>
<path d="M45.334 240 0 213.334v120L45.334 360V240Z" fill="#B04304"></path>
</svg>
<span class="text-xl font-bold sm:text-2xl">Astro Cactus</span>
</a>
<nav
id="navigation-menu"
class="absolute -inset-x-4 top-14 hidden flex-col items-end gap-y-4 rounded-md bg-bgColor/[.85] py-4 text-accent shadow backdrop-blur group-[.menu-open]:z-50 group-[.menu-open]:flex sm:static sm:z-auto sm:-ms-4 sm:mt-1 sm:flex sm:flex-row sm:items-center sm:divide-x sm:divide-dashed sm:divide-accent sm:rounded-none sm:bg-transparent sm:py-0 sm:shadow-none sm:backdrop-blur-none"
aria-label="Main menu"
>
{
menuLinks.map((link) => (
<a
href={link.path}
class="px-4 py-4 sm:py-0 sm:hover:underline"
aria-current={url.pathname === link.path ? "page" : false}
data-astro-prefetch
>
{link.title}
</a>
))
}
</nav>
</div>
<Search />
<ThemeToggle />
<mobile-button>
<button
id="toggle-navigation-menu"
class="group relative ms-4 h-7 w-7 sm:invisible sm:hidden"
type="button"
aria-label="Open main menu"
aria-expanded="false"
aria-haspopup="menu"
>
<svg
id="line-svg"
class="absolute start-1/2 top-1/2 h-full w-full -translate-x-1/2 -translate-y-1/2 transition-all group-aria-expanded:scale-0 group-aria-expanded:opacity-0"
aria-hidden="true"
focusable="false"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 9h16.5m-16.5 6.75h16.5"
></path>
</svg>
<svg
id="cross-svg"
class="absolute start-1/2 top-1/2 h-full w-full -translate-x-1/2 -translate-y-1/2 scale-0 text-accent opacity-0 transition-all group-aria-expanded:scale-100 group-aria-expanded:opacity-100"
class="text-accent"
aria-hidden="true"
focusable="false"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</mobile-button>
</header>
<script>
import { toggleClass } from "@/utils";
class MobileNavBtn extends HTMLElement {
private headerEl: HTMLElement;
private mobileButtonEl: HTMLButtonElement;
private menuOpen: boolean;
constructor() {
super();
this.headerEl = document.getElementById("main-header")!;
this.mobileButtonEl = this.querySelector("button") as HTMLButtonElement;
this.menuOpen = false;
this.mobileButtonEl.addEventListener("click", this.toggleMobileMenu);
}
toggleMobileMenu = () => {
toggleClass(this.headerEl, "menu-open");
this.menuOpen = !this.menuOpen;
this.mobileButtonEl.setAttribute("aria-expanded", this.menuOpen.toString());
};
}
customElements.define("mobile-button", MobileNavBtn);
</script>

View File

@ -1,36 +0,0 @@
import { z, defineCollection } from "astro:content";
function removeDupsAndLowerCase(array: string[]) {
if (!array.length) return array;
const lowercaseItems = array.map((str) => str.toLowerCase());
const distinctItems = new Set(lowercaseItems);
return Array.from(distinctItems);
}
const post = defineCollection({
type: "content",
schema: ({ image }) =>
z.object({
title: z.string().max(60),
description: z.string().min(50).max(160),
publishDate: z
.string()
.or(z.date())
.transform((val) => new Date(val)),
updatedDate: z
.string()
.optional()
.transform((str) => (str ? new Date(str) : undefined)),
coverImage: z
.object({
src: image(),
alt: z.string(),
})
.optional(),
draft: z.boolean().default(false),
tags: z.array(z.string()).default([]).transform(removeDupsAndLowerCase),
ogImage: z.string().optional(),
}),
});
export const collections = { post };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 KiB

View File

@ -1,10 +0,0 @@
---
title: "Example Cover Image"
description: "This post is an example of how to add a cover image"
publishDate: "04 July 2023"
updatedDate: "14 August 2023"
coverImage:
src: "./cover.png"
alt: "Astro build wallpaper"
tags: ["test", "image"]
---

View File

@ -1,9 +0,0 @@
---
title: "A working draft title"
description: "This post is for testing the draft post functionality"
publishDate: "10 Sept 2023"
tags: ["test"]
draft: true
---
If this is working correctly, this post should only be accessible in a dev environment, as well as any tags that are unique to this post.

Some files were not shown because too many files have changed in this diff Show More