Skip to content

Commit

Permalink
Merge pull request #126 from tgxn/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
tgxn authored Dec 12, 2023
2 parents 72417c4 + 10cf9a1 commit 46252c4
Show file tree
Hide file tree
Showing 7 changed files with 535 additions and 15 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

⚡ A moderation tool for [Lemmy](https://github.com/LemmyNet/lemmy) community moderators and site admins. ⚡

> Currently only compatible with 0.18.x instances _(and not 0.19.x)_, as the [Lemmy SDK is not backwards-compatible.](https://github.com/LemmyNet/lemmy-js-client/issues/194)
> 🎉🎉 We now have cross-version support for 0.19! 🎉🙌 ~~Currently only compatible with 0.18.x instances _(and not 0.19.x)_, as the [Lemmy SDK is not backwards-compatible.](https://github.com/LemmyNet/lemmy-js-client/issues/194)~~
## Screenshots
| | | |
Expand Down Expand Up @@ -34,7 +34,6 @@ There are 3 types of users in Lemmy Modder: user, mod and `admin`
This is determined based on the amount of moderated communities you manage.



## Hosting Options

You can either use the hosted option at https://modder.lemmyverse.net/ or host your own instance.
Expand Down
22 changes: 22 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
"css-minimizer-webpack-plugin": "^5.0.1",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.3",
"lemmy-js-client18": "npm:lemmy-js-client@^0.18.1",
"lemmy-js-client": "npm:lemmy-js-client@^0.19.0-rc.19",
"lemmy-js-client18": "npm:lemmy-js-client@^0.18.1",
"moment": "^2.29.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand All @@ -45,9 +45,10 @@
"react-number-format": "^5.3.1",
"react-redux": "^8.1.1",
"react-router-dom": "^6.20.1",
"react-window": "^1.8.10",
"redux": "^4.2.1",
"remove-markdown": "^0.5.0",
"redux-persist": "^6.0.0",
"remove-markdown": "^0.5.0",
"sass": "^1.69.5",
"sass-loader": "^13.3.2",
"sonner": "^1.2.4",
Expand Down
283 changes: 281 additions & 2 deletions src/components/Filters.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,33 @@ import React from "react";

import { useDispatch, useSelector } from "react-redux";

import Autocomplete from "@mui/joy/Autocomplete";
import CircularProgress from "@mui/joy/CircularProgress";
import AutocompleteOption from "@mui/joy/AutocompleteOption";
import ListItemDecorator from "@mui/joy/ListItemDecorator";
import ListItemContent from "@mui/joy/ListItemContent";
import Box from "@mui/joy/Box";
import Select from "@mui/joy/Select";
import Option from "@mui/joy/Option";
import Typography from "@mui/joy/Typography";
import Checkbox from "@mui/joy/Checkbox";

import Chip from "@mui/joy/Chip";

import { selectFilterCommunity, selectFilterType, selectHideReadApprovals, selectModLogType, selectShowRemoved, selectShowResolved, setConfigItem } from "../redux/reducer/configReducer";
import PersonSearchIcon from "@mui/icons-material/PersonSearch";
import SupervisedUserCircleIcon from "@mui/icons-material/SupervisedUserCircle";

import { useLemmyHttpAction } from "../hooks/useLemmyHttp.js";

import { UserAvatar } from "./Display.jsx";
import {
selectFilterCommunity,
selectFilterType,
selectHideReadApprovals,
selectModLogType,
selectShowRemoved,
selectShowResolved,
setConfigItem,
} from "../redux/reducer/configReducer";

import { getSiteData } from "../hooks/getSiteData";
import { getModLogTypeNames } from "../utils";
Expand Down Expand Up @@ -193,3 +211,264 @@ export function FilterRemoved() {
/>
);
}

export function FilterUserAutocomplete({ value, onChange }) {
const [open, setOpen] = React.useState(false);
const [options, setOptions] = React.useState([]);
const [userSelected, setUserSelected] = React.useState(null);
const [managedInputValue, setManagedInputValue] = React.useState(null);

const {
data: localUserData,
callAction: getLocalUserData,
isSuccess: localUserIsSuccess,
isLoading: localUserIsLoading,
} = useLemmyHttpAction("getPersonDetails");

// lookup the users data if we are loading a value
React.useEffect(() => {
if (value && !localUserIsLoading) {
getLocalUserData({ person_id: value });
}
}, [value]);

// if the pre-load user is loaded
React.useEffect(() => {
if (localUserIsSuccess) {
console.log("localUserIsSuccess", localUserData.person_view);

const thePerson = localUserData.person_view.person;
const personFQUN = thePerson.name + "@" + thePerson.actor_id.split("/")[2];

setUserSelected(localUserData.person_view);
setOptions([
{
id: localUserData.person_view.person.id,
title: personFQUN,
person: localUserData.person_view.person,
},
]);
setManagedInputValue({
title: personFQUN,
});
}
}, [localUserIsSuccess, localUserData]);

// need to show an autocomplete, and then call the search api for results
const { data, callAction, isSuccess, isLoading } = useLemmyHttpAction("search");

const searchUsers = (searchTerm) => {
console.log("searchUsers", searchTerm);

if (!searchTerm || isLoading) {
return;
}

if (searchTerm.length < 2) {
return;
}

callAction({ q: searchTerm, listing_type: "Local", type_: "Users" });
};

const onUserSelected = (user) => {
console.log("onUserSelected", user);
setUserSelected(user);
onChange && onChange(user);
};

React.useEffect(() => {
if (isLoading) {
return;
}

if (data) {
const opts = data.users.map((user) => {
return {
id: user.person.id,
title: user.person.name + "@" + user.person.actor_id.split("/")[2],
person: user.person,
};
});
setOptions(opts);
}
}, [isLoading, data]);

return (
<Autocomplete
value={managedInputValue}
sx={{ width: 300 }}
placeholder="User Filter"
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOptions([]);
setOpen(false);
}}
isOptionEqualToValue={(option, value) => option.title === value.title}
getOptionLabel={(option) => option.title}
options={options}
startDecorator={
userSelected ? <UserAvatar source={userSelected.person.avatar} /> : <PersonSearchIcon />
}
// defaultValue={
// userSelected
// ? {
// id: userSelected.person.id,
// title: userSelected.person.name + "@" + userSelected.person.actor_id.split("/")[2],
// person: userSelected.person,
// }
// : null
// }
loading={localUserIsLoading || isLoading}
noOptionsText={data ? "Nothing Found" : "Search Users"}
onInputChange={(e, newValue) => {
searchUsers(newValue);
}}
onChange={(e, newValue) => {
console.log("onChange", newValue);
onUserSelected(newValue);
}}
renderOption={(props, option) => (
<AutocompleteOption {...props}>
<ListItemDecorator>
<UserAvatar source={option.person.avatar} />
</ListItemDecorator>
<ListItemContent sx={{ fontSize: "sm" }}>
{option.person.display_name ? option.person.display_name : option.person.name}
<Typography level="body-xs">{option.person.actor_id}</Typography>
</ListItemContent>
</AutocompleteOption>
)}
endDecorator={isLoading ? <CircularProgress size="sm" sx={{ bgcolor: "background.surface" }} /> : null}
/>
);
}

