Refactor frontend code to allow for extension of assets

- Simpler class naming:
    Frontend\CompilerFactory → Frontend\Assets
    Frontend\HtmlDocumentFactory → Frontend\Frontend
    Frontend\HtmlDocument → Frontend\Document

- Remove AssetInterface and simply collect callbacks in Frontend\Assets
  instead

- Remove ContentInterface because it serves no purpose (never type-
  hinted or type-checked)

- Commit and add asset URLs to the Document via a content callback
  instead of in the Document factory class itself

- Add translations and locale assets to Assets separate to the assets
  factory, as non-forum/admin asset bundles probably won't want them

- Update Frontend Extender to allow the creation of new asset bundles

- Make custom LESS validation listener a standalone class instead of
  extending RecompileFrontendAssets
This commit is contained in:
Toby Zerner 2018-11-16 13:54:13 +10:30
parent 9e63f32105
commit edaca3160e
34 changed files with 519 additions and 946 deletions

View File

@ -14,6 +14,9 @@ namespace Flarum\Admin;
use Flarum\Event\ConfigureMiddleware;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Foundation\Application;
use Flarum\Frontend\AddLocaleAssets;
use Flarum\Frontend\AddTranslations;
use Flarum\Frontend\Compiler\Source\SourceCollector;
use Flarum\Frontend\RecompileFrontendAssets;
use Flarum\Http\Middleware as HttpMiddleware;
use Flarum\Http\RouteCollection;
@ -62,17 +65,31 @@ class AdminServiceProvider extends AbstractServiceProvider
$pipe->pipe(new HttpMiddleware\DispatchRoute($this->app->make('flarum.admin.routes')));
});
$this->app->bind('flarum.admin.assets', function () {
return $this->app->make('flarum.frontend.assets.defaults')('admin');
$this->app->bind('flarum.assets.admin', function () {
/** @var \Flarum\Frontend\Assets $assets */
$assets = $this->app->make('flarum.assets.factory')('admin');
$assets->js(function (SourceCollector $sources) {
$sources->addFile(__DIR__.'/../../js/dist/admin.js');
});
$assets->css(function (SourceCollector $sources) {
$sources->addFile(__DIR__.'/../../less/admin.less');
});
$this->app->make(AddTranslations::class)->forFrontend('admin')->to($assets);
$this->app->make(AddLocaleAssets::class)->to($assets);
return $assets;
});
$this->app->bind('flarum.admin.frontend', function () {
$view = $this->app->make('flarum.frontend.view.defaults')('admin');
$this->app->bind('flarum.frontend.admin', function () {
/** @var \Flarum\Frontend\Frontend $frontend */
$frontend = $this->app->make('flarum.frontend.factory')('admin');
$view->setAssets($this->app->make('flarum.admin.assets'));
$view->add($this->app->make(Content\AdminPayload::class));
$frontend->content($this->app->make(Content\AdminPayload::class));
return $view;
return $frontend;
});
}
@ -87,7 +104,7 @@ class AdminServiceProvider extends AbstractServiceProvider
$this->app->make('events')->subscribe(
new RecompileFrontendAssets(
$this->app->make('flarum.admin.assets'),
$this->app->make('flarum.assets.admin'),
$this->app->make('flarum.locales')
)
);

View File

@ -12,8 +12,7 @@
namespace Flarum\Admin\Content;
use Flarum\Extension\ExtensionManager;
use Flarum\Frontend\Content\ContentInterface;
use Flarum\Frontend\HtmlDocument;
use Flarum\Frontend\Document;
use Flarum\Group\Permission;
use Flarum\Settings\Event\Deserializing;
use Flarum\Settings\SettingsRepositoryInterface;
@ -21,7 +20,7 @@ use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\ConnectionInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
class AdminPayload implements ContentInterface
class AdminPayload
{
/**
* @var SettingsRepositoryInterface
@ -52,7 +51,7 @@ class AdminPayload implements ContentInterface
$this->events = $events;
}
public function __invoke(HtmlDocument $document, Request $request)
public function __invoke(Document $document, Request $request)
{
$settings = $this->settings->all();

View File

@ -12,9 +12,9 @@
namespace Flarum\Extend;
use Flarum\Extension\Extension;
use Flarum\Frontend\Asset\ExtensionAssets;
use Flarum\Frontend\CompilerFactory;
use Flarum\Frontend\HtmlDocumentFactory;
use Flarum\Frontend\Assets;
use Flarum\Frontend\Compiler\Source\SourceCollector;
use Flarum\Frontend\RecompileFrontendAssets;
use Flarum\Http\RouteHandlerFactory;
use Illuminate\Contracts\Container\Container;
@ -77,16 +77,42 @@ class Frontend implements ExtenderInterface
return;
}
$container->resolving(
"flarum.$this->frontend.assets",
function (CompilerFactory $assets) use ($moduleName) {
$assets->add(function () use ($moduleName) {
return new ExtensionAssets(
$moduleName, $this->css, $this->js
);
$abstract = 'flarum.assets.'.$this->frontend;
$container->resolving($abstract, function (Assets $assets) use ($moduleName) {
if ($this->js) {
$assets->js(function (SourceCollector $sources) use ($moduleName) {
$sources->addString(function () {
return 'var module={}';
});
$sources->addFile($this->js);
$sources->addString(function () use ($moduleName) {
return "flarum.extensions['$moduleName']=module.exports";
});
});
}
);
if ($this->css) {
$assets->css(function (SourceCollector $sources) {
foreach ($this->css as $path) {
$sources->addFile($path);
}
});
}
});
if (! $container->bound($abstract)) {
$container->bind($abstract, function (Container $container) {
return $container->make('flarum.assets.factory')($this->frontend);
});
$container->make('events')->subscribe(
new RecompileFrontendAssets(
$container->make($abstract),
$container->make('flarum.locales')
)
);
}
}
private function registerRoutes(Container $container)
@ -114,13 +140,13 @@ class Frontend implements ExtenderInterface
$container->resolving(
"flarum.$this->frontend.frontend",
function (HtmlDocumentFactory $view, Container $container) {
function (Frontend $frontend, Container $container) {
foreach ($this->content as $content) {
if (is_string($content)) {
$content = $container->make($content);
}
$view->add($content);
$frontend->content($content);
}
}
);

View File

@ -1,51 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Forum\Asset;
use Flarum\Frontend\Asset\AssetInterface;
use Flarum\Frontend\Compiler\Source\SourceCollector;
use Flarum\Settings\SettingsRepositoryInterface;
class CustomCss implements AssetInterface
{
/**
* @var SettingsRepositoryInterface
*/
protected $settings;
/**
* @param SettingsRepositoryInterface $settings
*/
public function __construct(SettingsRepositoryInterface $settings)
{
$this->settings = $settings;
}
public function css(SourceCollector $sources)
{
$sources->addString(function () {
return $this->settings->get('custom_less');
});
}
public function js(SourceCollector $sources)
{
}
public function localeJs(SourceCollector $sources, string $locale)
{
}
public function localeCss(SourceCollector $sources, string $locale)
{
}
}

View File

@ -1,51 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Forum\Asset;
use Flarum\Formatter\Formatter;
use Flarum\Frontend\Asset\AssetInterface;
use Flarum\Frontend\Compiler\Source\SourceCollector;
class FormatterJs implements AssetInterface
{
/**
* @var Formatter
*/
protected $formatter;
/**
* @param Formatter $formatter
*/
public function __construct(Formatter $formatter)
{
$this->formatter = $formatter;
}
public function js(SourceCollector $sources)
{
$sources->addString(function () {
return $this->formatter->getJs();
});
}
public function css(SourceCollector $sources)
{
}
public function localeJs(SourceCollector $sources, string $locale)
{
}
public function localeCss(SourceCollector $sources, string $locale)
{
}
}

View File

@ -11,16 +11,15 @@
namespace Flarum\Forum\Content;
use Flarum\Frontend\Content\ContentInterface;
use Flarum\Frontend\HtmlDocument;
use Flarum\Frontend\Document;
use Flarum\User\AssertPermissionTrait;
use Psr\Http\Message\ServerRequestInterface as Request;
class AssertRegistered implements ContentInterface
class AssertRegistered
{
use AssertPermissionTrait;
public function __invoke(HtmlDocument $document, Request $request)
public function __invoke(Document $document, Request $request)
{
$this->assertRegistered($request->getAttribute('actor'));
}

View File

@ -12,15 +12,14 @@
namespace Flarum\Forum\Content;
use Flarum\Api\Client;
use Flarum\Frontend\Content\ContentInterface;
use Flarum\Frontend\HtmlDocument;
use Flarum\Frontend\Document;
use Flarum\Http\Exception\RouteNotFoundException;
use Flarum\Http\UrlGenerator;
use Flarum\User\User;
use Illuminate\Contracts\View\Factory;
use Psr\Http\Message\ServerRequestInterface as Request;
class Discussion implements ContentInterface
class Discussion
{
/**
* @var Client
@ -49,7 +48,7 @@ class Discussion implements ContentInterface
$this->view = $view;
}
public function __invoke(HtmlDocument $document, Request $request)
public function __invoke(Document $document, Request $request)
{
$queryParams = $request->getQueryParams();
$page = max(1, array_get($queryParams, 'page'));

View File

@ -13,13 +13,12 @@ namespace Flarum\Forum\Content;
use Flarum\Api\Client;
use Flarum\Api\Controller\ListDiscussionsController;
use Flarum\Frontend\Content\ContentInterface;
use Flarum\Frontend\HtmlDocument;
use Flarum\Frontend\Document;
use Flarum\User\User;
use Illuminate\Contracts\View\Factory;
use Psr\Http\Message\ServerRequestInterface as Request;
class Index implements ContentInterface
class Index
{
/**
* @var Client
@ -41,10 +40,7 @@ class Index implements ContentInterface
$this->view = $view;
}
/**
* {@inheritdoc}
*/
public function __invoke(HtmlDocument $document, Request $request)
public function __invoke(Document $document, Request $request)
{
$queryParams = $request->getQueryParams();

View File

@ -13,8 +13,13 @@ namespace Flarum\Forum;
use Flarum\Event\ConfigureForumRoutes;
use Flarum\Event\ConfigureMiddleware;
use Flarum\Formatter\Formatter;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Foundation\Application;
use Flarum\Frontend\AddLocaleAssets;
use Flarum\Frontend\AddTranslations;
use Flarum\Frontend\Assets;
use Flarum\Frontend\Compiler\Source\SourceCollector;
use Flarum\Frontend\RecompileFrontendAssets;
use Flarum\Http\Middleware as HttpMiddleware;
use Flarum\Http\RouteCollection;
@ -66,25 +71,32 @@ class ForumServiceProvider extends AbstractServiceProvider
$pipe->pipe(new HttpMiddleware\DispatchRoute($this->app->make('flarum.forum.routes')));
});
$this->app->bind('flarum.forum.assets', function () {
$assets = $this->app->make('flarum.frontend.assets.defaults')('forum');
$this->app->bind('flarum.assets.forum', function () {
/** @var Assets $assets */
$assets = $this->app->make('flarum.assets.factory')('forum');
$assets->add(function () {
return [
$this->app->make(Asset\FormatterJs::class),
$this->app->make(Asset\CustomCss::class)
];
$assets->js(function (SourceCollector $sources) {
$sources->addFile(__DIR__.'/../../js/dist/forum.js');
$sources->addString(function () {
return $this->app->make(Formatter::class)->getJs();
});
});
$assets->css(function (SourceCollector $sources) {
$sources->addFile(__DIR__.'/../../less/forum.less');
$sources->addString(function () {
return $this->app->make(SettingsRepositoryInterface::class)->get('custom_less');
});
});
$this->app->make(AddTranslations::class)->forFrontend('forum')->to($assets);
$this->app->make(AddLocaleAssets::class)->to($assets);
return $assets;
});
$this->app->bind('flarum.forum.frontend', function () {
$view = $this->app->make('flarum.frontend.view.defaults')('forum');
$view->setAssets($this->app->make('flarum.forum.assets'));
return $view;
$this->app->bind('flarum.frontend.forum', function () {
return $this->app->make('flarum.frontend.factory')('forum');
});
}
@ -102,9 +114,18 @@ class ForumServiceProvider extends AbstractServiceProvider
'settings' => $this->app->make(SettingsRepositoryInterface::class)
]);
$this->app->make('events')->subscribe(
$events = $this->app->make('events');
$events->subscribe(
new RecompileFrontendAssets(
$this->app->make('flarum.forum.assets'),
$this->app->make('flarum.assets.forum'),
$this->app->make('flarum.locales')
)
);
$events->subscribe(
new ValidateCustomLess(
$this->app->make('flarum.assets.forum'),
$this->app->make('flarum.locales'),
$this->app
)

View File

@ -12,8 +12,7 @@
namespace Flarum\Forum;
use Flarum\Foundation\ValidationException;
use Flarum\Frontend\CompilerFactory;
use Flarum\Frontend\RecompileFrontendAssets as BaseListener;
use Flarum\Frontend\Assets;
use Flarum\Locale\LocaleManager;
use Flarum\Settings\Event\Saved;
use Flarum\Settings\Event\Saving;
@ -26,22 +25,32 @@ use League\Flysystem\Adapter\NullAdapter;
use League\Flysystem\Filesystem;
use Less_Exception_Parser;
class RecompileFrontendAssets extends BaseListener
class ValidateCustomLess
{
/**
* @var Assets
*/
protected $assets;
/**
* @var LocaleManager
*/
protected $locales;
/**
* @var Container
*/
protected $container;
/**
* @param CompilerFactory $assets
* @param Assets $assets
* @param LocaleManager $locales
* @param Container $container
*/
public function __construct(CompilerFactory $assets, LocaleManager $locales, Container $container)
public function __construct(Assets $assets, LocaleManager $locales, Container $container)
{
parent::__construct($assets, $locales);
$this->assets = $assets;
$this->locales = $locales;
$this->container = $container;
}
@ -50,9 +59,8 @@ class RecompileFrontendAssets extends BaseListener
*/
public function subscribe(Dispatcher $events)
{
parent::subscribe($events);
$events->listen(Saving::class, [$this, 'whenSettingsSaving']);
$events->listen(Saved::class, [$this, 'whenSettingsSaved']);
}
/**
@ -101,10 +109,12 @@ class RecompileFrontendAssets extends BaseListener
*/
public function whenSettingsSaved(Saved $event)
{
parent::whenSettingsSaved($event);
if (isset($event->settings['custom_less'])) {
$this->flushCss();
$this->assets->makeCss()->flush();
foreach ($this->locales->getLocales() as $locale => $name) {
$this->assets->makeLocaleCss($locale)->flush();
}
}
}
}

View File

@ -0,0 +1,46 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Frontend;
use Flarum\Frontend\Compiler\Source\SourceCollector;
use Flarum\Locale\LocaleManager;
class AddLocaleAssets
{
/**
* @var LocaleManager
*/
protected $locales;
/**
* @param LocaleManager $locales
*/
public function __construct(LocaleManager $locales)
{
$this->locales = $locales;
}
public function to(Assets $assets)
{
$assets->localeJs(function (SourceCollector $sources, string $locale) {
foreach ($this->locales->getJsFiles($locale) as $file) {
$sources->addFile($file);
}
});
$assets->localeCss(function (SourceCollector $sources, string $locale) {
foreach ($this->locales->getCssFiles($locale) as $file) {
$sources->addFile($file);
}
});
}
}

View File

@ -0,0 +1,64 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Frontend;
use Flarum\Frontend\Compiler\Source\SourceCollector;
use Flarum\Locale\LocaleManager;
class AddTranslations
{
/**
* @var LocaleManager
*/
protected $locales;
/**
* @var callable
*/
protected $filter;
public function __construct(LocaleManager $locales, callable $filter = null)
{
$this->locales = $locales;
$this->filter = $filter;
}
public function forFrontend(string $name)
{
$this->filter = function (string $id) use ($name) {
return preg_match('/^.+(?:\.|::)(?:'.$name.'|lib)\./', $id);
};
return $this;
}
public function to(Assets $assets)
{
$assets->localeJs(function (SourceCollector $sources, string $locale) {
$sources->addString(function () use ($locale) {
$translations = $this->getTranslations($locale);
return 'flarum.core.app.translator.addTranslations('.json_encode($translations).')';
});
});
}
private function getTranslations(string $locale)
{
$translations = $this->locales->getTranslator()->getCatalogue($locale)->all('messages');
return array_only(
$translations,
array_filter(array_keys($translations), $this->filter)
);
}
}

View File

@ -1,39 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Frontend\Asset;
use Flarum\Frontend\Compiler\Source\SourceCollector;
interface AssetInterface
{
/**
* @param SourceCollector $sources
*/
public function js(SourceCollector $sources);
/**
* @param SourceCollector $sources
*/
public function css(SourceCollector $sources);
/**
* @param SourceCollector $sources
* @param string $locale
*/
public function localeJs(SourceCollector $sources, string $locale);
/**
* @param SourceCollector $sources
* @param string $locale
*/
public function localeCss(SourceCollector $sources, string $locale);
}

View File

@ -1,48 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Frontend\Asset;
use Flarum\Frontend\Compiler\Source\SourceCollector;
class CoreAssets implements AssetInterface
{
/**
* @var string
*/
protected $name;
/**
* @param string $name
*/
public function __construct(string $name)
{
$this->name = $name;
}
public function js(SourceCollector $sources)
{
$sources->addFile(__DIR__."/../../../js/dist/$this->name.js");
}
public function css(SourceCollector $sources)
{
$sources->addFile(__DIR__."/../../../less/$this->name.less");
}
public function localeJs(SourceCollector $sources, string $locale)
{
}
public function localeCss(SourceCollector $sources, string $locale)
{
}
}

View File

@ -1,82 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Frontend\Asset;
use Flarum\Frontend\Compiler\Source\SourceCollector;
class ExtensionAssets implements AssetInterface
{
/**
* @var string
*/
protected $moduleName;
/**
* @var array
*/
protected $css;
/**
* @var string|callable|null
*/
protected $js;
/**
* @param string $moduleName
* @param array $css
* @param string|callable|null $js
*/
public function __construct(string $moduleName, array $css, $js = null)
{
$this->moduleName = $moduleName;
$this->css = $css;
$this->js = $js;
}
public function js(SourceCollector $sources)
{
if ($this->js) {
$sources->addString(function () {
return 'var module={}';
});
if (is_callable($this->js)) {
$sources->addString($this->js);
} else {
$sources->addFile($this->js);
}
$sources->addString(function () {
return "flarum.extensions['$this->moduleName']=module.exports";
});
}
}
public function css(SourceCollector $sources)
{
foreach ($this->css as $asset) {
if (is_callable($asset)) {
$sources->addString($asset);
} else {
$sources->addFile($asset);
}
}
}
public function localeJs(SourceCollector $sources, string $locale)
{
}
public function localeCss(SourceCollector $sources, string $locale)
{
}
}

View File

@ -1,65 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Frontend\Asset;
use Flarum\Frontend\Compiler\Source\SourceCollector;
use Flarum\Settings\SettingsRepositoryInterface;
class LessVariables implements AssetInterface
{
/**
* @var SettingsRepositoryInterface
*/
protected $settings;
/**
* @param SettingsRepositoryInterface $settings
*/
public function __construct(SettingsRepositoryInterface $settings)
{
$this->settings = $settings;
}
public function css(SourceCollector $sources)
{
$this->addLessVariables($sources);
}
public function localeCss(SourceCollector $sources, string $locale)
{
$this->addLessVariables($sources);
}
private function addLessVariables(SourceCollector $compiler)
{
$vars = [
'config-primary-color' => $this->settings->get('theme_primary_color', '#000'),
'config-secondary-color' => $this->settings->get('theme_secondary_color', '#000'),
'config-dark-mode' => $this->settings->get('theme_dark_mode') ? 'true' : 'false',
'config-colored-header' => $this->settings->get('theme_colored_header') ? 'true' : 'false'
];
$compiler->addString(function () use ($vars) {
return array_reduce(array_keys($vars), function ($string, $name) use ($vars) {
return $string."@$name: {$vars[$name]};";
}, '');
});
}
public function js(SourceCollector $sources)
{
}
public function localeJs(SourceCollector $sources, string $locale)
{
}
}

View File

@ -1,53 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Frontend\Asset;
use Flarum\Frontend\Compiler\Source\SourceCollector;
use Flarum\Locale\LocaleManager;
class LocaleAssets implements AssetInterface
{
/**
* @var LocaleManager
*/
protected $locales;
/**
* @param LocaleManager $locales
*/
public function __construct(LocaleManager $locales)
{
$this->locales = $locales;
}
public function localeJs(SourceCollector $sources, string $locale)
{
foreach ($this->locales->getJsFiles($locale) as $file) {
$sources->addFile($file);
}
}
public function localeCss(SourceCollector $sources, string $locale)
{
foreach ($this->locales->getCssFiles($locale) as $file) {
$sources->addFile($file);
}
}
public function js(SourceCollector $sources)
{
}
public function css(SourceCollector $sources)
{
}
}

View File

@ -1,87 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Frontend\Asset;
use Flarum\Frontend\Compiler\Source\SourceCollector;
use Flarum\Locale\LocaleManager;
class Translations implements AssetInterface
{
/**
* @var LocaleManager
*/
protected $locales;
/**
* @var callable
*/
protected $filter;
/**
* @param LocaleManager $locales
*/
public function __construct(LocaleManager $locales)
{
$this->locales = $locales;
$this->filter = function () {
return false;
};
}
public function localeJs(SourceCollector $sources, string $locale)
{
$sources->addString(function () use ($locale) {
$translations = $this->getTranslations($locale);
return 'flarum.core.app.translator.addTranslations('.json_encode($translations).')';
});
}
private function getTranslations(string $locale)
{
$translations = $this->locales->getTranslator()->getCatalogue($locale)->all('messages');
return array_only(
$translations,
array_filter(array_keys($translations), $this->filter)
);
}
/**
* @return callable
*/
public function getFilter(): callable
{
return $this->filter;
}
/**
* @param callable $filter
*/
public function setFilter(callable $filter)
{
$this->filter = $filter;
}
public function js(SourceCollector $sources)
{
}
public function css(SourceCollector $sources)
{
}
public function localeCss(SourceCollector $sources, string $locale)
{
}
}

View File

@ -11,25 +11,34 @@
namespace Flarum\Frontend;
use Flarum\Frontend\Asset\AssetInterface;
use Flarum\Frontend\Compiler\CompilerInterface;
use Flarum\Frontend\Compiler\JsCompiler;
use Flarum\Frontend\Compiler\LessCompiler;
use Flarum\Frontend\Compiler\Source\SourceCollector;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Contracts\Filesystem\Filesystem;
/**
* A factory class for creating frontend asset compilers.
*/
class CompilerFactory
class Assets
{
/**
* @var array
*/
public $sources = [
'js' => [],
'css' => [],
'localeJs' => [],
'localeCss' => []
];
/**
* @var string
*/
protected $name;
/**
* @var FilesystemAdapter
* @var Filesystem
*/
protected $assetsDir;
@ -43,23 +52,7 @@ class CompilerFactory
*/
protected $lessImportDirs;
/**
* @var AssetInterface[]
*/
protected $assets = [];
/**
* @var callable[]
*/
protected $addCallbacks = [];
/**
* @param string $name
* @param FilesystemAdapter $assetsDir
* @param string $cacheDir
* @param array|null $lessImportDirs
*/
public function __construct(string $name, FilesystemAdapter $assetsDir, string $cacheDir = null, array $lessImportDirs = null)
public function __construct(string $name, Filesystem $assetsDir, string $cacheDir = null, array $lessImportDirs = null)
{
$this->name = $name;
$this->assetsDir = $assetsDir;
@ -67,76 +60,84 @@ class CompilerFactory
$this->lessImportDirs = $lessImportDirs;
}
/**
* @param callable $callback
*/
public function add(callable $callback)
public function js($sources)
{
$this->addCallbacks[] = $callback;
$this->addSources('js', $sources);
return $this;
}
public function css($callback)
{
$this->addSources('css', $callback);
return $this;
}
public function localeJs($callback)
{
$this->addSources('localeJs', $callback);
return $this;
}
public function localeCss($callback)
{
$this->addSources('localeCss', $callback);
return $this;
}
private function addSources($type, $callback)
{
$this->sources[$type][] = $callback;
}
private function populate(CompilerInterface $compiler, string $type, string $locale = null)
{
$compiler->addSources(function (SourceCollector $sources) use ($type, $locale) {
foreach ($this->sources[$type] as $callback) {
$callback($sources, $locale);
}
});
}
/**
* @return JsCompiler
*/
public function makeJs(): JsCompiler
{
$compiler = new JsCompiler($this->assetsDir, $this->name.'.js');
$this->addSources($compiler, function (AssetInterface $asset, SourceCollector $sources) {
$asset->js($sources);
});
$this->populate($compiler, 'js');
return $compiler;
}
/**
* @return LessCompiler
*/
public function makeCss(): LessCompiler
{
$compiler = $this->makeLessCompiler($this->name.'.css');
$this->addSources($compiler, function (AssetInterface $asset, SourceCollector $sources) {
$asset->css($sources);
});
$this->populate($compiler, 'css');
return $compiler;
}
/**
* @param string $locale
* @return JsCompiler
*/
public function makeLocaleJs(string $locale): JsCompiler
{
$compiler = new JsCompiler($this->assetsDir, $this->name.'-'.$locale.'.js');
$this->addSources($compiler, function (AssetInterface $asset, SourceCollector $sources) use ($locale) {
$asset->localeJs($sources, $locale);
});
$this->populate($compiler, 'localeJs', $locale);
return $compiler;
}
/**
* @param string $locale
* @return LessCompiler
*/
public function makeLocaleCss(string $locale): LessCompiler
{
$compiler = $this->makeLessCompiler($this->name.'-'.$locale.'.css');
$this->addSources($compiler, function (AssetInterface $asset, SourceCollector $sources) use ($locale) {
$asset->localeCss($sources, $locale);
});
$this->populate($compiler, 'localeCss', $locale);
return $compiler;
}
/**
* @param string $filename
* @return LessCompiler
*/
protected function makeLessCompiler(string $filename): LessCompiler
{
$compiler = new LessCompiler($this->assetsDir, $filename);
@ -152,86 +153,41 @@ class CompilerFactory
return $compiler;
}
protected function fireAddCallbacks()
{
foreach ($this->addCallbacks as $callback) {
$assets = $callback($this);
$this->assets = array_merge($this->assets, is_array($assets) ? $assets : [$assets]);
}
$this->addCallbacks = [];
}
private function addSources(CompilerInterface $compiler, callable $callback)
{
$compiler->addSources(function ($sources) use ($callback) {
$this->fireAddCallbacks();
foreach ($this->assets as $asset) {
$callback($asset, $sources);
}
});
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @param string $name
*/
public function setName(string $name)
{
$this->name = $name;
}
/**
* @return FilesystemAdapter
*/
public function getAssetsDir(): FilesystemAdapter
public function getAssetsDir(): Filesystem
{
return $this->assetsDir;
}
/**
* @param FilesystemAdapter $assetsDir
*/
public function setAssetsDir(FilesystemAdapter $assetsDir)
public function setAssetsDir(Filesystem $assetsDir)
{
$this->assetsDir = $assetsDir;
}
/**
* @return string
*/
public function getCacheDir(): ?string
{
return $this->cacheDir;
}
/**
* @param string $cacheDir
*/
public function setCacheDir(?string $cacheDir)
{
$this->cacheDir = $cacheDir;
}
/**
* @return array
*/
public function getLessImportDirs(): array
{
return $this->lessImportDirs;
}
/**
* @param array $lessImportDirs
*/
public function setLessImportDirs(array $lessImportDirs)
{
$this->lessImportDirs = $lessImportDirs;

View File

@ -59,7 +59,7 @@ class LessCompiler extends RevisionCompiler
}
/**
* {@inheritdoc}
* @throws \Less_Exception_Parser
*/
protected function compile(array $sources): string
{

View File

@ -13,7 +13,7 @@ namespace Flarum\Frontend\Compiler;
use Flarum\Frontend\Compiler\Source\SourceCollector;
use Flarum\Frontend\Compiler\Source\SourceInterface;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Contracts\Filesystem\Filesystem;
class RevisionCompiler implements CompilerInterface
{
@ -22,7 +22,7 @@ class RevisionCompiler implements CompilerInterface
const EMPTY_REVISION = 'empty';
/**
* @var FilesystemAdapter
* @var Filesystem
*/
protected $assetsDir;
@ -37,10 +37,10 @@ class RevisionCompiler implements CompilerInterface
protected $sourcesCallbacks = [];
/**
* @param FilesystemAdapter $assetsDir
* @param Filesystem $assetsDir
* @param string $filename
*/
public function __construct(FilesystemAdapter $assetsDir, string $filename)
public function __construct(Filesystem $assetsDir, string $filename)
{
$this->assetsDir = $assetsDir;
$this->filename = $filename;

View File

@ -0,0 +1,74 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Frontend\Content;
use Flarum\Foundation\Application;
use Flarum\Frontend\Compiler\CompilerInterface;
use Flarum\Frontend\Document;
use Psr\Http\Message\ServerRequestInterface as Request;
class Assets
{
protected $app;
/**
* @var \Flarum\Frontend\Assets
*/
protected $assets;
public function __construct(Application $app)
{
$this->app = $app;
}
public function forFrontend(string $name)
{
$this->assets = $this->app->make('flarum.assets.'.$name);
return $this;
}
public function __invoke(Document $document, Request $request)
{
$locale = $request->getAttribute('locale');
$compilers = [
'js' => [$this->assets->makeJs(), $this->assets->makeLocaleJs($locale)],
'css' => [$this->assets->makeCss(), $this->assets->makeLocaleCss($locale)]
];
if ($this->app->inDebugMode()) {
$this->commit(array_flatten($compilers));
}
$document->js = array_merge($document->js, $this->getUrls($compilers['js']));
$document->css = array_merge($document->css, $this->getUrls($compilers['css']));
}
private function commit(array $compilers)
{
foreach ($compilers as $compiler) {
$compiler->commit();
}
}
/**
* @param CompilerInterface[] $compilers
* @return string[]
*/
private function getUrls(array $compilers)
{
return array_filter(array_map(function (CompilerInterface $compiler) {
return $compiler->getUrl();
}, $compilers));
}
}

View File

@ -1,24 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Frontend\Content;
use Flarum\Frontend\HtmlDocument;
use Psr\Http\Message\ServerRequestInterface as Request;
interface ContentInterface
{
/**
* @param HtmlDocument $document
* @param Request $request
*/
public function __invoke(HtmlDocument $document, Request $request);
}

View File

@ -13,13 +13,13 @@ namespace Flarum\Frontend\Content;
use Flarum\Api\Client;
use Flarum\Api\Controller\ShowUserController;
use Flarum\Frontend\HtmlDocument;
use Flarum\Frontend\Document;
use Flarum\Locale\LocaleManager;
use Flarum\User\User;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
class CorePayload implements ContentInterface
class CorePayload
{
/**
* @var LocaleManager
@ -41,7 +41,7 @@ class CorePayload implements ContentInterface
$this->api = $api;
}
public function __invoke(HtmlDocument $document, Request $request)
public function __invoke(Document $document, Request $request)
{
$document->payload = array_merge(
$document->payload,
@ -49,7 +49,7 @@ class CorePayload implements ContentInterface
);
}
private function buildPayload(HtmlDocument $document, Request $request)
private function buildPayload(Document $document, Request $request)
{
$data = $this->getDataFromApiDocument($document->getForumApiDocument());

View File

@ -1,36 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Frontend\Content;
use Flarum\Frontend\HtmlDocument;
use Psr\Http\Message\ServerRequestInterface as Request;
class Layout implements ContentInterface
{
/**
* @var string
*/
protected $layoutView;
/**
* @param string $layoutView
*/
public function __construct(string $layoutView)
{
$this->layoutView = $layoutView;
}
public function __invoke(HtmlDocument $document, Request $request)
{
$document->layoutView = $this->layoutView;
}
}

View File

@ -11,18 +11,18 @@
namespace Flarum\Frontend\Content;
use Flarum\Frontend\HtmlDocument;
use Flarum\Frontend\Document;
use Psr\Http\Message\ServerRequestInterface as Request;
class Meta implements ContentInterface
class Meta
{
public function __invoke(HtmlDocument $document, Request $request)
public function __invoke(Document $document, Request $request)
{
$document->meta = array_merge($document->meta, $this->buildMeta($document));
$document->head = array_merge($document->head, $this->buildHead($document));
}
private function buildMeta(HtmlDocument $document)
private function buildMeta(Document $document)
{
$forumApiDocument = $document->getForumApiDocument();
@ -35,7 +35,7 @@ class Meta implements ContentInterface
return $meta;
}
private function buildHead(HtmlDocument $document)
private function buildHead(Document $document)
{
$head = [];

View File

@ -19,25 +19,19 @@ use Zend\Diactoros\Response\HtmlResponse;
class Controller implements RequestHandlerInterface
{
/**
* @var HtmlDocumentFactory
* @var Frontend
*/
protected $document;
protected $frontend;
/**
* @param HtmlDocumentFactory $document
*/
public function __construct(HtmlDocumentFactory $document)
public function __construct(Frontend $frontend)
{
$this->document = $document;
$this->frontend = $frontend;
}
/**
* {@inheritdoc}
*/
public function handle(Request $request): Response
{
return new HtmlResponse(
$this->document->make($request)->render()
$this->frontend->document($request)->render()
);
}
}

View File

@ -18,7 +18,7 @@ use Illuminate\Contracts\View\View;
/**
* A view which renders a HTML skeleton for Flarum's frontend app.
*/
class HtmlDocument implements Renderable
class Document implements Renderable
{
/**
* The title of the document, displayed in the <title> tag.

84
src/Frontend/Frontend.php Normal file
View File

@ -0,0 +1,84 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Frontend;
use Flarum\Api\Client;
use Flarum\Api\Controller\ShowForumController;
use Illuminate\Contracts\View\Factory;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class Frontend
{
/**
* @var Factory
*/
protected $view;
/**
* @var Client
*/
protected $api;
/**
* @var callable[]
*/
protected $content = [];
public function __construct(Factory $view, Client $api)
{
$this->view = $view;
$this->api = $api;
}
/**
* @param callable $content
*/
public function content(callable $content)
{
$this->content[] = $content;
}
public function document(Request $request): Document
{
$forumDocument = $this->getForumDocument($request);
$document = new Document($this->view, $forumDocument);
$this->populate($document, $request);
return $document;
}
protected function populate(Document $document, Request $request)
{
foreach ($this->content as $content) {
$content($document, $request);
}
}
private function getForumDocument(Request $request): array
{
$actor = $request->getAttribute('actor');
$this->api->setErrorHandler(null);
return $this->getResponseBody(
$this->api->send(ShowForumController::class, $actor)
);
}
private function getResponseBody(Response $response): array
{
return json_decode($response->getBody(), true);
}
}

View File

@ -12,21 +12,18 @@
namespace Flarum\Frontend;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Frontend\Compiler\Source\SourceCollector;
use Flarum\Http\UrlGenerator;
use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Contracts\View\Factory as ViewFactory;
class FrontendServiceProvider extends AbstractServiceProvider
{
public function register()
{
// Yo dawg, I heard you like factories, so I made you a factory to
// create your factory. We expose a couple of factory functions that
// will create frontend factories and configure them with some default
// settings common to both the forum and admin frontends.
$this->app->singleton('flarum.frontend.assets.defaults', function () {
$this->app->singleton('flarum.assets.factory', function () {
return function (string $name) {
$assets = new CompilerFactory(
$assets = new Assets(
$name,
$this->app->make('filesystem')->disk('flarum-assets'),
$this->app->storagePath()
@ -36,35 +33,26 @@ class FrontendServiceProvider extends AbstractServiceProvider
$this->app->basePath().'/vendor/components/font-awesome/less' => ''
]);
$assets->add(function () use ($name) {
$translations = $this->app->make(Asset\Translations::class);
$translations->setFilter(function (string $id) use ($name) {
return preg_match('/^.+(?:\.|::)(?:'.$name.'|lib)\./', $id);
});
return [
new Asset\CoreAssets($name),
$this->app->make(Asset\LessVariables::class),
$translations,
$this->app->make(Asset\LocaleAssets::class)
];
});
$assets->css([$this, 'addLessVariables']);
$assets->localeCss([$this, 'addLessVariables']);
return $assets;
};
});
$this->app->singleton('flarum.frontend.view.defaults', function () {
$this->app->singleton('flarum.frontend.factory', function () {
return function (string $name) {
$view = $this->app->make(HtmlDocumentFactory::class);
$frontend = $this->app->make(Frontend::class);
$view->setCommitAssets($this->app->inDebugMode());
$frontend->content(function (Document $document) use ($name) {
$document->layoutView = 'flarum::frontend.'.$name;
});
$view->add(new Content\Layout('flarum::frontend.'.$name));
$view->add($this->app->make(Content\CorePayload::class));
$view->add($this->app->make(Content\Meta::class));
$frontend->content($this->app->make(Content\Assets::class)->forFrontend($name));
$frontend->content($this->app->make(Content\CorePayload::class));
$frontend->content($this->app->make(Content\Meta::class));
return $view;
return $frontend;
};
});
}
@ -81,4 +69,22 @@ class FrontendServiceProvider extends AbstractServiceProvider
'url' => $this->app->make(UrlGenerator::class)
]);
}
public function addLessVariables(SourceCollector $sources)
{
$sources->addString(function () {
$settings = $this->app->make(SettingsRepositoryInterface::class);
$vars = [
'config-primary-color' => $settings->get('theme_primary_color', '#000'),
'config-secondary-color' => $settings->get('theme_secondary_color', '#000'),
'config-dark-mode' => $settings->get('theme_dark_mode') ? 'true' : 'false',
'config-colored-header' => $settings->get('theme_colored_header') ? 'true' : 'false'
];
return array_reduce(array_keys($vars), function ($string, $name) use ($vars) {
return $string."@$name: {$vars[$name]};";
}, '');
});
}
}

View File

@ -1,182 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Frontend;
use Flarum\Api\Client;
use Flarum\Api\Controller\ShowForumController;
use Flarum\Frontend\Compiler\CompilerInterface;
use Flarum\Frontend\Content\ContentInterface;
use Illuminate\Contracts\View\Factory;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class HtmlDocumentFactory
{
/**
* @var Factory
*/
protected $view;
/**
* @var Client
*/
protected $api;
/**
* @var CompilerFactory
*/
protected $assets;
/**
* @var bool
*/
protected $commitAssets;
/**
* @var ContentInterface[]
*/
protected $content = [];
/**
* @param Factory $view
* @param Client $api
* @param CompilerFactory|null $assets
* @param bool $commitAssets
*/
public function __construct(Factory $view, Client $api, CompilerFactory $assets = null, bool $commitAssets = false)
{
$this->view = $view;
$this->api = $api;
$this->assets = $assets;
$this->commitAssets = $commitAssets;
}
/**
* @param ContentInterface|callable $content
*/
public function add($content)
{
$this->content[] = $content;
}
/**
* @param Request $request
* @return HtmlDocument
*/
public function make(Request $request): HtmlDocument
{
$forumDocument = $this->getForumDocument($request);
$view = new HtmlDocument($this->view, $forumDocument);
$locale = $request->getAttribute('locale');
$js = [$this->assets->makeJs(), $this->assets->makeLocaleJs($locale)];
$css = [$this->assets->makeCss(), $this->assets->makeLocaleCss($locale)];
$this->maybeCommitAssets(array_merge($js, $css));
$view->js = array_merge($view->js, $this->getUrls($js));
$view->css = array_merge($view->css, $this->getUrls($css));
$this->populate($view, $request);
return $view;
}
/**
* @return CompilerFactory
*/
public function getAssets(): CompilerFactory
{
return $this->assets;
}
/**
* @param CompilerFactory $assets
*/
public function setAssets(CompilerFactory $assets)
{
$this->assets = $assets;
}
/**
* @param HtmlDocument $view
* @param Request $request
*/
protected function populate(HtmlDocument $view, Request $request)
{
foreach ($this->content as $content) {
$content($view, $request);
}
}
/**
* @param Request $request
* @return array
*/
private function getForumDocument(Request $request): array
{
$actor = $request->getAttribute('actor');
$this->api->setErrorHandler(null);
return $this->getResponseBody(
$this->api->send(ShowForumController::class, $actor)
);
}
/**
* @param Response $response
* @return array
*/
private function getResponseBody(Response $response)
{
return json_decode($response->getBody(), true);
}
private function maybeCommitAssets(array $compilers)
{
if ($this->commitAssets) {
foreach ($compilers as $compiler) {
$compiler->commit();
}
}
}
/**
* @param CompilerInterface[] $compilers
* @return string[]
*/
private function getUrls(array $compilers)
{
return array_filter(array_map(function (CompilerInterface $compiler) {
return $compiler->getUrl();
}, $compilers));
}
/**
* @return bool
*/
public function getCommitAssets(): bool
{
return $this->commitAssets;
}
/**
* @param bool $commitAssets
*/
public function setCommitAssets(bool $commitAssets)
{
$this->commitAssets = $commitAssets;
}
}

View File

@ -21,7 +21,7 @@ use Illuminate\Contracts\Events\Dispatcher;
class RecompileFrontendAssets
{
/**
* @var CompilerFactory
* @var Assets
*/
protected $assets;
@ -31,10 +31,10 @@ class RecompileFrontendAssets
protected $locales;
/**
* @param CompilerFactory $assets
* @param Assets $assets
* @param LocaleManager $locales
*/
public function __construct(CompilerFactory $assets, LocaleManager $locales)
public function __construct(Assets $assets, LocaleManager $locales)
{
$this->assets = $assets;
$this->locales = $locales;

View File

@ -46,7 +46,7 @@ class RouteHandlerFactory
public function toFrontend(string $frontend, string $content = null)
{
return $this->toController(function (Container $container) use ($frontend, $content) {
$frontend = $container->make("flarum.$frontend.frontend");
$frontend = $container->make("flarum.frontend.$frontend");
if ($content) {
$frontend->add($container->make($content));

View File

@ -21,7 +21,7 @@ namespace Flarum\Settings;
* Within Flarum, this can be used to test out new setting values in a system
* before they are committed to the database.
*
* @see \Flarum\Forum\RecompileFrontendAssets For an example usage.
* @see \Flarum\Forum\ValidateCustomLess For an example usage.
*/
class OverrideSettingsRepository implements SettingsRepositoryInterface
{