Skip to content

Commit

Permalink
feat: Block instance (#2144)
Browse files Browse the repository at this point in the history
* Start modifying settings

* feat: Finish making block instance setting

* feat: Add translations

* fix: Handle first load fetch

* chore: Fix linting error

* fix: Fix broken import
  • Loading branch information
SleeplessOne1917 committed Sep 27, 2023
1 parent d9fe7d1 commit f0ccf93
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 7 deletions.
2 changes: 1 addition & 1 deletion lemmy-translations
170 changes: 164 additions & 6 deletions src/shared/components/person/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,38 @@ import {
fetchCommunities,
fetchThemeList,
fetchUsers,
instanceToChoice,
myAuth,
personToChoice,
setIsoData,
setTheme,
showLocal,
updateCommunityBlock,
updateInstanceBlock,
updatePersonBlock,
} from "@utils/app";
import { capitalizeFirstLetter, debounce } from "@utils/helpers";
import { Choice } from "@utils/types";
import { Choice, RouteDataResponse } from "@utils/types";
import classNames from "classnames";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
import {
BlockCommunityResponse,
BlockInstanceResponse,
BlockPersonResponse,
CommunityBlockView,
DeleteAccountResponse,
GetFederatedInstancesResponse,
GetSiteResponse,
Instance,
InstanceBlockView,
ListingType,
LoginResponse,
PersonBlockView,
SortType,
} from "lemmy-js-client";
import { elementUrl, emDash, relTags } from "../../config";
import { UserService } from "../../services";
import { FirstLoadService, UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { I18NextService, languages } from "../../services/I18NextService";
import { setupTippy } from "../../tippy";
Expand All @@ -45,11 +51,17 @@ import { SortSelect } from "../common/sort-select";
import Tabs from "../common/tabs";
import { CommunityLink } from "../community/community-link";
import { PersonListing } from "./person-listing";
import { InitialFetchRequest } from "../../interfaces";

type SettingsData = RouteDataResponse<{
instancesRes: GetFederatedInstancesResponse;
}>;

interface SettingsState {
saveRes: RequestState<LoginResponse>;
changePasswordRes: RequestState<LoginResponse>;
deleteAccountRes: RequestState<DeleteAccountResponse>;
instancesRes: RequestState<GetFederatedInstancesResponse>;
// TODO redo these forms
saveUserSettingsForm: {
show_nsfw?: boolean;
Expand Down Expand Up @@ -86,6 +98,7 @@ interface SettingsState {
};
personBlocks: PersonBlockView[];
communityBlocks: CommunityBlockView[];
instanceBlocks: InstanceBlockView[];
currentTab: string;
themeList: string[];
deleteAccountShowConfirm: boolean;
Expand All @@ -94,22 +107,24 @@ interface SettingsState {
searchCommunityOptions: Choice[];
searchPersonLoading: boolean;
searchPersonOptions: Choice[];
searchInstanceOptions: Choice[];
isIsomorphic: boolean;
}

type FilterType = "user" | "community";
type FilterType = "user" | "community" | "instance";

const Filter = ({
filterType,
options,
onChange,
onSearch,
loading,
loading = false,
}: {
filterType: FilterType;
options: Choice[];
onSearch: (text: string) => void;
onChange: (choice: Choice) => void;
loading: boolean;
loading?: boolean;
}) => (
<div className="mb-3 row">
<label
Expand All @@ -133,24 +148,28 @@ const Filter = ({
);

export class Settings extends Component<any, SettingsState> {
private isoData = setIsoData(this.context);
private isoData = setIsoData<SettingsData>(this.context);
state: SettingsState = {
saveRes: { state: "empty" },
deleteAccountRes: { state: "empty" },
changePasswordRes: { state: "empty" },
instancesRes: { state: "empty" },
saveUserSettingsForm: {},
changePasswordForm: {},
deleteAccountShowConfirm: false,
deleteAccountForm: {},
personBlocks: [],
communityBlocks: [],
instanceBlocks: [],
currentTab: "settings",
siteRes: this.isoData.site_res,
themeList: [],
searchCommunityLoading: false,
searchCommunityOptions: [],
searchPersonLoading: false,
searchPersonOptions: [],
searchInstanceOptions: [],
isIsomorphic: false,
};

constructor(props: any, context: any) {
Expand All @@ -172,6 +191,7 @@ export class Settings extends Component<any, SettingsState> {

this.handleBlockPerson = this.handleBlockPerson.bind(this);
this.handleBlockCommunity = this.handleBlockCommunity.bind(this);
this.handleBlockInstance = this.handleBlockInstance.bind(this);

const mui = UserService.Instance.myUserInfo;
if (mui) {
Expand Down Expand Up @@ -232,11 +252,40 @@ export class Settings extends Component<any, SettingsState> {
},
};
}

// Only fetch the data if coming from another route
if (FirstLoadService.isFirstLoad) {
const { instancesRes } = this.isoData.routeData;

this.state = {
...this.state,
instancesRes,
isIsomorphic: true,
};
}
}

async componentDidMount() {
setupTippy();
this.setState({ themeList: await fetchThemeList() });

if (!this.state.isIsomorphic) {
this.setState({
instancesRes: { state: "loading" },
});

this.setState({
instancesRes: await HttpService.client.getFederatedInstances(),
});
}
}

static async fetchInitialData({
client,
}: InitialFetchRequest): Promise<SettingsData> {
return {
instancesRes: await client.getFederatedInstances(),
};
}

get documentTitle(): string {
Expand Down Expand Up @@ -315,6 +364,11 @@ export class Settings extends Component<any, SettingsState> {
<div className="card-body">{this.blockCommunityCard()}</div>
</div>
</div>
<div className="col-12 col-md-6">
<div className="card border-secondary mb-3">
<div className="card-body">{this.blockInstanceCard()}</div>
</div>
</div>
</div>
</div>
);
Expand Down Expand Up @@ -459,6 +513,49 @@ export class Settings extends Component<any, SettingsState> {
);
}

blockInstanceCard() {
const { searchInstanceOptions } = this.state;

return (
<div>
<Filter
filterType="instance"
onChange={this.handleBlockInstance}
onSearch={this.handleInstanceSearch}
options={searchInstanceOptions}
/>
{this.blockedInstancesList()}
</div>
);
}

blockedInstancesList() {
return (
<>
<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}>
<span>
{ib.instance.domain}
<button
className="btn btn-sm"
onClick={linkEvent(
{ ctx: this, instanceId: ib.instance.id },
this.handleUnblockInstance,
)}
data-tippy-content={I18NextService.i18n.t("unblock_instance")}
>
<Icon icon="x" classes="icon-inline" />
</button>
</span>
</li>
))}
</ul>
</>
);
}

saveUserSettingsHtmlForm() {
const selectedLangs = this.state.saveUserSettingsForm.discussion_languages;

Expand Down Expand Up @@ -987,6 +1084,27 @@ export class Settings extends Component<any, SettingsState> {
});
});

handleInstanceSearch = debounce(async (text: string) => {
let searchInstanceOptions: Instance[] = [];

if (this.state.instancesRes.state === "success") {
searchInstanceOptions =
this.state.instancesRes.data.federated_instances?.linked.filter(
instance =>
instance.domain.toLowerCase().includes(text.toLowerCase()) ||
!this.state.instanceBlocks.some(
blockedIntance => blockedIntance.instance.id === instance.id,
),
) ?? [];
}

this.setState({
searchInstanceOptions: searchInstanceOptions
.slice(0, 30)
.map(instanceToChoice),
});
});

async handleBlockPerson({ value }: Choice) {
if (value !== "0") {
const res = await HttpService.client.blockPerson({
Expand Down Expand Up @@ -1031,6 +1149,31 @@ export class Settings extends Component<any, SettingsState> {
}
}

async handleBlockInstance({ value }: Choice) {
if (value !== "0") {
const id = Number(value);
const res = await HttpService.client.blockInstance({
block: true,
instance_id: id,
});
this.instanceBlock(id, res);
}
}

async handleUnblockInstance({
ctx,
instanceId,
}: {
ctx: Settings;
instanceId: number;
}) {
const res = await HttpService.client.blockInstance({
block: false,
instance_id: instanceId,
});
ctx.instanceBlock(instanceId, res);
}

handleShowNsfwChange(i: Settings, event: any) {
i.setState(
s => ((s.saveUserSettingsForm.show_nsfw = event.target.checked), s),
Expand Down Expand Up @@ -1315,4 +1458,19 @@ export class Settings extends Component<any, SettingsState> {
}
}
}

instanceBlock(id: number, res: RequestState<BlockInstanceResponse>) {
if (
res.state === "success" &&
this.state.instancesRes.state === "success"
) {
const linkedInstances =
this.state.instancesRes.data.federated_instances?.linked ?? [];
updateInstanceBlock(res.data, id, linkedInstances);
const mui = UserService.Instance.myUserInfo;
if (mui) {
this.setState({ instanceBlocks: mui.instance_blocks });
}
}
}
}
1 change: 1 addition & 0 deletions src/shared/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export const routes: IRoutePropsWithFetch<Record<string, any>>[] = [
{
path: `/settings`,
component: Settings,
fetchInitialData: Settings.fetchInitialData,
},
{
path: `/modlog/:communityId`,
Expand Down
4 changes: 4 additions & 0 deletions src/shared/utils/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ import showScores from "./show-scores";
import siteBannerCss from "./site-banner-css";
import updateCommunityBlock from "./update-community-block";
import updatePersonBlock from "./update-person-block";
import instanceToChoice from "./instance-to-choice";
import updateInstanceBlock from "./update-instance-block";

export {
buildCommentsTree,
Expand Down Expand Up @@ -108,4 +110,6 @@ export {
siteBannerCss,
updateCommunityBlock,
updatePersonBlock,
instanceToChoice,
updateInstanceBlock,
};
9 changes: 9 additions & 0 deletions src/shared/utils/app/instance-to-choice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Choice } from "@utils/types";
import { Instance } from "lemmy-js-client";

export default function instanceToChoice({ id, domain }: Instance): Choice {
return {
value: id.toString(),
label: domain,
};
}
27 changes: 27 additions & 0 deletions src/shared/utils/app/update-instance-block.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { BlockInstanceResponse, Instance, MyUserInfo } from "lemmy-js-client";
import { I18NextService, UserService } from "../../services";
import { toast } from "../../toast";

export default function updateInstanceBlock(
data: BlockInstanceResponse,
id: number,
linkedInstances: Instance[],
myUserInfo: MyUserInfo | undefined = UserService.Instance.myUserInfo,
) {
if (myUserInfo) {
const instance = linkedInstances.find(i => i.id === id)!;

if (data.blocked) {
myUserInfo.instance_blocks.push({
person: myUserInfo.local_user_view.person,
instance,
});
toast(`${I18NextService.i18n.t("blocked")} ${instance.domain}`);
} else {
myUserInfo.instance_blocks = myUserInfo.instance_blocks.filter(
i => i.instance.id !== id,
);
toast(`${I18NextService.i18n.t("unblocked")} ${instance.domain}`);
}
}
}

0 comments on commit f0ccf93

Please sign in to comment.