export function FilterCommunityAutocomplete({ value, onChange }) {
const [open, setOpen] = React.useState(false);
const [options, setOptions] = React.useState([]);
const [selected, setSelected] = React.useState(null);
const [managedInputValue, setManagedInputValue] = React.useState(null);

const {
data: communityData,
callAction: getCommunityData,
isSuccess: getCommunityIsSuccess,
isLoading: getCommunityIsLoading,
} = useLemmyHttpAction("getCommunity");

// lookup the Community data if we are loading a value
React.useEffect(() => {
if (value) {
getCommunityData({ id: value });
}
}, [value]);

// if the pre-load Community is loaded
React.useEffect(() => {
if (getCommunityIsSuccess) {
console.log("getCommunityIsSuccess", communityData);

const theCommunity = communityData.community_view;
const communityFQUN = theCommunity.community.name + "@" + theCommunity.community.actor_id.split("/")[2];

setSelected(communityData.community_view);
setOptions([
{
id: theCommunity.community.id,
title: communityFQUN,
community: theCommunity.community,
},
]);
setManagedInputValue({
title: communityFQUN,
});
}
}, [getCommunityIsSuccess, communityData]);

// need to show an autocomplete, and then call the search api for results
const { data, callAction, isSuccess, isLoading } = useLemmyHttpAction("search");

const searchUsers = (searchTerm) => {
console.log("searchcommunity", searchTerm);

if (!searchTerm || isLoading) {
return;
}

if (searchTerm.length < 2) {
return;
}

callAction({ q: searchTerm, listing_type: "Local", type_: "Communities" });
};

const onSelected = (community) => {
console.log("onSelected", community);
setSelected(community);
onChange && onChange(community);
};

React.useEffect(() => {
if (isLoading) {
return;
}

if (data) {
const opts = data.communities.map((item) => {
return {
id: item.community.id,
title: item.community.name + "@" + item.community.actor_id.split("/")[2],
community: item.community,
};
});
setOptions(opts);
}
}, [isLoading, data]);

return (
<Autocomplete
value={managedInputValue}
sx={{ width: 300 }}
placeholder="Community Filter"
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOptions([]);
setOpen(false);
}}
isOptionEqualToValue={(option, value) => option.title === value.title}
getOptionLabel={(option) => option.title}
options={options}
startDecorator={
selected ? <UserAvatar source={selected.community.avatar} /> : <SupervisedUserCircleIcon />
}
loading={getCommunityIsLoading || isLoading}
noOptionsText={data ? "Nothing Found" : "Search Communities"}
onInputChange={(e, newValue) => {
searchUsers(newValue);
}}
onChange={(e, newValue) => {
console.log("onChange", newValue);
onSelected(newValue);
}}
renderOption={(props, option) => (
<AutocompleteOption {...props}>
<ListItemDecorator>
<UserAvatar source={option.community.avatar} />
</ListItemDecorator>
<ListItemContent sx={{ fontSize: "sm" }}>
{option.community.title ? option.community.title : option.community.name}
<Typography level="body-xs">{option.community.actor_id}</Typography>
</ListItemContent>
</AutocompleteOption>
)}
endDecorator={isLoading ? <CircularProgress size="sm" sx={{ bgcolor: "background.surface" }} /> : null}
/>
);
}
Loading

0 comments on commit 46252c4

Please sign in to comment.