diff --git a/src/shared/components/common/content-actions/content-action-dropdown.tsx b/src/shared/components/common/content-actions/content-action-dropdown.tsx index 58693f12..1fb0e47f 100644 --- a/src/shared/components/common/content-actions/content-action-dropdown.tsx +++ b/src/shared/components/common/content-actions/content-action-dropdown.tsx @@ -54,6 +54,7 @@ export type ContentPostProps = { onLock: () => Promise; onFeatureLocal: () => Promise; onFeatureCommunity: () => Promise; + onHidePost: () => Promise; } & ContentActionDropdownPropsBase; type ContentActionDropdownProps = ContentCommentProps | ContentPostProps; @@ -178,6 +179,17 @@ export default class ContentActionDropdown extends Component<
    + {type === "post" && ( +
  • + +
  • + )} {this.amCreator ? ( <>
  • diff --git a/src/shared/components/common/post-hidden-select.tsx b/src/shared/components/common/post-hidden-select.tsx new file mode 100644 index 00000000..6bb9e323 --- /dev/null +++ b/src/shared/components/common/post-hidden-select.tsx @@ -0,0 +1,71 @@ +import { StringBoolean } from "@utils/types"; +import classNames from "classnames"; +import { Icon } from "./icon"; +import { tippyMixin } from "../mixins/tippy-mixin"; +import { Component, linkEvent } from "inferno"; +import { I18NextService } from "../../services/I18NextService"; + +// Need to disable this rule because ESLint flat out lies about labels not +// having an associated control in this component +/* eslint-disable jsx-a11y/label-has-associated-control */ +interface PostHiddenSelectProps { + showHidden?: StringBoolean; + onShowHiddenChange: (hidden?: StringBoolean) => void; +} + +function handleShowHiddenChange(i: PostHiddenSelect, event: any) { + i.props.onShowHiddenChange(event.target.value); +} + +@tippyMixin +export default class PostHiddenSelect extends Component< + PostHiddenSelectProps, + never +> { + render() { + const { showHidden } = this.props; + + return ( +
    + + + +
    + ); + } +} diff --git a/src/shared/components/common/url-list-textarea.tsx b/src/shared/components/common/url-list-textarea.tsx index 7e6972f9..5063e101 100644 --- a/src/shared/components/common/url-list-textarea.tsx +++ b/src/shared/components/common/url-list-textarea.tsx @@ -47,7 +47,7 @@ export default class UrlListTextarea extends Component< UrlListTextareaState > { state: UrlListTextareaState = { - text: "", + text: this.props.urls.join("\n"), }; render() { diff --git a/src/shared/components/community/community.tsx b/src/shared/components/community/community.tsx index 8ec272cb..d0b8c6d7 100644 --- a/src/shared/components/community/community.tsx +++ b/src/shared/components/community/community.tsx @@ -20,7 +20,7 @@ import { resourcesSettled, } from "@utils/helpers"; import { scrollMixin } from "../mixins/scroll-mixin"; -import type { QueryParams } from "@utils/types"; +import type { QueryParams, StringBoolean } from "@utils/types"; import { RouteDataResponse } from "@utils/types"; import { Component, RefObject, createRef, linkEvent } from "inferno"; import { RouteComponentProps } from "inferno-router/dist/Route"; @@ -59,6 +59,7 @@ import { GetPosts, GetPostsResponse, GetSiteResponse, + HidePost, LemmyHttp, LockPost, MarkCommentReplyAsRead, @@ -111,6 +112,7 @@ import { } from "../common/loading-skeleton"; import { Sidebar } from "./sidebar"; import { IRoutePropsWithFetch } from "../../routes"; +import PostHiddenSelect from "../common/post-hidden-select"; type CommunityData = RouteDataResponse<{ communityRes: GetCommunityResponse; @@ -132,6 +134,7 @@ interface CommunityProps { dataType: DataType; sort: SortType; pageCursor?: PaginationCursor; + showHidden?: StringBoolean; } type Fallbacks = { sort: SortType }; @@ -148,6 +151,7 @@ export function getCommunityQueryParams( dataType: getDataTypeFromQuery, pageCursor: (cursor?: string) => cursor, sort: getSortTypeFromQuery, + showHidden: (include?: StringBoolean) => include, }, source, { @@ -242,6 +246,9 @@ export class Community extends Component { this.handleSavePost = this.handleSavePost.bind(this); this.handlePurgePost = this.handlePurgePost.bind(this); this.handleFeaturePost = this.handleFeaturePost.bind(this); + this.handleHidePost = this.handleHidePost.bind(this); + this.handleShowHiddenChange = this.handleShowHiddenChange.bind(this); + this.mainContentRef = createRef(); // Only fetch the data if coming from another route if (FirstLoadService.isFirstLoad) { @@ -274,7 +281,7 @@ export class Community extends Component { static async fetchInitialData({ headers, - query: { dataType, pageCursor, sort }, + query: { dataType, pageCursor, sort, showHidden }, match: { params: { name: communityName }, }, @@ -303,6 +310,7 @@ export class Community extends Component { sort, type_: "All", saved_only: false, + show_hidden: showHidden === "true", }; postsFetch = client.getPosts(getPostsForm); @@ -481,6 +489,7 @@ export class Community extends Component { onTransferCommunity={this.handleTransferCommunity} onFeaturePost={this.handleFeaturePost} onMarkPostAsRead={async () => {}} + onHidePost={this.handleHidePost} /> ); } @@ -560,10 +569,7 @@ export class Community extends Component { } selects(res: GetCommunityResponse) { - // let communityRss = this.state.communityRes.map(r => - // communityRSSUrl(r.community_view.community.actor_id, this.state.sort) - // ); - const { dataType, sort } = this.props; + const { dataType, sort, showHidden } = this.props; const communityRss = res ? communityRSSUrl(res.community_view.community.actor_id, sort) : undefined; @@ -576,6 +582,14 @@ export class Community extends Component { onChange={this.handleDataTypeChange} /> + {dataType === DataType.Post && UserService.Instance.myUserInfo && ( + + + + )} @@ -611,19 +625,36 @@ export class Community extends Component { this.updateUrl({ dataType, pageCursor: undefined }); } + handleShowHiddenChange(show?: StringBoolean) { + this.updateUrl({ + showHidden: show, + pageCursor: undefined, + }); + } + handleShowSidebarMobile(i: Community) { i.setState(({ showSidebarMobile }) => ({ showSidebarMobile: !showSidebarMobile, })); } - async updateUrl({ dataType, pageCursor, sort }: Partial) { - const { dataType: urlDataType, sort: urlSort } = this.props; + async updateUrl({ + dataType, + pageCursor, + sort, + showHidden, + }: Partial) { + const { + dataType: urlDataType, + sort: urlSort, + showHidden: urlShowHidden, + } = this.props; const queryParams: QueryParams = { dataType: getDataTypeString(dataType ?? urlDataType), pageCursor: pageCursor, sort: sort ?? urlSort, + showHidden: showHidden ?? urlShowHidden, }; this.props.history.push( @@ -634,7 +665,7 @@ export class Community extends Component { } async fetchData() { - const { dataType, pageCursor, sort } = this.props; + const { dataType, pageCursor, sort, showHidden } = this.props; const { name } = this.props.match.params; if (dataType === DataType.Post) { @@ -647,6 +678,7 @@ export class Community extends Component { type_: "All", community_name: name, saved_only: false, + show_hidden: showHidden === "true", }), }); } else { @@ -824,6 +856,26 @@ export class Community extends Component { this.findAndUpdatePost(lockRes); } + async handleHidePost(form: HidePost) { + const hideRes = await HttpService.client.hidePost(form); + + if (hideRes.state === "success") { + this.setState(prev => { + if (prev.postsRes.state === "success") { + for (const post of prev.postsRes.data.posts.filter(p => + form.post_ids.some(id => id === p.post.id), + )) { + post.hidden = form.hide; + } + } + + return prev; + }); + + toast(I18NextService.i18n.t(form.hide ? "post_hidden" : "post_unhidden")); + } + } + async handleDistinguishComment(form: DistinguishComment) { const distinguishRes = await HttpService.client.distinguishComment(form); this.findAndUpdateComment(distinguishRes); diff --git a/src/shared/components/home/home.tsx b/src/shared/components/home/home.tsx index 9944a969..5ce4f80a 100644 --- a/src/shared/components/home/home.tsx +++ b/src/shared/components/home/home.tsx @@ -21,7 +21,7 @@ import { } from "@utils/helpers"; import { scrollMixin } from "../mixins/scroll-mixin"; import { canCreateCommunity } from "@utils/roles"; -import type { QueryParams } from "@utils/types"; +import type { QueryParams, StringBoolean } from "@utils/types"; import { RouteDataResponse } from "@utils/types"; import { NoOptionI18nKeys } from "i18next"; import { Component, MouseEventHandler, linkEvent } from "inferno"; @@ -54,6 +54,7 @@ import { GetPosts, GetPostsResponse, GetSiteResponse, + HidePost, LemmyHttp, ListCommunities, ListCommunitiesResponse, @@ -109,6 +110,7 @@ import { } from "../common/loading-skeleton"; import { RouteComponentProps } from "inferno-router/dist/Route"; import { IRoutePropsWithFetch } from "../../routes"; +import PostHiddenSelect from "../common/post-hidden-select"; import { snapToTop } from "@utils/browser"; interface HomeState { @@ -130,6 +132,7 @@ interface HomeProps { dataType: DataType; sort: SortType; pageCursor?: PaginationCursor; + showHidden?: StringBoolean; } type HomeData = RouteDataResponse<{ @@ -206,6 +209,7 @@ export function getHomeQueryParams( listingType: getListingTypeFromQuery, pageCursor: (cursor?: string) => cursor, dataType: getDataTypeFromQuery, + showHidden: (include?: StringBoolean) => include, }, source, { @@ -287,6 +291,7 @@ export class Home extends Component { this.handleSortChange = this.handleSortChange.bind(this); this.handleListingTypeChange = this.handleListingTypeChange.bind(this); this.handleDataTypeChange = this.handleDataTypeChange.bind(this); + this.handleShowHiddenChange = this.handleShowHiddenChange.bind(this); this.handlePageNext = this.handlePageNext.bind(this); this.handlePagePrev = this.handlePagePrev.bind(this); @@ -317,6 +322,7 @@ export class Home extends Component { this.handleSavePost = this.handleSavePost.bind(this); this.handlePurgePost = this.handlePurgePost.bind(this); this.handleFeaturePost = this.handleFeaturePost.bind(this); + this.handleHidePost = this.handleHidePost.bind(this); // Only fetch the data if coming from another route if (FirstLoadService.isFirstLoad) { @@ -349,7 +355,7 @@ export class Home extends Component { } static async fetchInitialData({ - query: { listingType, dataType, sort, pageCursor }, + query: { listingType, dataType, sort, pageCursor, showHidden }, headers, }: InitialFetchRequest): Promise { const client = wrapClient( @@ -368,6 +374,7 @@ export class Home extends Component { limit: fetchLimit, sort, saved_only: false, + show_hidden: showHidden === "true", }; postsFetch = client.getPosts(getPostsForm); @@ -658,11 +665,13 @@ export class Home extends Component { listingType, pageCursor, sort, + showHidden, }: Partial) { const { dataType: urlDataType, listingType: urlListingType, sort: urlSort, + showHidden: urlShowHidden, } = this.props; const queryParams: QueryParams = { @@ -670,6 +679,7 @@ export class Home extends Component { listingType: listingType ?? urlListingType, pageCursor: pageCursor, sort: sort ?? urlSort, + showHidden: showHidden ?? urlShowHidden, }; this.props.history.push({ @@ -739,6 +749,7 @@ export class Home extends Component { onTransferCommunity={this.handleTransferCommunity} onFeaturePost={this.handleFeaturePost} onMarkPostAsRead={async () => {}} + onHidePost={this.handleHidePost} /> ); } @@ -786,7 +797,7 @@ export class Home extends Component { } get selects() { - const { listingType, dataType, sort } = this.props; + const { listingType, dataType, sort, showHidden } = this.props; return (
    @@ -796,6 +807,14 @@ export class Home extends Component { onChange={this.handleDataTypeChange} />
    + {dataType === DataType.Post && UserService.Instance.myUserInfo && ( +
    + +
    + )}
    { } async fetchData() { - const { dataType, pageCursor, listingType, sort } = this.props; + const { dataType, pageCursor, listingType, sort, showHidden } = this.props; if (dataType === DataType.Post) { this.setState({ postsRes: LOADING_REQUEST }); @@ -844,6 +863,7 @@ export class Home extends Component { sort, saved_only: false, type_: listingType, + show_hidden: showHidden === "true", }), }); } else { @@ -899,6 +919,14 @@ export class Home extends Component { this.updateUrl({ dataType: val, pageCursor: undefined }); } + handleShowHiddenChange(show?: StringBoolean) { + console.log(`Got ${show}`); + this.updateUrl({ + showHidden: show, + pageCursor: undefined, + }); + } + async handleAddModToCommunity(form: AddModToCommunity) { // TODO not sure what to do here await HttpService.client.addModToCommunity(form); @@ -1049,6 +1077,26 @@ export class Home extends Component { this.updateBan(banRes); } + async handleHidePost(form: HidePost) { + const hideRes = await HttpService.client.hidePost(form); + + if (hideRes.state === "success") { + this.setState(prev => { + if (prev.postsRes.state === "success") { + for (const post of prev.postsRes.data.posts.filter(p => + form.post_ids.some(id => id === p.post.id), + )) { + post.hidden = form.hide; + } + } + + return prev; + }); + + toast(I18NextService.i18n.t(form.hide ? "post_hidden" : "post_unhidden")); + } + } + updateBanFromCommunity(banRes: RequestState) { // Maybe not necessary if (banRes.state === "success") { diff --git a/src/shared/components/person/person-details.tsx b/src/shared/components/person/person-details.tsx index 90ee16e7..864c0a69 100644 --- a/src/shared/components/person/person-details.tsx +++ b/src/shared/components/person/person-details.tsx @@ -210,6 +210,7 @@ export class PersonDetails extends Component { onAddAdmin={this.props.onAddAdmin} onTransferCommunity={this.props.onTransferCommunity} onMarkPostAsRead={this.props.onMarkPostAsRead} + onHidePost={async () => {}} /> ); } @@ -322,6 +323,7 @@ export class PersonDetails extends Component { onAddAdmin={this.props.onAddAdmin} onTransferCommunity={this.props.onTransferCommunity} onMarkPostAsRead={this.props.onMarkPostAsRead} + onHidePost={async () => {}} />
    diff --git a/src/shared/components/post/post-form.tsx b/src/shared/components/post/post-form.tsx index 7b7c16d1..f934c555 100644 --- a/src/shared/components/post/post-form.tsx +++ b/src/shared/components/post/post-form.tsx @@ -475,6 +475,7 @@ export class PostForm extends Component { onAddAdmin={async () => {}} onTransferCommunity={async () => {}} onMarkPostAsRead={async () => {}} + onHidePost={async () => {}} /> )} @@ -688,6 +689,7 @@ export class PostForm extends Component { onAddAdmin={async () => {}} onTransferCommunity={async () => {}} onMarkPostAsRead={async () => {}} + onHidePost={async () => {}} /> ) diff --git a/src/shared/components/post/post-listing.tsx b/src/shared/components/post/post-listing.tsx index 76ca3dc6..c9c1b889 100644 --- a/src/shared/components/post/post-listing.tsx +++ b/src/shared/components/post/post-listing.tsx @@ -19,6 +19,7 @@ import { DeletePost, EditPost, FeaturePost, + HidePost, Language, LockPost, MarkPostAsRead, @@ -91,6 +92,7 @@ interface PostListingProps { onAddAdmin(form: AddAdmin): Promise; onTransferCommunity(form: TransferCommunity): Promise; onMarkPostAsRead(form: MarkPostAsRead): void; + onHidePost(form: HidePost): Promise; onScrollIntoCommentsClick?(e: MouseEvent): void; } @@ -126,6 +128,7 @@ export class PostListing extends Component { this.handleModBanFromSite = this.handleModBanFromSite.bind(this); this.handlePurgePerson = this.handlePurgePerson.bind(this); this.handlePurgePost = this.handlePurgePost.bind(this); + this.handleHidePost = this.handleHidePost.bind(this); } componentDidMount(): void { @@ -611,6 +614,7 @@ export class PostListing extends Component { onPurgeUser={this.handlePurgePerson} onPurgeContent={this.handlePurgePost} onAppointAdmin={this.handleAppointAdmin} + onHidePost={this.handleHidePost} /> )}
    @@ -907,6 +911,13 @@ export class PostListing extends Component { }); } + handleHidePost() { + return this.props.onHidePost({ + hide: !this.postView.hidden, + post_ids: [this.postView.post.id], + }); + } + handleModBanFromCommunity({ daysUntilExpires, reason, diff --git a/src/shared/components/post/post-listings.tsx b/src/shared/components/post/post-listings.tsx index aa350f53..cf7b4bdb 100644 --- a/src/shared/components/post/post-listings.tsx +++ b/src/shared/components/post/post-listings.tsx @@ -12,6 +12,7 @@ import { DeletePost, EditPost, FeaturePost, + HidePost, Language, LockPost, MarkPostAsRead, @@ -53,6 +54,7 @@ interface PostListingsProps { onAddAdmin(form: AddAdmin): Promise; onTransferCommunity(form: TransferCommunity): Promise; onMarkPostAsRead(form: MarkPostAsRead): Promise; + onHidePost(form: HidePost): Promise; } export class PostListings extends Component { @@ -100,6 +102,7 @@ export class PostListings extends Component { onAddAdmin={this.props.onAddAdmin} onTransferCommunity={this.props.onTransferCommunity} onMarkPostAsRead={this.props.onMarkPostAsRead} + onHidePost={this.props.onHidePost} /> {idx + 1 !== this.posts.length &&
    } diff --git a/src/shared/components/post/post-report.tsx b/src/shared/components/post/post-report.tsx index 4fba6f81..7078cd1d 100644 --- a/src/shared/components/post/post-report.tsx +++ b/src/shared/components/post/post-report.tsx @@ -94,6 +94,7 @@ export class PostReport extends Component { onAddAdmin={async () => {}} onTransferCommunity={async () => {}} onMarkPostAsRead={async () => {}} + onHidePost={async () => {}} />
    {I18NextService.i18n.t("reporter")}:{" "} diff --git a/src/shared/components/post/post.tsx b/src/shared/components/post/post.tsx index 69519acf..613f1c07 100644 --- a/src/shared/components/post/post.tsx +++ b/src/shared/components/post/post.tsx @@ -61,6 +61,7 @@ import { GetPost, GetPostResponse, GetSiteResponse, + HidePost, LemmyHttp, LockPost, MarkCommentReplyAsRead, @@ -195,6 +196,7 @@ export class Post extends Component { this.handleSavePost = this.handleSavePost.bind(this); this.handlePurgePost = this.handlePurgePost.bind(this); this.handleFeaturePost = this.handleFeaturePost.bind(this); + this.handleHidePost = this.handleHidePost.bind(this); this.handleScrollIntoCommentsClick = this.handleScrollIntoCommentsClick.bind(this); @@ -405,6 +407,7 @@ export class Post extends Component { onTransferCommunity={this.handleTransferCommunity} onFeaturePost={this.handleFeaturePost} onMarkPostAsRead={() => {}} + onHidePost={this.handleHidePost} onScrollIntoCommentsClick={this.handleScrollIntoCommentsClick} />
    @@ -1044,6 +1047,22 @@ export class Post extends Component { } } + async handleHidePost(form: HidePost) { + const hideRes = await HttpService.client.hidePost(form); + + if (hideRes.state === "success") { + this.setState(s => { + if (s.postRes.state === "success") { + s.postRes.data.post_view.hidden = form.hide; + } + + return s; + }); + + toast(I18NextService.i18n.t(form.hide ? "post_hidden" : "post_unhidden")); + } + } + updateBanFromCommunity(banRes: RequestState) { // Maybe not necessary if (banRes.state === "success") { diff --git a/src/shared/components/search.tsx b/src/shared/components/search.tsx index 4d49a352..30a14e0b 100644 --- a/src/shared/components/search.tsx +++ b/src/shared/components/search.tsx @@ -741,6 +741,7 @@ export class Search extends Component { onAddAdmin={async () => {}} onTransferCommunity={async () => {}} onMarkPostAsRead={async () => {}} + onHidePost={async () => {}} /> )} {i.type_ === "comments" && ( @@ -892,6 +893,7 @@ export class Search extends Component { onAddAdmin={async () => {}} onTransferCommunity={async () => {}} onMarkPostAsRead={() => {}} + onHidePost={async () => {}} />
    diff --git a/src/shared/utils/types/index.ts b/src/shared/utils/types/index.ts index 65926f79..ec390e5d 100644 --- a/src/shared/utils/types/index.ts +++ b/src/shared/utils/types/index.ts @@ -7,6 +7,7 @@ import { RouteDataResponse } from "./route-data-response"; import { ThemeColor } from "./theme-color"; import WithComment from "./with-comment"; import CrossPostParams from "./cross-post-params"; +import StringBoolean from "./string-boolean"; export { Choice, @@ -18,4 +19,5 @@ export { ThemeColor, WithComment, CrossPostParams, + StringBoolean, }; diff --git a/src/shared/utils/types/string-boolean.ts b/src/shared/utils/types/string-boolean.ts new file mode 100644 index 00000000..f8089d96 --- /dev/null +++ b/src/shared/utils/types/string-boolean.ts @@ -0,0 +1,3 @@ +type StringBoolean = "true" | "false"; + +export default StringBoolean;