import React, { Component } from "react"; import { List, ListItem, ListItemText, ListSubheader, ListItemSecondaryAction, ListItemAvatar, Paper, withStyles, Typography, CircularProgress, Tooltip, IconButton } from "@material-ui/core"; import AssignmentIndIcon from "@material-ui/icons/AssignmentInd"; import PersonAddIcon from "@material-ui/icons/PersonAdd"; import { styles } from "./PageLayout.styles"; import { LinkableIconButton, LinkableAvatar } from "../interfaces/overrides"; import Mastodon from "megalodon"; import { parse as parseParams, ParsedQuery } from "query-string"; import { Results } from "../types/Search"; import { withSnackbar } from "notistack"; import Post from "../components/Post"; import { Status } from "../types/Status"; import { Account } from "../types/Account"; import Masonry from "react-masonry-css"; import { getUserDefaultBool } from "../utilities/settings"; interface ISearchPageState { query: string[] | string; type?: string[] | string; results?: Results; tagResults?: [Status]; viewIsLoading: boolean; viewDidLoad?: boolean; viewDidError?: boolean; viewDidErrorCode?: string; isMasonryLayout: boolean; } class SearchPage extends Component { client: Mastodon; constructor(props: any) { super(props); this.client = new Mastodon( localStorage.getItem("access_token") as string, localStorage.getItem("baseurl") + "/api/v2" ); let searchParams = this.getQueryAndType(props); this.state = { viewIsLoading: true, query: searchParams.query, type: searchParams.type, isMasonryLayout: getUserDefaultBool("isMasonryLayout") }; if (searchParams.type === "tag") { this.searchForPostsWithTags(searchParams.query); } else { this.searchQuery(searchParams.query); } } componentWillReceiveProps(props: any) { this.setState({ viewDidLoad: false, viewIsLoading: true, viewDidError: false, viewDidErrorCode: "", results: undefined }); let searchParams = this.getQueryAndType(props); this.setState({ query: searchParams.query, type: searchParams.type }); if (searchParams.type === "tag") { this.searchForPostsWithTags(searchParams.query); } else { this.searchQuery(searchParams.query); } } runQueryCheck(newLocation?: string): ParsedQuery { let searchParams = ""; if (newLocation !== undefined && typeof newLocation === "string") { searchParams = newLocation.replace("#/search", ""); } else { searchParams = this.props.location.hash.replace("#/search", ""); } return parseParams(searchParams); } getQueryAndType(props: any) { let newSearch = this.runQueryCheck(props.location); let query: string | string[]; let type; if (newSearch.query) { if (newSearch.query.toString().startsWith("tag:")) { type = "tag"; query = newSearch.query.toString().replace("tag:", ""); } else { query = newSearch.query; } } else { query = ""; } if (newSearch.type && newSearch.type !== undefined) { type = newSearch.type; } return { query: query, type: type }; } searchQuery(query: string | string[]) { this.client .get("/search", { q: query }) .then((resp: any) => { let results: Results = resp.data; this.setState({ results, viewDidLoad: true, viewIsLoading: false }); }) .catch((err: Error) => { this.setState({ viewIsLoading: false, viewDidError: true, viewDidErrorCode: err.message }); this.props.enqueueSnackbar( `Couldn't search for ${this.state.query}: ${err.name}`, { variant: "error" } ); }); } searchForPostsWithTags(query: string | string[]) { let client = new Mastodon( localStorage.getItem("access_token") as string, localStorage.getItem("baseurl") + "/api/v1" ); client .get(`/timelines/tag/${query}`) .then((resp: any) => { let tagResults: [Status] = resp.data; this.setState({ tagResults, viewDidLoad: true, viewIsLoading: false }); // console.log(this.state.tagResults); }) .catch((err: Error) => { this.setState({ viewIsLoading: false, viewDidError: true, viewDidErrorCode: err.message }); this.props.enqueueSnackbar( `Couldn't search for posts with tag ${this.state.query}: ${err.name}`, { variant: "error" } ); }); } followMemberFromQuery(acct: Account) { let client = new Mastodon( localStorage.getItem("access_token") as string, localStorage.getItem("baseurl") + "/api/v1" ); client .post(`/accounts/${acct.id}/follow`) .then((resp: any) => { this.props.enqueueSnackbar( "You are now following this account." ); }) .catch((err: Error) => { this.props.enqueueSnackbar( "Couldn't follow account: " + err.name, { variant: "error" } ); console.error(err.message); }); } showAllAccountsFromQuery() { const { classes } = this.props; return (
Accounts {this.state.results && this.state.results.accounts.length > 0 ? ( {this.state.results.accounts.map( (acct: Account) => { return ( this.followMemberFromQuery( acct ) } > ); } )} ) : ( No results found )}
); } renderPosts(posts: Status[]) { const { classes } = this.props; const postComponents = posts.map((post: Status) => { return ; }); if (this.state.isMasonryLayout) { return ( {postComponents} ); } else { return
{postComponents}
; } } showAllPostsFromQuery() { const { classes } = this.props; const containerClasses = `${classes.pageLayoutConstraints} ${ this.state.isMasonryLayout ? classes.pageLayoutMasonry + " " + classes.noTopPaddingMargin : "" }`; return (
Posts {this.state.results ? ( this.state.results.statuses.length > 0 ? ( this.renderPosts(this.state.results.statuses) ) : ( No results found. ) ) : null}
); } showAllPostsWithTag() { const { classes } = this.props; const containerClasses = `${classes.pageLayoutMaxConstraints} ${ this.state.isMasonryLayout ? classes.pageLayoutMasonry : "" }`; return (
Tagged posts {this.state.tagResults ? ( this.state.tagResults.length > 0 ? ( this.renderPosts(this.state.tagResults) ) : ( No results found. ) ) : null}
); } render() { const { classes } = this.props; return (
{this.state.type && this.state.type === "tag" ? ( this.showAllPostsWithTag() ) : (
{this.showAllAccountsFromQuery()} {this.showAllPostsFromQuery()}
)} {this.state.viewDidError ? ( Bummer. Something went wrong when loading this timeline. {this.state.viewDidErrorCode ? this.state.viewDidErrorCode : ""} ) : ( )} {this.state.viewIsLoading ? (
) : ( )}
); } } export default withStyles(styles)(withSnackbar(SearchPage));