Skip to content

Commit

Permalink
Merge pull request #1498 from bolt/feature/global-search-in-backend
Browse files Browse the repository at this point in the history
Add Global search to backend
  • Loading branch information
bobdenotter authored Jun 18, 2020
2 parents bda9a2b + 4031de7 commit a9689ea
Show file tree
Hide file tree
Showing 13 changed files with 156 additions and 58 deletions.
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

0 comments on commit a9689ea

Please sign in to comment.