2019-09-18 19:52:39 +02:00
|
|
|
import React, { Component } from "react";
|
|
|
|
import {
|
|
|
|
Dialog,
|
|
|
|
DialogContent,
|
|
|
|
DialogActions,
|
|
|
|
withStyles,
|
|
|
|
Button,
|
|
|
|
CardHeader,
|
|
|
|
Avatar,
|
|
|
|
TextField,
|
|
|
|
Toolbar,
|
|
|
|
IconButton,
|
|
|
|
Fade,
|
|
|
|
Typography,
|
|
|
|
Tooltip,
|
|
|
|
Menu,
|
|
|
|
MenuItem,
|
|
|
|
GridList,
|
|
|
|
ListSubheader,
|
|
|
|
GridListTile
|
|
|
|
} from "@material-ui/core";
|
|
|
|
import { parse as parseParams, ParsedQuery } from "query-string";
|
|
|
|
import { styles } from "./Compose.styles";
|
|
|
|
import { UAccount } from "../types/Account";
|
|
|
|
import { Visibility } from "../types/Visibility";
|
|
|
|
import CameraAltIcon from "@material-ui/icons/CameraAlt";
|
|
|
|
import TagFacesIcon from "@material-ui/icons/TagFaces";
|
|
|
|
import HowToVoteIcon from "@material-ui/icons/HowToVote";
|
|
|
|
import VisibilityIcon from "@material-ui/icons/Visibility";
|
|
|
|
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 { withSnackbar } from "notistack";
|
|
|
|
import { Attachment } from "../types/Attachment";
|
|
|
|
import { PollWizard, PollWizardOption } from "../types/Poll";
|
|
|
|
import filedialog from "file-dialog";
|
|
|
|
import ComposeMediaAttachment from "../components/ComposeMediaAttachment";
|
|
|
|
import EmojiPicker from "../components/EmojiPicker";
|
|
|
|
import { DateTimePicker, MuiPickersUtilsProvider } from "material-ui-pickers";
|
|
|
|
import MomentUtils from "@date-io/moment";
|
|
|
|
import { getUserDefaultVisibility, getConfig } from "../utilities/settings";
|
2019-04-05 19:22:30 +02:00
|
|
|
|
2019-04-04 02:01:54 +02:00
|
|
|
interface IComposerState {
|
|
|
|
account: UAccount;
|
|
|
|
visibility: Visibility;
|
|
|
|
sensitive: boolean;
|
|
|
|
sensitiveText?: string;
|
|
|
|
visibilityMenu: boolean;
|
|
|
|
text: string;
|
|
|
|
remainingChars: number;
|
|
|
|
reply?: string;
|
|
|
|
acct?: string;
|
|
|
|
attachments?: [Attachment];
|
|
|
|
poll?: PollWizard;
|
2019-04-06 19:41:43 +02:00
|
|
|
pollExpiresDate?: any;
|
2019-04-05 22:29:11 +02:00
|
|
|
showEmojis: boolean;
|
2019-04-27 19:24:24 +02:00
|
|
|
federated: boolean;
|
2019-04-04 02:01:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class Composer extends Component<any, IComposerState> {
|
|
|
|
client: Mastodon;
|
2019-09-18 19:52:39 +02:00
|
|
|
|
2019-04-04 02:01:54 +02:00
|
|
|
constructor(props: any) {
|
|
|
|
super(props);
|
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
this.client = new Mastodon(
|
|
|
|
localStorage.getItem("access_token") as string,
|
|
|
|
localStorage.getItem("baseurl") + "/api/v1"
|
|
|
|
);
|
2019-04-04 02:01:54 +02:00
|
|
|
|
|
|
|
this.state = {
|
2019-09-18 19:52:39 +02:00
|
|
|
account: JSON.parse(localStorage.getItem("account") as string),
|
2019-04-20 23:23:08 +02:00
|
|
|
visibility: getUserDefaultVisibility(),
|
2019-04-04 02:01:54 +02:00
|
|
|
sensitive: false,
|
|
|
|
visibilityMenu: false,
|
2019-09-18 19:52:39 +02:00
|
|
|
text: "",
|
2019-04-05 22:29:11 +02:00
|
|
|
remainingChars: 500,
|
2019-04-27 19:24:24 +02:00
|
|
|
showEmojis: false,
|
|
|
|
federated: true
|
2019-09-18 19:52:39 +02:00
|
|
|
};
|
2019-04-04 02:01:54 +02:00
|
|
|
}
|
|
|
|
|
2019-04-05 19:22:30 +02:00
|
|
|
componentDidMount() {
|
|
|
|
let state = this.getComposerParams(this.props);
|
2019-09-18 19:52:39 +02:00
|
|
|
let text = state.acct ? `@${state.acct}: ` : "";
|
2019-04-27 19:24:24 +02:00
|
|
|
getConfig().then((config: any) => {
|
2019-09-18 19:52:39 +02:00
|
|
|
this.setState({
|
2019-05-08 21:24:36 +02:00
|
|
|
federated: config.federation.allowPublicPosts,
|
2019-04-27 19:24:24 +02:00
|
|
|
reply: state.reply,
|
|
|
|
acct: state.acct,
|
|
|
|
visibility: state.visibility,
|
|
|
|
text,
|
|
|
|
remainingChars: 500 - text.length
|
|
|
|
});
|
2019-09-18 19:52:39 +02:00
|
|
|
});
|
2019-04-05 19:22:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
componentWillReceiveProps(props: any) {
|
|
|
|
let state = this.getComposerParams(props);
|
2019-09-18 19:52:39 +02:00
|
|
|
let text = state.acct ? `@${state.acct}: ` : "";
|
2019-04-05 19:22:30 +02:00
|
|
|
this.setState({
|
|
|
|
reply: state.reply,
|
|
|
|
acct: state.acct,
|
|
|
|
visibility: state.visibility,
|
2019-04-05 22:45:54 +02:00
|
|
|
text,
|
|
|
|
remainingChars: 500 - text.length
|
2019-09-18 19:52:39 +02:00
|
|
|
});
|
2019-04-05 19:22:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
checkComposerParams(location?: string): ParsedQuery {
|
|
|
|
let params = "";
|
2019-09-18 19:52:39 +02:00
|
|
|
if (location !== undefined && typeof location === "string") {
|
2019-04-05 19:22:30 +02:00
|
|
|
params = location.replace("#/compose", "");
|
|
|
|
} else {
|
|
|
|
params = window.location.hash.replace("#/compose", "");
|
|
|
|
}
|
|
|
|
return parseParams(params);
|
|
|
|
}
|
|
|
|
|
|
|
|
getComposerParams(props: any) {
|
|
|
|
let params = this.checkComposerParams(props.location);
|
|
|
|
let reply: string = "";
|
|
|
|
let acct: string = "";
|
2019-09-18 19:52:39 +02:00
|
|
|
let visibility = this.state.visibility;
|
2019-04-05 19:22:30 +02:00
|
|
|
|
|
|
|
if (params.reply) {
|
|
|
|
reply = params.reply.toString();
|
|
|
|
}
|
|
|
|
if (params.acct) {
|
|
|
|
acct = params.acct.toString();
|
|
|
|
}
|
|
|
|
if (params.visibility) {
|
|
|
|
visibility = params.visibility.toString() as Visibility;
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
reply,
|
|
|
|
acct,
|
|
|
|
visibility
|
2019-09-18 19:52:39 +02:00
|
|
|
};
|
2019-04-05 19:22:30 +02:00
|
|
|
}
|
|
|
|
|
2019-04-04 02:01:54 +02:00
|
|
|
updateTextFromField(text: string) {
|
|
|
|
this.setState({ text, remainingChars: 500 - text.length });
|
|
|
|
}
|
|
|
|
|
|
|
|
updateWarningFromField(sensitiveText: string) {
|
|
|
|
this.setState({ sensitiveText });
|
|
|
|
}
|
|
|
|
|
|
|
|
changeVisibility(visibility: Visibility) {
|
|
|
|
this.setState({ visibility });
|
|
|
|
}
|
2019-09-18 19:52:39 +02:00
|
|
|
|
2019-04-04 02:01:54 +02:00
|
|
|
uploadMedia() {
|
|
|
|
filedialog({
|
|
|
|
multiple: false,
|
|
|
|
accept: "image/*, video/*"
|
2019-09-18 19:52:39 +02:00
|
|
|
})
|
|
|
|
.then((media: FileList) => {
|
|
|
|
let mediaForm = new FormData();
|
|
|
|
mediaForm.append("file", media[0]);
|
|
|
|
this.props.enqueueSnackbar("Uploading media...", {
|
|
|
|
persist: true,
|
|
|
|
key: "media-upload"
|
|
|
|
});
|
|
|
|
this.client
|
|
|
|
.post("/media", mediaForm)
|
|
|
|
.then((resp: any) => {
|
|
|
|
let attachment: Attachment = resp.data;
|
|
|
|
let attachments = this.state.attachments;
|
|
|
|
if (attachments) {
|
|
|
|
attachments.push(attachment);
|
|
|
|
} else {
|
|
|
|
attachments = [attachment];
|
|
|
|
}
|
|
|
|
this.setState({ attachments });
|
|
|
|
this.props.closeSnackbar("media-upload");
|
|
|
|
this.props.enqueueSnackbar("Media uploaded.");
|
|
|
|
})
|
|
|
|
.catch((err: Error) => {
|
|
|
|
this.props.closeSnackbar("media-upload");
|
|
|
|
this.props.enqueueSnackbar(
|
|
|
|
"Couldn't upload media: " + err.name,
|
|
|
|
{ variant: "error" }
|
|
|
|
);
|
|
|
|
});
|
2019-04-04 02:01:54 +02:00
|
|
|
})
|
2019-09-18 19:52:39 +02:00
|
|
|
.catch((err: Error) => {
|
|
|
|
this.props.enqueueSnackbar("Couldn't get media: " + err.name, {
|
|
|
|
variant: "error"
|
|
|
|
});
|
|
|
|
console.error(err.message);
|
|
|
|
});
|
2019-04-04 02:01:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
getOnlyMediaIds() {
|
|
|
|
let ids: string[] = [];
|
|
|
|
if (this.state.attachments) {
|
|
|
|
this.state.attachments.map((attachment: Attachment) => {
|
|
|
|
ids.push(attachment.id);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return ids;
|
|
|
|
}
|
|
|
|
|
2019-04-05 22:29:11 +02:00
|
|
|
fetchAttachmentAfterUpdate(attachment: Attachment) {
|
|
|
|
let attachments = this.state.attachments;
|
|
|
|
if (attachments) {
|
|
|
|
attachments.forEach((attach: Attachment) => {
|
|
|
|
if (attach.id === attachment.id && attachments) {
|
|
|
|
attachments[attachments.indexOf(attach)] = attachment;
|
|
|
|
}
|
2019-09-18 19:52:39 +02:00
|
|
|
});
|
2019-04-05 22:29:11 +02:00
|
|
|
this.setState({ attachments });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
deleteMediaAttachment(attachment: Attachment) {
|
|
|
|
let attachments = this.state.attachments;
|
|
|
|
if (attachments) {
|
|
|
|
attachments.forEach((attach: Attachment) => {
|
|
|
|
if (attach.id === attachment.id && attachments) {
|
|
|
|
attachments.splice(attachments.indexOf(attach), 1);
|
|
|
|
}
|
|
|
|
this.setState({ attachments });
|
2019-09-18 19:52:39 +02:00
|
|
|
});
|
2019-04-05 22:29:11 +02:00
|
|
|
this.props.enqueueSnackbar("Attachment removed.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
insertEmoji(e: any) {
|
|
|
|
if (e.custom) {
|
2019-09-18 19:52:39 +02:00
|
|
|
let text = this.state.text + e.colons;
|
|
|
|
this.setState({
|
2019-04-05 22:29:11 +02:00
|
|
|
text,
|
|
|
|
remainingChars: 500 - text.length
|
|
|
|
});
|
|
|
|
} else {
|
2019-09-18 19:52:39 +02:00
|
|
|
let text = this.state.text + e.native;
|
|
|
|
this.setState({
|
2019-04-05 22:29:11 +02:00
|
|
|
text,
|
|
|
|
remainingChars: 500 - text.length
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-06 19:41:43 +02:00
|
|
|
createPoll() {
|
|
|
|
if (this.state.poll === undefined) {
|
|
|
|
let expiration = new Date();
|
|
|
|
let current = new Date();
|
|
|
|
expiration.setMinutes(expiration.getMinutes() + 30);
|
2019-09-18 19:52:39 +02:00
|
|
|
let expiryDifference =
|
|
|
|
expiration.getTime() - current.getTime() / 1000;
|
2019-04-06 19:41:43 +02:00
|
|
|
let temporaryPoll: PollWizard = {
|
|
|
|
expires_at: expiryDifference.toString(),
|
|
|
|
multiple: false,
|
2019-09-18 19:52:39 +02:00
|
|
|
options: [{ title: "Option 1" }, { title: "Option 2" }]
|
|
|
|
};
|
2019-04-06 19:41:43 +02:00
|
|
|
this.setState({
|
|
|
|
poll: temporaryPoll,
|
|
|
|
pollExpiresDate: expiration
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
addPollItem() {
|
2019-09-18 19:52:39 +02:00
|
|
|
if (
|
|
|
|
this.state.poll !== undefined &&
|
|
|
|
this.state.poll.options.length < 4
|
|
|
|
) {
|
|
|
|
let newOption = { title: "New option" };
|
2019-04-06 19:41:43 +02:00
|
|
|
let options = this.state.poll.options;
|
|
|
|
let poll = this.state.poll;
|
|
|
|
options.push(newOption);
|
|
|
|
poll.options = options;
|
|
|
|
poll.multiple = true;
|
|
|
|
this.setState({
|
|
|
|
poll: poll
|
2019-09-18 19:52:39 +02:00
|
|
|
});
|
2019-04-06 19:41:43 +02:00
|
|
|
} else if (this.state.poll && this.state.poll.options.length == 4) {
|
2019-09-18 19:52:39 +02:00
|
|
|
this.props.enqueueSnackbar(
|
|
|
|
"You've reached the options limit in your poll.",
|
|
|
|
{ variant: "error" }
|
|
|
|
);
|
2019-04-06 19:41:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
});
|
2019-09-18 19:52:39 +02:00
|
|
|
this.props.enqueueSnackbar("Option edited.");
|
2019-04-06 19:41:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
removePollItem(item: string) {
|
2019-09-18 19:52:39 +02:00
|
|
|
if (
|
|
|
|
this.state.poll !== undefined &&
|
|
|
|
this.state.poll.options.length > 2
|
|
|
|
) {
|
2019-04-06 19:41:43 +02:00
|
|
|
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
|
2019-09-18 19:52:39 +02:00
|
|
|
});
|
2019-04-06 19:41:43 +02:00
|
|
|
} else if (this.state.poll && this.state.poll.options.length <= 2) {
|
2019-09-18 19:52:39 +02:00
|
|
|
this.props.enqueueSnackbar("Polls must have at least two items.", {
|
|
|
|
variant: "error"
|
|
|
|
});
|
2019-04-06 19:41:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setPollExpires(date: string) {
|
|
|
|
let currentDate = new Date();
|
|
|
|
let newDate = new Date(date);
|
|
|
|
let poll = this.state.poll;
|
|
|
|
if (poll) {
|
2019-09-18 19:52:39 +02:00
|
|
|
let expiry = (newDate.getTime() - currentDate.getTime()) / 1000;
|
2019-04-06 19:41:43 +02:00
|
|
|
console.log(expiry);
|
|
|
|
if (expiry >= 1800) {
|
|
|
|
poll.expires_at = expiry.toString();
|
|
|
|
this.setState({ poll, pollExpiresDate: date });
|
2019-09-18 19:52:39 +02:00
|
|
|
this.props.enqueueSnackbar("Expiration updated.");
|
2019-04-06 19:41:43 +02:00
|
|
|
} else {
|
2019-09-18 19:52:39 +02:00
|
|
|
this.props.enqueueSnackbar(
|
|
|
|
"Expiration is too small (min. 30 minutes).",
|
|
|
|
{ variant: "error" }
|
|
|
|
);
|
2019-04-06 19:41:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
removePoll() {
|
|
|
|
this.setState({
|
|
|
|
poll: undefined
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-04-08 22:10:21 +02:00
|
|
|
postViaKeyboard(event: any) {
|
|
|
|
if ((event.metaKey || event.ctrlKey) && event.keyCode === 13) {
|
|
|
|
this.post();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-04 02:01:54 +02:00
|
|
|
post() {
|
2019-04-06 19:41:43 +02:00
|
|
|
let pollOptions: string[] = [];
|
|
|
|
if (this.state.poll) {
|
|
|
|
this.state.poll.options.forEach((option: PollWizardOption) => {
|
|
|
|
pollOptions.push(option.title);
|
2019-09-18 19:52:39 +02:00
|
|
|
});
|
2019-04-06 19:41:43 +02:00
|
|
|
}
|
2019-09-18 19:52:39 +02:00
|
|
|
this.client
|
|
|
|
.post("/statuses", {
|
|
|
|
status: this.state.text,
|
|
|
|
media_ids: this.getOnlyMediaIds(),
|
|
|
|
visibility: this.state.visibility,
|
|
|
|
sensitive: this.state.sensitive,
|
|
|
|
spoiler_text: this.state.sensitiveText,
|
|
|
|
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(() => {
|
|
|
|
this.props.enqueueSnackbar("Posted!");
|
|
|
|
window.history.back();
|
|
|
|
})
|
|
|
|
.catch((err: Error) => {
|
|
|
|
this.props.enqueueSnackbar("Couldn't post: " + err.name);
|
|
|
|
console.log(err.message);
|
|
|
|
});
|
2019-04-04 02:01:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
toggleSensitive() {
|
|
|
|
this.setState({ sensitive: !this.state.sensitive });
|
|
|
|
}
|
|
|
|
|
|
|
|
toggleVisibilityMenu() {
|
|
|
|
this.setState({ visibilityMenu: !this.state.visibilityMenu });
|
|
|
|
}
|
|
|
|
|
2019-04-05 22:29:11 +02:00
|
|
|
toggleEmojis() {
|
|
|
|
this.setState({ showEmojis: !this.state.showEmojis });
|
2019-04-05 18:44:03 +02:00
|
|
|
}
|
|
|
|
|
2019-04-04 02:01:54 +02:00
|
|
|
render() {
|
2019-09-18 19:52:39 +02:00
|
|
|
const { classes } = this.props;
|
2019-04-20 23:14:47 +02:00
|
|
|
console.log(this.state);
|
2019-04-04 02:01:54 +02:00
|
|
|
|
|
|
|
return (
|
2019-09-18 19:52:39 +02:00
|
|
|
<Dialog
|
|
|
|
open={true}
|
|
|
|
maxWidth="sm"
|
|
|
|
fullWidth={true}
|
|
|
|
className={classes.dialog}
|
|
|
|
onClose={() => window.history.back()}
|
|
|
|
>
|
|
|
|
<CardHeader
|
|
|
|
avatar={<Avatar src={this.state.account.avatar_static} />}
|
2019-04-04 02:01:54 +02:00
|
|
|
title={`${this.state.account.display_name} (@${this.state.account.acct})`}
|
2019-09-18 19:52:39 +02:00
|
|
|
subheader={
|
|
|
|
this.state.visibility.charAt(0).toUpperCase() +
|
|
|
|
this.state.visibility.substr(1)
|
|
|
|
}
|
2019-04-04 02:01:54 +02:00
|
|
|
/>
|
|
|
|
<DialogContent className={classes.dialogContent}>
|
2019-09-18 19:52:39 +02:00
|
|
|
{this.state.sensitive ? (
|
|
|
|
<Fade in={this.state.sensitive}>
|
|
|
|
<TextField
|
|
|
|
variant="outlined"
|
|
|
|
fullWidth
|
|
|
|
label="Content warning"
|
|
|
|
margin="dense"
|
|
|
|
onChange={event =>
|
|
|
|
this.updateWarningFromField(
|
|
|
|
event.target.value
|
|
|
|
)
|
|
|
|
}
|
|
|
|
></TextField>
|
|
|
|
</Fade>
|
|
|
|
) : null}
|
|
|
|
{this.state.visibility === "direct" ? (
|
|
|
|
<Typography variant="caption">
|
|
|
|
<WarningIcon className={classes.warningCaption} />{" "}
|
|
|
|
Don't forget to add the usernames of the accounts
|
|
|
|
you want to message in your post.
|
|
|
|
</Typography>
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
<TextField
|
|
|
|
variant="outlined"
|
2019-04-04 02:01:54 +02:00
|
|
|
multiline
|
|
|
|
fullWidth
|
|
|
|
placeholder="What's on your mind?"
|
|
|
|
margin="normal"
|
2019-09-18 19:52:39 +02:00
|
|
|
onChange={event =>
|
|
|
|
this.updateTextFromField(event.target.value)
|
2019-04-04 02:01:54 +02:00
|
|
|
}
|
2019-09-18 19:52:39 +02:00
|
|
|
onKeyDown={event => this.postViaKeyboard(event)}
|
|
|
|
inputProps={{
|
|
|
|
maxLength: 500
|
|
|
|
}}
|
2019-04-05 22:29:11 +02:00
|
|
|
value={this.state.text}
|
2019-04-04 02:01:54 +02:00
|
|
|
/>
|
2019-09-18 19:52:39 +02:00
|
|
|
<Typography
|
|
|
|
variant="caption"
|
|
|
|
className={
|
|
|
|
this.state.remainingChars <= 100
|
|
|
|
? classes.charsReachingLimit
|
|
|
|
: null
|
|
|
|
}
|
|
|
|
>
|
|
|
|
{`${this.state.remainingChars} character${
|
|
|
|
this.state.remainingChars === 1 ? "" : "s"
|
|
|
|
} remaining`}
|
2019-04-04 02:01:54 +02:00
|
|
|
</Typography>
|
2019-09-18 19:52:39 +02:00
|
|
|
{this.state.attachments &&
|
|
|
|
this.state.attachments.length > 0 ? (
|
|
|
|
<div className={classes.composeAttachmentArea}>
|
|
|
|
<GridList
|
|
|
|
cellHeight={48}
|
|
|
|
className={
|
|
|
|
classes.composeAttachmentAreaGridList
|
2019-04-06 19:41:43 +02:00
|
|
|
}
|
2019-09-18 19:52:39 +02:00
|
|
|
>
|
|
|
|
<GridListTile
|
|
|
|
key="Subheader-composer"
|
|
|
|
cols={2}
|
|
|
|
style={{ height: "auto" }}
|
|
|
|
>
|
|
|
|
<ListSubheader>Attachments</ListSubheader>
|
|
|
|
</GridListTile>
|
|
|
|
{this.state.attachments.map(
|
|
|
|
(attachment: Attachment) => {
|
|
|
|
let c = (
|
|
|
|
<ComposeMediaAttachment
|
|
|
|
client={this.client}
|
|
|
|
attachment={attachment}
|
|
|
|
onAttachmentUpdate={(
|
|
|
|
attachment: Attachment
|
|
|
|
) =>
|
|
|
|
this.fetchAttachmentAfterUpdate(
|
|
|
|
attachment
|
|
|
|
)
|
|
|
|
}
|
|
|
|
onDeleteCallback={(
|
|
|
|
attachment: Attachment
|
|
|
|
) =>
|
|
|
|
this.deleteMediaAttachment(
|
|
|
|
attachment
|
|
|
|
)
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
)}
|
|
|
|
</GridList>
|
|
|
|
</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}
|
2019-04-04 02:01:54 +02:00
|
|
|
</DialogContent>
|
|
|
|
<Toolbar className={classes.dialogActions}>
|
|
|
|
<Tooltip title="Add photos or videos">
|
2019-09-18 19:52:39 +02:00
|
|
|
<IconButton
|
|
|
|
disabled={this.state.poll !== undefined}
|
|
|
|
onClick={() => this.uploadMedia()}
|
|
|
|
id="compose-media"
|
|
|
|
>
|
|
|
|
<CameraAltIcon />
|
2019-04-04 02:01:54 +02:00
|
|
|
</IconButton>
|
|
|
|
</Tooltip>
|
|
|
|
<Tooltip title="Insert emoji">
|
2019-09-18 19:52:39 +02:00
|
|
|
<IconButton
|
|
|
|
id="compose-emoji"
|
|
|
|
onClick={() => this.toggleEmojis()}
|
|
|
|
className={classes.desktopOnly}
|
|
|
|
>
|
|
|
|
<TagFacesIcon />
|
2019-04-04 02:01:54 +02:00
|
|
|
</IconButton>
|
|
|
|
</Tooltip>
|
2019-04-05 22:29:11 +02:00
|
|
|
<Menu
|
|
|
|
open={this.state.showEmojis}
|
2019-09-18 19:52:39 +02:00
|
|
|
anchorEl={document.getElementById("compose-emoji")}
|
2019-04-05 22:29:11 +02:00
|
|
|
onClose={() => this.toggleEmojis()}
|
2019-04-05 22:45:54 +02:00
|
|
|
className={classes.composeEmoji}
|
2019-04-05 22:29:11 +02:00
|
|
|
>
|
2019-09-18 19:52:39 +02:00
|
|
|
<EmojiPicker
|
|
|
|
onGetEmoji={(emoji: any) => this.insertEmoji(emoji)}
|
|
|
|
/>
|
2019-04-05 22:29:11 +02:00
|
|
|
</Menu>
|
2019-04-06 19:42:11 +02:00
|
|
|
<Tooltip title="Add/remove a poll">
|
2019-09-18 19:52:39 +02:00
|
|
|
<IconButton
|
|
|
|
disabled={
|
|
|
|
this.state.attachments &&
|
|
|
|
this.state.attachments.length > 0
|
|
|
|
}
|
|
|
|
id="compose-poll"
|
|
|
|
onClick={() => {
|
|
|
|
this.state.poll
|
|
|
|
? this.removePoll()
|
|
|
|
: this.createPoll();
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<HowToVoteIcon />
|
2019-04-04 02:01:54 +02:00
|
|
|
</IconButton>
|
|
|
|
</Tooltip>
|
|
|
|
<Tooltip title="Change who sees your post">
|
2019-09-18 19:52:39 +02:00
|
|
|
<IconButton
|
|
|
|
id="compose-visibility"
|
|
|
|
onClick={() => this.toggleVisibilityMenu()}
|
|
|
|
>
|
|
|
|
<VisibilityIcon />
|
2019-04-04 02:01:54 +02:00
|
|
|
</IconButton>
|
|
|
|
</Tooltip>
|
|
|
|
<Tooltip title="Set a content warning">
|
2019-09-18 19:52:39 +02:00
|
|
|
<IconButton
|
|
|
|
onClick={() => this.toggleSensitive()}
|
|
|
|
id="compose-warning"
|
|
|
|
>
|
|
|
|
<WarningIcon />
|
2019-04-04 02:01:54 +02:00
|
|
|
</IconButton>
|
|
|
|
</Tooltip>
|
2019-09-18 19:52:39 +02:00
|
|
|
<Menu
|
|
|
|
open={this.state.visibilityMenu}
|
|
|
|
anchorEl={document.getElementById("compose-visibility")}
|
|
|
|
onClose={() => this.toggleVisibilityMenu()}
|
|
|
|
>
|
|
|
|
<MenuItem
|
|
|
|
onClick={() => this.changeVisibility("direct")}
|
|
|
|
>
|
|
|
|
Direct (direct message)
|
|
|
|
</MenuItem>
|
|
|
|
<MenuItem
|
|
|
|
onClick={() => this.changeVisibility("private")}
|
|
|
|
>
|
|
|
|
Private (followers only)
|
|
|
|
</MenuItem>
|
|
|
|
<MenuItem
|
|
|
|
onClick={() => this.changeVisibility("unlisted")}
|
|
|
|
>
|
|
|
|
Unlisted
|
|
|
|
</MenuItem>
|
|
|
|
{this.state.federated ? (
|
|
|
|
<MenuItem
|
|
|
|
onClick={() => this.changeVisibility("public")}
|
|
|
|
>
|
|
|
|
Public
|
|
|
|
</MenuItem>
|
|
|
|
) : null}
|
2019-04-04 02:01:54 +02:00
|
|
|
</Menu>
|
|
|
|
</Toolbar>
|
|
|
|
<DialogActions>
|
2019-09-18 19:52:39 +02:00
|
|
|
<Button color="secondary" onClick={() => this.post()}>
|
|
|
|
Post
|
|
|
|
</Button>
|
2019-04-04 02:01:54 +02:00
|
|
|
</DialogActions>
|
|
|
|
</Dialog>
|
2019-09-18 19:52:39 +02:00
|
|
|
);
|
2019-04-04 02:01:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
export default withStyles(styles)(withSnackbar(Composer));
|