Merge pull request #1332 from alectrocute/breakout-role-utils

Organize `utils.ts` into folder, update imports
This commit is contained in:
SleeplessOne1917 2023-06-20 21:14:19 +00:00 committed by GitHub
commit 94bc9092ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 376 additions and 303 deletions

View File

@ -121,6 +121,7 @@
"sortpack": "^2.3.4",
"style-loader": "^3.3.2",
"terser": "^5.17.3",
"tsconfig-paths-webpack-plugin": "^4.0.1",
"typescript": "^5.0.4",
"webpack-dev-server": "4.15.0"
},

View File

@ -1,14 +1,13 @@
import { hydrate } from "inferno-hydrate";
import { Router } from "inferno-router";
import { App } from "../shared/components/app/app";
import { HistoryService } from "../shared/services/HistoryService";
import { initializeSite } from "../shared/utils";
import "bootstrap/js/dist/collapse";
import "bootstrap/js/dist/dropdown";
import { HistoryService } from "../shared/services/HistoryService";
const site = window.isoData.site_res;
initializeSite(site);
initializeSite(window.isoData.site_res);
const wrapper = (
<Router history={HistoryService.history}>
@ -17,6 +16,7 @@ const wrapper = (
);
const root = document.getElementById("root");
if (root) {
hydrate(wrapper, root);
}

View File

@ -1,3 +1,6 @@
import { isBrowser } from "@utils/browser";
import { poll } from "@utils/helpers";
import { amAdmin, canCreateCommunity } from "@utils/roles";
import { Component, createRef, linkEvent } from "inferno";
import { NavLink } from "inferno-router";
import {
@ -10,13 +13,9 @@ import { i18n } from "../../i18next";
import { UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import {
amAdmin,
canCreateCommunity,
donateLemmyUrl,
isBrowser,
myAuth,
numToSI,
poll,
showAvatars,
toast,
updateUnreadCountsInterval,

View File

@ -1,3 +1,11 @@
import {
amCommunityCreator,
canAdmin,
canMod,
isAdmin,
isBanned,
isMod,
} from "@utils/roles";
import classNames from "classnames";
import { Component, InfernoNode, linkEvent } from "inferno";
import { Link } from "inferno-router";
@ -40,16 +48,10 @@ import {
} from "../../interfaces";
import { UserService } from "../../services";
import {
amCommunityCreator,
canAdmin,
canMod,
colorList,
commentTreeMaxDepth,
futureDaysToUnixTime,
getCommentParentId,
isAdmin,
isBanned,
isMod,
mdToHtml,
mdToHtmlNoImages,
myAuth,

View File

@ -1,3 +1,4 @@
import { isBrowser } from "@utils/browser";
import autosize from "autosize";
import classNames from "classnames";
import { NoOptionI18nKeys } from "i18next";
@ -8,7 +9,6 @@ import { HttpService, UserService } from "../../services";
import {
concurrentImageUpload,
customEmojisLookup,
isBrowser,
markdownFieldCharacterLimit,
markdownHelpUrl,
maxUploadImages,

View File

@ -1,3 +1,5 @@
import { getQueryParams, getQueryString } from "@utils/helpers";
import type { QueryParams } from "@utils/types";
import { Component, linkEvent } from "inferno";
import {
CommunityResponse,
@ -11,12 +13,9 @@ import { InitialFetchRequest } from "../../interfaces";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
import {
QueryParams,
RouteDataResponse,
editCommunity,
getPageFromString,
getQueryParams,
getQueryString,
myAuth,
myAuthRequired,
numToSI,

View File

@ -1,3 +1,5 @@
import { getQueryParams, getQueryString } from "@utils/helpers";
import type { QueryParams } from "@utils/types";
import { Component, linkEvent } from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route";
import {
@ -62,7 +64,6 @@ import { UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
import {
QueryParams,
RouteDataResponse,
commentsToFlatNodes,
communityRSSUrl,
@ -75,8 +76,6 @@ import {
getCommentParentId,
getDataTypeString,
getPageFromString,
getQueryParams,
getQueryString,
myAuth,
postToCommentSortType,
relTags,

View File

@ -1,3 +1,4 @@
import { amAdmin, amMod, amTopMod } from "@utils/roles";
import { Component, InfernoNode, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import { Link } from "inferno-router";
@ -16,15 +17,7 @@ import {
} from "lemmy-js-client";
import { i18n } from "../../i18next";
import { UserService } from "../../services";
import {
amAdmin,
amMod,
amTopMod,
getUnixTime,
hostname,
mdToHtml,
myAuthRequired,
} from "../../utils";
import { getUnixTime, hostname, mdToHtml, myAuthRequired } from "../../utils";
import { Badges } from "../common/badges";
import { BannerIconHeader } from "../common/banner-icon-header";
import { Icon, PurgeWarning, Spinner } from "../common/icon";

View File

@ -1,5 +1,8 @@
import { getQueryParams, getQueryString } from "@utils/helpers";
import { canCreateCommunity } from "@utils/roles";
import type { QueryParams } from "@utils/types";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent, MouseEventHandler } from "inferno";
import { Component, MouseEventHandler, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import { Link } from "inferno-router";
import {
@ -57,7 +60,7 @@ import { UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
import {
canCreateCommunity,
RouteDataResponse,
commentsToFlatNodes,
editComment,
editPost,
@ -68,16 +71,12 @@ import {
getCommentParentId,
getDataTypeString,
getPageFromString,
getQueryParams,
getQueryString,
getRandomFromList,
mdToHtml,
myAuth,
postToCommentSortType,
QueryParams,
relTags,
restoreScrollPosition,
RouteDataResponse,
saveScrollPosition,
setIsoData,
setupTippy,

View File

@ -1,9 +1,10 @@
import { isBrowser } from "@utils/browser";
import { Component, linkEvent } from "inferno";
import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
import { i18n } from "../../i18next";
import { UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import { isBrowser, myAuth, setIsoData, toast, validEmail } from "../../utils";
import { myAuth, setIsoData, toast, validEmail } from "../../utils";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";

View File

@ -1,3 +1,4 @@
import { isBrowser } from "@utils/browser";
import { Options, passwordStrength } from "check-password-strength";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
@ -13,7 +14,6 @@ import { i18n } from "../../i18next";
import { UserService } from "../../services";
import { HttpService, RequestState } from "../../services/HttpService";
import {
isBrowser,
joinLemmyUrl,
mdToHtml,
myAuth,

View File

@ -1,3 +1,6 @@
import { debounce, getQueryParams, getQueryString } from "@utils/helpers";
import { amAdmin, amMod } from "@utils/roles";
import type { QueryParams } from "@utils/types";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
@ -34,17 +37,11 @@ import { FirstLoadService } from "../services/FirstLoadService";
import { HttpService, RequestState } from "../services/HttpService";
import {
Choice,
QueryParams,
RouteDataResponse,
amAdmin,
amMod,
debounce,
fetchLimit,
fetchUsers,
getIdFromString,
getPageFromString,
getQueryParams,
getQueryString,
getUpdatedSearchId,
myAuth,
personToChoice,

View File

@ -1,3 +1,6 @@
import { getQueryParams, getQueryString } from "@utils/helpers";
import { canMod, isAdmin, isBanned } from "@utils/roles";
import type { QueryParams } from "@utils/types";
import classNames from "classnames";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
@ -53,9 +56,7 @@ import { UserService } from "../../services";
import { FirstLoadService } from "../../services/FirstLoadService";
import { HttpService, RequestState } from "../../services/HttpService";
import {
QueryParams,
RouteDataResponse,
canMod,
capitalizeFirstLetter,
editComment,
editPost,
@ -66,10 +67,6 @@ import {
futureDaysToUnixTime,
getCommentParentId,
getPageFromString,
getQueryParams,
getQueryString,
isAdmin,
isBanned,
mdToHtml,
myAuth,
myAuthRequired,

View File

@ -1,3 +1,4 @@
import { amAdmin } from "@utils/roles";
import { Component, linkEvent } from "inferno";
import {
CommentReportResponse,
@ -24,7 +25,6 @@ import { FirstLoadService } from "../../services/FirstLoadService";
import { RequestState } from "../../services/HttpService";
import {
RouteDataResponse,
amAdmin,
editCommentReport,
editPostReport,
editPrivateMessageReport,

View File

@ -1,3 +1,4 @@
import { debounce } from "@utils/helpers";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
import {
@ -18,7 +19,6 @@ import {
Choice,
capitalizeFirstLetter,
communityToChoice,
debounce,
elementUrl,
emDash,
fetchCommunities,

View File

@ -1,3 +1,5 @@
import { getQueryParams } from "@utils/helpers";
import type { QueryParams } from "@utils/types";
import { Component } from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route";
import {
@ -17,12 +19,10 @@ import {
} from "../../services/HttpService";
import {
Choice,
QueryParams,
RouteDataResponse,
enableDownvotes,
enableNsfw,
getIdFromString,
getQueryParams,
myAuth,
setIsoData,
} from "../../utils";

View File

@ -1,3 +1,4 @@
import { debounce } from "@utils/helpers";
import autosize from "autosize";
import { Component, InfernoNode, linkEvent } from "inferno";
import {
@ -18,7 +19,6 @@ import {
archiveTodayUrl,
capitalizeFirstLetter,
communityToChoice,
debounce,
fetchCommunities,
getIdFromString,
ghostArchiveUrl,

View File

@ -1,3 +1,14 @@
import { canShare, share } from "@utils/browser";
import {
amAdmin,
amCommunityCreator,
amMod,
canAdmin,
canMod,
isAdmin,
isBanned,
isMod,
} from "@utils/roles";
import classNames from "classnames";
import { Component, linkEvent } from "inferno";
import { Link } from "inferno-router";
@ -28,18 +39,9 @@ import { i18n } from "../../i18next";
import { BanType, PostFormParams, PurgeType, VoteType } from "../../interfaces";
import { UserService } from "../../services";
import {
amAdmin,
amCommunityCreator,
amMod,
canAdmin,
canMod,
canShare,
futureDaysToUnixTime,
hostname,
isAdmin,
isBanned,
isImage,
isMod,
isVideo,
mdNoImages,
mdToHtml,
@ -49,7 +51,6 @@ import {
numToSI,
relTags,
setupTippy,
share,
showScores,
} from "../../utils";
import { Icon, PurgeWarning, Spinner } from "../common/icon";

View File

@ -1,3 +1,5 @@
import { isBrowser } from "@utils/browser";
import { debounce } from "@utils/helpers";
import autosize from "autosize";
import { Component, createRef, linkEvent, RefObject } from "inferno";
import {
@ -64,7 +66,6 @@ import {
buildCommentsTree,
commentsToFlatNodes,
commentTreeMaxDepth,
debounce,
editComment,
editWith,
enableDownvotes,
@ -73,7 +74,6 @@ import {
getCommentParentId,
getDepthFromComment,
getIdFromProps,
isBrowser,
isImage,
myAuth,
restoreScrollPosition,

View File

@ -1,3 +1,5 @@
import { debounce, getQueryParams, getQueryString } from "@utils/helpers";
import type { QueryParams } from "@utils/types";
import type { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
import {
@ -26,12 +28,10 @@ import { FirstLoadService } from "../services/FirstLoadService";
import { HttpService, RequestState } from "../services/HttpService";
import {
Choice,
QueryParams,
RouteDataResponse,
capitalizeFirstLetter,
commentsToFlatNodes,
communityToChoice,
debounce,
enableDownvotes,
enableNsfw,
fetchCommunities,
@ -39,8 +39,6 @@ import {
fetchUsers,
getIdFromString,
getPageFromString,
getQueryParams,
getQueryString,
getUpdatedSearchId,
myAuth,
numToSI,

View File

@ -1,4 +1,4 @@
import { isBrowser } from "./utils";
import { isBrowser } from "@utils/browser";
const testHost = "0.0.0.0:8536";

View File

@ -1,3 +1,4 @@
import { isBrowser } from "@utils/browser";
import i18next, { i18nTyped, Resource } from "i18next";
import { UserService } from "./services";
import { ar } from "./translations/ar";
@ -31,7 +32,6 @@ import { sv } from "./translations/sv";
import { vi } from "./translations/vi";
import { zh } from "./translations/zh";
import { zh_Hant } from "./translations/zh_Hant";
import { isBrowser } from "./utils";
export const languages = [
{ resource: ar, code: "ar", name: "العربية" },

View File

@ -1,10 +1,11 @@
// import Cookies from 'js-cookie';
import { isBrowser } from "@utils/browser";
import IsomorphicCookie from "isomorphic-cookie";
import jwt_decode from "jwt-decode";
import { LoginResponse, MyUserInfo } from "lemmy-js-client";
import { isHttps } from "../env";
import { i18n } from "../i18next";
import { isAuthPath, isBrowser, toast } from "../utils";
import { isAuthPath, toast } from "../utils";
interface Claims {
sub: number;

View File

@ -1,3 +1,5 @@
import { isBrowser } from "@utils/browser";
import { debounce, groupBy } from "@utils/helpers";
import { Picker } from "emoji-mart";
import emojiShortName from "emoji-short-name";
import {
@ -9,7 +11,6 @@ import {
CommentReportView,
CommentSortType,
CommentView,
CommunityModeratorView,
CommunityView,
CustomEmojiView,
GetSiteMetadata,
@ -17,7 +18,6 @@ import {
Language,
LemmyHttp,
MyUserInfo,
Person,
PersonMentionView,
PersonView,
PostReportView,
@ -235,92 +235,6 @@ export function futureDaysToUnixTime(days?: number): number | undefined {
: undefined;
}
export function canMod(
creator_id: number,
mods?: CommunityModeratorView[],
admins?: PersonView[],
myUserInfo = UserService.Instance.myUserInfo,
onSelf = false
): boolean {
// You can do moderator actions only on the mods added after you.
let adminsThenMods =
admins
?.map(a => a.person.id)
.concat(mods?.map(m => m.moderator.id) ?? []) ?? [];
if (myUserInfo) {
const myIndex = adminsThenMods.findIndex(
id => id == myUserInfo.local_user_view.person.id
);
if (myIndex == -1) {
return false;
} else {
// onSelf +1 on mod actions not for yourself, IE ban, remove, etc
adminsThenMods = adminsThenMods.slice(0, myIndex + (onSelf ? 0 : 1));
return !adminsThenMods.includes(creator_id);
}
} else {
return false;
}
}
export function canAdmin(
creatorId: number,
admins?: PersonView[],
myUserInfo = UserService.Instance.myUserInfo,
onSelf = false
): boolean {
return canMod(creatorId, undefined, admins, myUserInfo, onSelf);
}
export function isMod(
creatorId: number,
mods?: CommunityModeratorView[]
): boolean {
return mods?.map(m => m.moderator.id).includes(creatorId) ?? false;
}
export function amMod(
mods?: CommunityModeratorView[],
myUserInfo = UserService.Instance.myUserInfo
): boolean {
return myUserInfo ? isMod(myUserInfo.local_user_view.person.id, mods) : false;
}
export function isAdmin(creatorId: number, admins?: PersonView[]): boolean {
return admins?.map(a => a.person.id).includes(creatorId) ?? false;
}
export function amAdmin(myUserInfo = UserService.Instance.myUserInfo): boolean {
return myUserInfo?.local_user_view.person.admin ?? false;
}
export function amCommunityCreator(
creator_id: number,
mods?: CommunityModeratorView[],
myUserInfo = UserService.Instance.myUserInfo
): boolean {
const myId = myUserInfo?.local_user_view.person.id;
// Don't allow mod actions on yourself
return myId == mods?.at(0)?.moderator.id && myId != creator_id;
}
export function amSiteCreator(
creator_id: number,
admins?: PersonView[],
myUserInfo = UserService.Instance.myUserInfo
): boolean {
const myId = myUserInfo?.local_user_view.person.id;
return myId == admins?.at(0)?.person.id && myId != creator_id;
}
export function amTopMod(
mods: CommunityModeratorView[],
myUserInfo = UserService.Instance.myUserInfo
): boolean {
return mods.at(0)?.moderator.id == myUserInfo?.local_user_view.person.id;
}
const imageRegex = /(http)?s?:?(\/\/[^"']*\.(?:jpg|jpeg|gif|png|svg|webp))/;
const videoRegex = /(http)?s?:?(\/\/[^"']*\.(?:mp4|webm))/;
const tldRegex = /([a-z0-9]+\.)*[a-z0-9]+\.[a-z]+/;
@ -371,51 +285,6 @@ export function getDataTypeString(dt: DataType) {
return dt === DataType.Post ? "Post" : "Comment";
}
export function debounce<T extends any[], R>(
func: (...e: T) => R,
wait = 1000,
immediate = false
) {
// 'private' variable for instance
// The returned function will be able to reference this due to closure.
// Each call to the returned function will share this common timer.
let timeout: NodeJS.Timeout | null;
// Calling debounce returns a new anonymous function
return function () {
// reference the context and args for the setTimeout function
const args = arguments;
// Should the function be called now? If immediate is true
// and not already in a timeout then the answer is: Yes
const callNow = immediate && !timeout;
// This is the basic debounce behavior where you can call this
// function several times, but it will only execute once
// [before or after imposing a delay].
// Each time the returned function is called, the timer starts over.
clearTimeout(timeout ?? undefined);
// Set the new timeout
timeout = setTimeout(function () {
// Inside the timeout function, clear the timeout variable
// which will let the next execution run when in 'immediate' mode
timeout = null;
// Check if the function already ran with the immediate flag
if (!immediate) {
// Call the original function with apply
// apply lets you define the 'this' object as well as the arguments
// (both captured before setTimeout)
func.apply(this, args);
}
}, wait);
// Immediate mode and no wait timer? Execute the function..
if (callNow) func.apply(this, args);
} as (...e: T) => R;
}
export async function fetchThemeList(): Promise<string[]> {
return fetch("/css/themelist").then(res => res.json());
}
@ -1153,10 +1022,6 @@ export function siteBannerCss(banner: string): string {
`;
}
export function isBrowser() {
return typeof window !== "undefined";
}
export function setIsoData<T extends RouteData>(context: any): IsoData<T> {
// If its the browser, you need to deserialize the data from the window
if (isBrowser()) {
@ -1286,21 +1151,6 @@ export function numToSI(value: number): string {
return SHORTNUM_SI_FORMAT.format(value);
}
export function isBanned(ps: Person): boolean {
const expires = ps.ban_expires;
// Add Z to convert from UTC date
// TODO this check probably isn't necessary anymore
if (expires) {
if (ps.banned && new Date(expires + "Z") > new Date()) {
return true;
} else {
return false;
}
} else {
return ps.banned;
}
}
export function myAuth(): string | undefined {
return UserService.Instance.auth();
}
@ -1332,15 +1182,6 @@ export function postToCommentSortType(sort: SortType): CommentSortType {
}
}
export function canCreateCommunity(
siteRes: GetSiteResponse,
myUserInfo = UserService.Instance.myUserInfo
): boolean {
const adminOnly = siteRes.site_view.local_site.community_creation_admin_only;
// TODO: Make this check if user is logged on as well
return !adminOnly || amAdmin(myUserInfo);
}
export function isPostBlocked(
pv: PostView,
myUserInfo: MyUserInfo | undefined = UserService.Instance.myUserInfo
@ -1421,64 +1262,12 @@ interface EmojiMartSkin {
src: string;
}
const groupBy = <T>(
array: T[],
predicate: (value: T, index: number, array: T[]) => string
) =>
array.reduce((acc, value, index, array) => {
(acc[predicate(value, index, array)] ||= []).push(value);
return acc;
}, {} as { [key: string]: T[] });
export type QueryParams<T extends Record<string, any>> = {
[key in keyof T]?: string;
};
export function getQueryParams<T extends Record<string, any>>(processors: {
[K in keyof T]: (param: string) => T[K];
}): T {
if (isBrowser()) {
const searchParams = new URLSearchParams(window.location.search);
return Array.from(Object.entries(processors)).reduce(
(acc, [key, process]) => ({
...acc,
[key]: process(searchParams.get(key)),
}),
{} as T
);
}
return {} as T;
}
export function getQueryString<T extends Record<string, string | undefined>>(
obj: T
) {
return Object.entries(obj)
.filter(([, val]) => val !== undefined && val !== null)
.reduce(
(acc, [key, val], index) => `${acc}${index > 0 ? "&" : ""}${key}=${val}`,
"?"
);
}
export function isAuthPath(pathname: string) {
return /create_.*|inbox|settings|admin|reports|registration_applications/g.test(
pathname
);
}
export function canShare() {
return isBrowser() && !!navigator.canShare;
}
export function share(shareData: ShareData) {
if (isBrowser()) {
navigator.share(shareData);
}
}
export function newVote(voteType: VoteType, myVote?: number): number {
if (voteType == VoteType.Upvote) {
return myVote == 1 ? 0 : 1;
@ -1490,18 +1279,3 @@ export function newVote(voteType: VoteType, myVote?: number): number {
export type RouteDataResponse<T extends Record<string, any>> = {
[K in keyof T]: RequestState<T[K]>;
};
function sleep(millis: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, millis));
}
/**
* Polls / repeatedly runs a promise, every X milliseconds
*/
export async function poll(promiseFn: any, millis: number) {
if (window.document.visibilityState !== "hidden") {
await promiseFn();
}
await sleep(millis);
return poll(promiseFn, millis);
}

View File

@ -0,0 +1,5 @@
import { isBrowser } from "@utils/browser";
export default function canShare() {
return isBrowser() && !!navigator.canShare;
}

View File

@ -0,0 +1,5 @@
import canShare from "./can-share";
import isBrowser from "./is-browser";
import share from "./share";
export { canShare, isBrowser, share };

View File

@ -0,0 +1,3 @@
export default function isBrowser() {
return typeof window !== "undefined";
}

View File

@ -0,0 +1,7 @@
import { isBrowser } from "@utils/browser";
export default function share(shareData: ShareData) {
if (isBrowser()) {
navigator.share(shareData);
}
}

View File

@ -0,0 +1,24 @@
export default function debounce<T extends any[], R>(
func: (...e: T) => R,
wait = 1000,
immediate = false
) {
let timeout: NodeJS.Timeout | null;
return function () {
const args = arguments;
const callNow = immediate && !timeout;
clearTimeout(timeout ?? undefined);
timeout = setTimeout(function () {
timeout = null;
if (!immediate) {
func.apply(this, args);
}
}, wait);
if (callNow) func.apply(this, args);
} as (...e: T) => R;
}

View File

@ -0,0 +1,21 @@
import { isBrowser } from "@utils/browser";
export default function getQueryParams<
T extends Record<string, any>
>(processors: {
[K in keyof T]: (param: string) => T[K];
}): T {
if (isBrowser()) {
const searchParams = new URLSearchParams(window.location.search);
return Array.from(Object.entries(processors)).reduce(
(acc, [key, process]) => ({
...acc,
[key]: process(searchParams.get(key)),
}),
{} as T
);
}
return {} as T;
}

View File

@ -0,0 +1,10 @@
export default function getQueryString<
T extends Record<string, string | undefined>
>(obj: T) {
return Object.entries(obj)
.filter(([, val]) => val !== undefined && val !== null)
.reduce(
(acc, [key, val], index) => `${acc}${index > 0 ? "&" : ""}${key}=${val}`,
"?"
);
}

View File

@ -0,0 +1,8 @@
export const groupBy = <T>(
array: T[],
predicate: (value: T, index: number, array: T[]) => string
) =>
array.reduce((acc, value, index, array) => {
(acc[predicate(value, index, array)] ||= []).push(value);
return acc;
}, {} as { [key: string]: T[] });

View File

@ -0,0 +1,8 @@
import debounce from "./debounce";
import getQueryParams from "./get-query-params";
import getQueryString from "./get-query-string";
import { groupBy } from "./group-by";
import poll from "./poll";
import sleep from "./sleep";
export { debounce, getQueryParams, getQueryString, groupBy, poll, sleep };

View File

@ -0,0 +1,12 @@
import sleep from "./sleep";
/**
* Polls / repeatedly runs a promise, every X milliseconds
*/
export default async function poll(promiseFn: any, millis: number) {
if (window.document.visibilityState !== "hidden") {
await promiseFn();
}
await sleep(millis);
return poll(promiseFn, millis);
}

View File

@ -0,0 +1,3 @@
export default function sleep(millis: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, millis));
}

View File

@ -0,0 +1,7 @@
import { UserService } from "../../services";
export default function amAdmin(
myUserInfo = UserService.Instance.myUserInfo
): boolean {
return myUserInfo?.local_user_view.person.admin ?? false;
}

View File

@ -0,0 +1,12 @@
import { CommunityModeratorView } from "lemmy-js-client";
import { UserService } from "../../services";
export default function amCommunityCreator(
creator_id: number,
mods?: CommunityModeratorView[],
myUserInfo = UserService.Instance.myUserInfo
): boolean {
const myId = myUserInfo?.local_user_view.person.id;
// Don't allow mod actions on yourself
return myId == mods?.at(0)?.moderator.id && myId != creator_id;
}

View File

@ -0,0 +1,10 @@
import { isMod } from "@utils/roles";
import { CommunityModeratorView } from "lemmy-js-client";
import { UserService } from "../../services";
export default function amMod(
mods?: CommunityModeratorView[],
myUserInfo = UserService.Instance.myUserInfo
): boolean {
return myUserInfo ? isMod(myUserInfo.local_user_view.person.id, mods) : false;
}

View File

@ -0,0 +1,11 @@
import { PersonView } from "lemmy-js-client";
import { UserService } from "../../services";
export default function amSiteCreator(
creator_id: number,
admins?: PersonView[],
myUserInfo = UserService.Instance.myUserInfo
): boolean {
const myId = myUserInfo?.local_user_view.person.id;
return myId == admins?.at(0)?.person.id && myId != creator_id;
}

View File

@ -0,0 +1,9 @@
import { CommunityModeratorView } from "lemmy-js-client";
import { UserService } from "../../services";
export default function amTopMod(
mods: CommunityModeratorView[],
myUserInfo = UserService.Instance.myUserInfo
): boolean {
return mods.at(0)?.moderator.id == myUserInfo?.local_user_view.person.id;
}

View File

@ -0,0 +1,12 @@
import { canMod } from "@utils/roles";
import { PersonView } from "lemmy-js-client";
import { UserService } from "../../services";
export default function canAdmin(
creatorId: number,
admins?: PersonView[],
myUserInfo = UserService.Instance.myUserInfo,
onSelf = false
): boolean {
return canMod(creatorId, undefined, admins, myUserInfo, onSelf);
}

View File

@ -0,0 +1,12 @@
import { amAdmin } from "@utils/roles";
import { GetSiteResponse } from "lemmy-js-client";
import { UserService } from "../../services";
export default function canCreateCommunity(
siteRes: GetSiteResponse,
myUserInfo = UserService.Instance.myUserInfo
): boolean {
const adminOnly = siteRes.site_view.local_site.community_creation_admin_only;
// TODO: Make this check if user is logged on as well
return !adminOnly || amAdmin(myUserInfo);
}

View File

@ -0,0 +1,31 @@
import { CommunityModeratorView, PersonView } from "lemmy-js-client";
import { UserService } from "../../services";
export default function canMod(
creator_id: number,
mods?: CommunityModeratorView[],
admins?: PersonView[],
myUserInfo = UserService.Instance.myUserInfo,
onSelf = false
): boolean {
// You can do moderator actions only on the mods added after you.
let adminsThenMods =
admins
?.map(a => a.person.id)
.concat(mods?.map(m => m.moderator.id) ?? []) ?? [];
if (myUserInfo) {
const myIndex = adminsThenMods.findIndex(
id => id == myUserInfo.local_user_view.person.id
);
if (myIndex == -1) {
return false;
} else {
// onSelf +1 on mod actions not for yourself, IE ban, remove, etc
adminsThenMods = adminsThenMods.slice(0, myIndex + (onSelf ? 0 : 1));
return !adminsThenMods.includes(creator_id);
}
} else {
return false;
}
}

View File

@ -0,0 +1,25 @@
import amAdmin from "./am-admin";
import amCommunityCreator from "./am-community-creator";
import amMod from "./am-mod";
import amSiteCreator from "./am-site-creator";
import amTopMod from "./am-top-mod";
import canAdmin from "./can-admin";
import canCreateCommunity from "./can-create-community";
import canMod from "./can-mod";
import isAdmin from "./is-admin";
import isBanned from "./is-banned";
import isMod from "./is-mod";
export {
amAdmin,
amCommunityCreator,
amMod,
amSiteCreator,
amTopMod,
canAdmin,
canCreateCommunity,
canMod,
isAdmin,
isBanned,
isMod,
};

View File

@ -0,0 +1,8 @@
import { PersonView } from "lemmy-js-client";
export default function isAdmin(
creatorId: number,
admins?: PersonView[]
): boolean {
return admins?.map(a => a.person.id).includes(creatorId) ?? false;
}

View File

@ -0,0 +1,16 @@
import { Person } from "lemmy-js-client";
export default function isBanned(ps: Person): boolean {
const expires = ps.ban_expires;
// Add Z to convert from UTC date
// TODO this check probably isn't necessary anymore
if (expires) {
if (ps.banned && new Date(expires + "Z") > new Date()) {
return true;
} else {
return false;
}
} else {
return ps.banned;
}
}

View File

@ -0,0 +1,8 @@
import { CommunityModeratorView } from "lemmy-js-client";
export default function isMod(
creatorId: number,
mods?: CommunityModeratorView[]
): boolean {
return mods?.map(m => m.moderator.id).includes(creatorId) ?? false;
}

View File

@ -0,0 +1,3 @@
import { QueryParams } from "./query-params";
export { QueryParams };

View File

@ -0,0 +1,3 @@
export type QueryParams<T extends Record<string, any>> = {
[key in keyof T]?: string;
};

View File

@ -18,7 +18,13 @@
"noImplicitReturns": true,
"experimentalDecorators": true,
"strictNullChecks": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"paths": {
"@utils/roles": ["./shared/utils/roles/index"],
"@utils/browser": ["./shared/utils/browser/index"],
"@utils/helpers": ["./shared/utils/helpers/index"],
"@utils/types": ["./shared/utils/types/index"],
}
},
"include": [
"src/**/*.ts",

View File

@ -5,6 +5,7 @@ const CopyPlugin = require("copy-webpack-plugin");
const RunNodeWebpackPlugin = require("run-node-webpack-plugin");
const merge = require("lodash/merge");
const { ServiceWorkerPlugin } = require("service-worker-webpack");
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
const banner = `
hash:[contentHash], chunkhash:[chunkhash], name:[name], filebase:[base], query:[query], file:[file]
Source code: https://github.com/LemmyNet/lemmy-ui
@ -19,6 +20,7 @@ const base = {
hashFunction: "xxhash64",
},
resolve: {
plugins: [new TsconfigPathsPlugin()],
extensions: [".js", ".jsx", ".ts", ".tsx"],
},
performance: {

View File

@ -2714,7 +2714,7 @@ chalk@^2.0.0, chalk@^2.0.1:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^4.0.0, chalk@^4.0.2:
chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@ -3578,6 +3578,14 @@ enhanced-resolve@^5.14.0:
graceful-fs "^4.2.4"
tapable "^2.2.0"
enhanced-resolve@^5.7.0:
version "5.15.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35"
integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==
dependencies:
graceful-fs "^4.2.4"
tapable "^2.2.0"
entities@^4.2.0, entities@^4.4.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
@ -8686,6 +8694,11 @@ strip-ansi@^7.0.1:
dependencies:
ansi-regex "^6.0.1"
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==
strip-comments@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-comments/-/strip-comments-2.0.1.tgz#4ad11c3fbcac177a67a40ac224ca339ca1c1ba9b"
@ -8938,6 +8951,24 @@ tributejs@^5.1.3:
resolved "https://registry.yarnpkg.com/tributejs/-/tributejs-5.1.3.tgz#980600fc72865be5868893078b4bfde721129eae"
integrity sha512-B5CXihaVzXw+1UHhNFyAwUTMDk1EfoLP5Tj1VhD9yybZ1I8DZJEv8tZ1l0RJo0t0tk9ZhR8eG5tEsaCvRigmdQ==
tsconfig-paths-webpack-plugin@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.0.1.tgz#a24651d0f69668a1abad38d3c2489855c257460d"
integrity sha512-m5//KzLoKmqu2MVix+dgLKq70MnFi8YL8sdzQZ6DblmCdfuq/y3OqvJd5vMndg2KEVCOeNz8Es4WVZhYInteLw==
dependencies:
chalk "^4.1.0"
enhanced-resolve "^5.7.0"
tsconfig-paths "^4.1.2"
tsconfig-paths@^4.1.2:
version "4.2.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c"
integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==
dependencies:
json5 "^2.2.2"
minimist "^1.2.6"
strip-bom "^3.0.0"
tslib@^1.8.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"