Skip to content

Commit

Permalink
[TASK] Allow searching only inside single manual scope
Browse files Browse the repository at this point in the history
This commit follows up on the changes made in the Rendering Guides repository,
where a new option for searching within a single manual scope was introduced.

These changes implement the same functionality for the search results page:

- The search form has been updated to display a list of search scopes (All | Current).
- The selected scope is now passed as a filter to the Elasticsearch query to match against manual_slug.
- If a scope is selected, facets are hidden on the search results page.
- Styles have been updated to the latest version.
- Several improvements have been made to the templates.
  • Loading branch information
Marcin Sągol committed Mar 22, 2024
1 parent 3279a1c commit 51eabd4
Show file tree
Hide file tree
Showing 10 changed files with 65 additions and 38 deletions.
16 changes: 6 additions & 10 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,16 @@ parameters:
assets:
css:
header:
- 'https://typo3.azureedge.net/typo3documentation/theme/sphinx_typo3_theme/4.9.0/css/theme.css'
- 'https://typo3.azureedge.net/typo3documentation/theme/sphinx_typo3_theme/4.9.0/css/webfonts.css'
- 'https://typo3.azureedge.net/typo3documentation/theme/sphinx_typo3_theme/4.9.0/css/fontawesome.css'
- 'https://typo3.azureedge.net/typo3documentation/theme/typo3-docs-theme/0.2.32/css/theme.css'
footer:
js:
header:
- 'https://typo3.azureedge.net/typo3infrastructure/universe/dist/webcomponents-loader.js'
- 'https://typo3.azureedge.net/typo3infrastructure/universe/dist/typo3-universe.js'
footer:
- 'https://typo3.azureedge.net/typo3documentation/theme/sphinx_typo3_theme/4.9.0/js/jquery.min.js'
- 'https://typo3.azureedge.net/typo3documentation/theme/sphinx_typo3_theme/4.9.0/js/underscore.min.js'
- 'https://typo3.azureedge.net/typo3documentation/theme/sphinx_typo3_theme/4.9.0/js/doctools.min.js'
- 'https://typo3.azureedge.net/typo3documentation/theme/sphinx_typo3_theme/4.9.0/js/popper.min.js'
- 'https://typo3.azureedge.net/typo3documentation/theme/sphinx_typo3_theme/4.9.0/js/bootstrap.min.js'
- 'https://typo3.azureedge.net/typo3documentation/theme/sphinx_typo3_theme/4.9.0/js/autocomplete.min.js'
- 'https://typo3.azureedge.net/typo3documentation/theme/sphinx_typo3_theme/4.9.0/js/theme.min.js'
- 'https://typo3.azureedge.net/typo3documentation/theme/typo3-docs-theme/0.2.32/js/popper.min.js'
- 'https://typo3.azureedge.net/typo3documentation/theme/typo3-docs-theme/0.2.32/js/bootstrap.min.js'
- 'https://typo3.azureedge.net/typo3documentation/theme/typo3-docs-theme/0.2.32/js/theme.min.js'

