Merge branch 'beta7' into electron-badge
This commit is contained in:
commit
6e7658d986
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"version": "1.0.0beta7",
|
||||
"location": "desktop",
|
||||
"location": "https://localhost:3000",
|
||||
"branding": {
|
||||
"name": "Hyperspace",
|
||||
"logo": "logo.svg",
|
||||
|
|
|
@ -20,7 +20,7 @@ let mainWindow;
|
|||
// to when authorizing Hyperspace.
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{ scheme: 'hyperspace', privileges: { standard: true, secure: true } }
|
||||
])
|
||||
]);
|
||||
|
||||
/**
|
||||
* Determine whether the desktop app is on macOS
|
||||
|
@ -218,15 +218,8 @@ function createMenubar() {
|
|||
click() {
|
||||
safelyGoTo("hyperspace://hyperspace/app/#compose")
|
||||
}
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Edit Profile',
|
||||
accelerator: "Shift+CmdOrCtrl+P",
|
||||
click() {
|
||||
safelyGoTo("hyperspace://hyperspace/app/#/you")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -284,7 +277,7 @@ function createMenubar() {
|
|||
]
|
||||
},
|
||||
{
|
||||
label: "Places",
|
||||
label: "Timelines",
|
||||
submenu: [
|
||||
{
|
||||
label: 'Home',
|
||||
|
@ -308,27 +301,53 @@ function createMenubar() {
|
|||
}
|
||||
},
|
||||
{
|
||||
label: 'Recommendations',
|
||||
label: 'Messages',
|
||||
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() {
|
||||
safelyGoTo("hyperspace://hyperspace/app/#/recommended")
|
||||
}
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Notifications',
|
||||
accelerator: "CmdOrCtrl+4",
|
||||
label: 'Edit Profile',
|
||||
accelerator: "Shift+CmdOrCtrl+P",
|
||||
click() {
|
||||
safelyGoTo("hyperspace://hyperspace/app/#/notifications")
|
||||
safelyGoTo("hyperspace://hyperspace/app/#/you")
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Messages',
|
||||
accelerator: "CmdOrCtrl+5",
|
||||
label: 'Blocked Servers',
|
||||
accelerator: "Shift+CmdOrCtrl+B",
|
||||
click() {
|
||||
safelyGoTo("hyperspace://hyperspace/app/#/messages")
|
||||
safelyGoTo("hyperspace://hyperspace/app/#/blocked")
|
||||
}
|
||||
},
|
||||
{ type: 'separator'},
|
||||
{
|
||||
label: 'Switch Accounts...',
|
||||
click() {
|
||||
safelyGoTo("hyperspace://hyperspace/app/#/welcome")
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -357,7 +376,7 @@ function createMenubar() {
|
|||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
menuBar.unshift({
|
||||
|
@ -386,7 +405,7 @@ function createMenubar() {
|
|||
{ type: 'separator' },
|
||||
{ role: 'quit' }
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
// Edit menu
|
||||
menuBar[2].submenu.push(
|
||||
|
@ -398,10 +417,10 @@ function createMenubar() {
|
|||
{ role: 'stopspeaking' }
|
||||
]
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Window menu
|
||||
menuBar[5].submenu = [
|
||||
menuBar[6].submenu = [
|
||||
{ role: 'close' },
|
||||
{ role: 'minimize' },
|
||||
{ role: 'zoom' },
|
||||
|
|
39
src/App.tsx
39
src/App.tsx
|
@ -3,7 +3,7 @@ import { MuiThemeProvider, CssBaseline, withStyles } from "@material-ui/core";
|
|||
import { setHyperspaceTheme, darkMode } from "./utilities/themes";
|
||||
import AppLayout from "./components/AppLayout";
|
||||
import { styles } from "./App.styles";
|
||||
import { Route } from "react-router-dom";
|
||||
import { Route, withRouter } from "react-router-dom";
|
||||
import AboutPage from "./pages/About";
|
||||
import Settings from "./pages/Settings";
|
||||
import { getUserDefaultBool, getUserDefaultTheme } from "./utilities/settings";
|
||||
|
@ -27,14 +27,22 @@ import { userLoggedIn } from "./utilities/accounts";
|
|||
import { isDarwinApp } from "./utilities/desktop";
|
||||
let theme = setHyperspaceTheme(getUserDefaultTheme());
|
||||
|
||||
class App extends Component<any, any> {
|
||||
interface IAppState {
|
||||
theme: any;
|
||||
showLayout: boolean;
|
||||
}
|
||||
|
||||
class App extends Component<any, IAppState> {
|
||||
offline: any;
|
||||
unlisten: any;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
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,
|
||||
getUserDefaultBool("darkModeEnabled")
|
||||
);
|
||||
this.setState({ theme: newTheme });
|
||||
this.setState({
|
||||
theme: newTheme,
|
||||
showLayout:
|
||||
userLoggedIn() && !window.location.hash.includes("#/welcome")
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.removeBodyBackground();
|
||||
this.unlisten = this.props.history.listen(
|
||||
(location: Location, action: any) => {
|
||||
this.setState({
|
||||
showLayout:
|
||||
userLoggedIn() &&
|
||||
!location.pathname.includes("/welcome")
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.removeBodyBackground();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unlisten();
|
||||
}
|
||||
|
||||
removeBodyBackground() {
|
||||
if (isDarwinApp()) {
|
||||
|
@ -71,7 +95,7 @@ class App extends Component<any, any> {
|
|||
<CssBaseline />
|
||||
<Route path="/welcome" component={WelcomePage} />
|
||||
<div>
|
||||
{userLoggedIn() ? <AppLayout /> : null}
|
||||
{this.state.showLayout ? <AppLayout /> : null}
|
||||
<PrivateRoute exact path="/" component={HomePage} />
|
||||
<PrivateRoute path="/home" component={HomePage} />
|
||||
<PrivateRoute path="/local" component={LocalPage} />
|
||||
|
@ -91,7 +115,7 @@ class App extends Component<any, any> {
|
|||
/>
|
||||
<PrivateRoute path="/search" component={SearchPage} />
|
||||
<PrivateRoute path="/settings" component={Settings} />
|
||||
<PrivateRoute path="/blocked" component={Blocked}/>
|
||||
<PrivateRoute path="/blocked" component={Blocked} />
|
||||
<PrivateRoute path="/you" component={You} />
|
||||
<PrivateRoute path="/about" component={AboutPage} />
|
||||
<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)));
|
||||
|
|
|
@ -39,10 +39,10 @@ import GroupIcon from "@material-ui/icons/Group";
|
|||
import SettingsIcon from "@material-ui/icons/Settings";
|
||||
import InfoIcon from "@material-ui/icons/Info";
|
||||
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 { styles } from "./AppLayout.styles";
|
||||
import { UAccount } from "../../types/Account";
|
||||
import { MultiAccount, UAccount } from "../../types/Account";
|
||||
import {
|
||||
LinkableListItem,
|
||||
LinkableIconButton,
|
||||
|
@ -59,6 +59,10 @@ import {
|
|||
getElectronApp
|
||||
} from "../../utilities/desktop";
|
||||
import { Config } from "../../types/Config";
|
||||
import {
|
||||
getAccountRegistry,
|
||||
removeAccountFromRegistry
|
||||
} from "../../utilities/accounts";
|
||||
|
||||
interface IAppLayoutState {
|
||||
acctMenuOpen: boolean;
|
||||
|
@ -96,23 +100,7 @@ export class AppLayout extends Component<any, IAppLayoutState> {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
let acct = localStorage.getItem("account");
|
||||
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);
|
||||
});
|
||||
}
|
||||
this.getAccountData();
|
||||
|
||||
getConfig().then((result: any) => {
|
||||
if (result !== undefined) {
|
||||
|
@ -130,6 +118,24 @@ export class AppLayout extends Component<any, IAppLayoutState> {
|
|||
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() {
|
||||
this.streamListener = this.client.stream("/streaming/user");
|
||||
|
||||
|
@ -235,10 +241,22 @@ export class AppLayout extends Component<any, IAppLayoutState> {
|
|||
logOutAndRestart() {
|
||||
let loginData = localStorage.getItem("login");
|
||||
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"];
|
||||
items.forEach(entry => {
|
||||
localStorage.removeItem(entry);
|
||||
});
|
||||
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
@ -317,10 +335,22 @@ export class AppLayout extends Component<any, IAppLayoutState> {
|
|||
}
|
||||
/>
|
||||
</LinkableListItem>
|
||||
{/* <LinkableListItem button key="acctSwitch-module" to="/switchacct">
|
||||
<ListItemIcon><SupervisedUserCircleIcon/></ListItemIcon>
|
||||
<ListItemText primary="Switch account"/>
|
||||
</LinkableListItem> */}
|
||||
<LinkableListItem
|
||||
button
|
||||
key="acctSwitch-module"
|
||||
to="/welcome"
|
||||
>
|
||||
<ListItemIcon>
|
||||
<SupervisedUserCircleIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={
|
||||
getAccountRegistry().length > 1
|
||||
? "Switch account"
|
||||
: "Add account"
|
||||
}
|
||||
/>
|
||||
</LinkableListItem>
|
||||
<ListItem
|
||||
button
|
||||
key="acctLogout-mobile"
|
||||
|
@ -581,7 +611,14 @@ export class AppLayout extends Component<any, IAppLayoutState> {
|
|||
Edit profile
|
||||
</ListItemText>
|
||||
</LinkableListItem>
|
||||
{/* <MenuItem>Switch account</MenuItem> */}
|
||||
<LinkableListItem to={"/welcome"}>
|
||||
<ListItemText>
|
||||
{getAccountRegistry()
|
||||
.length > 1
|
||||
? "Switch account"
|
||||
: "Add account"}
|
||||
</ListItemText>
|
||||
</LinkableListItem>
|
||||
<MenuItem
|
||||
onClick={() =>
|
||||
this.toggleLogOutDialog()
|
||||
|
@ -623,48 +660,7 @@ export class AppLayout extends Component<any, IAppLayoutState> {
|
|||
</Hidden>
|
||||
</nav>
|
||||
</div>
|
||||
<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">
|
||||
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>
|
||||
{this.logoutDialog()}
|
||||
<Tooltip title="Create a new post">
|
||||
<LinkableFab
|
||||
to="/compose"
|
||||
|
@ -678,6 +674,57 @@ export class AppLayout extends Component<any, IAppLayoutState> {
|
|||
</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));
|
||||
|
|
|
@ -1,33 +1,32 @@
|
|||
import React from "react";
|
||||
import {
|
||||
Typography,
|
||||
IconButton,
|
||||
Card,
|
||||
CardHeader,
|
||||
Avatar,
|
||||
CardContent,
|
||||
CardActions,
|
||||
withStyles,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Chip,
|
||||
Divider,
|
||||
CardMedia,
|
||||
CardActionArea,
|
||||
ExpansionPanel,
|
||||
ExpansionPanelSummary,
|
||||
ExpansionPanelDetails,
|
||||
Zoom,
|
||||
Tooltip,
|
||||
RadioGroup,
|
||||
Radio,
|
||||
FormControlLabel,
|
||||
Button,
|
||||
Card,
|
||||
CardActionArea,
|
||||
CardActions,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardMedia,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogActions
|
||||
DialogTitle,
|
||||
Divider,
|
||||
ExpansionPanel,
|
||||
ExpansionPanelDetails,
|
||||
ExpansionPanelSummary,
|
||||
FormControlLabel,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Tooltip,
|
||||
Typography,
|
||||
withStyles,
|
||||
Zoom
|
||||
} from "@material-ui/core";
|
||||
import MoreVertIcon from "@material-ui/icons/MoreVert";
|
||||
import ReplyIcon from "@material-ui/icons/Reply";
|
||||
|
@ -51,10 +50,10 @@ import moment from "moment";
|
|||
import AttachmentComponent from "../Attachment";
|
||||
import Mastodon from "megalodon";
|
||||
import {
|
||||
LinkableAvatar,
|
||||
LinkableChip,
|
||||
LinkableMenuItem,
|
||||
LinkableIconButton,
|
||||
LinkableAvatar
|
||||
LinkableMenuItem
|
||||
} from "../../interfaces/overrides";
|
||||
import { withSnackbar } from "notistack";
|
||||
import ShareMenu from "./PostShareMenu";
|
||||
|
@ -73,6 +72,7 @@ interface IPostState {
|
|||
menuIsOpen: boolean;
|
||||
myVote?: [number];
|
||||
deletePostDialog: boolean;
|
||||
myAccount?: string;
|
||||
}
|
||||
|
||||
export class Post extends React.Component<any, IPostState> {
|
||||
|
@ -94,6 +94,12 @@ export class Post extends React.Component<any, IPostState> {
|
|||
this.client = this.props.client;
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.setState({
|
||||
myAccount: sessionStorage.getItem("id") as string
|
||||
});
|
||||
}
|
||||
|
||||
togglePostMenu() {
|
||||
this.setState({ menuIsOpen: !this.state.menuIsOpen });
|
||||
}
|
||||
|
@ -105,7 +111,7 @@ export class Post extends React.Component<any, IPostState> {
|
|||
deletePost() {
|
||||
this.client
|
||||
.del("/statuses/" + this.state.post.id)
|
||||
.then((resp: any) => {
|
||||
.then(() => {
|
||||
this.props.enqueueSnackbar(
|
||||
"Post deleted. Refresh to see changes."
|
||||
);
|
||||
|
@ -261,7 +267,7 @@ export class Post extends React.Component<any, IPostState> {
|
|||
<RadioGroup value={this.findBiggestVote()}>
|
||||
{status.poll.options.map(
|
||||
(pollOption: PollOption) => {
|
||||
let x = (
|
||||
return (
|
||||
<FormControlLabel
|
||||
disabled
|
||||
value={pollOption.title}
|
||||
|
@ -273,7 +279,6 @@ export class Post extends React.Component<any, IPostState> {
|
|||
}
|
||||
/>
|
||||
);
|
||||
return x;
|
||||
}
|
||||
)}
|
||||
</RadioGroup>
|
||||
|
@ -302,7 +307,7 @@ export class Post extends React.Component<any, IPostState> {
|
|||
>
|
||||
{status.poll.options.map(
|
||||
(pollOption: PollOption) => {
|
||||
let x = (
|
||||
return (
|
||||
<FormControlLabel
|
||||
value={pollOption.title}
|
||||
control={<Radio />}
|
||||
|
@ -313,13 +318,12 @@ export class Post extends React.Component<any, IPostState> {
|
|||
}
|
||||
/>
|
||||
);
|
||||
return x;
|
||||
}
|
||||
)}
|
||||
</RadioGroup>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={(event: any) => this.submitVote()}
|
||||
onClick={() => this.submitVote()}
|
||||
>
|
||||
Vote
|
||||
</Button>
|
||||
|
@ -380,7 +384,6 @@ export class Post extends React.Component<any, IPostState> {
|
|||
}
|
||||
|
||||
getReblogOfPost(of: Status | null) {
|
||||
const { classes } = this.props;
|
||||
if (of !== null) {
|
||||
return of.sensitive
|
||||
? this.getSensitiveContent(of.spoiler_text, of)
|
||||
|
@ -653,7 +656,7 @@ export class Post extends React.Component<any, IPostState> {
|
|||
dangerouslySetInnerHTML={{
|
||||
__html: this.getReblogAuthors(post)
|
||||
}}
|
||||
></Typography>
|
||||
/>
|
||||
}
|
||||
subheader={moment(post.created_at).format(
|
||||
"MMMM Do YYYY [at] h:mm A"
|
||||
|
@ -827,9 +830,8 @@ export class Post extends React.Component<any, IPostState> {
|
|||
Open in Web
|
||||
</MenuItem>
|
||||
</div>
|
||||
{post.account.id ==
|
||||
JSON.parse(localStorage.getItem("account") as string)
|
||||
.id ? (
|
||||
{this.state.myAccount &&
|
||||
post.account.id === this.state.myAccount ? (
|
||||
<div>
|
||||
<Divider />
|
||||
<MenuItem
|
||||
|
|
|
@ -22,6 +22,12 @@ if (userLoggedIn()) {
|
|||
refreshUserAccountData();
|
||||
}
|
||||
|
||||
window.onstorage = (event: any) => {
|
||||
if (event.key == "account") {
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
ReactDOM.render(
|
||||
<HashRouter>
|
||||
<SnackbarProvider
|
||||
|
|
|
@ -84,6 +84,10 @@ class Composer extends Component<any, IComposerState> {
|
|||
componentDidMount() {
|
||||
let state = this.getComposerParams(this.props);
|
||||
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) => {
|
||||
this.setState({
|
||||
federated: config.federation.allowPublicPosts,
|
||||
|
@ -439,7 +443,7 @@ class Composer extends Component<any, IComposerState> {
|
|||
event.target.value
|
||||
)
|
||||
}
|
||||
></TextField>
|
||||
/>
|
||||
</Fade>
|
||||
) : null}
|
||||
{this.state.visibility === "direct" ? (
|
||||
|
|
|
@ -12,7 +12,13 @@ import {
|
|||
Dialog,
|
||||
DialogTitle,
|
||||
DialogActions,
|
||||
DialogContent
|
||||
DialogContent,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemAvatar,
|
||||
ListItemSecondaryAction,
|
||||
IconButton
|
||||
} from "@material-ui/core";
|
||||
import { styles } from "./WelcomePage.styles";
|
||||
import Mastodon from "megalodon";
|
||||
|
@ -28,6 +34,16 @@ import { isDarwinApp } from "../utilities/desktop";
|
|||
import axios from "axios";
|
||||
import { withSnackbar, withSnackbarProps } from "notistack";
|
||||
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 {
|
||||
classes: any;
|
||||
|
@ -39,7 +55,7 @@ interface IWelcomeState {
|
|||
brandName?: string;
|
||||
registerBase?: string;
|
||||
federates?: boolean;
|
||||
wantsToLogin: boolean;
|
||||
proceedToGetCode: boolean;
|
||||
user: string;
|
||||
userInputError: boolean;
|
||||
userInputErrorMessage: string;
|
||||
|
@ -47,7 +63,7 @@ interface IWelcomeState {
|
|||
clientSecret?: string;
|
||||
authUrl?: string;
|
||||
foundSavedLogin: boolean;
|
||||
authority: boolean;
|
||||
authorizing: boolean;
|
||||
license?: string;
|
||||
repo?: string;
|
||||
defaultRedirectAddress: string;
|
||||
|
@ -55,6 +71,7 @@ interface IWelcomeState {
|
|||
authCode: string;
|
||||
emergencyMode: boolean;
|
||||
version: string;
|
||||
willAddAccount: boolean;
|
||||
}
|
||||
|
||||
class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
|
||||
|
@ -64,17 +81,18 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
wantsToLogin: false,
|
||||
proceedToGetCode: false,
|
||||
user: "",
|
||||
userInputError: false,
|
||||
foundSavedLogin: false,
|
||||
authority: false,
|
||||
authorizing: false,
|
||||
userInputErrorMessage: "",
|
||||
defaultRedirectAddress: "",
|
||||
openAuthDialog: false,
|
||||
authCode: "",
|
||||
emergencyMode: false,
|
||||
version: ""
|
||||
version: "",
|
||||
willAddAccount: false
|
||||
};
|
||||
|
||||
getConfig()
|
||||
|
@ -155,6 +173,11 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
|
|||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
localStorage.removeItem("access_token");
|
||||
localStorage.removeItem("baseurl");
|
||||
}
|
||||
|
||||
getSavedSession() {
|
||||
let loginData = localStorage.getItem("login");
|
||||
if (loginData) {
|
||||
|
@ -253,7 +276,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
|
|||
clientId: resp.clientId,
|
||||
clientSecret: resp.clientSecret,
|
||||
authUrl: resp.url,
|
||||
wantsToLogin: true
|
||||
proceedToGetCode: true
|
||||
});
|
||||
});
|
||||
} else {
|
||||
|
@ -304,7 +327,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
|
|||
clientSecret: session.clientSecret,
|
||||
authUrl: session.authUrl,
|
||||
emergencyMode: session.emergency,
|
||||
wantsToLogin: true
|
||||
proceedToGetCode: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -332,8 +355,8 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
|
|||
axios
|
||||
.get(
|
||||
"https://" +
|
||||
baseUrl +
|
||||
"/api/v1/timelines/public"
|
||||
baseUrl +
|
||||
"/api/v1/timelines/public"
|
||||
)
|
||||
.catch((err: Error) => {
|
||||
let userInputError = true;
|
||||
|
@ -375,7 +398,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
|
|||
let location = window.location.href;
|
||||
if (location.includes("?code=")) {
|
||||
let code = parseUrl(location).query.code as string;
|
||||
this.setState({ authority: true });
|
||||
this.setState({ authorizing: true });
|
||||
let loginData = localStorage.getItem("login");
|
||||
if (loginData) {
|
||||
let clientLoginSession: SaveClientSession = JSON.parse(
|
||||
|
@ -389,12 +412,12 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
|
|||
this.state.emergencyMode
|
||||
? undefined
|
||||
: clientLoginSession.authUrl.includes(
|
||||
"urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob"
|
||||
)
|
||||
"urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob"
|
||||
)
|
||||
? undefined
|
||||
: window.location.protocol === "hyperspace:"
|
||||
? "hyperspace://hyperspace/app/"
|
||||
: `https://${window.location.host}`
|
||||
? "hyperspace://hyperspace/app/"
|
||||
: `https://${window.location.host}`
|
||||
)
|
||||
.then((tokenData: any) => {
|
||||
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() {
|
||||
const { classes } = this.props;
|
||||
return (
|
||||
|
@ -452,7 +534,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
|
|||
onKeyDown={event => this.watchUsernameField(event)}
|
||||
error={this.state.userInputError}
|
||||
onBlur={() => this.checkForErrors()}
|
||||
></TextField>
|
||||
/>
|
||||
{this.state.userInputError ? (
|
||||
<Typography color="error">
|
||||
{this.state.userInputErrorMessage}
|
||||
|
@ -597,7 +679,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
|
|||
this.updateAuthCode(event.target.value)
|
||||
}
|
||||
onKeyDown={event => this.watchAuthField(event)}
|
||||
></TextField>
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => this.toggleAuthDialog()}>
|
||||
|
@ -614,7 +696,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
|
|||
);
|
||||
}
|
||||
|
||||
showAuthority() {
|
||||
showAuthorizationLoader() {
|
||||
const { classes } = this.props;
|
||||
return (
|
||||
<div>
|
||||
|
@ -659,11 +741,14 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
|
|||
/>
|
||||
<br />
|
||||
<Fade in={true}>
|
||||
{this.state.authority
|
||||
? this.showAuthority()
|
||||
: this.state.wantsToLogin
|
||||
? this.showLoginAuth()
|
||||
: this.showLanding()}
|
||||
{this.state.authorizing
|
||||
? this.showAuthorizationLoader()
|
||||
: this.state.proceedToGetCode
|
||||
? this.showLoginAuth()
|
||||
: getAccountRegistry().length > 0 &&
|
||||
!this.state.willAddAccount
|
||||
? this.showMultiAccount()
|
||||
: this.showLanding()}
|
||||
</Fade>
|
||||
<br />
|
||||
<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));
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
import React, { Component } from "react";
|
||||
import {
|
||||
withStyles,
|
||||
Typography,
|
||||
Paper,
|
||||
Avatar,
|
||||
Button,
|
||||
CircularProgress,
|
||||
Paper,
|
||||
TextField,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemAvatar,
|
||||
List,
|
||||
Grid
|
||||
Typography,
|
||||
withStyles
|
||||
} from "@material-ui/core";
|
||||
import { withSnackbar, withSnackbarProps } from "notistack";
|
||||
import { styles } from "./PageLayout.styles";
|
||||
|
@ -18,16 +14,17 @@ import { Account } from "../types/Account";
|
|||
import Mastodon from "megalodon";
|
||||
import filedialog from "file-dialog";
|
||||
|
||||
import PersonIcon from "@material-ui/icons/Person";
|
||||
|
||||
interface IYouProps extends withSnackbarProps {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
interface IYouState {
|
||||
currentAccount: Account;
|
||||
currentAccount?: Account;
|
||||
newDisplayName?: string;
|
||||
newBio?: string;
|
||||
viewIsLoading: boolean;
|
||||
viewLoaded: boolean;
|
||||
viewErrored: boolean;
|
||||
}
|
||||
|
||||
class You extends Component<IYouProps, IYouState> {
|
||||
|
@ -42,12 +39,42 @@ class You extends Component<IYouProps, IYouState> {
|
|||
);
|
||||
|
||||
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() {
|
||||
let acct = localStorage.getItem("account");
|
||||
console.log(acct);
|
||||
if (acct) {
|
||||
return JSON.parse(acct);
|
||||
}
|
||||
|
@ -142,15 +169,16 @@ class You extends Component<IYouProps, IYouState> {
|
|||
removeHTMLContent(text: string) {
|
||||
const div = document.createElement("div");
|
||||
div.innerHTML = text;
|
||||
let innerContent = div.textContent || div.innerText || "";
|
||||
return innerContent;
|
||||
return div.textContent || div.innerText || "";
|
||||
}
|
||||
changeDisplayName() {
|
||||
this.client
|
||||
.patch("/accounts/update_credentials", {
|
||||
display_name: this.state.newDisplayName
|
||||
? this.state.newDisplayName
|
||||
: this.state.currentAccount.display_name
|
||||
: this.state.currentAccount
|
||||
? this.state.currentAccount.display_name
|
||||
: ""
|
||||
})
|
||||
.then((acct: any) => {
|
||||
let currentAccount: Account = acct.data;
|
||||
|
@ -179,7 +207,9 @@ class You extends Component<IYouProps, IYouState> {
|
|||
.patch("/accounts/update_credentials", {
|
||||
note: this.state.newBio
|
||||
? this.state.newBio
|
||||
: this.state.currentAccount.note
|
||||
: this.state.currentAccount
|
||||
? this.state.currentAccount.note
|
||||
: ""
|
||||
})
|
||||
.then((acct: any) => {
|
||||
let currentAccount: Account = acct.data;
|
||||
|
@ -205,116 +235,155 @@ class You extends Component<IYouProps, IYouState> {
|
|||
const { classes } = this.props;
|
||||
return (
|
||||
<div className={classes.pageLayoutMinimalConstraints}>
|
||||
<div className={classes.pageHeroBackground}>
|
||||
<div
|
||||
className={classes.pageHeroBackgroundImage}
|
||||
style={{
|
||||
backgroundImage: `url("${this.state.currentAccount.header_static}")`
|
||||
}}
|
||||
/>
|
||||
<div className={classes.profileContent}>
|
||||
<br />
|
||||
<Avatar
|
||||
className={classes.profileAvatar}
|
||||
src={this.state.currentAccount.avatar_static}
|
||||
/>
|
||||
<div
|
||||
className={classes.profileUserBox}
|
||||
style={{ paddingTop: 8, paddingBottom: 8 }}
|
||||
>
|
||||
<Typography
|
||||
variant="h4"
|
||||
color="inherit"
|
||||
component="h1"
|
||||
>
|
||||
Edit your profile
|
||||
</Typography>
|
||||
<Typography color="inherit">
|
||||
Change information such as your display name,
|
||||
bio, and images used here.
|
||||
</Typography>
|
||||
<div>
|
||||
<Button
|
||||
className={classes.pageProfileFollowButton}
|
||||
variant="contained"
|
||||
onClick={() => this.updateAvatar()}
|
||||
{this.state.viewErrored ? (
|
||||
<Paper className={classes.errorCard}>
|
||||
<Typography variant="h4">Bummer.</Typography>
|
||||
<Typography variant="h6">
|
||||
Something went wrong when trying to get your account
|
||||
information.
|
||||
</Typography>
|
||||
</Paper>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
{this.state.currentAccount ? (
|
||||
<div>
|
||||
<div className={classes.pageHeroBackground}>
|
||||
<div
|
||||
className={classes.pageHeroBackgroundImage}
|
||||
style={{
|
||||
backgroundImage: `url("${this.state.currentAccount.header_static}")`
|
||||
}}
|
||||
/>
|
||||
<div className={classes.profileContent}>
|
||||
<br />
|
||||
<Avatar
|
||||
className={classes.profileAvatar}
|
||||
src={
|
||||
this.state.currentAccount.avatar_static
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className={classes.profileUserBox}
|
||||
style={{ paddingTop: 8, paddingBottom: 8 }}
|
||||
>
|
||||
Change Avatar
|
||||
</Button>
|
||||
<Button
|
||||
className={classes.pageProfileFollowButton}
|
||||
variant="contained"
|
||||
onClick={() => this.updateHeader()}
|
||||
>
|
||||
Change Header
|
||||
</Button>
|
||||
<Typography
|
||||
variant="h4"
|
||||
color="inherit"
|
||||
component="h1"
|
||||
>
|
||||
Edit your profile
|
||||
</Typography>
|
||||
<Typography color="inherit">
|
||||
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 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 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)
|
||||
}
|
||||
) : (
|
||||
"AAA"
|
||||
)}
|
||||
{this.state.viewIsLoading ? (
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<CircularProgress
|
||||
className={classes.progress}
|
||||
color="primary"
|
||||
/>
|
||||
<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>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -26,9 +26,32 @@ export type Account = {
|
|||
bot: boolean | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Watered-down type for Mastodon accounts
|
||||
*/
|
||||
export type UAccount = {
|
||||
id: string;
|
||||
acct: string;
|
||||
display_name: 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;
|
||||
};
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
import Mastodon from "megalodon";
|
||||
import { MultiAccount, Account } from "../types/Account";
|
||||
|
||||
export function userLoggedIn(): boolean {
|
||||
if (
|
||||
localStorage.getItem("baseurl") &&
|
||||
localStorage.getItem("access_token")
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return !!(
|
||||
localStorage.getItem("baseurl") && localStorage.getItem("access_token")
|
||||
);
|
||||
}
|
||||
|
||||
export function refreshUserAccountData() {
|
||||
let client = new Mastodon(
|
||||
localStorage.getItem("access_token") as string,
|
||||
(localStorage.getItem("baseurl") as string) + "/api/v1"
|
||||
);
|
||||
let host = localStorage.getItem("baseurl") as string;
|
||||
let token = localStorage.getItem("access_token") as string;
|
||||
|
||||
let client = new Mastodon(token, host + "/api/v1");
|
||||
|
||||
client
|
||||
.get("/accounts/verify_credentials")
|
||||
.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) => {
|
||||
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));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue