Fix streaming, add poll support to composer
This commit is contained in:
parent
27b6c92c22
commit
4978af6b6e
|
@ -944,6 +944,21 @@
|
||||||
"integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==",
|
"integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@date-io/core": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@date-io/core/-/core-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-PyjhyR2fbp7Q8xpB5zoOyT3dqr8Bn4kXfREf1w6AnQalwdftNxChB2/p88fI1qsx8KNmHDJY12eCgLoZaPegTw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@date-io/moment": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-U7Vrhk7nlrKMtlLuNs7DAWmMsMBTgWFAvgpKg6Z5Opo/U7ab5DvI8nLks5dqJbeEFQDSlO+KCmJWfsL9hVI3Jw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@date-io/core": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@material-ui/core": {
|
"@material-ui/core": {
|
||||||
"version": "3.9.3",
|
"version": "3.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-3.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-3.9.3.tgz",
|
||||||
|
@ -1290,6 +1305,15 @@
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/react-text-mask": {
|
||||||
|
"version": "5.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-text-mask/-/react-text-mask-5.4.4.tgz",
|
||||||
|
"integrity": "sha512-mnwyDgUwFtJVAZ8f+tzPGmYjpH7TLXxHSGty338abca6aAjdjRLCOC4h+CxlvB8xrVAIU5pkNllpHhfJCA3hXQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/react-transition-group": {
|
"@types/react-transition-group": {
|
||||||
"version": "2.0.16",
|
"version": "2.0.16",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.0.16.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.0.16.tgz",
|
||||||
|
@ -4152,6 +4176,12 @@
|
||||||
"shallow-clone": "^0.1.2"
|
"shallow-clone": "^0.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"clsx": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-xLoSw6DMp7YvbEeLrQJBcWWRRerdHrU1WHoL1hYJOKUeDpVMRq7pv7NI2JHQbCRAe5ptINNzhdYmtfN6MsdCUw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"co": {
|
"co": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||||
|
@ -10806,6 +10836,20 @@
|
||||||
"object-visit": "^1.0.0"
|
"object-visit": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"material-ui-pickers": {
|
||||||
|
"version": "2.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/material-ui-pickers/-/material-ui-pickers-2.2.4.tgz",
|
||||||
|
"integrity": "sha512-QCQh08Ylmnt+o4laW+rPs92QRAcESv3sPXl50YadLm++rAZAXAOh3K8lreGdynCMYFgZfdyu81Oz9xzTlAZNfw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react-text-mask": "^5.4.3",
|
||||||
|
"clsx": "^1.0.2",
|
||||||
|
"react-event-listener": "^0.6.6",
|
||||||
|
"react-text-mask": "^5.4.3",
|
||||||
|
"react-transition-group": "^2.5.3",
|
||||||
|
"tslib": "^1.9.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"math-random": {
|
"math-random": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz",
|
||||||
|
@ -15315,6 +15359,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-text-mask": {
|
||||||
|
"version": "5.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-text-mask/-/react-text-mask-5.4.3.tgz",
|
||||||
|
"integrity": "sha1-mR77QpnjDC5sLEbRP2FxaUY+DS0=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.5.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-transition-group": {
|
"react-transition-group": {
|
||||||
"version": "2.7.1",
|
"version": "2.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.7.1.tgz",
|
||||||
|
|
|
@ -24,7 +24,9 @@
|
||||||
"react-web-share-api": "^0.0.2",
|
"react-web-share-api": "^0.0.2",
|
||||||
"query-string": "^6.4.2",
|
"query-string": "^6.4.2",
|
||||||
"file-dialog": "^0.0.7",
|
"file-dialog": "^0.0.7",
|
||||||
"emoji-mart": "^2.8.2"
|
"emoji-mart": "^2.8.2",
|
||||||
|
"material-ui-pickers": "^2.2.4",
|
||||||
|
"@date-io/moment": "^1.1.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "BROWSER='Safari Technology Preview' react-scripts start",
|
"start": "BROWSER='Safari Technology Preview' react-scripts start",
|
||||||
|
|
|
@ -254,7 +254,7 @@ export class AppLayout extends Component<any, IAppLayoutState> {
|
||||||
<ListItemText primary={this.state.currentUser.display_name || this.state.currentUser.acct} secondary={'@' + this.state.currentUser.acct}/>
|
<ListItemText primary={this.state.currentUser.display_name || this.state.currentUser.acct} secondary={'@' + this.state.currentUser.acct}/>
|
||||||
</LinkableListItem>
|
</LinkableListItem>
|
||||||
<Divider/>
|
<Divider/>
|
||||||
<MenuItem>Switch account</MenuItem>
|
{/* <MenuItem>Switch account</MenuItem> */}
|
||||||
<MenuItem>Log out</MenuItem>
|
<MenuItem>Log out</MenuItem>
|
||||||
</div>
|
</div>
|
||||||
</ClickAwayListener>
|
</ClickAwayListener>
|
||||||
|
|
|
@ -80,5 +80,8 @@ export const styles = (theme: Theme) => createStyles({
|
||||||
postAuthorEmoji: {
|
postAuthorEmoji: {
|
||||||
height: theme.typography.fontSize,
|
height: theme.typography.fontSize,
|
||||||
verticalAlign: "middle"
|
verticalAlign: "middle"
|
||||||
|
},
|
||||||
|
heading: {
|
||||||
|
color: "inherit"
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Typography, IconButton, Card, CardHeader, Avatar, CardContent, CardActions, withStyles, Menu, MenuItem, Chip, Divider, CardMedia, CardActionArea, ExpansionPanel, ExpansionPanelSummary, ExpansionPanelDetails, Zoom, Tooltip } from '@material-ui/core';
|
import { Typography, IconButton, Card, CardHeader, Avatar, CardContent, CardActions, withStyles, Menu, MenuItem, Chip, Divider, CardMedia, CardActionArea, ExpansionPanel, ExpansionPanelSummary, ExpansionPanelDetails, Zoom, Tooltip, RadioGroup, Radio, FormControlLabel, Button } 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';
|
||||||
import FavoriteIcon from '@material-ui/icons/Favorite';
|
import FavoriteIcon from '@material-ui/icons/Favorite';
|
||||||
|
@ -25,6 +25,7 @@ import { LinkableChip, LinkableMenuItem, LinkableIconButton } from '../../interf
|
||||||
import {withSnackbar} from 'notistack';
|
import {withSnackbar} from 'notistack';
|
||||||
import ShareMenu from './PostShareMenu';
|
import ShareMenu from './PostShareMenu';
|
||||||
import {emojifyString} from '../../utilities/emojis';
|
import {emojifyString} from '../../utilities/emojis';
|
||||||
|
import { PollOption, Poll } from '../../types/Poll';
|
||||||
|
|
||||||
interface IPostProps {
|
interface IPostProps {
|
||||||
post: Status;
|
post: Status;
|
||||||
|
@ -36,6 +37,7 @@ interface IPostState {
|
||||||
post: Status;
|
post: Status;
|
||||||
media_slides?: number;
|
media_slides?: number;
|
||||||
menuIsOpen: boolean;
|
menuIsOpen: boolean;
|
||||||
|
myVote?: [number];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Post extends React.Component<any, IPostState> {
|
export class Post extends React.Component<any, IPostState> {
|
||||||
|
@ -59,6 +61,54 @@ export class Post extends React.Component<any, IPostState> {
|
||||||
this.setState({ menuIsOpen: !this.state.menuIsOpen })
|
this.setState({ menuIsOpen: !this.state.menuIsOpen })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findBiggestVote() {
|
||||||
|
let poll = this.state.post.poll;
|
||||||
|
let votes: number[] = [];
|
||||||
|
let bv = "";
|
||||||
|
if (poll) {
|
||||||
|
poll.options.forEach((option: PollOption) => {
|
||||||
|
votes.push(option.votes_count? option.votes_count: 0);
|
||||||
|
});
|
||||||
|
let biggestVote = Math.max.apply(null, votes);
|
||||||
|
poll.options.forEach((option: PollOption) => {
|
||||||
|
if (option.votes_count === biggestVote) {
|
||||||
|
bv = option.title;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return bv;
|
||||||
|
} else {
|
||||||
|
return "No poll option was the best.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
captureVote(option: any) {
|
||||||
|
let poll = this.state.post.poll;
|
||||||
|
let pollIndex: number = 0;
|
||||||
|
if (poll) {
|
||||||
|
poll.options.forEach((pollOption: PollOption, index: number) => {
|
||||||
|
if (pollOption.title === option) {
|
||||||
|
pollIndex = index;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.setState({ myVote: [pollIndex] });
|
||||||
|
}
|
||||||
|
|
||||||
|
submitVote() {
|
||||||
|
let poll = this.state.post.poll;
|
||||||
|
if (poll) {
|
||||||
|
this.client.post(`/polls/${poll.id}/votes`, {choices: this.state.myVote}).then((resp: any) => {
|
||||||
|
let post = this.state.post;
|
||||||
|
post.poll = resp.data;
|
||||||
|
this.setState({ post });
|
||||||
|
this.props.enqueueSnackbar("Vote submitted.");
|
||||||
|
}).catch((err: Error) => {
|
||||||
|
this.props.enqueueSnackbar("Couldn't vote: " + err.name);
|
||||||
|
console.error(err.message);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
materializeContent(status: Status) {
|
materializeContent(status: Status) {
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
|
|
||||||
|
@ -105,22 +155,81 @@ export class Post extends React.Component<any, IPostState> {
|
||||||
<AttachmentComponent media={status.media_attachments}/>:
|
<AttachmentComponent media={status.media_attachments}/>:
|
||||||
<span/>
|
<span/>
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
status.poll?
|
||||||
|
status.poll.voted || status.poll.expired?
|
||||||
|
<div>
|
||||||
|
<Typography variant="caption">You can't vote on this poll. Below are the results of the poll.</Typography>
|
||||||
|
<RadioGroup
|
||||||
|
value={this.findBiggestVote()}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
status.poll.options.map((pollOption: PollOption) => {
|
||||||
|
let x = <FormControlLabel
|
||||||
|
disabled
|
||||||
|
value={pollOption.title}
|
||||||
|
control={<Radio />}
|
||||||
|
label={`${pollOption.title} (${pollOption.votes_count} votes)`}
|
||||||
|
key={pollOption.title+pollOption.votes_count}
|
||||||
|
/>;
|
||||||
|
return (x);
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</RadioGroup>
|
||||||
|
{
|
||||||
|
status.poll && status.poll.expired?
|
||||||
|
<Typography variant="caption">This poll has expired.</Typography>:
|
||||||
|
<Typography variant="caption">This poll will expire on {moment(status.poll.expires_at? status.poll.expires_at: "").format('MMMM Do YYYY, [at] h:mm A')}.</Typography>
|
||||||
|
}
|
||||||
|
</div>:
|
||||||
|
<div>
|
||||||
|
<RadioGroup
|
||||||
|
onChange={(event: any, option: any) => this.captureVote(option)}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
status.poll.options.map((pollOption: PollOption) => {
|
||||||
|
let x = <FormControlLabel
|
||||||
|
value={pollOption.title}
|
||||||
|
control={<Radio />}
|
||||||
|
label={pollOption.title}
|
||||||
|
key={pollOption.title+pollOption.votes_count}
|
||||||
|
/>;
|
||||||
|
return (x);
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</RadioGroup>
|
||||||
|
<Button color="primary" onClick={(event: any) => this.submitVote()}>Vote</Button>
|
||||||
|
</div>: null
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
spoilerContainsFlags(text: string): boolean {
|
||||||
|
let unsafeFlags = ["NSFW", "nsfw", "lewd", "sex"];
|
||||||
|
let result: boolean = false;
|
||||||
|
unsafeFlags.forEach((flag: string) => {
|
||||||
|
if (text.includes(flag)) {
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
getSensitiveContent(spoiler_text: string, content: Status) {
|
getSensitiveContent(spoiler_text: string, content: Status) {
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
const warningText = spoiler_text || "Unmarked content";
|
const warningText = spoiler_text || "Unmarked content";
|
||||||
let icon;
|
let icon;
|
||||||
if (spoiler_text.includes("NSFW") || spoiler_text.includes("Spoiler") || warningText === "Unmarked content") {
|
if (this.spoilerContainsFlags(spoiler_text) || spoiler_text.includes("Spoiler") || warningText === "Unmarked content") {
|
||||||
icon = <WarningIcon className={classes.postWarningIcon}/>;
|
icon = <WarningIcon className={classes.postWarningIcon}/>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<ExpansionPanel className={spoiler_text.includes("NSFW")? classes.nsfwCard: classes.spoilerCard} color="inherit">
|
<ExpansionPanel className={this.spoilerContainsFlags(spoiler_text)? classes.nsfwCard: classes.spoilerCard} color="inherit">
|
||||||
<ExpansionPanelSummary expandIcon={<ExpandMoreIcon/>} color="inherit">
|
<ExpansionPanelSummary expandIcon={<ExpandMoreIcon/>} color="inherit">
|
||||||
{icon}<Typography className={classes.heading} color="inherit">{warningText}</Typography>
|
{icon}<Typography>{warningText}</Typography>
|
||||||
</ExpansionPanelSummary>
|
</ExpansionPanelSummary>
|
||||||
<ExpansionPanelDetails className={classes.postContent} color="inherit">
|
<ExpansionPanelDetails className={classes.postContent} color="inherit">
|
||||||
{this.materializeContent(content)}
|
{this.materializeContent(content)}
|
||||||
|
|
|
@ -35,5 +35,14 @@ export const styles = (theme: Theme) => createStyles({
|
||||||
[theme.breakpoints.up('sm')]: {
|
[theme.breakpoints.up('sm')]: {
|
||||||
display: "block"
|
display: "block"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
pollWizardOptionIcon: {
|
||||||
|
marginRight: theme.spacing.unit * 2,
|
||||||
|
marginTop: 4,
|
||||||
|
marginBottom: 4,
|
||||||
|
color: theme.palette.grey[700]
|
||||||
|
},
|
||||||
|
pollWizardFlexGrow: {
|
||||||
|
flexGrow: 1
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -9,14 +9,17 @@ import TagFacesIcon from '@material-ui/icons/TagFaces';
|
||||||
import HowToVoteIcon from '@material-ui/icons/HowToVote';
|
import HowToVoteIcon from '@material-ui/icons/HowToVote';
|
||||||
import VisibilityIcon from '@material-ui/icons/Visibility';
|
import VisibilityIcon from '@material-ui/icons/Visibility';
|
||||||
import WarningIcon from '@material-ui/icons/Warning';
|
import WarningIcon from '@material-ui/icons/Warning';
|
||||||
|
import DeleteIcon from '@material-ui/icons/Delete';
|
||||||
|
import RadioButtonCheckedIcon from '@material-ui/icons/RadioButtonChecked';
|
||||||
import Mastodon from 'megalodon';
|
import Mastodon from 'megalodon';
|
||||||
import {withSnackbar} from 'notistack';
|
import {withSnackbar} from 'notistack';
|
||||||
import { Attachment } from '../types/Attachment';
|
import { Attachment } from '../types/Attachment';
|
||||||
import { PollWizard } from '../types/Poll';
|
import { PollWizard, PollWizardOption } from '../types/Poll';
|
||||||
import filedialog from 'file-dialog';
|
import filedialog from 'file-dialog';
|
||||||
import ComposeMediaAttachment from '../components/ComposeMediaAttachment';
|
import ComposeMediaAttachment from '../components/ComposeMediaAttachment';
|
||||||
import EmojiPicker from '../components/EmojiPicker';
|
import EmojiPicker from '../components/EmojiPicker';
|
||||||
|
import { DateTimePicker, MuiPickersUtilsProvider } from 'material-ui-pickers';
|
||||||
|
import MomentUtils from '@date-io/moment';
|
||||||
|
|
||||||
interface IComposerState {
|
interface IComposerState {
|
||||||
account: UAccount;
|
account: UAccount;
|
||||||
|
@ -30,6 +33,7 @@ interface IComposerState {
|
||||||
acct?: string;
|
acct?: string;
|
||||||
attachments?: [Attachment];
|
attachments?: [Attachment];
|
||||||
poll?: PollWizard;
|
poll?: PollWizard;
|
||||||
|
pollExpiresDate?: any;
|
||||||
showEmojis: boolean;
|
showEmojis: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,14 +204,120 @@ class Composer extends Component<any, IComposerState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createPoll() {
|
||||||
|
if (this.state.poll === undefined) {
|
||||||
|
let expiration = new Date();
|
||||||
|
let current = new Date();
|
||||||
|
expiration.setMinutes(expiration.getMinutes() + 30);
|
||||||
|
let expiryDifference = (expiration.getTime() - current.getTime() / 1000);
|
||||||
|
let temporaryPoll: PollWizard = {
|
||||||
|
expires_at: expiryDifference.toString(),
|
||||||
|
multiple: false,
|
||||||
|
options: [{title: 'Option 1'}, {title: 'Option 2'}]
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
poll: temporaryPoll,
|
||||||
|
pollExpiresDate: expiration
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addPollItem() {
|
||||||
|
if (this.state.poll !== undefined && this.state.poll.options.length < 4) {
|
||||||
|
let newOption = {title: 'New option'}
|
||||||
|
let options = this.state.poll.options;
|
||||||
|
let poll = this.state.poll;
|
||||||
|
options.push(newOption);
|
||||||
|
poll.options = options;
|
||||||
|
poll.multiple = true;
|
||||||
|
this.setState({
|
||||||
|
poll: poll
|
||||||
|
})
|
||||||
|
} else if (this.state.poll && this.state.poll.options.length == 4) {
|
||||||
|
this.props.enqueueSnackbar("You've reached the options limit in your poll.", { variant: 'error' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editPollItem(position: number, newTitle: any) {
|
||||||
|
if (this.state.poll !== undefined) {
|
||||||
|
let poll = this.state.poll;
|
||||||
|
let options = this.state.poll.options;
|
||||||
|
options.forEach((option: PollWizardOption) => {
|
||||||
|
if (position === options.indexOf(option)) {
|
||||||
|
option.title = newTitle.target.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
poll.options = options;
|
||||||
|
this.setState({
|
||||||
|
poll: poll
|
||||||
|
});
|
||||||
|
this.props.enqueueSnackbar('Option edited.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removePollItem(item: string) {
|
||||||
|
if (this.state.poll !== undefined && this.state.poll.options.length > 2) {
|
||||||
|
let options = this.state.poll.options;
|
||||||
|
let poll = this.state.poll;
|
||||||
|
options.forEach((option: PollWizardOption) => {
|
||||||
|
if (item === option.title) {
|
||||||
|
options.splice(options.indexOf(option), 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
poll.options = options;
|
||||||
|
if (options.length === 2) {
|
||||||
|
poll.multiple = false;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
poll: poll
|
||||||
|
})
|
||||||
|
} else if (this.state.poll && this.state.poll.options.length <= 2) {
|
||||||
|
this.props.enqueueSnackbar('Polls must have at least two items.', { variant: 'error'} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setPollExpires(date: string) {
|
||||||
|
let currentDate = new Date();
|
||||||
|
let newDate = new Date(date);
|
||||||
|
let poll = this.state.poll;
|
||||||
|
if (poll) {
|
||||||
|
let expiry = ((newDate.getTime() - currentDate.getTime()) / 1000);
|
||||||
|
console.log(expiry);
|
||||||
|
if (expiry >= 1800) {
|
||||||
|
poll.expires_at = expiry.toString();
|
||||||
|
this.setState({ poll, pollExpiresDate: date });
|
||||||
|
this.props.enqueueSnackbar("Expiration updated.")
|
||||||
|
} else {
|
||||||
|
this.props.enqueueSnackbar("Expiration is too small (min. 30 minutes).", { variant: 'error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removePoll() {
|
||||||
|
this.setState({
|
||||||
|
poll: undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
post() {
|
post() {
|
||||||
|
let pollOptions: string[] = [];
|
||||||
|
if (this.state.poll) {
|
||||||
|
this.state.poll.options.forEach((option: PollWizardOption) => {
|
||||||
|
pollOptions.push(option.title);
|
||||||
|
})
|
||||||
|
}
|
||||||
this.client.post('/statuses', {
|
this.client.post('/statuses', {
|
||||||
status: this.state.text,
|
status: this.state.text,
|
||||||
media_ids: this.getOnlyMediaIds(),
|
media_ids: this.getOnlyMediaIds(),
|
||||||
visibility: this.state.visibility,
|
visibility: this.state.visibility,
|
||||||
sensitive: this.state.sensitive,
|
sensitive: this.state.sensitive,
|
||||||
spoiler_text: this.state.sensitiveText,
|
spoiler_text: this.state.sensitiveText,
|
||||||
in_reply_to_id: this.state.reply
|
in_reply_to_id: this.state.reply,
|
||||||
|
poll: this.state.poll? {
|
||||||
|
options: pollOptions,
|
||||||
|
expires_in: this.state.poll.expires_at,
|
||||||
|
multiple: this.state.poll.multiple
|
||||||
|
}: null
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.props.enqueueSnackbar('Posted!');
|
this.props.enqueueSnackbar('Posted!');
|
||||||
window.history.back();
|
window.history.back();
|
||||||
|
@ -299,6 +409,44 @@ class Composer extends Component<any, IComposerState> {
|
||||||
</GridList>
|
</GridList>
|
||||||
</div>: null
|
</div>: null
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
this.state.poll?
|
||||||
|
<div style={{ marginTop: 4}}>
|
||||||
|
|
||||||
|
{
|
||||||
|
this.state.poll?
|
||||||
|
this.state.poll.options.map((option: PollWizardOption, index: number) => {
|
||||||
|
let c = <div style={{ display: "flex" }} key={"compose_option_" + index.toString()}>
|
||||||
|
<RadioButtonCheckedIcon className={classes.pollWizardOptionIcon}/>
|
||||||
|
<TextField
|
||||||
|
onBlur={(event: any) => this.editPollItem(index, event)}
|
||||||
|
defaultValue={option.title}/>
|
||||||
|
<div className={classes.pollWizardFlexGrow}/>
|
||||||
|
<Tooltip title="Remove poll option">
|
||||||
|
<IconButton onClick={() => this.removePollItem(option.title)}>
|
||||||
|
<DeleteIcon/>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
return c;
|
||||||
|
}): null
|
||||||
|
}
|
||||||
|
<div style={{ display: "flex"}}>
|
||||||
|
<MuiPickersUtilsProvider utils={MomentUtils}>
|
||||||
|
<DateTimePicker
|
||||||
|
value={this.state.pollExpiresDate? this.state.pollExpiresDate: new Date()}
|
||||||
|
onChange={(date: any) => {
|
||||||
|
this.setPollExpires(date.toISOString());
|
||||||
|
}}
|
||||||
|
label="Poll exipres on"
|
||||||
|
disablePast
|
||||||
|
/>
|
||||||
|
</MuiPickersUtilsProvider>
|
||||||
|
<div className={classes.pollWizardFlexGrow}/>
|
||||||
|
<Button onClick={() => this.addPollItem()}>Add Option</Button>
|
||||||
|
</div>
|
||||||
|
</div>: null
|
||||||
|
}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<Toolbar className={classes.dialogActions}>
|
<Toolbar className={classes.dialogActions}>
|
||||||
<Tooltip title="Add photos or videos">
|
<Tooltip title="Add photos or videos">
|
||||||
|
@ -320,7 +468,11 @@ class Composer extends Component<any, IComposerState> {
|
||||||
<EmojiPicker onGetEmoji={(emoji: any) => this.insertEmoji(emoji)}/>
|
<EmojiPicker onGetEmoji={(emoji: any) => this.insertEmoji(emoji)}/>
|
||||||
</Menu>
|
</Menu>
|
||||||
<Tooltip title="Add a poll">
|
<Tooltip title="Add a poll">
|
||||||
<IconButton disabled={this.state.attachments && this.state.attachments.length > 0} id="compose-poll">
|
<IconButton disabled={this.state.attachments && this.state.attachments.length > 0} id="compose-poll" onClick={() => {
|
||||||
|
this.state.poll?
|
||||||
|
this.removePoll():
|
||||||
|
this.createPoll()
|
||||||
|
}}>
|
||||||
<HowToVoteIcon/>
|
<HowToVoteIcon/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { withStyles, CircularProgress, Typography, Paper, Button, Chip, Avatar,
|
||||||
import {styles} from './PageLayout.styles';
|
import {styles} from './PageLayout.styles';
|
||||||
import Post from '../components/Post';
|
import Post from '../components/Post';
|
||||||
import { Status } from '../types/Status';
|
import { Status } from '../types/Status';
|
||||||
import Mastodon from 'megalodon';
|
import Mastodon, { StreamListener } from 'megalodon';
|
||||||
import {withSnackbar} from 'notistack';
|
import {withSnackbar} from 'notistack';
|
||||||
import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward';
|
import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward';
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ interface IHomePageState {
|
||||||
class HomePage extends Component<any, IHomePageState> {
|
class HomePage extends Component<any, IHomePageState> {
|
||||||
|
|
||||||
client: Mastodon;
|
client: Mastodon;
|
||||||
streamListener: any;
|
streamListener: StreamListener;
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -31,11 +31,11 @@ class HomePage extends Component<any, IHomePageState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.client = new Mastodon(localStorage.getItem('access_token') as string, localStorage.getItem('baseurl') as string + "/api/v1");
|
this.client = new Mastodon(localStorage.getItem('access_token') as string, localStorage.getItem('baseurl') as string + "/api/v1");
|
||||||
|
this.streamListener = this.client.stream('/streaming/user');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.streamListener = this.client.stream('/streaming/user');
|
|
||||||
|
|
||||||
this.streamListener.on('connect', () => {
|
this.streamListener.on('connect', () => {
|
||||||
this.client.get('/timelines/home', {limit: 40}).then((resp: any) => {
|
this.client.get('/timelines/home', {limit: 40}).then((resp: any) => {
|
||||||
|
@ -92,6 +92,10 @@ class HomePage extends Component<any, IHomePageState> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.streamListener.stop();
|
||||||
|
}
|
||||||
|
|
||||||
insertBacklog() {
|
insertBacklog() {
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
let posts = this.state.posts;
|
let posts = this.state.posts;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { withStyles, CircularProgress, Typography, Paper, Button, Chip, Avatar,
|
||||||
import {styles} from './PageLayout.styles';
|
import {styles} from './PageLayout.styles';
|
||||||
import Post from '../components/Post';
|
import Post from '../components/Post';
|
||||||
import { Status } from '../types/Status';
|
import { Status } from '../types/Status';
|
||||||
import Mastodon from 'megalodon';
|
import Mastodon, { StreamListener } from 'megalodon';
|
||||||
import {withSnackbar} from 'notistack';
|
import {withSnackbar} from 'notistack';
|
||||||
import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward';
|
import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward';
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ interface ILocalPageState {
|
||||||
class LocalPage extends Component<any, ILocalPageState> {
|
class LocalPage extends Component<any, ILocalPageState> {
|
||||||
|
|
||||||
client: Mastodon;
|
client: Mastodon;
|
||||||
streamListener: any;
|
streamListener: StreamListener;
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -31,11 +31,11 @@ class LocalPage extends Component<any, ILocalPageState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.client = new Mastodon(localStorage.getItem('access_token') as string, localStorage.getItem('baseurl') as string + "/api/v1");
|
this.client = new Mastodon(localStorage.getItem('access_token') as string, localStorage.getItem('baseurl') as string + "/api/v1");
|
||||||
|
this.streamListener = this.client.stream('/streaming/public/local');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.streamListener = this.client.stream('/streaming/public/local');
|
|
||||||
|
|
||||||
this.streamListener.on('connect', () => {
|
this.streamListener.on('connect', () => {
|
||||||
this.client.get('/timelines/public', {limit: 40, local: true}).then((resp: any) => {
|
this.client.get('/timelines/public', {limit: 40, local: true}).then((resp: any) => {
|
||||||
|
@ -92,6 +92,10 @@ class LocalPage extends Component<any, ILocalPageState> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.streamListener.stop();
|
||||||
|
}
|
||||||
|
|
||||||
insertBacklog() {
|
insertBacklog() {
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
let posts = this.state.posts;
|
let posts = this.state.posts;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { withStyles, CircularProgress, Typography, Paper, Button, Chip, Avatar,
|
||||||
import {styles} from './PageLayout.styles';
|
import {styles} from './PageLayout.styles';
|
||||||
import Post from '../components/Post';
|
import Post from '../components/Post';
|
||||||
import { Status } from '../types/Status';
|
import { Status } from '../types/Status';
|
||||||
import Mastodon from 'megalodon';
|
import Mastodon, { StreamListener } from 'megalodon';
|
||||||
import {withSnackbar} from 'notistack';
|
import {withSnackbar} from 'notistack';
|
||||||
import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward';
|
import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward';
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ interface IPublicPageState {
|
||||||
class PublicPage extends Component<any, IPublicPageState> {
|
class PublicPage extends Component<any, IPublicPageState> {
|
||||||
|
|
||||||
client: Mastodon;
|
client: Mastodon;
|
||||||
streamListener: any;
|
streamListener: StreamListener;
|
||||||
|
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -31,12 +31,11 @@ class PublicPage extends Component<any, IPublicPageState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.client = new Mastodon(localStorage.getItem('access_token') as string, localStorage.getItem('baseurl') as string + "/api/v1");
|
this.client = new Mastodon(localStorage.getItem('access_token') as string, localStorage.getItem('baseurl') as string + "/api/v1");
|
||||||
|
this.streamListener = this.client.stream('/streaming/public');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.streamListener = this.client.stream('/streaming/public');
|
|
||||||
|
|
||||||
this.streamListener.on('connect', () => {
|
this.streamListener.on('connect', () => {
|
||||||
this.client.get('/timelines/public', {limit: 40}).then((resp: any) => {
|
this.client.get('/timelines/public', {limit: 40}).then((resp: any) => {
|
||||||
let statuses: [Status] = resp.data;
|
let statuses: [Status] = resp.data;
|
||||||
|
@ -92,6 +91,10 @@ class PublicPage extends Component<any, IPublicPageState> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.streamListener.stop();
|
||||||
|
}
|
||||||
|
|
||||||
insertBacklog() {
|
insertBacklog() {
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
let posts = this.state.posts;
|
let posts = this.state.posts;
|
||||||
|
|
Loading…
Reference in New Issue