From: elektrischerwalfisch Date: Fri, 4 Apr 2025 08:37:21 +0000 (+0200) Subject: upload files X-Git-Url: https://skyeroc.xyz/gitweb/?a=commitdiff_plain;h=7c8adeb9bd33ebcda6073a2d93dfa766d8b07d69;p=flatmark upload files --- diff --git a/.gitignore b/.gitignore index 5469669..7b50b18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,48 +1,2 @@ -# Wordpress - ignore core, configuration, examples, uploads and logs. -# https://github.com/github/gitignore/blob/main/WordPress.gitignore - -# Core -# -# Note: if you want to stage/commit WP core files -# you can delete this whole section/until Configuration. -/wp-admin/ -/wp-content/index.php -/wp-content/languages -/wp-content/plugins/index.php -/wp-content/themes/index.php -/wp-includes/ -/index.php -/license.txt -/readme.html -/wp-*.php -/xmlrpc.php - -# Configuration -wp-config.php - -# Example themes -/wp-content/themes/twenty*/ - -# Example plugin -/wp-content/plugins/hello.php - -# Uploads -/wp-content/uploads/ - -# Log files -*.log - -# htaccess -/.htaccess - -# All plugins -# -# Note: If you wish to whitelist plugins, -# uncomment the next line -#/wp-content/plugins - -# All themes -# -# Note: If you wish to whitelist themes, -# uncomment the next line -#/wp-content/themes \ No newline at end of file +.vscode +.htaccess \ No newline at end of file diff --git a/README.md b/README.md index 4f460f6..27335b9 100644 --- a/README.md +++ b/README.md @@ -1 +1,79 @@ -# flatmark \ No newline at end of file +# FlatMark + +*A lightweight, flat-file Markdown-based site generator with optional multilingual support.* + +## Features +✅ Simple, no database required – just Markdown files +✅ Supports **single** and **multi-language** setups +✅ Auto-parses **Markdown** to HTML using [Parsedown](https://parsedown.org/) +✅ Easy-to-edit content, just upload `.md` files +✅ **Custom meta fields** for title, description, and robots meta tag +✅ Customizable with your own **themes and styles** +✅ Lightweight and fast + +## How It Works +FlatMark dynamically converts Markdown files in the `/pages` folder into HTML pages. +- In **single-language mode**, pages are stored in `/pages/`. +- In **multi-language mode**, each language has its own folder, e.g., `/pages-en/`, `/pages-de/`. + +### URL Structure + +| Mode | Example URL | Maps to File | +|----------------|-------------|-----------------------| +| Single Language | `/about` | `/pages/about.md` | +| Multi-Language | `/en/about` | `/pages-en/about.md` | + +## Installation +1. **Upload** the files to your web server. +2. **Edit content** inside `/pages` (or `/pages-XX` for multilingual sites). +3. Done! Your site is ready. + +### Requirements +- PHP 7.4+ +- Apache/Nginx with mod_rewrite enabled + +## Configuration +Set up **single** or **multi-language mode** in `index.php`: + +```php +$multilingual = true; // Set to false for single-language mode +require $multilingual ? 'config-multilang.php' : 'config-basic.php'; + + +## Meta Information (Title, Description & Robots) + +Each Markdown page can include optional meta information using HTML comments at the top of the file. +These values will be automatically extracted and used in the section of the generated HTML page. + +Example Markdown file (about.md) with meta information: + + + + + + # Welcome to Our Company + + We are committed to providing the best services... + +- title → Sets the of the page. Defaults to the filename if not provided. +- description → Used for the <meta name="description"> tag. Defaults to an empty string if not set. +- robots → Controls search engine indexing (index, follow / noindex, nofollow). Defaults to index, follow. + +### Folder Structure + + /flatmark/ + │── /pages-en/ # English pages + │── /pages-de/ # German pages + │── /theme/ # Styles and templates + │── /config-multilang.php # Multi-language setup + │── /config-basic.php # Single-language setup + │── index.php # Main file + │── .htaccess # URL rewriting + │── README.md # Documentation + +### Customization +- Themes: Modify /theme/css/style.css for styling. +- Header/Footer: Edit 01-header.md and 02-footer.md in each language folder. + +## License +FlatMark is released under the MIT License. \ No newline at end of file diff --git a/config-basic.php b/config-basic.php new file mode 100644 index 0000000..9302b4e --- /dev/null +++ b/config-basic.php @@ -0,0 +1,31 @@ +<?php + /** + * Project: FlatMark + * File: config-basic.php + * Description: Configuration for a single-language setup (basic configuration). + * Author: elektrischerwalfisch + * License: MIT (or another open-source license) + * Version: 1.0 + */ + + // Define page language + $lang = 'de'; + + // Use the first URI segment if available; otherwise default to 'home' + $page = !empty($uriParts[0]) ? $uriParts[0] : 'home'; + + // Set content-folder + $folder = __DIR__ . '/pages/'; + $file = $folder . $page . '.md'; + + // Define header and footer file paths + $headerFile = $folder . '01-header.md'; + $footerFile = $folder . '02-footer.md'; + + // Check if file exists and prevent rendering of header/footer files + if (!file_exists($file) || in_array($page, ['01-header', '02-footer'])) { + http_response_code(404); + $file = $folder . '404.md'; + } + +?> \ No newline at end of file diff --git a/config-multilang.php b/config-multilang.php new file mode 100644 index 0000000..8223226 --- /dev/null +++ b/config-multilang.php @@ -0,0 +1,45 @@ +<?php + /** + * Project: FlatMark + * File: config-multilang.php + * Description: Configuration for multilingual setup (supports multiple languages). + * Author: elektrischerwalfisch + * License: MIT (or another open-source license) + * Version: 1.0 + */ + + // Define supported languages (first one is default) + $supportedLanguages = ['en', 'de']; + + // Dynamic definition of content folders (folders have to be named like /pages-en, /pages-de etc.) + $languageFolders = []; + foreach ($supportedLanguages as $lang) { + $languageFolders[$lang] = __DIR__ . "/pages-$lang/"; + } + + // Detect browser language + $browserLang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'en', 0, 2);// Get the user's browser language (first two letters), defaulting to 'en' if not set + $defaultLang = in_array($browserLang, $supportedLanguages) ? $browserLang : $supportedLanguages[0]; + + // Handle default language and redirect logic + if (!in_array($uriParts[0], $supportedLanguages)) { + header("Location: /$defaultLang"); + exit; + } + + // Determine the requested language and page from the URL and build the file path + $lang = $uriParts[0]; // Set language based on the first URL segment + $page = $uriParts[1] ?? 'home'; // Set page based on the second URL segment, default to 'home' + $file = $languageFolders[$lang] . $page . '.md'; // Construct the full file path for the Markdown file + + // Define header and footer file paths + $headerFile = $languageFolders[$lang] . '01-header.md'; + $footerFile = $languageFolders[$lang] . '02-footer.md'; + + // Check if file exists and prevent rendering of header/footer files + if (!file_exists($file) || in_array($page, ['01-header', '02-footer'])) { + http_response_code(404); + $file = $languageFolders[$lang] . '404.md'; // Define 404-page + } + +?> \ No newline at end of file diff --git a/files/example-pic-01.jpg b/files/example-pic-01.jpg new file mode 100644 index 0000000..4eecd91 Binary files /dev/null and b/files/example-pic-01.jpg differ diff --git a/files/example-pic-02.jpg b/files/example-pic-02.jpg new file mode 100644 index 0000000..6a61ee0 Binary files /dev/null and b/files/example-pic-02.jpg differ diff --git a/files/example-pic-03.jpg b/files/example-pic-03.jpg new file mode 100644 index 0000000..05f8994 Binary files /dev/null and b/files/example-pic-03.jpg differ diff --git a/files/logo.gif b/files/logo.gif new file mode 100644 index 0000000..0f3b093 Binary files /dev/null and b/files/logo.gif differ diff --git a/index.php b/index.php new file mode 100644 index 0000000..6886431 --- /dev/null +++ b/index.php @@ -0,0 +1,113 @@ +<?php + /** + * Project: FlatMark + * File: index.php + * Description: Main entry point for the website. + * Dynamically loads Markdown content, basic or multilingual configuration and shortcode-settings. + * Author: elektrischerwalfisch + * License: MIT (or another open-source license) + * Version: 1.0 + */ + + // Start output buffering (prevents page to "jump" before everything is loaded) + ob_start(); + + // Include shortcode functions (if file exists) + if (file_exists('theme/shortcodes.php')) { + require 'theme/shortcodes.php'; + } + + // Include Parsedown + require 'theme/libs/Parsedown.php'; + $Parsedown = new Parsedown(); + + // Get requested language and page from URL rewriting + $requestUri = trim($_SERVER['REQUEST_URI'], '/'); + $uriParts = explode('/', $requestUri); + + // Multilingual setup. If your site is a single-language setup, change 'config-multilang.php' to 'config-basic.php' + require 'config-multilang.php'; + + // Read the content of the requested Markdown file into a string + $markdown = file_get_contents($file); + + // Set defaults for Titel and meta-values + $defaultPageTitle = ucfirst($page); + $defaultMetaDescription = ""; + $defaultMetaRobots = "index, follow"; + + // Overwrite defaults by extracting individual title, description and robots from the first lines of the markdown file + $pageTitle = $defaultPageTitle; + $metaDescription = $defaultMetaDescription; + $metaRobots = $defaultMetaRobots; + if (preg_match('/^<!--\s*title:(.*?)\s*-->$/m', $markdown, $matches)) { + $pageTitle = trim($matches[1]); + } + if (preg_match('/^<!--\s*description:(.*?)\s*-->$/m', $markdown, $matches)) { + $metaDescription = trim($matches[1]); + } + if (preg_match('/^<!--\s*robots:(.*?)\s*-->$/m', $markdown, $matches)) { + $metaRobots = trim($matches[1]); + } + + // Load header and footer markdown files and convert to HTML + $headerContent = ''; + if (file_exists($headerFile)) { + $headerContent = file_get_contents($headerFile); + // Apply theme/functions.php before converting to HTML (if function exists) + if (function_exists('processShortcodes')) { + $headerContent = processShortcodes($headerContent); + } + // Convert Markdown to HTML + $headerContent = $Parsedown->text($headerContent); + } + $footerContent = ''; + if (file_exists($footerFile)) { + $footerContent = file_get_contents($footerFile); + // Apply theme/functions.php before converting to HTML (if function exists) + if (function_exists('processShortcodes')) { + $footerContent = processShortcodes($footerContent); + } + // Convert Markdown to HTML + $footerContent = $Parsedown->text($footerContent); + } + + // Apply theme/shortcodes.php before converting to HTML (if function exists) + if (function_exists('processShortcodes')) { + $markdown = processShortcodes($markdown); + } + + // Convert Markdown to HTML + $htmlContent = $Parsedown->text($markdown); + +?> + +<!DOCTYPE html> +<html lang="<?= htmlspecialchars($lang) ?>"> + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title><?= htmlspecialchars($pageTitle) ?> + + + + + +
+
+ +
+
+ +
+
+ +
+ +
+ + + + \ No newline at end of file diff --git a/pages-de/01-header.md b/pages-de/01-header.md new file mode 100644 index 0000000..cd2ffdc --- /dev/null +++ b/pages-de/01-header.md @@ -0,0 +1,12 @@ +{langmenu} +- [English](/en) +- [Deutsch](/de) +{/langmenu} + +![Logo](/files/logo.gif) +[flatMark](/de) +A lightweight, flat-file Markdown-based site generator + +- [Home](/de/home) +- [Über uns](/de/ueber-uns) +- [Kontakt](/de/kontakt) \ No newline at end of file diff --git a/pages-de/02-footer.md b/pages-de/02-footer.md new file mode 100644 index 0000000..18fe83b --- /dev/null +++ b/pages-de/02-footer.md @@ -0,0 +1,4 @@ +© {year} Flatmark + +- [Impressum](/de/impressum) +- [Datenschutzerklärung](/de/datenschutz) \ No newline at end of file diff --git a/pages-de/404.md b/pages-de/404.md new file mode 100644 index 0000000..bae7aae --- /dev/null +++ b/pages-de/404.md @@ -0,0 +1,6 @@ + + + +# 404 Error + +Diese Seite wurde gelöscht oder existiert nicht. \ No newline at end of file diff --git a/pages-de/datenschutz.md b/pages-de/datenschutz.md new file mode 100644 index 0000000..b695594 --- /dev/null +++ b/pages-de/datenschutz.md @@ -0,0 +1,5 @@ + + +# Datenschutzerklärung + +... \ No newline at end of file diff --git a/pages-de/home.md b/pages-de/home.md new file mode 100644 index 0000000..6a6879b --- /dev/null +++ b/pages-de/home.md @@ -0,0 +1,4 @@ +# Home + +... + diff --git a/pages-de/impressum.md b/pages-de/impressum.md new file mode 100644 index 0000000..70403f5 --- /dev/null +++ b/pages-de/impressum.md @@ -0,0 +1,5 @@ + + +# Impressum + +... \ No newline at end of file diff --git a/pages-de/kontakt.md b/pages-de/kontakt.md new file mode 100644 index 0000000..f62e484 --- /dev/null +++ b/pages-de/kontakt.md @@ -0,0 +1,3 @@ +# Kontakt + +... \ No newline at end of file diff --git a/pages-de/ueber-uns.md b/pages-de/ueber-uns.md new file mode 100644 index 0000000..ce85d2c --- /dev/null +++ b/pages-de/ueber-uns.md @@ -0,0 +1,3 @@ +# About + +... \ No newline at end of file diff --git a/pages-en/01-header.md b/pages-en/01-header.md new file mode 100644 index 0000000..60f1089 --- /dev/null +++ b/pages-en/01-header.md @@ -0,0 +1,12 @@ +{langmenu} +- [English](/en) +- [Deutsch](/de) +{/langmenu} + +![Logo](/files/logo.gif) +[flatMark](/de) +A lightweight, flat-file Markdown-based website generator + +- [Home](/en/home) +- [Examples](/en/examples) +- [Contact](/en/contact) \ No newline at end of file diff --git a/pages-en/02-footer.md b/pages-en/02-footer.md new file mode 100644 index 0000000..d68c9f3 --- /dev/null +++ b/pages-en/02-footer.md @@ -0,0 +1,4 @@ +© {year} flatMark + +- [Legal notice](/en/legal-notice) +- [PrivacyPolicy](/de/datenschutz) \ No newline at end of file diff --git a/pages-en/404.md b/pages-en/404.md new file mode 100644 index 0000000..d319ea9 --- /dev/null +++ b/pages-en/404.md @@ -0,0 +1,7 @@ + + + + +# 404 Not Found + +Sorry, the page you are looking for does not exist. \ No newline at end of file diff --git a/pages-en/contact.md b/pages-en/contact.md new file mode 100644 index 0000000..eb4b541 --- /dev/null +++ b/pages-en/contact.md @@ -0,0 +1,3 @@ +# Contact + +... \ No newline at end of file diff --git a/pages-en/examples.md b/pages-en/examples.md new file mode 100644 index 0000000..b37f5b3 --- /dev/null +++ b/pages-en/examples.md @@ -0,0 +1,52 @@ +# Examples + +## Shortcodes + +{columns 50-50} +`{columns 50-50}` Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. +{columns-seperator} +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. +{/columns} + + +{columns 33-66} +`{columns 33-66}` Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. +{columns-seperator} +![Pic](/files/example-pic-03.jpg) +{/columns} + +{columns 66-33} +`{columns 66-33}` Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. + +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. +{columns-seperator} +![Pic](/files/example-pic-02.jpg) +{/columns} + +{columns 33-33-33} +`{columns 33-33-33}` Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr. +{columns-seperator} +![Pic](/files/example-pic-01.jpg) +{columns-seperator} +![Pic](/files/example-pic-01.jpg) +{/columns} + +{columns 50-50} +`{columns 50-50}` Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, +{columns-seperator} +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, +{/columns} + +{bg-color} +`{bg-color}` Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, +{/bg-color} + +`{bg-color}` + +`{img-rounded}` + +{img-rounded} +![Pic](/files/example-pic-01.jpg) +{/img-rounded} + + diff --git a/pages-en/home.md b/pages-en/home.md new file mode 100644 index 0000000..19a3125 --- /dev/null +++ b/pages-en/home.md @@ -0,0 +1,24 @@ + + + + +{bg-color}{columns 33-66} +{img-rounded} +![Pic](/files/example-pic-01.jpg) +{/img-rounded} +{columns-seperator} +# flatMark +dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, +{/bg-color}{/columns} + +{columns 33-66} +![Pic](/files/example-pic-02.jpg) +{columns-seperator} +![Pic](/files/example-pic-03.jpg) +{/columns} + +{columns 50-50} +dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, +{columns-seperator} +dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, +{/columns} \ No newline at end of file diff --git a/pages-en/legal-notice.md b/pages-en/legal-notice.md new file mode 100644 index 0000000..9e425ec --- /dev/null +++ b/pages-en/legal-notice.md @@ -0,0 +1,5 @@ + + +# Legal notice + +... \ No newline at end of file diff --git a/pages-en/privacy-policy.md b/pages-en/privacy-policy.md new file mode 100644 index 0000000..996e4bf --- /dev/null +++ b/pages-en/privacy-policy.md @@ -0,0 +1,5 @@ + + +# Privacy policy + +... \ No newline at end of file diff --git a/theme/css/style.css b/theme/css/style.css new file mode 100644 index 0000000..e367077 --- /dev/null +++ b/theme/css/style.css @@ -0,0 +1,230 @@ + +@font-face { + font-family: 'opensans'; /* Light */ + src: url('../fonts/opensans/OpenSans-Light.woff2') format('woff2'), + url('../fonts/opensans/OpenSans-Light.woff') format('woff'), + url('../fonts/opensans/OpenSans-Light.ttf') format('truetype'); + font-weight: 300; + font-style: normal; +} + +@font-face { + font-family: 'opensans'; /* Light Italic*/ + src: url('../fonts/opensans/OpenSans-LightItalic.woff2') format('woff2'), + url('../fonts/opensans/OpenSans-LightItalic.woff') format('woff'), + url('../fonts/opensans/OpenSans-LightItalic.ttf') format('truetype'); + font-weight: 300; + font-style: italic; +} + +@font-face { + font-family: 'opensans'; /* Normal */ + src: url('../fonts/opensans/OpenSans-Regular.woff2') format('woff2'), + url('../fonts/opensans/OpenSans-Regular.woff') format('woff'), + url('../fonts/opensans/OpenSans-Regular.ttf') format('truetype'); + font-weight: 400; /* entspricht font-weight: normal */ + font-style: normal; +} + +@font-face { + font-family: 'opensans'; /* Regular Italic */ + src: url('../fonts/opensans/OpenSans-Italic.woff2') format('woff2'), + url('../fonts/opensans/OpenSans-Italic.woff') format('woff'), + url('../fonts/opensans/OpenSans-Italic.ttf') format('truetype'); + font-weight: 400; /* entspricht font-weight: normal */ + font-style: italic; +} + +@font-face { + font-family: 'opensans'; /* Semi Bold */ + src: url('../fonts/opensans/OpenSans-Semibold.woff2') format('woff2'), + url('../fonts/opensans/OpenSans-Semibold.woff') format('woff'), + url('../fonts/opensans/OpenSans-Semibold.ttf') format('truetype'); + font-weight: 600; + font-style: normal; +} + +@font-face { + font-family: 'opensans'; /* Semi Bold Italic */ + src: url('../fonts/opensans/OpenSans-SemiboldItalic.woff2') format('woff2'), + url('../fonts/opensans/OpenSans-SemiboldItalic.woff') format('woff'), + url('../fonts/opensans/OpenSans-SemiboldItalic.ttf') format('truetype'); + font-weight: 600; + font-style: italic; +} + +@font-face { + font-family: 'opensans'; /* Bold */ + src: url('../fonts/opensans/OpenSans-Bold.woff2') format('woff2'), + url('../fonts/opensans/OpenSans-Bold.woff') format('woff'), + url('../fonts/opensans/OpenSans-Bold.ttf') format('truetype'); + font-weight: 700; /* entspricht font-weight: bold */ + font-style: normal; +} + +@font-face { + font-family: 'opensans'; /* Bold Italic */ + src: url('../fonts/opensans/OpenSans-BoldItalic.woff2') format('woff2'), + url('../fonts/opensans/OpenSans-BoldItalic.woff') format('woff'), + url('../fonts/opensans/OpenSans-BoldItalic.ttf') format('truetype'); + font-weight: 700; /* entspricht font-weight: bold */ + font-style: italic; +} + +@font-face { + font-family: 'opensans'; /* Extra Bold */ + src: url('../fonts/opensans/OpenSans-ExtraBold.woff2') format('woff2'), + url('../fonts/opensans/OpenSans-ExtraBold.woff') format('woff'), + url('../fonts/opensans/OpenSans-ExtraBold.ttf') format('truetype'); + font-weight: 800; + font-style: normal; +} + +@font-face { + font-family: 'opensans'; /* Extra Bold Italic */ + src: url('../fonts/opensans/OpenSans-ExtraBoldItalic.woff2') format('woff2'), + url('../fonts/opensans/OpenSans-ExtraBoldItalic.woff') format('woff'), + url('../fonts/opensans/OpenSans-ExtraBoldItalic.ttf') format('truetype'); + font-weight: 800; + font-style: italic; +} + + +/* VARIABLES */ +:root { --content-width01: 900px;} +:root { --menu-width: 150px;} + +:root { --colorblack: #000000;} +:root { --colordark: #666666;} +:root { --colorlight: #FFFFFF;} +:root { --color-gray-10: #424242;} + +:root { --color-01: #DBA901;} +:root { --color-02: #DF3A01;} +:root { --color-03: #999999;} + +:root { --space-below-00: 5px;} +:root { --space-below-01: 15px;} +:root { --space-below-02: 25px;} +:root { --space-below-03: 60px;} +:root { --side-space-01: 15px;} +:root { --side-space-02: 25px;} +:root { --gap-01: 15px;} +:root { --animation-01: all 0.4s ease-in-out 0s;} + +:root { --fontsize-s: 12px;} +:root { --fontsize-m: 14px;} /* Font Size Default */ +:root { --fontsize-l: 24px;} +:root { --fontsize-xl: 34px;} +:root { --fontsize-xxl: 64px;} +:root { --lineheight-m: 1.4;} /* Line Height Default */ +:root { --lineheight-xxl: 1.2;} + +* {margin: 0;padding: 0;box-sizing: border-box;} +header, footer, section, article, nav {display: block;} + +body {background: var(--color-gray-10);color: var(--colordark);font-family: 'opensans', Arial, Helvetica, Sans-Serif;font-weight: 400;font-style: normal;font-size: var(--fontsize-m); line-height: var(--lineheight-m);} + +#wrapper {max-width: var(--content-width01);margin: 20px auto;padding: var(--side-space-01) var(--space-below-01);} +header, main {background: var(--colorlight);} + +h1 {font-size: var(--fontsize-xxl); font-weight: 800;color: var(--colordark);margin-bottom: var(--space-below-01);} +h2 {font-size: var(--fontsize-l); font-weight: 800;color: var(--color-01);} +h3 {font-size: var(--fontsize-m); font-weight: 700;} + +h2, p, ul, ol {margin-bottom: var(--space-below-01);} + +a {color: var(--color-01); text-decoration: underline;} +a:hover {text-decoration: none;outline: 0;} +ul {list-style: none;} +img {border: none;max-width: 100%;height: auto;} +strong {font-weight: 700;} +hr {height: 1px;border: none;width: 100%;background: var(--color-01);margin-bottom: var(--space-below-00);} +table {border-collapse: collapse;border-spacing: 0;} + +header ul {display: none;} /* prevent output of menu-lists before the are rendered in /theme/js/presets.js */ +header {position: relative;padding: var(--space-below-02) var(--side-space-02) var(--space-below-01) var(--side-space-02) ;} +header img[alt=Logo] {width: 64px;display: block;float: left;margin-right: 10px;} +header > p > a {color: var(--colorblack);display: block;text-decoration: none;font-size: var(--fontsize-xl);line-height: var(--lineheight-xxl);font-weight: 500;} +header p {margin-bottom: 0;color: var(--colorblack);} + +header #lang {position: absolute;top: var(--space-below-02);right: var(--side-space-02);display: flex;gap: 10px;} +header #lang ul {display: block;text-align: right;} +header #lang li a {color: var(--color-01);font-size: var(--fontsize-s);font-weight: 600;text-decoration: none;text-transform: uppercase;} +header #lang li a:hover {text-decoration: underline;} +header #lang li a.active {color: var(--colorblack);} + +header #main-nav {clear: both;padding-top: var(--space-below-01);} +header #main-nav ul {display: flex;background: var(--color-01);margin-bottom: 0;} +header #main-nav ul a {display: block;font-size: var(--fontsize-m);font-weight: 700;color: var(--colorlight); text-decoration: none;padding: 0 var(--side-space-01);border-bottom: 2px solid var(--color-01);border-top: 2px solid var(--color-01);} +header #main-nav ul a:is(:hover, :focus, .active) {background: var(--colorlight);color: var(--color-01); outline: 0;} + +#toggle-nav {display: none;} + +main {padding: 0 var(--side-space-02) var(--space-below-01) var(--side-space-02);margin-bottom: var(--space-below-00);} +/* main p:has(img) {float: right;margin: 0 0 var(--space-below-01) var(--space-below-01);} */ + +main > h1 {background: var(--color-02);color: var(--colorlight);padding: var(--space-below-00) var(--side-space-01);text-align: right;} +main ul {list-style: disc;padding-left: 40px;} +main li {margin-bottom: var(--space-below-01);} +main a::before {content: '[';} +main a::after {content: ']';} +main table {width: 100%;} +main table th, +main table td {text-align: left;border: none;min-width: 100px;vertical-align: top;padding-bottom: var(--space-below-01);} +main table tr > td:first-child {font-weight: 600;} + +main .columns {display: grid;grid-template-rows: auto;gap: var(--gap-01);box-sizing: border-box;} +main .columns p:empty {display: none;} +main .columns, +main .columns.c-50-50 {grid-template-columns: 1fr 1fr;} +main .columns.c-33-66 {grid-template-columns: 1fr 2fr;} +main .columns.c-66-33 {grid-template-columns: 2fr 1fr;} +main .columns.c-33-33-33 {grid-template-columns: 1fr 1fr 1fr;} + +main .bg-color {padding: var(--space-below-01) var(--side-space-01);background:var(--color-02);margin-bottom: var(--space-below-01);} +main .bg-color * {color:var(--colorlight);} + +main .img-rounded {aspect-ratio: 1/1;border-radius: 50%;border: var(--space-below-01) solid var(--colorlight);overflow: hidden;margin: calc(0px - var(--space-below-02)) 0;} +main .img-rounded img {object-fit: cover;width: 100%;height: 100%;display: block;} + +footer {display: flex;justify-content: space-between;} +footer * {margin-bottom: 0;color: var(--colorlight);font-weight: 600;} +footer ul {display: flex;gap: var(--gap-01);} +footer a {text-decoration: none;} +footer a:is(:hover, :focus) {text-decoration: underline;} + +code {background: var(--color-01);padding: 3px;color: var(--colorlight);font-style: italic;font-weight: 600;} + +@media screen and (max-width: 781px) { + + #wrapper {margin: 0;} + + header {padding-bottom: var(--space-below-00);} + header #lang {position: static;} + header > p > a {font-size: var(--fontsize-xl);} + header p {border-bottom: 2px solid var(--color-01);margin-bottom: var(--space-below-00);padding-bottom: var(--space-below-01);} + + #toggle-nav {cursor: pointer;position: relative;display: block;border: none;background: none;padding: 0 0 0 4px;} + html[lang="de"] #toggle-nav::after {content: 'Menü';} + html[lang="en"] #toggle-nav::after {content: 'Menu';} + #toggle-nav::after {content: '';position: absolute;left: 50px;top: 5px;font-size: var(--fontsize-l);color: var(--color-01);} + #toggle-nav span {background: var(--color-01);display: block;width: 36px;height: 4px;margin: 6px 0;transition: var(--animation-01);} + header.menu-active #toggle-nav span {opacity: 0;transform-origin: 4px center;} + header.menu-active #toggle-nav span:first-child {opacity: 1;transform: rotate(45deg);} + header.menu-active #toggle-nav span:last-child {opacity: 1;transform: rotate(-45deg);} + + header #main-nav ul {position: static;margin-bottom: 0;width:100%;} + header #main-nav ul li {overflow: hidden;height: 0;transition: var(--animation-01);} + header.menu-active #main-nav ul li {height: 30px!important;} + + main {padding-left: 0;} + main p:has(img) {float: none;margin: 0 0 var(--space-below-01) 0;} + + footer {display: block;} + footer ul {display: block;padding-top: var(--space-below-01);} + +} + + + diff --git a/theme/fonts/opensans/OpenSans-Bold.ttf b/theme/fonts/opensans/OpenSans-Bold.ttf new file mode 100644 index 0000000..fd79d43 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-Bold.ttf differ diff --git a/theme/fonts/opensans/OpenSans-Bold.woff b/theme/fonts/opensans/OpenSans-Bold.woff new file mode 100644 index 0000000..b1a36d0 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-Bold.woff differ diff --git a/theme/fonts/opensans/OpenSans-Bold.woff2 b/theme/fonts/opensans/OpenSans-Bold.woff2 new file mode 100644 index 0000000..9e68ce8 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-Bold.woff2 differ diff --git a/theme/fonts/opensans/OpenSans-BoldItalic.ttf b/theme/fonts/opensans/OpenSans-BoldItalic.ttf new file mode 100644 index 0000000..9bc8009 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-BoldItalic.ttf differ diff --git a/theme/fonts/opensans/OpenSans-BoldItalic.woff b/theme/fonts/opensans/OpenSans-BoldItalic.woff new file mode 100644 index 0000000..51ebfeb Binary files /dev/null and b/theme/fonts/opensans/OpenSans-BoldItalic.woff differ diff --git a/theme/fonts/opensans/OpenSans-BoldItalic.woff2 b/theme/fonts/opensans/OpenSans-BoldItalic.woff2 new file mode 100644 index 0000000..c3a3464 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-BoldItalic.woff2 differ diff --git a/theme/fonts/opensans/OpenSans-ExtraBold.ttf b/theme/fonts/opensans/OpenSans-ExtraBold.ttf new file mode 100644 index 0000000..21f6f84 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-ExtraBold.ttf differ diff --git a/theme/fonts/opensans/OpenSans-ExtraBold.woff2 b/theme/fonts/opensans/OpenSans-ExtraBold.woff2 new file mode 100644 index 0000000..09b3b7b Binary files /dev/null and b/theme/fonts/opensans/OpenSans-ExtraBold.woff2 differ diff --git a/theme/fonts/opensans/OpenSans-ExtraBoldItalic.ttf b/theme/fonts/opensans/OpenSans-ExtraBoldItalic.ttf new file mode 100644 index 0000000..31cb688 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-ExtraBoldItalic.ttf differ diff --git a/theme/fonts/opensans/OpenSans-ExtraBoldItalic.woff2 b/theme/fonts/opensans/OpenSans-ExtraBoldItalic.woff2 new file mode 100644 index 0000000..cdb6116 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-ExtraBoldItalic.woff2 differ diff --git a/theme/fonts/opensans/OpenSans-Extrabold.woff b/theme/fonts/opensans/OpenSans-Extrabold.woff new file mode 100644 index 0000000..6dbf753 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-Extrabold.woff differ diff --git a/theme/fonts/opensans/OpenSans-ExtraboldItalic.woff b/theme/fonts/opensans/OpenSans-ExtraboldItalic.woff new file mode 100644 index 0000000..f4f6acb Binary files /dev/null and b/theme/fonts/opensans/OpenSans-ExtraboldItalic.woff differ diff --git a/theme/fonts/opensans/OpenSans-Italic.ttf b/theme/fonts/opensans/OpenSans-Italic.ttf new file mode 100644 index 0000000..c90da48 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-Italic.ttf differ diff --git a/theme/fonts/opensans/OpenSans-Italic.woff b/theme/fonts/opensans/OpenSans-Italic.woff new file mode 100644 index 0000000..1848ebe Binary files /dev/null and b/theme/fonts/opensans/OpenSans-Italic.woff differ diff --git a/theme/fonts/opensans/OpenSans-Italic.woff2 b/theme/fonts/opensans/OpenSans-Italic.woff2 new file mode 100644 index 0000000..6dc0df6 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-Italic.woff2 differ diff --git a/theme/fonts/opensans/OpenSans-Light.ttf b/theme/fonts/opensans/OpenSans-Light.ttf new file mode 100644 index 0000000..0d38189 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-Light.ttf differ diff --git a/theme/fonts/opensans/OpenSans-Light.woff b/theme/fonts/opensans/OpenSans-Light.woff new file mode 100644 index 0000000..7b8e112 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-Light.woff differ diff --git a/theme/fonts/opensans/OpenSans-Light.woff2 b/theme/fonts/opensans/OpenSans-Light.woff2 new file mode 100644 index 0000000..5a07e82 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-Light.woff2 differ diff --git a/theme/fonts/opensans/OpenSans-LightItalic.ttf b/theme/fonts/opensans/OpenSans-LightItalic.ttf new file mode 100644 index 0000000..68299c4 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-LightItalic.ttf differ diff --git a/theme/fonts/opensans/OpenSans-LightItalic.woff b/theme/fonts/opensans/OpenSans-LightItalic.woff new file mode 100644 index 0000000..baf918b Binary files /dev/null and b/theme/fonts/opensans/OpenSans-LightItalic.woff differ diff --git a/theme/fonts/opensans/OpenSans-LightItalic.woff2 b/theme/fonts/opensans/OpenSans-LightItalic.woff2 new file mode 100644 index 0000000..d978f58 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-LightItalic.woff2 differ diff --git a/theme/fonts/opensans/OpenSans-Regular.ttf b/theme/fonts/opensans/OpenSans-Regular.ttf new file mode 100644 index 0000000..db43334 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-Regular.ttf differ diff --git a/theme/fonts/opensans/OpenSans-Regular.woff b/theme/fonts/opensans/OpenSans-Regular.woff new file mode 100644 index 0000000..e1b3143 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-Regular.woff differ diff --git a/theme/fonts/opensans/OpenSans-Regular.woff2 b/theme/fonts/opensans/OpenSans-Regular.woff2 new file mode 100644 index 0000000..d2483e9 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-Regular.woff2 differ diff --git a/theme/fonts/opensans/OpenSans-Semibold.ttf b/theme/fonts/opensans/OpenSans-Semibold.ttf new file mode 100644 index 0000000..1a7679e Binary files /dev/null and b/theme/fonts/opensans/OpenSans-Semibold.ttf differ diff --git a/theme/fonts/opensans/OpenSans-Semibold.woff b/theme/fonts/opensans/OpenSans-Semibold.woff new file mode 100644 index 0000000..b0443d5 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-Semibold.woff differ diff --git a/theme/fonts/opensans/OpenSans-Semibold.woff2 b/theme/fonts/opensans/OpenSans-Semibold.woff2 new file mode 100644 index 0000000..0c9638e Binary files /dev/null and b/theme/fonts/opensans/OpenSans-Semibold.woff2 differ diff --git a/theme/fonts/opensans/OpenSans-SemiboldItalic.ttf b/theme/fonts/opensans/OpenSans-SemiboldItalic.ttf new file mode 100644 index 0000000..59b6d16 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-SemiboldItalic.ttf differ diff --git a/theme/fonts/opensans/OpenSans-SemiboldItalic.woff b/theme/fonts/opensans/OpenSans-SemiboldItalic.woff new file mode 100644 index 0000000..027a357 Binary files /dev/null and b/theme/fonts/opensans/OpenSans-SemiboldItalic.woff differ diff --git a/theme/fonts/opensans/OpenSans-SemiboldItalic.woff2 b/theme/fonts/opensans/OpenSans-SemiboldItalic.woff2 new file mode 100644 index 0000000..1616d1a Binary files /dev/null and b/theme/fonts/opensans/OpenSans-SemiboldItalic.woff2 differ diff --git a/theme/img/logo.gif b/theme/img/logo.gif new file mode 100644 index 0000000..0f3b093 Binary files /dev/null and b/theme/img/logo.gif differ diff --git a/theme/js/presets.js b/theme/js/presets.js new file mode 100644 index 0000000..0ff7b9e --- /dev/null +++ b/theme/js/presets.js @@ -0,0 +1,86 @@ +document.addEventListener("DOMContentLoaded", function() { + +// generate toggle-button and nav-wrapper for main-menu + document.querySelector('header').insertAdjacentHTML('beforeend', ' '); + document.querySelector('header').insertAdjacentHTML('beforeend', ''); + +// menu & toggle-button variables + const modHeader = document.querySelector("header"); + const toggleNav = document.querySelector("#toggle-nav"); + const mainNav = document.querySelector("#main-nav"); + +// move main-menu into nav-wrapper + mainNav.appendChild(document.querySelector('header > ul:not(#lang)')); + +// detect screensize + function updateARIA() { + if (window.matchMedia("(max-width: 781px)").matches) { + // small screens, close menu + toggleNav.removeAttribute("aria-hidden"); + toggleNav.setAttribute("aria-controls", "main-nav"); + toggleNav.setAttribute("aria-expanded", "false"); + mainNav.setAttribute("aria-hidden", "true"); + mainNav.setAttribute("inert", ""); // Disable interaction + } else { + // large screens, open menu + toggleNav.setAttribute("aria-hidden", "true"); + toggleNav.removeAttribute("aria-controls"); + toggleNav.removeAttribute("aria-expanded"); + mainNav.removeAttribute("aria-hidden"); + mainNav.removeAttribute("inert"); // Enable interaction + } + } + // Run once on page load + updateARIA(); + // Run again on window resize (for responsiveness) + window.addEventListener("resize", updateARIA); + +// toggle menu + toggleNav.addEventListener("click", function() { + if (modHeader.classList.contains("menu-active")) { + // close menu + modHeader.classList.remove("menu-active"); + toggleNav.setAttribute("aria-expanded", "false"); + mainNav.setAttribute("aria-hidden", "true"); + mainNav.setAttribute("inert", ""); // Disable interaction + } else { + // open menu + modHeader.classList.add("menu-active"); + toggleNav.setAttribute("aria-expanded", "true"); + mainNav.removeAttribute("aria-hidden", "true"); + mainNav.removeAttribute("inert"); // Enable interaction + } + }); + +// highlight active language in language-switch menu + const langSwitch = document.querySelectorAll('#lang a'); + const currentLang = document.documentElement.lang; // Get the lang attribute from the element + + langSwitch.forEach(link => { + // Check if the href matches the current lang + if (link.getAttribute('href').includes(currentLang)) { + link.classList.add('active'); // Add the active class to the correct link + } + }); + +// highlight active page in menu + const currentUrl = window.location.href; + const currentPath = window.location.pathname; + const menuLinks = document.querySelectorAll('header :not(#lang) ul a'); + menuLinks.forEach((link) => { + // Check if the href matches the current URL or pathname + if (link.href === currentUrl || link.pathname === currentPath) { + link.classList.add('active'); + } + // Additional check for the Home link + if (currentPath === '/' && link.getAttribute('href') === '/') { + link.classList.add('active'); + } + }); + +// open all links inside main in a new window + document.querySelectorAll('#main a').forEach((link) => { + link.setAttribute('target', '_blank'); + }); + +}); \ No newline at end of file diff --git a/theme/libs/Parsedown.php b/theme/libs/Parsedown.php new file mode 100644 index 0000000..1b9d6d5 --- /dev/null +++ b/theme/libs/Parsedown.php @@ -0,0 +1,1712 @@ +DefinitionData = array(); + + # standardize line breaks + $text = str_replace(array("\r\n", "\r"), "\n", $text); + + # remove surrounding line breaks + $text = trim($text, "\n"); + + # split text into lines + $lines = explode("\n", $text); + + # iterate through lines to identify blocks + $markup = $this->lines($lines); + + # trim line breaks + $markup = trim($markup, "\n"); + + return $markup; + } + + # + # Setters + # + + function setBreaksEnabled($breaksEnabled) + { + $this->breaksEnabled = $breaksEnabled; + + return $this; + } + + protected $breaksEnabled; + + function setMarkupEscaped($markupEscaped) + { + $this->markupEscaped = $markupEscaped; + + return $this; + } + + protected $markupEscaped; + + function setUrlsLinked($urlsLinked) + { + $this->urlsLinked = $urlsLinked; + + return $this; + } + + protected $urlsLinked = true; + + function setSafeMode($safeMode) + { + $this->safeMode = (bool) $safeMode; + + return $this; + } + + protected $safeMode; + + protected $safeLinksWhitelist = array( + 'http://', + 'https://', + 'ftp://', + 'ftps://', + 'mailto:', + 'data:image/png;base64,', + 'data:image/gif;base64,', + 'data:image/jpeg;base64,', + 'irc:', + 'ircs:', + 'git:', + 'ssh:', + 'news:', + 'steam:', + ); + + # + # Lines + # + + protected $BlockTypes = array( + '#' => array('Header'), + '*' => array('Rule', 'List'), + '+' => array('List'), + '-' => array('SetextHeader', 'Table', 'Rule', 'List'), + '0' => array('List'), + '1' => array('List'), + '2' => array('List'), + '3' => array('List'), + '4' => array('List'), + '5' => array('List'), + '6' => array('List'), + '7' => array('List'), + '8' => array('List'), + '9' => array('List'), + ':' => array('Table'), + '<' => array('Comment', 'Markup'), + '=' => array('SetextHeader'), + '>' => array('Quote'), + '[' => array('Reference'), + '_' => array('Rule'), + '`' => array('FencedCode'), + '|' => array('Table'), + '~' => array('FencedCode'), + ); + + # ~ + + protected $unmarkedBlockTypes = array( + 'Code', + ); + + # + # Blocks + # + + protected function lines(array $lines) + { + $CurrentBlock = null; + + foreach ($lines as $line) + { + if (chop($line) === '') + { + if (isset($CurrentBlock)) + { + $CurrentBlock['interrupted'] = true; + } + + continue; + } + + if (strpos($line, "\t") !== false) + { + $parts = explode("\t", $line); + + $line = $parts[0]; + + unset($parts[0]); + + foreach ($parts as $part) + { + $shortage = 4 - mb_strlen($line, 'utf-8') % 4; + + $line .= str_repeat(' ', $shortage); + $line .= $part; + } + } + + $indent = 0; + + while (isset($line[$indent]) and $line[$indent] === ' ') + { + $indent ++; + } + + $text = $indent > 0 ? substr($line, $indent) : $line; + + # ~ + + $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); + + # ~ + + if (isset($CurrentBlock['continuable'])) + { + $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock); + + if (isset($Block)) + { + $CurrentBlock = $Block; + + continue; + } + else + { + if ($this->isBlockCompletable($CurrentBlock['type'])) + { + $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); + } + } + } + + # ~ + + $marker = $text[0]; + + # ~ + + $blockTypes = $this->unmarkedBlockTypes; + + if (isset($this->BlockTypes[$marker])) + { + foreach ($this->BlockTypes[$marker] as $blockType) + { + $blockTypes []= $blockType; + } + } + + # + # ~ + + foreach ($blockTypes as $blockType) + { + $Block = $this->{'block'.$blockType}($Line, $CurrentBlock); + + if (isset($Block)) + { + $Block['type'] = $blockType; + + if ( ! isset($Block['identified'])) + { + $Blocks []= $CurrentBlock; + + $Block['identified'] = true; + } + + if ($this->isBlockContinuable($blockType)) + { + $Block['continuable'] = true; + } + + $CurrentBlock = $Block; + + continue 2; + } + } + + # ~ + + if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted'])) + { + $CurrentBlock['element']['text'] .= "\n".$text; + } + else + { + $Blocks []= $CurrentBlock; + + $CurrentBlock = $this->paragraph($Line); + + $CurrentBlock['identified'] = true; + } + } + + # ~ + + if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) + { + $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); + } + + # ~ + + $Blocks []= $CurrentBlock; + + unset($Blocks[0]); + + # ~ + + $markup = ''; + + foreach ($Blocks as $Block) + { + if (isset($Block['hidden'])) + { + continue; + } + + $markup .= "\n"; + $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']); + } + + $markup .= "\n"; + + # ~ + + return $markup; + } + + protected function isBlockContinuable($Type) + { + return method_exists($this, 'block'.$Type.'Continue'); + } + + protected function isBlockCompletable($Type) + { + return method_exists($this, 'block'.$Type.'Complete'); + } + + # + # Code + + protected function blockCode($Line, $Block = null) + { + if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted'])) + { + return; + } + + if ($Line['indent'] >= 4) + { + $text = substr($Line['body'], 4); + + $Block = array( + 'element' => array( + 'name' => 'pre', + 'handler' => 'element', + 'text' => array( + 'name' => 'code', + 'text' => $text, + ), + ), + ); + + return $Block; + } + } + + protected function blockCodeContinue($Line, $Block) + { + if ($Line['indent'] >= 4) + { + if (isset($Block['interrupted'])) + { + $Block['element']['text']['text'] .= "\n"; + + unset($Block['interrupted']); + } + + $Block['element']['text']['text'] .= "\n"; + + $text = substr($Line['body'], 4); + + $Block['element']['text']['text'] .= $text; + + return $Block; + } + } + + protected function blockCodeComplete($Block) + { + $text = $Block['element']['text']['text']; + + $Block['element']['text']['text'] = $text; + + return $Block; + } + + # + # Comment + + protected function blockComment($Line) + { + if ($this->markupEscaped or $this->safeMode) + { + return; + } + + if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!') + { + $Block = array( + 'markup' => $Line['body'], + ); + + if (preg_match('/-->$/', $Line['text'])) + { + $Block['closed'] = true; + } + + return $Block; + } + } + + protected function blockCommentContinue($Line, array $Block) + { + if (isset($Block['closed'])) + { + return; + } + + $Block['markup'] .= "\n" . $Line['body']; + + if (preg_match('/-->$/', $Line['text'])) + { + $Block['closed'] = true; + } + + return $Block; + } + + # + # Fenced Code + + protected function blockFencedCode($Line) + { + if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches)) + { + $Element = array( + 'name' => 'code', + 'text' => '', + ); + + if (isset($matches[1])) + { + /** + * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes + * Every HTML element may have a class attribute specified. + * The attribute, if specified, must have a value that is a set + * of space-separated tokens representing the various classes + * that the element belongs to. + * [...] + * The space characters, for the purposes of this specification, + * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab), + * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and + * U+000D CARRIAGE RETURN (CR). + */ + $language = substr($matches[1], 0, strcspn($matches[1], " \t\n\f\r")); + + $class = 'language-'.$language; + + $Element['attributes'] = array( + 'class' => $class, + ); + } + + $Block = array( + 'char' => $Line['text'][0], + 'element' => array( + 'name' => 'pre', + 'handler' => 'element', + 'text' => $Element, + ), + ); + + return $Block; + } + } + + protected function blockFencedCodeContinue($Line, $Block) + { + if (isset($Block['complete'])) + { + return; + } + + if (isset($Block['interrupted'])) + { + $Block['element']['text']['text'] .= "\n"; + + unset($Block['interrupted']); + } + + if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text'])) + { + $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1); + + $Block['complete'] = true; + + return $Block; + } + + $Block['element']['text']['text'] .= "\n".$Line['body']; + + return $Block; + } + + protected function blockFencedCodeComplete($Block) + { + $text = $Block['element']['text']['text']; + + $Block['element']['text']['text'] = $text; + + return $Block; + } + + # + # Header + + protected function blockHeader($Line) + { + if (isset($Line['text'][1])) + { + $level = 1; + + while (isset($Line['text'][$level]) and $Line['text'][$level] === '#') + { + $level ++; + } + + if ($level > 6) + { + return; + } + + $text = trim($Line['text'], '# '); + + $Block = array( + 'element' => array( + 'name' => 'h' . min(6, $level), + 'text' => $text, + 'handler' => 'line', + ), + ); + + return $Block; + } + } + + # + # List + + protected function blockList($Line) + { + list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]'); + + if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) + { + $Block = array( + 'indent' => $Line['indent'], + 'pattern' => $pattern, + 'element' => array( + 'name' => $name, + 'handler' => 'elements', + ), + ); + + if($name === 'ol') + { + $listStart = stristr($matches[0], '.', true); + + if($listStart !== '1') + { + $Block['element']['attributes'] = array('start' => $listStart); + } + } + + $Block['li'] = array( + 'name' => 'li', + 'handler' => 'li', + 'text' => array( + $matches[2], + ), + ); + + $Block['element']['text'] []= & $Block['li']; + + return $Block; + } + } + + protected function blockListContinue($Line, array $Block) + { + if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches)) + { + if (isset($Block['interrupted'])) + { + $Block['li']['text'] []= ''; + + $Block['loose'] = true; + + unset($Block['interrupted']); + } + + unset($Block['li']); + + $text = isset($matches[1]) ? $matches[1] : ''; + + $Block['li'] = array( + 'name' => 'li', + 'handler' => 'li', + 'text' => array( + $text, + ), + ); + + $Block['element']['text'] []= & $Block['li']; + + return $Block; + } + + if ($Line['text'][0] === '[' and $this->blockReference($Line)) + { + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); + + $Block['li']['text'] []= $text; + + return $Block; + } + + if ($Line['indent'] > 0) + { + $Block['li']['text'] []= ''; + + $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); + + $Block['li']['text'] []= $text; + + unset($Block['interrupted']); + + return $Block; + } + } + + protected function blockListComplete(array $Block) + { + if (isset($Block['loose'])) + { + foreach ($Block['element']['text'] as &$li) + { + if (end($li['text']) !== '') + { + $li['text'] []= ''; + } + } + } + + return $Block; + } + + # + # Quote + + protected function blockQuote($Line) + { + if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) + { + $Block = array( + 'element' => array( + 'name' => 'blockquote', + 'handler' => 'lines', + 'text' => (array) $matches[1], + ), + ); + + return $Block; + } + } + + protected function blockQuoteContinue($Line, array $Block) + { + if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) + { + if (isset($Block['interrupted'])) + { + $Block['element']['text'] []= ''; + + unset($Block['interrupted']); + } + + $Block['element']['text'] []= $matches[1]; + + return $Block; + } + + if ( ! isset($Block['interrupted'])) + { + $Block['element']['text'] []= $Line['text']; + + return $Block; + } + } + + # + # Rule + + protected function blockRule($Line) + { + if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text'])) + { + $Block = array( + 'element' => array( + 'name' => 'hr' + ), + ); + + return $Block; + } + } + + # + # Setext + + protected function blockSetextHeader($Line, array $Block = null) + { + if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) + { + return; + } + + if (chop($Line['text'], $Line['text'][0]) === '') + { + $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; + + return $Block; + } + } + + # + # Markup + + protected function blockMarkup($Line) + { + if ($this->markupEscaped or $this->safeMode) + { + return; + } + + if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) + { + $element = strtolower($matches[1]); + + if (in_array($element, $this->textLevelElements)) + { + return; + } + + $Block = array( + 'name' => $matches[1], + 'depth' => 0, + 'markup' => $Line['text'], + ); + + $length = strlen($matches[0]); + + $remainder = substr($Line['text'], $length); + + if (trim($remainder) === '') + { + if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) + { + $Block['closed'] = true; + + $Block['void'] = true; + } + } + else + { + if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) + { + return; + } + + if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder)) + { + $Block['closed'] = true; + } + } + + return $Block; + } + } + + protected function blockMarkupContinue($Line, array $Block) + { + if (isset($Block['closed'])) + { + return; + } + + if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open + { + $Block['depth'] ++; + } + + if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close + { + if ($Block['depth'] > 0) + { + $Block['depth'] --; + } + else + { + $Block['closed'] = true; + } + } + + if (isset($Block['interrupted'])) + { + $Block['markup'] .= "\n"; + + unset($Block['interrupted']); + } + + $Block['markup'] .= "\n".$Line['body']; + + return $Block; + } + + # + # Reference + + protected function blockReference($Line) + { + if (preg_match('/^\[(.+?)\]:[ ]*?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches)) + { + $id = strtolower($matches[1]); + + $Data = array( + 'url' => $matches[2], + 'title' => null, + ); + + if (isset($matches[3])) + { + $Data['title'] = $matches[3]; + } + + $this->DefinitionData['Reference'][$id] = $Data; + + $Block = array( + 'hidden' => true, + ); + + return $Block; + } + } + + # + # Table + + protected function blockTable($Line, array $Block = null) + { + if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) + { + return; + } + + if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '') + { + $alignments = array(); + + $divider = $Line['text']; + + $divider = trim($divider); + $divider = trim($divider, '|'); + + $dividerCells = explode('|', $divider); + + foreach ($dividerCells as $dividerCell) + { + $dividerCell = trim($dividerCell); + + if ($dividerCell === '') + { + continue; + } + + $alignment = null; + + if ($dividerCell[0] === ':') + { + $alignment = 'left'; + } + + if (substr($dividerCell, - 1) === ':') + { + $alignment = $alignment === 'left' ? 'center' : 'right'; + } + + $alignments []= $alignment; + } + + # ~ + + $HeaderElements = array(); + + $header = $Block['element']['text']; + + $header = trim($header); + $header = trim($header, '|'); + + $headerCells = explode('|', $header); + + foreach ($headerCells as $index => $headerCell) + { + $headerCell = trim($headerCell); + + $HeaderElement = array( + 'name' => 'th', + 'text' => $headerCell, + 'handler' => 'line', + ); + + if (isset($alignments[$index])) + { + $alignment = $alignments[$index]; + + $HeaderElement['attributes'] = array( + 'style' => 'text-align: '.$alignment.';', + ); + } + + $HeaderElements []= $HeaderElement; + } + + # ~ + + $Block = array( + 'alignments' => $alignments, + 'identified' => true, + 'element' => array( + 'name' => 'table', + 'handler' => 'elements', + ), + ); + + $Block['element']['text'] []= array( + 'name' => 'thead', + 'handler' => 'elements', + ); + + $Block['element']['text'] []= array( + 'name' => 'tbody', + 'handler' => 'elements', + 'text' => array(), + ); + + $Block['element']['text'][0]['text'] []= array( + 'name' => 'tr', + 'handler' => 'elements', + 'text' => $HeaderElements, + ); + + return $Block; + } + } + + protected function blockTableContinue($Line, array $Block) + { + if (isset($Block['interrupted'])) + { + return; + } + + if ($Line['text'][0] === '|' or strpos($Line['text'], '|')) + { + $Elements = array(); + + $row = $Line['text']; + + $row = trim($row); + $row = trim($row, '|'); + + preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches); + + foreach ($matches[0] as $index => $cell) + { + $cell = trim($cell); + + $Element = array( + 'name' => 'td', + 'handler' => 'line', + 'text' => $cell, + ); + + if (isset($Block['alignments'][$index])) + { + $Element['attributes'] = array( + 'style' => 'text-align: '.$Block['alignments'][$index].';', + ); + } + + $Elements []= $Element; + } + + $Element = array( + 'name' => 'tr', + 'handler' => 'elements', + 'text' => $Elements, + ); + + $Block['element']['text'][1]['text'] []= $Element; + + return $Block; + } + } + + # + # ~ + # + + protected function paragraph($Line) + { + $Block = array( + 'element' => array( + 'name' => 'p', + 'text' => $Line['text'], + 'handler' => 'line', + ), + ); + + return $Block; + } + + # + # Inline Elements + # + + protected $InlineTypes = array( + '"' => array('SpecialCharacter'), + '!' => array('Image'), + '&' => array('SpecialCharacter'), + '*' => array('Emphasis'), + ':' => array('Url'), + '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'), + '>' => array('SpecialCharacter'), + '[' => array('Link'), + '_' => array('Emphasis'), + '`' => array('Code'), + '~' => array('Strikethrough'), + '\\' => array('EscapeSequence'), + ); + + # ~ + + protected $inlineMarkerList = '!"*_&[:<>`~\\'; + + # + # ~ + # + + public function line($text, $nonNestables=array()) + { + $markup = ''; + + # $excerpt is based on the first occurrence of a marker + + while ($excerpt = strpbrk($text, $this->inlineMarkerList)) + { + $marker = $excerpt[0]; + + $markerPosition = strpos($text, $marker); + + $Excerpt = array('text' => $excerpt, 'context' => $text); + + foreach ($this->InlineTypes[$marker] as $inlineType) + { + # check to see if the current inline type is nestable in the current context + + if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables)) + { + continue; + } + + $Inline = $this->{'inline'.$inlineType}($Excerpt); + + if ( ! isset($Inline)) + { + continue; + } + + # makes sure that the inline belongs to "our" marker + + if (isset($Inline['position']) and $Inline['position'] > $markerPosition) + { + continue; + } + + # sets a default inline position + + if ( ! isset($Inline['position'])) + { + $Inline['position'] = $markerPosition; + } + + # cause the new element to 'inherit' our non nestables + + foreach ($nonNestables as $non_nestable) + { + $Inline['element']['nonNestables'][] = $non_nestable; + } + + # the text that comes before the inline + $unmarkedText = substr($text, 0, $Inline['position']); + + # compile the unmarked text + $markup .= $this->unmarkedText($unmarkedText); + + # compile the inline + $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']); + + # remove the examined text + $text = substr($text, $Inline['position'] + $Inline['extent']); + + continue 2; + } + + # the marker does not belong to an inline + + $unmarkedText = substr($text, 0, $markerPosition + 1); + + $markup .= $this->unmarkedText($unmarkedText); + + $text = substr($text, $markerPosition + 1); + } + + $markup .= $this->unmarkedText($text); + + return $markup; + } + + # + # ~ + # + + protected function inlineCode($Excerpt) + { + $marker = $Excerpt['text'][0]; + + if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(? strlen($matches[0]), + 'element' => array( + 'name' => 'code', + 'text' => $text, + ), + ); + } + } + + protected function inlineEmailTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches)) + { + $url = $matches[1]; + + if ( ! isset($matches[2])) + { + $url = 'mailto:' . $url; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $matches[1], + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + protected function inlineEmphasis($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + $marker = $Excerpt['text'][0]; + + if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'strong'; + } + elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) + { + $emphasis = 'em'; + } + else + { + return; + } + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => $emphasis, + 'handler' => 'line', + 'text' => $matches[1], + ), + ); + } + + protected function inlineEscapeSequence($Excerpt) + { + if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) + { + return array( + 'markup' => $Excerpt['text'][1], + 'extent' => 2, + ); + } + } + + protected function inlineImage($Excerpt) + { + if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') + { + return; + } + + $Excerpt['text']= substr($Excerpt['text'], 1); + + $Link = $this->inlineLink($Excerpt); + + if ($Link === null) + { + return; + } + + $Inline = array( + 'extent' => $Link['extent'] + 1, + 'element' => array( + 'name' => 'img', + 'attributes' => array( + 'src' => $Link['element']['attributes']['href'], + 'alt' => $Link['element']['text'], + ), + ), + ); + + $Inline['element']['attributes'] += $Link['element']['attributes']; + + unset($Inline['element']['attributes']['href']); + + return $Inline; + } + + protected function inlineLink($Excerpt) + { + $Element = array( + 'name' => 'a', + 'handler' => 'line', + 'nonNestables' => array('Url', 'Link'), + 'text' => null, + 'attributes' => array( + 'href' => null, + 'title' => null, + ), + ); + + $extent = 0; + + $remainder = $Excerpt['text']; + + if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) + { + $Element['text'] = $matches[1]; + + $extent += strlen($matches[0]); + + $remainder = substr($remainder, $extent); + } + else + { + return; + } + + if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches)) + { + $Element['attributes']['href'] = $matches[1]; + + if (isset($matches[2])) + { + $Element['attributes']['title'] = substr($matches[2], 1, - 1); + } + + $extent += strlen($matches[0]); + } + else + { + if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) + { + $definition = strlen($matches[1]) ? $matches[1] : $Element['text']; + $definition = strtolower($definition); + + $extent += strlen($matches[0]); + } + else + { + $definition = strtolower($Element['text']); + } + + if ( ! isset($this->DefinitionData['Reference'][$definition])) + { + return; + } + + $Definition = $this->DefinitionData['Reference'][$definition]; + + $Element['attributes']['href'] = $Definition['url']; + $Element['attributes']['title'] = $Definition['title']; + } + + return array( + 'extent' => $extent, + 'element' => $Element, + ); + } + + protected function inlineMarkup($Excerpt) + { + if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false) + { + return; + } + + if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches)) + { + return array( + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) + { + return array( + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ); + } + + if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches)) + { + return array( + 'markup' => $matches[0], + 'extent' => strlen($matches[0]), + ); + } + } + + protected function inlineSpecialCharacter($Excerpt) + { + if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text'])) + { + return array( + 'markup' => '&', + 'extent' => 1, + ); + } + + $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot'); + + if (isset($SpecialCharacter[$Excerpt['text'][0]])) + { + return array( + 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';', + 'extent' => 1, + ); + } + } + + protected function inlineStrikethrough($Excerpt) + { + if ( ! isset($Excerpt['text'][1])) + { + return; + } + + if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) + { + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'del', + 'text' => $matches[1], + 'handler' => 'line', + ), + ); + } + } + + protected function inlineUrl($Excerpt) + { + if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') + { + return; + } + + if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) + { + $url = $matches[0][0]; + + $Inline = array( + 'extent' => strlen($matches[0][0]), + 'position' => $matches[0][1], + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + + return $Inline; + } + } + + protected function inlineUrlTag($Excerpt) + { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) + { + $url = $matches[1]; + + return array( + 'extent' => strlen($matches[0]), + 'element' => array( + 'name' => 'a', + 'text' => $url, + 'attributes' => array( + 'href' => $url, + ), + ), + ); + } + } + + # ~ + + protected function unmarkedText($text) + { + if ($this->breaksEnabled) + { + $text = preg_replace('/[ ]*\n/', "
\n", $text); + } + else + { + $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "
\n", $text); + $text = str_replace(" \n", "\n", $text); + } + + return $text; + } + + # + # Handlers + # + + protected function element(array $Element) + { + if ($this->safeMode) + { + $Element = $this->sanitiseElement($Element); + } + + $markup = '<'.$Element['name']; + + if (isset($Element['attributes'])) + { + foreach ($Element['attributes'] as $name => $value) + { + if ($value === null) + { + continue; + } + + $markup .= ' '.$name.'="'.self::escape($value).'"'; + } + } + + $permitRawHtml = false; + + if (isset($Element['text'])) + { + $text = $Element['text']; + } + // very strongly consider an alternative if you're writing an + // extension + elseif (isset($Element['rawHtml'])) + { + $text = $Element['rawHtml']; + $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode']; + $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode; + } + + if (isset($text)) + { + $markup .= '>'; + + if (!isset($Element['nonNestables'])) + { + $Element['nonNestables'] = array(); + } + + if (isset($Element['handler'])) + { + $markup .= $this->{$Element['handler']}($text, $Element['nonNestables']); + } + elseif (!$permitRawHtml) + { + $markup .= self::escape($text, true); + } + else + { + $markup .= $text; + } + + $markup .= ''; + } + else + { + $markup .= ' />'; + } + + return $markup; + } + + protected function elements(array $Elements) + { + $markup = ''; + + foreach ($Elements as $Element) + { + $markup .= "\n" . $this->element($Element); + } + + $markup .= "\n"; + + return $markup; + } + + # ~ + + protected function li($lines) + { + $markup = $this->lines($lines); + + $trimmedMarkup = trim($markup); + + if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '

