Some search additions.

- Adding an async community picker.
- Adding community_id to search page.
This commit is contained in:
Dessalines 2021-04-23 01:51:13 -04:00
parent 7858c17d3b
commit aee7d53907
7 changed files with 240 additions and 66 deletions

View file

@ -273,7 +273,7 @@ export class Communities extends Component<any, CommunitiesState> {
handleSearchSubmit(i: Communities) { handleSearchSubmit(i: Communities) {
const searchParamEncoded = encodeURIComponent(i.state.searchText); const searchParamEncoded = encodeURIComponent(i.state.searchText);
i.context.router.history.push( i.context.router.history.push(
`/search/q/${searchParamEncoded}/type/Communities/sort/TopAll/listing_type/All/page/1` `/search/q/${searchParamEncoded}/type/Communities/sort/TopAll/listing_type/All/community_id/0/page/1`
); );
} }

View file

@ -25,6 +25,8 @@ import {
SortType, SortType,
ListingType, ListingType,
PostView, PostView,
GetCommunity,
GetCommunityResponse,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { i18n } from "../i18next"; import { i18n } from "../i18next";
import { InitialFetchRequest, PostFormParams } from "shared/interfaces"; import { InitialFetchRequest, PostFormParams } from "shared/interfaces";
@ -67,15 +69,27 @@ export class CreatePost extends Component<any, CreatePostState> {
} }
refetch() { refetch() {
let listCommunitiesForm: ListCommunities = { if (this.params.community_id) {
type_: ListingType.All, let form: GetCommunity = {
sort: SortType.TopAll, id: this.params.community_id,
limit: fetchLimit, };
auth: authField(false), WebSocketService.Instance.send(wsClient.getCommunity(form));
}; } else if (this.params.community_name) {
WebSocketService.Instance.send( let form: GetCommunity = {
wsClient.listCommunities(listCommunitiesForm) name: this.params.community_name,
); };
WebSocketService.Instance.send(wsClient.getCommunity(form));
} else {
let listCommunitiesForm: ListCommunities = {
type_: ListingType.All,
sort: SortType.TopAll,
limit: fetchLimit,
auth: authField(false),
};
WebSocketService.Instance.send(
wsClient.listCommunities(listCommunitiesForm)
);
}
} }
componentWillUnmount() { componentWillUnmount() {
@ -181,6 +195,11 @@ export class CreatePost extends Component<any, CreatePostState> {
this.state.communities = data.communities; this.state.communities = data.communities;
this.state.loading = false; this.state.loading = false;
this.setState(this.state); this.setState(this.state);
} else if (op == UserOperation.GetCommunity) {
let data = wsJsonToRes<GetCommunityResponse>(msg).data;
this.state.communities = [data.community_view];
this.state.loading = false;
this.setState(this.state);
} }
} }
} }

View file

@ -134,7 +134,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
} else { } else {
const searchParamEncoded = encodeURIComponent(searchParam); const searchParamEncoded = encodeURIComponent(searchParam);
this.context.router.history.push( this.context.router.history.push(
`/search/q/${searchParamEncoded}/type/All/sort/TopAll/listing_type/All/page/1` `/search/q/${searchParamEncoded}/type/All/sort/TopAll/listing_type/All/community_id/0/page/1`
); );
} }
} }

View file

