[ReviewDB] add emojis, discord markdown & notifications (#1718)

Co-authored-by: V <vendicated@riseup.net>
This commit is contained in:
Manti 2023-09-21 18:16:15 +03:00 committed by GitHub
parent 9550b74b2a
commit e5c0898dd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 170 additions and 93 deletions

@ -28,8 +28,8 @@ export default function ReviewBadge(badge: Badge) {
{({ onMouseEnter, onMouseLeave }) => ( {({ onMouseEnter, onMouseLeave }) => (
<img <img
className={cl("badge")} className={cl("badge")}
width="24px" width="22px"
height="24px" height="22px"
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
src={badge.icon} src={badge.icon}

@ -20,7 +20,7 @@ import { openUserProfile } from "@utils/discord";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { LazyComponent } from "@utils/react"; import { LazyComponent } from "@utils/react";
import { filters, findBulk } from "@webpack"; import { filters, findBulk } from "@webpack";
import { Alerts, moment, Timestamp, UserStore } from "@webpack/common"; import { Alerts, moment, Parser, Timestamp, UserStore } from "@webpack/common";
import { Review, ReviewType } from "../entities"; import { Review, ReviewType } from "../entities";
import { deleteReview, reportReview } from "../reviewDbApi"; import { deleteReview, reportReview } from "../reviewDbApi";
@ -30,12 +30,12 @@ import { DeleteButton, ReportButton } from "./MessageButton";
import ReviewBadge from "./ReviewBadge"; import ReviewBadge from "./ReviewBadge";
export default LazyComponent(() => { export default LazyComponent(() => {
// this is terrible, blame mantika // this is terrible, blame ven
const p = filters.byProps; const p = filters.byProps;
const [ const [
{ cozyMessage, buttons, message, buttonsInner, groupStart }, { cozyMessage, buttons, message, buttonsInner, groupStart },
{ container, isHeader }, { container, isHeader },
{ avatar, clickable, username, messageContent, wrapper, cozy }, { avatar, clickable, username, wrapper, cozy },
buttonClasses, buttonClasses,
botTag botTag
] = findBulk( ] = findBulk(
@ -124,12 +124,10 @@ export default LazyComponent(() => {
</Timestamp>) </Timestamp>)
} }
<p <div className={cl("review-comment")}>
className={classes(messageContent)} {Parser.parseGuildEventDescription(review.comment)}
style={{ fontSize: 15, marginTop: 4, color: "var(--text-normal)" }} </div>
>
{review.comment}
</p>
{review.id !== 0 && ( {review.id !== 0 && (
<div className={classes(container, isHeader, buttons)} style={{ <div className={classes(container, isHeader, buttons)} style={{
padding: "0px", padding: "0px",

@ -16,11 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { classes } from "@utils/misc"; import { LazyComponent, useAwaiter, useForceUpdater } from "@utils/react";
import { useAwaiter, useForceUpdater } from "@utils/react"; import { find, findByPropsLazy } from "@webpack";
import { findByPropsLazy } from "@webpack"; import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common";
import { Forms, React, RelationshipStore, UserStore } from "@webpack/common";
import type { KeyboardEvent } from "react";
import { Review } from "../entities"; import { Review } from "../entities";
import { addReview, getReviews, Response, REVIEWS_PER_PAGE } from "../reviewDbApi"; import { addReview, getReviews, Response, REVIEWS_PER_PAGE } from "../reviewDbApi";
@ -28,7 +26,12 @@ import { settings } from "../settings";
import { authorize, cl, showToast } from "../utils"; import { authorize, cl, showToast } from "../utils";
import ReviewComponent from "./ReviewComponent"; import ReviewComponent from "./ReviewComponent";
const Classes = findByPropsLazy("inputDefault", "editable");
const Editor = findByPropsLazy("start", "end", "addMark");
const Transform = findByPropsLazy("unwrapNodes");
const InputTypes = findByPropsLazy("VOICE_CHANNEL_STATUS", "SIDEBAR");
const InputComponent = LazyComponent(() => find(m => m?.type?.render?.toString().includes("CHANNEL_TEXT_AREA).AnalyticsLocationProvider")));
interface UserProps { interface UserProps {
discordId: string; discordId: string;
@ -113,48 +116,82 @@ function ReviewList({ refetch, reviews, hideOwnReview }: { refetch(): void; revi
); );
} }
export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: { discordId: string, name: string; isAuthor: boolean; refetch(): void; }) { export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: { discordId: string, name: string; isAuthor: boolean; refetch(): void; }) {
const { token } = settings.store; const { token } = settings.store;
const editorRef = useRef<any>(null);
const inputType = InputTypes.FORM;
inputType.disableAutoFocus = true;
function onKeyPress({ key, target }: KeyboardEvent<HTMLTextAreaElement>) { const channel = {
if (key === "Enter") { flags_: 256,
addReview({ guild_id_: null,
userid: discordId, id: "0",
comment: (target as HTMLInputElement).value, getGuildId: () => null,
star: -1 isPrivate: () => true,
}).then(res => { isActiveThread: () => false,
if (res?.success) { isArchivedLockedThread: () => false,
(target as HTMLInputElement).value = ""; // clear the input isDM: () => true,
refetch(); roles: { "0": { permissions: 0n } },
} else if (res?.message) { getRecipientId: () => "0",
showToast(res.message); hasFlag: () => false,
} };
});
}
}
return ( return (
<textarea <>
className={classes(Classes.inputDefault, "enter-comment", cl("input"))} <div onClick={() => {
onKeyDownCapture={e => {
if (e.key === "Enter") {
e.preventDefault(); // prevent newlines
}
}}
placeholder={
!token
? "You need to authorize to review users!"
: isAuthor
? `Update review for @${name}`
: `Review @${name}`
}
onKeyDown={onKeyPress}
onClick={() => {
if (!token) { if (!token) {
showToast("Opening authorization window..."); showToast("Opening authorization window...");
authorize(); authorize();
} }
}} }}>
/> <InputComponent
className={cl("input")}
channel={channel}
placeholder={
!token
? "You need to authorize to review users!"
: isAuthor
? `Update review for @${name}`
: `Review @${name}`
}
type={inputType}
disableThemedBackground={true}
setEditorRef={ref => editorRef.current = ref}
textValue=""
onSubmit={
async res => {
const response = await addReview({
userid: discordId,
comment: res.value,
});
if (response?.success) {
refetch();
const slateEditor = editorRef.current.ref.current.getSlateEditor();
// clear editor
Transform.delete(slateEditor, {
at: {
anchor: Editor.start(slateEditor, []),
focus: Editor.end(slateEditor, []),
}
});
} else if (response?.message) {
showToast(response.message);
}
// even tho we need to return this, it doesnt do anything
return {
shouldClear: false,
shouldRefocus: true,
};
}
}
/>
</div>
</>
); );
} }

@ -29,6 +29,13 @@ export const enum ReviewType {
System = 3 System = 3
} }
export const enum NotificationType {
Info = 0,
Ban = 1,
Unban = 2,
Warning = 3
}
export interface Badge { export interface Badge {
name: string; name: string;
description: string; description: string;
@ -45,6 +52,13 @@ export interface BanInfo {
banEndDate: number; banEndDate: number;
} }
export interface Notification {
id: number;
title: string;
content: string;
type: NotificationType;
}
export interface ReviewDBUser { export interface ReviewDBUser {
ID: number; ID: number;
discordID: string; discordID: string;
@ -54,6 +68,7 @@ export interface ReviewDBUser {
warningCount: number; warningCount: number;
badges: any[]; badges: any[];
banInfo: BanInfo | null; banInfo: BanInfo | null;
notification: Notification | null;
lastReviewID: number; lastReviewID: number;
type: UserType; type: UserType;
} }

@ -24,13 +24,13 @@ import ExpandableHeader from "@components/ExpandableHeader";
import { OpenExternalIcon } from "@components/Icons"; import { OpenExternalIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { Alerts, Menu, useState } from "@webpack/common"; import { Alerts, Menu, Parser, useState } from "@webpack/common";
import { Guild, User } from "discord-types/general"; import { Guild, User } from "discord-types/general";
import { openReviewsModal } from "./components/ReviewModal"; import { openReviewsModal } from "./components/ReviewModal";
import ReviewsView from "./components/ReviewsView"; import ReviewsView from "./components/ReviewsView";
import { UserType } from "./entities"; import { NotificationType } from "./entities";
import { getCurrentUserInfo } from "./reviewDbApi"; import { getCurrentUserInfo, readNotification } from "./reviewDbApi";
import { settings } from "./settings"; import { settings } from "./settings";
import { showToast } from "./utils"; import { showToast } from "./utils";
@ -78,40 +78,33 @@ export default definePlugin({
addContextMenuPatch("guild-header-popout", guildPopoutPatch); addContextMenuPatch("guild-header-popout", guildPopoutPatch);
if (user.banInfo) { if (user.notification) {
const endDate = new Date(user.banInfo.banEndDate); const props = user.notification.type === NotificationType.Ban ? {
if (endDate.getTime() > Date.now() && (s.user?.banInfo?.banEndDate ?? 0) < endDate.getTime()) { cancelText: "Appeal",
Alerts.show({ confirmText: "Ok",
title: "You have been banned from ReviewDB", onCancel: () =>
body: ( VencordNative.native.openExternal(
<> "https://reviewdb.mantikafasi.dev/api/redirect?"
<p> + new URLSearchParams({
You are banned from ReviewDB { token: settings.store.token!,
user.type === UserType.Banned page: "dashboard/appeal"
? "permanently" })
: "until " + endDate.toLocaleString() )
} } : {};
</p>
{user.banInfo.reviewContent && (
<p>Offending Review: {user.banInfo.reviewContent}</p>
)}
<p>Continued offenses will result in a permanent ban.</p>
</>
),
cancelText: "Appeal",
confirmText: "Ok",
onCancel: () =>
VencordNative.native.openExternal(
"https://reviewdb.mantikafasi.dev/api/redirect?"
+ new URLSearchParams({
token: settings.store.token!,
page: "dashboard/appeal"
})
)
});
}
}
Alerts.show({
title: user.notification.title,
body: (
Parser.parse(
user.notification.content,
false
)
),
...props
});
readNotification(user.notification.id);
}
s.user = user; s.user = user;
}, 4000); }, 4000);
}, },

@ -140,3 +140,12 @@ export function getCurrentUserInfo(token: string): Promise<ReviewDBUser> {
method: "POST", method: "POST",
}).then(r => r.json()); }).then(r => r.json());
} }
export function readNotification(id: number) {
return fetch(API_URL + `/api/reviewdb/notifications?id=${id}`, {
method: "PATCH",
headers: {
"Authorization": settings.store.token || "",
},
});
}

@ -14,7 +14,16 @@
overflow: hidden; overflow: hidden;
background: transparent; background: transparent;
border: 1px solid var(--profile-message-input-border-color); border: 1px solid var(--profile-message-input-border-color);
font-size: 14px; }
.vc-rdb-modal-footer > div {
width: 100%;
margin: 6px 16px;
}
/* When input becomes disabled(while sending review), input adds unneccesary padding to left, this prevents it */
.vc-rdb-input > div > div {
padding-left: 0 !important;
} }
.vc-rdb-placeholder { .vc-rdb-placeholder {
@ -24,13 +33,12 @@
color: var(--text-muted); color: var(--text-muted);
} }
.vc-rdb-modal-footer { .vc-rdb-input * {
padding: 0; font-size: 14px;
} }
.vc-rdb-modal-footer > div { .vc-rdb-modal-footer {
width: 100%; padding: 0;
margin: 6px 16px;
} }
.vc-rdb-modal-footer .vc-rdb-input { .vc-rdb-modal-footer .vc-rdb-input {
@ -49,3 +57,20 @@
.vc-rdb-modal-reviews { .vc-rdb-modal-reviews {
margin-top: 16px; margin-top: 16px;
} }
.vc-rdb-review {
margin-top: 8px;
margin-bottom: 8px;
}
.vc-rdb-review-comment img {
vertical-align: text-top;
}
.vc-rdb-review-comment {
overflow-y: hidden;
margin-top: 1px;
margin-bottom: 8px;
color: var(--text-normal);
font-size: 15px;
}