diff --git a/package-lock.json b/package-lock.json index 8861623..0414e34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 3485b38..428ca14 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/App.tsx b/src/App.tsx index 62b5c3d..c9f0a63 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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 { }/> + diff --git a/src/components/AppLayout/AppLayout.tsx b/src/components/AppLayout/AppLayout.tsx index db004cf..d67191d 100644 --- a/src/components/AppLayout/AppLayout.tsx +++ b/src/components/AppLayout/AppLayout.tsx @@ -52,10 +52,7 @@ export class AppLayout extends Component { 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 { }) } + 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 { root: classes.appBarSearchInputRoot, input: classes.appBarSearchInputInput }} + onKeyUp={(event) => { + if (event.keyCode === 13) { + this.searchForQuery(event.currentTarget.value); + } + }} />
diff --git a/src/pages/Search.tsx b/src/pages/Search.tsx new file mode 100644 index 0000000..cb7916e --- /dev/null +++ b/src/pages/Search.tsx @@ -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 { + + 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 ( +
+ Accounts + + + { + this.state.results? + this.state.results.accounts.map((acct: Account) => { + return ( + + + + + + + + + + + + ); + }): null + } + + +
+ Posts + { + this.state.results? + this.state.results.statuses.length > 0? + this.state.results.statuses.map((post: Status) => { + return + }): No results found.: null + } + { + 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)); \ No newline at end of file diff --git a/src/types/Search.tsx b/src/types/Search.tsx new file mode 100644 index 0000000..eedb716 --- /dev/null +++ b/src/types/Search.tsx @@ -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]; +} \ No newline at end of file