@ -16,7 +16,6 @@ import {
SearchType, SearchType,
SearchResponse, SearchResponse,
ListingType, ListingType,
LemmyHttp,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { WebSocketService, UserService } from "../services"; import { WebSocketService, UserService } from "../services";
import { PostFormParams } from "../interfaces"; import { PostFormParams } from "../interfaces";
@ -39,7 +38,8 @@ import {
wsClient, wsClient,
authField, authField,
communityToChoice, communityToChoice,
fetchLimit, fetchCommunities,
choicesConfig,
} from "../utils"; } from "../utils";
import autosize from "autosize"; import autosize from "autosize";
@ -49,7 +49,7 @@ if (isBrowser()) {
} }
import { i18n } from "../i18next"; import { i18n } from "../i18next";
import { httpBase, pictrsUri } from "../env"; import { pictrsUri } from "../env";
const MAX_POST_TITLE_LENGTH = 200; const MAX_POST_TITLE_LENGTH = 200;
@ -456,20 +456,6 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
this.setState(this.state); this.setState(this.state);
} }
async fetchCommunities(q: string) {
let form: Search = {
q,
type_: SearchType.Communities,
sort: SortType.TopAll,
listing_type: ListingType.All,
page: 1,
limit: fetchLimit,
auth: authField(false),
};
let client = new LemmyHttp(httpBase);
return client.search(form);
}
handlePostBodyChange(val: string) { handlePostBodyChange(val: string) {
this.state.postForm.body = val; this.state.postForm.body = val;
this.setState(this.state); this.setState(this.state);
@ -556,37 +542,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
if (isBrowser()) { if (isBrowser()) {
let selectId: any = document.getElementById("post-community"); let selectId: any = document.getElementById("post-community");
if (selectId) { if (selectId) {
this.choices = new Choices(selectId, { this.choices = new Choices(selectId, choicesConfig);
shouldSort: false,
classNames: {
containerOuter: "choices",
containerInner: "choices__inner bg-light border-0",
input: "form-control",
inputCloned: "choices__input--cloned",
list: "choices__list",
listItems: "choices__list--multiple",
listSingle: "choices__list--single",
listDropdown: "choices__list--dropdown",
item: "choices__item bg-light",
itemSelectable: "choices__item--selectable",
itemDisabled: "choices__item--disabled",
itemChoice: "choices__item--choice",
placeholder: "choices__placeholder",
group: "choices__group",
groupHeading: "choices__heading",
button: "choices__button",
activeState: "is-active",
focusState: "is-focused",
openState: "is-open",
disabledState: "is-disabled",
highlightedState: "text-info",
selectedState: "text-info",
flippedState: "is-flipped",
loadingState: "is-loading",
noResults: "has-no-results",
noChoices: "has-no-choices",
},
});
this.choices.passedElement.element.addEventListener( this.choices.passedElement.element.addEventListener(
"choice", "choice",
(e: any) => { (e: any) => {
@ -598,7 +554,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
this.choices.passedElement.element.addEventListener( this.choices.passedElement.element.addEventListener(
"search", "search",
debounce(async (e: any) => { debounce(async (e: any) => {
let communities = (await this.fetchCommunities(e.detail.value)) let communities = (await fetchCommunities(e.detail.value))
.communities; .communities;
this.choices.setChoices( this.choices.setChoices(
communities.map(cv => communityToChoice(cv)), communities.map(cv => communityToChoice(cv)),

View file

@ -14,6 +14,9 @@ import {
CommentResponse, CommentResponse,
Site, Site,
ListingType, ListingType,
ListCommunities,
ListCommunitiesResponse,
GetCommunity,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { WebSocketService } from "../services"; import { WebSocketService } from "../services";
import { import {
@ -35,6 +38,12 @@ import {
restoreScrollPosition, restoreScrollPosition,
routeListingTypeToEnum, routeListingTypeToEnum,
showLocal, showLocal,
isBrowser,
choicesConfig,
debounce,
fetchCommunities,
communityToChoice,
hostname,
} from "../utils"; } from "../utils";
import { PostListing } from "./post-listing"; import { PostListing } from "./post-listing";
import { HtmlTags } from "./html-tags"; import { HtmlTags } from "./html-tags";
@ -47,11 +56,17 @@ import { CommentNodes } from "./comment-nodes";
import { i18n } from "../i18next"; import { i18n } from "../i18next";
import { InitialFetchRequest } from "shared/interfaces"; import { InitialFetchRequest } from "shared/interfaces";
var Choices;
if (isBrowser()) {
Choices = require("choices.js");
}
interface SearchProps { interface SearchProps {
q: string; q: string;
type_: SearchType; type_: SearchType;
sort: SortType; sort: SortType;
listingType: ListingType; listingType: ListingType;
communityId: number;
page: number; page: number;
} }
@ -60,8 +75,10 @@ interface SearchState {
type_: SearchType; type_: SearchType;
sort: SortType; sort: SortType;
listingType: ListingType; listingType: ListingType;
communityId: number;
page: number; page: number;
searchResponse: SearchResponse; searchResponse: SearchResponse;
communities: CommunityView[];
loading: boolean; loading: boolean;
site: Site; site: Site;
searchText: string; searchText: string;
@ -72,11 +89,13 @@ interface UrlParams {
type_?: SearchType; type_?: SearchType;
sort?: SortType; sort?: SortType;
listingType?: ListingType; listingType?: ListingType;
communityId?: number;
page?: number; page?: number;
} }
export class Search extends Component<any, SearchState> { export class Search extends Component<any, SearchState> {
private isoData = setIsoData(this.context); private isoData = setIsoData(this.context);
private choices: any;
private subscription: Subscription; private subscription: Subscription;
private emptyState: SearchState = { private emptyState: SearchState = {
q: Search.getSearchQueryFromProps(this.props.match.params.q), q: Search.getSearchQueryFromProps(this.props.match.params.q),
@ -87,6 +106,9 @@ export class Search extends Component<any, SearchState> {
), ),
page: Search.getPageFromProps(this.props.match.params.page), page: Search.getPageFromProps(this.props.match.params.page),
searchText: Search.getSearchQueryFromProps(this.props.match.params.q), searchText: Search.getSearchQueryFromProps(this.props.match.params.q),
communityId: Search.getCommunityIdFromProps(
this.props.match.params.community_id
),
searchResponse: { searchResponse: {
type_: null, type_: null,
posts: [], posts: [],
@ -96,6 +118,7 @@ export class Search extends Component<any, SearchState> {
}, },
loading: true, loading: true,
site: this.isoData.site_res.site_view.site, site: this.isoData.site_res.site_view.site,
communities: [],
}; };
static getSearchQueryFromProps(q: string): string { static getSearchQueryFromProps(q: string): string {
@ -114,6 +137,10 @@ export class Search extends Component<any, SearchState> {
return listingType ? routeListingTypeToEnum(listingType) : ListingType.All; return listingType ? routeListingTypeToEnum(listingType) : ListingType.All;
} }
static getCommunityIdFromProps(id: string): number {
return id ? Number(id) : 0;
}
static getPageFromProps(page: string): number { static getPageFromProps(page: string): number {
return page ? Number(page) : 1; return page ? Number(page) : 1;
} }
@ -129,13 +156,22 @@ export class Search extends Component<any, SearchState> {
this.subscription = wsSubscribe(this.parseMessage); this.subscription = wsSubscribe(this.parseMessage);
// Only fetch the data if coming from another route // Only fetch the data if coming from another route
if (this.state.q != "") { if (this.isoData.path == this.context.router.route.match.url) {
if (this.isoData.path == this.context.router.route.match.url) { let singleOrMultipleCommunities = this.isoData.routeData[0];
this.state.searchResponse = this.isoData.routeData[0]; if (singleOrMultipleCommunities.communities) {
this.state.communities = this.isoData.routeData[0].communities;
} else {
this.state.communities = [this.isoData.routeData[0].community_view];
}
if (this.state.q != "") {
this.state.searchResponse = this.isoData.routeData[1];
this.state.loading = false; this.state.loading = false;
} else { } else {
this.search(); this.search();
} }
} else {
this.fetchCommunities();
this.search();
} }
} }
@ -144,6 +180,10 @@ export class Search extends Component<any, SearchState> {
saveScrollPosition(this.context); saveScrollPosition(this.context);
} }
componentDidMount() {
this.setupCommunityFilter();
}
static getDerivedStateFromProps(props: any): SearchProps { static getDerivedStateFromProps(props: any): SearchProps {
return { return {
q: Search.getSearchQueryFromProps(props.match.params.q), q: Search.getSearchQueryFromProps(props.match.params.q),
@ -152,22 +192,57 @@ export class Search extends Component<any, SearchState> {
listingType: Search.getListingTypeFromProps( listingType: Search.getListingTypeFromProps(
props.match.params.listing_type props.match.params.listing_type
), ),
communityId: Search.getCommunityIdFromProps(
props.match.params.community_id
),
page: Search.getPageFromProps(props.match.params.page), page: Search.getPageFromProps(props.match.params.page),
}; };
} }
fetchCommunities() {
let listCommunitiesForm: ListCommunities = {
type_: ListingType.All,
sort: SortType.TopAll,
limit: fetchLimit,
auth: authField(false),
};
WebSocketService.Instance.send(
wsClient.listCommunities(listCommunitiesForm)
);
}
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] { static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
let pathSplit = req.path.split("/"); let pathSplit = req.path.split("/");
let promises: Promise<any>[] = []; let promises: Promise<any>[] = [];
let cId = this.getCommunityIdFromProps(pathSplit[11]);
if (cId !== 0) {
let f: GetCommunity = {
id: cId,
};
setOptionalAuth(f, req.auth);
promises.push(req.client.getCommunity(f));
} else {
let listCommunitiesForm: ListCommunities = {
type_: ListingType.All,
sort: SortType.TopAll,
limit: fetchLimit,
};
setOptionalAuth(listCommunitiesForm, req.auth);
promises.push(req.client.listCommunities(listCommunitiesForm));
}
let form: SearchForm = { let form: SearchForm = {
q: this.getSearchQueryFromProps(pathSplit[3]), q: this.getSearchQueryFromProps(pathSplit[3]),
type_: this.getSearchTypeFromProps(pathSplit[5]), type_: this.getSearchTypeFromProps(pathSplit[5]),
sort: this.getSortTypeFromProps(pathSplit[7]), sort: this.getSortTypeFromProps(pathSplit[7]),
listing_type: this.getListingTypeFromProps(pathSplit[9]), listing_type: this.getListingTypeFromProps(pathSplit[9]),
page: this.getPageFromProps(pathSplit[11]), page: this.getPageFromProps(pathSplit[13]),
limit: fetchLimit, limit: fetchLimit,
}; };
if (cId !== 0) {
form.community_id = cId;
}
setOptionalAuth(form, req.auth); setOptionalAuth(form, req.auth);
if (form.q != "") { if (form.q != "") {
@ -183,6 +258,7 @@ export class Search extends Component<any, SearchState> {
lastState.type_ !== this.state.type_ || lastState.type_ !== this.state.type_ ||
lastState.sort !== this.state.sort || lastState.sort !== this.state.sort ||
lastState.listingType !== this.state.listingType || lastState.listingType !== this.state.listingType ||
lastState.communityId !== this.state.communityId ||
lastState.page !== this.state.page lastState.page !== this.state.page
) { ) {
this.setState({ loading: true, searchText: this.state.q }); this.setState({ loading: true, searchText: this.state.q });
@ -277,6 +353,7 @@ export class Search extends Component<any, SearchState> {
hideMostComments hideMostComments
/> />
</span> </span>
{this.state.communities.length > 0 && this.communityFilter()}
</div> </div>
); );
} }
@ -440,6 +517,32 @@ export class Search extends Component<any, SearchState> {
); );
} }
communityFilter() {
return (
<div class="form-group row">
<label class="col-sm-2 col-form-label" htmlFor="post-community">
{i18n.t("community")}
</label>
<div class="col-sm-4">
<select
class="form-control"
id="community-filter"
value={this.state.communityId}
>
<option value="0">{i18n.t("all")}</option>
{this.state.communities.map(cv => (
<option value={cv.community.id}>
{cv.community.local
? cv.community.name
: `${hostname(cv.community.actor_id)}/${cv.community.name}`}
</option>
))}
</select>
</div>
</div>
);
}
paginator() { paginator() {
return ( return (
<div class="mt-2"> <div class="mt-2">
@ -492,12 +595,45 @@ export class Search extends Component<any, SearchState> {
limit: fetchLimit, limit: fetchLimit,
auth: authField(false), auth: authField(false),
}; };
if (this.state.communityId !== 0) {
form.community_id = this.state.communityId;
}
if (this.state.q != "") { if (this.state.q != "") {
WebSocketService.Instance.send(wsClient.search(form)); WebSocketService.Instance.send(wsClient.search(form));
} }
} }
setupCommunityFilter() {
if (isBrowser()) {
let selectId: any = document.getElementById("community-filter");
if (selectId) {
this.choices = new Choices(selectId, choicesConfig);
this.choices.passedElement.element.addEventListener(
"choice",
(e: any) => {
this.handleCommunityFilterChange(Number(e.detail.choice.value));
},
false
);
this.choices.passedElement.element.addEventListener(
"search",
debounce(async (e: any) => {
let communities = (await fetchCommunities(e.detail.value))
.communities;
this.choices.setChoices(
communities.map(cv => communityToChoice(cv)),
"value",
"label",
true
);
}, 400),
false
);
}
}
}
handleSortChange(val: SortType) { handleSortChange(val: SortType) {
this.updateUrl({ sort: val, page: 1 }); this.updateUrl({ sort: val, page: 1 });
} }
@ -516,12 +652,20 @@ export class Search extends Component<any, SearchState> {
}); });
} }
handleCommunityFilterChange(communityId: number) {
this.updateUrl({
communityId,
page: 1,
});
}
handleSearchSubmit(i: Search, event: any) { handleSearchSubmit(i: Search, event: any) {
event.preventDefault(); event.preventDefault();
i.updateUrl({ i.updateUrl({
q: i.state.searchText, q: i.state.searchText,
type_: i.state.type_, type_: i.state.type_,
listingType: i.state.listingType, listingType: i.state.listingType,
communityId: i.state.communityId,
sort: i.state.sort, sort: i.state.sort,
page: i.state.page, page: i.state.page,
}); });
@ -537,9 +681,11 @@ export class Search extends Component<any, SearchState> {
const typeStr = paramUpdates.type_ || this.state.type_; const typeStr = paramUpdates.type_ || this.state.type_;
const listingTypeStr = paramUpdates.listingType || this.state.listingType; const listingTypeStr = paramUpdates.listingType || this.state.listingType;
const sortStr = paramUpdates.sort || this.state.sort; const sortStr = paramUpdates.sort || this.state.sort;
const communityId =
paramUpdates.communityId || "0" || this.state.communityId;
const page = paramUpdates.page || this.state.page; const page = paramUpdates.page || this.state.page;
this.props.history.push( this.props.history.push(
`/search/q/${qStrEncoded}/type/${typeStr}/sort/${sortStr}/listing_type/${listingTypeStr}/page/${page}` `/search/q/${qStrEncoded}/type/${typeStr}/sort/${sortStr}/listing_type/${listingTypeStr}/community_id/${communityId}/page/${page}`
); );
} }
@ -567,6 +713,11 @@ export class Search extends Component<any, SearchState> {
let data = wsJsonToRes<PostResponse>(msg).data; let data = wsJsonToRes<PostResponse>(msg).data;
createPostLikeFindRes(data.post_view, this.state.searchResponse.posts); createPostLikeFindRes(data.post_view, this.state.searchResponse.posts);
this.setState(this.state); this.setState(this.state);
} else if (op == UserOperation.ListCommunities) {
let data = wsJsonToRes<ListCommunitiesResponse>(msg).data;
this.state.communities = data.communities;
this.setState(this.state);
this.setupCommunityFilter();
} }
} }
} }

View file

@ -133,7 +133,7 @@ export const routes: IRoutePropsWithFetch[] = [
fetchInitialData: req => AdminSettings.fetchInitialData(req), fetchInitialData: req => AdminSettings.fetchInitialData(req),
}, },
{ {
path: `/search/q/:q/type/:type/sort/:sort/listing_type/:listing_type/page/:page`, path: `/search/q/:q/type/:type/sort/:sort/listing_type/:listing_type/community_id/:community_id/page/:page`,
component: Search, component: Search,
fetchInitialData: req => Search.fetchInitialData(req), fetchInitialData: req => Search.fetchInitialData(req),
}, },

View file

@ -46,6 +46,7 @@ import {
LemmyWebsocket, LemmyWebsocket,
PersonViewSafe, PersonViewSafe,
CommunityView, CommunityView,
LemmyHttp,
} from "lemmy-js-client"; } from "lemmy-js-client";
import { import {
@ -70,6 +71,7 @@ import moment from "moment";
import { Subscription } from "rxjs"; import { Subscription } from "rxjs";
import { retryWhen, delay, take } from "rxjs/operators"; import { retryWhen, delay, take } from "rxjs/operators";
import { i18n } from "./i18next"; import { i18n } from "./i18next";
import { httpBase } from "./env";
export const wsClient = new LemmyWebsocket(); export const wsClient = new LemmyWebsocket();
@ -1245,3 +1247,49 @@ export function communityToChoice(cv: CommunityView): ChoicesValue {
}; };
return choice; return choice;
} }
export async function fetchCommunities(q: string) {
let form: Search = {
q,
type_: SearchType.Communities,
sort: SortType.TopAll,
listing_type: ListingType.All,
page: 1,
limit: fetchLimit,
auth: authField(false),
};
let client = new LemmyHttp(httpBase);
return client.search(form);
}
export const choicesConfig = {
shouldSort: false,
classNames: {
containerOuter: "choices",
containerInner: "choices__inner bg-light border-0",
input: "form-control",
inputCloned: "choices__input--cloned",
list: "choices__list",
listItems: "choices__list--multiple",
listSingle: "choices__list--single",
listDropdown: "choices__list--dropdown",
item: "choices__item bg-light",
itemSelectable: "choices__item--selectable",
itemDisabled: "choices__item--disabled",
itemChoice: "choices__item--choice",
placeholder: "choices__placeholder",
group: "choices__group",
groupHeading: "choices__heading",
button: "choices__button",
activeState: "is-active",
focusState: "is-focused",
openState: "is-open",
disabledState: "is-disabled",
highlightedState: "text-info",
selectedState: "text-info",
flippedState: "is-flipped",
loadingState: "is-loading",
noResults: "has-no-results",
noChoices: "has-no-choices",
},
};