Started backend theme system

Allows customization of back-end components via event-driven handling
from the theme folder.
This commit is contained in:
Dan Brown 2021-03-16 17:14:03 +00:00
parent 1420f239fc
commit c61c3bc608
7 changed files with 142 additions and 0 deletions

View file

@ -115,6 +115,7 @@ return [
BookStack\Providers\TranslationServiceProvider::class, BookStack\Providers\TranslationServiceProvider::class,
// BookStack custom service providers // BookStack custom service providers
BookStack\Providers\ThemeServiceProvider::class,
BookStack\Providers\AuthServiceProvider::class, BookStack\Providers\AuthServiceProvider::class,
BookStack\Providers\AppServiceProvider::class, BookStack\Providers\AppServiceProvider::class,
BookStack\Providers\BroadcastServiceProvider::class, BookStack\Providers\BroadcastServiceProvider::class,
@ -186,6 +187,7 @@ return [
'Views' => BookStack\Facades\Views::class, 'Views' => BookStack\Facades\Views::class,
'Images' => BookStack\Facades\Images::class, 'Images' => BookStack\Facades\Images::class,
'Permissions' => BookStack\Facades\Permissions::class, 'Permissions' => BookStack\Facades\Permissions::class,
'Theme' => BookStack\Facades\Theme::class,
], ],

View file

@ -2,6 +2,8 @@
use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Page;
use BookStack\Entities\Tools\Markdown\CustomStrikeThroughExtension; use BookStack\Entities\Tools\Markdown\CustomStrikeThroughExtension;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use DOMDocument; use DOMDocument;
use DOMNodeList; use DOMNodeList;
use DOMXPath; use DOMXPath;
@ -53,6 +55,7 @@ class PageContent
$environment->addExtension(new TableExtension()); $environment->addExtension(new TableExtension());
$environment->addExtension(new TaskListExtension()); $environment->addExtension(new TaskListExtension());
$environment->addExtension(new CustomStrikeThroughExtension()); $environment->addExtension(new CustomStrikeThroughExtension());
$environment = Theme::dispatch(ThemeEvents::COMMONMARK_ENVIRONMENT_CONFIGURE, $environment) ?? $environment;
$converter = new CommonMarkConverter([], $environment); $converter = new CommonMarkConverter([], $environment);
return $converter->convertToHtml($markdown); return $converter->convertToHtml($markdown);
} }

16
app/Facades/Theme.php Normal file
View file

@ -0,0 +1,16 @@
<?php namespace BookStack\Facades;
use Illuminate\Support\Facades\Facade;
class Theme extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'theme';
}
}

View file

@ -5,6 +5,7 @@ namespace BookStack\Providers;
use BookStack\Actions\ActivityService; use BookStack\Actions\ActivityService;
use BookStack\Actions\ViewService; use BookStack\Actions\ViewService;
use BookStack\Auth\Permissions\PermissionService; use BookStack\Auth\Permissions\PermissionService;
use BookStack\Theming\ThemeService;
use BookStack\Uploads\ImageService; use BookStack\Uploads\ImageService;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
@ -42,5 +43,9 @@ class CustomFacadeProvider extends ServiceProvider
$this->app->singleton('permissions', function () { $this->app->singleton('permissions', function () {
return $this->app->make(PermissionService::class); return $this->app->make(PermissionService::class);
}); });
$this->app->singleton('theme', function () {
return $this->app->make(ThemeService::class);
});
} }
} }

View file

@ -0,0 +1,34 @@
<?php
namespace BookStack\Providers;
use BookStack\Theming\ThemeEvents;
use BookStack\Theming\ThemeService;
use Illuminate\Support\ServiceProvider;
class ThemeServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
$this->app->singleton(ThemeService::class, function ($app) {
return new ThemeService;
});
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
$themeService = $this->app->make(ThemeService::class);
$themeService->readThemeActions();
$themeService->dispatch(ThemeEvents::APP_BOOT, $this->app);
}
}

View file

@ -0,0 +1,32 @@
<?php namespace BookStack\Theming;
/**
* The ThemeEvents used within BookStack.
*
* This file details the events that BookStack may fire via the custom
* theme system, including event names, parameters and expected return types.
*
* This system is regarded as semi-stable.
* We'll look to fix issues with it or migrate old event types but
* events and their signatures may change in new versions of BookStack.
* We'd advise testing any usage of these events upon upgrade.
*/
class ThemeEvents
{
/**
* Application boot-up.
* After main services are registered.
* @param \BookStack\Application $app
*/
const APP_BOOT = 'app_boot';
/**
* Commonmark environment configure.
* Provides the commonmark library environment for customization
* before its used to render markdown content.
* If the listener returns a non-null value, that will be used as an environment instead.
* @param \League\CommonMark\ConfigurableEnvironmentInterface $environment
* @returns \League\CommonMark\ConfigurableEnvironmentInterface|null
*/
const COMMONMARK_ENVIRONMENT_CONFIGURE = 'commonmark_environment_configure';
}

View file

@ -0,0 +1,50 @@
<?php namespace BookStack\Theming;
class ThemeService
{
protected $listeners = [];
/**
* Listen to a given custom theme event,
* setting up the action to be ran when the event occurs.
*/
public function listen(string $event, callable $action)
{
if (!isset($this->listeners[$event])) {
$this->listeners[$event] = [];
}
$this->listeners[$event][] = $action;
}
/**
* Dispatch the given event name.
* Runs any registered listeners for that event name,
* passing all additional variables to the listener action.
*
* If a callback returns a non-null value, this method will
* stop and return that value itself.
* @return mixed
*/
public function dispatch(string $event, ...$args)
{
foreach ($this->listeners[$event] ?? [] as $action) {
$result = call_user_func_array($action, $args);
if (!is_null($result)) {
return $result;
}
}
return null;
}
/**
* Read any actions from the set theme path if the 'functions.php' file exists.
*/
public function readThemeActions()
{
$themeActionsFile = theme_path('functions.php');
if (file_exists($themeActionsFile)) {
require $themeActionsFile;
}
}
}