Merge branch 'beta7' into thread-and-settings-redesigns

This commit is contained in:
Marquis Kurt 2019-10-04 13:08:59 -04:00 committed by GitHub
commit 1d2c77c429
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 744 additions and 305 deletions

48
package-lock.json generated
View File

@ -3220,6 +3220,14 @@
"integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
"dev": true "dev": true
}, },
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"requires": {
"file-uri-to-path": "1.0.0"
}
},
"bluebird": { "bluebird": {
"version": "3.5.4", "version": "3.5.4",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz",
@ -7262,10 +7270,13 @@
} }
}, },
"eslint-utils": { "eslint-utils": {
"version": "1.3.1", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz",
"integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==",
"dev": true "dev": true,
"requires": {
"eslint-visitor-keys": "^1.0.0"
}
}, },
"eslint-visitor-keys": { "eslint-visitor-keys": {
"version": "1.0.0", "version": "1.0.0",
@ -7325,6 +7336,11 @@
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
"dev": true "dev": true
}, },
"event-target-shim": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-1.1.1.tgz",
"integrity": "sha1-qG5e5r2qFgVEddp5fM3fDFVphJE="
},
"eventemitter3": { "eventemitter3": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
@ -8022,6 +8038,11 @@
"schema-utils": "^1.0.0" "schema-utils": "^1.0.0"
} }
}, },
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
},
"filename-regex": { "filename-regex": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
@ -9448,9 +9469,9 @@
"dev": true "dev": true
}, },
"handlebars": { "handlebars": {
"version": "4.1.2", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.4.0.tgz",
"integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", "integrity": "sha512-xkRtOt3/3DzTKMOt3xahj2M/EqNhY988T+imYSlMgs5fVhLN2fmKVVj0LtEGmb+3UUYV5Qmm1052Mm3dIQxOvw==",
"dev": true, "dev": true,
"requires": { "requires": {
"neo-async": "^2.6.0", "neo-async": "^2.6.0",
@ -12840,6 +12861,16 @@
} }
} }
}, },
"node-mac-notifier": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/node-mac-notifier/-/node-mac-notifier-1.2.0.tgz",
"integrity": "sha512-+9FZ01BbPMv3pQVRWgPlaIKbhQl35Pn3WmRg96zIrCJHb4XvClnAqc0+aPfHrWs8o1PYMAQFeYK5tF69ljkKQw==",
"requires": {
"bindings": "^1.2.1",
"event-target-shim": "^1.1.1",
"uuid": "^3.3.2"
}
},
"node-notifier": { "node-notifier": {
"version": "5.4.0", "version": "5.4.0",
"resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz",
@ -20242,8 +20273,7 @@
"uuid": { "uuid": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
"dev": true
}, },
"validate-npm-package-license": { "validate-npm-package-license": {
"version": "3.0.4", "version": "3.0.4",

View File

@ -1,6 +1,6 @@
{ {
"version": "1.0.0beta7", "version": "1.0.0beta7",
"location": "desktop", "location": "https://localhost:3000",
"branding": { "branding": {
"name": "Hyperspace", "name": "Hyperspace",
"logo": "logo.svg", "logo": "logo.svg",

View File

@ -20,7 +20,7 @@ let mainWindow;
// to when authorizing Hyperspace. // to when authorizing Hyperspace.
protocol.registerSchemesAsPrivileged([ protocol.registerSchemesAsPrivileged([
{ scheme: 'hyperspace', privileges: { standard: true, secure: true } } { scheme: 'hyperspace', privileges: { standard: true, secure: true } }
]) ]);
/** /**
* Determine whether the desktop app is on macOS * Determine whether the desktop app is on macOS
@ -217,15 +217,8 @@ function createMenubar() {
click() { click() {
safelyGoTo("hyperspace://hyperspace/app/#compose") safelyGoTo("hyperspace://hyperspace/app/#compose")
} }
}, }
{ type: 'separator' },
{
label: 'Edit Profile',
accelerator: "Shift+CmdOrCtrl+P",
click() {
safelyGoTo("hyperspace://hyperspace/app/#/you")
}
},
] ]
}, },
{ {
@ -283,7 +276,7 @@ function createMenubar() {
] ]
}, },
{ {
label: "Places", label: "Timelines",
submenu: [ submenu: [
{ {
label: 'Home', label: 'Home',
@ -307,27 +300,53 @@ function createMenubar() {
} }
}, },
{ {
label: 'Recommendations', label: 'Messages',
accelerator: "CmdOrCtrl+3", accelerator: "CmdOrCtrl+3",
click() {
safelyGoTo("hyperspace://hyperspace/app/#/messages")
}
}
]
},
{
label: "Account",
submenu: [
{
label: 'Notifications',
accelerator: "Alt+CmdOrCtrl+N",
click() {
safelyGoTo("hyperspace://hyperspace/app/#/notifications")
}
},
{
label: 'Recommendations...',
accelerator: "Alt+CmdOrCtrl+R",
click() { click() {
safelyGoTo("hyperspace://hyperspace/app/#/recommended") safelyGoTo("hyperspace://hyperspace/app/#/recommended")
} }
}, },
{ type: 'separator' }, { type: 'separator' },
{ {
label: 'Notifications', label: 'Edit Profile',
accelerator: "CmdOrCtrl+4", accelerator: "Shift+CmdOrCtrl+P",
click() { click() {
safelyGoTo("hyperspace://hyperspace/app/#/notifications") safelyGoTo("hyperspace://hyperspace/app/#/you")
} }
}, },
{ {
label: 'Messages', label: 'Blocked Servers',
accelerator: "CmdOrCtrl+5", accelerator: "Shift+CmdOrCtrl+B",
click() { click() {
safelyGoTo("hyperspace://hyperspace/app/#/messages") safelyGoTo("hyperspace://hyperspace/app/#/blocked")
} }
}, },
{ type: 'separator'},
{
label: 'Switch Accounts...',
click() {
safelyGoTo("hyperspace://hyperspace/app/#/welcome")
}
}
] ]
}, },
{ {
@ -356,7 +375,7 @@ function createMenubar() {
} }
] ]
} }
] ];
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
menuBar.unshift({ menuBar.unshift({
@ -385,7 +404,7 @@ function createMenubar() {
{ type: 'separator' }, { type: 'separator' },
{ role: 'quit' } { role: 'quit' }
] ]
}) });
// Edit menu // Edit menu
menuBar[2].submenu.push( menuBar[2].submenu.push(
@ -397,10 +416,10 @@ function createMenubar() {
{ role: 'stopspeaking' } { role: 'stopspeaking' }
] ]
} }
) );
// Window menu // Window menu
menuBar[5].submenu = [ menuBar[6].submenu = [
{ role: 'close' }, { role: 'close' },
{ role: 'minimize' }, { role: 'minimize' },
{ role: 'zoom' }, { role: 'zoom' },

View File

@ -3,7 +3,7 @@ import { MuiThemeProvider, CssBaseline, withStyles } from "@material-ui/core";
import { setHyperspaceTheme, darkMode } from "./utilities/themes"; import { setHyperspaceTheme, darkMode } from "./utilities/themes";
import AppLayout from "./components/AppLayout"; import AppLayout from "./components/AppLayout";
import { styles } from "./App.styles"; import { styles } from "./App.styles";
import { Route } from "react-router-dom"; import { Route, withRouter } from "react-router-dom";
import AboutPage from "./pages/About"; import AboutPage from "./pages/About";
import Settings from "./pages/Settings"; import Settings from "./pages/Settings";
import { getUserDefaultBool, getUserDefaultTheme } from "./utilities/settings"; import { getUserDefaultBool, getUserDefaultTheme } from "./utilities/settings";
@ -27,14 +27,22 @@ import { userLoggedIn } from "./utilities/accounts";
import { isDarwinApp } from "./utilities/desktop"; import { isDarwinApp } from "./utilities/desktop";
let theme = setHyperspaceTheme(getUserDefaultTheme()); let theme = setHyperspaceTheme(getUserDefaultTheme());
class App extends Component<any, any> { interface IAppState {
theme: any;
showLayout: boolean;
}
class App extends Component<any, IAppState> {
offline: any; offline: any;
unlisten: any;
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.state = { this.state = {
theme: theme theme: theme,
showLayout:
userLoggedIn() && !window.location.hash.includes("#/welcome")
}; };
} }
@ -43,17 +51,33 @@ class App extends Component<any, any> {
this.state.theme, this.state.theme,
getUserDefaultBool("darkModeEnabled") getUserDefaultBool("darkModeEnabled")
); );
this.setState({ theme: newTheme }); this.setState({
theme: newTheme,
showLayout:
userLoggedIn() && !window.location.hash.includes("#/welcome")
});
} }
componentDidMount() { componentDidMount() {
this.removeBodyBackground(); this.removeBodyBackground();
this.unlisten = this.props.history.listen(
(location: Location, action: any) => {
this.setState({
showLayout:
userLoggedIn() &&
!location.pathname.includes("/welcome")
});
}
);
} }
componentDidUpdate() { componentDidUpdate() {
this.removeBodyBackground(); this.removeBodyBackground();
} }
componentWillUnmount() {
this.unlisten();
}
removeBodyBackground() { removeBodyBackground() {
if (isDarwinApp()) { if (isDarwinApp()) {
@ -71,7 +95,7 @@ class App extends Component<any, any> {
<CssBaseline /> <CssBaseline />
<Route path="/welcome" component={WelcomePage} /> <Route path="/welcome" component={WelcomePage} />
<div> <div>
{userLoggedIn() ? <AppLayout /> : null} {this.state.showLayout ? <AppLayout /> : null}
<PrivateRoute exact path="/" component={HomePage} /> <PrivateRoute exact path="/" component={HomePage} />
<PrivateRoute path="/home" component={HomePage} /> <PrivateRoute path="/home" component={HomePage} />
<PrivateRoute path="/local" component={LocalPage} /> <PrivateRoute path="/local" component={LocalPage} />
@ -91,7 +115,7 @@ class App extends Component<any, any> {
/> />
<PrivateRoute path="/search" component={SearchPage} /> <PrivateRoute path="/search" component={SearchPage} />
<PrivateRoute path="/settings" component={Settings} /> <PrivateRoute path="/settings" component={Settings} />
<PrivateRoute path="/blocked" component={Blocked}/> <PrivateRoute path="/blocked" component={Blocked} />
<PrivateRoute path="/you" component={You} /> <PrivateRoute path="/you" component={You} />
<PrivateRoute path="/about" component={AboutPage} /> <PrivateRoute path="/about" component={AboutPage} />
<PrivateRoute path="/compose" component={Composer} /> <PrivateRoute path="/compose" component={Composer} />
@ -105,4 +129,5 @@ class App extends Component<any, any> {
} }
} }
export default withStyles(styles)(withSnackbar(App)); // @ts-ignore
export default withStyles(styles)(withSnackbar(withRouter(App)));

View File

@ -39,10 +39,10 @@ import GroupIcon from "@material-ui/icons/Group";
import SettingsIcon from "@material-ui/icons/Settings"; import SettingsIcon from "@material-ui/icons/Settings";
import InfoIcon from "@material-ui/icons/Info"; import InfoIcon from "@material-ui/icons/Info";
import CreateIcon from "@material-ui/icons/Create"; import CreateIcon from "@material-ui/icons/Create";
//import SupervisedUserCircleIcon from '@material-ui/icons/SupervisedUserCircle'; import SupervisedUserCircleIcon from "@material-ui/icons/SupervisedUserCircle";
import ExitToAppIcon from "@material-ui/icons/ExitToApp"; import ExitToAppIcon from "@material-ui/icons/ExitToApp";
import { styles } from "./AppLayout.styles"; import { styles } from "./AppLayout.styles";
import { UAccount } from "../../types/Account"; import { MultiAccount, UAccount } from "../../types/Account";
import { import {
LinkableListItem, LinkableListItem,
LinkableIconButton, LinkableIconButton,
@ -53,8 +53,16 @@ import { Notification } from "../../types/Notification";
import { sendNotificationRequest } from "../../utilities/notifications"; import { sendNotificationRequest } from "../../utilities/notifications";
import { withSnackbar } from "notistack"; import { withSnackbar } from "notistack";
import { getConfig, getUserDefaultBool } from "../../utilities/settings"; import { getConfig, getUserDefaultBool } from "../../utilities/settings";
import { isDesktopApp, isDarwinApp } from "../../utilities/desktop"; import {
isDesktopApp,
isDarwinApp,
getElectronApp
} from "../../utilities/desktop";
import { Config } from "../../types/Config"; import { Config } from "../../types/Config";
import {
getAccountRegistry,
removeAccountFromRegistry
} from "../../utilities/accounts";
interface IAppLayoutState { interface IAppLayoutState {
acctMenuOpen: boolean; acctMenuOpen: boolean;
@ -92,23 +100,7 @@ export class AppLayout extends Component<any, IAppLayoutState> {
} }
componentDidMount() { componentDidMount() {
let acct = localStorage.getItem("account"); this.getAccountData();
if (acct) {
this.setState({ currentUser: JSON.parse(acct) });
} else {
this.client
.get("/accounts/verify_credentials")
.then((resp: any) => {
let data: UAccount = resp.data;
this.setState({ currentUser: data });
})
.catch((err: Error) => {
this.props.enqueueSnackbar(
"Couldn't find profile info: " + err.name
);
console.error(err.message);
});
}
getConfig().then((result: any) => { getConfig().then((result: any) => {
if (result !== undefined) { if (result !== undefined) {
@ -126,6 +118,24 @@ export class AppLayout extends Component<any, IAppLayoutState> {
this.streamNotifications(); this.streamNotifications();
} }
getAccountData() {
this.client
.get("/accounts/verify_credentials")
.then((resp: any) => {
let data: UAccount = resp.data;
this.setState({ currentUser: data });
sessionStorage.setItem("id", data.id);
})
.catch((err: Error) => {
this.props.enqueueSnackbar(
"Couldn't find profile info: " + err.name
);
console.error(err.message);
let acct = localStorage.getItem("account") as string;
this.setState({ currentUser: JSON.parse(acct) });
});
}
streamNotifications() { streamNotifications() {
this.streamListener = this.client.stream("/streaming/user"); this.streamListener = this.client.stream("/streaming/user");
@ -139,6 +149,11 @@ export class AppLayout extends Component<any, IAppLayoutState> {
this.streamListener.on("notification", (notif: Notification) => { this.streamListener.on("notification", (notif: Notification) => {
const notificationCount = this.state.notificationCount + 1; const notificationCount = this.state.notificationCount + 1;
this.setState({ notificationCount }); this.setState({ notificationCount });
if (isDesktopApp()) {
getElectronApp().setBadgeCount(notificationCount);
}
if (!document.hasFocus()) { if (!document.hasFocus()) {
let primaryMessage = ""; let primaryMessage = "";
let secondaryMessage = ""; let secondaryMessage = "";
@ -220,16 +235,28 @@ export class AppLayout extends Component<any, IAppLayoutState> {
window.location.href = isDesktopApp() window.location.href = isDesktopApp()
? "hyperspace://hyperspace/app/index.html#/search?query=" + what ? "hyperspace://hyperspace/app/index.html#/search?query=" + what
: "/#/search?query=" + what; : "/#/search?query=" + what;
window.location.reload; window.location.reload();
} }
logOutAndRestart() { logOutAndRestart() {
let loginData = localStorage.getItem("login"); let loginData = localStorage.getItem("login");
if (loginData) { if (loginData) {
let registry = getAccountRegistry();
registry.forEach((registryItem: MultiAccount, index: number) => {
if (
registryItem.access_token ===
localStorage.getItem("access_token")
) {
removeAccountFromRegistry(index);
}
});
let items = ["login", "account", "baseurl", "access_token"]; let items = ["login", "account", "baseurl", "access_token"];
items.forEach(entry => { items.forEach(entry => {
localStorage.removeItem(entry); localStorage.removeItem(entry);
}); });
window.location.reload(); window.location.reload();
} }
} }
@ -238,6 +265,10 @@ export class AppLayout extends Component<any, IAppLayoutState> {
if (!getUserDefaultBool("displayAllOnNotificationBadge")) { if (!getUserDefaultBool("displayAllOnNotificationBadge")) {
this.setState({ notificationCount: 0 }); this.setState({ notificationCount: 0 });
} }
if (isDesktopApp() && getElectronApp().getBadgeCount() > 0) {
getElectronApp().setBadgeCount(0);
}
} }
titlebar() { titlebar() {
@ -307,10 +338,22 @@ export class AppLayout extends Component<any, IAppLayoutState> {
} }
/> />
</LinkableListItem> </LinkableListItem>
{/* <LinkableListItem button key="acctSwitch-module" to="/switchacct"> <LinkableListItem
<ListItemIcon><SupervisedUserCircleIcon/></ListItemIcon> button
<ListItemText primary="Switch account"/> key="acctSwitch-module"
</LinkableListItem> */} to="/welcome"
>
<ListItemIcon>
<SupervisedUserCircleIcon />
</ListItemIcon>
<ListItemText
primary={
getAccountRegistry().length > 1
? "Switch account"
: "Add account"
}
/>
</LinkableListItem>
<ListItem <ListItem
button button
key="acctLogout-mobile" key="acctLogout-mobile"
@ -571,7 +614,14 @@ export class AppLayout extends Component<any, IAppLayoutState> {
Edit profile Edit profile
</ListItemText> </ListItemText>
</LinkableListItem> </LinkableListItem>
{/* <MenuItem>Switch account</MenuItem> */} <LinkableListItem to={"/welcome"}>
<ListItemText>
{getAccountRegistry()
.length > 1
? "Switch account"
: "Add account"}
</ListItemText>
</LinkableListItem>
<MenuItem <MenuItem
onClick={() => onClick={() =>
this.toggleLogOutDialog() this.toggleLogOutDialog()
@ -613,48 +663,7 @@ export class AppLayout extends Component<any, IAppLayoutState> {
</Hidden> </Hidden>
</nav> </nav>
</div> </div>
<Dialog {this.logoutDialog()}
open={this.state.logOutOpen}
onClose={() => this.toggleLogOutDialog()}
>
<DialogTitle id="alert-dialog-title">
Log out of{" "}
{this.state.brandName
? this.state.brandName
: "Hyperspace"}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
You'll need to remove{" "}
{this.state.brandName
? this.state.brandName
: "Hyperspace"}{" "}
from your list of authorized apps and log in again
if you want to use{" "}
{this.state.brandName
? this.state.brandName
: "Hyperspace"}
.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
onClick={() => this.toggleLogOutDialog()}
color="primary"
autoFocus
>
Cancel
</Button>
<Button
onClick={() => {
this.logOutAndRestart();
}}
color="primary"
>
Log out
</Button>
</DialogActions>
</Dialog>
<Tooltip title="Create a new post"> <Tooltip title="Create a new post">
<LinkableFab <LinkableFab
to="/compose" to="/compose"
@ -668,6 +677,57 @@ export class AppLayout extends Component<any, IAppLayoutState> {
</div> </div>
); );
} }
logoutDialog() {
return (
<Dialog
open={this.state.logOutOpen}
onClose={() => this.toggleLogOutDialog()}
>
<DialogTitle id="alert-dialog-title">
Log out of{" "}
{this.state.brandName ? this.state.brandName : "Hyperspace"}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
<Typography paragraph>
You'll need to remove{" "}
{this.state.brandName
? this.state.brandName
: "Hyperspace"}{" "}
from your list of authorized apps and log in again
if you want to use{" "}
{this.state.brandName
? this.state.brandName
: "Hyperspace"}
.
</Typography>
<Typography paragraph>
Logging out will also remove this account from the
account list.
</Typography>
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
onClick={() => this.toggleLogOutDialog()}
color="primary"
autoFocus
>
Cancel
</Button>
<Button
onClick={() => {
this.logOutAndRestart();
}}
color="primary"
>
Log out
</Button>
</DialogActions>
</Dialog>
);
}
} }
export default withStyles(styles)(withSnackbar(AppLayout)); export default withStyles(styles)(withSnackbar(AppLayout));

View File

@ -1,33 +1,32 @@
import React from "react"; import React from "react";
import { import {
Typography,
IconButton,
Card,
CardHeader,
Avatar, Avatar,
CardContent,
CardActions,
withStyles,
Menu,
MenuItem,
Chip,
Divider,
CardMedia,
CardActionArea,
ExpansionPanel,
ExpansionPanelSummary,
ExpansionPanelDetails,
Zoom,
Tooltip,
RadioGroup,
Radio,
FormControlLabel,
Button, Button,
Card,
CardActionArea,
CardActions,
CardContent,
CardHeader,
CardMedia,
Dialog, Dialog,
DialogTitle, DialogActions,
DialogContent, DialogContent,
DialogContentText, DialogContentText,
DialogActions DialogTitle,
Divider,
ExpansionPanel,
ExpansionPanelDetails,
ExpansionPanelSummary,
FormControlLabel,
IconButton,
Menu,
MenuItem,
Radio,
RadioGroup,
Tooltip,
Typography,
withStyles,
Zoom
} from "@material-ui/core"; } from "@material-ui/core";
import MoreVertIcon from "@material-ui/icons/MoreVert"; import MoreVertIcon from "@material-ui/icons/MoreVert";
import ReplyIcon from "@material-ui/icons/Reply"; import ReplyIcon from "@material-ui/icons/Reply";
@ -51,10 +50,10 @@ import moment from "moment";
import AttachmentComponent from "../Attachment"; import AttachmentComponent from "../Attachment";
import Mastodon from "megalodon"; import Mastodon from "megalodon";
import { import {
LinkableAvatar,
LinkableChip, LinkableChip,
LinkableMenuItem,
LinkableIconButton, LinkableIconButton,
LinkableAvatar LinkableMenuItem
} from "../../interfaces/overrides"; } from "../../interfaces/overrides";
import { withSnackbar } from "notistack"; import { withSnackbar } from "notistack";
import ShareMenu from "./PostShareMenu"; import ShareMenu from "./PostShareMenu";
@ -74,6 +73,7 @@ interface IPostState {
menuIsOpen: boolean; menuIsOpen: boolean;
myVote?: [number]; myVote?: [number];
deletePostDialog: boolean; deletePostDialog: boolean;
myAccount?: string;
} }
export class Post extends React.Component<any, IPostState> { export class Post extends React.Component<any, IPostState> {
@ -95,6 +95,12 @@ export class Post extends React.Component<any, IPostState> {
this.client = this.props.client; this.client = this.props.client;
} }
componentWillMount() {
this.setState({
myAccount: sessionStorage.getItem("id") as string
});
}
togglePostMenu() { togglePostMenu() {
this.setState({ menuIsOpen: !this.state.menuIsOpen }); this.setState({ menuIsOpen: !this.state.menuIsOpen });
} }
@ -106,7 +112,7 @@ export class Post extends React.Component<any, IPostState> {
deletePost() { deletePost() {
this.client this.client
.del("/statuses/" + this.state.post.id) .del("/statuses/" + this.state.post.id)
.then((resp: any) => { .then(() => {
this.props.enqueueSnackbar( this.props.enqueueSnackbar(
"Post deleted. Refresh to see changes." "Post deleted. Refresh to see changes."
); );
@ -262,7 +268,7 @@ export class Post extends React.Component<any, IPostState> {
<RadioGroup value={this.findBiggestVote()}> <RadioGroup value={this.findBiggestVote()}>
{status.poll.options.map( {status.poll.options.map(
(pollOption: PollOption) => { (pollOption: PollOption) => {
let x = ( return (
<FormControlLabel <FormControlLabel
disabled disabled
value={pollOption.title} value={pollOption.title}
@ -274,7 +280,6 @@ export class Post extends React.Component<any, IPostState> {
} }
/> />
); );
return x;
} }
)} )}
</RadioGroup> </RadioGroup>
@ -303,7 +308,7 @@ export class Post extends React.Component<any, IPostState> {
> >
{status.poll.options.map( {status.poll.options.map(
(pollOption: PollOption) => { (pollOption: PollOption) => {
let x = ( return (
<FormControlLabel <FormControlLabel
value={pollOption.title} value={pollOption.title}
control={<Radio />} control={<Radio />}
@ -314,13 +319,12 @@ export class Post extends React.Component<any, IPostState> {
} }
/> />
); );
return x;
} }
)} )}
</RadioGroup> </RadioGroup>
<Button <Button
color="primary" color="primary"
onClick={(event: any) => this.submitVote()} onClick={() => this.submitVote()}
> >
Vote Vote
</Button> </Button>
@ -381,7 +385,6 @@ export class Post extends React.Component<any, IPostState> {
} }
getReblogOfPost(of: Status | null) { getReblogOfPost(of: Status | null) {
const { classes } = this.props;
if (of !== null) { if (of !== null) {
return of.sensitive return of.sensitive
? this.getSensitiveContent(of.spoiler_text, of) ? this.getSensitiveContent(of.spoiler_text, of)
@ -832,9 +835,8 @@ export class Post extends React.Component<any, IPostState> {
Open in Web Open in Web
</MenuItem> </MenuItem>
</div> </div>
{post.account.id == {this.state.myAccount &&
JSON.parse(localStorage.getItem("account") as string) post.account.id === this.state.myAccount ? (
.id ? (
<div> <div>
<Divider /> <Divider />
<MenuItem <MenuItem

View File

@ -22,6 +22,12 @@ if (userLoggedIn()) {
refreshUserAccountData(); refreshUserAccountData();
} }
window.onstorage = (event: any) => {
if (event.key == "account") {
window.location.reload();
}
};
ReactDOM.render( ReactDOM.render(
<HashRouter> <HashRouter>
<SnackbarProvider <SnackbarProvider

View File

@ -84,6 +84,10 @@ class Composer extends Component<any, IComposerState> {
componentDidMount() { componentDidMount() {
let state = this.getComposerParams(this.props); let state = this.getComposerParams(this.props);
let text = state.acct ? `@${state.acct}: ` : ""; let text = state.acct ? `@${state.acct}: ` : "";
this.client.get("/accounts/verify_credentials").then((resp: any) => {
let account: UAccount = resp.data;
this.setState({ account });
});
getConfig().then((config: any) => { getConfig().then((config: any) => {
this.setState({ this.setState({
federated: config.federation.allowPublicPosts, federated: config.federation.allowPublicPosts,
@ -439,7 +443,7 @@ class Composer extends Component<any, IComposerState> {
event.target.value event.target.value
) )
} }
></TextField> />
</Fade> </Fade>
) : null} ) : null}
{this.state.visibility === "direct" ? ( {this.state.visibility === "direct" ? (

View File

@ -110,7 +110,7 @@ class NotificationsPage extends Component<any, INotificationsPageState> {
removeNotification(id: string) { removeNotification(id: string) {
this.client this.client
.post("/notifications/dismiss", { id: id }) .post(`/notifications/${id}/dismiss`)
.then((resp: any) => { .then((resp: any) => {
let notifications = this.state.notifications; let notifications = this.state.notifications;
if (notifications !== undefined && notifications.length > 0) { if (notifications !== undefined && notifications.length > 0) {

View File

@ -12,7 +12,13 @@ import {
Dialog, Dialog,
DialogTitle, DialogTitle,
DialogActions, DialogActions,
DialogContent DialogContent,
List,
ListItem,
ListItemText,
ListItemAvatar,
ListItemSecondaryAction,
IconButton
} from "@material-ui/core"; } from "@material-ui/core";
import { styles } from "./WelcomePage.styles"; import { styles } from "./WelcomePage.styles";
import Mastodon from "megalodon"; import Mastodon from "megalodon";
@ -28,6 +34,16 @@ import { isDarwinApp } from "../utilities/desktop";
import axios from "axios"; import axios from "axios";
import { withSnackbar, withSnackbarProps } from "notistack"; import { withSnackbar, withSnackbarProps } from "notistack";
import { Config } from "../types/Config"; import { Config } from "../types/Config";
import {
addAccountToRegistry,
getAccountRegistry,
loginWithAccount,
removeAccountFromRegistry
} from "../utilities/accounts";
import { Account, MultiAccount } from "../types/Account";
import AccountCircleIcon from "@material-ui/icons/AccountCircle";
import CloseIcon from "@material-ui/icons/Close";
interface IWelcomeProps extends withSnackbarProps { interface IWelcomeProps extends withSnackbarProps {
classes: any; classes: any;
@ -39,7 +55,7 @@ interface IWelcomeState {
brandName?: string; brandName?: string;
registerBase?: string; registerBase?: string;
federates?: boolean; federates?: boolean;
wantsToLogin: boolean; proceedToGetCode: boolean;
user: string; user: string;
userInputError: boolean; userInputError: boolean;
userInputErrorMessage: string; userInputErrorMessage: string;
@ -47,7 +63,7 @@ interface IWelcomeState {
clientSecret?: string; clientSecret?: string;
authUrl?: string; authUrl?: string;
foundSavedLogin: boolean; foundSavedLogin: boolean;
authority: boolean; authorizing: boolean;
license?: string; license?: string;
repo?: string; repo?: string;
defaultRedirectAddress: string; defaultRedirectAddress: string;
@ -55,6 +71,7 @@ interface IWelcomeState {
authCode: string; authCode: string;
emergencyMode: boolean; emergencyMode: boolean;
version: string; version: string;
willAddAccount: boolean;
} }
class WelcomePage extends Component<IWelcomeProps, IWelcomeState> { class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
@ -64,17 +81,18 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
super(props); super(props);
this.state = { this.state = {
wantsToLogin: false, proceedToGetCode: false,
user: "", user: "",
userInputError: false, userInputError: false,
foundSavedLogin: false, foundSavedLogin: false,
authority: false, authorizing: false,
userInputErrorMessage: "", userInputErrorMessage: "",
defaultRedirectAddress: "", defaultRedirectAddress: "",
openAuthDialog: false, openAuthDialog: false,
authCode: "", authCode: "",
emergencyMode: false, emergencyMode: false,
version: "" version: "",
willAddAccount: false
}; };
getConfig() getConfig()
@ -155,6 +173,11 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
} }
} }
clear() {
localStorage.removeItem("access_token");
localStorage.removeItem("baseurl");
}
getSavedSession() { getSavedSession() {
let loginData = localStorage.getItem("login"); let loginData = localStorage.getItem("login");
if (loginData) { if (loginData) {
@ -253,7 +276,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
clientId: resp.clientId, clientId: resp.clientId,
clientSecret: resp.clientSecret, clientSecret: resp.clientSecret,
authUrl: resp.url, authUrl: resp.url,
wantsToLogin: true proceedToGetCode: true
}); });
}); });
} else { } else {
@ -304,7 +327,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
clientSecret: session.clientSecret, clientSecret: session.clientSecret,
authUrl: session.authUrl, authUrl: session.authUrl,
emergencyMode: session.emergency, emergencyMode: session.emergency,
wantsToLogin: true proceedToGetCode: true
}); });
} }
} }
@ -332,8 +355,8 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
axios axios
.get( .get(
"https://" + "https://" +
baseUrl + baseUrl +
"/api/v1/timelines/public" "/api/v1/timelines/public"
) )
.catch((err: Error) => { .catch((err: Error) => {
let userInputError = true; let userInputError = true;
@ -375,7 +398,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
let location = window.location.href; let location = window.location.href;
if (location.includes("?code=")) { if (location.includes("?code=")) {
let code = parseUrl(location).query.code as string; let code = parseUrl(location).query.code as string;
this.setState({ authority: true }); this.setState({ authorizing: true });
let loginData = localStorage.getItem("login"); let loginData = localStorage.getItem("login");
if (loginData) { if (loginData) {
let clientLoginSession: SaveClientSession = JSON.parse( let clientLoginSession: SaveClientSession = JSON.parse(
@ -389,12 +412,12 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
this.state.emergencyMode this.state.emergencyMode
? undefined ? undefined
: clientLoginSession.authUrl.includes( : clientLoginSession.authUrl.includes(
"urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob" "urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob"
) )
? undefined ? undefined
: window.location.protocol === "hyperspace:" : window.location.protocol === "hyperspace:"
? "hyperspace://hyperspace/app/" ? "hyperspace://hyperspace/app/"
: `https://${window.location.host}` : `https://${window.location.host}`
) )
.then((tokenData: any) => { .then((tokenData: any) => {
localStorage.setItem( localStorage.setItem(
@ -436,6 +459,65 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
} }
} }
showMultiAccount() {
const { classes } = this.props;
return (
<div>
<Typography variant="h5">Select an account</Typography>
<Typography>from the list below or add a new one</Typography>
<List>
{getAccountRegistry().map(
(account: MultiAccount, index: number) => (
<ListItem
onClick={() => {
loginWithAccount(account);
window.location.href =
window.location.protocol ===
"hyperspace:"
? "hyperspace://hyperspace/app/"
: `https://${window.location.host}/#/`;
}}
button={true}
>
<ListItemAvatar>
<AccountCircleIcon color="action" />
</ListItemAvatar>
<ListItemText
primary={`@${account.username}`}
secondary={account.host}
/>
<ListItemSecondaryAction>
<IconButton
onClick={(e: any) => {
e.preventDefault();
removeAccountFromRegistry(index);
window.location.reload();
}}
>
<CloseIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
)
)}
</List>
<div className={classes.middlePadding} />
<Button
onClick={() => {
this.setState({ willAddAccount: true });
this.clear();
}}
color={"primary"}
variant={"contained"}
>
Add Account
</Button>
</div>
);
}
showLanding() { showLanding() {
const { classes } = this.props; const { classes } = this.props;
return ( return (
@ -452,7 +534,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
onKeyDown={event => this.watchUsernameField(event)} onKeyDown={event => this.watchUsernameField(event)}
error={this.state.userInputError} error={this.state.userInputError}
onBlur={() => this.checkForErrors()} onBlur={() => this.checkForErrors()}
></TextField> />
{this.state.userInputError ? ( {this.state.userInputError ? (
<Typography color="error"> <Typography color="error">
{this.state.userInputErrorMessage} {this.state.userInputErrorMessage}
@ -597,7 +679,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
this.updateAuthCode(event.target.value) this.updateAuthCode(event.target.value)
} }
onKeyDown={event => this.watchAuthField(event)} onKeyDown={event => this.watchAuthField(event)}
></TextField> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={() => this.toggleAuthDialog()}> <Button onClick={() => this.toggleAuthDialog()}>
@ -614,7 +696,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
); );
} }
showAuthority() { showAuthorizationLoader() {
const { classes } = this.props; const { classes } = this.props;
return ( return (
<div> <div>
@ -659,11 +741,14 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
/> />
<br /> <br />
<Fade in={true}> <Fade in={true}>
{this.state.authority {this.state.authorizing
? this.showAuthority() ? this.showAuthorizationLoader()
: this.state.wantsToLogin : this.state.proceedToGetCode
? this.showLoginAuth() ? this.showLoginAuth()
: this.showLanding()} : getAccountRegistry().length > 0 &&
!this.state.willAddAccount
? this.showMultiAccount()
: this.showLanding()}
</Fade> </Fade>
<br /> <br />
<Typography variant="caption"> <Typography variant="caption">
@ -741,4 +826,4 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
} }
} }
export default withStyles(styles)(withSnackbar(WelcomePage)); export default withStyles(styles)(withSnackbar(WelcomePage));

View File

@ -1,16 +1,12 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { import {
withStyles,
Typography,
Paper,
Avatar, Avatar,
Button, Button,
CircularProgress,
Paper,
TextField, TextField,
ListItem, Typography,
ListItemText, withStyles
ListItemAvatar,
List,
Grid
} from "@material-ui/core"; } from "@material-ui/core";
import { withSnackbar, withSnackbarProps } from "notistack"; import { withSnackbar, withSnackbarProps } from "notistack";
import { styles } from "./PageLayout.styles"; import { styles } from "./PageLayout.styles";
@ -18,16 +14,17 @@ import { Account } from "../types/Account";
import Mastodon from "megalodon"; import Mastodon from "megalodon";
import filedialog from "file-dialog"; import filedialog from "file-dialog";
import PersonIcon from "@material-ui/icons/Person";
interface IYouProps extends withSnackbarProps { interface IYouProps extends withSnackbarProps {
classes: any; classes: any;
} }
interface IYouState { interface IYouState {
currentAccount: Account; currentAccount?: Account;
newDisplayName?: string; newDisplayName?: string;
newBio?: string; newBio?: string;
viewIsLoading: boolean;
viewLoaded: boolean;
viewErrored: boolean;
} }
class You extends Component<IYouProps, IYouState> { class You extends Component<IYouProps, IYouState> {
@ -42,12 +39,42 @@ class You extends Component<IYouProps, IYouState> {
); );
this.state = { this.state = {
currentAccount: this.getAccount() viewIsLoading: true,
viewLoaded: false,
viewErrored: false
}; };
} }
componentWillMount() {
this.client
.get("/accounts/verify_credentials")
.then((resp: any) => {
let currentAccount: Account = resp.data;
this.setState({
currentAccount,
viewIsLoading: false,
viewLoaded: true
});
})
.catch(() => {
if (this.getAccount()) {
this.setState({
currentAccount: this.getAccount(),
viewIsLoading: false,
viewLoaded: true
});
} else {
this.setState({
viewIsLoading: false,
viewErrored: true
});
}
});
}
getAccount() { getAccount() {
let acct = localStorage.getItem("account"); let acct = localStorage.getItem("account");
console.log(acct);
if (acct) { if (acct) {
return JSON.parse(acct); return JSON.parse(acct);
} }
@ -142,15 +169,16 @@ class You extends Component<IYouProps, IYouState> {
removeHTMLContent(text: string) { removeHTMLContent(text: string) {
const div = document.createElement("div"); const div = document.createElement("div");
div.innerHTML = text; div.innerHTML = text;
let innerContent = div.textContent || div.innerText || ""; return div.textContent || div.innerText || "";
return innerContent;
} }
changeDisplayName() { changeDisplayName() {
this.client this.client
.patch("/accounts/update_credentials", { .patch("/accounts/update_credentials", {
display_name: this.state.newDisplayName display_name: this.state.newDisplayName
? this.state.newDisplayName ? this.state.newDisplayName
: this.state.currentAccount.display_name : this.state.currentAccount
? this.state.currentAccount.display_name
: ""
}) })
.then((acct: any) => { .then((acct: any) => {
let currentAccount: Account = acct.data; let currentAccount: Account = acct.data;
@ -179,7 +207,9 @@ class You extends Component<IYouProps, IYouState> {
.patch("/accounts/update_credentials", { .patch("/accounts/update_credentials", {
note: this.state.newBio note: this.state.newBio
? this.state.newBio ? this.state.newBio
: this.state.currentAccount.note : this.state.currentAccount
? this.state.currentAccount.note
: ""
}) })
.then((acct: any) => { .then((acct: any) => {
let currentAccount: Account = acct.data; let currentAccount: Account = acct.data;
@ -205,116 +235,155 @@ class You extends Component<IYouProps, IYouState> {
const { classes } = this.props; const { classes } = this.props;
return ( return (
<div className={classes.pageLayoutMinimalConstraints}> <div className={classes.pageLayoutMinimalConstraints}>
<div className={classes.pageHeroBackground}> {this.state.viewErrored ? (
<div <Paper className={classes.errorCard}>
className={classes.pageHeroBackgroundImage} <Typography variant="h4">Bummer.</Typography>
style={{ <Typography variant="h6">
backgroundImage: `url("${this.state.currentAccount.header_static}")` Something went wrong when trying to get your account
}} information.
/> </Typography>
<div className={classes.profileContent}> </Paper>
<br /> ) : (
<Avatar <span />
className={classes.profileAvatar} )}
src={this.state.currentAccount.avatar_static} {this.state.currentAccount ? (
/> <div>
<div <div className={classes.pageHeroBackground}>
className={classes.profileUserBox} <div
style={{ paddingTop: 8, paddingBottom: 8 }} className={classes.pageHeroBackgroundImage}
> style={{
<Typography backgroundImage: `url("${this.state.currentAccount.header_static}")`
variant="h4" }}
color="inherit" />
component="h1" <div className={classes.profileContent}>
> <br />
Edit your profile <Avatar
</Typography> className={classes.profileAvatar}
<Typography color="inherit"> src={
Change information such as your display name, this.state.currentAccount.avatar_static
bio, and images used here. }
</Typography> />
<div> <div
<Button className={classes.profileUserBox}
className={classes.pageProfileFollowButton} style={{ paddingTop: 8, paddingBottom: 8 }}
variant="contained"
onClick={() => this.updateAvatar()}
> >
Change Avatar <Typography
</Button> variant="h4"
<Button color="inherit"
className={classes.pageProfileFollowButton} component="h1"
variant="contained" >
onClick={() => this.updateHeader()} Edit your profile
> </Typography>
Change Header <Typography color="inherit">
</Button> Change information such as your display
name, bio, and images used here.
</Typography>
<div>
<Button
className={
classes.pageProfileFollowButton
}
variant="contained"
onClick={() => this.updateAvatar()}
>
Change Avatar
</Button>
<Button
className={
classes.pageProfileFollowButton
}
variant="contained"
onClick={() => this.updateHeader()}
>
Change Header
</Button>
</div>
</div>
</div> </div>
</div> </div>
<div className={classes.pageContentLayoutConstraints}>
<Paper className={classes.youPaper}>
<Typography variant="h5" component="h2">
Display Name
</Typography>
<br />
<TextField
className={classes.TextField}
defaultValue={
this.state.currentAccount.display_name
}
rowsMax="1"
variant="outlined"
fullWidth
onChange={(event: any) =>
this.updateDisplayName(
event.target.value
)
}
/>
<div style={{ textAlign: "right" }}>
<Button
className={
classes.pageProfileFollowButton
}
color="primary"
onClick={() => this.changeDisplayName()}
>
Update display Name
</Button>
</div>
</Paper>
<br />
<Paper className={classes.youPaper}>
<Typography variant="h5" component="h2">
About you
</Typography>
<br />
<TextField
className={classes.TextField}
defaultValue={
this.state.currentAccount.note
? this.removeHTMLContent(
this.state.currentAccount.note
)
: "Tell a little bit about yourself"
}
multiline
variant="outlined"
rows="2"
rowsMax="5"
fullWidth
onChange={(event: any) =>
this.updateBio(event.target.value)
}
/>
<div style={{ textAlign: "right" }}>
<Button
className={
classes.pageProfileFollowButton
}
color="primary"
onClick={() => this.changeBio()}
>
Update biography
</Button>
</div>
</Paper>
</div>
</div> </div>
</div> ) : (
<div className={classes.pageContentLayoutConstraints}> "AAA"
<Paper className={classes.youPaper}> )}
<Typography variant="h5" component="h2"> {this.state.viewIsLoading ? (
Display Name <div style={{ textAlign: "center" }}>
</Typography> <CircularProgress
<br /> className={classes.progress}
<TextField color="primary"
className={classes.TextField}
defaultValue={
this.state.currentAccount.display_name
}
rowsMax="1"
variant="outlined"
fullWidth
onChange={(event: any) =>
this.updateDisplayName(event.target.value)
}
/> />
<div style={{ textAlign: "right" }}> </div>
<Button ) : (
className={classes.pageProfileFollowButton} <span />
color="primary" )}
onClick={() => this.changeDisplayName()}
>
Update display Name
</Button>
</div>
</Paper>
<br />
<Paper className={classes.youPaper}>
<Typography variant="h5" component="h2">
About you
</Typography>
<br />
<TextField
className={classes.TextField}
defaultValue={
this.state.currentAccount.note
? this.removeHTMLContent(
this.state.currentAccount.note
)
: "Tell a little bit about yourself"
}
multiline
variant="outlined"
rows="2"
rowsMax="5"
fullWidth
onChange={(event: any) =>
this.updateBio(event.target.value)
}
/>
<div style={{ textAlign: "right" }}>
<Button
className={classes.pageProfileFollowButton}
color="primary"
onClick={() => this.changeBio()}
>
Update biography
</Button>
</div>
</Paper>
</div>
</div> </div>
); );
} }

View File

@ -26,9 +26,32 @@ export type Account = {
bot: boolean | null; bot: boolean | null;
}; };
/**
* Watered-down type for Mastodon accounts
*/
export type UAccount = { export type UAccount = {
id: string; id: string;
acct: string; acct: string;
display_name: string; display_name: string;
avatar_static: string; avatar_static: string;
}; };
/**
* Account type for use with multi-account support
*/
export type MultiAccount = {
/**
* The host name of the account (ex.: mastodon.social)
*/
host: string;
/**
* The username of the account (@test)
*/
username: string;
/**
* The access token generated from the login
*/
access_token: string;
};

View File

@ -1,25 +1,26 @@
import Mastodon from "megalodon"; import Mastodon from "megalodon";
import { MultiAccount, Account } from "../types/Account";
export function userLoggedIn(): boolean { export function userLoggedIn(): boolean {
if ( return !!(
localStorage.getItem("baseurl") && localStorage.getItem("baseurl") && localStorage.getItem("access_token")
localStorage.getItem("access_token") );
) {
return true;
} else {
return false;
}
} }
export function refreshUserAccountData() { export function refreshUserAccountData() {
let client = new Mastodon( let host = localStorage.getItem("baseurl") as string;
localStorage.getItem("access_token") as string, let token = localStorage.getItem("access_token") as string;
(localStorage.getItem("baseurl") as string) + "/api/v1"
); let client = new Mastodon(token, host + "/api/v1");
client client
.get("/accounts/verify_credentials") .get("/accounts/verify_credentials")
.then((resp: any) => { .then((resp: any) => {
localStorage.setItem("account", JSON.stringify(resp.data)); let account: Account = resp.data;
localStorage.setItem("account", JSON.stringify(account));
sessionStorage.setItem("id", account.id);
addAccountToRegistry(host, token, account.acct);
}) })
.catch((err: Error) => { .catch((err: Error) => {
console.error(err.message); console.error(err.message);
@ -31,3 +32,111 @@ export function refreshUserAccountData() {
); );
}); });
} }
/**
* Set the access token and base URL to a given multi-account user.
* @param account The multi-account from localStorage to use
*/
export function loginWithAccount(account: MultiAccount) {
if (localStorage.getItem("access_token") !== null) {
console.info(
"Existing login detected. Removing and using assigned token..."
);
}
localStorage.setItem("access_token", account.access_token);
localStorage.setItem("baseurl", account.host);
}
/**
* Gets the account registry.
* @returns A list of accounts
*/
export function getAccountRegistry(): MultiAccount[] {
let accountRegistry: MultiAccount[] = [];
let accountRegistryString = localStorage.getItem("accountRegistry");
if (accountRegistryString !== null) {
accountRegistry = JSON.parse(accountRegistryString);
}
return accountRegistry;
}
/**
* Add an account to the multi-account registry if it doesn't exist already.
* @param base_url The base URL of the user (eg., the instance)
* @param access_token The access token for the user
* @param username The username of the user
*/
export function addAccountToRegistry(
base_url: string,
access_token: string,
username: string
) {
const newAccount: MultiAccount = {
host: base_url,
username,
access_token
};
let accountRegistry = getAccountRegistry();
const stringifiedRegistry = accountRegistry.map(account =>
JSON.stringify(account)
);
if (stringifiedRegistry.indexOf(JSON.stringify(newAccount)) === -1) {
accountRegistry.push(newAccount);
}
localStorage.setItem("accountRegistry", JSON.stringify(accountRegistry));
}
/**
* Remove an account from the multi-account registry, if possible
* @param accountIdentifier The index of the account from the registry or the MultiAccount object itself
*/
export function removeAccountFromRegistry(
accountIdentifier: number | MultiAccount
) {
let accountRegistry = getAccountRegistry();
if (typeof accountIdentifier === "number") {
if (accountRegistry.length > accountIdentifier) {
if (
localStorage.getItem("access_token") ===
accountRegistry[accountIdentifier].access_token
) {
localStorage.removeItem("baseurl");
localStorage.removeItem("access_token");
}
accountRegistry.splice(accountIdentifier);
} else {
console.log("Multi account index may be out of range");
}
} else {
const stringifiedRegistry = accountRegistry.map(account =>
JSON.stringify(account)
);
const stringifiedAccountId = JSON.stringify(accountIdentifier);
if (
stringifiedRegistry.indexOf(
JSON.stringify(stringifiedAccountId)
) !== -1
) {
if (
localStorage.getItem("access_token") ===
accountIdentifier.access_token
) {
localStorage.removeItem("baseurl");
localStorage.removeItem("access_token");
}
accountRegistry.splice(
stringifiedRegistry.indexOf(stringifiedAccountId)
);
}
}
localStorage.setItem("accountRegistry", JSON.stringify(accountRegistry));
}

View File

@ -47,3 +47,12 @@ export function getDarwinAccentColor(): number {
); );
return themeInteger === "" ? -2 : parseInt(themeInteger); return themeInteger === "" ? -2 : parseInt(themeInteger);
} }
/**
* Get the app component from the desktop app
*/
export function getElectronApp() {
const eWin = window as ElectronWindow;
const { remote } = eWin.require("electron");
return remote.app;
}

View File

@ -45,9 +45,7 @@ export function canSendNotifications() {
*/ */
export function sendNotificationRequest(title: string, body: string) { export function sendNotificationRequest(title: string, body: string) {
if (canSendNotifications()) { if (canSendNotifications()) {
let notif = new Notification(title, { let notif = new Notification(title, { body });
body: body
});
notif.onclick = () => { notif.onclick = () => {
window.focus(); window.focus();