ReviewDB: it's a big day for reddit nation (#4216)
add updoots/downdoots
This commit is contained in:
parent
2ca831f91c
commit
e8415d7c64
7 changed files with 328 additions and 140 deletions
|
|
@ -40,7 +40,7 @@ export async function updateAuth(newAuth: ReviewDBAuth) {
|
|||
});
|
||||
}
|
||||
|
||||
export function authorize(callback?: any) {
|
||||
export function authorize(callback?: () => void) {
|
||||
openModal(props =>
|
||||
<OAuth2AuthorizeModal
|
||||
{...props}
|
||||
|
|
@ -50,7 +50,7 @@ export function authorize(callback?: any) {
|
|||
permissions={0n}
|
||||
clientId="915703782174752809"
|
||||
cancelCompletesFlow={false}
|
||||
callback={async (response: any) => {
|
||||
callback={async (response: { location: string }) => {
|
||||
try {
|
||||
const url = new URL(response.location);
|
||||
url.searchParams.append("clientMod", "vencord");
|
||||
|
|
@ -60,7 +60,7 @@ export function authorize(callback?: any) {
|
|||
|
||||
if (!res.ok) {
|
||||
const { message } = await res.json();
|
||||
showToast(message || "An error occured while authorizing", Toasts.Type.FAILURE);
|
||||
showToast(message ?? "An error occured while authorizing", Toasts.Type.FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,13 +18,13 @@
|
|||
|
||||
import { Auth, getToken } from "@plugins/reviewDB/auth";
|
||||
import { Review, ReviewType } from "@plugins/reviewDB/entities";
|
||||
import { blockUser, deleteReview, reportReview, unblockUser } from "@plugins/reviewDB/reviewDbApi";
|
||||
import { blockUser, deleteReview, deleteReviewVote, reportReview, unblockUser, voteReview } from "@plugins/reviewDB/reviewDbApi";
|
||||
import { settings } from "@plugins/reviewDB/settings";
|
||||
import { canBlockReviewAuthor, canDeleteReview, canReportReview, cl, showToast } from "@plugins/reviewDB/utils";
|
||||
import { openUserProfile } from "@utils/discord";
|
||||
import { classes } from "@utils/misc";
|
||||
import { findCssClassesLazy } from "@webpack";
|
||||
import { ConfirmModal,IconUtils, openModal as openVencordModal, Parser, Timestamp, useState } from "@webpack/common";
|
||||
import { ConfirmModal, IconUtils, openModal as openVencordModal, Parser, Timestamp, useEffect, useState } from "@webpack/common";
|
||||
|
||||
import { openBlockModal } from "./BlockedUserModal";
|
||||
import { BlockButton, DeleteButton, ReportButton } from "./MessageButton";
|
||||
|
|
@ -40,6 +40,14 @@ const dateFormat = new Intl.DateTimeFormat();
|
|||
|
||||
export default function ReviewComponent({ review, refetch, profileId }: { review: Review; refetch(): void; profileId: string; }) {
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
const [localVote, setLocalVote] = useState<boolean | null>(review.userVote ?? null);
|
||||
const [score, setScore] = useState(review.score ?? 0);
|
||||
const [isVoting, setIsVoting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setLocalVote(review.userVote ?? null);
|
||||
setScore(review.score ?? 0);
|
||||
}, [review.score, review.userVote]);
|
||||
|
||||
function openModal() {
|
||||
openUserProfile(review.sender.discordID);
|
||||
|
|
@ -56,13 +64,9 @@ export default function ReviewComponent({ review, refetch, profileId }: { review
|
|||
onConfirm={async () => {
|
||||
if (!(await getToken())) {
|
||||
return showToast("You must be logged in to delete reviews.");
|
||||
} else {
|
||||
deleteReview(review.id).then(res => {
|
||||
if (res) {
|
||||
refetch();
|
||||
}
|
||||
});
|
||||
}
|
||||
const res = await deleteReview(review.id);
|
||||
if (res) refetch();
|
||||
}}
|
||||
/>
|
||||
));
|
||||
|
|
@ -79,9 +83,8 @@ export default function ReviewComponent({ review, refetch, profileId }: { review
|
|||
onConfirm={async () => {
|
||||
if (!(await getToken())) {
|
||||
return showToast("You must be logged in to report reviews.");
|
||||
} else {
|
||||
reportReview(review.id);
|
||||
}
|
||||
await reportReview(review.id);
|
||||
}}
|
||||
/>
|
||||
));
|
||||
|
|
@ -103,27 +106,87 @@ export default function ReviewComponent({ review, refetch, profileId }: { review
|
|||
onConfirm={async () => {
|
||||
if (!(await getToken())) {
|
||||
return showToast("You must be logged in to block users.");
|
||||
} else {
|
||||
blockUser(review.sender.discordID);
|
||||
}
|
||||
await blockUser(review.sender.discordID);
|
||||
}}
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
async function submitVote(isUpvote: boolean) {
|
||||
if (isVoting) return;
|
||||
|
||||
if (review.sender.discordID === Auth.user?.discordID) {
|
||||
return showToast("You cannot vote on your own review.");
|
||||
}
|
||||
|
||||
setIsVoting(true);
|
||||
|
||||
try {
|
||||
if (localVote === isUpvote) {
|
||||
if (await deleteReviewVote(review.id)) {
|
||||
setLocalVote(null);
|
||||
setScore(currentScore => currentScore + (isUpvote ? -1 : 1));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (await voteReview(review.id, isUpvote)) {
|
||||
const delta = localVote == null
|
||||
? isUpvote ? 1 : -1
|
||||
: isUpvote ? 2 : -2;
|
||||
|
||||
setLocalVote(isUpvote);
|
||||
setScore(currentScore => currentScore + delta);
|
||||
}
|
||||
} finally {
|
||||
setIsVoting(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes(cl("review"), MessageClasses.cozyMessage, AvatarClasses.wrapper, MessageClasses.message, MessageClasses.groupStart, AvatarClasses.cozy)} style={
|
||||
{
|
||||
marginLeft: "0px",
|
||||
paddingLeft: "52px", // wth is this
|
||||
paddingLeft: "52px",
|
||||
// nobody knows anymore
|
||||
}
|
||||
}>
|
||||
|
||||
{review.id !== 0 && (
|
||||
<div className={cl("vote-column")}>
|
||||
<span className={classes(cl("vote-column-score"), score > 0 && cl("vote-column-score-positive"), score < 0 && cl("vote-column-score-negative"))}>
|
||||
{score}
|
||||
</span>
|
||||
<div className={cl("vote-column-buttons")}>
|
||||
<button
|
||||
className={classes(cl("vote-column-button"), !!localVote && cl("vote-column-up-selected"))}
|
||||
disabled={isVoting}
|
||||
onClick={() => submitVote(true)}
|
||||
type="button"
|
||||
>
|
||||
<svg height="20" viewBox="0 0 12 12" width="20" fill="none">
|
||||
<path d="M3 7.5 6 4.5l3 3" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.8" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
className={classes(cl("vote-column-button"), localVote === false && cl("vote-column-down-selected"))}
|
||||
disabled={isVoting}
|
||||
onClick={() => submitVote(false)}
|
||||
type="button"
|
||||
>
|
||||
<svg height="20" viewBox="0 0 12 12" width="20" fill="none">
|
||||
<path d="M3 4.5 6 7.5l3-3" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.8" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<img
|
||||
className={classes(AvatarClasses.avatar, AvatarClasses.clickable)}
|
||||
onClick={openModal}
|
||||
src={review.sender.profilePhoto || IconUtils.getDefaultAvatarURL(review.sender.discordID)}
|
||||
src={review.sender.profilePhoto ?? IconUtils.getDefaultAvatarURL(review.sender.discordID)}
|
||||
style={{ left: "0px", zIndex: 0 }}
|
||||
onError={e => e.currentTarget.src = IconUtils.getDefaultAvatarURL(review.sender.discordID)}
|
||||
/>
|
||||
|
|
@ -163,7 +226,6 @@ export default function ReviewComponent({ review, refetch, profileId }: { review
|
|||
{dateFormat.format(review.timestamp * 1000)}
|
||||
</Timestamp>)
|
||||
}
|
||||
|
||||
<div className={cl("review-comment")}>
|
||||
{(review.comment.length > 200 && !showAll)
|
||||
? (
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export default function ReviewsView({
|
|||
}: Props) {
|
||||
const [signal, refetch] = useForceUpdater(true);
|
||||
|
||||
const [reviewData] = useAwaiter(() => getReviews(discordId, { offset: (page - 1) * REVIEWS_PER_PAGE }), {
|
||||
const [reviewData] = useAwaiter(() => getReviews(discordId, { offset: (page - 1) * REVIEWS_PER_PAGE, fetchVotes: true }), {
|
||||
fallbackValue: null,
|
||||
deps: [refetchSignal, signal, page],
|
||||
onSuccess: data => {
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ export interface ReviewDBUser {
|
|||
username: string;
|
||||
type: UserType;
|
||||
profilePhoto: string;
|
||||
badges: any[];
|
||||
badges: Badge[];
|
||||
}
|
||||
|
||||
export interface ReviewDBCurrentUser extends ReviewDBUser {
|
||||
|
|
@ -93,8 +93,10 @@ export interface ReviewAuthor {
|
|||
export interface Review {
|
||||
comment: string,
|
||||
id: number,
|
||||
score?: number,
|
||||
star: number,
|
||||
sender: ReviewAuthor,
|
||||
timestamp: number;
|
||||
type?: ReviewType;
|
||||
userVote?: boolean | null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,49 +115,51 @@ export default definePlugin({
|
|||
setTimeout(async () => {
|
||||
if (!Auth.token) return;
|
||||
|
||||
const user = await getCurrentUserInfo(Auth.token);
|
||||
updateAuth({ user });
|
||||
const user = await getCurrentUserInfo();
|
||||
if (user) {
|
||||
updateAuth({ user });
|
||||
|
||||
if (notifyReviews) {
|
||||
if (lastReviewId && lastReviewId < user.lastReviewID) {
|
||||
s.lastReviewId = user.lastReviewID;
|
||||
if (user.lastReviewID !== 0)
|
||||
showToast("You have new reviews on your profile!");
|
||||
if (notifyReviews) {
|
||||
if (lastReviewId && lastReviewId < user.lastReviewID) {
|
||||
s.lastReviewId = user.lastReviewID;
|
||||
if (user.lastReviewID !== 0)
|
||||
showToast("You have new reviews on your profile!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { notification } = user;
|
||||
if (notification) {
|
||||
const props = notification.type === NotificationType.Ban ? {
|
||||
cancelText: "Appeal",
|
||||
confirmText: "Ok",
|
||||
onCancel: async () =>
|
||||
VencordNative.native.openExternal(
|
||||
"https://reviewdb.mantikafasi.dev/api/redirect?"
|
||||
+ new URLSearchParams({
|
||||
token: Auth.token!,
|
||||
page: "dashboard/appeal"
|
||||
})
|
||||
)
|
||||
} : {};
|
||||
const { notification } = user;
|
||||
if (notification) {
|
||||
const props = notification.type === NotificationType.Ban ? {
|
||||
cancelText: "Appeal",
|
||||
confirmText: "Ok",
|
||||
onCancel: async () =>
|
||||
VencordNative.native.openExternal(
|
||||
"https://reviewdb.mantikafasi.dev/api/redirect?"
|
||||
+ new URLSearchParams({
|
||||
token: Auth.token!,
|
||||
page: "dashboard/appeal"
|
||||
})
|
||||
)
|
||||
} : {};
|
||||
|
||||
openModal(modalProps => (
|
||||
<ConfirmModal
|
||||
{...modalProps}
|
||||
title={notification.title}
|
||||
confirmText={props.confirmText ?? "OK"}
|
||||
cancelText={props.cancelText}
|
||||
variant="primary"
|
||||
onCancel={props.onCancel}
|
||||
>
|
||||
{Parser.parse(
|
||||
notification.content,
|
||||
false
|
||||
)}
|
||||
</ConfirmModal>
|
||||
));
|
||||
openModal(modalProps => (
|
||||
<ConfirmModal
|
||||
{...modalProps}
|
||||
title={notification.title}
|
||||
confirmText={props.confirmText ?? "OK"}
|
||||
cancelText={props.cancelText}
|
||||
variant="primary"
|
||||
onCancel={props.onCancel}
|
||||
>
|
||||
{Parser.parse(
|
||||
notification.content,
|
||||
false
|
||||
)}
|
||||
</ConfirmModal>
|
||||
));
|
||||
|
||||
readNotification(notification.id);
|
||||
readNotification(notification.id);
|
||||
}
|
||||
}
|
||||
}, 4000);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,20 +1,8 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2022 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2026 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Toasts } from "@webpack/common";
|
||||
|
||||
|
|
@ -36,19 +24,51 @@ export interface UserReviewsData {
|
|||
hasOptedOut: boolean;
|
||||
}
|
||||
|
||||
const WarningFlag = 0b00000010;
|
||||
|
||||
async function rdbRequest(path: string, options: RequestInit = {}) {
|
||||
return fetch(API_URL + path, {
|
||||
...options,
|
||||
headers: {
|
||||
...options.headers,
|
||||
Authorization: await getToken() || "",
|
||||
}
|
||||
});
|
||||
export interface ReviewVote {
|
||||
reviewID: number;
|
||||
isUpvote: boolean;
|
||||
}
|
||||
|
||||
export async function getReviews(id: string, { limit, offset = 0 }: { limit?: number; offset?: number; } = {}): Promise<UserReviewsData> {
|
||||
interface ReviewVotesData {
|
||||
message?: string;
|
||||
votes: ReviewVote[];
|
||||
}
|
||||
|
||||
const WarningFlag = 0b00000010;
|
||||
|
||||
async function rdbRequest<T = unknown>(path: string, options: RequestInit = {}): Promise<T | null> {
|
||||
const headers: Record<string, string> = {
|
||||
Accept: "application/json",
|
||||
Authorization: await getToken() ?? "",
|
||||
...options.headers as Record<string, string>,
|
||||
};
|
||||
|
||||
if (options.body) {
|
||||
headers["Content-Type"] = "application/json";
|
||||
}
|
||||
|
||||
const res = await fetch(API_URL + path, {
|
||||
...options,
|
||||
headers,
|
||||
}).catch(err => {
|
||||
showToast("Network error: Failed to connect to ReviewDB.", Toasts.Type.FAILURE);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (!res) return null;
|
||||
|
||||
const data = await res.json().catch(() => null);
|
||||
|
||||
if (!res.ok) {
|
||||
const message = data?.message ?? `ReviewDB: Request failed with status ${res.status}`;
|
||||
showToast(message, Toasts.Type.FAILURE);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data as T;
|
||||
}
|
||||
|
||||
export async function getReviews(id: string, { limit, offset = 0, fetchVotes = false }: { limit?: number; offset?: number; fetchVotes?: boolean } = {}): Promise<UserReviewsData> {
|
||||
let flags = 0;
|
||||
if (!settings.store.showWarning) flags |= WarningFlag;
|
||||
|
||||
|
|
@ -57,6 +77,7 @@ export async function getReviews(id: string, { limit, offset = 0 }: { limit?: nu
|
|||
if (offset) params.append("offset", String(offset));
|
||||
if (limit) params.append("limit", String(limit));
|
||||
|
||||
const votesPromise = fetchVotes ? getReviewVotes(id).catch(() => []) : Promise.resolve([]);
|
||||
const req = await fetch(`${API_URL}/users/${id}/reviews?${params}`);
|
||||
|
||||
const res = (req.ok)
|
||||
|
|
@ -93,10 +114,33 @@ export async function getReviews(id: string, { limit, offset = 0 }: { limit?: nu
|
|||
};
|
||||
}
|
||||
|
||||
if (!fetchVotes || res.reviews.length === 0) return res;
|
||||
|
||||
const votes = await votesPromise;
|
||||
if (votes.length === 0) return res;
|
||||
|
||||
const voteByReviewId = new Map<number, boolean>();
|
||||
for (const vote of votes) {
|
||||
voteByReviewId.set(vote.reviewID, vote.isUpvote);
|
||||
}
|
||||
|
||||
res.reviews = res.reviews.map(review => ({
|
||||
...review,
|
||||
userVote: voteByReviewId.get(review.id) ?? null,
|
||||
}));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function addReview(review: any): Promise<UserReviewsData | null> {
|
||||
export async function getReviewVotes(id: string): Promise<ReviewVote[]> {
|
||||
const token = await getToken();
|
||||
if (!token) return [];
|
||||
|
||||
const res = await rdbRequest<ReviewVotesData>(`/users/${id}/reviews/votes`);
|
||||
return res?.votes ?? [];
|
||||
}
|
||||
|
||||
export async function addReview(review: { userid: string; comment: string }): Promise<UserReviewsData | null> {
|
||||
|
||||
const token = await getToken();
|
||||
if (!token) {
|
||||
|
|
@ -105,75 +149,88 @@ export async function addReview(review: any): Promise<UserReviewsData | null> {
|
|||
return null;
|
||||
}
|
||||
|
||||
return await rdbRequest(`/users/${review.userid}/reviews`, {
|
||||
const data = await rdbRequest<UserReviewsData>(`/users/${review.userid}/reviews`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(review),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
}).then(async r => {
|
||||
const data = await r.json() as UserReviewsData;
|
||||
showToast(data.message);
|
||||
return r.ok ? data : null;
|
||||
});
|
||||
if (data?.message) showToast(data.message);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function deleteReview(id: number): Promise<UserReviewsData | null> {
|
||||
return await rdbRequest(`/users/${id}/reviews`, {
|
||||
const data = await rdbRequest<UserReviewsData>(`/users/${id}/reviews`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
reviewid: id
|
||||
})
|
||||
}).then(async r => {
|
||||
const data = await r.json() as UserReviewsData;
|
||||
showToast(data.message);
|
||||
return r.ok ? data : null;
|
||||
});
|
||||
if (data?.message) showToast(data.message);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function reportReview(id: number) {
|
||||
const res = await rdbRequest("/reports", {
|
||||
const data = await rdbRequest<UserReviewsData>("/reports", {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
reviewid: id,
|
||||
})
|
||||
}).then(r => r.json()) as UserReviewsData;
|
||||
});
|
||||
if (data?.message) showToast(data.message);
|
||||
}
|
||||
|
||||
showToast(res.message);
|
||||
export async function voteReview(id: number, isUpvote: boolean) {
|
||||
const token = await getToken();
|
||||
if (!token) {
|
||||
showToast("Please authorize to vote on reviews.");
|
||||
authorize();
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await rdbRequest<{ message?: string }>(`/reviews/${id}/vote`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ isUpvote })
|
||||
});
|
||||
|
||||
if (!data) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function deleteReviewVote(id: number) {
|
||||
const token = await getToken();
|
||||
if (!token) {
|
||||
showToast("Please authorize to vote on reviews.");
|
||||
authorize();
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await rdbRequest<{ message?: string }>(`/reviews/${id}/vote`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
if (!data) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function patchBlock(action: "block" | "unblock", userId: string) {
|
||||
const res = await rdbRequest("/blocks", {
|
||||
const data = await rdbRequest("/blocks", {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: action,
|
||||
discordId: userId
|
||||
})
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
showToast(`Failed to ${action} user`, Toasts.Type.FAILURE);
|
||||
} else {
|
||||
showToast(`Successfully ${action}ed user`, Toasts.Type.SUCCESS);
|
||||
if (!data) return;
|
||||
|
||||
if (Auth?.user?.blockedUsers) {
|
||||
const newBlockedUsers = action === "block"
|
||||
? [...Auth.user.blockedUsers, userId]
|
||||
: Auth.user.blockedUsers.filter(id => id !== userId);
|
||||
updateAuth({ user: { ...Auth.user, blockedUsers: newBlockedUsers } });
|
||||
}
|
||||
showToast(`Successfully ${action}ed user`, Toasts.Type.SUCCESS);
|
||||
|
||||
if (Auth?.user?.blockedUsers) {
|
||||
const newBlockedUsers = action === "block"
|
||||
? [...Auth.user.blockedUsers, userId]
|
||||
: Auth.user.blockedUsers.filter(id => id !== userId);
|
||||
updateAuth({ user: { ...Auth.user, blockedUsers: newBlockedUsers } });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -181,25 +238,17 @@ export const blockUser = (userId: string) => patchBlock("block", userId);
|
|||
export const unblockUser = (userId: string) => patchBlock("unblock", userId);
|
||||
|
||||
export async function fetchBlocks(): Promise<ReviewDBUser[]> {
|
||||
const res = await rdbRequest("/blocks", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
}
|
||||
});
|
||||
|
||||
if (!res.ok) throw new Error(`${res.status}: ${res.statusText}`);
|
||||
return res.json();
|
||||
return await rdbRequest<ReviewDBUser[]>("/blocks") ?? [];
|
||||
}
|
||||
|
||||
export function getCurrentUserInfo(token: string): Promise<ReviewDBCurrentUser> {
|
||||
return rdbRequest("/users", {
|
||||
export function getCurrentUserInfo(): Promise<ReviewDBCurrentUser | null> {
|
||||
return rdbRequest<ReviewDBCurrentUser>("/users", {
|
||||
method: "POST",
|
||||
}).then(r => r.json());
|
||||
});
|
||||
}
|
||||
|
||||
export async function readNotification(id: number) {
|
||||
return rdbRequest(`/notifications?id=${id}`, {
|
||||
return await rdbRequest(`/notifications?id=${id}`, {
|
||||
method: "PATCH"
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
.vc-rdb-review {
|
||||
padding-top: 8px !important;
|
||||
padding-bottom: 8px !important;
|
||||
padding-right: 32px !important;
|
||||
padding-right: 64px !important;
|
||||
}
|
||||
|
||||
.vc-rdb-review:hover {
|
||||
|
|
@ -57,6 +57,7 @@
|
|||
.vc-rdb-review-comment {
|
||||
overflow-y: hidden;
|
||||
margin-top: 1px;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 8px;
|
||||
color: var(--text-default);
|
||||
font-size: 15px;
|
||||
|
|
@ -116,4 +117,76 @@
|
|||
.vc-rdb-profile-popout-disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.vc-rdb-vote-column {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.vc-rdb-vote-column-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.vc-rdb-vote-column-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: var(--interactive-muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vc-rdb-vote-column-button:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.vc-rdb-vote-column-button:hover:not(:disabled) {
|
||||
background: var(--background-modifier-hover);
|
||||
color: var(--interactive-hover);
|
||||
}
|
||||
|
||||
.vc-rdb-vote-column-score {
|
||||
min-width: 15px;
|
||||
color: var(--text-muted);
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.vc-rdb-vote-column-score-positive,
|
||||
.vc-rdb-vote-column-up-selected {
|
||||
color: var(--status-positive);
|
||||
}
|
||||
|
||||
.vc-rdb-vote-column-score-negative,
|
||||
.vc-rdb-vote-column-down-selected {
|
||||
color: var(--status-danger);
|
||||
}
|
||||
|
||||
.vc-rdb-vote-column-up-selected:hover:not(:disabled) {
|
||||
background: color-mix(in srgb, var(--status-positive) 14%, transparent);
|
||||
color: var(--status-positive);
|
||||
}
|
||||
|
||||
.vc-rdb-vote-column-down-selected:hover:not(:disabled) {
|
||||
background: color-mix(in srgb, var(--status-danger) 14%, transparent);
|
||||
color: var(--status-danger);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue