Skip to content

Commit b0ec1a6

Browse files
committed
FIX: bring back sidebar filter
In this PR, filter was removed and replaced by search. #32485 However, moderator should still be able to filter sidebar. Also, plugins like `doc-category` should have filterable sidebar.
1 parent 5c041a1 commit b0ec1a6

File tree

10 files changed

+250
-1
lines changed

10 files changed

+250
-1
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import Component from "@glimmer/component";
2+
import { service } from "@ember/service";
3+
import { i18n } from "discourse-i18n";
4+
5+
export default class FilterNoResults extends Component {
6+
@service sidebarState;
7+
8+
get shouldDisplay() {
9+
return (
10+
this.sidebarState.currentPanel.filterable &&
11+
!!(this.args.sections?.length === 0)
12+
);
13+
}
14+
15+
get noResultsDescription() {
16+
return this.sidebarState.currentPanel.filterNoResultsDescription(
17+
this.sidebarState.filter
18+
);
19+
}
20+
21+
<template>
22+
{{#if this.shouldDisplay}}
23+
<div class="sidebar-no-results">
24+
<h4 class="sidebar-no-results__title">{{i18n
25+
"sidebar.no_results.title"
26+
}}</h4>
27+
{{#if this.noResultsDescription}}
28+
<p class="sidebar-no-results__description">
29+
{{this.noResultsDescription}}
30+
</p>
31+
{{/if}}
32+
</div>
33+
{{/if}}
34+
</template>
35+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import Component from "@glimmer/component";
2+
import { on } from "@ember/modifier";
3+
import { action } from "@ember/object";
4+
import { service } from "@ember/service";
5+
import DButton from "discourse/components/d-button";
6+
import { i18n } from "discourse-i18n";
7+
8+
export default class Filter extends Component {
9+
@service sidebarState;
10+
@service router;
11+
@service currentUser;
12+
13+
willDestroy() {
14+
super.willDestroy(...arguments);
15+
this.sidebarState.clearFilter();
16+
}
17+
18+
get shouldDisplay() {
19+
return this.sidebarState.currentPanel.filterable;
20+
}
21+
22+
get displayClearFilter() {
23+
return this.sidebarState.filter.length > 0;
24+
}
25+
26+
@action
27+
setFilter(event) {
28+
this.sidebarState.filter = event.target.value;
29+
}
30+
31+
@action
32+
handleEscape(event) {
33+
if (event.key === "Escape") {
34+
event.stopPropagation();
35+
36+
if (this.sidebarState.filter.length > 0) {
37+
this.sidebarState.filter = "";
38+
} else {
39+
event.target.blur();
40+
}
41+
}
42+
}
43+
44+
@action
45+
clearFilter() {
46+
this.sidebarState.clearFilter();
47+
document.querySelector(".sidebar-filter__input").focus();
48+
}
49+
50+
<template>
51+
{{#if this.shouldDisplay}}
52+
<div class="sidebar-filter">
53+
<div class="sidebar-filter__input-container">
54+
<input
55+
{{on "input" this.setFilter}}
56+
{{on "keydown" this.handleEscape}}
57+
value={{this.sidebarState.filter}}
58+
placeholder={{i18n "sidebar.filter_links"}}
59+
type="text"
60+
enterkeyhint="done"
61+
class="sidebar-filter__input"
62+
/>
63+
64+
{{#if this.displayClearFilter}}
65+
<DButton
66+
@action={{this.clearFilter}}
67+
@icon="xmark"
68+
class="sidebar-filter__clear"
69+
/>
70+
{{/if}}
71+
</div>
72+
</div>
73+
{{/if}}
74+
</template>
75+
}

app/assets/javascripts/discourse/app/components/sidebar/panel-header.gjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ import { service } from "@ember/service";
33
import BackToForum from "discourse/components/sidebar/back-to-forum";
44
import Search from "discourse/components/sidebar/search";
55
import ToggleAllSections from "./toggle-all-sections";
6+
import Filter from "./filter";
7+
import FilterNoResults from "./filter-no-results";
68

79
export default class PanelHeader extends Component {
810
@service sidebarState;
11+
@service currentUser;
912

1013
get shouldDisplay() {
1114
return this.sidebarState.currentPanel.displayHeader;
@@ -20,7 +23,9 @@ export default class PanelHeader extends Component {
2023
</div>
2124
<div class="sidebar-panel-header__row">
2225
<Search />
26+
<Filter />
2327
</div>
28+
<FilterNoResults @sections={{@sections}} />
2429
</div>
2530
{{/if}}
2631
</template>

app/assets/javascripts/discourse/app/lib/sidebar/admin-sidebar.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { cached } from "@glimmer/tracking";
22
import { warn } from "@ember/debug";
3+
import { htmlSafe } from "@ember/template";
34
import { configNavForPlugin } from "discourse/lib/admin-plugin-config-nav";
45
import { adminRouteValid } from "discourse/lib/admin-utilities";
56
import { getOwnerWithFallback } from "discourse/lib/get-owner";
@@ -10,6 +11,7 @@ import BaseCustomSidebarPanel from "discourse/lib/sidebar/base-custom-sidebar-pa
1011
import BaseCustomSidebarSection from "discourse/lib/sidebar/base-custom-sidebar-section";
1112
import BaseCustomSidebarSectionLink from "discourse/lib/sidebar/base-custom-sidebar-section-link";
1213
import { ADMIN_PANEL } from "discourse/lib/sidebar/panels";
14+
import { escapeExpression } from "discourse/lib/utilities";
1315
import I18n, { i18n } from "discourse-i18n";
1416

1517
let additionalAdminSidebarSectionLinks = {};
@@ -409,7 +411,27 @@ export default class AdminSidebarPanel extends BaseCustomSidebarPanel {
409411
}
410412

411413
get searchable() {
412-
return true;
414+
const currentUser = getOwnerWithFallback(this).lookup(
415+
"service:current-user"
416+
);
417+
return currentUser.admin;
418+
}
419+
420+
get filterable() {
421+
const currentUser = getOwnerWithFallback(this).lookup(
422+
"service:current-user"
423+
);
424+
return !currentUser.admin && currentUser.moderator;
425+
}
426+
427+
filterNoResultsDescription(filter) {
428+
const escapedFilter = escapeExpression(filter);
429+
430+
return htmlSafe(
431+
i18n("sidebar.no_results.description_admin_search", {
432+
filter: escapedFilter,
433+
})
434+
);
413435
}
414436

415437
get onSearchClick() {

app/assets/javascripts/discourse/app/lib/sidebar/base-custom-sidebar-panel.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,24 @@ export default class BaseCustomSidebarPanel {
5454
return false;
5555
}
5656

57+
/**
58+
* @returns {boolean} Controls whether the filter is shown
59+
*/
60+
get filterable() {
61+
return false;
62+
}
63+
64+
/**
65+
* @param {string} filter filter applied
66+
*
67+
* @returns {string | SafeString} Description displayed when the applied filter has no results.
68+
* Use `htmlSafe` from `from "@ember/template` to use HTML strings.
69+
*/
70+
// eslint-disable-next-line no-unused-vars
71+
filterNoResultsDescription(filter) {
72+
return null;
73+
}
74+
5775
/**
5876
* @returns {boolean} Controls whether the search is shown
5977
*/

app/assets/stylesheets/common/base/menu-panel.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,10 @@
233233
vertical-align: text-bottom;
234234
}
235235

236+
.sidebar-filter {
237+
width: 100%;
238+
}
239+
236240
.sidebar-search {
237241
width: 100%;
238242
}

app/assets/stylesheets/common/base/sidebar.scss

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,68 @@
353353
}
354354
}
355355

356+
.sidebar-filter {
357+
margin-top: 1em;
358+
margin-bottom: 1em;
359+
border: 1px solid var(--primary-400);
360+
border-radius: var(--d-input-border-radius);
361+
background: var(--secondary);
362+
width: calc(
363+
var(--d-sidebar-width) - 2 * var(--d-sidebar-row-horizontal-padding)
364+
);
365+
366+
&:focus-within {
367+
border-color: var(--tertiary);
368+
outline: 1px solid var(--tertiary);
369+
outline-offset: -1px;
370+
}
371+
372+
&__input-container {
373+
position: relative;
374+
display: flex;
375+
align-items: center;
376+
background: var(--secondary);
377+
border-radius: var(--d-input-border-radius);
378+
}
379+
380+
&__shortcut-hint {
381+
background-color: rgba(var(--tertiary-rgb), 0.1);
382+
padding: 0.25em 0.5em;
383+
margin-right: 0.5em;
384+
font-size: var(--font-down-3);
385+
color: var(--primary-medium);
386+
}
387+
388+
&__input[type="text"] {
389+
border: 0;
390+
background: none;
391+
margin-bottom: 0;
392+
height: 2em;
393+
width: 100%;
394+
395+
&:focus-within {
396+
outline: 0;
397+
}
398+
}
399+
400+
&__clear {
401+
width: 2em;
402+
height: 2em;
403+
color: var(--primary-medium);
404+
background-color: var(--secondary);
405+
}
406+
}
407+
408+
.sidebar-no-results {
409+
display: block;
410+
margin: 0.5em var(--d-sidebar-row-horizontal-padding) 0
411+
var(--d-sidebar-row-horizontal-padding);
412+
413+
&__title {
414+
font-weight: bold;
415+
}
416+
}
417+
356418
.sidebar-search {
357419
width: 100%;
358420

config/locales/client.en.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5096,6 +5096,11 @@ en:
50965096
forum:
50975097
label: Forum
50985098
back_to_forum: "Back to Forum"
5099+
filter_links: "Filter links..."
5100+
clear_filter: "Clear filter"
5101+
no_results:
5102+
title: "No results"
5103+
description_admin_search: 'We couldn’t find anything matching ‘%{filter}’.'
50995104
collapse_all_sections: "Collapse all sections"
51005105
expand_all_sections: "Expand all sections"
51015106
search: "Search"

spec/system/admin_sidebar_navigation_spec.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
let(:sidebar) { PageObjects::Components::NavigationMenu::Sidebar.new }
1010
let(:sidebar_dropdown) { PageObjects::Components::SidebarHeaderDropdown.new }
11+
let(:filter) { PageObjects::Components::Filter.new }
1112

1213
before do
1314
SiteSetting.navigation_menu = "sidebar"
@@ -158,5 +159,10 @@
158159
I18n.t("admin_js.admin.config.staff_action_logs.title"),
159160
],
160161
)
162+
163+
filter.filter("watched")
164+
links = page.all(".sidebar-section-link-content-text")
165+
expect(links.count).to eq(1)
166+
expect(links.map(&:text)).to eq(["Watched words"])
161167
end
162168
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# frozen_string_literal: true
2+
3+
module PageObjects
4+
module Components
5+
class Filter < PageObjects::Components::Base
6+
def filter(text)
7+
page.find(".sidebar-filter__input").fill_in(with: text)
8+
self
9+
end
10+
11+
def clear
12+
page.find(".sidebar-filter__clear").click
13+
self
14+
end
15+
end
16+
end
17+
end

0 commit comments

Comments
 (0)