services:
# default configuration for services in *this* file
Expand Down
2 changes: 1 addition & 1 deletion src/Controller/SearchController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ public function __construct(private readonly ElasticRepository $elasticRepositor
{
}


/**
* @return Response
*/
Expand All @@ -41,6 +40,7 @@ public function search(Request $request): Response

return $this->render('search/search.html.twig', [
'q' => $searchDemand->getQuery(),
'searchScope' => $searchDemand->getScope(),
'filters' => $request->get('filters', []),
'results' => $this->elasticRepository->findByQuery($searchDemand),
]);
Expand Down
15 changes: 13 additions & 2 deletions src/Dto/SearchDemand.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

readonly class SearchDemand
{
public function __construct(private string $query, private int $page, private array $filters)
public function __construct(private string $query, private string $scope, private int $page, private array $filters)
{
}

Expand Down Expand Up @@ -34,14 +34,25 @@ public static function createFromRequest(Request $request): SearchDemand
$page = (int)$request->query->get('page', '1');
$query = $request->query->get('q', '');

return new self($query, max($page, 1), $filters);
// scope points to given manual version and language
$scope = trim(htmlspecialchars(strip_tags((string)$request->query->get('scope'))), '/');
if ($scope) {
$filters['manual_slug'] = [$scope];
}

return new self($query, $scope, max($page, 1), $filters);
}

public function getQuery(): string
{
return $this->query;
}

public function getScope(): string
{
return $this->scope;
}

public function getPage(): int
{
return $this->page;
Expand Down
6 changes: 3 additions & 3 deletions src/Twig/AppExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ public function aggregationBucket(string $category, string $index, array $bucket
} else {
$checked = '';
}
return '<div class="custom-control custom-checkbox">'
. '<input type="checkbox" class="custom-control-input" id="' . $category . '-' . $index . '" name="filters[' . $category . '][' . $key . ']" ' . $checked . ' value="true" onchange="this.form.submit()">'
. '<label class="custom-control-label custom-control-label-hascount" for="' . $category . '-' . $index . '">'
return '<div class="form-check">'
. '<input type="checkbox" class="form-check-input" id="' . $category . '-' . $index . '" name="filters[' . $category . '][' . $key . ']" ' . $checked . ' value="true" onchange="this.form.submit()">'
. '<label class="form-check-label custom-control-label-hascount" for="' . $category . '-' . $index . '">'
. '<span class="custom-control-label-title">' . $label . '</span> <span class="custom-control-label-count">(' . $docCount . ')</span>'
. '</label>'
. '</div>';
Expand Down
6 changes: 3 additions & 3 deletions templates/partial/aggregations.html.twig
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<div class="aggregations" id="aggregations" role="tablist">
<div class="card">
<div class="card-header p-0">
<h2 class="mb-0">
<h2 class="mb-0 d-grid">
<button
type="button"
class="btn btn-link btn-block text-left pl-3 aggregations-toggle-body"
class="btn text-primary aggregations-toggle-body"
data-text-hide="Hide filters"
data-text-show="Show filters"
onclick="document.getElementById('aggregations').classList.toggle('is-expanded')"
Expand All @@ -15,7 +15,7 @@
{% for title, buckets in aggregations %}
<div class="aggregation">
<div class="aggregation-title" id="aggregation-title-{{ loop.index }}">
<span class="aggregation-title-text">{{title}}</span>
<span class="fs-4 fw-bold aggregation-title-text">{{title}}</span>
</div>
<div id="collapse-{{ loop.index }}" class="">
<div class="aggregation-body">
Expand Down
2 changes: 1 addition & 1 deletion templates/partial/base/breadcrumbs.html.twig
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{#TODO: Replace hardcoded breadcrumbs with custom twig-function for generating breadcrumbs#}
<div aria-label="breadcrumbs navigation" class="breadcrumb-bar" role="navigation">
<div class="breadcrumb-bar" aria-label="breadcrumbs navigation" role="navigation">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">Start</a></li>
<li class="breadcrumb-item"><a href="#">Search results</a></li>
Expand Down
14 changes: 9 additions & 5 deletions templates/partial/base/page_header.html.twig
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
<div class="page-header">
<div class="page-header-inner">
<div class="row">
<div class="col-sm-6 col-lg-7">
<div class="col-sm-3 col-md-4 col-lg-6">
<a class="logo" href="/" title="TYPO3 Documentation">
<img alt="TYPO3 Logo" class="logo-image"
height="130" src="https://typo3.azureedge.net/typo3documentation/theme/sphinx_typo3_theme/4.5.1/img/typo3-logo.svg" width="484"/>
</a>
</div>
<div class="col-sm-6 col-lg-5">
<div class="col-sm-9 col-md-8 col-lg-6">
<div role="search">
<form id="rtd-search-form" class="wy-form" action="{{ path('searchresult') }}" method="get">
<div class="input-group mb-3 mt-sm-3">
<select class="form-select search__scope" id="searchscope" name="scope">
<option value="">Search all</option>
{% if searchScope is defined and searchScope is not empty %}
<option value="{{ searchScope|striptags|escape }}" selected="selected">Search current</option>
{% endif %}
</select>
<input type="text" class="form-control shadow-none" name="q" autocomplete="off" placeholder="Search in TYPO3 documentation" id="searchinput" value="{% if q is defined %}{{ q }}{% endif %}">
<div class="input-group-append">
<button class="btn btn-sm btn-primary" type="submit"><i class="fa fa-search"></i>&nbsp;Search</button>
</div>
<button class="btn btn-primary" type="submit"><i class="fa fa-search"></i>&nbsp;<span class="d-none d-md-inline">Search</span></button>
</div>
</form>
</div>
Expand Down
5 changes: 3 additions & 2 deletions templates/partial/paginator.html.twig
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{% set filteredArgumnets = arguments|filter(value => value != '') %}
<ul class="pagination flex-wrap">
{% for pageDesc, page in result.pagesToLinkTo %}
{% if result.currentPage == page %}
<li class="page-item active">
<a class="page-link" href="{{ path('searchresult', arguments|merge({'page': page})) }}">{{ page }}</a>
<a class="page-link" href="{{ path('searchresult', filteredArgumnets|merge({'page': page})) }}">{{ page }}</a>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="{{ path('searchresult', arguments|merge({'page': page})) }}">{{ page }}</a>
<a class="page-link" href="{{ path('searchresult', filteredArgumnets|merge({'page': page})) }}">{{ page }}</a>
</li>
{% endif %}
{% endfor %}
Expand Down
18 changes: 9 additions & 9 deletions templates/search/search.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,12 @@
display: block;
}
.aggregation-title-text {
font-size: 18px;
font-weight: bold;
}
.aggregation-body {
padding-left: 20px;
}
.custom-control-label,
.custom-control-input {
.form-check-label,
.form-check-input {
cursor: pointer;
}
.page-item {
Expand Down Expand Up @@ -70,7 +68,7 @@
{% if results.totalResults > 0 %}
<h4>Showing hits <strong>{{ results.startingAtItem }} </strong>to <strong>{{ results.endingAtItem }}</strong> of <strong>{{ results.totalResults }}</strong></h4>
<div class="row">
<div class="col-md-8 col-lg-9 order-2 order-md-1 mb-4">
<div class="{{ (searchScope is empty) ? 'col-md-8 col-lg-9 order-2 order-md-1 mb-4' : 'mb-4' }}">
<ul class="list-group">
{% for hit in results.results %}
<li class="list-group-item list-group-item-action hit">
Expand All @@ -79,15 +77,15 @@
{% set versionInSlug = hit.data.manual_version|filter(item => item in slugParts) | first %}
{% set latestVersionSlug = hit.data.manual_slug|replace({ (versionInSlug): sortVersions(hit.data.manual_version, 'desc')|first }) %}
<a class="text-dark" href="https://docs.typo3.org/{{ latestVersionSlug }}/{{ hit.data.relative_url }}#{{ hit.data.fragment }}">{{ hit.data.snippet_title }}</a>
<small class="text-muted text-decoration-none">{{ hit.data.manual_title }}</small>
<span class="badge badge-secondary text-decoration-none">{{ hit.data.manual_type }}</span>
<small class="text-muted fw-normal">{{ hit.data.manual_title }}</small>
<span class="badge bg-secondary">{{ hit.data.manual_type }}</span>
{% for version in sortVersions(filterVersions(hit.data.manual_version), 'desc') %}
{% if version != versionInSlug %}
{% set versionBadgeSlug = hit.data.manual_slug|replace({ (versionInSlug): version }) %}
{% else %}
{% set versionBadgeSlug = hit.data.manual_slug %}
{% endif %}
<a class="badge badge-primary text-decoration-none" href="https://docs.typo3.org/{{ versionBadgeSlug }}/{{ hit.data.relative_url }}#{{ hit.data.fragment }}">{{ version }}</a>
<a class="badge bg-primary text-decoration-none" href="https://docs.typo3.org/{{ versionBadgeSlug }}/{{ hit.data.relative_url }}#{{ hit.data.fragment }}">{{ version }}</a>
{% endfor %}
</h4>
<div class="position-relative">
Expand All @@ -102,6 +100,7 @@
{% endfor %}
</ul>
</div>
{% if searchScope is empty %}
<div class="col-md-4 col-lg-3 order-1 order-md-2 mb-4">
<div class="wy-menuXX wy-menu-verticalXX" data-spy="affix" role="navigation" aria-label="main navigation">
<form action="{{ path('searchresult') }}" method="get" class="form">
Expand All @@ -110,9 +109,10 @@
</form>
</div>
</div>
{% endif %}
</div>
{% else %}
<h4>No search results found for: {{ q }}</h4>
{% endif %}
{% include '/partial/paginator.html.twig' with {'result':results, 'arguments': {'q': q, 'filters': filters}} %}
{% include '/partial/paginator.html.twig' with {'result':results, 'arguments': {'q': q, 'scope': searchScope|striptags|escape, 'filters': filters}} %}
{% endblock %}
19 changes: 17 additions & 2 deletions tests/Unit/Dto/SearchDemandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@ public function createFromRequestWithAllParameters(): void
$request = Request::create(
'/search',
'GET',
['q' => 'TCA', 'page' => '2', 'filters' => ['Document Type' => ['manual' => 'true']]]
['q' => 'TCA', 'scope' => 'p/vendor/package/main/en-us', 'page' => '2', 'filters' => ['Document Type' => ['manual' => 'true']]]
);

$searchDemand = SearchDemand::createFromRequest($request);

$this->assertSame('TCA', $searchDemand->getQuery());
$this->assertSame('p/vendor/package/main/en-us', $searchDemand->getScope());
$this->assertSame(2, $searchDemand->getPage());
$this->assertSame([
'manual_type' => ['manual']
'manual_type' => ['manual'],
'manual_slug' => ['p/vendor/package/main/en-us']
], $searchDemand->getFilters());
}

Expand All @@ -41,6 +43,7 @@ public function createFromRequestWithDefaultValues(): void
$searchDemand = SearchDemand::createFromRequest($request);

$this->assertSame('', $searchDemand->getQuery());
$this->assertSame('', $searchDemand->getScope());
$this->assertSame(1, $searchDemand->getPage());
$this->assertSame([], $searchDemand->getFilters());
}
Expand Down Expand Up @@ -94,6 +97,18 @@ public function createFromRequestWithSpecialCharactersInQuery(): void
$this->assertSame('test+query+%26+%22something+more%22', $searchDemand->getQuery());
}

/**
* @test
*/
public function createFromRequestWithSpecialCharactersInScope(): void
{
$request = Request::create('/search', 'GET', ['scope' => 'p/news/news+%26+%22something+more%22/main/en-us']);

$searchDemand = SearchDemand::createFromRequest($request);

$this->assertSame('p/news/news+%26+%22something+more%22/main/en-us', $searchDemand->getScope());
}

/**
* @test
*/
Expand Down

0 comments on commit 51eabd4

Please sign in to comment.