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, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions } 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 { LinkableChip, LinkableMenuItem, LinkableIconButton, LinkableAvatar } 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; } interface IPostState { post: Status; media_slides?: number; menuIsOpen: boolean; myVote?: [number]; deletePostDialog: boolean; } export class Post extends React.Component { 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; } togglePostMenu() { this.setState({ menuIsOpen: !this.state.menuIsOpen }); } togglePostDeleteDialog() { this.setState({ deletePostDialog: !this.state.deletePostDialog }); } deletePost() { this.client .del("/statuses/" + this.state.post.id) .then((resp: any) => { this.props.enqueueSnackbar("Post deleted. Refresh to see changes."); }) .catch((err: Error) => { this.props.enqueueSnackbar("Couldn't delete post: " + err.name); console.log(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 (
{status.card ? (
{status.card.title} {status.card.description.slice(0, 500) + (status.card.description.length > 500 ? "..." : "") || "No description provided. Click with caution."} {status.card.image && status.media_attachments.length <= 0 ? ( ) : ( )} {status.card.provider_url || status.card.author_url || status.card.author_url}
) : ( )} {status.media_attachments.length > 0 ? ( ) : ( )} {status.poll ? ( status.poll.voted || status.poll.expired ? (
You can't vote on this poll. Below are the results of the poll. {status.poll.options.map((pollOption: PollOption) => { let x = ( } label={`${pollOption.title} (${pollOption.votes_count} votes)`} key={pollOption.title + pollOption.votes_count} /> ); return x; })} {status.poll && status.poll.expired ? ( This poll has expired. ) : ( This poll will expire on{" "} {moment( status.poll.expires_at ? status.poll.expires_at : "" ).format("MMMM Do YYYY, [at] h:mm A")} . )}
) : (
this.captureVote(option) } > {status.poll.options.map((pollOption: PollOption) => { let x = ( } label={pollOption.title} key={pollOption.title + pollOption.votes_count} /> ); return x; })}
) ) : null}
); } 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 = ; } return ( } color="inherit"> {icon} {warningText} {this.materializeContent(content)} ); } getReblogOfPost(of: Status | null) { const { classes } = this.props; 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; if (post.reblog) { let author = post.reblog.account; let origString = `${author.display_name || author.username} (@${ author.acct }) 🔄 ${post.account.display_name || post.account.username}`; let emojis = author.emojis; emojis.concat(post.account.emojis); return emojifyString(origString, emojis, classes.postAuthorEmoji); } else { let author = post.account; let origString = `${author.display_name || author.username} (@${ author.acct })`; return emojifyString(origString, author.emojis, classes.postAuthorEmoji); } } getMentions(mention: [Mention]) { const { classes } = this.props; if (mention.length > 0) { return ( Mentions {mention.map((person: Mention) => { return ( } label={person.username} key={this.state.post.id + "_mention_" + person.id} to={`/profile/${person.id}`} className={classes.postMention} clickable /> ); })} ); } else { return null; } } getTags(tags: [Tag]) { const { classes } = this.props; if (tags.length > 0) { return ( Tags {tags.map((tag: Tag) => { return ( } label={tag.name} key={this.state.post.id + "_tag_" + tag.name} to={`/search?query=${tag.name}&type=tag`} className={classes.postMention} clickable /> ); })} ); } else { return null; } } showVisibilityIcon(visibility: Visibility) { const { classes } = this.props; switch (visibility) { case "public": return ( ); case "private": return ( ); case "unlisted": return ( ); } } getMastodonUrl(post: Status) { let url = ""; if (post.reblog) { url = post.reblog.uri; } else { url = post.uri; } return url; } toggleFavorited(post: Status) { let _this = this; if (post.favourited) { this.client .post(`/statuses/${post.id}/unfavourite`) .then((resp: any) => { let post: Status = resp.data; this.setState({ post }); }) .catch((err: Error) => { _this.props.enqueueSnackbar(`Couldn't unfavorite post: ${err.name}`, { variant: "error" }); console.log(err.message); }); } else { this.client .post(`/statuses/${post.id}/favourite`) .then((resp: any) => { let post: Status = resp.data; this.setState({ post }); }) .catch((err: Error) => { _this.props.enqueueSnackbar(`Couldn't favorite post: ${err.name}`, { variant: "error" }); console.log(err.message); }); } } toggleReblogged(post: Status) { if (post.reblogged) { this.client .post(`/statuses/${post.id}/unreblog`) .then((resp: any) => { let post: Status = resp.data; this.setState({ post }); }) .catch((err: Error) => { this.props.enqueueSnackbar(`Couldn't unboost post: ${err.name}`, { variant: "error" }); console.log(err.message); }); } else { this.client .post(`/statuses/${post.id}/reblog`) .then((resp: any) => { let post: Status = resp.data; this.setState({ post }); }) .catch((err: Error) => { this.props.enqueueSnackbar(`Couldn't boost post: ${err.name}`, { variant: "error" }); console.log(err.message); }); } } showDeleteDialog() { return ( this.togglePostDeleteDialog()} > Delete this post? Are you sure you want to delete this post? This action cannot be undone. ); } render() { const { classes } = this.props; const post = this.state.post; return ( } action={ this.togglePostMenu()} > } title={ } 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)} {post.reblog ? post.reblog.replies_count : post.replies_count} this.toggleFavorited(post)}> {post.reblog ? post.reblog.favourites_count : post.favourites_count} this.toggleReblogged(post)}> {post.reblog ? post.reblog.reblogs_count : post.reblogs_count}
{this.showVisibilityIcon(post.visibility)}
this.togglePostMenu()} > 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 ? (
View author profile View reblogger profile
) : ( View profile )}
View thread Open in Web
{post.account.id == JSON.parse(localStorage.getItem("account") as string).id ? (
this.togglePostDeleteDialog()}> Delete
) : null} {this.showDeleteDialog()}
); } } export default withStyles(styles)(withSnackbar(Post));