Add support for defining quick actions in the Backend's main nav (#5344)
Plugins now have the ability to define quick actions through a "registerQuickActions" method, which follows the same configuration as the "registerNavigation" method. It is still recommended and preferred that most plugin functionality be defined in their own main menu items, but this will allow a plugin to easily define a shortcut (or remove one).
This commit is contained in:
parent
f18769e282
commit
50816a9556
|
|
@ -679,9 +679,10 @@ nav#layout-mainmenu .toolbar-item:before {left:-12px}
|
|||
nav#layout-mainmenu .toolbar-item:after {right:-12px}
|
||||
nav#layout-mainmenu .toolbar-item.scroll-active-before:before {color:#fff}
|
||||
nav#layout-mainmenu .toolbar-item.scroll-active-after:after {color:#fff}
|
||||
nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-preview {margin:0 0 0 21px}
|
||||
nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-preview i {font-size:20px}
|
||||
nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-preview a {position:relative;padding:0 10px;top:-1px}
|
||||
nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-quick-action {margin:0}
|
||||
nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-quick-action:first-child {margin-left:21px}
|
||||
nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-quick-action i {font-size:20px}
|
||||
nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-quick-action a {position:relative;padding:0 10px;top:-1px}
|
||||
nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-account {margin-right:0}
|
||||
nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-account >a {padding:0 15px 0 10px;font-size:13px;position:relative}
|
||||
nav#layout-mainmenu ul.mainmenu-toolbar li.mainmenu-account.highlight >a {z-index:600}
|
||||
|
|
@ -706,8 +707,8 @@ nav#layout-mainmenu ul li .mainmenu-accountmenu li:first-child a:active:after {c
|
|||
nav#layout-mainmenu ul li .mainmenu-accountmenu li.divider {height:1px;width:100%;background-color:#e0e0e0}
|
||||
nav#layout-mainmenu.navbar-mode-inline,
|
||||
nav#layout-mainmenu.navbar-mode-inline_no_icons {height:60px}
|
||||
nav#layout-mainmenu.navbar-mode-inline ul.mainmenu-toolbar li.mainmenu-preview a,
|
||||
nav#layout-mainmenu.navbar-mode-inline_no_icons ul.mainmenu-toolbar li.mainmenu-preview a {height:60px;line-height:60px}
|
||||
nav#layout-mainmenu.navbar-mode-inline ul.mainmenu-toolbar li.mainmenu-quick-action a,
|
||||
nav#layout-mainmenu.navbar-mode-inline_no_icons ul.mainmenu-toolbar li.mainmenu-quick-action a {height:60px;line-height:60px}
|
||||
nav#layout-mainmenu.navbar-mode-inline ul.mainmenu-toolbar li.mainmenu-account >a,
|
||||
nav#layout-mainmenu.navbar-mode-inline_no_icons ul.mainmenu-toolbar li.mainmenu-account >a {height:60px;line-height:60px}
|
||||
nav#layout-mainmenu.navbar-mode-inline ul li .mainmenu-accountmenu,
|
||||
|
|
@ -730,7 +731,7 @@ nav#layout-mainmenu.navbar-mode-inline ul.mainmenu-nav li:last-child,
|
|||
nav#layout-mainmenu.navbar-mode-inline_no_icons ul.mainmenu-nav li:last-child {margin-right:0}
|
||||
nav#layout-mainmenu.navbar-mode-inline_no_icons .nav-icon {display:none !important}
|
||||
nav#layout-mainmenu.navbar-mode-tile {height:78px}
|
||||
nav#layout-mainmenu.navbar-mode-tile ul.mainmenu-toolbar li.mainmenu-preview a {height:78px;line-height:78px}
|
||||
nav#layout-mainmenu.navbar-mode-tile ul.mainmenu-toolbar li.mainmenu-quick-action a {height:78px;line-height:78px}
|
||||
nav#layout-mainmenu.navbar-mode-tile ul.mainmenu-toolbar li.mainmenu-account >a {height:78px;line-height:78px}
|
||||
nav#layout-mainmenu.navbar-mode-tile ul li .mainmenu-accountmenu {top:88px}
|
||||
nav#layout-mainmenu.navbar-mode-tile ul.mainmenu-nav li a {position:relative;width:65px;height:65px}
|
||||
|
|
@ -749,14 +750,14 @@ nav#layout-mainmenu .menu-toggle .menu-toggle-title {margin-left:10px}
|
|||
nav#layout-mainmenu .menu-toggle:hover .menu-toggle-icon {opacity:1}
|
||||
body.mainmenu-open nav#layout-mainmenu .menu-toggle-icon {opacity:1}
|
||||
nav#layout-mainmenu.navbar-mode-collapse {padding-left:0;height:45px}
|
||||
nav#layout-mainmenu.navbar-mode-collapse ul.mainmenu-toolbar li.mainmenu-preview a {height:45px;line-height:45px}
|
||||
nav#layout-mainmenu.navbar-mode-collapse ul.mainmenu-toolbar li.mainmenu-quick-action a {height:45px;line-height:45px}
|
||||
nav#layout-mainmenu.navbar-mode-collapse ul.mainmenu-toolbar li.mainmenu-account >a {height:45px;line-height:45px}
|
||||
nav#layout-mainmenu.navbar-mode-collapse ul li .mainmenu-accountmenu {top:55px}
|
||||
nav#layout-mainmenu.navbar-mode-collapse ul.mainmenu-toolbar li.mainmenu-account >a {padding-right:0}
|
||||
nav#layout-mainmenu.navbar-mode-collapse ul li .mainmenu-accountmenu:after {right:13px}
|
||||
nav#layout-mainmenu.navbar-mode-collapse ul.nav {display:none}
|
||||
nav#layout-mainmenu.navbar-mode-collapse .menu-toggle {display:inline-block;color:#fff !important}
|
||||
@media (max-width:769px) {nav#layout-mainmenu.navbar {padding-left:0;height:45px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-preview a {height:45px;line-height:45px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-account >a {height:45px;line-height:45px }nav#layout-mainmenu.navbar ul li .mainmenu-accountmenu {top:55px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-account >a {padding-right:0 }nav#layout-mainmenu.navbar ul li .mainmenu-accountmenu:after {right:13px }nav#layout-mainmenu.navbar ul.nav {display:none }nav#layout-mainmenu.navbar .menu-toggle {display:inline-block;color:#fff !important }}
|
||||
@media (max-width:769px) {nav#layout-mainmenu.navbar {padding-left:0;height:45px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-quick-action a {height:45px;line-height:45px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-account >a {height:45px;line-height:45px }nav#layout-mainmenu.navbar ul li .mainmenu-accountmenu {top:55px }nav#layout-mainmenu.navbar ul.mainmenu-toolbar li.mainmenu-account >a {padding-right:0 }nav#layout-mainmenu.navbar ul li .mainmenu-accountmenu:after {right:13px }nav#layout-mainmenu.navbar ul.nav {display:none }nav#layout-mainmenu.navbar .menu-toggle {display:inline-block;color:#fff !important }}
|
||||
.mainmenu-collapsed {position:absolute;height:100%;top:0;left:0;margin:0;background:#000}
|
||||
.mainmenu-collapsed >div {display:block;height:100%}
|
||||
.mainmenu-collapsed >div ul.mainmenu-nav li a {position:relative;width:65px;height:65px}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ body.mainmenu-open {
|
|||
height: @height;
|
||||
|
||||
ul.mainmenu-toolbar {
|
||||
li.mainmenu-preview {
|
||||
li.mainmenu-quick-action {
|
||||
a {
|
||||
height: @height;
|
||||
line-height: @height;
|
||||
|
|
@ -191,8 +191,12 @@ nav#layout-mainmenu {
|
|||
//
|
||||
|
||||
ul.mainmenu-toolbar {
|
||||
li.mainmenu-preview {
|
||||
margin: 0 0 0 21px;
|
||||
li.mainmenu-quick-action {
|
||||
margin: 0;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 21px;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 20px;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@ class NavigationManager
|
|||
*/
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* @var QuickActionItem[] List of registered quick actions.
|
||||
*/
|
||||
protected $quickActions;
|
||||
|
||||
protected $contextSidenavPartials = [];
|
||||
|
||||
protected $contextOwner;
|
||||
|
|
@ -54,6 +59,9 @@ class NavigationManager
|
|||
*/
|
||||
protected function loadItems()
|
||||
{
|
||||
$this->items = [];
|
||||
$this->quickActions = [];
|
||||
|
||||
/*
|
||||
* Load module items
|
||||
*/
|
||||
|
|
@ -68,11 +76,18 @@ class NavigationManager
|
|||
|
||||
foreach ($plugins as $id => $plugin) {
|
||||
$items = $plugin->registerNavigation();
|
||||
if (!is_array($items)) {
|
||||
$quickActions = $plugin->registerQuickActions();
|
||||
|
||||
if (!is_array($items) && !is_array($quickActions)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->registerMenuItems($id, $items);
|
||||
if (is_array($items)) {
|
||||
$this->registerMenuItems($id, $items);
|
||||
}
|
||||
if (is_array($quickActions)) {
|
||||
$this->registerQuickActions($id, $quickActions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -91,17 +106,21 @@ class NavigationManager
|
|||
Event::fire('backend.menu.extendItems', [$this]);
|
||||
|
||||
/*
|
||||
* Sort menu items
|
||||
* Sort menu items and quick actions
|
||||
*/
|
||||
uasort($this->items, static function ($a, $b) {
|
||||
return $a->order - $b->order;
|
||||
});
|
||||
uasort($this->quickActions, static function ($a, $b) {
|
||||
return $a->order - $b->order;
|
||||
});
|
||||
|
||||
/*
|
||||
* Filter items user lacks permission for
|
||||
* Filter items and quick actions that the user lacks permission for
|
||||
*/
|
||||
$user = BackendAuth::getUser();
|
||||
$this->items = $this->filterItemPermissions($user, $this->items);
|
||||
$this->quickActions = $this->filterItemPermissions($user, $this->quickActions);
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
if (!$item->sideMenu || !count($item->sideMenu)) {
|
||||
|
|
@ -183,10 +202,6 @@ class NavigationManager
|
|||
*/
|
||||
public function registerMenuItems($owner, array $definitions)
|
||||
{
|
||||
if (!$this->items) {
|
||||
$this->items = [];
|
||||
}
|
||||
|
||||
$validator = Validator::make($definitions, [
|
||||
'*.label' => 'required',
|
||||
'*.icon' => 'required_without:*.iconSvg',
|
||||
|
|
@ -319,7 +334,7 @@ class NavigationManager
|
|||
$this->items[$itemKey]->addSideMenuItem($item);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove multiple side menu items
|
||||
*
|
||||
|
|
@ -361,10 +376,14 @@ class NavigationManager
|
|||
*/
|
||||
public function listMainMenuItems()
|
||||
{
|
||||
if ($this->items === null) {
|
||||
if ($this->items === null && $this->quickActions === null) {
|
||||
$this->loadItems();
|
||||
}
|
||||
|
||||
if ($this->items === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
if ($item->badge) {
|
||||
$item->counter = (string) $item->badge;
|
||||
|
|
@ -444,6 +463,137 @@ class NavigationManager
|
|||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers quick actions in the main navigation.
|
||||
*
|
||||
* Quick actions are single purpose links displayed to the left of the user menu in the
|
||||
* backend main navigation.
|
||||
*
|
||||
* The argument is an array of the quick action items. The array keys represent the
|
||||
* quick action item codes, specific for the plugin/module. Each element in the
|
||||
* array should be an associative array with the following keys:
|
||||
* - label - specifies the action label localization string key, used as a tooltip, required.
|
||||
* - icon - an icon name from the Font Awesome icon collection, required if iconSvg is unspecified.
|
||||
* - iconSvg - a custom SVG icon to use for the icon, required if icon is unspecified.
|
||||
* - url - the back-end relative URL the quick action item should point to, required.
|
||||
* - permissions - an array of permissions the back-end user should have, optional.
|
||||
* The item will be displayed if the user has any of the specified permissions.
|
||||
* - order - a position of the item in the menu, optional.
|
||||
*
|
||||
* @param string $owner Specifies the quick action items owner plugin or module in the format Author.Plugin.
|
||||
* @param array $definitions An array of the quick action item definitions.
|
||||
* @return void
|
||||
* @throws SystemException If the validation of the quick action configuration fails
|
||||
*/
|
||||
public function registerQuickActions($owner, array $definitions)
|
||||
{
|
||||
$validator = Validator::make($definitions, [
|
||||
'*.label' => 'required',
|
||||
'*.icon' => 'required_without:*.iconSvg',
|
||||
'*.url' => 'required'
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$errorMessage = 'Invalid quick action item detected in ' . $owner . '. Contact the plugin author to fix (' . $validator->errors()->first() . ')';
|
||||
if (Config::get('app.debug', false)) {
|
||||
throw new SystemException($errorMessage);
|
||||
}
|
||||
|
||||
Log::error($errorMessage);
|
||||
}
|
||||
|
||||
$this->addQuickActionItems($owner, $definitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically add an array of quick action items
|
||||
*
|
||||
* @param string $owner
|
||||
* @param array $definitions
|
||||
* @return void
|
||||
*/
|
||||
public function addQuickActionItems($owner, array $definitions)
|
||||
{
|
||||
foreach ($definitions as $code => $definition) {
|
||||
$this->addQuickActionItem($owner, $code, $definition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically add a single quick action item
|
||||
*
|
||||
* @param string $owner
|
||||
* @param string $code
|
||||
* @param array $definition
|
||||
* @return void
|
||||
*/
|
||||
public function addQuickActionItem($owner, $code, array $definition)
|
||||
{
|
||||
$itemKey = $this->makeItemKey($owner, $code);
|
||||
|
||||
if (isset($this->quickActions[$itemKey])) {
|
||||
$definition = array_merge((array) $this->quickActions[$itemKey], $definition);
|
||||
}
|
||||
|
||||
$item = array_merge($definition, [
|
||||
'code' => $code,
|
||||
'owner' => $owner
|
||||
]);
|
||||
|
||||
$this->quickActions[$itemKey] = QuickActionItem::createFromArray($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the instance of a specified quick action item.
|
||||
*
|
||||
* @param string $owner
|
||||
* @param string $code
|
||||
* @return QuickActionItem
|
||||
* @throws SystemException
|
||||
*/
|
||||
public function getQuickActionItem(string $owner, string $code)
|
||||
{
|
||||
$itemKey = $this->makeItemKey($owner, $code);
|
||||
|
||||
if (!array_key_exists($itemKey, $this->quickActions)) {
|
||||
throw new SystemException('No quick action item found with key ' . $itemKey);
|
||||
}
|
||||
|
||||
return $this->quickActions[$itemKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a single quick action item
|
||||
*
|
||||
* @param $owner
|
||||
* @param $code
|
||||
* @return void
|
||||
*/
|
||||
public function removeQuickActionItem($owner, $code)
|
||||
{
|
||||
$itemKey = $this->makeItemKey($owner, $code);
|
||||
unset($this->quickActions[$itemKey]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of quick action items.
|
||||
*
|
||||
* @return array
|
||||
* @throws SystemException
|
||||
*/
|
||||
public function listQuickActionItems()
|
||||
{
|
||||
if ($this->items === null && $this->quickActions === null) {
|
||||
$this->loadItems();
|
||||
}
|
||||
|
||||
if ($this->quickActions === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->quickActions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the navigation context.
|
||||
* The function sets the navigation owner, main menu item code and the side menu item code.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
<?php namespace Backend\Classes;
|
||||
|
||||
/**
|
||||
* Class QuickActionItem
|
||||
*
|
||||
* @package Backend\Classes
|
||||
*/
|
||||
class QuickActionItem
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $code;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $owner;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $label;
|
||||
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
public $icon;
|
||||
|
||||
/**
|
||||
* @var null|string
|
||||
*/
|
||||
public $iconSvg;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $url;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $order = -1;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $attributes = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $permissions = [];
|
||||
|
||||
/**
|
||||
* @param null|string|int $attribute
|
||||
* @param null|string|array $value
|
||||
*/
|
||||
public function addAttribute($attribute, $value)
|
||||
{
|
||||
$this->attributes[$attribute] = $value;
|
||||
}
|
||||
|
||||
public function removeAttribute($attribute)
|
||||
{
|
||||
unset($this->attributes[$attribute]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $permission
|
||||
* @param array $definition
|
||||
*/
|
||||
public function addPermission(string $permission, array $definition)
|
||||
{
|
||||
$this->permissions[$permission] = $definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $permission
|
||||
* @return void
|
||||
*/
|
||||
public function removePermission(string $permission)
|
||||
{
|
||||
unset($this->permissions[$permission]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return static
|
||||
*/
|
||||
public static function createFromArray(array $data)
|
||||
{
|
||||
$instance = new static();
|
||||
$instance->code = $data['code'];
|
||||
$instance->owner = $data['owner'];
|
||||
$instance->label = $data['label'];
|
||||
$instance->url = $data['url'];
|
||||
$instance->icon = $data['icon'] ?? null;
|
||||
$instance->iconSvg = $data['iconSvg'] ?? null;
|
||||
$instance->attributes = $data['attributes'] ?? $instance->attributes;
|
||||
$instance->permissions = $data['permissions'] ?? $instance->permissions;
|
||||
$instance->order = $data['order'] ?? $instance->order;
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
|
@ -49,15 +49,24 @@
|
|||
</div>
|
||||
<div class="toolbar-item toolbar-item-account">
|
||||
<ul class="mainmenu-toolbar">
|
||||
<li class="mainmenu-preview with-tooltip">
|
||||
<a
|
||||
href="<?= Url::to('/') ?>"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="<?= e(trans('backend::lang.tooltips.preview_website')) ?>">
|
||||
<i class="icon-crosshairs"></i>
|
||||
</a>
|
||||
</li>
|
||||
<?php foreach (BackendMenu::listQuickActionItems() as $item): ?>
|
||||
<li class="mainmenu-quick-action with-tooltip">
|
||||
<a
|
||||
href="<?= $item->url ?>"
|
||||
title="<?= e(trans($item->label)) ?>"
|
||||
<?= Html::attributes($item->attributes) ?>
|
||||
>
|
||||
|
||||
<?php if ($item->iconSvg): ?>
|
||||
<img
|
||||
src="<?= Url::asset($item->iconSvg) ?>"
|
||||
class="svg-icon" loading="lazy" width="20" height="20" />
|
||||
<?php endif ?>
|
||||
|
||||
<i class="<?= $item->iconSvg ? 'svg-replace' : null ?> <?= $item->icon ?>"></i>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
<li class="mainmenu-account with-tooltip">
|
||||
<a
|
||||
href="javascript:;" onclick="$.oc.layout.toggleAccountMenu(this)"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<?php namespace Cms;
|
||||
|
||||
use App;
|
||||
use Url;
|
||||
use Lang;
|
||||
use File;
|
||||
use Event;
|
||||
|
|
@ -171,6 +172,19 @@ class ServiceProvider extends ModuleServiceProvider
|
|||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$manager->registerQuickActions('October.Cms', [
|
||||
'preview' => [
|
||||
'label' => 'backend::lang.tooltips.preview_website',
|
||||
'icon' => 'icon-crosshairs',
|
||||
'url' => Url::to('/'),
|
||||
'order' => 10,
|
||||
'attributes' => [
|
||||
'target' => '_blank',
|
||||
'rel' => 'noopener noreferrer',
|
||||
],
|
||||
],
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -120,6 +120,29 @@ class PluginBase extends ServiceProviderBase
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers back-end quick actions for this plugin.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function registerQuickActions()
|
||||
{
|
||||
$configuration = $this->getConfigurationFromYaml();
|
||||
if (array_key_exists('quickActions', $configuration)) {
|
||||
$quickActions = $configuration['quickActions'];
|
||||
|
||||
if (is_array($quickActions)) {
|
||||
array_walk_recursive($quickActions, function (&$item, $key) {
|
||||
if ($key === 'url') {
|
||||
$item = Backend::url($item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return $quickActions;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers any back-end permissions used by this plugin.
|
||||
*
|
||||
|
|
|
|||
Loading…
Reference in New Issue