Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion assets/js/app/toolbar/Components/Toolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,33 @@
</li>
<li>
<a href="https://docs.bolt.cm/" target="_blank">
<i class="fas fa-globe-americas fa-fw"></i>
<i class="fas fa-book fa-fw"></i>
{{ labels['about.bolt_documentation'] }}
</a>
</li>
</ul>
</div>
</div>

<form
:action="backendPrefix"
class="toolbar-item toolbar-item__filter input-group"
>
<input
type="text"
class="form-control"
:placeholder="labels['listing.placeholder_search']"
name="filter"
id="global-search"
:value="filterValue"
/>
<div class="input-group-append">
<button class="btn btn-tertiary" type="submit">
<i class="fas fa-search"></i>{{ labels['listing.button_search'] }}
</button>
</div>
</form>

<div class="toolbar-item toolbar-item__site">
<a href="/" target="_blank">
<i class="fas fa-sign-out-alt"></i>{{ labels['action.view_site'] }}
Expand Down Expand Up @@ -107,6 +126,7 @@ export default {
menu: Array,
labels: Object,
backendPrefix: RegExp,
filterValue: String,
},
computed: {
contrast() {
Expand Down
6 changes: 5 additions & 1 deletion assets/scss/modules/admin/_toolbar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,13 @@
padding-right: 1rem;
}

&__filter {
margin-left: auto;
max-width: 400px;
}

&__site {
display: none;
margin-left: auto;

@include media-breakpoint-up(sm) {
display: block;
Expand Down
22 changes: 14 additions & 8 deletions src/Controller/Backend/DashboardController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
namespace Bolt\Controller\Backend;

use Bolt\Controller\TwigAwareController;
use Bolt\Entity\Content;
use Bolt\Repository\ContentRepository;
use Bolt\Storage\Query;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

Expand All @@ -20,17 +18,25 @@ class DashboardController extends TwigAwareController implements BackendZoneInte
/**
* @Route("/", name="bolt_dashboard", methods={"GET"})
*/
public function index(ContentRepository $content, Request $request): Response
public function index(Query $query): Response
{
$amount = (int) $this->config->get('general/records_per_page', 10);
$page = (int) $request->get('page', 1);
$contentTypes = $this->config->get('contenttypes')->where('show_on_dashboard', true);
$page = (int) $this->request->get('page', 1);
$contentTypes = $this->config->get('contenttypes')->where('show_on_dashboard', true)->keys()->implode(',');
$filter = $this->getFromRequest('filter');

/** @var Content $records */
$records = $content->findLatest($contentTypes, $page, $amount);
$pager = $this->createPager($query, $contentTypes, $amount, '-modifiedAt');
$nbPages = $pager->getNbPages();

if ($page > $nbPages) {
return $this->redirectToRoute('bolt_dashboard');
}

$records = $pager->setCurrentPage($page);

return $this->renderTemplate('@bolt/pages/dashboard.html.twig', [
'records' => $records,
'filter_value' => $filter,
]);
}
}
30 changes: 5 additions & 25 deletions src/Controller/Backend/ListingController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use Bolt\Controller\TwigAwareController;
use Bolt\Storage\Query;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

Expand All @@ -20,32 +19,13 @@ class ListingController extends TwigAwareController implements BackendZoneInterf
/**
* @Route("/content/{contentType}", name="bolt_content_overview")
*/
public function overview(Request $request, Query $query, string $contentType = ''): Response
public function overview(Query $query, string $contentType = ''): Response
{
$contentTypeObject = ContentType::factory($contentType, $this->config->get('contenttypes'));
$page = (int) $request->query->get('page', '1');
$page = (int) $this->getFromRequest('page', '1');

$params = [
'status' => '!unknown',
];
$pager = $this->createPager($query, $contentType, $contentTypeObject->get('records_per_page'), $contentTypeObject->get('order'));

if ($request->get('sortBy')) {
$params['order'] = $request->get('sortBy');
} else {
$params['order'] = $contentTypeObject->get('order');
}

if ($request->get('filter')) {
$params['anyField'] = '%' . $request->get('filter') . '%';
}

if ($request->get('taxonomy')) {
$taxonomy = explode('=', $request->get('taxonomy'));
$params[$taxonomy[0]] = $taxonomy[1];
}

$pager = $query->getContentForTwig($contentType, $params)
->setMaxPerPage($contentTypeObject->get('records_per_page'));
$nbPages = $pager->getNbPages();

if ($page > $nbPages) {
Expand All @@ -60,8 +40,8 @@ public function overview(Request $request, Query $query, string $contentType = '
return $this->renderTemplate('@bolt/content/listing.html.twig', [
'contentType' => $contentTypeObject,
'records' => $records,
'sortBy' => $request->get('sortBy'),
'filterValue' => $request->get('filter'),
'sortBy' => $this->getFromRequest('sortBy'),
'filterValue' => $this->getFromRequest('filter'),
]);
}
}
8 changes: 1 addition & 7 deletions src/Controller/Frontend/DetailController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
use Bolt\Enum\Statuses;
use Bolt\Repository\ContentRepository;
use Bolt\TemplateChooser;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
Expand All @@ -24,14 +22,10 @@ class DetailController extends TwigAwareController implements FrontendZoneInterf
/** @var ContentRepository */
private $contentRepository;

/** @var Request */
private $request;

public function __construct(TemplateChooser $templateChooser, ContentRepository $contentRepository, RequestStack $requestStack)
public function __construct(TemplateChooser $templateChooser, ContentRepository $contentRepository)
{
$this->templateChooser = $templateChooser;
$this->contentRepository = $contentRepository;
$this->request = $requestStack->getCurrentRequest();
}

/**
Expand Down
7 changes: 1 addition & 6 deletions src/Controller/Frontend/SearchController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use Bolt\Controller\TwigAwareController;
use Bolt\Repository\ContentRepository;
use Bolt\TemplateChooser;
use Bolt\Utils\Sanitiser;
use Pagerfanta\Adapter\ArrayAdapter;
use Pagerfanta\Pagerfanta;
use Symfony\Component\HttpFoundation\Request;
Expand All @@ -19,13 +18,9 @@ class SearchController extends TwigAwareController implements FrontendZoneInterf
/** @var TemplateChooser */
private $templateChooser;

/** @var Sanitiser */
private $sanitiser;

public function __construct(TemplateChooser $templateChooser, Sanitiser $sanitiser)
public function __construct(TemplateChooser $templateChooser)
{
$this->templateChooser = $templateChooser;
$this->sanitiser = $sanitiser;
}

/**
Expand Down
47 changes: 46 additions & 1 deletion src/Controller/TwigAwareController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@
use Bolt\Canonical;
use Bolt\Configuration\Config;
use Bolt\Entity\Field\TemplateselectField;
use Bolt\Storage\Query;
use Bolt\Utils\Sanitiser;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\TwigBundle\Loader\NativeFilesystemLoader;
use Symfony\Component\Asset\Packages;
use Symfony\Component\Asset\PathPackage;
use Symfony\Component\Asset\VersionStrategy\EmptyVersionStrategy;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Tightenco\Collect\Support\Collection;
use Twig\Environment;
Expand All @@ -31,15 +35,23 @@ class TwigAwareController extends AbstractController
/** @var Canonical */
protected $canonical;

/** @var Sanitiser */
protected $sanitiser;

/** @var Request */
protected $request;

/**
* @required
*/
public function setAutowire(Config $config, Environment $twig, Packages $packages, Canonical $canonical): void
public function setAutowire(Config $config, Environment $twig, Packages $packages, Canonical $canonical, Sanitiser $sanitiser, RequestStack $requestStack): void
{
$this->config = $config;
$this->twig = $twig;
$this->packages = $packages;
$this->canonical = $canonical;
$this->sanitiser = $sanitiser;
$this->request = $requestStack->getCurrentRequest();
}

/**
Expand Down Expand Up @@ -123,4 +135,37 @@ private function setThemePackage(): void
$filesPackage = new PathPackage('/files/', new EmptyVersionStrategy());
$this->packages->addPackage('files', $filesPackage);
}

protected function createPager(Query $query, string $contentType, int $pageSize, string $order)
{
$params = [
'status' => '!unknown',
];

if ($this->request->get('sortBy')) {
$params['order'] = $this->getFromRequest('sortBy');
} else {
$params['order'] = $order;
}

if ($this->request->get('filter')) {
$params['anyField'] = '%' . $this->getFromRequest('filter') . '%';
}

if ($this->request->get('taxonomy')) {
$taxonomy = explode('=', $this->getFromRequest('taxonomy'));
$params[$taxonomy[0]] = $taxonomy[1];
}

return $query->getContentForTwig($contentType, $params)
->setMaxPerPage($pageSize);
}

protected function getFromRequest(string $parameter, ?string $default = null): ?string
{
$parameter = trim($this->sanitiser->clean($this->request->get($parameter, '')));

// `clean` returns a string, but we want to be able to get `null`.
return empty($parameter) ? $default : $parameter;
}
}
5 changes: 4 additions & 1 deletion templates/_base/layout.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,17 @@
'general.greeting': 'general.greeting'|trans({'%name%': user_display_name}),
'action.logout': 'action.logout'|trans,
'action.edit_profile': 'action.edit_profile'|trans,
'about.visit_bolt': 'about.visit_bolt'|trans
'about.visit_bolt': 'about.visit_bolt'|trans,
'listing.button_search': 'general.phrase.search'|trans,
'listing.placeholder_search': 'listing.placeholder_search'|trans,
}|json_encode %}

<admin-toolbar
site-name="{{ config.get('general/sitename') }}"
:menu="{{ admin_menu_json }}"
:labels="{{ labels }}"
:backend-prefix="{{ path('bolt_dashboard') }}"
filter-value="{{ filter_value|default('') }}"
></admin-toolbar>
</nav>
<!-- End Admin Toolbar -->
Expand Down
2 changes: 1 addition & 1 deletion templates/content/listing.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
#}
<p>
<strong>{{ 'listing.title_filterby'|trans }}</strong>:
<input class="form-control" type="text" name="filter" value="{{ filterValue }}"
<input class="form-control" type="text" name="filter" id="content-filter" value="{{ filterValue }}"
placeholder="{{ 'listing.placeholder_filter'|trans }}"/>
</p>
</div>
Expand Down
9 changes: 7 additions & 2 deletions templates/pages/dashboard.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@
{% endblock shoulder %}

{% block title %}
{{ macro.icon('tachometer-alt') }}
{{ config.get('general/sitename') }}
{% if filter_value|default() %}
{{ macro.icon('search') }}
{{ 'title.filtered_by'|trans({'%filter%': filter_value})|raw }}
{% else %}
{{ macro.icon('tachometer-alt') }}
{{ config.get('general/sitename') }}
{% endif %}
{% endblock title %}

{# This 'topsection' gets output _before_ the main form, allowing `dump()`, without breaking Vue #}
Expand Down
31 changes: 31 additions & 0 deletions tests/e2e/dashboard_globalsearch.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Feature: Global Search on Dashboard

@javascript
@foo
Scenario: As an Admin I want to filter content
Given I am logged in as "admin"
And I am on "/bolt"

Then I should see "Bolt Dashboard"

When I fill "#global-search" element with "a"
And I press "Search"

Then I should be on "/bolt/?filter=a"
Then I should see 8 ".listing--container" elements
And I should see "All content, filtered by 'a'"

Then I wait 1 seconds

When I fill "#global-search" element with "Entries"
And I press "Search"
Then I should be on "/bolt/?filter=Entries"
Then I should see 1 ".listing--container" elements
And I should see "Entries" in the ".listing--container" element

Then I wait 1 seconds

When I fill "#global-search" element with ""
And I press "Search"
Then I should be on "/bolt/"
And I should see 8 ".listing--container" elements
11 changes: 7 additions & 4 deletions tests/e2e/record_listing.feature
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,23 @@ Feature: Record listing

Then I should see "Contentlisting"

When I fill "filter" element with "a"
When I fill "#content-filter" element with "a"
And I press "Filter"

Then I should be on "/bolt/content/entries?sortBy=&filter=a"
Then I should see 10 ".listing--container" elements

When I fill "filter" element with "Entries"
And I press "Filter"
Then I wait 1 seconds

When I fill "#content-filter" element with "Entries"
And I press "Filter"
Then I should be on "/bolt/content/entries?sortBy=&filter=Entries"
Then I should see 1 ".listing--container" elements
And I should see "Entries" in the ".listing--container" element

When I fill "filter" element with ""
Then I wait 1 seconds

When I fill "#content-filter" element with ""
And I press "Filter"
Then I should be on "/bolt/content/entries?sortBy=&filter="
And I should see 10 ".listing--container" elements
Expand Down
Loading