Initial commit

This commit is contained in:
KirbyCMS Deploy
2026-04-15 11:50:31 +00:00
commit 0a8c107828
1114 changed files with 203788 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 2
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_style = space

View File

@@ -0,0 +1,18 @@
KIRBY_DEBUG=false
REQUIRES_LOGIN=false
KIRBY_CACHE=false
APP_URL=http://localhost:3000
DREAMFORM_SECRET=changeme
PLAUSIBLE_SHARED_LINK=
# MailerSend
KIRBY_MAIL_FROM=baukasten@moeritz.io
KIRBY_MAIL_HOST=smtp.mailersend.net
KIRBY_MAIL_PORT=587
KIRBY_MAIL_USER=
KIRBY_MAIL_PASS=
# Only neccessary for dev environments
VITE_DEV_PORT=3000
KIRBY_DEV_HOSTNAME=127.0.0.1
KIRBY_DEV_PORT=3001

View File

@@ -0,0 +1 @@
github: tobimori

21
kirby-baukasten-main/.github/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021-2023 Tobias Möritz
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.

168
kirby-baukasten-main/.github/README.md vendored Normal file
View File

@@ -0,0 +1,168 @@
![Kirby 4 Baukasten Banner by Johann Schopplich](/.github/banner.png)
# Kirby Baukasten
An opinionated, supercharged version of the Kirby Plainkit used internally at Love & Kindness for our clients' sites, with preconfigured tooling and plugins.
> [!NOTE]
> While Kirby Baukasten is open source & used in production as base for my own projects, it's not properly versioned, and I'm not offering support for it. Instead, it should serve as a reference or guide for implementing certain best practices in your own starterkit.
## Requirements
- PHP 8.2+ with composer
- Node.js 20+ with pnpm
## Usage
##### Install Composer & Node dependencies with `composer install` and `pnpm install`.
```
composer install && pnpm install
```
##### Running the scaffold command with Kirby CLI
```
kirby baukasten:scaffold
```
> NOTE: If you don't have the Kirby CLI installed, you will need to run `composer global require getkirby/cli` first.
##### Start the dev server.
```
pnpm run dev
```
## Best Practices/Features
### Styling ([Tailwind CSS](https://tailwindcss.com/))
Styling is done with [Tailwind CSS](https://tailwindcss.com/) directly in Kirby templates or snippets.
The only pre-made change to the default theme involves switching from the Mobile-first approach to a Desktop-first approach. I do think that this is still the go-to approach for most projects.
#### What does this mean?
- Dont use `sm:` to target all non-mobile devices
- Use unprefixed utilities to target desktop, and override them at smaller breakpoints
### TypeScript / interactivity ([Stimulus](https://stimulus.hotwired.dev/))
I try to avoid using TypeScript as much as possible, but some things are impossible to solve with just HTML + CSS, which is why I'm following a strict Progressive Enhancement policy when needed.
Since Kirby v4, I switched to [Stimulus](https://stimulus.hotwired.dev/) as framework supporting this approach. It's integrated with server-side templates exactly how I need it to be and very similiar to my own, previously used, "micro framework".
More information can be found in [src ▸ controllers ▸ README.md](src/controllers/README.md).
### Block system & the `$block->attr()` helper
Most of our pages are build in a page-builder-like fashion utilizing the Kirby Blocks field. To make it easier to work with, I've implemented a helper that allows you to deploy a block with a set of base attributes.
```php
<section <?= $block->attr(['class' => 'container']) ?>>
// <section
// class="container"
// id="id-from-fields"
// data-next-block="footer"
// data-prev-block="navigation"
// >
```
This function is part of the [`tobimori/kirby-spielzeug`](https://github.com/tobimori/kirby-spielzeug) plugin, which contains a encapsulated set of helpers & tools I use for my projects and serves as the independent foundation for Baukasten.
### View Transitions ([Taxi.js](https://taxi.js.org/))
When working on fancy sites that use a lot of animations, I use [Taxi.js](https://taxi.js.org/) to go the extra-mile & handle view transitions. It's a very lightweight library that has a nice API and is easy to use.
If you don't want to use Taxi:
- remove the `@unseenco/taxi` JS dependency
- delete the `src/transitions` & `src/renderers` folder
- remove the `data-taxi` & `data-taxi-view` attributes from `layout.php`
- remove the related code from `src/index.ts`
### Code Typing
This template tries to make Kirby development more accessible by adding PHP code
typing and auto completion. Sadly, this doesn't work straight out of the box.
#### Controllers
For controllers & other PHP files, we can add type declarations by importing the classes using
PHPs `use`:
```php
<?php // site/controllers/article.php
use Kirby\Cms\App;
use Kirby\Cms\Page;
use Kirby\Cms\Site;
return function (Site $site, App $kirby, Page $page) {
[]
}
```
#### Templates
Templates will receive variables defined by Kirby (like the `$page` and `$kirby`
objects), and any other variable you return in a controller. Unfortunately, we can't
declare them in PHP directly, so we need to
[use the PHPDoc @var tag](https://github.com/php-fig/fig-standards/blob/2668020622d9d9eaf11d403bc1d26664dfc3ef8e/proposed/phpdoc-tags.md#517-var).
```php
<?php // site/templates/article.php
/** @var Kirby\Cms\Page $page */
<h1><?= $page->title() ?></h1>
```
As PHPDoc comments aren't a native PHP feature, this won't affect how our code
runs, although all IDEs and most code editors (like VS Code) should support
them.
#### Page Models
If we're using a
[Page Model](https://getkirby.com/docs/guide/templates/page-models) to expand
Kirby's default page object, we can use it in our templates in the same way.
```php
<?php // site/models/article.php
class ArticlePage extends Kirby\Cms\Page {
public function getArticleBody(): string {
if ($this->content()->body()->isNotEmpty()) {
return $this->content()->body()->markdown();
}
return '';
}
}
```
```php
<?php // site/templates/article.php
/** @var ArticlePage $page */
<h1><?= $page->title() ?></h1>
<?= $page->getArticleBody() ?>
```
For classes reference, check out the
[Kirby reference](https://getkirby.com/docs/reference/objects).
#### Auto completion in VS Code
For excellent PHP support in VS Code, we use
[PHP Intelephense](https://marketplace.visualstudio.com/items?itemName=bmewburn.vscode-intelephense-client).
Follow the Quick Start instructions. Other IDEs like PhpStorm may support this
out-of-the-box.
## License
[MIT License](.github/LICENSE) © 2021-2024 [Tobias Möritz](https://github.com/tobimori)
Thanks to [Johann](https://github.com/johannschopplich) for the cute banner gecko!

BIN
kirby-baukasten-main/.github/banner.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

63
kirby-baukasten-main/.gitignore vendored Normal file
View File

@@ -0,0 +1,63 @@
# System files
# ------------
Icon
.DS_Store
# Temporary files
# ---------------
/media/*
!/media/.gitkeep
/public/backups
# Lock files
# ---------------
hot
# Editors
# ---------------
*.sublime-project
*.sublime-workspace
/.idea
/.vscode/*
!/.vscode/extensions.json
!/.vscode/settings.json
# Vendor files
# ---------------
/vendor
/kirby
node_modules
# Build assets
# ---------------
/public/media
/public/assets/dev
/public/dist
/assets/sprite.svg
# Kirby
# ---------------
# Runtime data
/data/runtime/sessions/*
!/data/runtime/sessions/.gitkeep
/data/runtime/logs/*
!/data/runtime/logs/.gitkeep
/data/runtime/cache/*
!/data/runtime/cache/.gitkeep
# Storage
/data/storage/accounts/*
!/data/storage/accounts/.gitkeep
/data/storage/content/*
!/data/storage/content/.gitkeep
/data/storage/.license
/data/storage/retour/*
/site/plugins/*
!/site/plugins/extended
site/plugins/extended/index.dev.mjs
# Env
# ---------------
.env

View File

@@ -0,0 +1 @@
22

View File

@@ -0,0 +1 @@
!*.yml

View File

@@ -0,0 +1,6 @@
{
"semi": false,
"trailingComma": "none",
"useTabs": true,
"tabWidth": 2
}

View File

@@ -0,0 +1,14 @@
{
"recommendations": [
"esbenp.prettier-vscode",
"eamodio.gitlens",
"kisstkondoros.vscode-gutter-preview",
"github.vscode-pull-request-github",
"bmewburn.vscode-intelephense-client",
"editorconfig.editorconfig",
"bradlc.vscode-tailwindcss",
"emeraldwalk.runonsave",
"yoavbls.pretty-ts-errors",
"biomejs.biome"
]
}

View File

@@ -0,0 +1,44 @@
{
"editor.formatOnSave": true,
"intelephense.telemetry.enabled": false,
// Default formatters for each language
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[css]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[tailwindcss]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[php]": {
"editor.defaultFormatter": "bmewburn.vscode-intelephense-client"
},
// YAML schema association
"yaml.schemas": {
"https://json.schemastore.org/yamllint.json": "**/*.yml"
},
// Tailwind CSS experimental settings for custom class detection
"tailwindCSS.experimental.classRegex": ["([a-zA-Z0-9\\-:]+)"],
// Run specific command on save for PHP files
"emeraldwalk.runonsave": {
"commands": [
{
"match": "\\.php$",
"cmd": "pnpm env-cmd --use-shell \"rustywind --write '${file}' --vite-css \\$APP_URL/src/styles/index.css \""
}
]
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16">
<path fill="currentcolor"
d="M1.2426 8.2426 8 15l6.7574-6.7574a4.2428 4.2428 0 0 0 1.2426-3v-.1903C16 2.8143 14.1857 1 11.9477 1a4.0523 4.0523 0 0 0-3.1644 1.5209L8 3.5l-.7833-.9791A4.0524 4.0524 0 0 0 4.0523 1C1.8143 1 0 2.8143 0 5.0523v.1903c0 1.1253.447 2.2044 1.2426 3Z" />
</svg>

After

Width:  |  Height:  |  Size: 360 B

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"organizeImports": {
"enabled": true
},
"files": {
"ignore": ["./src/styles/*.css"]
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"formatter": {
"lineWidth": 120
},
"javascript": {
"formatter": {
"semicolons": "asNeeded",
"trailingCommas": "none"
}
}
}

View File

@@ -0,0 +1,42 @@
{
"@schema": "https://getcomposer.org/schema.json",
"name": "tobimori/kirby-baukasten",
"description": "An opinionated, supercharged version of the Kirby CMS Plainkit",
"type": "project",
"keywords": [
"kirby",
"cms",
"starterkit"
],
"homepage": "https://moeritz.io",
"authors": [
{
"name": "Tobias Möritz",
"email": "tobias@moeritz.io",
"homepage": "https://moeritz.io"
}
],
"minimum-stability": "beta",
"require": {
"php": ">=8.2.0",
"getkirby/cms": "^5",
"bnomei/kirby3-dotenv": "^2.0",
"genxbe/kirby3-ray": "^2.0.1",
"tobimori/kirby-seo": "dev-main",
"lukaskleinschmidt/kirby-laravel-vite": "^2.1.1",
"tobimori/kirby-spielzeug": "^2.1.0",
"distantnative/retour-for-kirby": "^5.0",
"getkirby/cli": "^1.2",
"bnomei/kirby3-janitor": "^4.0",
"tobimori/kirby-tailwind-merge": "^3.0",
"tobimori/kirby-thumbhash": "^1.1",
"johannschopplich/kirby-plausible": "^0.5.0"
},
"config": {
"optimize-autoloader": true,
"allow-plugins": {
"getkirby/composer-installer": true,
"php-http/discovery": true
}
}
}

3758
kirby-baukasten-main/composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
# data
This folder contains all data stored or generated by Kirby.
There are categorized in two subfolders: `runtime` and `storage`.

View File

@@ -0,0 +1,3 @@
# data > runtime
This folder contains all data generated automatically by Kirby at runtime that doesn't necessarily need to be persisted.

View File

View File

@@ -0,0 +1,3 @@
# data > storage
This folder contains all data generated through user-input in the Kirby panel, such as the sites' content or accounts.

View File

@@ -0,0 +1,53 @@
{
"private": true,
"type": "module",
"scripts": {
"dev": "pnpm run \"/dev:/\"",
"dev:kirby": "env-cmd --use-shell \"PHP_CLI_SERVER_WORKERS=4 php -S \\$KIRBY_DEV_HOSTNAME:\\$KIRBY_DEV_PORT -t public kirby/router.php\"",
"dev:vite": "vite",
"dev:plugin": "cd site/plugins/extended && kirbyup serve src/index.js",
"build": "pnpm run \"/build:/\"",
"build:site": "vite build",
"build:plugin": "cd site/plugins/extended && kirbyup src/index.js"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@rollup/plugin-inject": "^5.0.5",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/vite": "4.0.3",
"@total-typescript/ts-reset": "^0.6.1",
"@types/node": "^22.13.0",
"autoprefixer": "^10.4.20",
"browserslist": "^4.24.4",
"env-cmd": "^10.1.0",
"kirbyup": "^3.2.0",
"laravel-vite-plugin": "^1.2.0",
"lightningcss": "^1.29.1",
"postcss": "^8.5.1",
"prettier": "^3.4.2",
"tailwindcss": "^4.0.3",
"typescript": "^5.7.3",
"vite": "^6.0.11",
"vite-svg-sprite-wrapper": "^1.4.1",
"vite-tsconfig-paths": "^5.1.4"
},
"dependencies": {
"@hotwired/stimulus": "^3.2.2",
"@unseenco/taxi": "^1.8.0",
"css-clamper": "^0.2.0",
"htmx.org": "^1.9.12",
"idiomorph": "^0.4.0",
"stimulus-typescript": "^0.1.3",
"stimulus-use": "^0.52.3",
"unlazy": "^0.12.1"
},
"browserslist": [
"last 2 versions",
">= 0.4%",
"not dead",
"Firefox ESR",
"not op_mini all",
"not and_uc > 0"
],
"packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0"
}

3815
kirby-baukasten-main/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
<?php
use Kirby\Cms\App;
define("KIRBY_HELPER_ATTR", false); // attr is set by tailwind-merge
define("KIRBY_HELPER_DUMP", false); // dump is set by ray
$base = dirname(__DIR__);
require $base . '/kirby/bootstrap.php';
require $base . '/vendor/autoload.php';
$kirby = new App([
'roots' => [
'index' => __DIR__,
'base' => $base,
'site' => $base . '/site',
'data' => $data = $base . '/data',
'content' => $data . '/storage/content',
'accounts' => $data . '/storage/accounts',
'license' => $data . '/storage/.license',
'cache' => $data . '/runtime/cache',
'logs' => $data . '/runtime/logs',
'sessions' => $data . '/runtime/sessions',
]
]);
echo $kirby->render();

View File

@@ -0,0 +1,23 @@
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
.logo {
fill: #000000;
}
@media (prefers-color-scheme: dark) {
.logo {
fill: #ffffff;
}
}
</style>
<path
d="m20.428 0 15.429 8.998v18.004L20.429 36 5 27.002V8.998L20.428 0ZM6.714 10.023v15.954l13.714 8 13.715-8V10.023l-13.715-8-13.714 8Z"
class="logo" />
<path
d="M27.286 18.002 23 20.221v.351h4.286v3.428H13.572v-3.428h4.286v-.375l-4.286-2.197v-4.285l6.857 3.683 6.857-3.683"
class="logo" />
<rect x="16" y="26" width="24" height="14" rx="3" fill="#FAD6D6" />
<path
d="M24.41 33.45c0 .98-.1 2.04-.83 2.79-.47.49-1.18.76-1.99.76h-2.76v-7.09h2.76c.81 0 1.52.27 1.99.76.73.75.83 1.8.83 2.78Zm-1.31 0c0-.65-.01-1.48-.51-1.98-.24-.24-.59-.4-1-.4h-1.47v4.77h1.47c.41 0 .76-.16 1-.4.5-.5.51-1.34.51-1.99ZM30.716 37h-5.05v-7.09h5.05v1.17h-3.76v1.65h3.31v1.16h-3.31v1.94h3.76V37Zm6.5-7.09L34.885 37h-1.42l-2.35-7.09h1.37l1.72 5.41 1.71-5.41h1.3Z"
fill="#801919" />
</svg>

After

Width:  |  Height:  |  Size: 1023 B

View File

@@ -0,0 +1,23 @@
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
.logo {
fill: #000000;
}
@media (prefers-color-scheme: dark) {
.logo {
fill: #ffffff;
}
}
</style>
<path
d="m20.428 0 15.429 8.998v18.004L20.429 36 5 27.002V8.998L20.428 0ZM6.714 10.023v15.954l13.714 8 13.715-8V10.023l-13.715-8-13.714 8Z"
class="logo" />
<path
d="M27.286 18.002 23 20.221v.351h4.286v3.428H13.572v-3.428h4.286v-.375l-4.286-2.197v-4.285l6.857 3.683 6.857-3.683"
class="logo" />
<rect x="15" y="26" width="25" height="14" rx="3" fill="#CEE7CE" />
<path
d="M22.41 37h-4.58v-7.09h1.29v5.87h3.29V37Zm2.228 0h-1.31v-7.09h1.31V37Zm6.929-7.09L29.237 37h-1.42l-2.35-7.09h1.37l1.72 5.41 1.71-5.41h1.3ZM37.45 37H32.4v-7.09h5.05v1.17h-3.76v1.65H37v1.16h-3.31v1.94h3.76V37Z"
fill="#2E5C2E" />
</svg>

After

Width:  |  Height:  |  Size: 862 B

View File

@@ -0,0 +1,23 @@
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
.logo {
fill: #000000;
}
@media (prefers-color-scheme: dark) {
.logo {
fill: #ffffff;
}
}
</style>
<path
d="m20.428 0 15.429 8.998v18.004L20.429 36 5 27.002V8.998L20.428 0ZM6.714 10.023v15.954l13.714 8 13.715-8V10.023l-13.715-8-13.714 8Z"
class="logo" />
<path
d="M27.286 18.002 23 20.221v.351h4.286v3.428H13.572v-3.428h4.286v-.375l-4.286-2.197v-4.285l6.857 3.683 6.857-3.683"
class="logo" />
<rect x="15" y="26" width="25" height="14" rx="3" fill="#CEE7CE" />
<path
d="M22.41 37h-4.58v-7.09h1.29v5.87h3.29V37Zm2.228 0h-1.31v-7.09h1.31V37Zm6.929-7.09L29.237 37h-1.42l-2.35-7.09h1.37l1.72 5.41 1.71-5.41h1.3ZM37.45 37H32.4v-7.09h5.05v1.17h-3.76v1.65H37v1.16h-3.31v1.94h3.76V37Z"
fill="#2E5C2E" />
</svg>

After

Width:  |  Height:  |  Size: 862 B

View File

@@ -0,0 +1,15 @@
preview: fields
wysiwyg: true
tabs:
content:
label: tab.content.label
fields:
advanced:
label: tab.advanced.label
fields:
blockId:
label: field.blockId.label
type: slug
before: "#"
icon: false

View File

@@ -0,0 +1,11 @@
extends: blocks/_base
title: example
icon: title
label: "{{ text }}"
tabs:
content:
fields:
text:
label: text
type: text

View File

@@ -0,0 +1,4 @@
label: field.blocks.label
type: blocks
fieldsets:
- example

View File

@@ -0,0 +1,2 @@
extends: fields/link
label: field.button.label

View File

@@ -0,0 +1,10 @@
label: field.image.label
type: files
uploads:
parent: site.find('page://images')
template: image
multiple: false
image:
cover: false
info: "{{ file.alt }}"
query: site.find('page://images').images.filterBy('template', 'image')

View File

@@ -0,0 +1,34 @@
label: field.link.label
type: object
empty: field.link.empty
columns:
label:
width: 1/2
link:
width: 1/2
fields:
label:
label: field.link.label.label
type: text
width: 1/3
link:
label: field.link.link.label
type: link
width: 2/3
options:
- url
- page
- file
- email
- tel
newTab:
label: field.link.newTab.label
type: toggle
width: 1/3
default: false
anchor:
label: field.link.anchor.label
type: slug
icon: false
width: 2/3
before: "#"

View File

@@ -0,0 +1,12 @@
extends: fields/writer
inline: false
nodes:
- paragraph
- heading
- bulletList
- orderedList
headings:
- 2
- 3
- 4
- 5

View File

@@ -0,0 +1,7 @@
label: field.text.label
type: writer
marks:
- bold
- link
- email
inline: true

View File

@@ -0,0 +1,4 @@
title: file.file.title
fields:
hidden: true

View File

@@ -0,0 +1,10 @@
title: file.image.title
accept:
extension: jpg, jpeg, png, svg
type: image
fields:
alt:
label: file.image.alt.label
type: text
help: file.image.alt.help

View File

@@ -0,0 +1,4 @@
extends: pages/home
title: page.error.title
image:
icon: cancel

View File

@@ -0,0 +1,16 @@
extends: pages/images
title: page.files.titles
image:
icon: file-word
sections:
files:
label: section.files.label
type: files
template: file
layout: table
limit: 60
uploads:
template: file
search: true
sortBy: filename desc

View File

@@ -0,0 +1,25 @@
extends: pages/layout
title: page.home.title
image:
icon: home
options:
# access
# changeSlug
# changeStatus
# changeTemplate
# changeTitle
# create
delete: false
duplicate: false
# list
move: false
# preview
# sort
# update
tabs:
content:
columns:
sidebar: false

View File

@@ -0,0 +1,40 @@
title: page.images.title
image:
icon: file-image
back: "transparent"
color: "var(--color-gray-600)"
query: false
options:
# access
changeSlug: false
changeStatus: false
# changeTemplate
changeTitle: false
# create
delete: false
duplicate: false
# list
move: false
preview: false
# sort
update: false
status:
draft: false
unlisted: true
listed: false
sections:
files:
label: section.images.label
type: files
layout: cardlets
template: image
uploads:
template: image
search: true
sortBy: filename desc
info: '{{ file.alt.isNotEmpty ? file.filename : "" }}'
text: "{{ file.alt.or(file.filename) }}"
limit: 100

View File

@@ -0,0 +1,39 @@
title: page.layout.title
image:
icon: grid
back: "var(--theme-accent)"
color: "var(--theme-primary)"
options:
# access
# changeSlug
# changeStatus
# changeTemplate
# changeTitle
# create
# delete
# duplicate
# list
# move
# preview
# sort
# update
tabs:
content:
label: tab.content.label
icon: text
columns:
main:
width: 3/4
type: fields
fields:
blocks: fields/blocks
sidebar:
width: 1/4
sections:
children:
label: section.children.label
type: pages
template: layout
seo: seo/page

View File

@@ -0,0 +1,22 @@
title: site.title
tabs:
pages:
label: tab.pages.label
icon: page
columns:
- width: 2/3
sections:
pages:
label: field.pages.label
type: pages
info: "/{{ page.slug }}"
create:
- layout
templates:
- home
- error
- layout
image: icon
navigation: tabs/navigation
seo: seo/site

View File

@@ -0,0 +1,26 @@
icon: bars
label: tab.navigation.label
columns:
- width: 1/4
fields:
_navigation:
label: field.navigation.label
type: headline
- width: 3/4
sections:
placeholder: &placeholder
headline: Placeholder
type: info
text: " "
- width: 1
fields:
_line1: &line
type: line
- width: 1/4
fields:
_footer:
label: field.footer.label
type: headline
- width: 3/4
sections:
placeholder: *placeholder

View File

@@ -0,0 +1,7 @@
title: user.admin.title
description: user.admin.description
fields:
favorites:
label: user.favorites.label
type: pages

View File

@@ -0,0 +1,10 @@
extends: users/admin
title: user.editor.title
description: user.editor.description
permissions:
access:
system: false
languages: false
backups: false
users: false

View File

@@ -0,0 +1,80 @@
<?php
use Kirby\Cms\Pages;
use Kirby\Http\Url;
use tobimori\Spielzeug\Menu;
require_once dirname(__DIR__) . '/plugins/kirby3-dotenv/global.php';
loadenv([
'dir' => realpath(dirname(__DIR__, 2)),
'file' => '.env',
]);
return [
'debug' => json_decode(env('KIRBY_DEBUG')),
'routes' => require __DIR__ . '/routes.php',
'thumbs' => require __DIR__ . '/thumbs.php',
'yaml.handler' => 'symfony',
'date.handler' => 'intl',
'url' => env("APP_URL"),
'tobimori.seo' => require __DIR__ . '/seo.php',
'tobimori.dreamform' => require __DIR__ . '/dreamform.php',
'tobimori.icon-field' => [
// use generated sprite, requires pnpm run build
// to generate (dev sprite doesn't work since it's not in the public folder)
'folder' => '',
'sprite' => fn() => Url::path(vite()->useHotFile('.never')->asset('assets/sprite.svg'))
],
/** Email */
'email' => require __DIR__ . '/email.php',
'auth' => [
'methods' => ['password', 'password-reset'],
'challenge' => [
'email' => [
'from' => env("KIRBY_MAIL_FROM"),
'subject' => 'Login-Code'
]
]
],
/** Caching */
'cache' => [
'pages' => [
'active' => json_decode(env('KIRBY_CACHE')),
]
],
/** Build Env / Vite / etc. */
'bnomei.dotenv.dir' => fn() => realpath(kirby()->roots()->base()),
'lukaskleinschmidt.kirby-laravel-vite.buildDirectory' => 'dist',
/** Panel */
'johannschopplich.plausible.sharedLink' => env('PLAUSIBLE_SHARED_LINK'),
'distantnative' => [
'retour' => [
'config' => __DIR__ . './../../data/storage/retour/config.yml',
'database' => __DIR__ . './../../data/storage/retour/log.sqlite'
]
],
'panel' => [
'menu' => fn() => array_merge(
[
'site' => Menu::site(),
],
Menu::favorites(kirby()->user()?->favorites()->toPages() ?? new Pages([])),
[
'-',
'images' => Menu::page(null, 'file-image', page('page://images')),
'files' => Menu::page(null, 'file-word', page('page://files')),
'-',
'users',
'plausible',
'retour',
]
)
],
'ready' => fn() => [
'panel' => [
'favicon' => option('debug') ? 'static/panel/favicon-dev.svg' : 'static/panel/favicon-live.svg',
'css' => vite('src/styles/panel.css'),
],
]
];

View File

@@ -0,0 +1,12 @@
<?php
return [
'multiStep' => false,
'mode' => 'htmx',
'useDataAttributes' => true,
'precognition' => true,
'secret' => env('DREAMFORM_SECRET'),
'guards' => [
'available' => ['honeypot']
],
];

View File

@@ -0,0 +1,13 @@
<?php
return [
'transport' => [
'type' => 'smtp',
'host' => env("KIRBY_MAIL_HOST"),
'port' => json_decode(env("KIRBY_MAIL_PORT")),
'security' => true,
'auth' => 'tls',
'username' => env("KIRBY_MAIL_USER"),
'password' => env("KIRBY_MAIL_PASS")
]
];

View File

@@ -0,0 +1,3 @@
<?php
return [];

View File

@@ -0,0 +1,16 @@
<?php
return [
'robots' => [
'pageSettings' => false
],
'canonicalBase' => env("APP_URL"),
'files' => [
'parent' => 'site.find("page://images")',
'template' => 'image'
],
'socialMedia' => [
'twitter' => false,
'youtube' => false
],
];

View File

@@ -0,0 +1,9 @@
<?php
return [
'srcsets' => [
'small' => [300, 500, 800],
'default' => [400, 800, 1000, 1200],
'large' => [800, 1400, 2000, 2600],
]
];

View File

@@ -0,0 +1,15 @@
<?php
use Kirby\Data\Yaml;
return [
'code' => 'de',
'default' => true,
'direction' => 'ltr',
'locale' => [
'LC_ALL' => 'de_DE'
],
'name' => 'Deutsch',
'translations' => Yaml::read(__DIR__ . '/../translations/de.yml'),
'url' => ''
];

View File

@@ -0,0 +1,15 @@
<?php
use Kirby\Data\Yaml;
return [
'code' => 'en',
'default' => false,
'direction' => 'ltr',
'locale' => [
'LC_ALL' => 'en_US'
],
'name' => 'English',
'translations' => Yaml::read(__DIR__ . '/../translations/en.yml'),
'url' => '/en'
];

View File

@@ -0,0 +1,23 @@
<?php
use Kirby\Cms\Page;
use Kirby\Content\Field;
class FilesPage extends Page
{
public function metaDefaults()
{
return [
'robotsIndex' => false
];
}
/**
* Override the page title to be static
* to the template name
*/
public function title(): Field
{
return new Field($this, 'title', t("page.{$this->intendedTemplate()->name()}.title"));
}
}

View File

@@ -0,0 +1,5 @@
<?php
class ImagesPage extends FilesPage
{
}

View File

@@ -0,0 +1,5 @@
<?php
class VideosPage extends FilesPage
{
}

View File

@@ -0,0 +1,13 @@
<?php
use Kirby\Cms\App;
App::plugin('project/extended', []);
// helpers
if (!function_exists('icon')) {
function icon(string $type, array|string|null $class = null, array $attr = []): string
{
return snippet('icon', compact('type', 'class', 'attr'), true);
}
}

View File

@@ -0,0 +1 @@
panel.plugin("project/extended", {})

View File

@@ -0,0 +1,5 @@
<?php
/** @var Kirby\Cms\Site $site */ ?>
<footer></footer>

View File

@@ -0,0 +1,5 @@
<?php
/** @var Kirby\Cms\Site $site */ ?>
<header></header>

View File

@@ -0,0 +1,7 @@
<?php
/** @var Kirby\Cms\Site $site */ ?>
<a class="text-white bg-black border-white outline-none sr-only focus-visible:fixed focus-visible:top-4 focus-visible:left-4 focus-visible:py-2.5 focus-visible:px-4 focus-visible:not-sr-only" href="#main">
<?= t('aria.skipNavigation') ?>
</a>

View File

@@ -0,0 +1,12 @@
<?php
use Kirby\Toolkit\A;
if (isset($type)) : ?>
<svg <?= attr(A::merge($attr ?? [], [
'aria-hidden' => true,
'class' => cls($class ?? 'icon')
])) ?>>
<use xlink:href="<?= vite()->asset('assets/sprite.svg') ?>#<?= $type ?>"></use>
</svg>
<?php endif ?>

View File

@@ -0,0 +1,41 @@
<?php
/** @var Kirby\Cms\Page $page
* @var Kirby\Cms\Site $site
* @var Kirby\Cms\App $kirby */
if (json_decode(env('REQUIRES_LOGIN')) && !$kirby->user()) {
go('/panel');
} ?>
<!DOCTYPE html>
<html lang="<?= $kirby->language()?->code() ?>">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<?php snippet('seo/head') ?>
<link rel="icon" href="/favicon.svg" />
<link rel="mask-icon" href="/favicon.svg" color="#000000" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<meta name="theme-color" content="#000000">
<?= vite([
'src/styles/index.css',
'src/index.ts'
]) ?>
</head>
<body class="flex flex-col min-h-screen antialiased overflow-x-clip">
<?php snippet('core/skip-nav') ?>
<?php snippet('core/nav') ?>
<main class="flex-grow">
<div id="main"></div>
<?= $slot ?>
</main>
<?php snippet('core/footer') ?>
<?php snippet('seo/schemas') ?>
</body>
</html>

View File

@@ -0,0 +1,45 @@
<?php
/** @var Kirby\Cms\Page $page
* @var Kirby\Cms\Site $site
* @var Kirby\Cms\App $kirby */
if (json_decode(env('REQUIRES_LOGIN')) && !$kirby->user()) {
go('/panel');
} ?>
<!DOCTYPE html>
<html lang="<?= $kirby->language()?->code() ?>">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<?php snippet('seo/head') ?>
<link rel="icon" href="/favicon.svg" />
<link rel="mask-icon" href="/favicon.svg" color="#000000" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<meta name="theme-color" content="#000000">
<?= vite([
'src/styles/index.css',
'src/index.ts'
]) ?>
</head>
<body class="min-h-screen antialiased overflow-x-clip">
<?php snippet('core/skip-nav') ?>
<?php snippet('core/nav') ?>
<div data-taxi>
<div class="flex flex-col" data-taxi-view>
<main class="flex-grow">
<div id="main"></div>
<?= $slot ?>
</main>
<?php snippet('core/footer') ?>
</div>
</div>
<?php snippet('seo/schemas') ?>
</body>
</html>

View File

@@ -0,0 +1,18 @@
<?php
/**
* @var Kirby\Cms\StructureObject|Kirby\Content\Content $link
*/ ?>
<?php if ($link->link()->isNotEmpty()) : ?>
<a <?= attr([
'class' => $class ?? '',
'href' => $link->link()->toUrl(),
'target' => $link->newTab()->toBool() ? '_blank' : null,
'rel' => $link->newTab()->toBool() ? 'noopener' : null,
'aria-current' => $link->link()->linkType() === 'page' && $link->link()->toPage()?->isActive() ? 'page' : null,
...($attr ?? [])
]) ?>>
<?= $slot ?? $link->label()->or($link->link()->linkTitle()) ?>
</a>
<?php endif ?>

View File

@@ -0,0 +1,97 @@
<?php
/**
* @var Kirby\Cms\File|Kirby\Filesystem\Asset $image The image to display
* @var string|null $alt An optional alt text, defaults to the file's alt text
* @var int|null $ratio Specify a ratio for the image crop
*
* @var array|null $formats Which formats to render (avif, jpeg, etc.)
* @var string|null $preset Which srcset preset to use
* @var string|null $sizes The sizes attribute for the <img> element
*
* @var string|array|null $class Additional classes for the <picture> element
* @var string|array|null $imgClass Additional classes for the <img> element
* @var array|null $attr Additional attributes for the <picture> element
*
* @var bool|null $clientBlur Whether to use client-side blurhash/thumbhash, defaults to true
* @var bool|null $lazy Whether to use lazy loading, defaults to true
* */
use Kirby\Toolkit\A;
$focus ??= is_a($image, 'Kirby\Cms\File') ? $image?->focus() ?? 'center' : 'center';
$ratio ??= null;
$preset ??= 'default';
$clientBlur ??= true;
$attr ??= [];
$formats ??= ['webp', $image?->extension()];
$lazy ??= true;
if (is_a($image, 'Kirby\Cms\File') || is_a($image, 'Kirby\Filesystem\Asset')) : ?>
<picture <?= attr([
'class' => ['block', $class ?? ''],
'style' => '--ratio: ' . ($ratio ?? round($image->ratio(), 2)) . ';',
...$attr
]) ?>>
<?php if ($image->extension() == 'svg') : ?>
<?= svg($image) ?>
<?php else : ?>
<?php foreach ($formats as $format) :
$widths = option("thumbs.srcsets.{$preset}");
$srcset = [];
$median = $ratio ? $image->crop(median($widths), floor(median($widths) / $ratio)) : $image->resize(median($widths)); ?>
<?php
// Generate srcset array
foreach ($widths as $width) {
$srcset[$width . 'w'] = [
'width' => $width,
'height' => $ratio ? floor($width / $ratio) : null,
'crop' => $ratio ? true : false,
'format' => $format
];
} ?>
<?php if ($lazy) : ?>
<source <?= attr([
'type' => "image/{$format}",
'data-srcset' => $image->srcset($srcset),
'data-sizes' => $sizes ?? 'auto',
]) ?>>
<?php else : ?>
<source <?= attr([
'type' => "image/{$format}",
'srcset' => $image->srcset($srcset),
'sizes' => $sizes ?? '100vw'
]) ?>>
<?php endif; ?>
<?php endforeach; ?>
<img <?= attr([
'data-thumbhash' => $clientBlur ? $image->th() : null,
'src' => !$clientBlur ? $image->thUri() : null,
'data-src' => $median->url(),
'width' => $image->width(),
'height' => $ratio ? floor($image->width() / $ratio) : $image->height(),
'alt' => $alt ?? (is_a($image, 'Kirby\Cms\File') ? $image->alt() : null),
'loading' => $lazy ? "lazy" : null,
'data-sizes' => $sizes ?? 'auto',
'class' => cls(['size-full object-cover', $imgClass ?? ' ']),
'style' => A::join([
"aspect-ratio: " . ($ratio ?? $image->ratio()),
"object-position: {$focus}"
], '; '),
]) ?>>
<?php endif ?>
</picture>
<?php
// Dummy element that will be rendered when specified image is not an image
else : ?>
<picture <?= attr(['class' => ['block', $class ?? ''], ...$attr]) ?>></picture>
<?php endif ?>

View File

@@ -0,0 +1,2 @@
<?php $kirby->response()->code(404); // set status code
echo $site->errorPage()->render(); // render 404 if unmatched template

View File

@@ -0,0 +1,3 @@
<?php
require __DIR__ . '/layout.php';

View File

@@ -0,0 +1,3 @@
<?php
require __DIR__ . '/layout.php';

View File

@@ -0,0 +1,11 @@
<?php
/**
* @var Kirby\Cms\App $kirby
* @var Kirby\Cms\Page $page
* @var Kirby\Cms\Site $site
*/
snippet('layout', slots: true); ?>
<?= $page->blocks()->toBlocks() ?>
<?php endsnippet() ?>

View File

@@ -0,0 +1,69 @@
# ARIA
aria.skipNavigation: Navigation überspringen
# Social Media
twitter: Twitter
facebook: Facebook
instagram: Instagram
linkedin: LinkedIn
youtube: YouTube
vimeo: Vimeo
# Roles
user.admin.title: Administrator
user.admin.description: Hat Zugriff auf alle Inhalte und administrative Tools.
user.editor.title: Editor
user.editor.description: Kann alle Inhalte bearbeiten, aber keine administrativen Tools nutzen. (z.B. Nutzerverwaltung)
# Tabs
tab.content.label: Inhalt
tab.advanced.label: Erweitert
tab.pages.label: Seiten
tab.navigation.label: Navigation & Footer
# Files
file.image.title: Bild
file.image.alt.label: Alternativer Text/Bildbeschreibung
file.image.alt.help: Der alternative Text soll Nutzern mit Seheinschränkung & Suchmaschinen-Crawlern helfen, den Inhalt des Bildes zu verstehen.
file.video.title: Video
file.file.title: Dokument
# Pages
page.layout.title: Layout-Seite
page.error.title: Fehlerseite
page.home.title: Startseite
page.images.title: Bilder
page.videos.title: Videos
page.files.title: Dokumente
# Fields
field.blocks.label: Inhalt
field.image.label: Bild
field.link.label: Link
field.link.label.label: Beschriftung
field.link.link.label: Link
field.link.newTab.label: In neuem Tab öffnen?
field.link.empty: Kein Link angegeben
field.link.anchor.label: Anker (Block-ID)
field.button.label: Button
field.blockId.label: Eindeutige ID (Anker)
field.pages.label: Seiten
field.text.label: Text
field.navigation.label: Navigation
field.footer.label: Footer
# Blocks
block.example.title: Block
# Sections
section.images.label: Alle Bilder
section.videos.label: Alle Videos
section.files.label: Alle Dokumente
section.children.label: Unterseiten
# Other
site.title: Dashboard

View File

@@ -0,0 +1,69 @@
# ARIA
aria.skipNavigation: Skip navigation
# Social Media
twitter: Twitter
facebook: Facebook
instagram: Instagram
linkedin: LinkedIn
youtube: YouTube
vimeo: Vimeo
# Roles
user.admin.title: Administrator
user.admin.description: Has access to all content and administrative tools.
user.editor.title: Editor
user.editor.description: Can edit all content but cannot use administrative tools (e.g., user management).
# Tabs
tab.content.label: Content
tab.advanced.label: Advanced
tab.pages.label: Pages
tab.navigation.label: Navigation & Footer
# Files
file.image.title: Image
file.image.alt.label: Alternative Text/Image Description
file.image.alt.help: The alternative text helps visually impaired users & search engine crawlers understand the content of the image.
file.video.title: Video
file.file.title: Document
# Pages
page.layout.title: Layout Page
page.error.title: Error Page
page.home.title: Home Page
page.images.title: Images
page.videos.title: Videos
page.files.title: Documents
# Fields
field.blocks.label: Content
field.image.label: Image
field.link.label: Link
field.link.label.label: Label
field.link.link.label: Link
field.link.newTab.label: Open in new tab?
field.link.empty: No link provided
field.link.anchor.label: Anchor (Block ID)
field.button.label: Button
field.blockId.label: Unique ID (Anchor)
field.pages.label: Pages
field.text.label: Text
field.navigation.label: Navigation
field.footer.label: Footer
# Blocks
block.example.title: Block
# Sections
section.images.label: All Images
section.videos.label: All Videos
section.files.label: All Documents
section.children.label: Subpages
# Other
site.title: Dashboard

View File

@@ -0,0 +1,20 @@
# src > controllers
This folder contains controller files for [https://stimulus.hotwired.dev/](Stimulus) framework. They will be autoloaded by a script included in index.ts.
The naming conventions are different from the Stimulus default. Please do not append `*_controller.ts` at the end of the filename. Instead:
- Use hyphens to separate words
- Just use your desired controller name as the filename
`hello-world.ts`, not `hello_world_controller.ts`
## Lazy loading
When importing large libraries, it is recommended to lazy load them. This can be done by appending `.lazy` to the filename. Thanks to ES Modules, the file will then be loaded only when the controller is used for the first time.
`hello-world.lazy.ts`
## TypeScript
We use [stimulus-typescript](https://github.com/ajaishankar/stimulus-typescript) for strongly typed Stimulus controllers.

View File

@@ -0,0 +1,8 @@
import { Controller } from "@hotwired/stimulus"
import { Typed } from "stimulus-typescript"
export default class extends Typed(Controller, {}) {
connect() {
console.log("Hello world!")
}
}

1
kirby-baukasten-main/src/env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@@ -0,0 +1,66 @@
import "htmx.org"
import "idiomorph/dist/idiomorph-ext.js"
import { Application, AttributeObserver } from "@hotwired/stimulus"
import type { ControllerConstructor } from "@hotwired/stimulus"
import { lazyLoad } from "unlazy"
import.meta.glob(["../assets/**"]) // Import all assets for copying them to dist
console.log(
"%cMade with Kirby and ❤️ by Love & Kindness GmbH",
"font-size: 12px; font-weight: bold; color: #fff; background-color: #000; padding: 8px 12px; margin: 4px 0; border-radius: 4px;"
)
declare global {
interface Window {
Stimulus: Application
}
}
// Register Stimulus & controllers
window.Stimulus = Application.start()
// Register all eager controllers in the controllers folder
for (const [key, controller] of Object.entries(
import.meta.glob(["./controllers/*.ts", "!./controllers/*.lazy.ts"], { eager: true })
)) {
window.Stimulus.register(key.slice(14, -3), (controller as { default: ControllerConstructor }).default)
}
// Register observer & lazy controllers
const lazyControllers = Object.entries(import.meta.glob("./controllers/*.lazy.ts")).reduce(
(prev, [key, controller]) => {
prev.set(key.slice(14, -8), controller as () => Promise<{ default: ControllerConstructor }>)
return prev
},
new Map<string, () => Promise<{ default: ControllerConstructor }>>()
)
const loadController = async (element: HTMLElement) => {
// data-controller attribute can contain multiple controllers
const controllerNames = element
.getAttribute(window.Stimulus.schema.controllerAttribute ?? "data-controller")
?.split(/\s+/)
for (const name of controllerNames ?? []) {
// If the controller is not registered yet, register it
if (!window.Stimulus.router.modules.some((module) => module.definition.identifier === name)) {
const controllerDefinition = (await lazyControllers.get(name)?.())?.default
if (controllerDefinition) window.Stimulus.register(name, controllerDefinition)
}
}
}
const controllerObserver = new AttributeObserver(window.Stimulus.element, window.Stimulus.schema.controllerAttribute, {
elementMatchedAttribute: loadController,
elementAttributeValueChanged: loadController
})
controllerObserver.start()
if (import.meta.env.DEV) {
window.Stimulus.debug = true
}
lazyLoad()

View File

@@ -0,0 +1,81 @@
import "htmx.org"
import "idiomorph/dist/idiomorph-ext.js"
import { Core } from "@unseenco/taxi"
import { Application, AttributeObserver } from "@hotwired/stimulus"
import type { ControllerConstructor } from "@hotwired/stimulus"
import type { Transition, Renderer } from "@unseenco/taxi"
import.meta.glob(["../assets/**"]) // Import all assets for copying them to dist
declare global {
interface Window {
Stimulus: Application
Taxi: Core
}
}
// Register Stimulus & controllers
window.Stimulus = Application.start()
// Register all eager controllers in the controllers folder
for (const [key, controller] of Object.entries(
import.meta.glob(["./controllers/*.ts", "!./controllers/*.lazy.ts"], { eager: true })
)) {
window.Stimulus.register(key.slice(14, -3), (controller as { default: ControllerConstructor }).default)
}
// Register observer & lazy controllers
const lazyControllers = Object.entries(import.meta.glob("./controllers/*.lazy.ts")).reduce(
(prev, [key, controller]) => {
prev.set(key.slice(14, -8), controller as () => Promise<{ default: ControllerConstructor }>)
return prev
},
new Map<string, () => Promise<{ default: ControllerConstructor }>>()
)
const loadController = async (element: HTMLElement) => {
// data-controller attribute can contain multiple controllers
const controllerNames = element
.getAttribute(window.Stimulus.schema.controllerAttribute ?? "data-controller")
?.split(/\s+/)
for (const name of controllerNames ?? []) {
// If the controller is not registered yet, register it
if (!window.Stimulus.router.modules.some((module) => module.definition.identifier === name)) {
const controllerDefinition = (await lazyControllers.get(name)?.())?.default
if (controllerDefinition) window.Stimulus.register(name, controllerDefinition)
}
}
}
const controllerObserver = new AttributeObserver(window.Stimulus.element, window.Stimulus.schema.controllerAttribute, {
elementMatchedAttribute: loadController,
elementAttributeValueChanged: loadController
})
controllerObserver.start()
if (import.meta.env.DEV) {
window.Stimulus.debug = true
}
// Install Taxi
window.Taxi = new Core({
renderers: Object.entries(import.meta.glob("./renderers/*.ts", { eager: true })).reduce(
(acc, [key, transition]) => {
acc[key.slice(12, -3)] = (transition as { default: typeof Renderer }).default
return acc
},
{} as Record<string, typeof Renderer>
),
transitions: Object.entries(import.meta.glob("./transitions/*.ts", { eager: true })).reduce(
(acc, [key, transition]) => {
acc[key.slice(14, -3)] = (transition as { default: typeof Transition }).default
return acc
},
{} as Record<string, typeof Transition>
),
allowInterruption: true,
removeOldContent: false
})

View File

@@ -0,0 +1,3 @@
# src > renderers
This folder contains renderers for use with [https://taxi.js.org/renderers/](Taxi.js).

View File

@@ -0,0 +1,13 @@
import { Renderer } from "@unseenco/taxi"
import { lazyLoad } from "unlazy"
export default class extends Renderer {
initialLoad() {
lazyLoad()
}
onEnterCompleted() {
this.remove()
lazyLoad()
}
}

2
kirby-baukasten-main/src/reset.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
// Do not add any other lines of code to this file!
import "@total-typescript/ts-reset"

View File

@@ -0,0 +1,15 @@
@font-face {
font-family: 'Inter';
src: url('/assets/fonts/inter/normal.woff2') format('woff2');
font-weight: 100 900;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Inter';
src: url('/assets/fonts/inter/italic.woff2') format('woff2');
font-weight: 100 900;
font-display: swap;
font-style: italic;
}

View File

@@ -0,0 +1,39 @@
@import './fonts.css';
@import 'tailwindcss';
@plugin "@tailwindcss/forms";
@source "../../site/**/*.php";
@theme {
/* Fonts */
--font-family-*: initial;
--font-family-sans: 'Inter', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
/* Reset Breakpoints to fix sorting */
--breakpoint-*: initial;
/* Colors */
--color-*: initial;
/* neutrals */
--color-black: #000;
--color-white: #fff;
}
@layer base {
svg {
@apply w-full h-full;
}
/* Fix Safari issue related to <summary> / <details> arrow */
details > summary.list-none::-webkit-details-marker,
details > summary.list-none::marker {
display: none;
}
/* https://defensivecss.dev/tip/flexbox-min-content-size/ */
* {
min-height: 0;
min-width: 0;
}
}

View File

@@ -0,0 +1,76 @@
@import './fonts.css';
:root {
@apply antialiased;
}
/* Panel Max Width */
@media (min-width: 60rem) {
.k-panel-main {
max-width: 112rem;
margin-inline: auto;
padding-inline-start: calc(var(--main-start, 0) + var(--main-padding-inline));
}
}
/* Custom login form styling */
.k-login-view {
position: relative;
.k-login-dialog {
overflow: visible;
&::before {
--height: 1.8rem;
--width: 2.7rem;
bottom: calc(100% + 2rem);
display: block;
height: var(--height);
width: var(--width);
margin: 0 auto;
left: 0.25rem;
position: absolute;
content: var(--logo);
}
}
&::before {
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 172 20'%3E%3Cpath fill='%23000' d='M3.48 3.14V18H1.3V3.14h2.18Zm2.265 9.74c0-3.16 2.16-5.34 5.26-5.34 3.12 0 5.28 2.18 5.28 5.34 0 3.16-2.16 5.32-5.28 5.32-3.1 0-5.26-2.16-5.26-5.32Zm2.24-.02c0 2.1 1.2 3.46 3.02 3.46 1.84 0 3.04-1.36 3.04-3.46 0-2.12-1.2-3.46-3.04-3.46-1.82 0-3.02 1.34-3.02 3.46ZM19.16 7.74l3.06 7.94 3.02-7.94h2.36L23.4 18h-2.44l-4.1-10.26h2.3Zm19.127 4.92v.82h-7.84c.04 1.92 1.3 2.92 2.9 2.92 1.2 0 2.2-.56 2.62-1.68h2.22c-.54 2.08-2.38 3.48-4.88 3.48-3.06 0-5.08-2.12-5.08-5.34 0-3.2 2.02-5.32 5.06-5.32 2.98 0 5 2.04 5 5.12Zm-5-3.38c-1.46 0-2.66.9-2.82 2.68h5.6c-.12-1.78-1.3-2.68-2.78-2.68Zm46.859 6.8h.98V18h-1.16c-1.38 0-2.2-.32-3-1.24-1.14 1-2.58 1.44-4.1 1.44-2.74 0-4.9-1.42-4.9-3.9 0-1.34.62-2.68 2.82-3.9l.34-.18-.22-.26c-1.16-1.3-1.48-2.22-1.48-3.12 0-1.98 1.58-3.28 3.92-3.28 2.3 0 3.8 1.26 3.8 3.1 0 1.38-.86 2.48-2.14 3.32l-.88.58 2.94 3.26c.5-.88.84-1.96.88-3.32h1.92c0 1.86-.56 3.54-1.48 4.8l.02.02c.5.54.94.76 1.74.76Zm-6.86-10.84c-1.08 0-1.76.66-1.76 1.58 0 .7.42 1.3 1.12 2.1l.34.38.74-.46c.9-.56 1.36-1.32 1.36-2.04 0-.94-.72-1.56-1.8-1.56Zm-3.06 8.92c0 1.5 1.26 2.24 2.74 2.24.94 0 1.98-.3 2.8-.98l-3.5-3.92-.3.18c-1.38.84-1.74 1.66-1.74 2.48ZM87.966 18V3.14h2.18v9.16l4.48-4.56h2.72l-4.38 4.42 4.42 5.84h-2.76l-3.22-4.3-1.2 1.22V18h-2.24ZM98.784 5.84V3.56h2.42v2.28h-2.42Zm.12 12.16V7.76h2.18V18h-2.18Zm10.184-10.44c2.58 0 4.1 1.7 4.1 4.32V18h-2.2v-5.66c0-1.92-.98-2.86-2.48-2.86-1.7 0-2.62 1.2-2.62 3.02V18h-2.18V7.76h1.98l.16 1.38c.58-.98 1.68-1.58 3.24-1.58Zm14.593-4.42h2.2V18h-2l-.18-1.56c-.62 1.04-1.96 1.76-3.54 1.76-2.82 0-4.86-1.96-4.86-5.3 0-3.44 2.14-5.36 4.86-5.36 1.6 0 2.9.68 3.52 1.72V3.14Zm-3.08 13.12c1.72 0 3.04-1.24 3.04-3.36 0-2.1-1.32-3.42-3.04-3.4-1.8 0-3.04 1.32-3.04 3.4 0 2.12 1.26 3.36 3.04 3.36Zm13.253-8.7c2.58 0 4.1 1.7 4.1 4.32V18h-2.2v-5.66c0-1.92-.98-2.86-2.48-2.86-1.7 0-2.62 1.2-2.62 3.02V18h-2.18V7.76h1.98l.16 1.38c.58-.98 1.68-1.58 3.24-1.58Zm16.333 5.1v.82h-7.84c.04 1.92 1.3 2.92 2.9 2.92 1.2 0 2.2-.56 2.62-1.68h2.22c-.54 2.08-2.38 3.48-4.88 3.48-3.06 0-5.08-2.12-5.08-5.34 0-3.2 2.02-5.32 5.06-5.32 2.98 0 5 2.04 5 5.12Zm-5-3.38c-1.46 0-2.66.9-2.82 2.68h5.6c-.12-1.78-1.3-2.68-2.78-2.68Zm6.592 5.62h2.18c.02 1.06 1 1.68 2.34 1.68 1.34 0 2.14-.5 2.14-1.4 0-.78-.4-1.2-1.56-1.38l-2.06-.36c-2-.34-2.78-1.38-2.78-2.84 0-1.84 1.82-3.04 4.14-3.04 2.5 0 4.32 1.2 4.34 3.24h-2.16c-.02-1.04-.92-1.62-2.18-1.62-1.2 0-1.96.5-1.96 1.34 0 .74.52 1.08 1.66 1.28l2.02.36c1.9.34 2.78 1.28 2.78 2.82 0 2.12-1.96 3.22-4.34 3.22-2.7 0-4.52-1.3-4.56-3.3Zm10.215 0h2.18c.02 1.06 1 1.68 2.34 1.68 1.34 0 2.14-.5 2.14-1.4 0-.78-.4-1.2-1.56-1.38l-2.06-.36c-2-.34-2.78-1.38-2.78-2.84 0-1.84 1.82-3.04 4.14-3.04 2.5 0 4.32 1.2 4.34 3.24h-2.16c-.02-1.04-.92-1.62-2.18-1.62-1.2 0-1.96.5-1.96 1.34 0 .74.52 1.08 1.66 1.28l2.02.36c1.9.34 2.78 1.28 2.78 2.82 0 2.12-1.96 3.22-4.34 3.22-2.7 0-4.52-1.3-4.56-3.3Z'/%3E%3C/svg%3E");
position: absolute;
opacity: 0.5;
bottom: 2.5rem;
height: 1.125rem;
width: 8.95rem;
}
}
/** Custom styles for plausible embed */
@media (min-width: 60rem) {
.k-panel-main:has(iframe[plausible-embed]) {
max-width: 104rem;
}
}
.k-header:has(+ iframe[plausible-embed]) {
display: none;
}
/** No page header for files */
:where([data-template="images"], [data-template="files"]) .k-page-view-header {
display: none;
}
/* Hide pages in Link field that we don't want to link to */
.k-page-browser .k-page-tree li:has(.k-icon-frame > .k-icon:is([data-type="survey"], [data-type="file-image"], [data-type="file-word"])) {
display: none;
}
.k-file-browser-tree ul li ul li:has(.k-icon-frame > .k-icon:not([data-type="file-image"], [data-type="file-word"])) {
display: none;
}
/* No padding in Block selector plugin */
.k-block-selector-button-preview {
padding: 0;
}

View File

@@ -0,0 +1,3 @@
# src > transitions
This folder contains transitions for use with [https://taxi.js.org/transitions/](Taxi.js).

View File

@@ -0,0 +1,13 @@
import { Transition } from "@unseenco/taxi"
export default class extends Transition {
onEnter({ done }: { done: CallableFunction }) {
window.scrollTo({
top: 0,
left: 0,
behavior: "instant"
})
done()
}
}

View File

@@ -0,0 +1,41 @@
/* main breakpoints definition */
export const breakpoints = {
xxl: 96,
xl: 72,
lg: 62,
md: 44,
sm: 29.5
} as const
export const remToPx = (rem: number) => {
return rem * Number.parseFloat(getComputedStyle(document.documentElement).fontSize)
}
export const breakpointsString = Object.fromEntries(
Object.entries(breakpoints).map(([key, value]) => [key, `${value}rem` as `${number}rem`])
) as { [key in keyof typeof breakpoints]: `${number}rem` }
export const getBreakpointValue = <T>(values: { [K in keyof typeof breakpoints | number | "_"]?: T }) => {
const viewportWidth = window.innerWidth
// find the closest breakpoint
const closestBreakpoint = Object.entries(breakpoints).find(
([, breakpointValue]) => viewportWidth >= remToPx(breakpointValue)
)
if (closestBreakpoint && Object.hasOwn(values, closestBreakpoint[0])) {
return values[closestBreakpoint[0] as keyof typeof breakpoints]
}
// nothing found yet, check if values has a rem value
const remEntry = Object.entries(values).find(([key]) => !Number.isNaN(Number(key)))
if (remEntry) {
const [remValue, value] = remEntry
if (viewportWidth >= remToPx(Number.parseFloat(remValue))) {
return value
}
}
// return fallback
return values._
}

View File

@@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "ES2023",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
/* Paths */
"baseUrl": "./src",
"paths": {
"@/*": ["./*"]
}
},
"include": ["src"]
}

View File

@@ -0,0 +1,66 @@
import { resolve } from "node:path"
import { defineConfig, loadEnv } from "vite"
import laravel from "laravel-vite-plugin"
import tsconfigPaths from "vite-tsconfig-paths"
import svgSprite from "vite-svg-sprite-wrapper"
import tailwind from "@tailwindcss/vite"
import inject from "@rollup/plugin-inject"
import browserslist from "browserslist"
import { browserslistToTargets } from "lightningcss"
import { browserslist as browserslistConfig } from "./package.json"
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "")
return {
base: mode === "development" ? "/" : "/dist/",
build: {
outDir: resolve(__dirname, "public/dist"),
emptyOutDir: true,
manifest: "manifest.json",
cssMinify: "lightningcss"
},
plugins: [
// this plugin is necessary for our HTMX extensions to correctly register
inject({
htmx: "htmx.org"
}),
svgSprite({
sprite: {
shape: {
transform: [
{
svgo: {
plugins: [{ name: "preset-default" }, "removeXMLNS"]
}
}
]
}
},
icons: "assets/icons/*.svg",
outputDir: "assets/"
}),
laravel({
input: ["src/index.ts", "src/styles/index.css", "src/styles/panel.css"],
refresh: ["site/{layouts,snippets,templates}/**/*"]
}),
tsconfigPaths(),
tailwind()
],
css: {
transformer: "lightningcss",
lightningcss: {
targets: browserslistToTargets(browserslist(browserslistConfig))
}
},
server: {
origin: env.APP_URL,
port: Number(env.VITE_DEV_PORT || 3000),
proxy: {
// we proxy anything except the folders our vite dev assets are in
"^(?!/src|/node_modules|/@vite|/@react-refresh|/assets).*$": `http://${env.KIRBY_DEV_HOSTNAME}:${env.KIRBY_DEV_PORT}`
}
}
}
})