Get basic search working
This commit is contained in:
parent
d4580b0cb5
commit
d32c0730fe
|
@ -14787,6 +14787,17 @@
|
|||
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
|
||||
"dev": true
|
||||
},
|
||||
"query-string": {
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-6.4.2.tgz",
|
||||
"integrity": "sha512-DfJqAen17LfLA3rQ+H5S4uXphrF+ANU1lT2ijds4V/Tj4gZxA3gx5/tg1bz7kYCmwna7LyJNCYqO7jNRzo3aLw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"decode-uri-component": "^0.2.0",
|
||||
"split-on-first": "^1.0.0",
|
||||
"strict-uri-encode": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"querystring": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
|
||||
|
@ -17114,6 +17125,12 @@
|
|||
"wbuf": "^1.7.3"
|
||||
}
|
||||
},
|
||||
"split-on-first": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.0.0.tgz",
|
||||
"integrity": "sha512-mjA57TQtdWztVZ9THAjGNpgbuIrNfsNrGa5IyK94NoPaT4N14M+GI4jD7t4arLjFkYRQWdETC5RxFzLWouoB3A==",
|
||||
"dev": true
|
||||
},
|
||||
"split-string": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
||||
|
@ -17291,6 +17308,12 @@
|
|||
"integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
|
||||
"dev": true
|
||||
},
|
||||
"strict-uri-encode": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
|
||||
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=",
|
||||
"dev": true
|
||||
},
|
||||
"string-length": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz",
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
"react-swipeable-views": "^0.13.1",
|
||||
"typescript": "3.3.4000",
|
||||
"notistack": "^0.5.1",
|
||||
"react-web-share-api": "^0.0.2"
|
||||
"react-web-share-api": "^0.0.2",
|
||||
"query-string": "^6.4.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "BROWSER='Safari Technology Preview' react-scripts start",
|
||||
|
|
|
@ -13,6 +13,8 @@ import LocalPage from './pages/Local';
|
|||
import PublicPage from './pages/Public';
|
||||
import Conversation from './pages/Conversation';
|
||||
import NotificationsPage from './pages/Notifications';
|
||||
import SearchPage from './pages/Search';
|
||||
|
||||
import {withSnackbar} from 'notistack';
|
||||
let theme = setHyperspaceTheme(getUserDefaultTheme());
|
||||
|
||||
|
@ -48,6 +50,7 @@ class App extends Component<any, any> {
|
|||
<Route path="/notifications" component={NotificationsPage}/>
|
||||
<Route path="/profile/:profileId" render={props => <ProfilePage {...props}></ProfilePage>}/>
|
||||
<Route path="/conversation/:conversationId" component={Conversation}/>
|
||||
<Route path="/search" component={SearchPage}/>
|
||||
<Route path="/settings" component={Settings}/>
|
||||
<Route path="/about" component={AboutPage}/>
|
||||
</MuiThemeProvider>
|
||||
|
|
|
@ -52,10 +52,7 @@ export class AppLayout extends Component<any, IAppLayoutState> {
|
|||
componentDidMount() {
|
||||
this.streamListener = this.client.stream('/streaming/user');
|
||||
|
||||
this.streamListener.on('connect', () => { console.log ('Streaming notifs!')});
|
||||
|
||||
this.streamListener.on('notification', (notif: Notification) => {
|
||||
console.log("received notif");
|
||||
const notificationCount = this.state.notificationCount + 1;
|
||||
this.setState({ notificationCount });
|
||||
if (!document.hasFocus()) {
|
||||
|
@ -107,6 +104,11 @@ export class AppLayout extends Component<any, IAppLayoutState> {
|
|||
})
|
||||
}
|
||||
|
||||
searchForQuery(what: string) {
|
||||
window.location.href = "/#/search?query=" + what;
|
||||
window.location.reload;
|
||||
}
|
||||
|
||||
titlebar() {
|
||||
const { classes } = this.props;
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
|
@ -216,6 +218,11 @@ export class AppLayout extends Component<any, IAppLayoutState> {
|
|||
root: classes.appBarSearchInputRoot,
|
||||
input: classes.appBarSearchInputInput
|
||||
}}
|
||||
onKeyUp={(event) => {
|
||||
if (event.keyCode === 13) {
|
||||
this.searchForQuery(event.currentTarget.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.appBarFlexGrow}/>
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
import React, { Component } from 'react';
|
||||
import {
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListSubheader,
|
||||
ListItemSecondaryAction,
|
||||
ListItemAvatar,
|
||||
Avatar,
|
||||
Paper,
|
||||
IconButton,
|
||||
withStyles,
|
||||
Typography,
|
||||
Link,
|
||||
CircularProgress
|
||||
} from '@material-ui/core';
|
||||
import OpenInNewIcon from '@material-ui/icons/OpenInNew';
|
||||
import DomainIcon from '@material-ui/icons/Domain';
|
||||
import ChatIcon from '@material-ui/icons/Chat';
|
||||
import PersonIcon from '@material-ui/icons/Person';
|
||||
import {styles} from './PageLayout.styles';
|
||||
import {LinkableIconButton} 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';
|
||||
|
||||
interface ISearchPageState {
|
||||
query: string[] | string;
|
||||
type?: string[] | string;
|
||||
results?: Results;
|
||||
viewIsLoading: boolean;
|
||||
viewDidLoad?: boolean;
|
||||
viewDidError?: boolean;
|
||||
viewDidErrorCode?: string;
|
||||
}
|
||||
|
||||
class SearchPage extends Component<any, ISearchPageState> {
|
||||
|
||||
client: Mastodon;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
this.client = new Mastodon(localStorage.getItem('access_token') as string, localStorage.getItem('baseurl') + "/api/v2");
|
||||
|
||||
let search = this.runQueryCheck();
|
||||
let query;
|
||||
let type;
|
||||
|
||||
if (search.query) {
|
||||
query = search.query;
|
||||
} else {
|
||||
query = "";
|
||||
}
|
||||
|
||||
if (search.type && search.type !== undefined) {
|
||||
type = search.type;
|
||||
}
|
||||
|
||||
this.state = {
|
||||
viewIsLoading: true,
|
||||
query,
|
||||
type
|
||||
}
|
||||
this.searchQuery(query);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props: any) {
|
||||
this.setState({ viewDidLoad: false, viewIsLoading: true, viewDidError: false, viewDidErrorCode: '', results: undefined});
|
||||
|
||||
let newSearch = this.runQueryCheck(props.location);
|
||||
let query;
|
||||
let type;
|
||||
|
||||
if (newSearch.query) {
|
||||
query = newSearch.query;
|
||||
} else {
|
||||
query = "";
|
||||
}
|
||||
|
||||
if (newSearch.type && newSearch.type !== undefined) {
|
||||
type = newSearch.type;
|
||||
}
|
||||
|
||||
this.setState({ query, type });
|
||||
this.searchQuery(query);
|
||||
}
|
||||
|
||||
runQueryCheck(newLocation?: string): ParsedQuery {
|
||||
let searchParams = "";
|
||||
if (newLocation !== undefined && typeof(newLocation) === "string") {
|
||||
searchParams = newLocation.replace("#/search", "");
|
||||
} else {
|
||||
searchParams = location.hash.replace("#/search", "");
|
||||
}
|
||||
return parseParams(searchParams);
|
||||
}
|
||||
|
||||
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' });
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
return (
|
||||
<div className={classes.pageLayoutConstraints}>
|
||||
<ListSubheader>Accounts</ListSubheader>
|
||||
<Paper className={classes.pageListConstraints}>
|
||||
<List>
|
||||
{
|
||||
this.state.results?
|
||||
this.state.results.accounts.map((acct: Account) => {
|
||||
return (
|
||||
<ListItem key={acct.id}>
|
||||
<ListItemAvatar>
|
||||
<Avatar alt={acct.username} src={acct.avatar_static}/>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={acct.display_name || acct.acct} secondary={acct.acct}/>
|
||||
<ListItemSecondaryAction>
|
||||
<LinkableIconButton to={`/profile/${acct.id}`}>
|
||||
<PersonIcon/>
|
||||
</LinkableIconButton>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
);
|
||||
}): null
|
||||
}
|
||||
</List>
|
||||
</Paper>
|
||||
<br/>
|
||||
<ListSubheader>Posts</ListSubheader>
|
||||
{
|
||||
this.state.results?
|
||||
this.state.results.statuses.length > 0?
|
||||
this.state.results.statuses.map((post: Status) => {
|
||||
return <Post key={post.id} post={post} client={this.client}/>
|
||||
}): <Typography variant="caption">No results found.</Typography>: null
|
||||
}
|
||||
{
|
||||
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/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withStyles(styles)(withSnackbar(SearchPage));
|
|
@ -0,0 +1,9 @@
|
|||
import { Account } from "./Account";
|
||||
import { Status } from "./Status";
|
||||
import { Tag } from "./Tag";
|
||||
|
||||
export type Results = {
|
||||
accounts: [Account];
|
||||
statuses: [Status];
|
||||
hashtags: [Tag];
|
||||
}
|
Loading…
Reference in New Issue