2019-09-18 19:52:39 +02:00
|
|
|
import React, { Component } from "react";
|
2019-04-02 23:28:04 +02:00
|
|
|
import {
|
2019-09-18 19:52:39 +02:00
|
|
|
List,
|
|
|
|
ListItem,
|
|
|
|
ListItemText,
|
|
|
|
ListSubheader,
|
|
|
|
ListItemSecondaryAction,
|
|
|
|
ListItemAvatar,
|
|
|
|
Avatar,
|
|
|
|
Paper,
|
|
|
|
withStyles,
|
2019-04-02 23:28:04 +02:00
|
|
|
Typography,
|
2019-04-24 23:12:08 +02:00
|
|
|
CircularProgress,
|
|
|
|
Tooltip,
|
|
|
|
IconButton
|
2019-09-18 19:52:39 +02:00
|
|
|
} from "@material-ui/core";
|
|
|
|
import PersonIcon from "@material-ui/icons/Person";
|
|
|
|
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";
|
2020-01-23 16:48:23 +01:00
|
|
|
import Masonry from "react-masonry-css";
|
|
|
|
import { getUserDefaultBool } from "../utilities/settings";
|
2019-04-02 23:28:04 +02:00
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
interface ISearchPageState {
|
2019-04-02 23:28:04 +02:00
|
|
|
query: string[] | string;
|
|
|
|
type?: string[] | string;
|
|
|
|
results?: Results;
|
2019-04-03 03:15:51 +02:00
|
|
|
tagResults?: [Status];
|
2019-04-02 23:28:04 +02:00
|
|
|
viewIsLoading: boolean;
|
|
|
|
viewDidLoad?: boolean;
|
|
|
|
viewDidError?: boolean;
|
|
|
|
viewDidErrorCode?: string;
|
2020-01-23 16:48:23 +01:00
|
|
|
isMasonryLayout: boolean;
|
2019-04-02 23:28:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class SearchPage extends Component<any, ISearchPageState> {
|
|
|
|
client: Mastodon;
|
|
|
|
|
|
|
|
constructor(props: any) {
|
|
|
|
super(props);
|
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
this.client = new Mastodon(
|
|
|
|
localStorage.getItem("access_token") as string,
|
|
|
|
localStorage.getItem("baseurl") + "/api/v2"
|
|
|
|
);
|
2019-04-02 23:28:04 +02:00
|
|
|
|
2019-04-03 03:15:51 +02:00
|
|
|
let searchParams = this.getQueryAndType(props);
|
2019-04-02 23:28:04 +02:00
|
|
|
|
|
|
|
this.state = {
|
|
|
|
viewIsLoading: true,
|
2019-04-03 03:15:51 +02:00
|
|
|
query: searchParams.query,
|
2020-01-23 16:48:23 +01:00
|
|
|
type: searchParams.type,
|
2020-01-23 17:02:46 +01:00
|
|
|
isMasonryLayout: getUserDefaultBool("isMasonryLayout")
|
2019-09-18 19:52:39 +02:00
|
|
|
};
|
2019-04-03 03:15:51 +02:00
|
|
|
|
|
|
|
if (searchParams.type === "tag") {
|
|
|
|
this.searchForPostsWithTags(searchParams.query);
|
|
|
|
} else {
|
|
|
|
this.searchQuery(searchParams.query);
|
|
|
|
}
|
2019-04-02 23:28:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
componentWillReceiveProps(props: any) {
|
2019-09-18 19:52:39 +02:00
|
|
|
this.setState({
|
|
|
|
viewDidLoad: false,
|
|
|
|
viewIsLoading: true,
|
|
|
|
viewDidError: false,
|
|
|
|
viewDidErrorCode: "",
|
|
|
|
results: undefined
|
|
|
|
});
|
2019-04-03 03:15:51 +02:00
|
|
|
let searchParams = this.getQueryAndType(props);
|
|
|
|
this.setState({ query: searchParams.query, type: searchParams.type });
|
|
|
|
if (searchParams.type === "tag") {
|
|
|
|
this.searchForPostsWithTags(searchParams.query);
|
2019-04-02 23:28:04 +02:00
|
|
|
} else {
|
2019-04-03 03:15:51 +02:00
|
|
|
this.searchQuery(searchParams.query);
|
2019-04-02 23:28:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
runQueryCheck(newLocation?: string): ParsedQuery {
|
|
|
|
let searchParams = "";
|
2019-09-18 19:52:39 +02:00
|
|
|
if (newLocation !== undefined && typeof newLocation === "string") {
|
2019-04-02 23:28:04 +02:00
|
|
|
searchParams = newLocation.replace("#/search", "");
|
|
|
|
} else {
|
2020-03-18 17:33:32 +01:00
|
|
|
searchParams = this.props.location.hash.replace("#/search", "");
|
2019-04-02 23:28:04 +02:00
|
|
|
}
|
|
|
|
return parseParams(searchParams);
|
|
|
|
}
|
|
|
|
|
2019-04-03 03:15:51 +02:00
|
|
|
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
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-04-02 23:28:04 +02:00
|
|
|
searchQuery(query: string | string[]) {
|
2019-09-18 19:52:39 +02:00
|
|
|
this.client
|
|
|
|
.get("/search", { q: query })
|
|
|
|
.then((resp: any) => {
|
|
|
|
let results: Results = resp.data;
|
|
|
|
this.setState({
|
|
|
|
results,
|
|
|
|
viewDidLoad: true,
|
|
|
|
viewIsLoading: false
|
|
|
|
});
|
2019-04-02 23:28:04 +02:00
|
|
|
})
|
2019-09-18 19:52:39 +02:00
|
|
|
.catch((err: Error) => {
|
|
|
|
this.setState({
|
|
|
|
viewIsLoading: false,
|
|
|
|
viewDidError: true,
|
|
|
|
viewDidErrorCode: err.message
|
|
|
|
});
|
2019-04-02 23:28:04 +02:00
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
this.props.enqueueSnackbar(
|
|
|
|
`Couldn't search for ${this.state.query}: ${err.name}`,
|
|
|
|
{ variant: "error" }
|
|
|
|
);
|
|
|
|
});
|
2019-04-02 23:28:04 +02:00
|
|
|
}
|
|
|
|
|
2019-04-03 03:15:51 +02:00
|
|
|
searchForPostsWithTags(query: string | string[]) {
|
2019-09-18 19:52:39 +02:00
|
|
|
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
|
|
|
|
});
|
2020-02-16 20:49:55 +01:00
|
|
|
// console.log(this.state.tagResults);
|
2019-04-03 03:15:51 +02:00
|
|
|
})
|
2019-09-18 19:52:39 +02:00
|
|
|
.catch((err: Error) => {
|
|
|
|
this.setState({
|
|
|
|
viewIsLoading: false,
|
|
|
|
viewDidError: true,
|
|
|
|
viewDidErrorCode: err.message
|
|
|
|
});
|
2019-04-03 03:15:51 +02:00
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
this.props.enqueueSnackbar(
|
|
|
|
`Couldn't search for posts with tag ${this.state.query}: ${err.name}`,
|
|
|
|
{ variant: "error" }
|
|
|
|
);
|
|
|
|
});
|
2019-04-03 03:15:51 +02:00
|
|
|
}
|
|
|
|
|
2019-04-24 23:12:08 +02:00
|
|
|
followMemberFromQuery(acct: Account) {
|
2019-09-18 19:52:39 +02:00
|
|
|
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);
|
|
|
|
});
|
2019-04-24 23:12:08 +02:00
|
|
|
}
|
|
|
|
|
2019-04-03 03:15:51 +02:00
|
|
|
showAllAccountsFromQuery() {
|
2019-04-02 23:28:04 +02:00
|
|
|
const { classes } = this.props;
|
|
|
|
return (
|
2020-01-23 16:48:23 +01:00
|
|
|
<div className={classes.pageLayoutConstraints}>
|
2019-04-02 23:28:04 +02:00
|
|
|
<ListSubheader>Accounts</ListSubheader>
|
2019-04-27 18:43:43 +02:00
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
{this.state.results &&
|
|
|
|
this.state.results.accounts.length > 0 ? (
|
|
|
|
<Paper className={classes.pageListConstraints}>
|
|
|
|
<List>
|
|
|
|
{this.state.results.accounts.map(
|
|
|
|
(acct: Account) => {
|
|
|
|
return (
|
2019-04-03 03:15:51 +02:00
|
|
|
<ListItem key={acct.id}>
|
|
|
|
<ListItemAvatar>
|
2019-09-18 19:52:39 +02:00
|
|
|
<LinkableAvatar
|
|
|
|
to={`/profile/${acct.id}`}
|
|
|
|
alt={acct.username}
|
|
|
|
src={acct.avatar_static}
|
|
|
|
/>
|
2019-04-03 03:15:51 +02:00
|
|
|
</ListItemAvatar>
|
2019-09-18 19:52:39 +02:00
|
|
|
<ListItemText
|
|
|
|
primary={
|
|
|
|
acct.display_name ||
|
|
|
|
acct.acct
|
|
|
|
}
|
|
|
|
secondary={acct.acct}
|
|
|
|
/>
|
2019-04-03 03:15:51 +02:00
|
|
|
<ListItemSecondaryAction>
|
2019-04-24 23:12:08 +02:00
|
|
|
<Tooltip title="View profile">
|
2019-09-18 19:52:39 +02:00
|
|
|
<LinkableIconButton
|
|
|
|
to={`/profile/${acct.id}`}
|
|
|
|
>
|
|
|
|
<AssignmentIndIcon />
|
2019-04-24 23:12:08 +02:00
|
|
|
</LinkableIconButton>
|
|
|
|
</Tooltip>
|
|
|
|
<Tooltip title="Follow">
|
2019-09-18 19:52:39 +02:00
|
|
|
<IconButton
|
|
|
|
onClick={() =>
|
|
|
|
this.followMemberFromQuery(
|
|
|
|
acct
|
|
|
|
)
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<PersonAddIcon />
|
2019-04-24 23:12:08 +02:00
|
|
|
</IconButton>
|
|
|
|
</Tooltip>
|
2019-04-03 03:15:51 +02:00
|
|
|
</ListItemSecondaryAction>
|
|
|
|
</ListItem>
|
2019-09-18 19:52:39 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
)}
|
|
|
|
</List>
|
|
|
|
</Paper>
|
|
|
|
) : (
|
|
|
|
<Typography
|
|
|
|
variant="caption"
|
|
|
|
className={classes.pageLayoutEmptyTextConstraints}
|
|
|
|
>
|
|
|
|
No results found
|
|
|
|
</Typography>
|
|
|
|
)}
|
2019-04-27 18:43:43 +02:00
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
<br />
|
2019-04-03 03:15:51 +02:00
|
|
|
</div>
|
2019-09-18 19:52:39 +02:00
|
|
|
);
|
2019-04-03 03:15:51 +02:00
|
|
|
}
|
|
|
|
|
2020-01-23 16:48:23 +01:00
|
|
|
renderPosts(posts: Status[]) {
|
|
|
|
const { classes } = this.props;
|
|
|
|
const postComponents = posts.map((post: Status) => {
|
2020-01-23 17:02:46 +01:00
|
|
|
return <Post key={post.id} post={post} client={this.client} />;
|
2020-01-23 16:48:23 +01:00
|
|
|
});
|
|
|
|
if (this.state.isMasonryLayout) {
|
|
|
|
return (
|
|
|
|
<Masonry
|
|
|
|
className={classes.masonryGrid}
|
|
|
|
columnClassName={classes["my-masonry-grid_column"]}
|
|
|
|
breakpointCols={{
|
|
|
|
default: 4,
|
|
|
|
2000: 3,
|
|
|
|
1400: 2,
|
2020-01-23 17:02:46 +01:00
|
|
|
1050: 1
|
2020-01-23 16:48:23 +01:00
|
|
|
}}
|
|
|
|
>
|
|
|
|
{postComponents}
|
|
|
|
</Masonry>
|
|
|
|
);
|
|
|
|
} else {
|
2020-01-23 17:02:46 +01:00
|
|
|
return <div>{postComponents}</div>;
|
2020-01-23 16:48:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-03 03:15:51 +02:00
|
|
|
showAllPostsFromQuery() {
|
2019-09-18 19:52:39 +02:00
|
|
|
const { classes } = this.props;
|
2020-01-23 16:48:23 +01:00
|
|
|
const containerClasses = `${classes.pageLayoutConstraints} ${
|
|
|
|
this.state.isMasonryLayout
|
|
|
|
? classes.pageLayoutMasonry + " " + classes.noTopPaddingMargin
|
|
|
|
: ""
|
|
|
|
}`;
|
2019-04-03 03:15:51 +02:00
|
|
|
return (
|
2020-01-23 16:48:23 +01:00
|
|
|
<div className={containerClasses}>
|
2019-04-02 23:28:04 +02:00
|
|
|
<ListSubheader>Posts</ListSubheader>
|
2019-09-18 19:52:39 +02:00
|
|
|
{this.state.results ? (
|
|
|
|
this.state.results.statuses.length > 0 ? (
|
2020-01-23 16:48:23 +01:00
|
|
|
this.renderPosts(this.state.results.statuses)
|
2019-09-18 19:52:39 +02:00
|
|
|
) : (
|
|
|
|
<Typography
|
|
|
|
variant="caption"
|
|
|
|
className={classes.pageLayoutEmptyTextConstraints}
|
|
|
|
>
|
|
|
|
No results found.
|
|
|
|
</Typography>
|
|
|
|
)
|
|
|
|
) : null}
|
2019-04-03 03:15:51 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
showAllPostsWithTag() {
|
2019-09-18 19:52:39 +02:00
|
|
|
const { classes } = this.props;
|
2020-01-23 16:48:23 +01:00
|
|
|
const containerClasses = `${classes.pageLayoutMaxConstraints} ${
|
2020-01-23 17:02:46 +01:00
|
|
|
this.state.isMasonryLayout ? classes.pageLayoutMasonry : ""
|
2020-01-23 16:48:23 +01:00
|
|
|
}`;
|
2019-04-03 03:15:51 +02:00
|
|
|
return (
|
2020-01-23 16:48:23 +01:00
|
|
|
<div className={containerClasses}>
|
2019-04-03 03:15:51 +02:00
|
|
|
<ListSubheader>Tagged posts</ListSubheader>
|
2019-09-18 19:52:39 +02:00
|
|
|
{this.state.tagResults ? (
|
|
|
|
this.state.tagResults.length > 0 ? (
|
2020-01-23 16:48:23 +01:00
|
|
|
this.renderPosts(this.state.tagResults)
|
2019-09-18 19:52:39 +02:00
|
|
|
) : (
|
|
|
|
<Typography
|
|
|
|
variant="caption"
|
|
|
|
className={classes.pageLayoutEmptyTextConstraints}
|
|
|
|
>
|
|
|
|
No results found.
|
|
|
|
</Typography>
|
|
|
|
)
|
|
|
|
) : null}
|
2019-04-03 03:15:51 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const { classes } = this.props;
|
|
|
|
return (
|
2020-01-23 16:48:23 +01:00
|
|
|
<div>
|
2019-09-18 19:52:39 +02:00
|
|
|
{this.state.type && this.state.type === "tag" ? (
|
|
|
|
this.showAllPostsWithTag()
|
|
|
|
) : (
|
|
|
|
<div>
|
|
|
|
{this.showAllAccountsFromQuery()}
|
|
|
|
{this.showAllPostsFromQuery()}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
{this.state.viewDidError ? (
|
|
|
|
<Paper className={classes.errorCard}>
|
|
|
|
<Typography variant="h4">Bummer.</Typography>
|
|
|
|
<Typography variant="h6">
|
|
|
|
Something went wrong when loading this timeline.
|
|
|
|
</Typography>
|
|
|
|
<Typography>
|
|
|
|
{this.state.viewDidErrorCode
|
|
|
|
? this.state.viewDidErrorCode
|
|
|
|
: ""}
|
|
|
|
</Typography>
|
|
|
|
</Paper>
|
|
|
|
) : (
|
|
|
|
<span />
|
|
|
|
)}
|
|
|
|
{this.state.viewIsLoading ? (
|
|
|
|
<div style={{ textAlign: "center" }}>
|
|
|
|
<CircularProgress
|
|
|
|
className={classes.progress}
|
|
|
|
color="primary"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<span />
|
|
|
|
)}
|
2019-04-02 23:28:04 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
export default withStyles(styles)(withSnackbar(SearchPage));
|