Adding a post title only filter to the search page. (#2695)

* Adding a post title only filter to the search page.

- Also fixing restore data on unban.
- Accounting for newly removed block views.

* Only show post title button for All and Post searches.

* Addressing PR comments.
This commit is contained in:
Dessalines 2024-09-20 09:20:39 -04:00 committed by GitHub
parent 5f535e7dad
commit 8a44c4d38f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 120 additions and 83 deletions

@ -1 +1 @@
Subproject commit 37c437e753b52b1a61b225df9d4ffd99ddf17314
Subproject commit 7ba4cac33e47b712227c2b8a133c489abed7c727

View file

@ -60,7 +60,7 @@
"inferno-router": "^8.2.3",
"inferno-server": "^8.2.3",
"jwt-decode": "^4.0.0",
"lemmy-js-client": "0.19.6-beta.1",
"lemmy-js-client": "0.20.0-alpha.7",
"lodash.isequal": "^4.5.0",
"markdown-it": "^14.1.0",
"markdown-it-bidi": "^0.1.0",

View file

@ -114,8 +114,8 @@ importers:
specifier: ^4.0.0
version: 4.0.0
lemmy-js-client:
specifier: 0.19.6-beta.1
version: 0.19.6-beta.1
specifier: 0.20.0-alpha.7
version: 0.20.0-alpha.7
lodash.isequal:
specifier: ^4.5.0
version: 4.5.0
@ -3063,8 +3063,8 @@ packages:
leac@0.6.0:
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
lemmy-js-client@0.19.6-beta.1:
resolution: {integrity: sha512-CiTbTpqKA7t1daBMdkKVLVXUrpy2wSuxkoWrnK5sVvmBrlFq1jpqC9h9SgNngiVqSgx6Tvsg3asmvne7OaEZRQ==}
lemmy-js-client@0.20.0-alpha.7:
resolution: {integrity: sha512-lhPs8gJFLX0EvlwkkgtTF2F/v22lKjcQ81u7u5CShrO05Dii33fZ5x9SrEJYHD0qw4FIN/jpfKucN9QnndScpA==}
leven@3.1.0:
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
@ -7834,7 +7834,7 @@ snapshots:
leac@0.6.0: {}
lemmy-js-client@0.19.6-beta.1: {}
lemmy-js-client@0.20.0-alpha.7: {}
leven@3.1.0: {}

View file

@ -637,7 +637,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
async handleBanFromCommunity({
daysUntilExpires,
reason,
shouldRemove,
shouldRemoveOrRestoreData,
}: BanUpdateForm) {
const {
creator: { id: person_id },
@ -646,10 +646,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
} = this.commentView;
const ban = !creator_banned_from_community;
// If its an unban, restore all their data
if (ban === false) {
shouldRemove = false;
shouldRemoveOrRestoreData = true;
}
const expires = futureDaysToUnixTime(daysUntilExpires);
@ -657,7 +656,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
community_id,
person_id,
ban,
remove_data: shouldRemove,
remove_or_restore_data: shouldRemoveOrRestoreData,
reason,
expires,
});
@ -666,7 +665,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
async handleBanFromSite({
daysUntilExpires,
reason,
shouldRemove,
shouldRemoveOrRestoreData,
}: BanUpdateForm) {
const {
creator: { id: person_id, banned },
@ -676,14 +675,14 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
// If its an unban, restore all their data
if (ban === false) {
shouldRemove = false;
shouldRemoveOrRestoreData = true;
}
const expires = futureDaysToUnixTime(daysUntilExpires);
this.props.onBanPerson({
person_id,
ban,
remove_data: shouldRemove,
remove_or_restore_data: shouldRemoveOrRestoreData,
reason,
expires,
});

View file

@ -16,7 +16,7 @@ import { modalMixin } from "../../mixins/modal-mixin";
export interface BanUpdateForm {
reason?: string;
shouldRemove?: boolean;
shouldRemoveOrRestoreData?: boolean;
daysUntilExpires?: number;
}
@ -69,7 +69,7 @@ interface ModActionFormFormState {
loading: boolean;
reason: string;
daysUntilExpire?: number;
shouldRemoveData?: boolean;
shouldRemoveOrRestoreData?: boolean;
shouldPermaBan?: boolean;
}
@ -84,7 +84,7 @@ function handleExpiryChange(i: ModActionFormModal, event: any) {
function handleToggleRemove(i: ModActionFormModal) {
i.setState(prev => ({
...prev,
shouldRemoveData: !prev.shouldRemoveData,
shouldRemoveOrRestoreData: !prev.shouldRemoveOrRestoreData,
}));
}
@ -104,7 +104,7 @@ async function handleSubmit(i: ModActionFormModal, event: any) {
await i.props.onSubmit({
reason: i.state.reason,
daysUntilExpires: i.state.daysUntilExpire!,
shouldRemove: i.state.shouldRemoveData!,
shouldRemoveOrRestoreData: i.state.shouldRemoveOrRestoreData!,
} as BanUpdateForm & string); // Need to & string to handle type weirdness
} else {
await i.props.onSubmit(i.state.reason);
@ -135,7 +135,7 @@ export default class ModActionFormModal extends Component<
this.reasonRef = createRef();
if (this.isBanModal) {
this.state.shouldRemoveData = false;
this.state.shouldRemoveOrRestoreData = false;
}
}
@ -144,7 +144,7 @@ export default class ModActionFormModal extends Component<
loading,
reason,
daysUntilExpire,
shouldRemoveData,
shouldRemoveOrRestoreData,
shouldPermaBan,
} = this.state;
const reasonId = `mod-form-reason-${randomStr()}`;
@ -249,7 +249,7 @@ export default class ModActionFormModal extends Component<
<input
className="form-check-input user-select-none"
type="checkbox"
checked={shouldRemoveData}
checked={shouldRemoveOrRestoreData}
onChange={linkEvent(this, handleToggleRemove)}
/>
{I18NextService.i18n.t("remove_content")}

View file

@ -64,7 +64,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
description: site.description,
enable_downvotes: ls.enable_downvotes,
registration_mode: ls.registration_mode,
enable_nsfw: ls.enable_nsfw,
community_creation_admin_only: ls.community_creation_admin_only,
icon: site.icon,
banner: site.banner,

View file

@ -119,7 +119,7 @@ interface ProfileState {
banReason?: string;
banExpireDays?: number;
showBanDialog: boolean;
removeData: boolean;
removeOrRestoreData: boolean;
siteRes: GetSiteResponse;
isIsomorphic: boolean;
showRegistrationDialog: boolean;
@ -182,7 +182,7 @@ function isPersonBlocked(personRes: RequestState<GetPersonDetailsResponse>) {
return (
(personRes.state === "success" &&
UserService.Instance.myUserInfo?.person_blocks.some(
({ target: { id } }) => id === personRes.data.person_view.person.id,
({ id }) => id === personRes.data.person_view.person.id,
)) ??
false
);
@ -206,7 +206,7 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
personBlocked: false,
siteRes: this.isoData.site_res,
showBanDialog: false,
removeData: false,
removeOrRestoreData: false,
isIsomorphic: false,
showRegistrationDialog: false,
registrationRes: EMPTY_REQUEST,
@ -891,7 +891,7 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
className="form-check-input"
id="mod-ban-remove-data"
type="checkbox"
checked={this.state.removeData}
checked={this.state.removeOrRestoreData}
onChange={linkEvent(this, this.handleModRemoveDataChange)}
/>
<label
@ -980,7 +980,7 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
}
handleModRemoveDataChange(i: Profile, event: any) {
i.setState({ removeData: event.target.checked });
i.setState({ removeOrRestoreData: event.target.checked });
}
handleModBanSubmitCancel(i: Profile) {
@ -1015,7 +1015,7 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
async handleModBanSubmit(i: Profile, event: any) {
event.preventDefault();
const { removeData, banReason, banExpireDays } = i.state;
const { banReason, banExpireDays } = i.state;
const personRes = i.state.personRes;
@ -1025,13 +1025,13 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
// If its an unban, restore all their data
if (!ban) {
i.setState({ removeData: false });
i.setState({ removeOrRestoreData: true });
}
const res = await HttpService.client.banPerson({
person_id: person.id,
ban,
remove_data: removeData,
remove_or_restore_data: i.state.removeOrRestoreData,
reason: banReason,
expires: futureDaysToUnixTime(banExpireDays),
});

View file

@ -22,16 +22,15 @@ import {
BlockCommunityResponse,
BlockInstanceResponse,
BlockPersonResponse,
CommunityBlockView,
Community,
GenerateTotpSecretResponse,
GetFederatedInstancesResponse,
GetSiteResponse,
Instance,
InstanceBlockView,
LemmyHttp,
ListingType,
LoginResponse,
PersonBlockView,
Person,
SortType,
SuccessResponse,
UpdateTotpResponse,
@ -125,9 +124,9 @@ interface SettingsState {
delete_content?: boolean;
password?: string;
};
personBlocks: PersonBlockView[];
communityBlocks: CommunityBlockView[];
instanceBlocks: InstanceBlockView[];
personBlocks: Person[];
communityBlocks: Community[];
instanceBlocks: Instance[];
currentTab: string;
themeList: string[];
deleteAccountShowConfirm: boolean;
@ -556,14 +555,14 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
<>
<h2 className="h5">{I18NextService.i18n.t("blocked_users")}</h2>
<ul className="list-unstyled mb-0">
{this.state.personBlocks.map(pb => (
<li key={pb.target.id}>
{this.state.personBlocks.map(p => (
<li key={p.id}>
<span>
<PersonListing person={pb.target} />
<PersonListing person={p} />
<button
className="btn btn-sm"
onClick={linkEvent(
{ ctx: this, recipientId: pb.target.id },
{ ctx: this, recipientId: p.id },
this.handleUnblockPerson,
)}
data-tippy-content={I18NextService.i18n.t("unblock_user")}
@ -600,14 +599,14 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
<>
<h2 className="h5">{I18NextService.i18n.t("blocked_communities")}</h2>
<ul className="list-unstyled mb-0">
{this.state.communityBlocks.map(cb => (
<li key={cb.community.id}>
{this.state.communityBlocks.map(c => (
<li key={c.id}>
<span>
<CommunityLink community={cb.community} />
<CommunityLink community={c} />
<button
className="btn btn-sm"
onClick={linkEvent(
{ ctx: this, communityId: cb.community.id },
{ ctx: this, communityId: c.id },
this.handleUnblockCommunity,
)}
data-tippy-content={I18NextService.i18n.t(
@ -645,14 +644,14 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
<>
<h2 className="h5">{I18NextService.i18n.t("blocked_instances")}</h2>
<ul className="list-unstyled mb-0">
{this.state.instanceBlocks.map(ib => (
<li key={ib.instance.id}>
{this.state.instanceBlocks.map(i => (
<li key={i.id}>
<span>
{ib.instance.domain}
{i.domain}
<button
className="btn btn-sm"
onClick={linkEvent(
{ ctx: this, instanceId: ib.instance.id },
{ ctx: this, instanceId: i.id },
this.handleUnblockInstance,
)}
data-tippy-content={I18NextService.i18n.t("unblock_instance")}
@ -1349,7 +1348,7 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
instance =>
instance.domain.toLowerCase().includes(text.toLowerCase()) &&
!this.state.instanceBlocks.some(
blockedInstance => blockedInstance.instance.id === instance.id,
blockedInstance => blockedInstance.id === instance.id,
),
) ?? [];
}
@ -1751,7 +1750,6 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
interface_language,
show_avatars,
show_bot_accounts,
show_scores,
show_read_posts,
send_notifications_to_email,
email,
@ -1795,7 +1793,6 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
open_links_in_new_tab,
send_notifications_to_email,
show_read_posts,
show_scores,
},
}));
}

View file

@ -1023,7 +1023,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
handleModBanFromCommunity({
daysUntilExpires,
reason,
shouldRemove,
shouldRemoveOrRestoreData,
}: BanUpdateForm) {
const {
creator: { id: person_id },
@ -1034,7 +1034,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
// If its an unban, restore all their data
if (ban === false) {
shouldRemove = false;
shouldRemoveOrRestoreData = true;
}
const expires = futureDaysToUnixTime(daysUntilExpires);
@ -1042,7 +1042,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
community_id,
person_id,
ban,
remove_data: shouldRemove,
remove_or_restore_data: shouldRemoveOrRestoreData,
reason,
expires,
});
@ -1051,7 +1051,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
handleModBanFromSite({
daysUntilExpires,
reason,
shouldRemove,
shouldRemoveOrRestoreData,
}: BanUpdateForm) {
const {
creator: { id: person_id, banned },
@ -1060,14 +1060,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
// If its an unban, restore all their data
if (ban === false) {
shouldRemove = false;
shouldRemoveOrRestoreData = true;
}
const expires = futureDaysToUnixTime(daysUntilExpires);
return this.props.onBanPerson({
person_id,
ban,
remove_data: shouldRemove,
remove_or_restore_data: shouldRemoveOrRestoreData,
reason,
expires,
});

View file

@ -18,6 +18,7 @@ import {
dedupByProperty,
getIdFromString,
getPageFromString,
getBoolFromString,
getQueryParams,
getQueryString,
numToSI,
@ -77,6 +78,7 @@ interface SearchProps {
type: SearchType;
sort: SortType;
listingType: ListingType;
postTitleOnly?: boolean;
communityId?: number;
creatorId?: number;
page: number;
@ -122,6 +124,7 @@ export function getSearchQueryParams(source?: string): SearchProps {
type: getSearchTypeFromQuery,
sort: getSortTypeFromQuery,
listingType: getListingTypeFromQuery,
postTitleOnly: getBoolFromString,
communityId: getIdFromString,
creatorId: getIdFromString,
page: getPageFromString,
@ -283,6 +286,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
this.handleCommunityFilterChange =
this.handleCommunityFilterChange.bind(this);
this.handleCreatorFilterChange = this.handleCreatorFilterChange.bind(this);
this.handlePostTitleChange = this.handlePostTitleChange.bind(this);
// Only fetch the data if coming from another route
if (FirstLoadService.isFirstLoad) {
@ -469,6 +473,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
type: searchType,
sort,
listingType: listing_type,
postTitleOnly: post_title_only,
communityId: community_id,
creatorId: creator_id,
page,
@ -514,6 +519,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
type_: searchType,
sort,
listing_type,
post_title_only,
page,
limit: fetchLimit,
};
@ -635,7 +641,8 @@ export class Search extends Component<SearchRouteProps, SearchState> {
}
get selects() {
const { type, listingType, sort, communityId, creatorId } = this.props;
const { type, listingType, postTitleOnly, sort, communityId, creatorId } =
this.props;
const {
communitySearchOptions,
creatorSearchOptions,
@ -673,6 +680,23 @@ export class Search extends Component<SearchRouteProps, SearchState> {
onChange={this.handleListingTypeChange}
/>
</div>
{(type === "All" || type === "Posts") && (
<div className="col">
<input
className="btn-check"
id="post-title-only"
type="checkbox"
checked={postTitleOnly}
onChange={this.handlePostTitleChange}
/>
<label
className="btn btn-outline-secondary"
htmlFor="post-title-only"
>
{I18NextService.i18n.t("post_title_only")}
</label>
</div>
)}
<div className="col">
<SortSelect
sort={sort}
@ -1043,7 +1067,16 @@ export class Search extends Component<SearchRouteProps, SearchState> {
searchToken?: symbol;
async search(props: SearchRouteProps) {
const token = (this.searchToken = Symbol());
const { q, communityId, creatorId, type, sort, listingType, page } = props;
const {
q,
communityId,
creatorId,
type,
sort,
listingType,
postTitleOnly,
page,
} = props;
if (q) {
this.setState({ searchRes: LOADING_REQUEST });
@ -1054,6 +1087,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
type_: type,
sort,
listing_type: listingType,
post_title_only: postTitleOnly,
page,
limit: fetchLimit,
});
@ -1122,6 +1156,11 @@ export class Search extends Component<SearchRouteProps, SearchState> {
this.updateUrl({ sort, page: 1, q: this.getQ() });
}
handlePostTitleChange(event: any) {
const postTitleOnly = event.target.checked;
this.updateUrl({ postTitleOnly, q: this.getQ() });
}
handleTypeChange(i: Search, event: any) {
const type = event.target.value as SearchType;
@ -1170,7 +1209,16 @@ export class Search extends Component<SearchRouteProps, SearchState> {
}
async updateUrl(props: Partial<SearchProps>) {
const { q, type, listingType, sort, communityId, creatorId, page } = {
const {
q,
type,
listingType,
postTitleOnly,
sort,
communityId,
creatorId,
page,
} = {
...this.props,
...props,
};
@ -1179,6 +1227,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
q,
type: type,
listingType: listingType,
postTitleOnly: postTitleOnly?.toString(),
communityId: communityId?.toString(),
creatorId: creatorId?.toString(),
page: page?.toString(),

View file

@ -1,5 +1,5 @@
import { GetSiteResponse } from "lemmy-js-client";
export default function enableNsfw(siteRes: GetSiteResponse): boolean {
return siteRes.site_view.local_site.enable_nsfw;
return !!siteRes.site_view.site.content_warning;
}

View file

@ -6,12 +6,8 @@ export default function isPostBlocked(
myUserInfo: MyUserInfo | undefined = UserService.Instance.myUserInfo,
): boolean {
return (
(myUserInfo?.community_blocks
.map(c => c.community.id)
.includes(pv.community.id) ||
myUserInfo?.person_blocks
.map(p => p.target.id)
.includes(pv.creator.id)) ??
(myUserInfo?.community_blocks.some(c => c.id === pv.community.id) ||
myUserInfo?.person_blocks.some(p => p.id === pv.creator.id)) ??
false
);
}

View file

@ -8,10 +8,7 @@ export default function updateCommunityBlock(
) {
if (myUserInfo) {
if (data.blocked) {
myUserInfo.community_blocks.push({
person: myUserInfo.local_user_view.person,
community: data.community_view.community,
});
myUserInfo.community_blocks.push(data.community_view.community);
toast(
`${I18NextService.i18n.t("blocked")} ${
data.community_view.community.name
@ -19,7 +16,7 @@ export default function updateCommunityBlock(
);
} else {
myUserInfo.community_blocks = myUserInfo.community_blocks.filter(
i => i.community.id !== data.community_view.community.id,
c => c.id !== data.community_view.community.id,
);
toast(
`${I18NextService.i18n.t("unblocked")} ${

View file

@ -12,14 +12,11 @@ export default function updateInstanceBlock(
const instance = linkedInstances.find(i => i.id === id)!;
if (data.blocked) {
myUserInfo.instance_blocks.push({
person: myUserInfo.local_user_view.person,
instance,
});
myUserInfo.instance_blocks.push(instance);
toast(`${I18NextService.i18n.t("blocked")} ${instance.domain}`);
} else {
myUserInfo.instance_blocks = myUserInfo.instance_blocks.filter(
i => i.instance.id !== id,
i => i.id !== id,
);
toast(`${I18NextService.i18n.t("unblocked")} ${instance.domain}`);
}

View file

@ -8,16 +8,13 @@ export default function updatePersonBlock(
) {
if (myUserInfo) {
if (data.blocked) {
myUserInfo.person_blocks.push({
person: myUserInfo.local_user_view.person,
target: data.person_view.person,
});
myUserInfo.person_blocks.push(data.person_view.person);
toast(
`${I18NextService.i18n.t("blocked")} ${data.person_view.person.name}`,
);
} else {
myUserInfo.person_blocks = myUserInfo.person_blocks.filter(
i => i.target.id !== data.person_view.person.id,
p => p.id !== data.person_view.person.id,
);
toast(
`${I18NextService.i18n.t("unblocked")} ${data.person_view.person.name}`,

View file

@ -5,7 +5,6 @@ export default function voteDisplayMode(
): LocalUserVoteDisplayMode {
return (
siteRes?.my_user?.local_user_view.local_user_vote_display_mode ?? {
local_user_id: -1,
upvotes: true,
downvotes: true,
score: false,

View file

@ -0,0 +1,5 @@
export default function getBoolFromString(
boolStr?: string,
): boolean | undefined {
return boolStr ? boolStr.toLowerCase() === "true" : undefined;
}

View file

@ -5,6 +5,7 @@ import editListImmutable from "./edit-list-immutable";
import formatPastDate from "./format-past-date";
import futureDaysToUnixTime from "./future-days-to-unix-time";
import getIdFromString from "./get-id-from-string";
import getBoolFromString from "./get-bool-from-string";
import getPageFromString from "./get-page-from-string";
import getQueryParams from "./get-query-params";
import getQueryString from "./get-query-string";
@ -36,6 +37,7 @@ export {
formatPastDate,
futureDaysToUnixTime,
getIdFromString,
getBoolFromString,
getPageFromString,
getQueryParams,
getQueryString,