866 lines
33 KiB
TypeScript
866 lines
33 KiB
TypeScript
import React from "react";
|
|
import {
|
|
Avatar,
|
|
Button,
|
|
Card,
|
|
CardActionArea,
|
|
CardActions,
|
|
CardContent,
|
|
CardHeader,
|
|
CardMedia,
|
|
Dialog,
|
|
DialogActions,
|
|
DialogContent,
|
|
DialogContentText,
|
|
DialogTitle,
|
|
Divider,
|
|
ExpansionPanel,
|
|
ExpansionPanelDetails,
|
|
ExpansionPanelSummary,
|
|
FormControlLabel,
|
|
IconButton,
|
|
Menu,
|
|
MenuItem,
|
|
Radio,
|
|
RadioGroup,
|
|
Tooltip,
|
|
Typography,
|
|
withStyles
|
|
} from "@material-ui/core";
|
|
import MoreVertIcon from "@material-ui/icons/MoreVert";
|
|
import ReplyIcon from "@material-ui/icons/Reply";
|
|
import FavoriteIcon from "@material-ui/icons/Favorite";
|
|
import AutorenewIcon from "@material-ui/icons/Autorenew";
|
|
import OpenInNewIcon from "@material-ui/icons/OpenInNew";
|
|
import PublicIcon from "@material-ui/icons/Public";
|
|
import VisibilityOffIcon from "@material-ui/icons/VisibilityOff";
|
|
import WarningIcon from "@material-ui/icons/Warning";
|
|
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
|
|
import GroupIcon from "@material-ui/icons/Group";
|
|
import ForumIcon from "@material-ui/icons/Forum";
|
|
import AlternateEmailIcon from "@material-ui/icons/AlternateEmail";
|
|
import LocalOfferIcon from "@material-ui/icons/LocalOffer";
|
|
import { styles } from "./Post.styles";
|
|
import { Status } from "../../types/Status";
|
|
import { Tag } from "../../types/Tag";
|
|
import { Mention } from "../../types/Mention";
|
|
import { Visibility } from "../../types/Visibility";
|
|
import moment from "moment";
|
|
import AttachmentComponent from "../Attachment";
|
|
import Mastodon from "megalodon";
|
|
import {
|
|
LinkableAvatar,
|
|
LinkableChip,
|
|
LinkableIconButton,
|
|
LinkableMenuItem
|
|
} from "../../interfaces/overrides";
|
|
import { withSnackbar } from "notistack";
|
|
import ShareMenu from "./PostShareMenu";
|
|
import { emojifyString } from "../../utilities/emojis";
|
|
import { PollOption } from "../../types/Poll";
|
|
|
|
interface IPostProps {
|
|
post: Status;
|
|
classes: any;
|
|
client: Mastodon;
|
|
threadHeader: boolean;
|
|
}
|
|
|
|
interface IPostState {
|
|
post: Status;
|
|
media_slides?: number;
|
|
menuIsOpen: boolean;
|
|
myVote?: [number];
|
|
deletePostDialog: boolean;
|
|
myAccount?: string;
|
|
}
|
|
|
|
export class Post extends React.Component<any, IPostState> {
|
|
client: Mastodon;
|
|
|
|
constructor(props: any) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
post: this.props.post,
|
|
media_slides:
|
|
this.props.post.media_attachments.length > 0
|
|
? this.props.post.media_attachments.length
|
|
: 0,
|
|
menuIsOpen: false,
|
|
deletePostDialog: false
|
|
};
|
|
|
|
this.client = this.props.client;
|
|
}
|
|
|
|
componentWillMount() {
|
|
this.setState({
|
|
myAccount: sessionStorage.getItem("id") as string
|
|
});
|
|
}
|
|
|
|
shouldComponentUpdate(nextProps: any, nextState: any) {
|
|
if (nextState === this.state) return false;
|
|
return true;
|
|
}
|
|
|
|
togglePostMenu() {
|
|
this.setState({ menuIsOpen: !this.state.menuIsOpen });
|
|
}
|
|
|
|
togglePostDeleteDialog() {
|
|
this.setState({ deletePostDialog: !this.state.deletePostDialog });
|
|
}
|
|
|
|
deletePost() {
|
|
this.client
|
|
.del("/statuses/" + this.state.post.id)
|
|
.then(() => {
|
|
this.props.enqueueSnackbar(
|
|
"Post deleted. Refresh to see changes."
|
|
);
|
|
})
|
|
.catch((err: Error) => {
|
|
this.props.enqueueSnackbar("Couldn't delete post: " + err.name);
|
|
console.error(err.message);
|
|
});
|
|
}
|
|
|
|
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) {
|
|
const { classes } = this.props;
|
|
|
|
const oldContent = document.createElement("div");
|
|
oldContent.innerHTML = status.content;
|
|
|
|
let anchors = oldContent.getElementsByTagName("a");
|
|
|
|
Array.prototype.forEach.call(anchors, (link: HTMLAnchorElement) => {
|
|
if (
|
|
link.className.includes("mention") ||
|
|
link.className.includes("hashtag")
|
|
) {
|
|
link.removeAttribute("href");
|
|
}
|
|
});
|
|
|
|
oldContent.innerHTML = emojifyString(
|
|
oldContent.innerHTML,
|
|
status.emojis,
|
|
classes.postEmoji
|
|
);
|
|
|
|
return (
|
|
<CardContent className={classes.postContent}>
|
|
<div className={classes.mediaContainer}>
|
|
<Typography
|
|
paragraph
|
|
dangerouslySetInnerHTML={{
|
|
__html: oldContent.innerHTML
|
|
}}
|
|
/>
|
|
{status.card ? (
|
|
<div className={classes.postCard}>
|
|
<Divider />
|
|
<CardActionArea
|
|
href={status.card.url}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
<CardContent>
|
|
<Typography
|
|
gutterBottom
|
|
variant="h6"
|
|
component="h2"
|
|
>
|
|
{status.card.title}
|
|
</Typography>
|
|
<Typography>
|
|
{status.card.description.slice(0, 500) +
|
|
(status.card.description.length >
|
|
500
|
|
? "..."
|
|
: "") ||
|
|
"No description provided. Click with caution."}
|
|
</Typography>
|
|
</CardContent>
|
|
{status.card.image &&
|
|
status.media_attachments.length <= 0 ? (
|
|
<CardMedia
|
|
className={classes.postMedia}
|
|
image={status.card.image}
|
|
/>
|
|
) : (
|
|
<span />
|
|
)}
|
|
<CardContent>
|
|
<Typography>
|
|
{status.card.provider_url ||
|
|
status.card.author_url ||
|
|
status.card.author_url}
|
|
</Typography>
|
|
</CardContent>
|
|
</CardActionArea>
|
|
<Divider />
|
|
</div>
|
|
) : (
|
|
<span />
|
|
)}
|
|
{status.media_attachments.length > 0 ? (
|
|
<AttachmentComponent media={status.media_attachments} />
|
|
) : (
|
|
<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) => {
|
|
return (
|
|
<FormControlLabel
|
|
disabled
|
|
value={pollOption.title}
|
|
control={<Radio />}
|
|
label={`${pollOption.title} (${pollOption.votes_count} votes)`}
|
|
key={
|
|
pollOption.title +
|
|
pollOption.votes_count
|
|
}
|
|
/>
|
|
);
|
|
}
|
|
)}
|
|
</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) => {
|
|
return (
|
|
<FormControlLabel
|
|
value={pollOption.title}
|
|
control={<Radio />}
|
|
label={pollOption.title}
|
|
key={
|
|
pollOption.title +
|
|
pollOption.votes_count
|
|
}
|
|
/>
|
|
);
|
|
}
|
|
)}
|
|
</RadioGroup>
|
|
<Button
|
|
color="primary"
|
|
onClick={() => this.submitVote()}
|
|
>
|
|
Vote
|
|
</Button>
|
|
</div>
|
|
)
|
|
) : null}
|
|
</div>
|
|
</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) {
|
|
const { classes } = this.props;
|
|
const warningText = spoiler_text || "Unmarked content";
|
|
let icon;
|
|
if (
|
|
this.spoilerContainsFlags(spoiler_text) ||
|
|
spoiler_text.includes("Spoiler") ||
|
|
warningText === "Unmarked content"
|
|
) {
|
|
icon = <WarningIcon className={classes.postWarningIcon} />;
|
|
}
|
|
return (
|
|
<ExpansionPanel
|
|
className={
|
|
this.spoilerContainsFlags(spoiler_text)
|
|
? classes.nsfwCard
|
|
: classes.spoilerCard
|
|
}
|
|
color="inherit"
|
|
>
|
|
<ExpansionPanelSummary
|
|
expandIcon={<ExpandMoreIcon />}
|
|
color="inherit"
|
|
>
|
|
{icon}
|
|
<Typography>{warningText}</Typography>
|
|
</ExpansionPanelSummary>
|
|
<ExpansionPanelDetails
|
|
className={classes.postContent}
|
|
color="inherit"
|
|
>
|
|
{this.materializeContent(content)}
|
|
</ExpansionPanelDetails>
|
|
</ExpansionPanel>
|
|
);
|
|
}
|
|
|
|
getReblogOfPost(of: Status | null) {
|
|
if (of !== null) {
|
|
return of.sensitive
|
|
? this.getSensitiveContent(of.spoiler_text, of)
|
|
: this.materializeContent(of);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
getReblogAuthors(post: Status) {
|
|
const { classes } = this.props;
|
|
|
|
let author = post.reblog ? post.reblog.account : post.account;
|
|
let emojis = author.emojis;
|
|
let reblogger = post.reblog ? post.account : undefined;
|
|
|
|
if (reblogger !== undefined) {
|
|
emojis.concat(reblogger.emojis);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<span className={classes.postAuthorNameAndAccount}>
|
|
<span
|
|
className={classes.postAuthorName}
|
|
dangerouslySetInnerHTML={{
|
|
__html: emojifyString(
|
|
author.display_name || author.username,
|
|
emojis,
|
|
classes.postAuthorEmoji
|
|
)
|
|
}}
|
|
></span>
|
|
<span
|
|
className={classes.postAuthorAccount}
|
|
dangerouslySetInnerHTML={{
|
|
__html:
|
|
"@" +
|
|
emojifyString(
|
|
author.acct || author.username,
|
|
emojis,
|
|
classes.postAuthorEmoji
|
|
)
|
|
}}
|
|
></span>
|
|
</span>
|
|
{reblogger ? (
|
|
<div>
|
|
<AutorenewIcon
|
|
fontSize="small"
|
|
className={classes.postReblogIcon}
|
|
/>
|
|
<span
|
|
dangerouslySetInnerHTML={{
|
|
__html: emojifyString(
|
|
reblogger.display_name ||
|
|
reblogger.username,
|
|
emojis,
|
|
classes.postAuthorEmoji
|
|
)
|
|
}}
|
|
></span>
|
|
</div>
|
|
) : null}
|
|
</>
|
|
);
|
|
}
|
|
|
|
getMentions(mention: [Mention]) {
|
|
const { classes } = this.props;
|
|
if (mention.length > 0) {
|
|
return (
|
|
<CardContent className={classes.postTags}>
|
|
<Typography variant="caption">Mentions</Typography>
|
|
{mention.map((person: Mention) => {
|
|
return (
|
|
<LinkableChip
|
|
avatar={
|
|
<Avatar>
|
|
<AlternateEmailIcon />
|
|
</Avatar>
|
|
}
|
|
label={person.username}
|
|
key={
|
|
this.state.post.id + "_mention_" + person.id
|
|
}
|
|
to={`/profile/${person.id}`}
|
|
className={classes.postMention}
|
|
clickable
|
|
/>
|
|
);
|
|
})}
|
|
</CardContent>
|
|
);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
getTags(tags: [Tag]) {
|
|
const { classes } = this.props;
|
|
if (tags.length > 0) {
|
|
return (
|
|
<CardContent className={classes.postTags}>
|
|
<Typography variant="caption">Tags</Typography>
|
|
{tags.map((tag: Tag) => {
|
|
return (
|
|
<LinkableChip
|
|
avatar={
|
|
<Avatar>
|
|
<LocalOfferIcon />
|
|
</Avatar>
|
|
}
|
|
label={tag.name}
|
|
key={this.state.post.id + "_tag_" + tag.name}
|
|
to={`/search?query=${tag.name}&type=tag`}
|
|
className={classes.postMention}
|
|
clickable
|
|
/>
|
|
);
|
|
})}
|
|
</CardContent>
|
|
);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
showVisibilityIcon(visibility: Visibility) {
|
|
const { classes } = this.props;
|
|
switch (visibility) {
|
|
case "public":
|
|
return (
|
|
<Tooltip title="Public">
|
|
<PublicIcon className={classes.postTypeIcon} />
|
|
</Tooltip>
|
|
);
|
|
case "private":
|
|
return (
|
|
<Tooltip title="Followers only">
|
|
<GroupIcon className={classes.postTypeIcon} />
|
|
</Tooltip>
|
|
);
|
|
case "unlisted":
|
|
return (
|
|
<Tooltip title="Unlisted (invisible from public timeline)">
|
|
<VisibilityOffIcon className={classes.postTypeIcon} />
|
|
</Tooltip>
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the post's URL
|
|
* @param post The post to get the URL from
|
|
* @returns A string containing the post's URI
|
|
*/
|
|
getMastodonUrl(post: Status) {
|
|
return post.reblog ? post.reblog.uri : post.uri;
|
|
}
|
|
|
|
/**
|
|
* Tell server a post has been un/favorited and update post state
|
|
* @param post The post to un/favorite
|
|
*/
|
|
async toggleFavorited(post: Status) {
|
|
let action: string = post.favourited ? "unfavourite" : "favourite";
|
|
try {
|
|
// favorite the original post, not the reblog
|
|
let resp: any = await this.client.post(
|
|
`/statuses/${post.reblog ? post.reblog.id : post.id}/${action}`
|
|
);
|
|
// compensate for slow server update
|
|
if (action === "unfavourite") {
|
|
resp.data.favourites_count -= 1;
|
|
// if you unlike both original and reblog before refresh
|
|
// and the post has only one favorite:
|
|
if (resp.data.favourites_count < 0) {
|
|
resp.data.favourites_count = 0;
|
|
}
|
|
}
|
|
this.setState({ post: resp.data as Status });
|
|
} catch (e) {
|
|
this.props.enqueueSnackbar(`Could not ${action} post: ${e.name}`);
|
|
console.error(e.message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tell server a post has been un/reblogged and update post state
|
|
* @param post The post to un/reblog
|
|
*/
|
|
async toggleReblogged(post: Status) {
|
|
let action: string =
|
|
post.reblogged || post.reblog ? "unreblog" : "reblog";
|
|
try {
|
|
// modify the original post, not the reblog
|
|
let resp: any = await this.client.post(
|
|
`/statuses/${post.reblog ? post.reblog.id : post.id}/${action}`
|
|
);
|
|
// compensate for slow server update
|
|
if (action === "unreblog") {
|
|
resp.data.reblogs_count -= 1;
|
|
}
|
|
if (resp.data.reblog) resp.data = resp.data.reblog;
|
|
this.setState({ post: resp.data as Status });
|
|
} catch (e) {
|
|
this.props.enqueueSnackbar(`Could not ${action} post: ${e.name}`);
|
|
console.error(e.message);
|
|
}
|
|
}
|
|
|
|
showDeleteDialog() {
|
|
return (
|
|
<Dialog
|
|
open={this.state.deletePostDialog}
|
|
onClose={() => this.togglePostDeleteDialog()}
|
|
>
|
|
<DialogTitle id="alert-dialog-title">
|
|
Delete this post?
|
|
</DialogTitle>
|
|
<DialogContent>
|
|
<DialogContentText id="alert-dialog-description">
|
|
Are you sure you want to delete this post? This action
|
|
cannot be undone.
|
|
</DialogContentText>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button
|
|
onClick={() => this.togglePostDeleteDialog()}
|
|
color="primary"
|
|
autoFocus
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
onClick={() => {
|
|
this.deletePost();
|
|
this.togglePostDeleteDialog();
|
|
}}
|
|
color="primary"
|
|
>
|
|
Delete
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
);
|
|
}
|
|
|
|
render() {
|
|
const { classes } = this.props;
|
|
const post = this.state.post;
|
|
return (
|
|
<Card
|
|
className={classes.post}
|
|
id={`post_${post.id}`}
|
|
elevation={this.props.threadHeader ? 0 : 1}
|
|
>
|
|
<CardHeader
|
|
classes={{
|
|
content: classes.postHeaderContent,
|
|
title: classes.postHeaderTitle
|
|
}}
|
|
avatar={
|
|
<LinkableAvatar
|
|
to={`/profile/${
|
|
post.reblog
|
|
? post.reblog.account.id
|
|
: post.account.id
|
|
}`}
|
|
src={
|
|
post.reblog
|
|
? post.reblog.account.avatar_static
|
|
: post.account.avatar_static
|
|
}
|
|
/>
|
|
}
|
|
action={
|
|
<Tooltip title="More" placement="left">
|
|
<IconButton
|
|
key={`${post.id}_submenu`}
|
|
id={`${post.id}_submenu`}
|
|
onClick={() => this.togglePostMenu()}
|
|
>
|
|
<MoreVertIcon />
|
|
</IconButton>
|
|
</Tooltip>
|
|
}
|
|
title={this.getReblogAuthors(post)}
|
|
subheader={moment(post.created_at).format(
|
|
"MMMM Do YYYY [at] h:mm A"
|
|
)}
|
|
/>
|
|
{post.reblog ? this.getReblogOfPost(post.reblog) : null}
|
|
{post.sensitive
|
|
? this.getSensitiveContent(post.spoiler_text, post)
|
|
: post.reblog
|
|
? null
|
|
: this.materializeContent(post)}
|
|
{post.reblog && post.reblog.mentions.length > 0
|
|
? this.getMentions(post.reblog.mentions)
|
|
: this.getMentions(post.mentions)}
|
|
{post.reblog && post.reblog.tags.length > 0
|
|
? this.getTags(post.reblog.tags)
|
|
: this.getTags(post.tags)}
|
|
<CardActions>
|
|
<Tooltip title="Reply">
|
|
<LinkableIconButton
|
|
to={`/compose?reply=${
|
|
post.reblog ? post.reblog.id : post.id
|
|
}&visibility=${post.visibility}&acct=${
|
|
post.reblog
|
|
? post.reblog.account.acct
|
|
: post.account.acct
|
|
}`}
|
|
>
|
|
<ReplyIcon />
|
|
</LinkableIconButton>
|
|
</Tooltip>
|
|
<Typography>
|
|
{post.reblog
|
|
? post.reblog.replies_count
|
|
: post.replies_count}
|
|
</Typography>
|
|
<Tooltip title="Favorite">
|
|
<IconButton onClick={() => this.toggleFavorited(post)}>
|
|
<FavoriteIcon
|
|
className={
|
|
post.reblog
|
|
? post.reblog.favourited
|
|
? classes.postDidAction
|
|
: ""
|
|
: post.favourited
|
|
? classes.postDidAction
|
|
: ""
|
|
}
|
|
/>
|
|
</IconButton>
|
|
</Tooltip>
|
|
<Typography>
|
|
{post.reblog
|
|
? post.reblog.favourites_count
|
|
: post.favourites_count}
|
|
</Typography>
|
|
<Tooltip title="Boost">
|
|
<IconButton onClick={() => this.toggleReblogged(post)}>
|
|
<AutorenewIcon
|
|
className={
|
|
post.reblog
|
|
? post.reblog.reblogged
|
|
? classes.postDidAction
|
|
: ""
|
|
: post.reblogged
|
|
? classes.postDidAction
|
|
: ""
|
|
}
|
|
/>
|
|
</IconButton>
|
|
</Tooltip>
|
|
<Typography>
|
|
{post.reblog
|
|
? post.reblog.reblogs_count
|
|
: post.reblogs_count}
|
|
</Typography>
|
|
<Tooltip
|
|
className={classes.desktopOnly}
|
|
title="View thread"
|
|
>
|
|
<LinkableIconButton
|
|
to={`/conversation/${
|
|
post.reblog ? post.reblog.id : post.id
|
|
}`}
|
|
>
|
|
<ForumIcon />
|
|
</LinkableIconButton>
|
|
</Tooltip>
|
|
<Tooltip
|
|
className={classes.desktopOnly}
|
|
title="Open in Web"
|
|
>
|
|
<IconButton
|
|
href={this.getMastodonUrl(post)}
|
|
rel="noreferrer"
|
|
target="_blank"
|
|
>
|
|
<OpenInNewIcon />
|
|
</IconButton>
|
|
</Tooltip>
|
|
<div className={classes.postFlexGrow} />
|
|
<div className={classes.postTypeIconDiv}>
|
|
{this.showVisibilityIcon(post.visibility)}
|
|
</div>
|
|
</CardActions>
|
|
<Menu
|
|
id="postmenu"
|
|
anchorEl={document.getElementById(`${post.id}_submenu`)}
|
|
open={this.state.menuIsOpen}
|
|
onClose={() => this.togglePostMenu()}
|
|
>
|
|
<ShareMenu
|
|
config={{
|
|
params: {
|
|
title: `@${post.account.username} posted on Mastodon: `,
|
|
text: post.content,
|
|
url: this.getMastodonUrl(post)
|
|
},
|
|
onShareSuccess: () =>
|
|
this.props.enqueueSnackbar("Post shared!", {
|
|
variant: "success"
|
|
}),
|
|
onShareError: (error: Error) => {
|
|
if (error.name !== "AbortError")
|
|
this.props.enqueueSnackbar(
|
|
`Couldn't share post: ${error.name}`,
|
|
{ variant: "error" }
|
|
);
|
|
}
|
|
}}
|
|
/>
|
|
{post.reblog ? (
|
|
<div className={classes.postReblogMenu}>
|
|
<LinkableMenuItem
|
|
to={`/profile/${post.reblog.account.id}`}
|
|
>
|
|
View author profile
|
|
</LinkableMenuItem>
|
|
<LinkableMenuItem
|
|
to={`/profile/${post.account.id}`}
|
|
>
|
|
View reblogger profile
|
|
</LinkableMenuItem>
|
|
</div>
|
|
) : (
|
|
<LinkableMenuItem to={`/profile/${post.account.id}`}>
|
|
View profile
|
|
</LinkableMenuItem>
|
|
)}
|
|
<div className={classes.mobileOnly}>
|
|
<Divider />
|
|
<LinkableMenuItem
|
|
to={`/conversation/${
|
|
post.reblog ? post.reblog.id : post.id
|
|
}`}
|
|
>
|
|
View thread
|
|
</LinkableMenuItem>
|
|
<MenuItem
|
|
component="a"
|
|
href={this.getMastodonUrl(post)}
|
|
rel="noreferrer"
|
|
target="_blank"
|
|
>
|
|
Open in Web
|
|
</MenuItem>
|
|
</div>
|
|
{this.state.myAccount &&
|
|
post.account.id === this.state.myAccount ? (
|
|
<div>
|
|
<Divider />
|
|
<MenuItem
|
|
onClick={() => this.togglePostDeleteDialog()}
|
|
>
|
|
Delete
|
|
</MenuItem>
|
|
</div>
|
|
) : null}
|
|
{this.showDeleteDialog()}
|
|
</Menu>
|
|
</Card>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default withStyles(styles)(withSnackbar(Post));
|