') + { + $markup = $trimmedMarkup; + $markup = substr($markup, 3); + + $position = strpos($markup, "

"); + + $markup = substr_replace($markup, '', $position, 4); + } + + return $markup; + } + + # + # Deprecated Methods + # + + function parse($text) + { + $markup = $this->text($text); + + return $markup; + } + + protected function sanitiseElement(array $Element) + { + static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/'; + static $safeUrlNameToAtt = array( + 'a' => 'href', + 'img' => 'src', + ); + + if (isset($safeUrlNameToAtt[$Element['name']])) + { + $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]); + } + + if ( ! empty($Element['attributes'])) + { + foreach ($Element['attributes'] as $att => $val) + { + # filter out badly parsed attribute + if ( ! preg_match($goodAttribute, $att)) + { + unset($Element['attributes'][$att]); + } + # dump onevent attribute + elseif (self::striAtStart($att, 'on')) + { + unset($Element['attributes'][$att]); + } + } + } + + return $Element; + } + + protected function filterUnsafeUrlInAttribute(array $Element, $attribute) + { + foreach ($this->safeLinksWhitelist as $scheme) + { + if (self::striAtStart($Element['attributes'][$attribute], $scheme)) + { + return $Element; + } + } + + $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]); + + return $Element; + } + + # + # Static Methods + # + + protected static function escape($text, $allowQuotes = false) + { + return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8'); + } + + protected static function striAtStart($string, $needle) + { + $len = strlen($needle); + + if ($len > strlen($string)) + { + return false; + } + else + { + return strtolower(substr($string, 0, $len)) === strtolower($needle); + } + } + + static function instance($name = 'default') + { + if (isset(self::$instances[$name])) + { + return self::$instances[$name]; + } + + $instance = new static(); + + self::$instances[$name] = $instance; + + return $instance; + } + + private static $instances = array(); + + # + # Fields + # + + protected $DefinitionData; + + # + # Read-Only + + protected $specialCharacters = array( + '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', + ); + + protected $StrongRegex = array( + '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s', + '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us', + ); + + protected $EmRegex = array( + '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', + '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', + ); + + protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?'; + + protected $voidElements = array( + 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', + ); + + protected $textLevelElements = array( + 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', + 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', + 'i', 'rp', 'del', 'code', 'strike', 'marquee', + 'q', 'rt', 'ins', 'font', 'strong', + 's', 'tt', 'kbd', 'mark', + 'u', 'xm', 'sub', 'nobr', + 'sup', 'ruby', + 'var', 'span', + 'wbr', 'time', + ); +} diff --git a/theme/shortcodes.php b/theme/shortcodes.php new file mode 100644 index 0000000..a7cd734 --- /dev/null +++ b/theme/shortcodes.php @@ -0,0 +1,68 @@ +text($matches[1]); + return '
' . $langLinks . '
'; + }, $markdown); + + // Shortcode: year (current year) + $pattern = '/\{year\}/s'; + $markdown = preg_replace_callback($pattern, function () { + return date("Y"); // Return current year + }, $markdown); + + // Process columns shortcode with optional classes + $pattern = '/\{columns(.*?)\}(.*?)\{\/columns\}/s'; + $markdown = preg_replace_callback($pattern, function ($matches) use ($Parsedown) { + // Capture the class (if any) directly after {columns} + $attributes = trim($matches[1]); // Capture the class (e.g., 50-50) + $colsContent = $matches[2]; // Capture the content inside {columns} ... {/columns} + + // Add prefix to the class + $prefix = 'c-'; // Define the prefix here + $class = !empty($attributes) ? $prefix . $attributes : ''; // Prefix added to class + + // Split the columns by {columns-seperator} + $cols = preg_split('/\{columns-seperator\}/', $colsContent); + + // Wrap each column in
+ $colHtml = ''; + foreach ($cols as $col) { + $colHtml .= '
' . $Parsedown->text(trim($col)) . '
'; + } + + // Wrap everything in a
and add class if available + return '
' . $colHtml . '
'; + }, $markdown); + + // Shortcode: bg color + $pattern = '/\{bg-color\}(.*?)\{\/bg-color\}/s'; + $markdown = preg_replace_callback($pattern, function ($matches) use ($Parsedown) { + // Process Markdown + $bgColor = $Parsedown->text($matches[1]); + return '
' . $bgColor . '
'; + }, $markdown); + + // Shortcode: img rounded + $pattern = '/\{img-rounded\}(.*?)\{\/img-rounded\}/s'; + $markdown = preg_replace_callback($pattern, function ($matches) use ($Parsedown) { + // Process Markdown + $imgRounded = $Parsedown->text($matches[1]); + return '
' . $imgRounded . '
'; + }, $markdown); + + + return $markdown; + } + +?> + +