mirror of
https://github.com/hyperspacedev/hyperspace
synced 2025-01-30 17:14:57 +01:00
Merge pull request #172 from hyperspacedev/HD-44-uneven-masonry-columns
HD-44 #done
This commit is contained in:
commit
5838039fef
@ -101,6 +101,11 @@ export class Post extends React.Component<any, IPostState> {
|
||||
});
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: any, nextState: any) {
|
||||
if (nextState == this.state) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
togglePostMenu() {
|
||||
this.setState({ menuIsOpen: !this.state.menuIsOpen });
|
||||
}
|
||||
@ -549,7 +554,7 @@ export class Post extends React.Component<any, IPostState> {
|
||||
* Tell server a post has been un/favorited and update post state
|
||||
* @param post The post to un/favorite
|
||||
*/
|
||||
async toggleFavorite(post: Status) {
|
||||
async toggleFavorited(post: Status) {
|
||||
let action: string = post.favourited ? "unfavourite" : "favourite";
|
||||
try {
|
||||
// favorite the original post, not the reblog
|
||||
@ -576,7 +581,7 @@ export class Post extends React.Component<any, IPostState> {
|
||||
* Tell server a post has been un/reblogged and update post state
|
||||
* @param post The post to un/reblog
|
||||
*/
|
||||
async toggleReblog(post: Status) {
|
||||
async toggleReblogged(post: Status) {
|
||||
let action: string =
|
||||
post.reblogged || post.reblog ? "unreblog" : "reblog";
|
||||
try {
|
||||
@ -637,222 +642,220 @@ export class Post extends React.Component<any, IPostState> {
|
||||
const { classes } = this.props;
|
||||
const post = this.state.post;
|
||||
return (
|
||||
<Zoom in={true}>
|
||||
<Card
|
||||
className={classes.post}
|
||||
id={`post_${post.id}`}
|
||||
elevation={this.props.threadHeader ? 0 : 1}
|
||||
>
|
||||
<CardHeader
|
||||
avatar={
|
||||
<LinkableAvatar
|
||||
to={`/profile/${
|
||||
<Card
|
||||
className={classes.post}
|
||||
id={`post_${post.id}`}
|
||||
elevation={this.props.threadHeader ? 0 : 1}
|
||||
>
|
||||
<CardHeader
|
||||
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={
|
||||
<Typography>{this.getReblogAuthors(post)}</Typography>
|
||||
}
|
||||
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.account.id
|
||||
: post.account.id
|
||||
}`}
|
||||
src={
|
||||
post.reblog
|
||||
? post.reblog.account.avatar_static
|
||||
: post.account.avatar_static
|
||||
? post.reblog.favourited
|
||||
? classes.postDidAction
|
||||
: ""
|
||||
: post.favourited
|
||||
? classes.postDidAction
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
}
|
||||
action={
|
||||
<Tooltip title="More" placement="left">
|
||||
<IconButton
|
||||
key={`${post.id}_submenu`}
|
||||
id={`${post.id}_submenu`}
|
||||
onClick={() => this.togglePostMenu()}
|
||||
>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
title={
|
||||
<Typography>
|
||||
{this.getReblogAuthors(post)}
|
||||
</Typography>
|
||||
}
|
||||
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=${
|
||||
</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.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.toggleFavorite(post)}
|
||||
>
|
||||
<FavoriteIcon
|
||||
className={
|
||||
post.favourited
|
||||
? post.reblog.reblogged
|
||||
? classes.postDidAction
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Typography>{post.favourites_count}</Typography>
|
||||
<Tooltip title="Boost">
|
||||
<IconButton onClick={() => this.toggleReblog(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.reblogged
|
||||
? classes.postDidAction
|
||||
: ""
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{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>
|
||||
) : (
|
||||
/>
|
||||
</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 profile
|
||||
View reblogger profile
|
||||
</LinkableMenuItem>
|
||||
)}
|
||||
<div className={classes.mobileOnly}>
|
||||
</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 />
|
||||
<LinkableMenuItem
|
||||
to={`/conversation/${
|
||||
post.reblog ? post.reblog.id : post.id
|
||||
}`}
|
||||
>
|
||||
View thread
|
||||
</LinkableMenuItem>
|
||||
<MenuItem
|
||||
component="a"
|
||||
href={this.getMastodonUrl(post)}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
onClick={() => this.togglePostDeleteDialog()}
|
||||
>
|
||||
Open in Web
|
||||
Delete
|
||||
</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>
|
||||
</Zoom>
|
||||
) : null}
|
||||
{this.showDeleteDialog()}
|
||||
</Menu>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +65,7 @@ import DomainDisabledIcon from "@material-ui/icons/DomainDisabled";
|
||||
import AccountSettingsIcon from "mdi-material-ui/AccountSettings";
|
||||
import AlphabeticalVariantOffIcon from "mdi-material-ui/AlphabeticalVariantOff";
|
||||
import DashboardIcon from "@material-ui/icons/Dashboard";
|
||||
import InfiniteIcon from "@material-ui/icons/AllInclusive";
|
||||
|
||||
import { Config } from "../types/Config";
|
||||
import { Account } from "../types/Account";
|
||||
@ -88,6 +89,7 @@ interface ISettingsState {
|
||||
currentUser?: Account;
|
||||
imposeCharacterLimit: boolean;
|
||||
masonryLayout?: boolean;
|
||||
infiniteScroll?: boolean;
|
||||
}
|
||||
|
||||
class SettingsPage extends Component<any, ISettingsState> {
|
||||
@ -120,7 +122,8 @@ class SettingsPage extends Component<any, ISettingsState> {
|
||||
brandName: "Hyperspace",
|
||||
federated: true,
|
||||
imposeCharacterLimit: getUserDefaultBool("imposeCharacterLimit"),
|
||||
masonryLayout: getUserDefaultBool("isMasonryLayout")
|
||||
masonryLayout: getUserDefaultBool("isMasonryLayout"),
|
||||
infiniteScroll: getUserDefaultBool("isInfiniteScroll")
|
||||
};
|
||||
|
||||
this.toggleDarkMode = this.toggleDarkMode.bind(this);
|
||||
@ -130,6 +133,7 @@ class SettingsPage extends Component<any, ISettingsState> {
|
||||
this.toggleThemeDialog = this.toggleThemeDialog.bind(this);
|
||||
this.toggleVisibilityDialog = this.toggleVisibilityDialog.bind(this);
|
||||
this.toggleMasonryLayout = this.toggleMasonryLayout.bind(this);
|
||||
this.toggleInfiniteScroll = this.toggleInfiniteScroll.bind(this);
|
||||
this.changeThemeName = this.changeThemeName.bind(this);
|
||||
this.changeTheme = this.changeTheme.bind(this);
|
||||
this.setVisibility = this.setVisibility.bind(this);
|
||||
@ -250,6 +254,11 @@ class SettingsPage extends Component<any, ISettingsState> {
|
||||
setUserDefaultBool("isMasonryLayout", !this.state.masonryLayout);
|
||||
}
|
||||
|
||||
toggleInfiniteScroll() {
|
||||
this.setState({ infiniteScroll: !this.state.infiniteScroll });
|
||||
setUserDefaultBool("isInfiniteScroll", !this.state.infiniteScroll);
|
||||
}
|
||||
|
||||
changeTheme() {
|
||||
setUserDefaultTheme(this.state.selectThemeName);
|
||||
window.location.reload();
|
||||
@ -675,6 +684,22 @@ class SettingsPage extends Component<any, ISettingsState> {
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<InfiniteIcon color="action" />
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary="Enable infinite scroll"
|
||||
secondary="Automatically load more posts when scrolling"
|
||||
/>
|
||||
<ListItemSecondaryAction>
|
||||
<Switch
|
||||
checked={this.state.infiniteScroll}
|
||||
onChange={this.toggleInfiniteScroll}
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Paper>
|
||||
<br />
|
||||
|
@ -77,6 +77,11 @@ interface ITimelinePageState {
|
||||
* the user settings.
|
||||
*/
|
||||
isMasonryLayout?: boolean;
|
||||
|
||||
/**
|
||||
* Whether posts should automatically load when scrolling.
|
||||
*/
|
||||
isInfiniteScroll?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,7 +114,8 @@ class TimelinePage extends Component<ITimelinePageProps, ITimelinePageState> {
|
||||
this.state = {
|
||||
viewIsLoading: true,
|
||||
backlogPosts: null,
|
||||
isMasonryLayout: getUserDefaultBool("isMasonryLayout")
|
||||
isMasonryLayout: getUserDefaultBool("isMasonryLayout"),
|
||||
isInfiniteScroll: getUserDefaultBool("isInfiniteScroll")
|
||||
};
|
||||
|
||||
// Generate the client.
|
||||
@ -120,6 +126,9 @@ class TimelinePage extends Component<ITimelinePageProps, ITimelinePageState> {
|
||||
|
||||
// Create the stream listener from the properties.
|
||||
this.streamListener = this.client.stream(this.props.stream);
|
||||
|
||||
this.loadMoreTimelinePieces = this.loadMoreTimelinePieces.bind(this);
|
||||
this.shouldLoadMorePosts = this.shouldLoadMorePosts.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,8 +138,7 @@ class TimelinePage extends Component<ITimelinePageProps, ITimelinePageState> {
|
||||
this.streamListener.on("connect", () => {
|
||||
// Get the latest posts from this timeline.
|
||||
this.client
|
||||
.get(this.props.timeline, { limit: 40 })
|
||||
|
||||
.get(this.props.timeline, { limit: 50 })
|
||||
// If we succeeded, update the state and turn off loading.
|
||||
.then((resp: any) => {
|
||||
let statuses: [Status] = resp.data;
|
||||
@ -198,10 +206,43 @@ class TimelinePage extends Component<ITimelinePageProps, ITimelinePageState> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Halt the stream listener when unmounting the component.
|
||||
* Insert a delay between repeated function calls
|
||||
* codeburst.io/throttling-and-debouncing-in-javascript-646d076d0a44
|
||||
* @param delay How long to wait before calling function (ms)
|
||||
* @param fn The function to call
|
||||
*/
|
||||
debounced(delay: number, fn: Function) {
|
||||
let lastCall = 0;
|
||||
return function(...args: any) {
|
||||
const now = new Date().getTime();
|
||||
if (now - lastCall < delay) {
|
||||
return;
|
||||
}
|
||||
lastCall = now;
|
||||
return fn(...args);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for when scroll position changes
|
||||
*/
|
||||
componentDidMount() {
|
||||
if (this.state.isInfiniteScroll) {
|
||||
window.addEventListener(
|
||||
"scroll",
|
||||
this.debounced(200, this.shouldLoadMorePosts)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Halt the stream and scroll listeners when unmounting the component.
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
this.streamListener.stop();
|
||||
if (this.state.isInfiniteScroll) {
|
||||
window.removeEventListener("scroll", this.shouldLoadMorePosts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -230,7 +271,7 @@ class TimelinePage extends Component<ITimelinePageProps, ITimelinePageState> {
|
||||
this.client
|
||||
.get(this.props.timeline, {
|
||||
max_id: this.state.posts[this.state.posts.length - 1].id,
|
||||
limit: 20
|
||||
limit: 50
|
||||
})
|
||||
|
||||
// If we succeeded, append them to the end of the list of posts.
|
||||
@ -261,6 +302,17 @@ class TimelinePage extends Component<ITimelinePageProps, ITimelinePageState> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load more posts when scroll is near the end of the page
|
||||
*/
|
||||
shouldLoadMorePosts(e: Event) {
|
||||
let difference =
|
||||
document.body.clientHeight - window.scrollY - window.innerHeight;
|
||||
if (difference < 10000 && this.state.viewIsLoading === false) {
|
||||
this.loadMoreTimelinePieces();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the timeline page.
|
||||
*/
|
||||
@ -316,9 +368,9 @@ class TimelinePage extends Component<ITimelinePageProps, ITimelinePageState> {
|
||||
return (
|
||||
<div
|
||||
className={classes.masonryGrid_item}
|
||||
key={post.id}
|
||||
>
|
||||
<Post
|
||||
key={post.id}
|
||||
post={post}
|
||||
client={this.client}
|
||||
/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user