,
+ ...results,
+ ];
+ }
+}
diff --git a/framework/core/js/src/forum/components/Search.js b/framework/core/js/src/forum/components/Search.tsx
similarity index 75%
rename from framework/core/js/src/forum/components/Search.js
rename to framework/core/js/src/forum/components/Search.tsx
index ba645c3b0..a8d146575 100644
--- a/framework/core/js/src/forum/components/Search.js
+++ b/framework/core/js/src/forum/components/Search.tsx
@@ -1,12 +1,42 @@
-import Component from '../../common/Component';
+import Component, { ComponentAttrs } from '../../common/Component';
import LoadingIndicator from '../../common/components/LoadingIndicator';
import ItemList from '../../common/utils/ItemList';
import classList from '../../common/utils/classList';
import extractText from '../../common/utils/extractText';
import KeyboardNavigatable from '../utils/KeyboardNavigatable';
import icon from '../../common/helpers/icon';
+import SearchState from '../states/SearchState';
import DiscussionsSearchSource from './DiscussionsSearchSource';
import UsersSearchSource from './UsersSearchSource';
+import Mithril from 'mithril';
+
+/**
+ * The `SearchSource` interface defines a section of search results in the
+ * search dropdown.
+ *
+ * Search sources should be registered with the `Search` component class
+ * by extending the `sourceItems` method. When the user types a
+ * query, each search source will be prompted to load search results via the
+ * `search` method. When the dropdown is redrawn, it will be constructed by
+ * putting together the output from the `view` method of each source.
+ */
+export interface SearchSource {
+ /**
+ * Make a request to get results for the given query.
+ */
+ search(query: string);
+
+ /**
+ * Get an array of virtual
s that list the search results for the given
+ * query.
+ */
+ view(query: string): Array;
+}
+
+export interface SearchAttrs extends ComponentAttrs {
+ /** The type of alert this is. Will be used to give the alert a class name of `Alert--{type}`. */
+ state: SearchState;
+}
/**
* The `Search` component displays a menu of as-you-type results from a variety
@@ -20,43 +50,44 @@ import UsersSearchSource from './UsersSearchSource';
*
* - state: SearchState instance.
*/
-export default class Search extends Component {
+export default class Search extends Component {
static MIN_SEARCH_LEN = 3;
- oninit(vnode) {
+ protected state!: SearchState;
+
+ /**
+ * Whether or not the search input has focus.
+ */
+ protected hasFocus = false;
+
+ /**
+ * An array of SearchSources.
+ */
+ protected sources!: SearchSource[];
+
+ /**
+ * The number of sources that are still loading results.
+ */
+ protected loadingSources = 0;
+
+ /**
+ * The index of the currently-selected
in the results list. This can be
+ * a unique string (to account for the fact that an item's position may jump
+ * around as new results load), but otherwise it will be numeric (the
+ * sequential position within the list).
+ */
+ protected index: number = 0;
+
+ protected navigator!: KeyboardNavigatable;
+
+ protected searchTimeout?: number;
+
+ private updateMaxHeightHandler?: () => void;
+
+ oninit(vnode: Mithril.Vnode) {
super.oninit(vnode);
+
this.state = this.attrs.state;
-
- /**
- * Whether or not the search input has focus.
- *
- * @type {Boolean}
- */
- this.hasFocus = false;
-
- /**
- * An array of SearchSources.
- *
- * @type {SearchSource[]}
- */
- this.sources = null;
-
- /**
- * The number of sources that are still loading results.
- *
- * @type {Integer}
- */
- this.loadingSources = 0;
-
- /**
- * The index of the currently-selected
in the results list. This can be
- * a unique string (to account for the fact that an item's position may jump
- * around as new results load), but otherwise it will be numeric (the
- * sequential position within the list).
- *
- * @type {String|Integer}
- */
- this.index = 0;
}
view() {
@@ -64,9 +95,7 @@ export default class Search extends Component {
// Initialize search sources in the view rather than the constructor so
// that we have access to app.forum.
- if (!this.sources) {
- this.sources = this.sourceItems().toArray();
- }
+ if (!this.sources) this.sources = this.sourceItems().toArray();
// Hide the search view if no sources were loaded
if (!this.sources.length) return ;
@@ -76,15 +105,13 @@ export default class Search extends Component {
return (
;
this.navigator = new KeyboardNavigatable();
this.navigator
@@ -233,10 +260,8 @@ export default class Search extends Component {
/**
* Build an item list of SearchSources.
- *
- * @return {ItemList}
*/
- sourceItems() {
+ sourceItems(): ItemList {
const items = new ItemList();
if (app.forum.attribute('canViewDiscussions')) items.add('discussions', new DiscussionsSearchSource());
@@ -247,29 +272,22 @@ export default class Search extends Component {
/**
* Get all of the search result items that are selectable.
- *
- * @return {jQuery}
*/
- selectableItems() {
+ selectableItems(): JQuery {
return this.$('.Search-results > li:not(.Dropdown-header)');
}
/**
* Get the position of the currently selected search result item.
- *
- * @return {Integer}
*/
- getCurrentNumericIndex() {
+ getCurrentNumericIndex(): number {
return this.selectableItems().index(this.getItem(this.index));
}
/**
* Get the
in the search results with the given index (numeric or named).
- *
- * @param {String} index
- * @return {DOMElement}
*/
- getItem(index) {
+ getItem(index: number): JQuery {
const $items = this.selectableItems();
let $item = $items.filter(`[data-index="${index}"]`);
@@ -283,12 +301,8 @@ export default class Search extends Component {
/**
* Set the currently-selected search result item to the one with the given
* index.
- *
- * @param {Integer} index
- * @param {Boolean} scrollToItem Whether or not to scroll the dropdown so that
- * the item is in view.
*/
- setIndex(index, scrollToItem) {
+ setIndex(index: number, scrollToItem: boolean = false) {
const $items = this.selectableItems();
const $dropdown = $items.parent();
@@ -301,7 +315,7 @@ export default class Search extends Component {
const $item = $items.removeClass('active').eq(fixedIndex).addClass('active');
- this.index = $item.attr('data-index') || fixedIndex;
+ this.index = parseInt($item.attr('data-index') as string) || fixedIndex;
if (scrollToItem) {
const dropdownScroll = $dropdown.scrollTop();
diff --git a/framework/core/js/src/forum/components/SearchSource.js b/framework/core/js/src/forum/components/SearchSource.js
deleted file mode 100644
index 8ae6bf1c3..000000000
--- a/framework/core/js/src/forum/components/SearchSource.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * The `SearchSource` interface defines a section of search results in the
- * search dropdown.
- *
- * Search sources should be registered with the `Search` component class
- * by extending the `sourceItems` method. When the user types a
- * query, each search source will be prompted to load search results via the
- * `search` method. When the dropdown is redrawn, it will be constructed by
- * putting together the output from the `view` method of each source.
- *
- * @interface
- */
-export default class SearchSource {
- /**
- * Make a request to get results for the given query.
- *
- * @param {String} query
- * @return {Promise}
- */
- search() {}
-
- /**
- * Get an array of virtual
s that list the search results for the given
- * query.
- *
- * @param {String} query
- * @return {Object}
- */
- view() {}
-}
diff --git a/framework/core/js/src/forum/components/UsersSearchSource.js b/framework/core/js/src/forum/components/UsersSearchSource.tsx
similarity index 72%
rename from framework/core/js/src/forum/components/UsersSearchSource.js
rename to framework/core/js/src/forum/components/UsersSearchSource.tsx
index de0a50635..5024d84b0 100644
--- a/framework/core/js/src/forum/components/UsersSearchSource.js
+++ b/framework/core/js/src/forum/components/UsersSearchSource.tsx
@@ -2,34 +2,32 @@ import highlight from '../../common/helpers/highlight';
import avatar from '../../common/helpers/avatar';
import username from '../../common/helpers/username';
import Link from '../../common/components/Link';
+import { SearchSource } from './Search';
+import Mithril from 'mithril';
/**
* The `UsersSearchSource` finds and displays user search results in the search
* dropdown.
- *
- * @implements SearchSource
*/
-export default class UsersSearchResults {
- constructor() {
- this.results = {};
- }
+export default class UsersSearchResults implements SearchSource {
+ protected results = new Map();
- search(query) {
+ search(query: string) {
return app.store
.find('users', {
filter: { q: query },
page: { limit: 5 },
})
.then((results) => {
- this.results[query] = results;
+ this.results.set(query, results);
m.redraw();
});
}
- view(query) {
+ view(query: string): Array {
query = query.toLowerCase();
- const results = (this.results[query] || [])
+ const results = (this.results.get(query) || [])
.concat(
app.store
.all('users')
@@ -38,14 +36,14 @@ export default class UsersSearchResults {
.filter((e, i, arr) => arr.lastIndexOf(e) === i)
.sort((a, b) => a.displayName().localeCompare(b.displayName()));
- if (!results.length) return '';
+ if (!results.length) return [];
return [