## Major CodePress CMS Enhancement
### 🚀 New Features - **League CommonMark Integration**: Replaced basic Markdown parser with full CommonMark 2.7 support - **Bootstrap Sass Architecture**: Modern SCSS build system with Bootstrap 5.3.8 - **Enhanced Navigation**: Uses filenames instead of H1 titles for consistency - **Improved Styling**: Transparent navigation backgrounds, no rounded corners ### 🎨 UI/UX Improvements - Navigation items now use formatted filenames (e.g., "kennis-boven-aantallen" → "Kennis Boven Aantallen") - Transparent navigation backgrounds with subtle hover effects - Removed rounded corners from first-level navigation - 50% opacity navigation background using Bootstrap variables ### 🔧 Technical Improvements - **Class Organization**: Extracted CodePressCMS and SimpleTemplate to separate files - **Full PHPDoc Documentation**: Complete documentation for all methods - **Modern Build Process**: npm scripts for SCSS compilation - **Enhanced Markdown Support**: Tables, strikethrough, task lists, autolinks - **Security**: Proper HTML sanitization with CommonMark ### 📦 Dependencies - Added `league/commonmark` for professional Markdown parsing - Added `bootstrap` for SCSS-based styling - Updated `sass` build process ### 🐛 Bug Fixes - Fixed content directory path configuration - Resolved navigation title inconsistencies - Improved Markdown bold/italic formatting - Fixed homepage 404 issues ### 🔄 Migration Notes - Content directory moved from `content/` to `public/content/` - Navigation now displays filenames instead of content H1 titles - CSS now compiled from SCSS source files The CMS now provides a professional, modern experience with robust Markdown support and clean, maintainable code architecture.
This commit is contained in:
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the league/commonmark package.
|
||||
*
|
||||
* (c) Colin O'Dell <colinodell@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace League\CommonMark\Extension\ExternalLink;
|
||||
|
||||
use League\CommonMark\Environment\EnvironmentBuilderInterface;
|
||||
use League\CommonMark\Event\DocumentParsedEvent;
|
||||
use League\CommonMark\Extension\ConfigurableExtensionInterface;
|
||||
use League\Config\ConfigurationBuilderInterface;
|
||||
use Nette\Schema\Expect;
|
||||
|
||||
final class ExternalLinkExtension implements ConfigurableExtensionInterface
|
||||
{
|
||||
public function configureSchema(ConfigurationBuilderInterface $builder): void
|
||||
{
|
||||
$applyOptions = [
|
||||
ExternalLinkProcessor::APPLY_NONE,
|
||||
ExternalLinkProcessor::APPLY_ALL,
|
||||
ExternalLinkProcessor::APPLY_INTERNAL,
|
||||
ExternalLinkProcessor::APPLY_EXTERNAL,
|
||||
];
|
||||
|
||||
$builder->addSchema('external_link', Expect::structure([
|
||||
'internal_hosts' => Expect::type('string|string[]'),
|
||||
'open_in_new_window' => Expect::bool(false),
|
||||
'html_class' => Expect::string()->default(''),
|
||||
'nofollow' => Expect::anyOf(...$applyOptions)->default(ExternalLinkProcessor::APPLY_NONE),
|
||||
'noopener' => Expect::anyOf(...$applyOptions)->default(ExternalLinkProcessor::APPLY_EXTERNAL),
|
||||
'noreferrer' => Expect::anyOf(...$applyOptions)->default(ExternalLinkProcessor::APPLY_EXTERNAL),
|
||||
]));
|
||||
}
|
||||
|
||||
public function register(EnvironmentBuilderInterface $environment): void
|
||||
{
|
||||
$environment->addEventListener(DocumentParsedEvent::class, new ExternalLinkProcessor($environment->getConfiguration()), -50);
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the league/commonmark package.
|
||||
*
|
||||
* (c) Colin O'Dell <colinodell@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace League\CommonMark\Extension\ExternalLink;
|
||||
|
||||
use League\CommonMark\Event\DocumentParsedEvent;
|
||||
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
|
||||
use League\Config\ConfigurationInterface;
|
||||
|
||||
final class ExternalLinkProcessor
|
||||
{
|
||||
public const APPLY_NONE = '';
|
||||
public const APPLY_ALL = 'all';
|
||||
public const APPLY_EXTERNAL = 'external';
|
||||
public const APPLY_INTERNAL = 'internal';
|
||||
|
||||
/** @psalm-readonly */
|
||||
private ConfigurationInterface $config;
|
||||
|
||||
public function __construct(ConfigurationInterface $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function __invoke(DocumentParsedEvent $e): void
|
||||
{
|
||||
$internalHosts = $this->config->get('external_link/internal_hosts');
|
||||
$openInNewWindow = $this->config->get('external_link/open_in_new_window');
|
||||
$classes = $this->config->get('external_link/html_class');
|
||||
|
||||
foreach ($e->getDocument()->iterator() as $link) {
|
||||
if (! ($link instanceof Link)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$host = \parse_url($link->getUrl(), PHP_URL_HOST);
|
||||
if (! \is_string($host)) {
|
||||
// Something is terribly wrong with this URL
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self::hostMatches($host, $internalHosts)) {
|
||||
$link->data->set('external', false);
|
||||
$this->applyRelAttribute($link, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Host does not match our list
|
||||
$this->markLinkAsExternal($link, $openInNewWindow, $classes);
|
||||
}
|
||||
}
|
||||
|
||||
private function markLinkAsExternal(Link $link, bool $openInNewWindow, string $classes): void
|
||||
{
|
||||
$link->data->set('external', true);
|
||||
$this->applyRelAttribute($link, true);
|
||||
|
||||
if ($openInNewWindow) {
|
||||
$link->data->set('attributes/target', '_blank');
|
||||
}
|
||||
|
||||
if ($classes !== '') {
|
||||
$link->data->append('attributes/class', $classes);
|
||||
}
|
||||
}
|
||||
|
||||
private function applyRelAttribute(Link $link, bool $isExternal): void
|
||||
{
|
||||
$options = [
|
||||
'nofollow' => $this->config->get('external_link/nofollow'),
|
||||
'noopener' => $this->config->get('external_link/noopener'),
|
||||
'noreferrer' => $this->config->get('external_link/noreferrer'),
|
||||
];
|
||||
|
||||
foreach ($options as $type => $option) {
|
||||
switch (true) {
|
||||
case $option === self::APPLY_ALL:
|
||||
case $isExternal && $option === self::APPLY_EXTERNAL:
|
||||
case ! $isExternal && $option === self::APPLY_INTERNAL:
|
||||
$link->data->append('attributes/rel', $type);
|
||||
}
|
||||
}
|
||||
|
||||
// No rel attributes? Mark the attribute as 'false' so LinkRenderer doesn't add defaults
|
||||
if (! $link->data->has('attributes/rel')) {
|
||||
$link->data->set('attributes/rel', false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal This method is only public so we can easily test it. DO NOT USE THIS OUTSIDE OF THIS EXTENSION!
|
||||
*
|
||||
* @param non-empty-string|list<non-empty-string> $compareTo
|
||||
*/
|
||||
public static function hostMatches(string $host, $compareTo): bool
|
||||
{
|
||||
foreach ((array) $compareTo as $c) {
|
||||
if (\strpos($c, '/') === 0) {
|
||||
if (\preg_match($c, $host)) {
|
||||
return true;
|
||||
}
|
||||
} elseif ($c === $host) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user